mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Coolix IR protocol improvements (#5105)
* coolix protocol * tests * 24-bit range * some DRY in coolix * added short condition * one more change * final prettify * v2023.8
This commit is contained in:
		| @@ -114,7 +114,7 @@ bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteRecei | |||||||
|   if (!decoded.has_value()) |   if (!decoded.has_value()) | ||||||
|     return false; |     return false; | ||||||
|   // Decoded remote state y 3 bytes long code. |   // Decoded remote state y 3 bytes long code. | ||||||
|   uint32_t remote_state = *decoded; |   uint32_t remote_state = (*decoded).second; | ||||||
|   ESP_LOGV(TAG, "Decoded 0x%06X", remote_state); |   ESP_LOGV(TAG, "Decoded 0x%06X", remote_state); | ||||||
|   if ((remote_state & 0xFF0000) != 0xB20000) |   if ((remote_state & 0xFF0000) != 0xB20000) | ||||||
|     return false; |     return false; | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ from esphome.const import ( | |||||||
|     CONF_PROTOCOL, |     CONF_PROTOCOL, | ||||||
|     CONF_GROUP, |     CONF_GROUP, | ||||||
|     CONF_DEVICE, |     CONF_DEVICE, | ||||||
|  |     CONF_SECOND, | ||||||
|     CONF_STATE, |     CONF_STATE, | ||||||
|     CONF_CHANNEL, |     CONF_CHANNEL, | ||||||
|     CONF_FAMILY, |     CONF_FAMILY, | ||||||
| @@ -39,6 +40,7 @@ AUTO_LOAD = ["binary_sensor"] | |||||||
|  |  | ||||||
| CONF_RECEIVER_ID = "receiver_id" | CONF_RECEIVER_ID = "receiver_id" | ||||||
| CONF_TRANSMITTER_ID = "transmitter_id" | CONF_TRANSMITTER_ID = "transmitter_id" | ||||||
|  | CONF_FIRST = "first" | ||||||
|  |  | ||||||
| ns = remote_base_ns = cg.esphome_ns.namespace("remote_base") | ns = remote_base_ns = cg.esphome_ns.namespace("remote_base") | ||||||
| RemoteProtocol = ns.class_("RemoteProtocol") | RemoteProtocol = ns.class_("RemoteProtocol") | ||||||
| @@ -349,19 +351,48 @@ async def canalsatld_action(var, config, args): | |||||||
|     CoolixAction, |     CoolixAction, | ||||||
|     CoolixDumper, |     CoolixDumper, | ||||||
| ) = declare_protocol("Coolix") | ) = declare_protocol("Coolix") | ||||||
| COOLIX_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint32_t}) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_binary_sensor("coolix", CoolixBinarySensor, COOLIX_SCHEMA) | COOLIX_BASE_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_FIRST): cv.hex_int_range(0, 16777215), | ||||||
|  |         cv.Optional(CONF_SECOND, default=0): cv.hex_int_range(0, 16777215), | ||||||
|  |         cv.Optional(CONF_DATA): cv.invalid( | ||||||
|  |             "'data' option has been removed in ESPHome 2023.8. " | ||||||
|  |             "Use the 'first' and 'second' options instead." | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | COOLIX_SENSOR_SCHEMA = cv.Any(cv.hex_int_range(0, 16777215), COOLIX_BASE_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @register_binary_sensor("coolix", CoolixBinarySensor, COOLIX_SENSOR_SCHEMA) | ||||||
| def coolix_binary_sensor(var, config): | def coolix_binary_sensor(var, config): | ||||||
|     cg.add( |     if isinstance(config, dict): | ||||||
|         var.set_data( |         cg.add( | ||||||
|             cg.StructInitializer( |             var.set_data( | ||||||
|                 CoolixData, |                 cg.StructInitializer( | ||||||
|                 ("data", config[CONF_DATA]), |                     CoolixData, | ||||||
|  |                     ("first", config[CONF_FIRST]), | ||||||
|  |                     ("second", config[CONF_SECOND]), | ||||||
|  |                 ) | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|     ) |     else: | ||||||
|  |         cg.add( | ||||||
|  |             var.set_data( | ||||||
|  |                 cg.StructInitializer(CoolixData, ("first", 0), ("second", config)) | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @register_action("coolix", CoolixAction, COOLIX_BASE_SCHEMA) | ||||||
|  | async def coolix_action(var, config, args): | ||||||
|  |     template_ = await cg.templatable(config[CONF_FIRST], args, cg.uint32) | ||||||
|  |     cg.add(var.set_first(template_)) | ||||||
|  |     template_ = await cg.templatable(config[CONF_SECOND], args, cg.uint32) | ||||||
|  |     cg.add(var.set_second(template_)) | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_trigger("coolix", CoolixTrigger, CoolixData) | @register_trigger("coolix", CoolixTrigger, CoolixData) | ||||||
| @@ -374,12 +405,6 @@ def coolix_dumper(var, config): | |||||||
|     pass |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_action("coolix", CoolixAction, COOLIX_SCHEMA) |  | ||||||
| async def coolix_action(var, config, args): |  | ||||||
|     template_ = await cg.templatable(config[CONF_DATA], args, cg.uint32) |  | ||||||
|     cg.add(var.set_data(template_)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Dish | # Dish | ||||||
| DishData, DishBinarySensor, DishTrigger, DishAction, DishDumper = declare_protocol( | DishData, DishBinarySensor, DishTrigger, DishAction, DishDumper = declare_protocol( | ||||||
|     "Dish" |     "Dish" | ||||||
|   | |||||||
| @@ -15,11 +15,21 @@ static const int32_t BIT_ZERO_SPACE_US = 1 * TICK_US; | |||||||
| static const int32_t FOOTER_MARK_US = 1 * TICK_US; | static const int32_t FOOTER_MARK_US = 1 * TICK_US; | ||||||
| static const int32_t FOOTER_SPACE_US = 10 * TICK_US; | static const int32_t FOOTER_SPACE_US = 10 * TICK_US; | ||||||
|  |  | ||||||
| static void encode_data(RemoteTransmitData *dst, const CoolixData &src) { | bool CoolixData::operator==(const CoolixData &other) const { | ||||||
|   //   Break data into bytes, starting at the Most Significant |   if (this->first == 0) | ||||||
|   //   Byte. Each byte then being sent normal, then followed inverted. |     return this->second == other.first || this->second == other.second; | ||||||
|  |   if (other.first == 0) | ||||||
|  |     return other.second == this->first || other.second == this->second; | ||||||
|  |   return this->first == other.first && this->second == other.second; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void encode_frame(RemoteTransmitData *dst, const uint32_t &src) { | ||||||
|  |   // Append header | ||||||
|  |   dst->item(HEADER_MARK_US, HEADER_SPACE_US); | ||||||
|  |   // Break data into bytes, starting at the Most Significant | ||||||
|  |   // Byte. Each byte then being sent normal, then followed inverted. | ||||||
|   for (unsigned shift = 16;; shift -= 8) { |   for (unsigned shift = 16;; shift -= 8) { | ||||||
|     // Grab a bytes worth of data. |     // Grab a bytes worth of data | ||||||
|     const uint8_t byte = src >> shift; |     const uint8_t byte = src >> shift; | ||||||
|     // Normal |     // Normal | ||||||
|     for (uint8_t mask = 1 << 7; mask; mask >>= 1) |     for (uint8_t mask = 1 << 7; mask; mask >>= 1) | ||||||
| @@ -27,27 +37,33 @@ static void encode_data(RemoteTransmitData *dst, const CoolixData &src) { | |||||||
|     // Inverted |     // Inverted | ||||||
|     for (uint8_t mask = 1 << 7; mask; mask >>= 1) |     for (uint8_t mask = 1 << 7; mask; mask >>= 1) | ||||||
|       dst->item(BIT_MARK_US, (byte & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US); |       dst->item(BIT_MARK_US, (byte & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US); | ||||||
|     // Data end |     // End of frame | ||||||
|     if (shift == 0) |     if (shift == 0) { | ||||||
|  |       // Append footer | ||||||
|  |       dst->mark(FOOTER_MARK_US); | ||||||
|       break; |       break; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void CoolixProtocol::encode(RemoteTransmitData *dst, const CoolixData &data) { | void CoolixProtocol::encode(RemoteTransmitData *dst, const CoolixData &data) { | ||||||
|   dst->set_carrier_frequency(38000); |   dst->set_carrier_frequency(38000); | ||||||
|   dst->reserve(2 + 2 * 48 + 2 + 2 + 2 * 48 + 1); |   dst->reserve(100 + 100 * data.has_second()); | ||||||
|   dst->item(HEADER_MARK_US, HEADER_SPACE_US); |   encode_frame(dst, data.first); | ||||||
|   encode_data(dst, data); |   if (data.has_second()) { | ||||||
|   dst->item(FOOTER_MARK_US, FOOTER_SPACE_US); |     dst->space(FOOTER_SPACE_US); | ||||||
|   dst->item(HEADER_MARK_US, HEADER_SPACE_US); |     encode_frame(dst, data.second); | ||||||
|   encode_data(dst, data); |   } | ||||||
|   dst->mark(FOOTER_MARK_US); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| static bool decode_data(RemoteReceiveData &src, CoolixData &dst) { | static bool decode_frame(RemoteReceiveData &src, uint32_t &dst) { | ||||||
|  |   // Checking for header | ||||||
|  |   if (!src.expect_item(HEADER_MARK_US, HEADER_SPACE_US)) | ||||||
|  |     return false; | ||||||
|  |   // Reading data | ||||||
|   uint32_t data = 0; |   uint32_t data = 0; | ||||||
|   for (unsigned n = 3;; data <<= 8) { |   for (unsigned n = 3;; data <<= 8) { | ||||||
|     // Read byte |     // Reading byte | ||||||
|     for (uint32_t mask = 1 << 7; mask; mask >>= 1) { |     for (uint32_t mask = 1 << 7; mask; mask >>= 1) { | ||||||
|       if (!src.expect_mark(BIT_MARK_US)) |       if (!src.expect_mark(BIT_MARK_US)) | ||||||
|         return false; |         return false; | ||||||
| @@ -57,13 +73,16 @@ static bool decode_data(RemoteReceiveData &src, CoolixData &dst) { | |||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     // Check for inverse byte |     // Checking for inverted byte | ||||||
|     for (uint32_t mask = 1 << 7; mask; mask >>= 1) { |     for (uint32_t mask = 1 << 7; mask; mask >>= 1) { | ||||||
|       if (!src.expect_item(BIT_MARK_US, (data & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US)) |       if (!src.expect_item(BIT_MARK_US, (data & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US)) | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|     // Checking the end of reading |     // End of frame | ||||||
|     if (--n == 0) { |     if (--n == 0) { | ||||||
|  |       // Checking for footer | ||||||
|  |       if (!src.expect_mark(FOOTER_MARK_US)) | ||||||
|  |         return false; | ||||||
|       dst = data; |       dst = data; | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
| @@ -71,15 +90,24 @@ static bool decode_data(RemoteReceiveData &src, CoolixData &dst) { | |||||||
| } | } | ||||||
|  |  | ||||||
| optional<CoolixData> CoolixProtocol::decode(RemoteReceiveData data) { | optional<CoolixData> CoolixProtocol::decode(RemoteReceiveData data) { | ||||||
|   CoolixData first, second; |   CoolixData result; | ||||||
|   if (data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && decode_data(data, first) && |   const auto size = data.size(); | ||||||
|       data.expect_item(FOOTER_MARK_US, FOOTER_SPACE_US) && data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && |   if ((size != 200 && size != 100) || !decode_frame(data, result.first)) | ||||||
|       decode_data(data, second) && data.expect_mark(FOOTER_MARK_US) && first == second) |     return {}; | ||||||
|     return first; |   if (size == 100 || !data.expect_space(FOOTER_SPACE_US) || !decode_frame(data, result.second)) | ||||||
|   return {}; |     result.second = 0; | ||||||
|  |   return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| void CoolixProtocol::dump(const CoolixData &data) { ESP_LOGD(TAG, "Received Coolix: 0x%06X", data); } | void CoolixProtocol::dump(const CoolixData &data) { | ||||||
|  |   if (data.is_strict()) { | ||||||
|  |     ESP_LOGD(TAG, "Received Coolix: 0x%06X", data.first); | ||||||
|  |   } else if (data.has_second()) { | ||||||
|  |     ESP_LOGD(TAG, "Received unstrict Coolix: [0x%06X, 0x%06X]", data.first, data.second); | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGD(TAG, "Received unstrict Coolix: [0x%06X]", data.first); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace remote_base | }  // namespace remote_base | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -7,7 +7,16 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace remote_base { | namespace remote_base { | ||||||
|  |  | ||||||
| using CoolixData = uint32_t; | struct CoolixData { | ||||||
|  |   CoolixData() {} | ||||||
|  |   CoolixData(uint32_t a) : first(a), second(a) {} | ||||||
|  |   CoolixData(uint32_t a, uint32_t b) : first(a), second(b) {} | ||||||
|  |   bool operator==(const CoolixData &other) const; | ||||||
|  |   bool is_strict() const { return this->first == this->second; } | ||||||
|  |   bool has_second() const { return this->second != 0; } | ||||||
|  |   uint32_t first; | ||||||
|  |   uint32_t second; | ||||||
|  | }; | ||||||
|  |  | ||||||
| class CoolixProtocol : public RemoteProtocol<CoolixData> { | class CoolixProtocol : public RemoteProtocol<CoolixData> { | ||||||
|  public: |  public: | ||||||
| @@ -19,10 +28,10 @@ class CoolixProtocol : public RemoteProtocol<CoolixData> { | |||||||
| DECLARE_REMOTE_PROTOCOL(Coolix) | DECLARE_REMOTE_PROTOCOL(Coolix) | ||||||
|  |  | ||||||
| template<typename... Ts> class CoolixAction : public RemoteTransmitterActionBase<Ts...> { | template<typename... Ts> class CoolixAction : public RemoteTransmitterActionBase<Ts...> { | ||||||
|   TEMPLATABLE_VALUE(CoolixData, data) |   TEMPLATABLE_VALUE(uint32_t, first) | ||||||
|  |   TEMPLATABLE_VALUE(uint32_t, second) | ||||||
|   void encode(RemoteTransmitData *dst, Ts... x) override { |   void encode(RemoteTransmitData *dst, Ts... x) override { | ||||||
|     CoolixData data = this->data_.value(x...); |     CoolixProtocol().encode(dst, {this->first_.value(x...), this->second_.value(x...)}); | ||||||
|     CoolixProtocol().encode(dst, data); |  | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1594,6 +1594,18 @@ binary_sensor: | |||||||
|           -2267, |           -2267, | ||||||
|           1709, |           1709, | ||||||
|         ] |         ] | ||||||
|  |   - platform: remote_receiver | ||||||
|  |     name: Coolix Test 1 | ||||||
|  |     coolix: 0xB21F98 | ||||||
|  |   - platform: remote_receiver | ||||||
|  |     name: Coolix Test 2 | ||||||
|  |     coolix: | ||||||
|  |       first: 0xB2E003 | ||||||
|  |   - platform: remote_receiver | ||||||
|  |     name: Coolix Test 3 | ||||||
|  |     coolix: | ||||||
|  |       first: 0xB2E003 | ||||||
|  |       second: 0xB21F98 | ||||||
|   - platform: as3935 |   - platform: as3935 | ||||||
|     name: Storm Alert |     name: Storm Alert | ||||||
|   - platform: analog_threshold |   - platform: analog_threshold | ||||||
| @@ -2265,8 +2277,16 @@ switch: | |||||||
|   - platform: template |   - platform: template | ||||||
|     name: MIDEA_RAW |     name: MIDEA_RAW | ||||||
|     turn_on_action: |     turn_on_action: | ||||||
|       remote_transmitter.transmit_midea: |       - remote_transmitter.transmit_coolix: | ||||||
|         code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF] |           first: 0xB21F98 | ||||||
|  |       - remote_transmitter.transmit_coolix: | ||||||
|  |           first: 0xB21F98 | ||||||
|  |           second: 0xB21F98 | ||||||
|  |       - remote_transmitter.transmit_coolix: | ||||||
|  |           first: !lambda "return 0xB21F98;" | ||||||
|  |           second: !lambda "return 0xB21F98;" | ||||||
|  |       - remote_transmitter.transmit_midea: | ||||||
|  |           code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF] | ||||||
|   - platform: gpio |   - platform: gpio | ||||||
|     name: "MCP23S08 Pin #0" |     name: "MCP23S08 Pin #0" | ||||||
|     pin: |     pin: | ||||||
| @@ -2846,6 +2866,9 @@ tm1651: | |||||||
| remote_receiver: | remote_receiver: | ||||||
|   pin: GPIO32 |   pin: GPIO32 | ||||||
|   dump: all |   dump: all | ||||||
|  |   on_coolix: | ||||||
|  |     then: | ||||||
|  |       delay: !lambda "return x.first + x.second;" | ||||||
|  |  | ||||||
| status_led: | status_led: | ||||||
|   pin: GPIO2 |   pin: GPIO2 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user