mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	add tcl112 receiver (#762)
This commit is contained in:
		
				
					committed by
					
						 Otto Winter
						Otto Winter
					
				
			
			
				
	
			
			
			
						parent
						
							84cfcf2b4a
						
					
				
				
					commit
					8292024306
				
			| @@ -1,20 +1,22 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import climate, remote_transmitter, sensor | from esphome.components import climate, remote_transmitter, remote_receiver, sensor | ||||||
| from esphome.const import CONF_ID, CONF_SENSOR | from esphome.const import CONF_ID, CONF_SENSOR | ||||||
|  |  | ||||||
| AUTO_LOAD = ['sensor'] | AUTO_LOAD = ['sensor', 'climate_ir'] | ||||||
|  |  | ||||||
| tcl112_ns = cg.esphome_ns.namespace('tcl112') | tcl112_ns = cg.esphome_ns.namespace('tcl112') | ||||||
| Tcl112Climate = tcl112_ns.class_('Tcl112Climate', climate.Climate, cg.Component) | Tcl112Climate = tcl112_ns.class_('Tcl112Climate', climate.Climate, cg.Component) | ||||||
|  |  | ||||||
| CONF_TRANSMITTER_ID = 'transmitter_id' | CONF_TRANSMITTER_ID = 'transmitter_id' | ||||||
|  | CONF_RECEIVER_ID = 'receiver_id' | ||||||
| CONF_SUPPORTS_HEAT = 'supports_heat' | CONF_SUPPORTS_HEAT = 'supports_heat' | ||||||
| CONF_SUPPORTS_COOL = 'supports_cool' | CONF_SUPPORTS_COOL = 'supports_cool' | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ | CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ | ||||||
|     cv.GenerateID(): cv.declare_id(Tcl112Climate), |     cv.GenerateID(): cv.declare_id(Tcl112Climate), | ||||||
|     cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent), |     cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent), | ||||||
|  |     cv.Optional(CONF_RECEIVER_ID): cv.use_id(remote_receiver.RemoteReceiverComponent), | ||||||
|     cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, |     cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, | ||||||
|     cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, |     cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, | ||||||
|     cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), |     cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), | ||||||
| @@ -31,6 +33,9 @@ def to_code(config): | |||||||
|     if CONF_SENSOR in config: |     if CONF_SENSOR in config: | ||||||
|         sens = yield cg.get_variable(config[CONF_SENSOR]) |         sens = yield cg.get_variable(config[CONF_SENSOR]) | ||||||
|         cg.add(var.set_sensor(sens)) |         cg.add(var.set_sensor(sens)) | ||||||
|  |     if CONF_RECEIVER_ID in config: | ||||||
|  |         receiver = yield cg.get_variable(config[CONF_RECEIVER_ID]) | ||||||
|  |         cg.add(receiver.register_listener(var)) | ||||||
|  |  | ||||||
|     transmitter = yield cg.get_variable(config[CONF_TRANSMITTER_ID]) |     transmitter = yield cg.get_variable(config[CONF_TRANSMITTER_ID]) | ||||||
|     cg.add(var.set_transmitter(transmitter)) |     cg.add(var.set_transmitter(transmitter)) | ||||||
|   | |||||||
| @@ -18,62 +18,15 @@ const uint8_t TCL112_AUTO = 8; | |||||||
| const uint8_t TCL112_POWER_MASK = 0x04; | const uint8_t TCL112_POWER_MASK = 0x04; | ||||||
|  |  | ||||||
| const uint8_t TCL112_HALF_DEGREE = 0b00100000; | const uint8_t TCL112_HALF_DEGREE = 0b00100000; | ||||||
| const float TCL112_TEMP_MAX = 31.0; |  | ||||||
| const float TCL112_TEMP_MIN = 16.0; |  | ||||||
|  |  | ||||||
| const uint16_t TCL112_HEADER_MARK = 3000; | const uint16_t TCL112_HEADER_MARK = 3100; | ||||||
| const uint16_t TCL112_HEADER_SPACE = 1650; | const uint16_t TCL112_HEADER_SPACE = 1650; | ||||||
| const uint16_t TCL112_BIT_MARK = 500; | const uint16_t TCL112_BIT_MARK = 500; | ||||||
| const uint16_t TCL112_ONE_SPACE = 1050; | const uint16_t TCL112_ONE_SPACE = 1100; | ||||||
| const uint16_t TCL112_ZERO_SPACE = 325; | const uint16_t TCL112_ZERO_SPACE = 350; | ||||||
| const uint32_t TCL112_GAP = TCL112_HEADER_SPACE; | const uint32_t TCL112_GAP = TCL112_HEADER_SPACE; | ||||||
|  |  | ||||||
| climate::ClimateTraits Tcl112Climate::traits() { | void Tcl112Climate::transmit_state() { | ||||||
|   auto traits = climate::ClimateTraits(); |  | ||||||
|   traits.set_supports_current_temperature(this->sensor_ != nullptr); |  | ||||||
|   traits.set_supports_auto_mode(true); |  | ||||||
|   traits.set_supports_cool_mode(this->supports_cool_); |  | ||||||
|   traits.set_supports_heat_mode(this->supports_heat_); |  | ||||||
|   traits.set_supports_two_point_target_temperature(false); |  | ||||||
|   traits.set_supports_away(false); |  | ||||||
|   traits.set_visual_min_temperature(TCL112_TEMP_MIN); |  | ||||||
|   traits.set_visual_max_temperature(TCL112_TEMP_MAX); |  | ||||||
|   traits.set_visual_temperature_step(.5f); |  | ||||||
|   return traits; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Tcl112Climate::setup() { |  | ||||||
|   if (this->sensor_) { |  | ||||||
|     this->sensor_->add_on_state_callback([this](float state) { |  | ||||||
|       this->current_temperature = state; |  | ||||||
|       // current temperature changed, publish state |  | ||||||
|       this->publish_state(); |  | ||||||
|     }); |  | ||||||
|     this->current_temperature = this->sensor_->state; |  | ||||||
|   } else |  | ||||||
|     this->current_temperature = NAN; |  | ||||||
|   // restore set points |  | ||||||
|   auto restore = this->restore_state_(); |  | ||||||
|   if (restore.has_value()) { |  | ||||||
|     restore->apply(this); |  | ||||||
|   } else { |  | ||||||
|     // restore from defaults |  | ||||||
|     this->mode = climate::CLIMATE_MODE_OFF; |  | ||||||
|     this->target_temperature = 24; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Tcl112Climate::control(const climate::ClimateCall &call) { |  | ||||||
|   if (call.get_mode().has_value()) |  | ||||||
|     this->mode = *call.get_mode(); |  | ||||||
|   if (call.get_target_temperature().has_value()) |  | ||||||
|     this->target_temperature = *call.get_target_temperature(); |  | ||||||
|  |  | ||||||
|   this->transmit_state_(); |  | ||||||
|   this->publish_state(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Tcl112Climate::transmit_state_() { |  | ||||||
|   uint8_t remote_state[TCL112_STATE_LENGTH] = {0}; |   uint8_t remote_state[TCL112_STATE_LENGTH] = {0}; | ||||||
|  |  | ||||||
|   // A known good state. (On, Cool, 24C) |   // A known good state. (On, Cool, 24C) | ||||||
| @@ -124,7 +77,10 @@ void Tcl112Climate::transmit_state_() { | |||||||
|   for (uint8_t checksum_byte = 0; checksum_byte < TCL112_STATE_LENGTH - 1; checksum_byte++) |   for (uint8_t checksum_byte = 0; checksum_byte < TCL112_STATE_LENGTH - 1; checksum_byte++) | ||||||
|     remote_state[TCL112_STATE_LENGTH - 1] += remote_state[checksum_byte]; |     remote_state[TCL112_STATE_LENGTH - 1] += remote_state[checksum_byte]; | ||||||
|  |  | ||||||
|   ESP_LOGV(TAG, "Sending tcl code: %u", remote_state[7]); |   ESP_LOGV(TAG, "Sending: %02X %02X %02X %02X   %02X %02X %02X %02X   %02X %02X %02X %02X   %02X %02X", remote_state[0], | ||||||
|  |            remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], remote_state[6], | ||||||
|  |            remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], remote_state[12], | ||||||
|  |            remote_state[13]); | ||||||
|  |  | ||||||
|   auto transmit = this->transmitter_->transmit(); |   auto transmit = this->transmitter_->transmit(); | ||||||
|   auto data = transmit.get_data(); |   auto data = transmit.get_data(); | ||||||
| @@ -148,5 +104,76 @@ void Tcl112Climate::transmit_state_() { | |||||||
|   transmit.perform(); |   transmit.perform(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool Tcl112Climate::on_receive(remote_base::RemoteReceiveData data) { | ||||||
|  |   // Validate header | ||||||
|  |   if (!data.expect_item(TCL112_HEADER_MARK, TCL112_HEADER_SPACE)) { | ||||||
|  |     ESP_LOGV(TAG, "Header fail"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t remote_state[TCL112_STATE_LENGTH] = {0}; | ||||||
|  |   // Read all bytes. | ||||||
|  |   for (int i = 0; i < TCL112_STATE_LENGTH; i++) { | ||||||
|  |     // Read bit | ||||||
|  |     for (int j = 0; j < 8; j++) { | ||||||
|  |       if (data.expect_item(TCL112_BIT_MARK, TCL112_ONE_SPACE)) | ||||||
|  |         remote_state[i] |= 1 << j; | ||||||
|  |       else if (!data.expect_item(TCL112_BIT_MARK, TCL112_ZERO_SPACE)) { | ||||||
|  |         ESP_LOGV(TAG, "Byte %d bit %d fail", i, j); | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // Validate footer | ||||||
|  |   if (!data.expect_mark(TCL112_BIT_MARK)) { | ||||||
|  |     ESP_LOGV(TAG, "Footer fail"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t checksum = 0; | ||||||
|  |   // Calculate & set the checksum for the current internal state of the remote. | ||||||
|  |   // Stored the checksum value in the last byte. | ||||||
|  |   for (uint8_t checksum_byte = 0; checksum_byte < TCL112_STATE_LENGTH - 1; checksum_byte++) | ||||||
|  |     checksum += remote_state[checksum_byte]; | ||||||
|  |   if (checksum != remote_state[TCL112_STATE_LENGTH - 1]) { | ||||||
|  |     ESP_LOGV(TAG, "Checksum fail"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGV(TAG, "Received: %02X %02X %02X %02X   %02X %02X %02X %02X   %02X %02X %02X %02X   %02X %02X", | ||||||
|  |            remote_state[0], remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], | ||||||
|  |            remote_state[6], remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], | ||||||
|  |            remote_state[12], remote_state[13]); | ||||||
|  |  | ||||||
|  |   // two first bytes are constant | ||||||
|  |   if (remote_state[0] != 0x23 || remote_state[1] != 0xCB) | ||||||
|  |     return false; | ||||||
|  |  | ||||||
|  |   if ((remote_state[5] & TCL112_POWER_MASK) == 0) { | ||||||
|  |     this->mode = climate::CLIMATE_MODE_OFF; | ||||||
|  |   } else { | ||||||
|  |     auto mode = remote_state[6] & 0x0F; | ||||||
|  |     switch (mode) { | ||||||
|  |       case TCL112_HEAT: | ||||||
|  |         this->mode = climate::CLIMATE_MODE_HEAT; | ||||||
|  |         break; | ||||||
|  |       case TCL112_COOL: | ||||||
|  |         this->mode = climate::CLIMATE_MODE_COOL; | ||||||
|  |         break; | ||||||
|  |       case TCL112_DRY: | ||||||
|  |       case TCL112_FAN: | ||||||
|  |       case TCL112_AUTO: | ||||||
|  |         this->mode = climate::CLIMATE_MODE_AUTO; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   auto temp = TCL112_TEMP_MAX - remote_state[7]; | ||||||
|  |   if (remote_state[12] & TCL112_HALF_DEGREE) | ||||||
|  |     temp += .5f; | ||||||
|  |   this->target_temperature = temp; | ||||||
|  |   this->publish_state(); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace tcl112 | }  // namespace tcl112 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -1,39 +1,23 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/components/climate_ir/climate_ir.h" | ||||||
| #include "esphome/core/automation.h" |  | ||||||
| #include "esphome/components/climate/climate.h" |  | ||||||
| #include "esphome/components/remote_base/remote_base.h" |  | ||||||
| #include "esphome/components/remote_transmitter/remote_transmitter.h" |  | ||||||
| #include "esphome/components/sensor/sensor.h" |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace tcl112 { | namespace tcl112 { | ||||||
|  |  | ||||||
| class Tcl112Climate : public climate::Climate, public Component { | // Temperature | ||||||
|  | const float TCL112_TEMP_MAX = 31.0; | ||||||
|  | const float TCL112_TEMP_MIN = 16.0; | ||||||
|  |  | ||||||
|  | class Tcl112Climate : public climate::ClimateIR { | ||||||
|  public: |  public: | ||||||
|   void setup() override; |   Tcl112Climate() : climate::ClimateIR(TCL112_TEMP_MIN, TCL112_TEMP_MAX, .5f) {} | ||||||
|   void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) { |  | ||||||
|     this->transmitter_ = transmitter; |  | ||||||
|   } |  | ||||||
|   void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } |  | ||||||
|   void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } |  | ||||||
|   void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } |  | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   /// Override control to change settings of the climate device. |  | ||||||
|   void control(const climate::ClimateCall &call) override; |  | ||||||
|   /// Return the traits of this controller. |  | ||||||
|   climate::ClimateTraits traits() override; |  | ||||||
|  |  | ||||||
|   /// Transmit via IR the state of this climate controller. |   /// Transmit via IR the state of this climate controller. | ||||||
|   void transmit_state_(); |   void transmit_state() override; | ||||||
|  |   /// Handle received IR Buffer | ||||||
|   bool supports_cool_{true}; |   bool on_receive(remote_base::RemoteReceiveData data) override; | ||||||
|   bool supports_heat_{true}; |  | ||||||
|  |  | ||||||
|   remote_transmitter::RemoteTransmitterComponent *transmitter_; |  | ||||||
|   sensor::Sensor *sensor_{nullptr}; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace tcl112 | }  // namespace tcl112 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user