mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	[remote_base] add support for Dyson cool AM07 tower fan (#10163)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
This commit is contained in:
		| @@ -17,6 +17,7 @@ from esphome.const import ( | |||||||
|     CONF_FAMILY, |     CONF_FAMILY, | ||||||
|     CONF_GROUP, |     CONF_GROUP, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|  |     CONF_INDEX, | ||||||
|     CONF_INVERTED, |     CONF_INVERTED, | ||||||
|     CONF_LEVEL, |     CONF_LEVEL, | ||||||
|     CONF_MAGNITUDE, |     CONF_MAGNITUDE, | ||||||
| @@ -616,6 +617,49 @@ async def dooya_action(var, config, args): | |||||||
|     cg.add(var.set_check(template_)) |     cg.add(var.set_check(template_)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Dyson | ||||||
|  | DysonData, DysonBinarySensor, DysonTrigger, DysonAction, DysonDumper = declare_protocol( | ||||||
|  |     "Dyson" | ||||||
|  | ) | ||||||
|  | DYSON_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_CODE): cv.hex_uint16_t, | ||||||
|  |         cv.Optional(CONF_INDEX, default=0xFF): cv.hex_uint8_t, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @register_binary_sensor("dyson", DysonBinarySensor, DYSON_SCHEMA) | ||||||
|  | def dyson_binary_sensor(var, config): | ||||||
|  |     cg.add( | ||||||
|  |         var.set_data( | ||||||
|  |             cg.StructInitializer( | ||||||
|  |                 DysonData, | ||||||
|  |                 ("code", config[CONF_CODE]), | ||||||
|  |                 ("index", config[CONF_INDEX]), | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @register_trigger("dyson", DysonTrigger, DysonData) | ||||||
|  | def dyson_trigger(var, config): | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @register_dumper("dyson", DysonDumper) | ||||||
|  | def dyson_dumper(var, config): | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @register_action("dyson", DysonAction, DYSON_SCHEMA) | ||||||
|  | async def dyson_action(var, config, args): | ||||||
|  |     template_ = await cg.templatable(config[CONF_CODE], args, cg.uint16) | ||||||
|  |     cg.add(var.set_code(template_)) | ||||||
|  |     template_ = await cg.templatable(config[CONF_INDEX], args, cg.uint8) | ||||||
|  |     cg.add(var.set_index(template_)) | ||||||
|  |  | ||||||
|  |  | ||||||
| # JVC | # JVC | ||||||
| JVCData, JVCBinarySensor, JVCTrigger, JVCAction, JVCDumper = declare_protocol("JVC") | JVCData, JVCBinarySensor, JVCTrigger, JVCAction, JVCDumper = declare_protocol("JVC") | ||||||
| JVC_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint32_t}) | JVC_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint32_t}) | ||||||
|   | |||||||
							
								
								
									
										71
									
								
								esphome/components/remote_base/dyson_protocol.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								esphome/components/remote_base/dyson_protocol.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | #include "dyson_protocol.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #include <cinttypes> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace remote_base { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "remote.dyson"; | ||||||
|  |  | ||||||
|  | // pulsewidth [µs] | ||||||
|  | constexpr uint32_t PW_MARK_US = 780; | ||||||
|  | constexpr uint32_t PW_SHORT_US = 720; | ||||||
|  | constexpr uint32_t PW_LONG_US = 1500; | ||||||
|  | constexpr uint32_t PW_START_US = 2280; | ||||||
|  |  | ||||||
|  | // MSB of 15 bit dyson code | ||||||
|  | constexpr uint16_t MSB_DYSON = (1 << 14); | ||||||
|  |  | ||||||
|  | // required symbols in transmit buffer = (start_symbol + 15 data_symbols) | ||||||
|  | constexpr uint32_t N_SYMBOLS_REQ = 2u * (1 + 15); | ||||||
|  |  | ||||||
|  | void DysonProtocol::encode(RemoteTransmitData *dst, const DysonData &data) { | ||||||
|  |   uint32_t raw_code = (data.code << 2) + (data.index & 3); | ||||||
|  |   dst->set_carrier_frequency(36000); | ||||||
|  |   dst->reserve(N_SYMBOLS_REQ + 1); | ||||||
|  |   dst->item(PW_START_US, PW_SHORT_US); | ||||||
|  |   for (uint16_t mask = MSB_DYSON; mask != 0; mask >>= 1) { | ||||||
|  |     if (mask == (mask & raw_code)) { | ||||||
|  |       dst->item(PW_MARK_US, PW_LONG_US); | ||||||
|  |     } else { | ||||||
|  |       dst->item(PW_MARK_US, PW_SHORT_US); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   dst->mark(PW_MARK_US);  // final carrier pulse | ||||||
|  | } | ||||||
|  |  | ||||||
|  | optional<DysonData> DysonProtocol::decode(RemoteReceiveData src) { | ||||||
|  |   uint32_t n_received = static_cast<uint32_t>(src.size()); | ||||||
|  |   uint16_t raw_code = 0; | ||||||
|  |   DysonData data{ | ||||||
|  |       .code = 0, | ||||||
|  |       .index = 0, | ||||||
|  |   }; | ||||||
|  |   if (n_received < N_SYMBOLS_REQ) | ||||||
|  |     return {};  // invalid frame length | ||||||
|  |   if (!src.expect_item(PW_START_US, PW_SHORT_US)) | ||||||
|  |     return {};  // start not found | ||||||
|  |   for (uint16_t mask = MSB_DYSON; mask != 0; mask >>= 1) { | ||||||
|  |     if (src.expect_item(PW_MARK_US, PW_SHORT_US)) { | ||||||
|  |       raw_code &= ~mask;  // zero detected | ||||||
|  |     } else if (src.expect_item(PW_MARK_US, PW_LONG_US)) { | ||||||
|  |       raw_code |= mask;  // one detected | ||||||
|  |     } else { | ||||||
|  |       return {};  // invalid data item | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   data.code = raw_code >> 2;          // extract button code | ||||||
|  |   data.index = raw_code & 3;          // extract rolling index | ||||||
|  |   if (src.expect_mark(PW_MARK_US)) {  // check total length | ||||||
|  |     return data; | ||||||
|  |   } | ||||||
|  |   return {};  // frame not complete | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DysonProtocol::dump(const DysonData &data) { | ||||||
|  |   ESP_LOGI(TAG, "Dyson: code=0x%x rolling index=%d", data.code, data.index); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace remote_base | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										46
									
								
								esphome/components/remote_base/dyson_protocol.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								esphome/components/remote_base/dyson_protocol.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "remote_base.h" | ||||||
|  |  | ||||||
|  | #include <cinttypes> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace remote_base { | ||||||
|  |  | ||||||
|  | static constexpr uint8_t IGNORE_INDEX = 0xFF; | ||||||
|  |  | ||||||
|  | struct DysonData { | ||||||
|  |   uint16_t code;  // the button, e.g. power, swing, fan++, ... | ||||||
|  |   uint8_t index;  // the rolling index counter | ||||||
|  |   bool operator==(const DysonData &rhs) const { | ||||||
|  |     if (IGNORE_INDEX == index || IGNORE_INDEX == rhs.index) { | ||||||
|  |       return code == rhs.code; | ||||||
|  |     } | ||||||
|  |     return code == rhs.code && index == rhs.index; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class DysonProtocol : public RemoteProtocol<DysonData> { | ||||||
|  |  public: | ||||||
|  |   void encode(RemoteTransmitData *dst, const DysonData &data) override; | ||||||
|  |   optional<DysonData> decode(RemoteReceiveData src) override; | ||||||
|  |   void dump(const DysonData &data) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | DECLARE_REMOTE_PROTOCOL(Dyson) | ||||||
|  |  | ||||||
|  | template<typename... Ts> class DysonAction : public RemoteTransmitterActionBase<Ts...> { | ||||||
|  |  public: | ||||||
|  |   TEMPLATABLE_VALUE(uint16_t, code) | ||||||
|  |   TEMPLATABLE_VALUE(uint8_t, index) | ||||||
|  |  | ||||||
|  |   void encode(RemoteTransmitData *dst, Ts... x) override { | ||||||
|  |     DysonData data{}; | ||||||
|  |     data.code = this->code_.value(x...); | ||||||
|  |     data.index = this->index_.value(x...); | ||||||
|  |     DysonProtocol().encode(dst, data); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace remote_base | ||||||
|  | }  // namespace esphome | ||||||
| @@ -48,6 +48,11 @@ on_drayton: | |||||||
|     - logger.log: |     - logger.log: | ||||||
|         format: "on_drayton: %u %u %u" |         format: "on_drayton: %u %u %u" | ||||||
|         args: ["x.address", "x.channel", "x.command"] |         args: ["x.address", "x.channel", "x.command"] | ||||||
|  | on_dyson: | ||||||
|  |   then: | ||||||
|  |     - logger.log: | ||||||
|  |         format: "on_dyson: %u %u" | ||||||
|  |         args: ["x.code", "x.index"] | ||||||
| on_gobox: | on_gobox: | ||||||
|   then: |   then: | ||||||
|     - logger.log: |     - logger.log: | ||||||
|   | |||||||
| @@ -6,6 +6,13 @@ button: | |||||||
|       remote_transmitter.transmit_beo4: |       remote_transmitter.transmit_beo4: | ||||||
|         source: 0x01 |         source: 0x01 | ||||||
|         command: 0x0C |         command: 0x0C | ||||||
|  |   - platform: template | ||||||
|  |     name: Dyson fan up | ||||||
|  |     id: dyson_fan_up | ||||||
|  |     on_press: | ||||||
|  |       remote_transmitter.transmit_dyson: | ||||||
|  |         code: 0x1215 | ||||||
|  |         index: 0x0 | ||||||
|   - platform: template |   - platform: template | ||||||
|     name: JVC Off |     name: JVC Off | ||||||
|     id: living_room_lights_on |     id: living_room_lights_on | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user