[toshiba] Move the lookup tables to PROGMEM (#20636)

This commit is contained in:
J. Nick Koston
2026-10-13 11:25:18 -10:00
committed by GitHub
parent 42341d05f9
commit 0dc1d3b5a1
2 changed files with 77 additions and 28 deletions
+22 -28
View File
@@ -3,8 +3,6 @@
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include <vector>
namespace esphome::toshiba {
struct RacPt1411hwruFanSpeed {
@@ -69,8 +67,8 @@ const uint8_t RAC_PT1411HWRU_CS_FOOTER_COOL = 0x72;
const uint8_t RAC_PT1411HWRU_CS_FOOTER_HEAT = 0x7E;
// RAC-PT1411HWRU Swing
const uint8_t RAC_PT1411HWRU_SWING_HEADER = 0xB9;
const std::vector<uint8_t> RAC_PT1411HWRU_SWING_VERTICAL{0xB9, 0x46, 0xF5, 0x0A, 0x04, 0xFB};
const std::vector<uint8_t> RAC_PT1411HWRU_SWING_OFF{0xB9, 0x46, 0xF5, 0x0A, 0x05, 0xFA};
static constexpr uint8_t RAC_PT1411HWRU_SWING_VERTICAL[] PROGMEM = {0xB9, 0x46, 0xF5, 0x0A, 0x04, 0xFB};
static constexpr uint8_t RAC_PT1411HWRU_SWING_OFF[] PROGMEM = {0xB9, 0x46, 0xF5, 0x0A, 0x05, 0xFA};
// RAC-PT1411HWRU Fan speeds
const uint8_t RAC_PT1411HWRU_FAN_OFF = 0x7B;
constexpr RacPt1411hwruFanSpeed RAC_PT1411HWRU_FAN_AUTO{0xBF, 0x66};
@@ -92,12 +90,12 @@ const uint8_t RAC_PT1411HWRU_TEMPERATURE_FAN_ONLY = 0x0E;
// Hence these look-up tables. In addition, the upper nibble is used here for additional
// "negative" and "fractional value" flags as required for some temperatures.
// RAC-PT1411HWRU °C Temperatures (short codes)
const std::vector<uint8_t> RAC_PT1411HWRU_TEMPERATURE_C{0x10, 0x00, 0x01, 0x03, 0x02, 0x06, 0x07, 0x05,
0x04, 0x0C, 0x0D, 0x09, 0x08, 0x0A, 0x0B};
static constexpr uint8_t RAC_PT1411HWRU_TEMPERATURE_C[] PROGMEM = {0x10, 0x00, 0x01, 0x03, 0x02, 0x06, 0x07, 0x05,
0x04, 0x0C, 0x0D, 0x09, 0x08, 0x0A, 0x0B};
// RAC-PT1411HWRU °F Temperatures (short codes)
const std::vector<uint8_t> RAC_PT1411HWRU_TEMPERATURE_F{0x10, 0x30, 0x00, 0x20, 0x01, 0x21, 0x03, 0x23, 0x02,
0x22, 0x06, 0x26, 0x07, 0x05, 0x25, 0x04, 0x24, 0x0C,
0x2C, 0x0D, 0x2D, 0x09, 0x08, 0x28, 0x0A, 0x2A, 0x0B};
static constexpr uint8_t RAC_PT1411HWRU_TEMPERATURE_F[] PROGMEM = {
0x10, 0x30, 0x00, 0x20, 0x01, 0x21, 0x03, 0x23, 0x02, 0x22, 0x06, 0x26, 0x07, 0x05,
0x25, 0x04, 0x24, 0x0C, 0x2C, 0x0D, 0x2D, 0x09, 0x08, 0x28, 0x0A, 0x2A, 0x0B};
// RAS-2819T protocol constants
const uint16_t RAS_2819T_HEADER1 = 0xC23D;
@@ -213,7 +211,7 @@ static constexpr uint8_t SEIYA_SWING_HORIZONTAL = 0x09;
static constexpr uint8_t SEIYA_SWING_NEUTRAL = 0x17; // no swing change in this frame
// RAS-2819T temperature codes for 18-30°C
static const uint8_t RAS_2819T_TEMP_CODES[] = {
static constexpr uint8_t RAS_2819T_TEMP_CODES[] PROGMEM = {
0x10, // 18°C
0x30, // 19°C
0x20, // 20°C
@@ -328,7 +326,7 @@ static uint8_t get_ras_2819t_temp_code(float temperature) {
return 0x40; // Default to 24°C
}
return RAS_2819T_TEMP_CODES[temp_index];
return progmem_read_byte(&RAS_2819T_TEMP_CODES[temp_index]);
}
/**
@@ -339,7 +337,7 @@ static float decode_ras_2819t_temperature(uint8_t temp_code) {
// Find the code in the temperature array
for (size_t temp_index = 0; temp_index < sizeof(RAS_2819T_TEMP_CODES); temp_index++) {
if (RAS_2819T_TEMP_CODES[temp_index] == base_temp_code) {
if (progmem_read_byte(&RAS_2819T_TEMP_CODES[temp_index]) == base_temp_code) {
return static_cast<float>(temp_index + 18); // 18°C is the minimum
}
}
@@ -680,10 +678,10 @@ void ToshibaClimate::transmit_rac_pt1411hwru_() {
index = static_cast<uint8_t>(roundf(temp_adjd));
if (this->model_ == MODEL_RAC_PT1411HWRU_F) {
code = RAC_PT1411HWRU_TEMPERATURE_F[index];
code = progmem_read_byte(&RAC_PT1411HWRU_TEMPERATURE_F[index]);
message[9] |= RAC_PT1411HWRU_FLAG_FAH;
} else {
code = RAC_PT1411HWRU_TEMPERATURE_C[index];
code = progmem_read_byte(&RAC_PT1411HWRU_TEMPERATURE_C[index]);
}
if ((this->mode == climate::CLIMATE_MODE_FAN_ONLY) || (this->mode == climate::CLIMATE_MODE_OFF)) {
code = RAC_PT1411HWRU_TEMPERATURE_FAN_ONLY;
@@ -752,15 +750,11 @@ void ToshibaClimate::transmit_rac_pt1411hwru_() {
// Swing Mode
data->reset();
data->space(TOSHIBA_PACKET_SPACE);
switch (this->swing_mode) {
case climate::CLIMATE_SWING_VERTICAL:
this->encode_(data, &RAC_PT1411HWRU_SWING_VERTICAL[0], RAC_PT1411HWRU_MESSAGE_LENGTH, 1);
break;
case climate::CLIMATE_SWING_OFF:
default:
this->encode_(data, &RAC_PT1411HWRU_SWING_OFF[0], RAC_PT1411HWRU_MESSAGE_LENGTH, 1);
}
const uint8_t *swing_frame =
this->swing_mode == climate::CLIMATE_SWING_VERTICAL ? RAC_PT1411HWRU_SWING_VERTICAL : RAC_PT1411HWRU_SWING_OFF;
uint8_t swing[RAC_PT1411HWRU_MESSAGE_LENGTH];
progmem_memcpy(swing, swing_frame, sizeof(swing));
this->encode_(data, swing, sizeof(swing), 1);
data->space(TOSHIBA_PACKET_SPACE);
transmit.perform();
@@ -1300,14 +1294,14 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) {
temperature_code =
(message[4] >> 4) | (message[14] & RAC_PT1411HWRU_FLAG_FRAC) | (message[15] & RAC_PT1411HWRU_FLAG_NEG);
if (message[15] & RAC_PT1411HWRU_FLAG_FAH) {
for (size_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_F.size(); i++) {
if (RAC_PT1411HWRU_TEMPERATURE_F[i] == temperature_code) {
for (size_t i = 0; i < sizeof(RAC_PT1411HWRU_TEMPERATURE_F); i++) {
if (progmem_read_byte(&RAC_PT1411HWRU_TEMPERATURE_F[i]) == temperature_code) {
this->target_temperature = static_cast<float>((i + TOSHIBA_RAC_PT1411HWRU_TEMP_F_MIN - 32) * 5) / 9;
}
}
} else {
for (size_t i = 0; i < RAC_PT1411HWRU_TEMPERATURE_C.size(); i++) {
if (RAC_PT1411HWRU_TEMPERATURE_C[i] == temperature_code) {
for (size_t i = 0; i < sizeof(RAC_PT1411HWRU_TEMPERATURE_C); i++) {
if (progmem_read_byte(&RAC_PT1411HWRU_TEMPERATURE_C[i]) == temperature_code) {
this->target_temperature = i + TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN;
}
}
@@ -1333,7 +1327,7 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) {
break;
// Swing mode
case RAC_PT1411HWRU_SWING_HEADER:
if (message[4] == RAC_PT1411HWRU_SWING_VERTICAL[4]) {
if (message[4] == progmem_read_byte(&RAC_PT1411HWRU_SWING_VERTICAL[4])) {
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
} else {
this->swing_mode = climate::CLIMATE_SWING_OFF;
@@ -130,6 +130,61 @@ TEST(ToshibaGeneric, WaveformIsUnchangedByTheSharedPath) {
expect_frame(f.last_frame(), waveform({0xF2, 0x0D, 0x03, 0xFC, 0x01, 0x30, 0xC3, 0x00, 0xF2}, GENERIC), 9);
}
// The RAC-PT1411HWRU sends its main message, then a separate swing packet
// wrapped in packet spaces; both use the generic timings.
static void expect_swing_packet(const remote_base::RawTimings &actual, const std::vector<uint8_t> &bytes) {
ASSERT_GE(actual.size(), 2u);
EXPECT_EQ(actual.front(), -10500);
EXPECT_EQ(actual.back(), -10500);
const remote_base::RawTimings frame(actual.begin() + 1, actual.end() - 1);
expect_frame(frame, waveform(bytes, GENERIC), 6);
}
TEST(ToshibaRacPt1411hwru, SendsTheSwingFrameFromFlash) {
Fixture f(MODEL_RAC_PT1411HWRU_C);
f.command(climate::CLIMATE_MODE_COOL, 24, climate::CLIMATE_FAN_AUTO, climate::CLIMATE_SWING_VERTICAL);
expect_swing_packet(f.last_frame(), {0xB9, 0x46, 0xF5, 0x0A, 0x04, 0xFB});
f.command(climate::CLIMATE_MODE_COOL, 24, climate::CLIMATE_FAN_AUTO, climate::CLIMATE_SWING_OFF);
expect_swing_packet(f.last_frame(), {0xB9, 0x46, 0xF5, 0x0A, 0x05, 0xFA});
}
// The temperature tables are read on the way out and searched on the way back
// in, so what one fixture sends must decode to the same state in another.
TEST(ToshibaRacPt1411hwru, TemperatureCodesRoundTrip) {
for (const Model model : {MODEL_RAC_PT1411HWRU_C, MODEL_RAC_PT1411HWRU_F}) {
for (int temperature = 16; temperature <= 30; temperature++) {
SCOPED_TRACE(::testing::Message() << "model " << model << " temperature " << temperature);
Fixture tx(model);
tx.command(climate::CLIMATE_MODE_COOL, temperature, climate::CLIMATE_FAN_AUTO);
// The main message is sent before the swing packet
const auto &main_frame = tx.transmitter.frames[tx.transmitter.frames.size() - 2];
Fixture rx(model);
remote_base::RemoteReceiveData data(main_frame, 25, remote_base::TOLERANCE_MODE_PERCENTAGE);
ASSERT_TRUE(static_cast<remote_base::RemoteReceiverListener &>(rx.climate).on_receive(data));
EXPECT_EQ(rx.climate.mode, climate::CLIMATE_MODE_COOL);
EXPECT_NEAR(rx.climate.target_temperature, temperature, 0.3f);
}
}
}
// The RAS-2819T table is read for the code on the way out and searched on the way back in.
TEST(ToshibaRas2819t, TemperatureCodesRoundTrip) {
for (int temperature = 18; temperature <= 30; temperature++) {
SCOPED_TRACE(::testing::Message() << "temperature " << temperature);
Fixture tx(MODEL_RAS_2819T);
tx.command(climate::CLIMATE_MODE_COOL, temperature, climate::CLIMATE_FAN_AUTO);
Fixture rx(MODEL_RAS_2819T);
remote_base::RemoteReceiveData data(tx.last_frame(), 25, remote_base::TOLERANCE_MODE_PERCENTAGE);
ASSERT_TRUE(static_cast<remote_base::RemoteReceiverListener &>(rx.climate).on_receive(data));
EXPECT_EQ(rx.climate.mode, climate::CLIMATE_MODE_COOL);
EXPECT_FLOAT_EQ(rx.climate.target_temperature, temperature);
}
}
static bool receive(ToshibaClimate &climate, const std::vector<uint8_t> &bytes, const Timings &t) {
const remote_base::RawTimings raw = waveform(bytes, t);
remote_base::RemoteReceiveData data(raw, 25, remote_base::TOLERANCE_MODE_PERCENTAGE);