From 18120cc4a894ebb92df99396ec92bd63d147a89b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 20 Oct 2026 02:45:03 -1000 Subject: [PATCH] [airton][ballu][delonghi][hitachi_ac424][mitsubishi] Send and read IR frame bytes with the remote_base helpers (#21169) --- esphome/components/airton/airton.cpp | 22 ++------- esphome/components/airton/airton.h | 4 +- esphome/components/ballu/ballu.cpp | 31 +++---------- esphome/components/delonghi/delonghi.cpp | 30 ++---------- esphome/components/delonghi/delonghi.h | 4 +- .../hitachi_ac424/hitachi_ac424.cpp | 24 ++-------- .../components/hitachi_ac424/hitachi_ac424.h | 4 +- esphome/components/mitsubishi/mitsubishi.cpp | 46 ++++++------------- 8 files changed, 42 insertions(+), 123 deletions(-) diff --git a/esphome/components/airton/airton.cpp b/esphome/components/airton/airton.cpp index a20ddc510c2..dffb4bc4961 100644 --- a/esphome/components/airton/airton.cpp +++ b/esphome/components/airton/airton.cpp @@ -136,13 +136,7 @@ void AirtonClimate::transmit_state() { data->set_carrier_frequency(AIRTON_IR_FREQUENCY); data->mark(AIRTON_HEADER_MARK); data->space(AIRTON_HEADER_SPACE); - for (uint8_t payload_byte : remote_state) { - for (uint8_t payload_bit_cursor = 0; payload_bit_cursor < 8; payload_bit_cursor++) { - data->mark(AIRTON_BIT_MARK); - bool bit = payload_byte & (1 << payload_bit_cursor); - data->space(bit ? AIRTON_ONE_SPACE : AIRTON_ZERO_SPACE); - } - } + data->item_bytes(remote_state, AIRTON_STATE_FRAME_SIZE); data->mark(AIRTON_BIT_MARK); data->space(AIRTON_MESSAGE_SPACE); @@ -284,20 +278,14 @@ void AirtonClimate::parse_state_frame_(const uint8_t *frame) { } bool AirtonClimate::on_receive(remote_base::RemoteReceiveData data) { - uint8_t remote_state[AIRTON_STATE_FRAME_SIZE] = {}; + uint8_t remote_state[AIRTON_STATE_FRAME_SIZE]; if (!data.expect_item(AIRTON_HEADER_MARK, AIRTON_HEADER_SPACE)) { ESP_LOGV(TAG, "Wrong header encoding detected!"); return false; } - for (int i = 0; i < AIRTON_STATE_FRAME_SIZE; i++) { - for (int j = 0; j < 8; j++) { - if (data.expect_item(AIRTON_BIT_MARK, AIRTON_ONE_SPACE)) { - remote_state[i] |= 1 << j; - } else if (!data.expect_item(AIRTON_BIT_MARK, AIRTON_ZERO_SPACE)) { - ESP_LOGV(TAG, "Wrong modulation encoding for: Byte %d, bit %d", i, j); - return false; - } - } + if (!data.expect_bytes(remote_state, AIRTON_STATE_FRAME_SIZE)) { + ESP_LOGV(TAG, "Wrong modulation encoding"); + return false; } if (remote_state[0] != AIRTON_HEADER_BYTE0 || remote_state[1] != AIRTON_HEADER_BYTE1) { diff --git a/esphome/components/airton/airton.h b/esphome/components/airton/airton.h index 46762f421a8..25f15372e67 100644 --- a/esphome/components/airton/airton.h +++ b/esphome/components/airton/airton.h @@ -36,8 +36,8 @@ static constexpr uint32_t AIRTON_IR_FREQUENCY = 38000; static constexpr uint32_t AIRTON_HEADER_MARK = 6630; static constexpr uint32_t AIRTON_HEADER_SPACE = 3350; static constexpr uint32_t AIRTON_BIT_MARK = 400; -static constexpr uint32_t AIRTON_ONE_SPACE = 1260; -static constexpr uint32_t AIRTON_ZERO_SPACE = 430; +static constexpr remote_base::BitTimings AIRTON_BIT_TIMINGS{ + .bit_mark = AIRTON_BIT_MARK, .one_space = 1260, .zero_space = 430}; static constexpr uint32_t AIRTON_MESSAGE_SPACE = 100000; static constexpr uint8_t AIRTON_STATE_FRAME_SIZE = 7; diff --git a/esphome/components/ballu/ballu.cpp b/esphome/components/ballu/ballu.cpp index e18325303c8..0469ae453ca 100644 --- a/esphome/components/ballu/ballu.cpp +++ b/esphome/components/ballu/ballu.cpp @@ -8,8 +8,8 @@ ESPHOME_LOG_TAG(TAG, "ballu.climate"); const uint16_t BALLU_HEADER_MARK = 9000; const uint16_t BALLU_HEADER_SPACE = 4500; const uint16_t BALLU_BIT_MARK = 575; -const uint16_t BALLU_ONE_SPACE = 1675; -const uint16_t BALLU_ZERO_SPACE = 550; +static constexpr remote_base::BitTimings BALLU_BIT_TIMINGS{ + .bit_mark = BALLU_BIT_MARK, .one_space = 1675, .zero_space = 550}; const uint32_t BALLU_CARRIER_FREQUENCY = 38000; @@ -104,13 +104,7 @@ void BalluClimate::transmit_state() { data->mark(BALLU_HEADER_MARK); data->space(BALLU_HEADER_SPACE); // Data - for (uint8_t i : remote_state) { - for (uint8_t j = 0; j < 8; j++) { - data->mark(BALLU_BIT_MARK); - bool bit = i & (1 << j); - data->space(bit ? BALLU_ONE_SPACE : BALLU_ZERO_SPACE); - } - } + data->item_bytes(remote_state, BALLU_STATE_LENGTH); // Footer data->mark(BALLU_BIT_MARK); @@ -124,21 +118,10 @@ bool BalluClimate::on_receive(remote_base::RemoteReceiveData data) { return false; } - uint8_t remote_state[BALLU_STATE_LENGTH] = {0}; - // Read all bytes. - for (int i = 0; i < BALLU_STATE_LENGTH; i++) { - // Read bit - for (int j = 0; j < 8; j++) { - if (data.expect_item(BALLU_BIT_MARK, BALLU_ONE_SPACE)) { - remote_state[i] |= 1 << j; - - } else if (!data.expect_item(BALLU_BIT_MARK, BALLU_ZERO_SPACE)) { - ESP_LOGV(TAG, "Byte %d bit %d fail", i, j); - return false; - } - } - - ESP_LOGVV(TAG, "Byte %d %02X", i, remote_state[i]); + uint8_t remote_state[BALLU_STATE_LENGTH]; + if (!data.expect_bytes(remote_state, BALLU_STATE_LENGTH)) { + ESP_LOGV(TAG, "Data fail"); + return false; } // Validate footer if (!data.expect_mark(BALLU_BIT_MARK)) { diff --git a/esphome/components/delonghi/delonghi.cpp b/esphome/components/delonghi/delonghi.cpp index 3ed794daea0..cb2c5127b0c 100644 --- a/esphome/components/delonghi/delonghi.cpp +++ b/esphome/components/delonghi/delonghi.cpp @@ -23,13 +23,7 @@ void DelonghiClimate::transmit_state() { data->mark(DELONGHI_HEADER_MARK); data->space(DELONGHI_HEADER_SPACE); - for (unsigned char b : remote_state) { - for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask - data->mark(DELONGHI_BIT_MARK); - bool bit = b & mask; - data->space(bit ? DELONGHI_ONE_SPACE : DELONGHI_ZERO_SPACE); - } - } + data->item_bytes(remote_state, DELONGHI_STATE_FRAME_SIZE); data->mark(DELONGHI_BIT_MARK); data->space(0); @@ -155,27 +149,13 @@ bool DelonghiClimate::parse_state_frame_(const uint8_t frame[]) { } bool DelonghiClimate::on_receive(remote_base::RemoteReceiveData data) { - uint8_t state_frame[DELONGHI_STATE_FRAME_SIZE] = {}; + uint8_t state_frame[DELONGHI_STATE_FRAME_SIZE]; if (!data.expect_item(DELONGHI_HEADER_MARK, DELONGHI_HEADER_SPACE)) { return false; } - for (uint8_t pos = 0; pos < DELONGHI_STATE_FRAME_SIZE; pos++) { - uint8_t byte = 0; - for (int8_t bit = 0; bit < 8; bit++) { - if (data.expect_item(DELONGHI_BIT_MARK, DELONGHI_ONE_SPACE)) { - byte |= 1 << bit; - } else if (!data.expect_item(DELONGHI_BIT_MARK, DELONGHI_ZERO_SPACE)) { - return false; - } - } - state_frame[pos] = byte; - if (pos == 0) { - // frame header - if (byte != DELONGHI_ADDRESS) { - return false; - } - } - } + if (!data.expect_bytes(state_frame, DELONGHI_STATE_FRAME_SIZE) || + state_frame[0] != DELONGHI_ADDRESS) + return false; return this->parse_state_frame_(state_frame); } diff --git a/esphome/components/delonghi/delonghi.h b/esphome/components/delonghi/delonghi.h index aee7ceeddaa..ea384a67e71 100644 --- a/esphome/components/delonghi/delonghi.h +++ b/esphome/components/delonghi/delonghi.h @@ -33,8 +33,8 @@ const uint32_t DELONGHI_IR_FREQUENCY = 38000; const uint32_t DELONGHI_HEADER_MARK = 9000; const uint32_t DELONGHI_HEADER_SPACE = 4500; const uint32_t DELONGHI_BIT_MARK = 465; -const uint32_t DELONGHI_ONE_SPACE = 1750; -const uint32_t DELONGHI_ZERO_SPACE = 670; +static constexpr remote_base::BitTimings DELONGHI_BIT_TIMINGS{ + .bit_mark = DELONGHI_BIT_MARK, .one_space = 1750, .zero_space = 670}; // State Frame size const uint8_t DELONGHI_STATE_FRAME_SIZE = 8; diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp index d8f7f661799..229cdad6aa8 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.cpp +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -224,13 +224,7 @@ void HitachiClimate::transmit_state() { // Header data->item(HITACHI_AC424_HDR_MARK, HITACHI_AC424_HDR_SPACE); // Data - for (uint8_t i : remote_state_) { - for (uint8_t j = 0; j < 8; j++) { - data->mark(HITACHI_AC424_BIT_MARK); - bool bit = i & (1 << j); - data->space(bit ? HITACHI_AC424_ONE_SPACE : HITACHI_AC424_ZERO_SPACE); - } - } + data->item_bytes(remote_state_, HITACHI_AC424_STATE_LENGTH); // Footer data->item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_MIN_GAP); } @@ -320,18 +314,10 @@ bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) { return false; } - uint8_t recv_state[HITACHI_AC424_STATE_LENGTH] = {0}; - // Read all bytes. - for (uint8_t pos = 0; pos < HITACHI_AC424_STATE_LENGTH; pos++) { - // Read bit - for (int8_t bit = 0; bit < 8; bit++) { - if (data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ONE_SPACE)) { - recv_state[pos] |= 1 << bit; - } else if (!data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ZERO_SPACE)) { - ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit); - return false; - } - } + uint8_t recv_state[HITACHI_AC424_STATE_LENGTH]; + if (!data.expect_bytes(recv_state, HITACHI_AC424_STATE_LENGTH)) { + ESP_LOGVV(TAG, "Data fail"); + return false; } // Validate footer diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.h b/esphome/components/hitachi_ac424/hitachi_ac424.h index 31efd98c3dd..87ce79cbeb0 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.h +++ b/esphome/components/hitachi_ac424/hitachi_ac424.h @@ -8,8 +8,8 @@ namespace esphome::hitachi_ac424 { const uint16_t HITACHI_AC424_HDR_MARK = 3416; // ac const uint16_t HITACHI_AC424_HDR_SPACE = 1604; // ac const uint16_t HITACHI_AC424_BIT_MARK = 463; -const uint16_t HITACHI_AC424_ONE_SPACE = 1208; -const uint16_t HITACHI_AC424_ZERO_SPACE = 372; +static constexpr remote_base::BitTimings HITACHI_AC424_BIT_TIMINGS{ + .bit_mark = HITACHI_AC424_BIT_MARK, .one_space = 1208, .zero_space = 372}; const uint32_t HITACHI_AC424_MIN_GAP = 100000; // just a guess. const uint16_t HITACHI_AC424_FREQ = 38000; // Hz. diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index 19b8bb3fad3..08b63dd313a 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -40,8 +40,8 @@ const uint8_t MITSUBISHI_NIGHTMODE = 0xC1; // Pulse parameters in usec const uint16_t MITSUBISHI_BIT_MARK = 430; -const uint16_t MITSUBISHI_ONE_SPACE = 1250; -const uint16_t MITSUBISHI_ZERO_SPACE = 390; +static constexpr remote_base::BitTimings MITSUBISHI_BIT_TIMINGS{ + .bit_mark = MITSUBISHI_BIT_MARK, .one_space = 1250, .zero_space = 390}; const uint16_t MITSUBISHI_HEADER_MARK = 3500; const uint16_t MITSUBISHI_HEADER_SPACE = 1700; const uint16_t MITSUBISHI_MIN_GAP = 17500; @@ -102,8 +102,8 @@ void MitsubishiClimate::transmit_state() { // Byte 15: HVAC specfic, i.e. POWERFUL, SMART SET, PLASMA, always 0x00 // Byte 16: Left vane control (for specific models). Constants match right vane controls. // Byte 17: Checksum: SUM[Byte0...Byte16] - uint8_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t remote_state[MITSUBISHI_FRAME_SIZE] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; switch (this->mode) { case climate::CLIMATE_MODE_HEAT: @@ -259,13 +259,7 @@ void MitsubishiClimate::transmit_state() { data->mark(MITSUBISHI_HEADER_MARK); data->space(MITSUBISHI_HEADER_SPACE); // Data - for (uint8_t i : remote_state) { - for (uint8_t j = 0; j < 8; j++) { - data->mark(MITSUBISHI_BIT_MARK); - bool bit = i & (1 << j); - data->space(bit ? MITSUBISHI_ONE_SPACE : MITSUBISHI_ZERO_SPACE); - } - } + data->item_bytes(remote_state, sizeof(remote_state)); // Footer if (r == 0) { data->mark(MITSUBISHI_BIT_MARK); @@ -280,33 +274,21 @@ void MitsubishiClimate::transmit_state() { bool MitsubishiClimate::parse_state_frame_(const uint8_t frame[]) { return false; } bool MitsubishiClimate::on_receive(remote_base::RemoteReceiveData data) { - uint8_t state_frame[18] = {}; + uint8_t state_frame[MITSUBISHI_FRAME_SIZE]; if (!data.expect_item(MITSUBISHI_HEADER_MARK, MITSUBISHI_HEADER_SPACE)) { ESP_LOGV(TAG, "Header fail"); return false; } - for (uint8_t pos = 0; pos < 18; pos++) { - uint8_t byte = 0; - for (int8_t bit = 0; bit < 8; bit++) { - if (data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ONE_SPACE)) { - byte |= 1 << bit; - } else if (!data.expect_item(MITSUBISHI_BIT_MARK, MITSUBISHI_ZERO_SPACE)) { - ESP_LOGV(TAG, "Byte %d bit %d fail", pos, bit); - return false; - } - } - state_frame[pos] = byte; - - // Check Header && Footer. Byte 16 only ever carries the left vane position (bits 3,4,5). - if ((pos == 0 && byte != MITSUBISHI_BYTE00) || (pos == 1 && byte != MITSUBISHI_BYTE01) || - (pos == 2 && byte != MITSUBISHI_BYTE02) || (pos == 3 && byte != MITSUBISHI_BYTE03) || - (pos == 4 && byte != MITSUBISHI_BYTE04) || (pos == 13 && byte != MITSUBISHI_BYTE13) || - (pos == 16 && (byte & ~MITSUBISHI_VERTICAL_VANE_MASK) != 0)) { - ESP_LOGV(TAG, "Bytes 0,1,2,3,4,13 or 16 fail - invalid value"); - return false; - } + // Check Header && Footer. Byte 16 only ever carries the left vane position (bits 3,4,5). + if (!data.expect_bytes(state_frame, sizeof(state_frame)) || + state_frame[0] != MITSUBISHI_BYTE00 || state_frame[1] != MITSUBISHI_BYTE01 || + state_frame[2] != MITSUBISHI_BYTE02 || state_frame[3] != MITSUBISHI_BYTE03 || + state_frame[4] != MITSUBISHI_BYTE04 || state_frame[13] != MITSUBISHI_BYTE13 || + (state_frame[16] & ~MITSUBISHI_VERTICAL_VANE_MASK) != 0) { + ESP_LOGV(TAG, "Bit decode or bytes 0,1,2,3,4,13 or 16 fail"); + return false; } // On/Off and Mode