mirror of
https://github.com/esphome/esphome.git
synced 2024-11-26 00:48:19 +01:00
Climate whirlpool (#1029)
* wip * transmitter ready * climate.whirlpool receiver implemented (#971) * receiver implemented * Support for two models of temp ranges * temperature type and lint * more lint Co-authored-by: Guillermo Ruffino <glm.net@gmail.com> * add test * not mess line endings Co-authored-by: mmanza <40872469+mmanza@users.noreply.github.com>
This commit is contained in:
parent
d447548893
commit
8040e3cf95
5 changed files with 380 additions and 0 deletions
0
esphome/components/whirlpool/__init__.py
Normal file
0
esphome/components/whirlpool/__init__.py
Normal file
26
esphome/components/whirlpool/climate.py
Normal file
26
esphome/components/whirlpool/climate.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import climate_ir
|
||||||
|
from esphome.const import CONF_ID, CONF_MODEL
|
||||||
|
|
||||||
|
AUTO_LOAD = ['climate_ir']
|
||||||
|
|
||||||
|
whirlpool_ns = cg.esphome_ns.namespace('whirlpool')
|
||||||
|
WhirlpoolClimate = whirlpool_ns.class_('WhirlpoolClimate', climate_ir.ClimateIR)
|
||||||
|
|
||||||
|
Model = whirlpool_ns.enum('Model')
|
||||||
|
MODELS = {
|
||||||
|
'DG11J1-3A': Model.MODEL_DG11J1_3A,
|
||||||
|
'DG11J1-91': Model.MODEL_DG11J1_91,
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({
|
||||||
|
cv.GenerateID(): cv.declare_id(WhirlpoolClimate),
|
||||||
|
cv.Optional(CONF_MODEL, default='DG11J1-3A'): cv.enum(MODELS, upper=True)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield climate_ir.register_climate_ir(var, config)
|
||||||
|
cg.add(var.set_model(config[CONF_MODEL]))
|
289
esphome/components/whirlpool/whirlpool.cpp
Normal file
289
esphome/components/whirlpool/whirlpool.cpp
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
#include "whirlpool.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace whirlpool {
|
||||||
|
|
||||||
|
static const char *TAG = "whirlpool.climate";
|
||||||
|
|
||||||
|
const uint16_t WHIRLPOOL_HEADER_MARK = 9000;
|
||||||
|
const uint16_t WHIRLPOOL_HEADER_SPACE = 4494;
|
||||||
|
const uint16_t WHIRLPOOL_BIT_MARK = 572;
|
||||||
|
const uint16_t WHIRLPOOL_ONE_SPACE = 1659;
|
||||||
|
const uint16_t WHIRLPOOL_ZERO_SPACE = 553;
|
||||||
|
const uint32_t WHIRLPOOL_GAP = 7960;
|
||||||
|
|
||||||
|
const uint32_t WHIRLPOOL_CARRIER_FREQUENCY = 38000;
|
||||||
|
|
||||||
|
const uint8_t WHIRLPOOL_STATE_LENGTH = 21;
|
||||||
|
|
||||||
|
const uint8_t WHIRLPOOL_HEAT = 0;
|
||||||
|
const uint8_t WHIRLPOOL_DRY = 3;
|
||||||
|
const uint8_t WHIRLPOOL_COOL = 2;
|
||||||
|
const uint8_t WHIRLPOOL_FAN = 4;
|
||||||
|
const uint8_t WHIRLPOOL_AUTO = 1;
|
||||||
|
|
||||||
|
const uint8_t WHIRLPOOL_FAN_AUTO = 0;
|
||||||
|
const uint8_t WHIRLPOOL_FAN_HIGH = 1;
|
||||||
|
const uint8_t WHIRLPOOL_FAN_MED = 2;
|
||||||
|
const uint8_t WHIRLPOOL_FAN_LOW = 3;
|
||||||
|
|
||||||
|
const uint8_t WHIRLPOOL_SWING_MASK = 128;
|
||||||
|
|
||||||
|
const uint8_t WHIRLPOOL_POWER = 0x04;
|
||||||
|
|
||||||
|
void WhirlpoolClimate::transmit_state() {
|
||||||
|
uint8_t remote_state[WHIRLPOOL_STATE_LENGTH] = {0};
|
||||||
|
remote_state[0] = 0x83;
|
||||||
|
remote_state[1] = 0x06;
|
||||||
|
remote_state[6] = 0x80;
|
||||||
|
// MODEL DG11J191
|
||||||
|
remote_state[18] = 0x08;
|
||||||
|
|
||||||
|
auto powered_on = this->mode != climate::CLIMATE_MODE_OFF;
|
||||||
|
if (powered_on != this->powered_on_assumed_) {
|
||||||
|
// Set power toggle command
|
||||||
|
remote_state[2] = 4;
|
||||||
|
remote_state[15] = 1;
|
||||||
|
this->powered_on_assumed_ = powered_on;
|
||||||
|
}
|
||||||
|
switch (this->mode) {
|
||||||
|
case climate::CLIMATE_MODE_AUTO:
|
||||||
|
// set fan auto
|
||||||
|
// set temp auto temp
|
||||||
|
// set sleep false
|
||||||
|
remote_state[3] = WHIRLPOOL_AUTO;
|
||||||
|
remote_state[15] = 0x17;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_HEAT:
|
||||||
|
remote_state[3] = WHIRLPOOL_HEAT;
|
||||||
|
remote_state[15] = 6;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_COOL:
|
||||||
|
remote_state[3] = WHIRLPOOL_COOL;
|
||||||
|
remote_state[15] = 6;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_DRY:
|
||||||
|
remote_state[3] = WHIRLPOOL_DRY;
|
||||||
|
remote_state[15] = 6;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||||
|
remote_state[3] = WHIRLPOOL_FAN;
|
||||||
|
remote_state[15] = 6;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_OFF:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temperature
|
||||||
|
auto temp = (uint8_t) roundf(clamp(this->target_temperature, this->temperature_min_(), this->temperature_max_()));
|
||||||
|
remote_state[3] |= (uint8_t)(temp - this->temperature_min_()) << 4;
|
||||||
|
|
||||||
|
// Fan speed
|
||||||
|
switch (this->fan_mode) {
|
||||||
|
case climate::CLIMATE_FAN_HIGH:
|
||||||
|
remote_state[2] |= WHIRLPOOL_FAN_HIGH;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_MEDIUM:
|
||||||
|
remote_state[2] |= WHIRLPOOL_FAN_MED;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_LOW:
|
||||||
|
remote_state[2] |= WHIRLPOOL_FAN_LOW;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swing
|
||||||
|
ESP_LOGV(TAG, "send swing %s", this->send_swing_cmd_ ? "true" : "false");
|
||||||
|
if (this->send_swing_cmd_) {
|
||||||
|
if (this->swing_mode == climate::CLIMATE_SWING_VERTICAL || this->swing_mode == climate::CLIMATE_SWING_OFF) {
|
||||||
|
remote_state[2] |= 128;
|
||||||
|
remote_state[8] |= 64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checksum
|
||||||
|
for (uint8_t i = 2; i < 12; i++)
|
||||||
|
remote_state[13] ^= remote_state[i];
|
||||||
|
for (uint8_t i = 14; i < 20; i++)
|
||||||
|
remote_state[20] ^= remote_state[i];
|
||||||
|
|
||||||
|
ESP_LOGV(TAG,
|
||||||
|
"Sending: %02X %02X %02X %02X %02X %02X %02X %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], remote_state[14], remote_state[15], remote_state[16], remote_state[17],
|
||||||
|
remote_state[18], remote_state[19], remote_state[20]);
|
||||||
|
|
||||||
|
// Send code
|
||||||
|
auto transmit = this->transmitter_->transmit();
|
||||||
|
auto data = transmit.get_data();
|
||||||
|
|
||||||
|
data->set_carrier_frequency(38000);
|
||||||
|
|
||||||
|
// Header
|
||||||
|
data->mark(WHIRLPOOL_HEADER_MARK);
|
||||||
|
data->space(WHIRLPOOL_HEADER_SPACE);
|
||||||
|
// Data
|
||||||
|
auto bytes_sent = 0;
|
||||||
|
for (uint8_t i : remote_state) {
|
||||||
|
for (uint8_t j = 0; j < 8; j++) {
|
||||||
|
data->mark(WHIRLPOOL_BIT_MARK);
|
||||||
|
bool bit = i & (1 << j);
|
||||||
|
data->space(bit ? WHIRLPOOL_ONE_SPACE : WHIRLPOOL_ZERO_SPACE);
|
||||||
|
}
|
||||||
|
bytes_sent++;
|
||||||
|
if (bytes_sent == 6 || bytes_sent == 14) {
|
||||||
|
// Divider
|
||||||
|
data->mark(WHIRLPOOL_BIT_MARK);
|
||||||
|
data->space(WHIRLPOOL_GAP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Footer
|
||||||
|
data->mark(WHIRLPOOL_BIT_MARK);
|
||||||
|
|
||||||
|
transmit.perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||||
|
// Validate header
|
||||||
|
if (!data.expect_item(WHIRLPOOL_HEADER_MARK, WHIRLPOOL_HEADER_SPACE)) {
|
||||||
|
ESP_LOGV(TAG, "Header fail");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t remote_state[WHIRLPOOL_STATE_LENGTH] = {0};
|
||||||
|
// Read all bytes.
|
||||||
|
for (int i = 0; i < WHIRLPOOL_STATE_LENGTH; i++) {
|
||||||
|
// Read bit
|
||||||
|
if (i == 6 || i == 14) {
|
||||||
|
if (!data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_GAP))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
if (data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ONE_SPACE))
|
||||||
|
remote_state[i] |= 1 << j;
|
||||||
|
|
||||||
|
else if (!data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ZERO_SPACE)) {
|
||||||
|
ESP_LOGV(TAG, "Byte %d bit %d fail", i, j);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "Byte %d %02X", i, remote_state[i]);
|
||||||
|
}
|
||||||
|
// Validate footer
|
||||||
|
if (!data.expect_mark(WHIRLPOOL_BIT_MARK)) {
|
||||||
|
ESP_LOGV(TAG, "Footer fail");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t checksum13 = 0;
|
||||||
|
uint8_t checksum20 = 0;
|
||||||
|
// Calculate checksum and compare with signal value.
|
||||||
|
for (uint8_t i = 2; i < 12; i++)
|
||||||
|
checksum13 ^= remote_state[i];
|
||||||
|
for (uint8_t i = 14; i < 20; i++)
|
||||||
|
checksum20 ^= remote_state[i];
|
||||||
|
|
||||||
|
if (checksum13 != remote_state[13] || checksum20 != remote_state[20]) {
|
||||||
|
ESP_LOGVV(TAG, "Checksum fail");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGV(
|
||||||
|
TAG,
|
||||||
|
"Received: %02X %02X %02X %02X %02X %02X %02X %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], remote_state[14], remote_state[15], remote_state[16], remote_state[17],
|
||||||
|
remote_state[18], remote_state[19], remote_state[20]);
|
||||||
|
|
||||||
|
// verify header remote code
|
||||||
|
if (remote_state[0] != 0x83 || remote_state[1] != 0x06)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// powr on/off button
|
||||||
|
ESP_LOGV(TAG, "Power: %02X", (remote_state[2] & WHIRLPOOL_POWER));
|
||||||
|
|
||||||
|
if ((remote_state[2] & WHIRLPOOL_POWER) == WHIRLPOOL_POWER) {
|
||||||
|
auto powered_on = this->mode != climate::CLIMATE_MODE_OFF;
|
||||||
|
|
||||||
|
if (powered_on) {
|
||||||
|
this->mode = climate::CLIMATE_MODE_OFF;
|
||||||
|
this->powered_on_assumed_ = false;
|
||||||
|
} else {
|
||||||
|
this->powered_on_assumed_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set received mode
|
||||||
|
if (powered_on_assumed_) {
|
||||||
|
auto mode = remote_state[3] & 0x7;
|
||||||
|
ESP_LOGV(TAG, "Mode: %02X", mode);
|
||||||
|
switch (mode) {
|
||||||
|
case WHIRLPOOL_HEAT:
|
||||||
|
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||||
|
break;
|
||||||
|
case WHIRLPOOL_COOL:
|
||||||
|
this->mode = climate::CLIMATE_MODE_COOL;
|
||||||
|
break;
|
||||||
|
case WHIRLPOOL_DRY:
|
||||||
|
this->mode = climate::CLIMATE_MODE_DRY;
|
||||||
|
break;
|
||||||
|
case WHIRLPOOL_FAN:
|
||||||
|
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||||
|
break;
|
||||||
|
case WHIRLPOOL_AUTO:
|
||||||
|
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set received temp
|
||||||
|
int temp = remote_state[3] & 0xF0;
|
||||||
|
ESP_LOGVV(TAG, "Temperature Raw: %02X", temp);
|
||||||
|
temp = (uint8_t) temp >> 4;
|
||||||
|
temp += static_cast<int>(this->temperature_min_());
|
||||||
|
ESP_LOGVV(TAG, "Temperature Climate: %u", temp);
|
||||||
|
this->target_temperature = temp;
|
||||||
|
|
||||||
|
// Set received fan speed
|
||||||
|
auto fan = remote_state[2] & 0x03;
|
||||||
|
ESP_LOGVV(TAG, "Fan: %02X", fan);
|
||||||
|
switch (fan) {
|
||||||
|
case WHIRLPOOL_FAN_HIGH:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||||
|
break;
|
||||||
|
case WHIRLPOOL_FAN_MED:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||||
|
break;
|
||||||
|
case WHIRLPOOL_FAN_LOW:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||||
|
break;
|
||||||
|
case WHIRLPOOL_FAN_AUTO:
|
||||||
|
default:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set received swing status
|
||||||
|
if ((remote_state[2] & WHIRLPOOL_SWING_MASK) == WHIRLPOOL_SWING_MASK && remote_state[8] == 0x40) {
|
||||||
|
ESP_LOGVV(TAG, "Swing toggle pressed ");
|
||||||
|
if (this->swing_mode == climate::CLIMATE_SWING_OFF) {
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
|
||||||
|
} else {
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->publish_state();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace whirlpool
|
||||||
|
} // namespace esphome
|
63
esphome/components/whirlpool/whirlpool.h
Normal file
63
esphome/components/whirlpool/whirlpool.h
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/climate_ir/climate_ir.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace whirlpool {
|
||||||
|
|
||||||
|
/// Simple enum to represent models.
|
||||||
|
enum Model {
|
||||||
|
MODEL_DG11J1_3A = 0, /// Temperature range is from 18 to 32
|
||||||
|
MODEL_DG11J1_91 = 1, /// Temperature range is from 16 to 30
|
||||||
|
};
|
||||||
|
|
||||||
|
// Temperature
|
||||||
|
const float WHIRLPOOL_DG11J1_3A_TEMP_MAX = 32.0;
|
||||||
|
const float WHIRLPOOL_DG11J1_3A_TEMP_MIN = 18.0;
|
||||||
|
const float WHIRLPOOL_DG11J1_91_TEMP_MAX = 30.0;
|
||||||
|
const float WHIRLPOOL_DG11J1_91_TEMP_MIN = 16.0;
|
||||||
|
|
||||||
|
class WhirlpoolClimate : public climate_ir::ClimateIR {
|
||||||
|
public:
|
||||||
|
WhirlpoolClimate()
|
||||||
|
: climate_ir::ClimateIR(temperature_min_(), temperature_max_(), 1.0f, true, true,
|
||||||
|
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
|
||||||
|
climate::CLIMATE_FAN_HIGH},
|
||||||
|
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}
|
||||||
|
|
||||||
|
void setup() override {
|
||||||
|
climate_ir::ClimateIR::setup();
|
||||||
|
|
||||||
|
this->powered_on_assumed_ = this->mode != climate::CLIMATE_MODE_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override control to change settings of the climate device.
|
||||||
|
void control(const climate::ClimateCall &call) override {
|
||||||
|
send_swing_cmd_ = call.get_swing_mode().has_value();
|
||||||
|
climate_ir::ClimateIR::control(call);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_model(Model model) { this->model_ = model; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Transmit via IR the state of this climate controller.
|
||||||
|
void transmit_state() override;
|
||||||
|
/// Handle received IR Buffer
|
||||||
|
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||||
|
|
||||||
|
// used to track when to send the power toggle command
|
||||||
|
bool powered_on_assumed_;
|
||||||
|
|
||||||
|
bool send_swing_cmd_{false};
|
||||||
|
Model model_;
|
||||||
|
|
||||||
|
float temperature_min_() {
|
||||||
|
return (model_ == MODEL_DG11J1_3A) ? WHIRLPOOL_DG11J1_3A_TEMP_MIN : WHIRLPOOL_DG11J1_91_TEMP_MIN;
|
||||||
|
}
|
||||||
|
float temperature_max_() {
|
||||||
|
return (model_ == MODEL_DG11J1_3A) ? WHIRLPOOL_DG11J1_3A_TEMP_MAX : WHIRLPOOL_DG11J1_91_TEMP_MAX;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace whirlpool
|
||||||
|
} // namespace esphome
|
|
@ -1228,6 +1228,8 @@ climate:
|
||||||
name: Yashima Climate
|
name: Yashima Climate
|
||||||
- platform: mitsubishi
|
- platform: mitsubishi
|
||||||
name: Mitsubishi
|
name: Mitsubishi
|
||||||
|
- platform: whirlpool
|
||||||
|
name: Whirlpool Climate
|
||||||
|
|
||||||
switch:
|
switch:
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
|
|
Loading…
Reference in a new issue