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()) | ||||
|     return false; | ||||
|   // 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); | ||||
|   if ((remote_state & 0xFF0000) != 0xB20000) | ||||
|     return false; | ||||
|   | ||||
| @@ -17,6 +17,7 @@ from esphome.const import ( | ||||
|     CONF_PROTOCOL, | ||||
|     CONF_GROUP, | ||||
|     CONF_DEVICE, | ||||
|     CONF_SECOND, | ||||
|     CONF_STATE, | ||||
|     CONF_CHANNEL, | ||||
|     CONF_FAMILY, | ||||
| @@ -39,6 +40,7 @@ AUTO_LOAD = ["binary_sensor"] | ||||
|  | ||||
| CONF_RECEIVER_ID = "receiver_id" | ||||
| CONF_TRANSMITTER_ID = "transmitter_id" | ||||
| CONF_FIRST = "first" | ||||
|  | ||||
| ns = remote_base_ns = cg.esphome_ns.namespace("remote_base") | ||||
| RemoteProtocol = ns.class_("RemoteProtocol") | ||||
| @@ -349,19 +351,48 @@ async def canalsatld_action(var, config, args): | ||||
|     CoolixAction, | ||||
|     CoolixDumper, | ||||
| ) = 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): | ||||
|     if isinstance(config, dict): | ||||
|         cg.add( | ||||
|             var.set_data( | ||||
|                 cg.StructInitializer( | ||||
|                     CoolixData, | ||||
|                 ("data", config[CONF_DATA]), | ||||
|                     ("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) | ||||
| @@ -374,12 +405,6 @@ def coolix_dumper(var, config): | ||||
|     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 | ||||
| DishData, DishBinarySensor, DishTrigger, DishAction, DishDumper = declare_protocol( | ||||
|     "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_SPACE_US = 10 * TICK_US; | ||||
|  | ||||
| static void encode_data(RemoteTransmitData *dst, const CoolixData &src) { | ||||
| bool CoolixData::operator==(const CoolixData &other) const { | ||||
|   if (this->first == 0) | ||||
|     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) { | ||||
|     // Grab a bytes worth of data. | ||||
|     // Grab a bytes worth of data | ||||
|     const uint8_t byte = src >> shift; | ||||
|     // Normal | ||||
|     for (uint8_t mask = 1 << 7; mask; mask >>= 1) | ||||
| @@ -27,27 +37,33 @@ static void encode_data(RemoteTransmitData *dst, const CoolixData &src) { | ||||
|     // Inverted | ||||
|     for (uint8_t mask = 1 << 7; mask; mask >>= 1) | ||||
|       dst->item(BIT_MARK_US, (byte & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US); | ||||
|     // Data end | ||||
|     if (shift == 0) | ||||
|     // End of frame | ||||
|     if (shift == 0) { | ||||
|       // Append footer | ||||
|       dst->mark(FOOTER_MARK_US); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void CoolixProtocol::encode(RemoteTransmitData *dst, const CoolixData &data) { | ||||
|   dst->set_carrier_frequency(38000); | ||||
|   dst->reserve(2 + 2 * 48 + 2 + 2 + 2 * 48 + 1); | ||||
|   dst->item(HEADER_MARK_US, HEADER_SPACE_US); | ||||
|   encode_data(dst, data); | ||||
|   dst->item(FOOTER_MARK_US, FOOTER_SPACE_US); | ||||
|   dst->item(HEADER_MARK_US, HEADER_SPACE_US); | ||||
|   encode_data(dst, data); | ||||
|   dst->mark(FOOTER_MARK_US); | ||||
|   dst->reserve(100 + 100 * data.has_second()); | ||||
|   encode_frame(dst, data.first); | ||||
|   if (data.has_second()) { | ||||
|     dst->space(FOOTER_SPACE_US); | ||||
|     encode_frame(dst, data.second); | ||||
|   } | ||||
| } | ||||
|  | ||||
| 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; | ||||
|   for (unsigned n = 3;; data <<= 8) { | ||||
|     // Read byte | ||||
|     // Reading byte | ||||
|     for (uint32_t mask = 1 << 7; mask; mask >>= 1) { | ||||
|       if (!src.expect_mark(BIT_MARK_US)) | ||||
|         return false; | ||||
| @@ -57,13 +73,16 @@ static bool decode_data(RemoteReceiveData &src, CoolixData &dst) { | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
|     // Check for inverse byte | ||||
|     // Checking for inverted byte | ||||
|     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)) | ||||
|         return false; | ||||
|     } | ||||
|     // Checking the end of reading | ||||
|     // End of frame | ||||
|     if (--n == 0) { | ||||
|       // Checking for footer | ||||
|       if (!src.expect_mark(FOOTER_MARK_US)) | ||||
|         return false; | ||||
|       dst = data; | ||||
|       return true; | ||||
|     } | ||||
| @@ -71,15 +90,24 @@ static bool decode_data(RemoteReceiveData &src, CoolixData &dst) { | ||||
| } | ||||
|  | ||||
| optional<CoolixData> CoolixProtocol::decode(RemoteReceiveData data) { | ||||
|   CoolixData first, second; | ||||
|   if (data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && decode_data(data, first) && | ||||
|       data.expect_item(FOOTER_MARK_US, FOOTER_SPACE_US) && data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && | ||||
|       decode_data(data, second) && data.expect_mark(FOOTER_MARK_US) && first == second) | ||||
|     return first; | ||||
|   CoolixData result; | ||||
|   const auto size = data.size(); | ||||
|   if ((size != 200 && size != 100) || !decode_frame(data, result.first)) | ||||
|     return {}; | ||||
|   if (size == 100 || !data.expect_space(FOOTER_SPACE_US) || !decode_frame(data, result.second)) | ||||
|     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 esphome | ||||
|   | ||||
| @@ -7,7 +7,16 @@ | ||||
| namespace esphome { | ||||
| 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> { | ||||
|  public: | ||||
| @@ -19,10 +28,10 @@ class CoolixProtocol : public RemoteProtocol<CoolixData> { | ||||
| DECLARE_REMOTE_PROTOCOL(Coolix) | ||||
|  | ||||
| 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 { | ||||
|     CoolixData data = this->data_.value(x...); | ||||
|     CoolixProtocol().encode(dst, data); | ||||
|     CoolixProtocol().encode(dst, {this->first_.value(x...), this->second_.value(x...)}); | ||||
|   } | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -1594,6 +1594,18 @@ binary_sensor: | ||||
|           -2267, | ||||
|           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 | ||||
|     name: Storm Alert | ||||
|   - platform: analog_threshold | ||||
| @@ -2265,7 +2277,15 @@ switch: | ||||
|   - platform: template | ||||
|     name: MIDEA_RAW | ||||
|     turn_on_action: | ||||
|       remote_transmitter.transmit_midea: | ||||
|       - remote_transmitter.transmit_coolix: | ||||
|           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 | ||||
|     name: "MCP23S08 Pin #0" | ||||
| @@ -2846,6 +2866,9 @@ tm1651: | ||||
| remote_receiver: | ||||
|   pin: GPIO32 | ||||
|   dump: all | ||||
|   on_coolix: | ||||
|     then: | ||||
|       delay: !lambda "return x.first + x.second;" | ||||
|  | ||||
| status_led: | ||||
|   pin: GPIO2 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user