Initial commit
This commit is contained in:
commit
b757279401
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/*/
|
||||
!custom_components/
|
||||
**/__pycache__/
|
0
custom_components/toshiba/__init__.py
Executable file
0
custom_components/toshiba/__init__.py
Executable file
30
custom_components/toshiba/climate.py
Executable file
30
custom_components/toshiba/climate.py
Executable file
@ -0,0 +1,30 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import climate, remote_transmitter, sensor
|
||||
from esphome.const import CONF_ID, CONF_SENSOR
|
||||
|
||||
AUTO_LOAD = ['sensor']
|
||||
|
||||
toshiba_ns = cg.esphome_ns.namespace('toshiba')
|
||||
ToshibaClimate = toshiba_ns.class_('ToshibaClimate', climate.Climate, cg.Component)
|
||||
|
||||
CONF_TRANSMITTER_ID = 'transmitter_id'
|
||||
|
||||
CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(ToshibaClimate),
|
||||
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent),
|
||||
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||
}).extend(cv.COMPONENT_SCHEMA))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield climate.register_climate(var, config)
|
||||
|
||||
if CONF_SENSOR in config:
|
||||
sens = yield cg.get_variable(config[CONF_SENSOR])
|
||||
cg.add(var.set_sensor(sens))
|
||||
|
||||
transmitter = yield cg.get_variable(config[CONF_TRANSMITTER_ID])
|
||||
cg.add(var.set_transmitter(transmitter))
|
225
custom_components/toshiba/toshiba.cpp
Executable file
225
custom_components/toshiba/toshiba.cpp
Executable file
@ -0,0 +1,225 @@
|
||||
#include "toshiba.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace toshiba {
|
||||
|
||||
static const char *TAG = "toshiba.climate";
|
||||
|
||||
const uint32_t TOSHIBA_HDR_MARK = 4400;
|
||||
const uint32_t TOSHIBA_HDR_SPACE = 4400;
|
||||
const uint32_t TOSHIBA_BIT_MARK = 550;
|
||||
const uint32_t TOSHIBA_ONE_SPACE = 1600;
|
||||
const uint32_t TOSHIBA_ZERO_SPACE = 550;
|
||||
|
||||
const uint8_t TOSHIBA_FAN_AUTO = 0x00;
|
||||
const uint8_t TOSHIBA_MODE_OFF = 0xE0;
|
||||
const uint8_t TOSHIBA_MODE_AUTO = 0x00;
|
||||
const uint8_t TOSHIBA_MODE_HEAT = 0xC0;
|
||||
const uint8_t TOSHIBA_MODE_COOL = 0x80;
|
||||
const uint8_t TOSHIBA_MODE_DRY = 0x40;
|
||||
const uint8_t TOSHIBA_FAN1 = 0x02;
|
||||
const uint8_t TOSHIBA_FAN2 = 0x06;
|
||||
const uint8_t TOSHIBA_FAN3 = 0x01;
|
||||
const uint8_t TOSHIBA_FAN4 = 0x05;
|
||||
const uint8_t TOSHIBA_FAN5 = 0x03;
|
||||
const uint8_t TOSHIBA_PURIFIER = 0x08;
|
||||
|
||||
const uint8_t TOSHIBA_TEMP_MIN = 17;
|
||||
const uint8_t TOSHIBA_TEMP_MAX = 30;
|
||||
|
||||
|
||||
climate::ClimateTraits ToshibaClimate::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(TOSHIBA_TEMP_MIN);
|
||||
traits.set_visual_max_temperature(TOSHIBA_TEMP_MAX);
|
||||
traits.set_visual_temperature_step(1);
|
||||
return traits;
|
||||
}
|
||||
|
||||
// TEMP MODE CHECK
|
||||
// Off 0x4f 0xb0 0xc0 0x3f 0x80 0x06 0xe0 0x00 0x66
|
||||
// 23 A H 0x4f 0xb0 0xc0 0x3f 0x80 0x06 0xc0 0x00 0x46
|
||||
// AUTO 0x4f 0xb0 0xc0 0x3f 0x80 0x06 0x00 0x00 0x86
|
||||
// Pure 0x4f 0xb0 0xc0 0x3f 0x80 0x06 0x00 0x08 0x8e
|
||||
//
|
||||
// High power 0x4f 0xb0 0x20 0xdf 0x90 0x06 0xc0 0x00 0x80 0xd6
|
||||
// Sleep mode 0x4f 0xb0 0x20 0xdf 0x90 0x06 0xc0 0x00 0xc0 0x96
|
||||
// Fix 0x4f 0xb0 0x80 0x7f 0x84 0x00 0x84
|
||||
// Swing 0x4f 0xb0 0x80 0x7f 0x84 0x20 0xa4
|
||||
void ToshibaClimate::fix_louvre() {
|
||||
/*
|
||||
* Fix
|
||||
* 0x4f, 0xb0, 0x80, 0x7f, 0x84, 0x00, 0x84
|
||||
*/
|
||||
|
||||
ESP_LOGI(TAG, "SEND FIX COMMAND");
|
||||
uint8_t remote_state[] = { 0x4F, 0xB0, 0x80, 0x7F, 0x84, 0x00, 0x84 };
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto data = transmit.get_data();
|
||||
data->set_carrier_frequency(38000);
|
||||
|
||||
data->mark(TOSHIBA_HDR_MARK);
|
||||
data->space(TOSHIBA_HDR_SPACE);
|
||||
for (uint8_t i : remote_state)
|
||||
for (uint8_t j = 0; j < 8; j++) {
|
||||
data->mark(TOSHIBA_BIT_MARK);
|
||||
bool bit = i & (1 << j);
|
||||
data->space(bit ? TOSHIBA_ONE_SPACE : TOSHIBA_ZERO_SPACE);
|
||||
}
|
||||
|
||||
data->mark(TOSHIBA_BIT_MARK);
|
||||
data->space(0);
|
||||
transmit.perform();
|
||||
}
|
||||
void ToshibaClimate::swing_louvre() {
|
||||
/*
|
||||
* Swing
|
||||
* 0x4f, 0xb0, 0x80, 0x7f, 0x84, 0x20, 0xa4
|
||||
*/
|
||||
ESP_LOGI(TAG, "SEND SWING COMMAND");
|
||||
uint8_t remote_state[] = { 0x4F, 0xB0, 0x80, 0x7F, 0x84, 0x20, 0xA4 };
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto data = transmit.get_data();
|
||||
data->set_carrier_frequency(38000);
|
||||
|
||||
data->mark(TOSHIBA_HDR_MARK);
|
||||
data->space(TOSHIBA_HDR_SPACE);
|
||||
for (uint8_t i : remote_state)
|
||||
for (uint8_t j = 0; j < 8; j++) {
|
||||
data->mark(TOSHIBA_BIT_MARK);
|
||||
bool bit = i & (1 << j);
|
||||
data->space(bit ? TOSHIBA_ONE_SPACE : TOSHIBA_ZERO_SPACE);
|
||||
}
|
||||
|
||||
data->mark(TOSHIBA_BIT_MARK);
|
||||
data->space(0);
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
void ToshibaClimate::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_AUTO;
|
||||
// initialize target temperature to some value so that it's not NAN
|
||||
this->target_temperature = 23;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void ToshibaClimate::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();
|
||||
}
|
||||
|
||||
uint8_t reverse_byte(uint8_t in) {
|
||||
const uint8_t BITS = 8;
|
||||
uint8_t out = 0;
|
||||
for(int i = 0; i < BITS; i++) {
|
||||
if(in & (1 << i)) {
|
||||
out |= (1 << (BITS-1-i));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void ToshibaClimate::transmit_state_() {
|
||||
uint8_t operating_mode;
|
||||
uint8_t fan_speed = TOSHIBA_FAN_AUTO;
|
||||
uint8_t temperature = 23;
|
||||
uint8_t purifier = 0;
|
||||
if (this->pure_) {
|
||||
purifier = TOSHIBA_PURIFIER;
|
||||
}
|
||||
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
operating_mode = TOSHIBA_MODE_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
operating_mode = TOSHIBA_MODE_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
operating_mode = TOSHIBA_MODE_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
operating_mode = TOSHIBA_MODE_OFF;
|
||||
break;
|
||||
}
|
||||
|
||||
temperature = (uint8_t) roundf(clamp(this->target_temperature, TOSHIBA_TEMP_MIN, TOSHIBA_TEMP_MAX));
|
||||
|
||||
uint8_t temperatures[] = {
|
||||
0x00, 0x08, 0x04, 0x0c, 0x02, 0x0a, 0x06,
|
||||
0x0e, 0x01, 0x09, 0x05, 0x0d, 0x03, 0x0b
|
||||
};
|
||||
|
||||
uint8_t remote_state[] = {
|
||||
0x4F, 0xB0, 0xC0, 0x3F, 0x80,
|
||||
(uint8_t)(temperatures[temperature - TOSHIBA_TEMP_MIN]),
|
||||
(uint8_t)(operating_mode | fan_speed),
|
||||
(uint8_t)(purifier),
|
||||
0x00
|
||||
};
|
||||
|
||||
|
||||
uint8_t checksum{0};
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
checksum ^= reverse_byte(remote_state[i]);
|
||||
}
|
||||
remote_state[8] = reverse_byte(checksum);
|
||||
|
||||
ESP_LOGV(TAG, "Sending toshiba code: %u", remote_state);
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto data = transmit.get_data();
|
||||
|
||||
data->set_carrier_frequency(38000);
|
||||
|
||||
// Header
|
||||
data->mark(TOSHIBA_HDR_MARK);
|
||||
data->space(TOSHIBA_HDR_SPACE);
|
||||
// Data
|
||||
for (uint8_t i : remote_state)
|
||||
for (uint8_t j = 0; j < 8; j++) {
|
||||
data->mark(TOSHIBA_BIT_MARK);
|
||||
bool bit = i & (1 << j);
|
||||
data->space(bit ? TOSHIBA_ONE_SPACE : TOSHIBA_ZERO_SPACE);
|
||||
}
|
||||
// End mark
|
||||
data->mark(TOSHIBA_BIT_MARK);
|
||||
data->space(0);
|
||||
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
} // namespace toshiba
|
||||
} // namespace esphome
|
51
custom_components/toshiba/toshiba.h
Executable file
51
custom_components/toshiba/toshiba.h
Executable file
@ -0,0 +1,51 @@
|
||||
#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/switch/switch.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace toshiba {
|
||||
|
||||
|
||||
class ToshibaClimate : public climate::Climate, public Component {
|
||||
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; }
|
||||
|
||||
void fix_louvre();
|
||||
void swing_louvre();
|
||||
|
||||
void set_purifier(bool mode) {this->pure_ = mode; this->transmit_state_();}
|
||||
bool get_purifier() {return this->pure_;}
|
||||
|
||||
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};
|
||||
bool pure_{true};
|
||||
|
||||
remote_transmitter::RemoteTransmitterComponent *transmitter_;
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace toshiba
|
||||
} // namespace esphome
|
||||
|
72
heatpump.yaml
Executable file
72
heatpump.yaml
Executable file
@ -0,0 +1,72 @@
|
||||
# https://github.com/glmnet/esphome/blob/climate-toshiba/esphome/components/toshiba
|
||||
esphome:
|
||||
name: heatpump
|
||||
platform: ESP8266
|
||||
board: nodemcuv2
|
||||
|
||||
wifi:
|
||||
ssid: "XXXX"
|
||||
password: "XXXX"
|
||||
fast_connect: true
|
||||
output_power: 15
|
||||
manual_ip:
|
||||
static_ip: 192.168.2.132
|
||||
gateway: 192.168.2.1
|
||||
subnet: 255.255.255.0
|
||||
|
||||
ap:
|
||||
ssid: "Heatpump Fallback Hotspot"
|
||||
password: "XXXX"
|
||||
|
||||
captive_portal:
|
||||
logger:
|
||||
api:
|
||||
ota:
|
||||
|
||||
dallas:
|
||||
- pin: D6
|
||||
|
||||
sensor:
|
||||
- platform: dallas
|
||||
address: 0x990000071E28AD28
|
||||
name: Current temperature
|
||||
internal: true
|
||||
id: temp_sensor
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
name: LED
|
||||
pin:
|
||||
number: D4
|
||||
inverted: yes
|
||||
- platform: template
|
||||
name: Fix Louvre
|
||||
lambda: return false;
|
||||
turn_on_action:
|
||||
- lambda: |-
|
||||
id(heatpump).fix_louvre();
|
||||
- platform: template
|
||||
name: Swing Louvre
|
||||
lambda: return false;
|
||||
turn_on_action:
|
||||
- lambda: |-
|
||||
id(heatpump).swing_louvre();
|
||||
- platform: template
|
||||
name: Air purifier
|
||||
lambda: |-
|
||||
return id(heatpump).get_purifier();
|
||||
turn_on_action:
|
||||
- lambda: |-
|
||||
id(heatpump).set_purifier(true);
|
||||
turn_off_action:
|
||||
- lambda: |-
|
||||
id(heatpump).set_purifier(false);
|
||||
|
||||
remote_transmitter:
|
||||
pin: D7
|
||||
carrier_duty_percent: 50%
|
||||
climate:
|
||||
- platform: toshiba
|
||||
name: Varmepump
|
||||
id: heatpump
|
||||
sensor: temp_sensor
|
Loading…
x
Reference in New Issue
Block a user