10static constexpr uint32_t CARRIER_FREQUENCY = 38000;
12static constexpr uint32_t HEADER_US = 19 * T_US / 2;
15static constexpr uint32_t GAP_MIN_US = HEADER_US - T_US / 4;
16static constexpr uint8_t MIN_HALF_DEGREES =
static_cast<uint8_t
>(IRA211_MIN_TEMPERATURE * 2);
17static constexpr uint8_t MAX_HALF_DEGREES =
static_cast<uint8_t
>(IRA211_MAX_TEMPERATURE * 2);
19static constexpr uint8_t DEVICE_ID = 0x66;
20static constexpr uint8_t BITS_PER_PACKET = 10;
21static constexpr uint8_t MIN_PACKETS = 4;
22static constexpr uint8_t MAX_PACKETS = 7;
23static constexpr uint8_t MAX_BITS = MAX_PACKETS * BITS_PER_PACKET;
27static constexpr uint8_t FIELD_TEMPERATURE = 1 << 0;
28static constexpr uint8_t FIELD_MODE = 1 << 1;
29static constexpr uint8_t FIELD_FAN = 1 << 2;
36static const CommandLayout LAYOUTS[] PROGMEM = {
45static optional<CommandLayout> find_layout(
IRA211Command command) {
46 for (
const auto &slot : LAYOUTS) {
49 if (layout.command == command)
56static uint8_t packet_count(
const CommandLayout &layout) {
57 return 3 + ((layout.fields & FIELD_TEMPERATURE) ? 2 : 0) + ((layout.fields & FIELD_MODE) ? 1 : 0) +
58 ((layout.fields & FIELD_FAN) ? 1 : 0);
62static uint8_t wire_xform(uint8_t value) {
return reverse_bits(
static_cast<uint8_t
>(~value)); }
64static bool is_known_mode(uint8_t value) {
75static bool is_known_fan(uint8_t value) {
89 for (uint8_t i = 0; i <
count; i++)
95 if (this->command != rhs.
command)
97 const optional<CommandLayout> layout = find_layout(this->command);
98 if (!layout.has_value())
100 if ((layout->fields & FIELD_TEMPERATURE) && this->half_degrees != rhs.
half_degrees)
102 if ((layout->fields & FIELD_MODE) && this->mode != rhs.
mode)
104 return !(layout->fields & FIELD_FAN) || this->
fan == rhs.
fan;
108 const optional<CommandLayout> layout = find_layout(data.command);
109 if (!layout.has_value())
112 uint8_t bytes[MAX_PACKETS];
114 bytes[
count++] = DEVICE_ID;
115 bytes[
count++] = wire_xform(
static_cast<uint8_t
>(data.command));
116 if (layout->fields & FIELD_TEMPERATURE) {
117 bytes[
count++] = wire_xform(data.half_degrees / 2);
118 bytes[
count++] = wire_xform((data.half_degrees & 1) ? 5 : 0);
120 if (layout->fields & FIELD_MODE)
121 bytes[
count++] = wire_xform(
static_cast<uint8_t
>(data.mode));
122 if (layout->fields & FIELD_FAN)
123 bytes[
count++] = wire_xform(
static_cast<uint8_t
>(data.fan));
130 dst->
item(HEADER_US, T_US);
131 dst->
item(T_US, HEADER_US);
136 for (uint8_t p = 0; p <
count; p++) {
137 const uint16_t packet = (1 << 9) | (bytes[p] << 1);
138 for (int8_t i = BITS_PER_PACKET - 1; i >= 0; i--) {
139 const bool bit = (packet >> i) & 1;
140 if (bit != level && run != 0) {
142 dst->
mark(run * T_US);
144 dst->
space(run * T_US);
153 dst->
space(run * T_US);
155 dst->
space(HEADER_US);
159 if (!
src.expect_item(HEADER_US, T_US) || !
src.expect_item(T_US, HEADER_US))
163 uint8_t bytes[MAX_PACKETS]{};
165 while (
src.is_valid()) {
166 const int32_t
raw =
src.peek();
167 const bool is_mark =
raw > 0;
170 if (is_mark && (!
src.is_valid(1) ||
src.peek(1) <= -
static_cast<int32_t
>(GAP_MIN_US)))
172 if (!is_mark &&
duration >= GAP_MIN_US)
175 if (run == 0 || bits + run > MAX_BITS)
177 for (
uint32_t i = 0; i < run; i++, bits++) {
178 const uint8_t
pos = bits % BITS_PER_PACKET;
179 if ((
pos == 0 && !is_mark) || (
pos == BITS_PER_PACKET - 1 && is_mark))
181 if (
pos != 0 &&
pos != BITS_PER_PACKET - 1) {
182 uint8_t &
byte = bytes[bits / BITS_PER_PACKET];
183 byte = (
byte << 1) | (is_mark ? 1 : 0);
189 const uint8_t
count = bits / BITS_PER_PACKET;
190 if (bits % BITS_PER_PACKET != 0 ||
count < MIN_PACKETS || bytes[0] != DEVICE_ID)
194 const optional<CommandLayout> layout = find_layout(
out.command);
196 ESP_LOGV(TAG,
"Invalid frame (%u bits)", bits);
202 if (layout->fields & FIELD_TEMPERATURE) {
203 const uint8_t whole = wire_xform(bytes[index++]);
204 const uint8_t tenths = wire_xform(bytes[index++]);
205 const uint16_t half_degrees = whole * 2 + (tenths >= 5 ? 1 : 0);
206 if (half_degrees < MIN_HALF_DEGREES || half_degrees > MAX_HALF_DEGREES)
208 out.half_degrees =
static_cast<uint8_t
>(half_degrees);
210 if (layout->fields & FIELD_MODE) {
211 const uint8_t
mode = wire_xform(bytes[index++]);
212 if (!is_known_mode(
mode))
216 if (layout->fields & FIELD_FAN) {
217 const uint8_t fan = wire_xform(bytes[index++]);
218 if (!is_known_fan(fan))
225static const LogString *command_to_string(
IRA211Command command) {
228 return LOG_STR(
"TEMP_UP");
230 return LOG_STR(
"TEMP_DOWN");
232 return LOG_STR(
"MODE");
234 return LOG_STR(
"FAN");
236 return LOG_STR(
"POWER");
238 return LOG_STR(
"SYNC");
240 return LOG_STR(
"UNKNOWN");
247 return LOG_STR(
"Protection");
249 return LOG_STR(
"Timer");
251 return LOG_STR(
"Comfort");
253 return LOG_STR(
"Unknown");
257static const LogString *fan_to_string(
IRA211Fan fan) {
260 return LOG_STR(
"Auto");
262 return LOG_STR(
"1/3");
264 return LOG_STR(
"2/3");
266 return LOG_STR(
"3/3");
268 return LOG_STR(
"Unknown");
273 const optional<CommandLayout> layout = find_layout(data.command);
274 const uint8_t fields = layout.has_value() ? layout->fields : 0;
275 const unsigned whole = data.half_degrees / 2;
276 const unsigned tenths = (data.half_degrees & 1) ? 5 : 0;
277 const auto *command = LOG_STR_ARG(command_to_string(data.command));
278 if (fields == (FIELD_TEMPERATURE | FIELD_MODE | FIELD_FAN)) {
279 ESP_LOGI(TAG,
"Received IRA211: command=%s, temperature=%u.%uC, mode=%s, fan=%s", command, whole, tenths,
280 LOG_STR_ARG(mode_to_string(data.mode)), LOG_STR_ARG(fan_to_string(data.fan)));
281 }
else if (fields == FIELD_TEMPERATURE) {
282 ESP_LOGI(TAG,
"Received IRA211: command=%s, temperature=%u.%uC", command, whole, tenths);
283 }
else if (fields == FIELD_MODE) {
284 ESP_LOGI(TAG,
"Received IRA211: command=%s, mode=%s", command, LOG_STR_ARG(mode_to_string(data.mode)));
285 }
else if (fields == FIELD_FAN) {
286 ESP_LOGI(TAG,
"Received IRA211: command=%s, fan=%s", command, LOG_STR_ARG(fan_to_string(data.fan)));
288 ESP_LOGI(TAG,
"Received IRA211: command=%s", command);
BedjetMode mode
BedJet operating mode.
optional< IRA211Data > decode(RemoteReceiveData src)
void encode(RemoteTransmitData *dst, const IRA211Data &data)
void dump(const IRA211Data &data)
void space(uint32_t length)
void set_carrier_frequency(uint32_t carrier_frequency)
void mark(uint32_t length)
void item(uint32_t mark, uint32_t space)
void reserve(uint32_t len)
std::vector< uint8_t > & out
ESPHOME_LOG_TAG(TAG, "remote.abbwelcome")
@ IRA211_COMMAND_TEMP_DOWN
void progmem_memcpy(void *dst, const void *src, size_t len)
uint8_t reverse_bits(uint8_t x)
Reverse the order of 8 bits.
bool operator==(const IRA211Data &rhs) const
Compares the command and only the fields that command carries.