8struct RacPt1411hwruFanSpeed {
48static constexpr uint8_t RAC_PT1411HWRU_SWING_VERTICAL[] PROGMEM = {0xB9, 0x46, 0xF5, 0x0A, 0x04, 0xFB};
49static constexpr uint8_t RAC_PT1411HWRU_SWING_OFF[] PROGMEM = {0xB9, 0x46, 0xF5, 0x0A, 0x05, 0xFA};
71static constexpr uint8_t RAC_PT1411HWRU_TEMPERATURE_C[] PROGMEM = {0x10, 0x00, 0x01, 0x03, 0x02, 0x06, 0x07, 0x05,
72 0x04, 0x0C, 0x0D, 0x09, 0x08, 0x0A, 0x0B};
74static constexpr uint8_t RAC_PT1411HWRU_TEMPERATURE_F[] PROGMEM = {
75 0x10, 0x30, 0x00, 0x20, 0x01, 0x21, 0x03, 0x23, 0x02, 0x22, 0x06, 0x26, 0x07, 0x05,
76 0x25, 0x04, 0x24, 0x0C, 0x2C, 0x0D, 0x2D, 0x09, 0x08, 0x28, 0x0A, 0x2A, 0x0B};
99struct Ras2819tPacketSuffix {
144struct ToshibaTimings {
145 uint16_t header_mark;
146 uint16_t header_space;
154static const ToshibaTimings TOSHIBA_TIMINGS PROGMEM = {TOSHIBA_HEADER_MARK,
155 TOSHIBA_HEADER_SPACE,
161static const ToshibaTimings SEIYA_TIMINGS PROGMEM = {4630, 4450, 625, 490, 1570, 5830,
false};
163static const ToshibaTimings RAS_B10N3KV2_TIMINGS
PROGMEM = {4400, 4300, 580, 490, 1600, 7400,
true};
165static ToshibaTimings timings_for(
Model model) {
166 const ToshibaTimings *source = &TOSHIBA_TIMINGS;
169 source = &SEIYA_TIMINGS;
172 source = &RAS_B10N3KV2_TIMINGS;
177 ToshibaTimings timings;
183static constexpr uint8_t SEIYA_MESSAGE_LENGTH = 11;
184static constexpr uint8_t SEIYA_FEATURE_MARKER = 0x21;
185static constexpr uint8_t SEIYA_SWING_BOTH = 0x01;
186static constexpr uint8_t SEIYA_SWING_OMNI = 0x02;
187static constexpr uint8_t SEIYA_SWING_VERTICAL = 0x08;
188static constexpr uint8_t SEIYA_SWING_HORIZONTAL = 0x09;
189static constexpr uint8_t SEIYA_SWING_NEUTRAL = 0x17;
192static constexpr uint8_t RAS_2819T_TEMP_CODES[]
PROGMEM = {
276struct Ras2819tSecondPacketCodes {
278 Ras2819tPacketSuffix suffix;
300static uint8_t get_ras_2819t_temp_code(
float temperature) {
301 int temp_index =
static_cast<int>(
temperature) - 18;
302 if (temp_index < 0 ||
static_cast<size_t>(temp_index) >=
sizeof(RAS_2819T_TEMP_CODES)) {
303 ESP_LOGW(TAG,
"Temperature %.1f°C out of range [18-30°C], defaulting to 24°C",
temperature);
313static float decode_ras_2819t_temperature(uint8_t temp_code) {
314 uint8_t base_temp_code = temp_code & 0xF0;
317 for (
size_t temp_index = 0; temp_index <
sizeof(RAS_2819T_TEMP_CODES); temp_index++) {
319 return static_cast<float>(temp_index + 18);
323 ESP_LOGW(TAG,
"Unknown temp code: 0x%02X, defaulting to 24°C", base_temp_code);
349static bool is_valid_ras_2819t_command(uint64_t rc_code_1, uint64_t rc_code_2 = 0) {
351 uint16_t header1 = (rc_code_1 >> 32) & 0xFFFF;
357 if (rc_code_2 == 0) {
359 if (rc_code_1 == valid_cmd) {
368 uint8_t header2 = (rc_code_2 >> 40) & 0xFF;
374 uint8_t temp_byte = (rc_code_1 >> 8) & 0xFF;
375 uint8_t temp_complement = rc_code_1 & 0xFF;
376 if (temp_byte !=
static_cast<uint8_t
>(~temp_complement)) {
381 uint16_t fan_code = (rc_code_1 >> 16) & 0xFFFF;
382 uint8_t fan2_byte = (rc_code_2 >> 32) & 0xFF;
385 bool valid_fan_combo =
false;
387 valid_fan_combo =
true;
389 valid_fan_combo =
true;
391 valid_fan_combo =
true;
393 valid_fan_combo =
true;
395 valid_fan_combo =
true;
397 valid_fan_combo =
true;
399 return valid_fan_combo;
419 if (restore.has_value()) {
425 restore->apply(
this);
439 ESP_LOGW(TAG,
"Invalid mode detected during setup, resetting to OFF");
444 if (!this->fan_mode.has_value() && !this->has_custom_fan_mode()) {
445 ESP_LOGW(TAG,
"Fan mode not set during setup, defaulting to AUTO");
452#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
454 const char *fan_mode_str =
"NONE";
455 char fan_mode_buf[4];
456 if (this->fan_mode.has_value()) {
457 buf_append_printf(fan_mode_buf,
sizeof(fan_mode_buf), 0,
"%d",
static_cast<int>(this->fan_mode.value()));
458 fan_mode_str = fan_mode_buf;
460 ESP_LOGV(TAG,
"Setup complete - Mode: %d, Fan: %s, Swing: %d, Temp: %.1f",
static_cast<int>(this->
mode), fan_mode_str,
478 const uint8_t message_length = seiya ? SEIYA_MESSAGE_LENGTH : 9;
481 message[4] = TOSHIBA_COMMAND_DEFAULT;
489 message[6] = this->encode_mode_fan_();
499 message[8] = SEIYA_FEATURE_MARKER;
501 message[9] = SEIYA_SWING_NEUTRAL;
503 message[9] = this->seiya_swing_code_();
512 auto *data = transmit.get_data();
520 switch (this->
mode) {
540 return TOSHIBA_FAN_SPEED_QUIET;
542 return TOSHIBA_FAN_SPEED_1;
544 return TOSHIBA_FAN_SPEED_3;
546 return TOSHIBA_FAN_SPEED_5;
549 return TOSHIBA_FAN_SPEED_AUTO;
553uint8_t ToshibaClimate::seiya_swing_code_()
const {
556 return SEIYA_SWING_VERTICAL;
558 return SEIYA_SWING_HORIZONTAL;
560 return SEIYA_SWING_BOTH;
562 return SEIYA_SWING_NEUTRAL;
572 auto *data = transmit.get_data();
617 index =
static_cast<uint8_t
>(roundf(temp_adjd));
637 switch (this->
mode) {
675 for (index = 6; index <= 10; index++) {
692 const uint8_t *swing_frame =
696 this->
encode_(data, swing,
sizeof(swing), 1);
712 auto *data = transmit.get_data();
728 if (cs_send_update) {
730 }
else if (cs_state) {
736 switch (this->
mode) {
767 bool mode_changed = (this->
mode != this->last_mode_);
768 bool fan_changed = (this->fan_mode != this->last_fan_mode_);
769 bool temp_changed = (abs(this->
target_temperature - this->last_target_temperature_) > 0.1f);
771 bool only_swing_changed = swing_changed && !mode_changed && !fan_changed && !temp_changed;
773 if (only_swing_changed) {
776 auto *swing_data = swing_transmit.
get_data();
789 swing_transmit.perform();
793 this->last_mode_ = this->
mode;
794 this->last_fan_mode_ = this->
fan_mode;
823 uint8_t temp_code = get_ras_2819t_temp_code(
temperature);
832 ESP_LOGW(TAG,
"Dry mode only supports AUTO fan speed, forcing AUTO");
836 uint16_t fan_code = get_ras_2819t_fan_code(effective_fan_mode);
839 switch (this->
mode) {
842 message1[2] = (fan_code >> 8) & 0xFF;
843 message1[3] = fan_code & 0xFF;
844 message1[4] = temp_code;
845 message1[5] = ~temp_code;
850 message1[2] = (fan_code >> 8) & 0xFF;
851 message1[3] = fan_code & 0xFF;
870 message1[5] = ~message1[4];
875 message1[2] = (fan_code >> 8) & 0xFF;
876 message1[3] = fan_code & 0xFF;
883 message1[2] = (fan_code >> 8) & 0xFF;
884 message1[3] = fan_code & 0xFF;
885 message1[4] = temp_code;
886 message1[5] = ~temp_code;
894 Ras2819tSecondPacketCodes second_packet_codes = get_ras_2819t_second_packet_codes(effective_fan_mode);
897 switch (this->
mode) {
899 message2[1] = second_packet_codes.fan_byte;
901 message2[3] = second_packet_codes.suffix.byte3;
902 message2[4] = second_packet_codes.suffix.byte4;
903 message2[5] = second_packet_codes.suffix.byte5;
907 message2[1] = second_packet_codes.fan_byte;
909 message2[3] = second_packet_codes.suffix.byte3;
925 message2[1] = second_packet_codes.fan_byte;
927 message2[3] = second_packet_codes.suffix.byte3;
933 message2[1] = second_packet_codes.fan_byte;
935 message2[3] = second_packet_codes.suffix.byte3;
936 message2[4] = second_packet_codes.suffix.byte4;
937 message2[5] = second_packet_codes.suffix.byte5;
946 auto *data = transmit.get_data();
960 this->last_mode_ = this->
mode;
961 this->last_fan_mode_ = this->
fan_mode;
969 for (
auto i : HEADERS) {
970 if ((
message[0] == i) && (
message[1] ==
static_cast<uint8_t
>(~i)))
981 if (message1[i] != message2[i])
1015 ESP_LOGI(TAG,
"Mode: OFF");
1025 ESP_LOGI(TAG,
"Swing: OFF");
1028 ESP_LOGI(TAG,
"Swing: VERTICAL");
1037 uint8_t message1[6], message2[6];
1038 for (uint8_t i = 0; i < 6; i++) {
1039 message1[i] = (toshiba_data.
rc_code_1 >> (40 - i * 8)) & 0xFF;
1040 message2[i] = (toshiba_data.
rc_code_2 >> (40 - i * 8)) & 0xFF;
1044 uint8_t temp_code = message1[4];
1047 if ((message1[2] == 0x7B) && (message1[3] == 0x84)) {
1050 ESP_LOGI(TAG,
"Mode: OFF");
1051 }
else if ((message1[2] == 0x1F) && (message1[3] == 0xE0)) {
1053 if ((temp_code & 0x0F) == 0x08) {
1055 ESP_LOGI(TAG,
"Mode: AUTO");
1056 }
else if ((temp_code & 0x0F) == 0x04) {
1058 ESP_LOGI(TAG,
"Mode: DRY");
1061 ESP_LOGI(TAG,
"Mode: COOL (low temp)");
1065 if ((temp_code & 0x0F) == 0x0C) {
1067 ESP_LOGI(TAG,
"Mode: HEAT");
1068 }
else if (message1[5] == 0x1B) {
1070 ESP_LOGI(TAG,
"Mode: FAN_ONLY");
1073 ESP_LOGI(TAG,
"Mode: COOL");
1078 uint16_t fan_code = (message1[2] << 8) | message1[3];
1079 this->fan_mode = decode_ras_2819t_fan_mode(fan_code);
1089 ESP_LOGD(TAG,
"Unknown single-packet RAS-2819T command: 0x%" PRIX64, toshiba_data.
rc_code_1);
1098 auto decode_result = toshiba_protocol.
decode(data);
1100 if (decode_result.has_value()) {
1101 auto toshiba_data = decode_result.value();
1103 if (is_valid_ras_2819t_command(toshiba_data.rc_code_1, toshiba_data.rc_code_2)) {
1111 uint8_t message_length = TOSHIBA_HEADER_LENGTH, temperature_code = 0;
1114 const ToshibaTimings timings = timings_for(this->
model_);
1115 if (!data.
expect_item(timings.header_mark, timings.header_space)) {
1134 message_length =
message[2] + 2;
1143 if (!data.expect_item(TOSHIBA_BIT_MARK, TOSHIBA_GAP_SPACE)) {
1147 if (!data.expect_item(TOSHIBA_HEADER_MARK, TOSHIBA_HEADER_SPACE)) {
1155 if (data.expect_item(TOSHIBA_BIT_MARK, TOSHIBA_GAP_SPACE)) {
1157 data.expect_item(TOSHIBA_HEADER_MARK, TOSHIBA_HEADER_SPACE);
1212 switch (message[2]) {
1235 for (
size_t i = 0; i <
sizeof(RAC_PT1411HWRU_TEMPERATURE_F); i++) {
1241 for (
size_t i = 0; i <
sizeof(RAC_PT1411HWRU_TEMPERATURE_C); i++) {
1277 message_length += TOSHIBA_HEADER_LENGTH;
1288 this->decode_mode_fan_temperature_(
message);
1291 this->seiya_decode_swing_(
message[9]);
1300 message[0] = TOSHIBA_HEADER_BYTE_0;
1301 message[1] = TOSHIBA_HEADER_BYTE_1;
1308 const uint8_t repeat) {
1309 const ToshibaTimings timings = timings_for(this->
model_);
1312 for (uint8_t copy = 0; copy <= repeat; copy++) {
1313 data->item(timings.header_mark, timings.header_space);
1315 for (uint8_t
byte = 0;
byte < nbytes;
byte++) {
1316 for (uint8_t bit = 0; bit < 8; bit++) {
1317 data->mark(timings.bit_mark);
1318 if (
message[
byte] & (1 << (7 - bit))) {
1319 data->space(timings.one_space);
1321 data->space(timings.zero_space);
1325 data->mark(timings.bit_mark);
1326 if (copy < repeat || timings.gap_after_last) {
1327 data->space(timings.gap_space);
1333 const ToshibaTimings timings = timings_for(this->
model_);
1334 for (uint8_t
byte = 0;
byte < nbytes;
byte++) {
1335 for (uint8_t bit = 0; bit < 8; bit++) {
1336 if (data->expect_item(timings.bit_mark, timings.one_space)) {
1337 message[byte] |= 1 << (7 - bit);
1338 }
else if (data->expect_item(timings.bit_mark, timings.zero_space)) {
1339 message[byte] &=
static_cast<uint8_t
>(~(1 << (7 - bit)));
1348void ToshibaClimate::decode_mode_fan_temperature_(
const uint8_t *
message) {
1385 case TOSHIBA_FAN_SPEED_QUIET:
1387 case TOSHIBA_FAN_SPEED_1:
1389 case TOSHIBA_FAN_SPEED_2:
1390 case TOSHIBA_FAN_SPEED_3:
1392 case TOSHIBA_FAN_SPEED_4:
1393 case TOSHIBA_FAN_SPEED_5:
1395 case TOSHIBA_FAN_SPEED_AUTO:
1401void ToshibaClimate::seiya_decode_swing_(uint8_t code) {
1404 case SEIYA_SWING_BOTH:
1405 case SEIYA_SWING_OMNI:
1408 case SEIYA_SWING_VERTICAL:
1411 case SEIYA_SWING_HORIZONTAL:
constexpr size_t count(ValueType value) const
Check if the set contains a specific value (std::set compatibility) Returns 1 if present,...
ClimateMode mode
The active mode of the climate device.
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
float target_temperature
The target temperature of the climate device.
ClimateSwingMode swing_mode
The active swing mode of the climate device.
float current_temperature
The current temperature of the climate device, as reported from the integration.
void publish_state()
Publish the state of the climate device, to be called from integrations.
optional< ClimateDeviceRestoreState > restore_state_()
Restore the state of the climate device, call this from your setup() method.
climate::ClimateSwingModeMask swing_modes_
float maximum_temperature_
float minimum_temperature_
bool expect_item(uint32_t mark, uint32_t space)
void set_carrier_frequency(uint32_t carrier_frequency)
RemoteTransmitterBase * transmitter_
RemoteTransmitData * get_data()
optional< ToshibaAcData > decode(RemoteReceiveData src)
void add_on_state_callback(F &&callback)
Add a callback that will be called every time a filtered value arrives.
float state
This member variable stores the last state that has passed through all filters.
static bool frame_header_valid(const uint8_t *message)
void encode_(remote_base::RemoteTransmitData *data, const uint8_t *message, uint8_t nbytes, uint8_t repeat)
virtual void transmit_current_temperature()
static void finalize_frame(uint8_t *message, uint8_t length)
bool compare_rac_pt1411hwru_packets_(const uint8_t *message1, const uint8_t *message2)
virtual float temperature_min()
virtual void sanitize_restore_state(climate::ClimateDeviceRestoreState &restore)
bool is_valid_rac_pt1411hwru_message_(const uint8_t *message)
static climate::ClimateFanMode decode_fan(uint8_t code)
void transmit_ras_2819t_()
uint8_t is_valid_rac_pt1411hwru_header_(const uint8_t *message)
uint8_t encode_fan_() const
void transmit_rac_pt1411hwru_()
uint8_t encode_mode_() const
bool process_ras_2819t_command_(const remote_base::ToshibaAcData &toshiba_data)
bool decode_(remote_base::RemoteReceiveData *data, uint8_t *message, uint8_t nbytes)
void decode_mode_(const uint8_t *message)
climate::ClimateSwingMode last_swing_mode_
void transmit_rac_pt1411hwru_temp_(bool cs_state=true, bool cs_send_update=true)
void transmit_state() override
bool on_receive(remote_base::RemoteReceiveData data) override
static bool frame_checksum_valid(const uint8_t *message, uint8_t length)
const LogString * message
constexpr float BME680_GAS_LOOKUP_TABLE_1[16] PROGMEM
@ CLIMATE_SWING_OFF
The swing mode is set to Off.
@ CLIMATE_SWING_HORIZONTAL
The fan mode is set to Horizontal.
@ CLIMATE_SWING_VERTICAL
The fan mode is set to Vertical.
@ CLIMATE_SWING_BOTH
The fan mode is set to Both.
@ CLIMATE_MODE_DRY
The climate device is set to dry/humidity mode.
@ CLIMATE_MODE_FAN_ONLY
The climate device only has the fan enabled, no heating or cooling is taking place.
@ CLIMATE_MODE_HEAT
The climate device is set to heat to reach the target temperature.
@ CLIMATE_MODE_COOL
The climate device is set to cool to reach the target temperature.
@ CLIMATE_MODE_HEAT_COOL
The climate device is set to heat/cool to reach the target temperature.
@ CLIMATE_MODE_OFF
The climate device is off.
ClimateFanMode
NOTE: If adding values, update ClimateFanModeMask in climate_traits.h to use the new last value.
@ CLIMATE_FAN_MEDIUM
The fan mode is set to Medium.
@ CLIMATE_FAN_ON
The fan mode is set to On.
@ CLIMATE_FAN_AUTO
The fan mode is set to Auto.
@ CLIMATE_FAN_LOW
The fan mode is set to Low.
@ CLIMATE_FAN_QUIET
The fan mode is set to Quiet.
@ CLIMATE_FAN_HIGH
The fan mode is set to High.
const uint8_t RAS_2819T_FAN2_MEDIUM
const uint8_t RAC_PT1411HWRU_MODE_HEAT
const uint16_t RAS_2819T_VALID_HEADER1
const uint8_t TOSHIBA_MODE_HEAT
const Ras2819tPacketSuffix RAS_2819T_SUFFIX_AUTO
const uint8_t RAC_PT1411HWRU_MODE_FAN
const uint8_t RAC_PT1411HWRU_CS_ENABLED
const uint16_t TOSHIBA_CARRIER_FREQUENCY
const uint8_t RAC_PT1411HWRU_MODE_AUTO
const uint16_t RAS_2819T_FAN_AUTO
const RacPt1411hwruFanSpeed RAC_PT1411HWRU_NO_FAN
const uint8_t RAC_PT1411HWRU_MODE_COOL
const uint8_t RAS_2819T_DRY_BYTE3
const uint64_t RAS_2819T_POWER_OFF_COMMAND
const uint8_t RAC_PT1411HWRU_TEMPERATURE_FAN_ONLY
const float TOSHIBA_RAS_2819T_TEMP_C_MAX
const uint8_t TOSHIBA_COMMAND_MOTION
const Ras2819tPacketSuffix RAS_2819T_SUFFIX_LOW
const uint8_t RAS_2819T_FAN2_QUIET
const uint8_t RAS_2819T_FAN2_AUTO
const uint8_t RAC_PT1411HWRU_FLAG_FAH
const uint8_t RAS_2819T_HEAT_SUFFIX
const uint8_t RAS_2819T_AUTO_DRY_SUFFIX
const uint64_t RAS_2819T_SWING_TOGGLE
const uint8_t RAC_PT1411HWRU_CS_FOOTER_COOL
const uint8_t RAC_PT1411HWRU_CS_DATA
const Ras2819tPacketSuffix RAS_2819T_SUFFIX_MEDIUM
const uint8_t TOSHIBA_MODE_DRY
const uint8_t RAC_PT1411HWRU_FAN_OFF
const uint8_t RAC_PT1411HWRU_FLAG_NEG
const uint8_t RAS_2819T_VALID_HEADER2
const uint8_t RAS_2819T_HEADER2
const float TOSHIBA_RAC_PT1411HWRU_TEMP_F_MIN
const uint8_t RAS_2819T_FAN2_LOW
const uint8_t TOSHIBA_MODE_FAN_ONLY
const float TOSHIBA_GENERIC_TEMP_C_MAX
const uint8_t TOSHIBA_MODE_COOL
const float TOSHIBA_GENERIC_TEMP_C_MIN
const uint16_t TOSHIBA_ZERO_SPACE
constexpr RacPt1411hwruFanSpeed RAC_PT1411HWRU_FAN_LOW
ESPHOME_LOG_TAG(TAG, "toshiba.climate")
const uint8_t RAC_PT1411HWRU_MESSAGE_HEADER1
const uint8_t RAS_2819T_DRY_BYTE2
const float TOSHIBA_RAC_PT1411HWRU_TEMP_C_MAX
const uint8_t RAC_PT1411HWRU_CS_HEADER
const uint16_t RAS_2819T_FAN_MEDIUM
constexpr RacPt1411hwruFanSpeed RAC_PT1411HWRU_FAN_AUTO
const Ras2819tPacketSuffix RAS_2819T_SUFFIX_QUIET
const uint8_t RAC_PT1411HWRU_CS_FOOTER_AUTO
const uint16_t RAS_2819T_HEADER1
const uint8_t TOSHIBA_MODE_AUTO
const uint8_t RAS_2819T_AUTO_TEMP_OFFSET
const uint8_t RAC_PT1411HWRU_MODE_DRY
const uint8_t RAS_2819T_FAN_ONLY_TEMP
const uint8_t RAS_2819T_AUTO_BYTE3
const uint16_t TOSHIBA_ONE_SPACE
constexpr RacPt1411hwruFanSpeed RAC_PT1411HWRU_FAN_HIGH
const uint8_t RAC_PT1411HWRU_MESSAGE_HEADER0
const float TOSHIBA_RAS_2819T_TEMP_C_MIN
constexpr RacPt1411hwruFanSpeed RAC_PT1411HWRU_FAN_MED
const uint8_t RAC_PT1411HWRU_FLAG_FRAC
const uint8_t RAS_2819T_AUTO_BYTE2
const uint8_t RAS_2819T_HEAT_TEMP_OFFSET
const uint8_t TOSHIBA_MODE_OFF
const Ras2819tPacketSuffix RAS_2819T_SUFFIX_HIGH
const uint8_t RAC_PT1411HWRU_CS_FOOTER_HEAT
const uint8_t RAS_2819T_FAN2_HIGH
const uint16_t TOSHIBA_PACKET_SPACE
const uint8_t RAS_2819T_FAN_ONLY_TEMP_INV
const uint8_t RAC_PT1411HWRU_MODE_OFF
const float TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN
const uint8_t RAC_PT1411HWRU_MESSAGE_LENGTH
const uint8_t RAC_PT1411HWRU_FLAG_MASK
const uint8_t RAS_2819T_AUTO_DRY_FAN_BYTE
const uint16_t RAS_2819T_FAN_LOW
const std::array< uint64_t, 2 > RAS_2819T_VALID_SINGLE_COMMANDS
const uint16_t RAS_2819T_FAN_QUIET
const uint16_t RAS_2819T_FAN_HIGH
const uint8_t RAC_PT1411HWRU_SWING_HEADER
const uint8_t RAS_2819T_MESSAGE_LENGTH
const uint8_t RAS_2819T_DRY_TEMP_OFFSET
constexpr uint8_t xor_bytes(const uint8_t *data, size_t len, uint8_t init=0)
XOR the bytes of data with size len, starting from init.
constexpr uint8_t sum_bytes(const uint8_t *data, size_t len, uint8_t sum=0)
Add up the bytes of data with size len, truncated to 8 bits: the checksum many serial and IR protocol...
void progmem_memcpy(void *dst, const void *src, size_t len)
uint8_t progmem_read_byte(const uint8_t *addr)