Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add tcl112 receiver #762

Merged
merged 1 commit into from Oct 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 7 additions & 2 deletions esphome/components/tcl112/climate.py
@@ -1,20 +1,22 @@
import esphome.codegen as cg
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

AUTO_LOAD = ['sensor']
AUTO_LOAD = ['sensor', 'climate_ir']

tcl112_ns = cg.esphome_ns.namespace('tcl112')
Tcl112Climate = tcl112_ns.class_('Tcl112Climate', climate.Climate, cg.Component)

CONF_TRANSMITTER_ID = 'transmitter_id'
CONF_RECEIVER_ID = 'receiver_id'
CONF_SUPPORTS_HEAT = 'supports_heat'
CONF_SUPPORTS_COOL = 'supports_cool'

CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(Tcl112Climate),
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_HEAT, default=True): cv.boolean,
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
Expand All @@ -31,6 +33,9 @@ def to_code(config):
if CONF_SENSOR in config:
sens = yield cg.get_variable(config[CONF_SENSOR])
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])
cg.add(var.set_transmitter(transmitter))
131 changes: 79 additions & 52 deletions esphome/components/tcl112/tcl112.cpp
Expand Up @@ -18,62 +18,15 @@ const uint8_t TCL112_AUTO = 8;
const uint8_t TCL112_POWER_MASK = 0x04;

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_BIT_MARK = 500;
const uint16_t TCL112_ONE_SPACE = 1050;
const uint16_t TCL112_ZERO_SPACE = 325;
const uint16_t TCL112_ONE_SPACE = 1100;
const uint16_t TCL112_ZERO_SPACE = 350;
const uint32_t TCL112_GAP = TCL112_HEADER_SPACE;

climate::ClimateTraits Tcl112Climate::traits() {
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_() {
void Tcl112Climate::transmit_state() {
uint8_t remote_state[TCL112_STATE_LENGTH] = {0};

// A known good state. (On, Cool, 24C)
Expand Down Expand Up @@ -124,7 +77,10 @@ void Tcl112Climate::transmit_state_() {
for (uint8_t checksum_byte = 0; checksum_byte < TCL112_STATE_LENGTH - 1; 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 data = transmit.get_data();
Expand All @@ -148,5 +104,76 @@ void Tcl112Climate::transmit_state_() {
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 esphome
36 changes: 10 additions & 26 deletions esphome/components/tcl112/tcl112.h
@@ -1,39 +1,23 @@
#pragma once

#include "esphome/core/component.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"
#include "esphome/components/climate_ir/climate_ir.h"

namespace esphome {
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:
void setup() override;
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; }
Tcl112Climate() : climate::ClimateIR(TCL112_TEMP_MIN, TCL112_TEMP_MAX, .5f) {}

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.
void transmit_state_();

bool supports_cool_{true};
bool supports_heat_{true};

remote_transmitter::RemoteTransmitterComponent *transmitter_;
sensor::Sensor *sensor_{nullptr};
void transmit_state() override;
/// Handle received IR Buffer
bool on_receive(remote_base::RemoteReceiveData data) override;
};

} // namespace tcl112
Expand Down