mirror of
https://github.com/esphome/esphome.git
synced 2024-12-20 16:18:49 +01:00
add tcl112 receiver (#762)
This commit is contained in:
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
|
||||||
|
Loading…
Reference in New Issue
Block a user