From 86af0af0e4161a157aa73b3528fe3a543e2cc20c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 11 Oct 2026 08:48:47 -1000 Subject: [PATCH] [core] Add sum_bytes() and use it for the additive byte checksums components hand-roll (#20507) --- esphome/components/ballu/ballu.cpp | 5 +---- esphome/components/bl0939/bl0939.cpp | 6 +----- esphome/components/bl0940/bl0940.cpp | 8 ++------ esphome/components/bl0942/bl0942.cpp | 9 +++------ esphome/components/cse7761/cse7761.cpp | 16 ++++------------ esphome/components/cse7766/cse7766.cpp | 5 +---- esphome/components/daikin/daikin.cpp | 9 +-------- esphome/components/daikin_arc/daikin_arc.cpp | 5 +---- esphome/components/daikin_brc/daikin_brc.cpp | 6 ++---- esphome/components/daly_bms/daly_bms.cpp | 7 +------ esphome/components/delonghi/delonghi.cpp | 6 ++---- esphome/components/fs3000/fs3000.cpp | 7 +------ .../fujitsu_general/fujitsu_general.cpp | 6 +----- esphome/components/hm3301/hm3301.cpp | 9 +-------- .../improv_serial/improv_serial_component.cpp | 11 ++--------- esphome/components/mhz19/mhz19.cpp | 8 +------- esphome/components/mitsubishi/mitsubishi.cpp | 4 +--- .../mitsubishi_cn105/mitsubishi_cn105.cpp | 3 +-- esphome/components/mk2pvrouter/mk2pvrouter.cpp | 9 +-------- esphome/components/pm1006/pm1006.cpp | 6 +----- esphome/components/pn532_i2c/pn532_i2c.cpp | 6 +----- esphome/components/pn532_spi/pn532_spi.cpp | 8 ++------ esphome/components/pzem004t/pzem004t.cpp | 4 +--- esphome/components/sds011/sds011.cpp | 6 +----- .../components/seeed_mr24hpc1/seeed_mr24hpc1.cpp | 12 +++--------- esphome/components/sm300d2/sm300d2.cpp | 8 +------- esphome/components/sonoff_d1/sonoff_d1.cpp | 8 +------- esphome/components/systa_bus/systa_bus.cpp | 7 +------ esphome/components/tcl112/tcl112.cpp | 4 +--- esphome/components/teleinfo/teleinfo.cpp | 10 +++++----- esphome/components/toshiba/toshiba.cpp | 7 +------ esphome/components/tuya/tuya.cpp | 10 +++------- esphome/components/ufm01/ufm01.cpp | 9 ++------- esphome/components/wts01/wts01.cpp | 5 +---- esphome/core/helpers.h | 8 ++++++++ tests/components/core/helpers_test.cpp | 8 ++++++++ 36 files changed, 69 insertions(+), 196 deletions(-) diff --git a/esphome/components/ballu/ballu.cpp b/esphome/components/ballu/ballu.cpp index 1553f33618..e18325303c 100644 --- a/esphome/components/ballu/ballu.cpp +++ b/esphome/components/ballu/ballu.cpp @@ -146,10 +146,7 @@ bool BalluClimate::on_receive(remote_base::RemoteReceiveData data) { return false; } - uint8_t checksum = 0; - // Calculate checksum and compare with signal value. - for (uint8_t i = 0; i < BALLU_STATE_LENGTH - 1; i++) - checksum += remote_state[i]; + const uint8_t checksum = sum_bytes(remote_state, BALLU_STATE_LENGTH - 1); if (checksum != remote_state[BALLU_STATE_LENGTH - 1]) { ESP_LOGVV(TAG, "Checksum fail"); diff --git a/esphome/components/bl0939/bl0939.cpp b/esphome/components/bl0939/bl0939.cpp index b0b9bb1c17..0e2da59ab1 100644 --- a/esphome/components/bl0939/bl0939.cpp +++ b/esphome/components/bl0939/bl0939.cpp @@ -51,12 +51,8 @@ void BL0939::loop() { } bool BL0939::validate_checksum(const DataPacket *data) { - uint8_t checksum = BL0939_READ_COMMAND; // Whole package but checksum - for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) { - checksum += data->raw[i]; - } - checksum ^= 0xFF; + const uint8_t checksum = sum_bytes(data->raw, sizeof(data->raw) - 1, BL0939_READ_COMMAND) ^ 0xFF; if (checksum != data->checksum) { ESP_LOGW(TAG, "BL0939 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); } diff --git a/esphome/components/bl0940/bl0940.cpp b/esphome/components/bl0940/bl0940.cpp index a8b470de7e..ac1386df03 100644 --- a/esphome/components/bl0940/bl0940.cpp +++ b/esphome/components/bl0940/bl0940.cpp @@ -44,13 +44,9 @@ void BL0940::loop() { } bool BL0940::validate_checksum_(DataPacket *data) { - uint8_t checksum = this->read_command_; // Whole package but checksum - uint8_t *raw = (uint8_t *) data; - for (uint32_t i = 0; i < sizeof(*data) - 1; i++) { - checksum += raw[i]; - } - checksum ^= 0xFF; + const uint8_t checksum = + sum_bytes(reinterpret_cast(data), sizeof(*data) - 1, this->read_command_) ^ 0xFF; if (checksum != data->checksum) { ESP_LOGW(TAG, "Invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); } diff --git a/esphome/components/bl0942/bl0942.cpp b/esphome/components/bl0942/bl0942.cpp index 7d0eff8c9d..7da8dcca54 100644 --- a/esphome/components/bl0942/bl0942.cpp +++ b/esphome/components/bl0942/bl0942.cpp @@ -70,13 +70,10 @@ void BL0942::loop() { } bool BL0942::validate_checksum_(DataPacket *data) { - uint8_t checksum = BL0942_READ_COMMAND | this->address_; // Whole package but checksum - uint8_t *raw = (uint8_t *) data; - for (uint32_t i = 0; i < sizeof(*data) - 1; i++) { - checksum += raw[i]; - } - checksum ^= 0xFF; + const uint8_t checksum = + sum_bytes(reinterpret_cast(data), sizeof(*data) - 1, BL0942_READ_COMMAND | this->address_) ^ + 0xFF; if (checksum != data->checksum) { ESP_LOGW(TAG, "BL0942 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); } diff --git a/esphome/components/cse7761/cse7761.cpp b/esphome/components/cse7761/cse7761.cpp index 695b7f33d6..c4073d2782 100644 --- a/esphome/components/cse7761/cse7761.cpp +++ b/esphome/components/cse7761/cse7761.cpp @@ -81,11 +81,7 @@ void CSE7761Component::write_(uint8_t reg, uint16_t data) { buffer[3] = data & 0xFF; len = 4; } - uint8_t crc = 0; - for (uint32_t i = 0; i < len; i++) { - crc += buffer[i]; - } - buffer[len] = ~crc; + buffer[len] = ~sum_bytes(buffer, len); len++; } @@ -115,16 +111,12 @@ bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) { } rcvd--; + if (static_cast(~sum_bytes(buffer, rcvd, 0xA5 + reg)) != buffer[rcvd]) { + return false; + } uint32_t result = 0; - // CRC check - uint8_t crc = 0xA5 + reg; for (uint32_t i = 0; i < rcvd; i++) { result = (result << 8) | buffer[i]; - crc += buffer[i]; - } - crc = ~crc; - if (crc != buffer[rcvd]) { - return false; } *value = result; diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 9276901156..0bf077b746 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -67,10 +67,7 @@ bool CSE7766Component::check_byte_() { } if (index == CSE7766_RAW_DATA_SIZE - 1) { - uint8_t checksum = 0; - for (uint8_t i = 2; i < CSE7766_RAW_DATA_SIZE - 1; i++) { - checksum += this->raw_data_[i]; - } + const uint8_t checksum = sum_bytes(this->raw_data_ + 2, CSE7766_RAW_DATA_SIZE - 3); if (checksum != this->raw_data_[CSE7766_RAW_DATA_SIZE - 1]) { ESP_LOGW(TAG, "Invalid checksum from CSE7766: 0x%02X != 0x%02X", checksum, diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index 8b8d16677a..370c04fbf6 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -1,6 +1,7 @@ #include "daikin.h" #include "esphome/components/remote_base/remote_base.h" #include "esphome/core/application.h" +#include "esphome/core/helpers.h" namespace esphome::daikin { @@ -8,14 +9,6 @@ ESPHOME_LOG_TAG(TAG, "daikin.climate"); static constexpr uint32_t BOOST_TIMEOUT_ID = 0; -static uint8_t sum_bytes(const uint8_t *data, size_t len) { - uint8_t sum = 0; - for (size_t i = 0; i < len; i++) { - sum += data[i]; - } - return sum; -} - void DaikinClimate::setup() { climate_ir::ClimateIR::setup(); // A boost preset restored from flash gets its timer too diff --git a/esphome/components/daikin_arc/daikin_arc.cpp b/esphome/components/daikin_arc/daikin_arc.cpp index e79135b185..fa01df73d8 100644 --- a/esphome/components/daikin_arc/daikin_arc.cpp +++ b/esphome/components/daikin_arc/daikin_arc.cpp @@ -246,10 +246,7 @@ climate::ClimateTraits DaikinArcClimate::traits() { } bool DaikinArcClimate::parse_state_frame_(const uint8_t frame[]) { - uint8_t checksum = 0; - for (int i = 0; i < (DAIKIN_STATE_FRAME_SIZE - 1); i++) { - checksum += frame[i]; - } + const uint8_t checksum = sum_bytes(frame, DAIKIN_STATE_FRAME_SIZE - 1); if (frame[DAIKIN_STATE_FRAME_SIZE - 1] != checksum) { ESP_LOGI(TAG, "checksum error"); return false; diff --git a/esphome/components/daikin_brc/daikin_brc.cpp b/esphome/components/daikin_brc/daikin_brc.cpp index 5d8ef52dc0..cfb3521a66 100644 --- a/esphome/components/daikin_brc/daikin_brc.cpp +++ b/esphome/components/daikin_brc/daikin_brc.cpp @@ -1,5 +1,6 @@ #include "daikin_brc.h" #include "esphome/components/remote_base/remote_base.h" +#include "esphome/core/helpers.h" namespace esphome::daikin_brc { @@ -160,10 +161,7 @@ uint8_t DaikinBrcClimate::temperature_() { } bool DaikinBrcClimate::parse_state_frame_(const uint8_t frame[]) { - uint8_t checksum = 0; - for (int i = 0; i < (DAIKIN_BRC_STATE_FRAME_SIZE - 1); i++) { - checksum += frame[i]; - } + const uint8_t checksum = sum_bytes(frame, DAIKIN_BRC_STATE_FRAME_SIZE - 1); if (frame[DAIKIN_BRC_STATE_FRAME_SIZE - 1] != checksum) { ESP_LOGCONFIG(TAG, "Bad CheckSum %x", checksum); return false; diff --git a/esphome/components/daly_bms/daly_bms.cpp b/esphome/components/daly_bms/daly_bms.cpp index a486037266..04ef798b69 100644 --- a/esphome/components/daly_bms/daly_bms.cpp +++ b/esphome/components/daly_bms/daly_bms.cpp @@ -129,12 +129,7 @@ void DalyBmsComponent::decode_data_(std::vector data) { while ((it = std::find(it, data.end(), 0xA5)) != data.end()) { if (data.end() - it >= DALY_FRAME_SIZE && it[1] == 0x01) { - uint8_t checksum; - int sum = 0; - for (int i = 0; i < 12; i++) { - sum += it[i]; - } - checksum = sum; + const uint8_t checksum = sum_bytes(&*it, 12); if (checksum == it[12]) { switch (it[2]) { diff --git a/esphome/components/delonghi/delonghi.cpp b/esphome/components/delonghi/delonghi.cpp index eec4d7f55b..3ed794daea 100644 --- a/esphome/components/delonghi/delonghi.cpp +++ b/esphome/components/delonghi/delonghi.cpp @@ -1,5 +1,6 @@ #include "delonghi.h" #include "esphome/components/remote_base/remote_base.h" +#include "esphome/core/helpers.h" namespace esphome::delonghi { @@ -102,10 +103,7 @@ uint8_t DelonghiClimate::temperature_() { } bool DelonghiClimate::parse_state_frame_(const uint8_t frame[]) { - uint8_t checksum = 0; - for (int i = 0; i < (DELONGHI_STATE_FRAME_SIZE - 1); i++) { - checksum += frame[i]; - } + const uint8_t checksum = sum_bytes(frame, DELONGHI_STATE_FRAME_SIZE - 1); if (frame[DELONGHI_STATE_FRAME_SIZE - 1] != checksum) { return false; } diff --git a/esphome/components/fs3000/fs3000.cpp b/esphome/components/fs3000/fs3000.cpp index 81d6647642..b5e1c98bcb 100644 --- a/esphome/components/fs3000/fs3000.cpp +++ b/esphome/components/fs3000/fs3000.cpp @@ -42,12 +42,7 @@ void FS3000Component::update() { } // checksum passes if the modulo 256 sum of the five bytes is 0 - uint8_t checksum = 0; - for (uint8_t i : data) { - checksum += i; - } - - if (checksum != 0) { + if (sum_bytes(data, sizeof(data)) != 0) { this->status_set_warning(); ESP_LOGW(TAG, "Checksum failure when reading from FS3000"); return; diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index 9a928dac01..af411d133a 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -224,11 +224,7 @@ void FujitsuGeneralClimate::transmit_(uint8_t const *message, uint8_t length) { } uint8_t FujitsuGeneralClimate::checksum_state_(uint8_t const *message) { - uint8_t checksum = 0; - for (uint8_t i = 7; i < FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1; ++i) { - checksum += message[i]; - } - return 256 - checksum; + return -sum_bytes(message + 7, FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 8); } uint8_t FujitsuGeneralClimate::checksum_util_(uint8_t const *message) { return 255 - message[5]; } diff --git a/esphome/components/hm3301/hm3301.cpp b/esphome/components/hm3301/hm3301.cpp index bfed99b21b..ddfe77d3ac 100644 --- a/esphome/components/hm3301/hm3301.cpp +++ b/esphome/components/hm3301/hm3301.cpp @@ -80,14 +80,7 @@ void HM3301Component::update() { this->status_clear_warning(); } -bool HM3301Component::validate_checksum_(const uint8_t *data) { - uint8_t sum = 0; - for (int i = 0; i < 28; i++) { - sum += data[i]; - } - - return sum == data[28]; -} +bool HM3301Component::validate_checksum_(const uint8_t *data) { return sum_bytes(data, 28) == data[28]; } uint16_t HM3301Component::get_sensor_value_(const uint8_t *data, uint8_t i) { return (uint16_t) data[i * 2] << 8 | data[i * 2 + 1]; diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index 1045467866..906781e17b 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -95,16 +95,9 @@ void ImprovSerialComponent::write_data_(const uint8_t *data, const size_t size) const uint8_t header_checksum_len = there_is_data ? TX_BUFFER_SIZE - 3 : TX_BUFFER_SIZE - 2; // Only transmit the full buffer length if there is no data (only state/error byte is provided in this case) const uint8_t header_tx_len = there_is_data ? TX_BUFFER_SIZE - 3 : TX_BUFFER_SIZE; - // Calculate checksum for message - uint8_t checksum = 0; - for (uint8_t i = 0; i < header_checksum_len; i++) { - checksum += this->tx_header_[i]; - } + uint8_t checksum = sum_bytes(this->tx_header_, header_checksum_len); if (there_is_data) { - // Include data in checksum - for (size_t i = 0; i < size; i++) { - checksum += data[i]; - } + checksum = sum_bytes(data, size, checksum); } this->tx_header_[TX_CHECKSUM_IDX] = checksum; diff --git a/esphome/components/mhz19/mhz19.cpp b/esphome/components/mhz19/mhz19.cpp index 5be6da88ef..0f623bfd72 100644 --- a/esphome/components/mhz19/mhz19.cpp +++ b/esphome/components/mhz19/mhz19.cpp @@ -31,13 +31,7 @@ static const LogString *detection_range_to_log_string(MHZ19DetectionRange range) } #endif -uint8_t mhz19_checksum(const uint8_t *command) { - uint8_t sum = 0; - for (uint8_t i = 1; i < MHZ19_REQUEST_LENGTH; i++) { - sum += command[i]; - } - return 0xFF - sum + 0x01; -} +uint8_t mhz19_checksum(const uint8_t *command) { return -sum_bytes(command + 1, MHZ19_REQUEST_LENGTH - 1); } void MHZ19Component::setup() { if (this->abc_boot_logic_ == MHZ19_ABC_ENABLED) { diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index 515cd81fdf..9fba222d63 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -242,9 +242,7 @@ void MitsubishiClimate::transmit_state() { } // Checksum - for (int i = 0; i < 17; i++) { - remote_state[17] += remote_state[i]; - } + remote_state[17] = sum_bytes(remote_state, 17); ESP_LOGD(TAG, "sending: %02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X,%02X", remote_state[0], remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp b/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp index c6c7b57c5f..3261d9d569 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include "mitsubishi_cn105_properties.h" namespace esphome::mitsubishi_cn105 { @@ -31,7 +30,7 @@ static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_REQUEST = 0x41; static constexpr uint8_t PACKET_TYPE_WRITE_SETTINGS_RESPONSE = 0x61; static constexpr uint8_t checksum(const uint8_t *bytes, size_t length) { - return static_cast(0xFC - std::accumulate(bytes, bytes + length, uint8_t{0})); + return static_cast(0xFC - sum_bytes(bytes, length)); } template diff --git a/esphome/components/mk2pvrouter/mk2pvrouter.cpp b/esphome/components/mk2pvrouter/mk2pvrouter.cpp index eecaa1a1d4..b759758019 100644 --- a/esphome/components/mk2pvrouter/mk2pvrouter.cpp +++ b/esphome/components/mk2pvrouter/mk2pvrouter.cpp @@ -36,14 +36,7 @@ static size_t get_field(char *dest, const char *buf_start, const char *buf_end, // Calculates the CRC (checksum) for a given group of characters. uint8_t Mk2PVRouter::calculate_crc_(const char *grp, size_t grp_len) { - uint8_t crc_tmp{0}; - const auto effective_len = grp_len - CRC_SUFFIX_LEN; - for (size_t i = 0; i < effective_len; i++) { - crc_tmp += grp[i]; - } - crc_tmp &= CRC_MASK; - crc_tmp += CRC_OFFSET; - return crc_tmp; + return (sum_bytes(reinterpret_cast(grp), grp_len - CRC_SUFFIX_LEN) & CRC_MASK) + CRC_OFFSET; } // Verifies the CRC of a group against its trailing CRC byte. diff --git a/esphome/components/pm1006/pm1006.cpp b/esphome/components/pm1006/pm1006.cpp index ebd508b535..2f57c02843 100644 --- a/esphome/components/pm1006/pm1006.cpp +++ b/esphome/components/pm1006/pm1006.cpp @@ -43,11 +43,7 @@ void PM1006Component::loop() { } uint8_t PM1006Component::pm1006_checksum_(const uint8_t *command_data, uint8_t length) const { - uint8_t sum = 0; - for (uint8_t i = 0; i < length; i++) { - sum += command_data[i]; - } - return sum; + return sum_bytes(command_data, length); } optional PM1006Component::check_byte_() const { diff --git a/esphome/components/pn532_i2c/pn532_i2c.cpp b/esphome/components/pn532_i2c/pn532_i2c.cpp index 1687edd511..5326aeb145 100644 --- a/esphome/components/pn532_i2c/pn532_i2c.cpp +++ b/esphome/components/pn532_i2c/pn532_i2c.cpp @@ -71,11 +71,7 @@ bool PN532I2C::read_response(uint8_t command, pn532::PN532Frame &data) { // frame: status, preamble, start code (2), LEN, LCS, TFI, command response code, data, DCS, postamble constexpr size_t tfi_offset = 6; - uint8_t checksum = 0; - for (size_t i = 0; i < len + 1U; i++) { - checksum += data[tfi_offset + i]; - } - checksum = ~checksum + 1; + const uint8_t checksum = -sum_bytes(data.data() + tfi_offset, len + 1U); if (data[tfi_offset + len + 1] != checksum) { ESP_LOGV(TAG, "read data invalid checksum! %02X != %02X", data[tfi_offset + len + 1], checksum); diff --git a/esphome/components/pn532_spi/pn532_spi.cpp b/esphome/components/pn532_spi/pn532_spi.cpp index e86153be86..34c6a3e44f 100644 --- a/esphome/components/pn532_spi/pn532_spi.cpp +++ b/esphome/components/pn532_spi/pn532_spi.cpp @@ -127,12 +127,8 @@ bool PN532Spi::read_response(uint8_t command, pn532::PN532Frame &data) { ESP_LOGV(TAG, "Response data: %s", format_hex_pretty_to(hex_buf, sizeof(hex_buf), data.data(), data.size())); - uint8_t checksum = header[5] + header[6]; // TFI + Command response code - for (int i = 0; i < len - 1; i++) { - uint8_t dat = data[i]; - checksum += dat; - } - checksum = ~checksum + 1; + // TFI + Command response code + data + const uint8_t checksum = -sum_bytes(data.data(), len - 1, header[5] + header[6]); if (data[len - 1] != checksum) { ESP_LOGV(TAG, "read data invalid checksum! %02X != %02X", data[len - 1], checksum); diff --git a/esphome/components/pzem004t/pzem004t.cpp b/esphome/components/pzem004t/pzem004t.cpp index 7809e803c9..606a03e0c2 100644 --- a/esphome/components/pzem004t/pzem004t.cpp +++ b/esphome/components/pzem004t/pzem004t.cpp @@ -34,9 +34,7 @@ void PZEM004T::loop() { // 1-5: data // 6: checksum (sum of other bytes) // see https://github.com/olehs/PZEM004T - uint8_t sum = 0; - for (int i = 0; i < 6; i++) - sum += resp[i]; + const uint8_t sum = sum_bytes(resp.data(), 6); if (sum != resp[6]) { ESP_LOGV(TAG, "PZEM004T invalid checksum! 0x%02X != 0x%02X", sum, resp[6]); diff --git a/esphome/components/sds011/sds011.cpp b/esphome/components/sds011/sds011.cpp index 3d46f0613e..147365251b 100644 --- a/esphome/components/sds011/sds011.cpp +++ b/esphome/components/sds011/sds011.cpp @@ -115,11 +115,7 @@ void SDS011Component::sds011_write_command_(const uint8_t *command_data) { } uint8_t SDS011Component::sds011_checksum_(const uint8_t *command_data, uint8_t length) const { - uint8_t sum = 0; - for (uint8_t i = 0; i < length; i++) { - sum += command_data[i]; - } - return sum; + return sum_bytes(command_data, length); } optional SDS011Component::check_byte_() const { diff --git a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp index 31c8e89af3..1bc3e0aeb4 100644 --- a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp +++ b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp @@ -251,17 +251,11 @@ void MR24HPC1Component::loop() { } } -// Calculate CRC check digit -static uint8_t get_frame_crc_sum(const uint8_t *data, int len) { - unsigned int crc_sum = 0; - for (int i = 0; i < len - 3; i++) { - crc_sum += data[i]; - } - return crc_sum & 0xff; -} +// Frames are at least 10 bytes, so the three trailing bytes can always be left out +static uint8_t get_frame_crc_sum(const uint8_t *data, size_t len) { return sum_bytes(data, len - 3); } // Check that the check digit is correct -static int get_frame_check_status(uint8_t *data, int len) { +static int get_frame_check_status(const uint8_t *data, size_t len) { uint8_t crc_sum = get_frame_crc_sum(data, len); uint8_t verified = data[len - 3]; return (verified == crc_sum) ? 1 : 0; diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp index 0660e0f20e..a7959a0999 100644 --- a/esphome/components/sm300d2/sm300d2.cpp +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -83,13 +83,7 @@ void SM300D2Sensor::update() { this->humidity_sensor_->publish_state(humidity); } -uint16_t SM300D2Sensor::sm300d2_checksum_(uint8_t *ptr) { - uint8_t sum = 0; - for (int i = 0; i < (SM300D2_RESPONSE_LENGTH - 1); i++) { - sum += *ptr++; - } - return sum; -} +uint16_t SM300D2Sensor::sm300d2_checksum_(uint8_t *ptr) { return sum_bytes(ptr, SM300D2_RESPONSE_LENGTH - 1); } void SM300D2Sensor::dump_config() { ESP_LOGCONFIG(TAG, "SM300D2:"); diff --git a/esphome/components/sonoff_d1/sonoff_d1.cpp b/esphome/components/sonoff_d1/sonoff_d1.cpp index 02282fef9c..c72210e932 100644 --- a/esphome/components/sonoff_d1/sonoff_d1.cpp +++ b/esphome/components/sonoff_d1/sonoff_d1.cpp @@ -52,13 +52,7 @@ ESPHOME_LOG_TAG(TAG, "sonoff_d1"); static constexpr size_t SONOFF_D1_ACK_SIZE = 7; static constexpr size_t SONOFF_D1_MAX_CMD_SIZE = 17; -uint8_t SonoffD1Output::calc_checksum_(const uint8_t *cmd, const size_t len) { - uint8_t crc = 0; - for (size_t i = 2; i < len - 1; i++) { - crc += cmd[i]; - } - return crc; -} +uint8_t SonoffD1Output::calc_checksum_(const uint8_t *cmd, const size_t len) { return sum_bytes(cmd + 2, len - 3); } void SonoffD1Output::populate_checksum_(uint8_t *cmd, const size_t len) { // Update the checksum diff --git a/esphome/components/systa_bus/systa_bus.cpp b/esphome/components/systa_bus/systa_bus.cpp index 4c537b75cb..6c780d07a9 100644 --- a/esphome/components/systa_bus/systa_bus.cpp +++ b/esphome/components/systa_bus/systa_bus.cpp @@ -10,12 +10,7 @@ ESPHOME_LOG_TAG(TAG, "systa_bus"); void SystaBus::dump_config() { ESP_LOGCONFIG(TAG, "SystaBus:"); } -static bool checksum(std::span data) { - uint8_t csum = 0; - for (uint8_t i : data) - csum += i; - return csum == 0; -} +static bool checksum(std::span data) { return sum_bytes(data.data(), data.size()) == 0; } void SystaBus::loop() { uint8_t c; diff --git a/esphome/components/tcl112/tcl112.cpp b/esphome/components/tcl112/tcl112.cpp index 5e7f0ef29c..15111338b2 100644 --- a/esphome/components/tcl112/tcl112.cpp +++ b/esphome/components/tcl112/tcl112.cpp @@ -168,11 +168,9 @@ bool Tcl112Climate::on_receive(remote_base::RemoteReceiveData data) { return false; } - uint8_t checksum = 0; // Calculate & set the checksum for the current internal state of the remote. // Stored the checksum value in the last byte. - for (uint8_t checksum_byte = 0; checksum_byte < TCL112_STATE_LENGTH - 1; checksum_byte++) - checksum += remote_state[checksum_byte]; + const uint8_t checksum = sum_bytes(remote_state, TCL112_STATE_LENGTH - 1); if (checksum != remote_state[TCL112_STATE_LENGTH - 1]) { ESP_LOGVV(TAG, "Checksum fail"); return false; diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index adf5670784..91a0d0e16f 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -24,12 +24,12 @@ static int get_field(char *dest, char *buf_start, char *buf_end, int sep, int ma /* TeleInfo methods */ bool TeleInfo::check_crc_(const char *grp, const char *grp_end) { int grp_len = grp_end - grp; + if (grp_len < this->checksum_area_end_) { + ESP_LOGE(TAG, "Empty or too short group"); + return false; + } uint8_t raw_crc = grp[grp_len - 1]; - uint8_t crc_tmp = 0; - int i; - - for (i = 0; i < grp_len - checksum_area_end_; i++) - crc_tmp += grp[i]; + uint8_t crc_tmp = sum_bytes(reinterpret_cast(grp), grp_len - this->checksum_area_end_); crc_tmp &= 0x3F; crc_tmp += 0x20; diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 57361a4a58..406a1bc55d 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -1052,8 +1052,6 @@ bool ToshibaClimate::compare_rac_pt1411hwru_packets_(const uint8_t *message1, co } bool ToshibaClimate::is_valid_rac_pt1411hwru_message_(const uint8_t *message) { - uint8_t checksum = 0; - switch (this->is_valid_rac_pt1411hwru_header_(message)) { case RAC_PT1411HWRU_MESSAGE_HEADER0: case RAC_PT1411HWRU_CS_HEADER: @@ -1065,10 +1063,7 @@ bool ToshibaClimate::is_valid_rac_pt1411hwru_message_(const uint8_t *message) { break; case RAC_PT1411HWRU_MESSAGE_HEADER1: - for (uint8_t i = 0; i < RAC_PT1411HWRU_MESSAGE_LENGTH - 1; i++) { - checksum += message[i]; - } - if (checksum == message[RAC_PT1411HWRU_MESSAGE_LENGTH - 1]) { + if (sum_bytes(message, RAC_PT1411HWRU_MESSAGE_LENGTH - 1) == message[RAC_PT1411HWRU_MESSAGE_LENGTH - 1]) { return true; } break; diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index c6316f59fc..025b97ee72 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -152,9 +152,7 @@ bool Tuya::validate_message_() { // Byte 6+LEN: CHECKSUM - sum of all bytes (including header) modulo 256 uint8_t rx_checksum = new_byte; - uint8_t calc_checksum = 0; - for (uint32_t i = 0; i < 6 + length; i++) - calc_checksum += data[i]; + const uint8_t calc_checksum = sum_bytes(data, 6 + length); if (rx_checksum != calc_checksum) { ESP_LOGW(TAG, "Tuya Received invalid message checksum %02X!=%02X", rx_checksum, calc_checksum); @@ -531,10 +529,8 @@ void Tuya::send_raw_command_(TuyaCommand command) { if (!command.payload.empty()) this->write_array(command.payload.data(), command.payload.size()); - uint8_t checksum = 0x55 + 0xAA + (uint8_t) command.cmd + len_hi + len_lo; - for (auto &data : command.payload) - checksum += data; - this->write_byte(checksum); + const uint8_t header_sum = 0x55 + 0xAA + (uint8_t) command.cmd + len_hi + len_lo; + this->write_byte(sum_bytes(command.payload.data(), command.payload.size(), header_sum)); } void Tuya::process_command_queue_() { diff --git a/esphome/components/ufm01/ufm01.cpp b/esphome/components/ufm01/ufm01.cpp index cd4388372c..c68842ab6c 100644 --- a/esphome/components/ufm01/ufm01.cpp +++ b/esphome/components/ufm01/ufm01.cpp @@ -68,9 +68,7 @@ static bool check_byte(const uint8_t data[FRAME_SIZE], size_t index, uint8_t exp } static bool validate_active_frame(const uint8_t data[FRAME_SIZE]) { - uint8_t sum = 0; - for (size_t i = 0; i < FRAME_CHECKSUM_INDEX; ++i) - sum += data[i]; + const uint8_t sum = sum_bytes(data, FRAME_CHECKSUM_INDEX); return check_byte(data, 0, FRAME_START_BYTE_1, "start byte 1") && check_byte(data, 1, FRAME_START_BYTE_2, "start byte 2") && check_byte(data, FRAME_INDEX_INSTANT_FLOW_FLAG, FRAME_FLAG_INSTANT_FLOW, "instant flow flag") && @@ -83,10 +81,7 @@ static bool validate_active_frame(const uint8_t data[FRAME_SIZE]) { static bool validate_passive_frame(const uint8_t data[PASSIVE_FRAME_SIZE]) { if (data[0] != FRAME_START_BYTE_1 || data[1] != PASSIVE_START_BYTE_2 || data[22] != FRAME_STOP_BYTE) return false; - uint8_t sum = 0; - for (size_t i = 0; i < 21; ++i) - sum += data[i]; - return data[21] == (sum & 0xFF); + return data[21] == sum_bytes(data, 21); } static void passive_no_id_to_active_frame(const uint8_t passive[PASSIVE_FRAME_SIZE], uint8_t active[FRAME_SIZE]) { diff --git a/esphome/components/wts01/wts01.cpp b/esphome/components/wts01/wts01.cpp index 2888fc0abb..b7b9e5cfa9 100644 --- a/esphome/components/wts01/wts01.cpp +++ b/esphome/components/wts01/wts01.cpp @@ -58,10 +58,7 @@ void WTS01Sensor::process_packet_() { // Based on Tasmota implementation // Format: 55 01 01 04 01 11 16 12 95 // header T Td Ck - T = Temperature, Td = Temperature decimal, Ck = Checksum - uint8_t calculated_checksum = 0; - for (uint8_t i = 0; i < PACKET_SIZE - 1; i++) { - calculated_checksum += this->buffer_[i]; - } + const uint8_t calculated_checksum = sum_bytes(this->buffer_, PACKET_SIZE - 1); uint8_t received_checksum = this->buffer_[PACKET_SIZE - 1]; if (calculated_checksum != received_checksum) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index a2ede27341..c54d1bc16f 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -879,6 +880,13 @@ template T remap(U value, U min, U max, T min_out, T max return (value - min) * (max_out - min_out) / (max - min) + min_out; } +/// Add up the bytes of \p data with size \p len, truncated to 8 bits: the checksum many serial and IR +/// protocols use. Prefer this over a bare std::accumulate, whose result type follows the seed, so a plain 0 +/// sums in int and compares wrong against a checksum byte. +constexpr uint8_t sum_bytes(const uint8_t *data, size_t len, uint8_t sum = 0) { + return std::accumulate(data, data + len, sum); +} + /// Calculate a CRC-8 checksum of \p data with size \p len. uint8_t crc8(const uint8_t *data, uint8_t len, uint8_t crc = 0x00, uint8_t poly = 0x8C, bool msb_first = false); diff --git a/tests/components/core/helpers_test.cpp b/tests/components/core/helpers_test.cpp index 521ae10e5c..d0058a634b 100644 --- a/tests/components/core/helpers_test.cpp +++ b/tests/components/core/helpers_test.cpp @@ -55,6 +55,14 @@ TEST(HelpersTest, Ilog10RoundTripMatchesLog10) { } } +TEST(HelpersTest, SumBytes) { + static constexpr uint8_t FRAME[] = {0x11, 0xDA, 0x27, 0x00, 0xC5, 0x00, 0x10}; + EXPECT_EQ(sum_bytes(FRAME, sizeof(FRAME)), 0xE7); // the Daikin comfort frame checksum + EXPECT_EQ(sum_bytes(FRAME, 0), 0); + EXPECT_EQ(sum_bytes(FRAME, sizeof(FRAME), 0x19), 0x00); // wraps at 8 bits + static_assert(sum_bytes(FRAME, 2) == 0xEB, "usable in constant expressions"); +} + TEST(StaticVectorTest, ConvertingConstructorFromSmaller) { StaticVector small{0x03, 0x00, 0x10, 0x00, 0x01}; StaticVector big = small;