mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 08:28:12 +01:00
Adding support for Whynter ARC-14S/SH Air Conditioners (#3641)
This commit is contained in:
parent
77fb02729e
commit
ced423748e
6 changed files with 260 additions and 0 deletions
|
@ -238,6 +238,7 @@ esphome/components/version/* @esphome/core
|
||||||
esphome/components/wake_on_lan/* @willwill2will54
|
esphome/components/wake_on_lan/* @willwill2will54
|
||||||
esphome/components/web_server_base/* @OttoWinter
|
esphome/components/web_server_base/* @OttoWinter
|
||||||
esphome/components/whirlpool/* @glmnet
|
esphome/components/whirlpool/* @glmnet
|
||||||
|
esphome/components/whynter/* @aeonsablaze
|
||||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||||
esphome/components/xiaomi_mhoc303/* @drug123
|
esphome/components/xiaomi_mhoc303/* @drug123
|
||||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||||
|
|
1
esphome/components/whynter/__init__.py
Normal file
1
esphome/components/whynter/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@aeonsablaze"]
|
24
esphome/components/whynter/climate.py
Normal file
24
esphome/components/whynter/climate.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import climate_ir
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
AUTO_LOAD = ["climate_ir"]
|
||||||
|
|
||||||
|
whynter_ns = cg.esphome_ns.namespace("whynter")
|
||||||
|
Whynter = whynter_ns.class_("Whynter", climate_ir.ClimateIR)
|
||||||
|
|
||||||
|
CONF_USE_FAHRENHEIT = "use_fahrenheit"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(Whynter),
|
||||||
|
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await climate_ir.register_climate_ir(var, config)
|
||||||
|
cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT]))
|
181
esphome/components/whynter/whynter.cpp
Normal file
181
esphome/components/whynter/whynter.cpp
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
#include "whynter.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace whynter {
|
||||||
|
|
||||||
|
static const char *const TAG = "climate.whynter";
|
||||||
|
|
||||||
|
const uint16_t BITS = 32;
|
||||||
|
|
||||||
|
// Static First Byte
|
||||||
|
const uint32_t COMMAND_MASK = 0xFF << 24;
|
||||||
|
const uint32_t COMMAND_CODE = 0x12 << 24;
|
||||||
|
|
||||||
|
// Power
|
||||||
|
const uint32_t POWER_SHIFT = 8;
|
||||||
|
const uint32_t POWER_MASK = 1 << POWER_SHIFT;
|
||||||
|
const uint32_t POWER_OFF = 0 << POWER_SHIFT;
|
||||||
|
|
||||||
|
// Mode
|
||||||
|
const uint32_t MODE_SHIFT = 16;
|
||||||
|
const uint32_t MODE_MASK = 0b1111 << MODE_SHIFT;
|
||||||
|
const uint32_t MODE_FAN = 0b0001 << MODE_SHIFT;
|
||||||
|
const uint32_t MODE_DRY = 0b0010 << MODE_SHIFT;
|
||||||
|
const uint32_t MODE_HEAT = 0b0100 << MODE_SHIFT;
|
||||||
|
const uint32_t MODE_COOL = 0b1000 << MODE_SHIFT;
|
||||||
|
|
||||||
|
// Fan Speed
|
||||||
|
const uint32_t FAN_SHIFT = 20;
|
||||||
|
const uint32_t FAN_MASK = 0b111 << FAN_SHIFT;
|
||||||
|
const uint32_t FAN_HIGH = 0b001 << FAN_SHIFT;
|
||||||
|
const uint32_t FAN_MED = 0b010 << FAN_SHIFT;
|
||||||
|
const uint32_t FAN_LOW = 0b100 << FAN_SHIFT;
|
||||||
|
|
||||||
|
// Temperature Unit
|
||||||
|
const uint32_t UNIT_SHIFT = 10;
|
||||||
|
const uint32_t UNIT_MASK = 1 << UNIT_SHIFT;
|
||||||
|
|
||||||
|
// Temperature Value
|
||||||
|
const uint32_t TEMP_MASK = 0xFF;
|
||||||
|
const uint32_t TEMP_OFFSET_C = 16;
|
||||||
|
|
||||||
|
void Whynter::transmit_state() {
|
||||||
|
uint32_t remote_state = COMMAND_CODE;
|
||||||
|
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL)
|
||||||
|
this->mode = climate::CLIMATE_MODE_COOL;
|
||||||
|
switch (this->mode) {
|
||||||
|
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||||
|
remote_state |= POWER_MASK;
|
||||||
|
remote_state |= MODE_FAN;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_DRY:
|
||||||
|
remote_state |= POWER_MASK;
|
||||||
|
remote_state |= MODE_DRY;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_HEAT:
|
||||||
|
remote_state |= POWER_MASK;
|
||||||
|
remote_state |= MODE_HEAT;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_COOL:
|
||||||
|
remote_state |= POWER_MASK;
|
||||||
|
remote_state |= MODE_COOL;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_OFF:
|
||||||
|
remote_state |= POWER_OFF;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
remote_state |= POWER_OFF;
|
||||||
|
}
|
||||||
|
mode_before_ = this->mode;
|
||||||
|
|
||||||
|
switch (this->fan_mode.value()) {
|
||||||
|
case climate::CLIMATE_FAN_LOW:
|
||||||
|
remote_state |= FAN_LOW;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_MEDIUM:
|
||||||
|
remote_state |= FAN_MED;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_HIGH:
|
||||||
|
remote_state |= FAN_HIGH;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
remote_state |= FAN_HIGH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fahrenheit_) {
|
||||||
|
remote_state |= UNIT_MASK;
|
||||||
|
uint8_t temp =
|
||||||
|
(uint8_t) clamp<float>(esphome::celsius_to_fahrenheit(this->target_temperature), TEMP_MIN_F, TEMP_MAX_F);
|
||||||
|
temp = esphome::reverse_bits(temp);
|
||||||
|
remote_state |= temp;
|
||||||
|
} else {
|
||||||
|
uint8_t temp = (uint8_t) roundf(clamp<float>(this->target_temperature, TEMP_MIN_C, TEMP_MAX_C) - TEMP_OFFSET_C);
|
||||||
|
temp = esphome::reverse_bits(temp);
|
||||||
|
remote_state |= temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
transmit_(remote_state);
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Whynter::on_receive(remote_base::RemoteReceiveData data) {
|
||||||
|
uint8_t nbits = 0;
|
||||||
|
uint32_t remote_state = 0;
|
||||||
|
|
||||||
|
if (!data.expect_item(this->header_high_, this->header_low_))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (nbits = 0; nbits < 32; nbits++) {
|
||||||
|
if (data.expect_item(this->bit_high_, this->bit_one_low_)) {
|
||||||
|
remote_state = (remote_state << 1) | 1;
|
||||||
|
} else if (data.expect_item(this->bit_high_, this->bit_zero_low_)) {
|
||||||
|
remote_state = (remote_state << 1) | 0;
|
||||||
|
} else if (nbits == BITS) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Decoded 0x%02X", remote_state);
|
||||||
|
if ((remote_state & COMMAND_MASK) != COMMAND_CODE)
|
||||||
|
return false;
|
||||||
|
if ((remote_state & POWER_MASK) != POWER_MASK) {
|
||||||
|
this->mode = climate::CLIMATE_MODE_OFF;
|
||||||
|
} else {
|
||||||
|
if ((remote_state & MODE_MASK) == MODE_FAN) {
|
||||||
|
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||||
|
} else if ((remote_state & MODE_MASK) == MODE_DRY) {
|
||||||
|
this->mode = climate::CLIMATE_MODE_DRY;
|
||||||
|
} else if ((remote_state & MODE_MASK) == MODE_HEAT) {
|
||||||
|
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||||
|
} else if ((remote_state & MODE_MASK) == MODE_COOL) {
|
||||||
|
this->mode = climate::CLIMATE_MODE_COOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temperature
|
||||||
|
if ((remote_state & UNIT_MASK) == UNIT_MASK) { // Fahrenheit
|
||||||
|
this->target_temperature = esphome::fahrenheit_to_celsius(esphome::reverse_bits(remote_state & TEMP_MASK) >> 24);
|
||||||
|
} else { // Celsius
|
||||||
|
this->target_temperature = (esphome::reverse_bits(remote_state & TEMP_MASK) >> 24) + TEMP_OFFSET_C;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fan Speed
|
||||||
|
if ((remote_state & FAN_MASK) == FAN_LOW) {
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||||
|
} else if ((remote_state & FAN_MASK) == FAN_MED) {
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||||
|
} else if ((remote_state & FAN_MASK) == FAN_HIGH) {
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->publish_state();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Whynter::transmit_(uint32_t value) {
|
||||||
|
ESP_LOGD(TAG, "Sending whynter code: 0x%02X", value);
|
||||||
|
|
||||||
|
auto transmit = this->transmitter_->transmit();
|
||||||
|
auto *data = transmit.get_data();
|
||||||
|
|
||||||
|
data->set_carrier_frequency(38000);
|
||||||
|
data->reserve(2 + BITS * 2u);
|
||||||
|
|
||||||
|
data->item(this->header_high_, this->header_low_);
|
||||||
|
|
||||||
|
for (uint32_t mask = 1UL << (BITS - 1); mask != 0; mask >>= 1) {
|
||||||
|
if (value & mask) {
|
||||||
|
data->item(this->bit_high_, this->bit_one_low_);
|
||||||
|
} else {
|
||||||
|
data->item(this->bit_high_, this->bit_zero_low_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data->mark(this->bit_high_);
|
||||||
|
transmit.perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace whynter
|
||||||
|
} // namespace esphome
|
51
esphome/components/whynter/whynter.h
Normal file
51
esphome/components/whynter/whynter.h
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/climate_ir/climate_ir.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace whynter {
|
||||||
|
|
||||||
|
// Temperature
|
||||||
|
const uint8_t TEMP_MIN_C = 16; // Celsius
|
||||||
|
const uint8_t TEMP_MAX_C = 32; // Celsius
|
||||||
|
const uint8_t TEMP_MIN_F = 61; // Fahrenheit
|
||||||
|
const uint8_t TEMP_MAX_F = 89; // Fahrenheit
|
||||||
|
|
||||||
|
class Whynter : public climate_ir::ClimateIR {
|
||||||
|
public:
|
||||||
|
Whynter()
|
||||||
|
: climate_ir::ClimateIR(TEMP_MIN_C, TEMP_MAX_C, 1.0, true, true,
|
||||||
|
{climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, {}) {}
|
||||||
|
|
||||||
|
/// Override control to change settings of the climate device.
|
||||||
|
void control(const climate::ClimateCall &call) override { climate_ir::ClimateIR::control(call); }
|
||||||
|
|
||||||
|
// Set use of Fahrenheit units
|
||||||
|
void set_fahrenheit(bool value) {
|
||||||
|
this->fahrenheit_ = value;
|
||||||
|
this->temperature_step_ = 1.0f;
|
||||||
|
this->minimum_temperature_ = esphome::fahrenheit_to_celsius(TEMP_MIN_F);
|
||||||
|
this->maximum_temperature_ = esphome::fahrenheit_to_celsius(TEMP_MAX_F);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
void transmit_(uint32_t value);
|
||||||
|
|
||||||
|
uint32_t header_high_ = 8000;
|
||||||
|
uint32_t header_low_ = 4000;
|
||||||
|
uint32_t bit_high_ = 600;
|
||||||
|
uint32_t bit_one_low_ = 1600;
|
||||||
|
uint32_t bit_zero_low_ = 550;
|
||||||
|
|
||||||
|
bool fahrenheit_{false};
|
||||||
|
|
||||||
|
climate::ClimateMode mode_before_{climate::CLIMATE_MODE_OFF};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace whynter
|
||||||
|
} // namespace esphome
|
|
@ -1897,6 +1897,8 @@ climate:
|
||||||
name: My Bedjet
|
name: My Bedjet
|
||||||
bedjet_id: my_bedjet_client
|
bedjet_id: my_bedjet_client
|
||||||
heat_mode: extended
|
heat_mode: extended
|
||||||
|
- platform: whynter
|
||||||
|
name: Whynter
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- id: climate_custom
|
- id: climate_custom
|
||||||
|
|
Loading…
Reference in a new issue