13static constexpr uint32_t CARRIER_FREQUENCY_HZ = 38000;
14static constexpr uint32_t HEADER_MARK_US = 3000;
15static constexpr uint32_t HEADER_SPACE_US = 9000;
16static constexpr uint32_t BIT_MARK_US = 500;
17static constexpr uint32_t ONE_SPACE_US = 1500;
18static constexpr uint32_t ZERO_SPACE_US = 500;
19static constexpr uint32_t SECTION_SPACE_US = 2000;
21static constexpr uint8_t EXTENDED_LENGTH = 3 * SAMSUNG_AC_SECTION_LENGTH;
24static const uint8_t RESET_STATE[SAMSUNG_AC_STATE_LENGTH] PROGMEM = {0x02, 0x92, 0x0F, 0x00, 0x00, 0x00, 0xF0,
25 0x01, 0xE2, 0xFE, 0x71, 0x80, 0x11, 0xF0};
27static const uint8_t TIMER_SECTION[SAMSUNG_AC_SECTION_LENGTH] PROGMEM = {0x01, 0xD2, 0x0F, 0x00, 0x00, 0x00, 0x00};
35static constexpr Field POWER_1{6, 4, 0b11};
36static constexpr Field SWING{9, 4, 0b111};
37static constexpr Field TEMP{11, 4, 0b1111};
38static constexpr Field FAN{12, 1, 0b111};
39static constexpr Field
MODE{12, 4, 0b111};
40static constexpr Field POWER_2{13, 4, 0b11};
41static constexpr uint8_t POWER_ON = 0b11;
43static uint8_t get_field(
const uint8_t *
state, Field field) {
return (
state[field.byte] >> field.shift) & field.mask; }
44static void set_field(uint8_t *
state, Field field, uint8_t value) {
45 state[field.byte] = (
state[field.byte] & ~(field.mask << field.shift)) | ((value & field.mask) << field.shift);
47static bool is_powered_on(
const uint8_t *
state) {
48 return get_field(
state, POWER_1) == POWER_ON && get_field(
state, POWER_2) == POWER_ON;
56static constexpr Code MODES[]
PROGMEM = {
60static constexpr Code FANS[]
PROGMEM = {
66static constexpr Code SWINGS[]
PROGMEM = {
74static Code find_entry(
const Code *table,
size_t count, uint8_t key,
bool match_code) {
76 for (
size_t i = 0; i <
count; i++) {
78 if ((match_code ? entry.code : entry.value) == key)
84template<
size_t N>
static uint8_t to_code(
const Code (&table)[N], uint8_t value) {
85 return find_entry(table, N, value,
false).code;
87template<
typename T,
size_t N>
static T from_code(
const Code (&table)[N], uint8_t code) {
88 return static_cast<T
>(find_entry(table, N, code,
true).value);
93static uint8_t section_checksum(
const uint8_t *section) {
95 __builtin_popcount(section[0]) + __builtin_popcount(section[1] & 0x0F) + __builtin_popcount(section[2] & 0xF0);
96 for (uint8_t i = 3; i < SAMSUNG_AC_SECTION_LENGTH; i++) {
97 ones += __builtin_popcount(section[i]);
99 return ~static_cast<uint8_t>(ones);
101static uint8_t stored_checksum(
const uint8_t *section) {
return (section[1] >> 4) | (section[2] << 4); }
102static void store_checksum(uint8_t *section) {
103 const uint8_t sum = section_checksum(section);
104 section[1] = (section[1] & 0x0F) | (sum << 4);
105 section[2] = (section[2] & 0xF0) | (sum >> 4);
109 : climate_ir::ClimateIR(
110 SAMSUNG_AC_TEMP_MIN, SAMSUNG_AC_TEMP_MAX, 1.0f, true, true,
114 progmem_memcpy(this->state_.data(), RESET_STATE, SAMSUNG_AC_STATE_LENGTH);
116 set_field(this->state_.data(), POWER_1, 0);
117 set_field(this->state_.data(), POWER_2, 0);
122 const bool was_on = is_powered_on(
state);
130 const uint8_t temp = clamp<uint8_t>(this->
target_temperature, SAMSUNG_AC_TEMP_MIN, SAMSUNG_AC_TEMP_MAX);
131 set_field(
state, TEMP, temp - SAMSUNG_AC_TEMP_MIN);
134 set_field(
state, POWER_1, on ? POWER_ON : 0);
135 set_field(
state, POWER_2, on ? POWER_ON : 0);
139 uint8_t timer[SAMSUNG_AC_SECTION_LENGTH];
141 const uint8_t *sections[] = {
state, timer,
state + SAMSUNG_AC_SECTION_LENGTH};
144 const uint8_t *sections[] = {
state,
state + SAMSUNG_AC_SECTION_LENGTH};
151 auto *data = transmit.get_data();
154 data->reserve(2 + (
count - 1) * 4 +
count * SAMSUNG_AC_SECTION_LENGTH * 8 * 2 + 1);
155 data->set_carrier_frequency(CARRIER_FREQUENCY_HZ);
156 data->item(HEADER_MARK_US, HEADER_SPACE_US);
158 for (uint8_t s = 0; s <
count; s++) {
160 data->item(BIT_MARK_US, SECTION_SPACE_US);
161 data->item(HEADER_MARK_US, HEADER_SPACE_US);
163 uint8_t section[SAMSUNG_AC_SECTION_LENGTH];
164 memcpy(section, sections[s], SAMSUNG_AC_SECTION_LENGTH);
165 store_checksum(section);
166 for (uint8_t
byte : section) {
167 for (uint8_t bit = 0; bit < 8; bit++, byte >>= 1) {
168 data->item(BIT_MARK_US, (
byte & 1) ? ONE_SPACE_US : ZERO_SPACE_US);
173 data->mark(BIT_MARK_US);
178 if (!data.expect_item(HEADER_MARK_US, HEADER_SPACE_US)) {
183 uint8_t frame[EXTENDED_LENGTH] = {0};
184 uint8_t
length = EXTENDED_LENGTH;
185 for (uint8_t i = 0; i < EXTENDED_LENGTH; i++) {
186 if (i != 0 && i % SAMSUNG_AC_SECTION_LENGTH == 0) {
187 const bool have_separator =
188 data.expect_item(BIT_MARK_US, SECTION_SPACE_US) && data.expect_item(HEADER_MARK_US, HEADER_SPACE_US);
189 if (!have_separator) {
191 if (i >= SAMSUNG_AC_STATE_LENGTH) {
195 ESP_LOGW(TAG,
"Missing section separator before byte %u", i);
199 for (uint8_t bit = 0; bit < 8; bit++) {
200 if (data.expect_item(BIT_MARK_US, ONE_SPACE_US)) {
201 frame[i] |= 1 << bit;
202 }
else if (!data.expect_item(BIT_MARK_US, ZERO_SPACE_US)) {
203 ESP_LOGW(TAG,
"Bad bit %u in byte %u", bit, i);
208 if (!data.expect_mark(BIT_MARK_US)) {
209 ESP_LOGVV(TAG,
"Footer fail");
212 for (uint8_t offset = 0; offset <
length; offset += SAMSUNG_AC_SECTION_LENGTH) {
213 if (stored_checksum(frame + offset) != section_checksum(frame + offset)) {
214 ESP_LOGW(TAG,
"Checksum mismatch in section %u", offset / SAMSUNG_AC_SECTION_LENGTH);
221 memcpy(
state, frame, SAMSUNG_AC_SECTION_LENGTH);
222 memcpy(
state + SAMSUNG_AC_SECTION_LENGTH, frame +
length - SAMSUNG_AC_SECTION_LENGTH, SAMSUNG_AC_SECTION_LENGTH);
224 this->
swing_mode = from_code<climate::ClimateSwingMode>(SWINGS, get_field(
state, SWING));
226 this->
fan_mode = from_code<climate::ClimateFanMode>(FANS, get_field(
state, FAN));
230 ESP_LOGD(TAG,
"Received %u byte frame, power %s",
length, ONOFF(is_powered_on(
state)));