mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 09:18:00 +01:00
Add support for Daikin BRC52A61
This commit is contained in:
parent
7a93dde5d4
commit
a2f1e64dab
7 changed files with 400 additions and 0 deletions
|
@ -103,6 +103,7 @@ esphome/components/current_based/* @djwmarcx
|
|||
esphome/components/dac7678/* @NickB1
|
||||
esphome/components/daikin_arc/* @MagicBear
|
||||
esphome/components/daikin_brc/* @hagak
|
||||
esphome/components/daikin_brc52a61/* @andrewkww
|
||||
esphome/components/dallas_temp/* @ssieb
|
||||
esphome/components/daly_bms/* @s1lvi0
|
||||
esphome/components/dashboard_import/* @esphome/core
|
||||
|
|
1
esphome/components/daikin_brc52a61/__init__.py
Normal file
1
esphome/components/daikin_brc52a61/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@andrewkww"]
|
20
esphome/components/daikin_brc52a61/climate.py
Normal file
20
esphome/components/daikin_brc52a61/climate.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
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"]
|
||||
|
||||
daikin_brc52a61_ns = cg.esphome_ns.namespace("daikin_brc52a61")
|
||||
DaikinClimate = daikin_brc52a61_ns.class_("DaikinClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DaikinClimate),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await climate_ir.register_climate_ir(var, config)
|
277
esphome/components/daikin_brc52a61/daikin_brc52a61.cpp
Normal file
277
esphome/components/daikin_brc52a61/daikin_brc52a61.cpp
Normal file
|
@ -0,0 +1,277 @@
|
|||
#include "daikin_brc52a61.h"
|
||||
#include "esphome/components/remote_base/remote_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace daikin_brc52a61 {
|
||||
|
||||
static const char *const TAG = "daikin_brc52a61.climate";
|
||||
|
||||
uint8_t uint2bcd(uint8_t dec) { return ((dec / 10) << 4) + (dec % 10); }
|
||||
|
||||
uint8_t bcd2uint(uint8_t bcd) { return ((bcd >> 4) * 10) + (bcd & 0xF); }
|
||||
|
||||
struct IRData {
|
||||
union {
|
||||
uint8_t bytes[8];
|
||||
|
||||
struct {
|
||||
uint8_t magic : 8;
|
||||
|
||||
uint8_t mode : 4;
|
||||
uint8_t fan_speed : 4;
|
||||
|
||||
uint8_t mins : 8;
|
||||
uint8_t hours : 8;
|
||||
|
||||
uint8_t on_hours : 6;
|
||||
uint8_t on_30mins : 1;
|
||||
uint8_t on_timer : 1;
|
||||
|
||||
uint8_t off_hours : 6;
|
||||
uint8_t off_30mins : 1;
|
||||
uint8_t off_timer : 1;
|
||||
|
||||
uint8_t temperature : 8;
|
||||
|
||||
uint8_t swing : 1;
|
||||
uint8_t sleep : 1;
|
||||
uint8_t unused : 1;
|
||||
uint8_t power : 1;
|
||||
uint8_t sum : 4;
|
||||
};
|
||||
};
|
||||
|
||||
void set_power(bool on) {
|
||||
// remote only supports toggle power on/off
|
||||
// since we cannot reliably know the current state, we cannot reliably
|
||||
// turn it on or off
|
||||
|
||||
// workaround by abusing timer on/off to set it to a known state
|
||||
|
||||
this->on_timer = 0;
|
||||
this->off_timer = 0;
|
||||
|
||||
this->hours = uint2bcd(00);
|
||||
this->mins = uint2bcd(00);
|
||||
|
||||
if (on) {
|
||||
this->on_hours = 0;
|
||||
this->on_30mins = 0;
|
||||
this->on_timer = 1;
|
||||
} else {
|
||||
this->off_hours = 0;
|
||||
this->off_30mins = 0;
|
||||
this->off_timer = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void set_checksum() {
|
||||
this->sum = 0;
|
||||
|
||||
uint8_t sum = 0;
|
||||
for (auto &byte : this->bytes) {
|
||||
sum += byte & 0xF;
|
||||
sum += byte >> 4;
|
||||
}
|
||||
sum &= 0xF;
|
||||
|
||||
this->sum = sum;
|
||||
}
|
||||
};
|
||||
|
||||
void DaikinClimate::transmit_state() {
|
||||
IRData irdata{{{0x16}}};
|
||||
|
||||
irdata.mode = this->operation_mode_();
|
||||
|
||||
irdata.fan_speed = this->fan_speed_();
|
||||
|
||||
irdata.temperature = this->temperature_();
|
||||
|
||||
irdata.swing = this->swing_();
|
||||
|
||||
irdata.set_power(this->mode != climate::CLIMATE_MODE_OFF);
|
||||
|
||||
irdata.set_checksum();
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto *data = transmit.get_data();
|
||||
data->set_carrier_frequency(DAIKIN_IR_FREQUENCY);
|
||||
|
||||
data->mark(DAIKIN_PRE_MARK);
|
||||
data->space(DAIKIN_PRE_SPACE);
|
||||
data->mark(DAIKIN_PRE_MARK);
|
||||
data->space(DAIKIN_PRE_SPACE);
|
||||
|
||||
data->mark(DAIKIN_HEADER_MARK);
|
||||
data->space(DAIKIN_HEADER_SPACE);
|
||||
for (auto byte : irdata.bytes) {
|
||||
for (int8_t i = 0; i < 8; i++) { // iterate through bit mask
|
||||
data->mark(DAIKIN_BIT_MARK);
|
||||
data->space(byte & 1 ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE);
|
||||
byte >>= 1;
|
||||
}
|
||||
}
|
||||
data->mark(DAIKIN_BIT_MARK);
|
||||
data->space(DAIKIN_FOOTER_SPACE);
|
||||
data->mark(DAIKIN_FOOTER_MARK);
|
||||
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
uint8_t DaikinClimate::operation_mode_() {
|
||||
uint8_t operating_mode = DAIKIN_MODE_AUTO;
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
operating_mode = DAIKIN_MODE_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
operating_mode = DAIKIN_MODE_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
operating_mode = DAIKIN_MODE_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
operating_mode = DAIKIN_MODE_FAN;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
operating_mode = DAIKIN_MODE_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
operating_mode = DAIKIN_MODE_AUTO;
|
||||
break;
|
||||
}
|
||||
return operating_mode;
|
||||
}
|
||||
|
||||
uint8_t DaikinClimate::fan_speed_() {
|
||||
uint8_t fan_speed = DAIKIN_FAN_AUTO;
|
||||
switch (this->fan_mode.value()) {
|
||||
case climate::CLIMATE_FAN_QUIET:
|
||||
fan_speed = DAIKIN_FAN_QUIET;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed = DAIKIN_FAN_LOW;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
fan_speed = DAIKIN_FAN_MED;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
fan_speed = DAIKIN_FAN_HIGH;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
fan_speed = DAIKIN_FAN_AUTO;
|
||||
}
|
||||
return fan_speed;
|
||||
}
|
||||
|
||||
uint8_t DaikinClimate::temperature_() {
|
||||
uint8_t temperature = (uint8_t) roundf(clamp<float>(this->target_temperature, DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX));
|
||||
return uint2bcd(temperature);
|
||||
}
|
||||
|
||||
uint8_t DaikinClimate::swing_() {
|
||||
uint8_t swing_mode = 1;
|
||||
switch (this->swing_mode) {
|
||||
case climate::CLIMATE_SWING_VERTICAL:
|
||||
swing_mode = 1;
|
||||
break;
|
||||
case climate::CLIMATE_SWING_OFF:
|
||||
default:
|
||||
swing_mode = 0;
|
||||
break;
|
||||
}
|
||||
return swing_mode;
|
||||
}
|
||||
|
||||
bool DaikinClimate::parse_state_frame_(IRData &irdata) {
|
||||
const uint8_t sum = irdata.sum;
|
||||
irdata.set_checksum();
|
||||
if (sum != irdata.sum)
|
||||
return false;
|
||||
|
||||
switch (irdata.mode) {
|
||||
case DAIKIN_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case DAIKIN_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case DAIKIN_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case DAIKIN_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
case DAIKIN_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
default:
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
break;
|
||||
}
|
||||
|
||||
this->target_temperature = bcd2uint(irdata.temperature);
|
||||
|
||||
switch (irdata.fan_speed) {
|
||||
case DAIKIN_FAN_QUIET:
|
||||
this->fan_mode = climate::CLIMATE_FAN_QUIET;
|
||||
break;
|
||||
case DAIKIN_FAN_LOW:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case DAIKIN_FAN_MED:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case DAIKIN_FAN_HIGH:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
case DAIKIN_FAN_AUTO:
|
||||
default:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (irdata.swing) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
|
||||
} else {
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
}
|
||||
|
||||
this->publish_state();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
if (!(data.expect_item(DAIKIN_PRE_MARK, DAIKIN_PRE_SPACE) && data.expect_item(DAIKIN_PRE_MARK, DAIKIN_PRE_SPACE))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IRData irdata{{{0}}};
|
||||
for (auto &byte : irdata.bytes) {
|
||||
for (int8_t bit = 0; bit < 8; bit++) {
|
||||
if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE)) {
|
||||
byte |= 1 << bit;
|
||||
} else if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) {
|
||||
byte &= ~(1 << bit);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// frame header
|
||||
if (irdata.bytes[0] != 0x16)
|
||||
return false;
|
||||
|
||||
return this->parse_state_frame_(irdata);
|
||||
}
|
||||
|
||||
} // namespace daikin_brc52a61
|
||||
} // namespace esphome
|
63
esphome/components/daikin_brc52a61/daikin_brc52a61.h
Normal file
63
esphome/components/daikin_brc52a61/daikin_brc52a61.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/climate_ir/climate_ir.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace daikin_brc52a61 {
|
||||
|
||||
// Values for Daikin BRC52A61 IR Controllers
|
||||
// Temperature
|
||||
const uint8_t DAIKIN_TEMP_MIN = 16; // Celsius
|
||||
const uint8_t DAIKIN_TEMP_MAX = 30; // Celsius
|
||||
|
||||
// Modes
|
||||
const uint8_t DAIKIN_MODE_AUTO = 0x00;
|
||||
const uint8_t DAIKIN_MODE_DRY = 0x01;
|
||||
const uint8_t DAIKIN_MODE_COOL = 0x02;
|
||||
const uint8_t DAIKIN_MODE_FAN = 0x04;
|
||||
const uint8_t DAIKIN_MODE_HEAT = 0x08;
|
||||
|
||||
// Fan Speed
|
||||
const uint8_t DAIKIN_FAN_AUTO = 0x01;
|
||||
const uint8_t DAIKIN_FAN_TURBO = 0x03;
|
||||
const uint8_t DAIKIN_FAN_HIGH = 0x02;
|
||||
const uint8_t DAIKIN_FAN_MED = 0x04;
|
||||
const uint8_t DAIKIN_FAN_LOW = 0x08;
|
||||
const uint8_t DAIKIN_FAN_QUIET = 0x09;
|
||||
|
||||
// IR Transmission
|
||||
const uint32_t DAIKIN_IR_FREQUENCY = 38000;
|
||||
const uint32_t DAIKIN_PRE_MARK = 9800;
|
||||
const uint32_t DAIKIN_PRE_SPACE = 9800;
|
||||
|
||||
const uint32_t DAIKIN_HEADER_MARK = 4600;
|
||||
const uint32_t DAIKIN_HEADER_SPACE = 2500;
|
||||
const uint32_t DAIKIN_BIT_MARK = 400;
|
||||
const uint32_t DAIKIN_ONE_SPACE = 920;
|
||||
const uint32_t DAIKIN_ZERO_SPACE = 340;
|
||||
const uint32_t DAIKIN_FOOTER_MARK = 4600;
|
||||
const uint32_t DAIKIN_FOOTER_SPACE = 20000;
|
||||
|
||||
class DaikinClimate : public climate_ir::ClimateIR {
|
||||
public:
|
||||
DaikinClimate()
|
||||
: climate_ir::ClimateIR(DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true,
|
||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_QUIET, climate::CLIMATE_FAN_LOW,
|
||||
climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
|
||||
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}
|
||||
|
||||
protected:
|
||||
// Transmit via IR the state of this climate controller.
|
||||
void transmit_state() override;
|
||||
uint8_t operation_mode_();
|
||||
uint8_t fan_speed_();
|
||||
uint8_t temperature_();
|
||||
uint8_t swing_();
|
||||
|
||||
// Handle received IR Buffer
|
||||
bool parse_state_frame_(struct IRData &irdata);
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||
};
|
||||
|
||||
} // namespace daikin_brc52a61
|
||||
} // namespace esphome
|
19
tests/components/daikin_brc52a61/test.esp32-ard.yaml
Normal file
19
tests/components/daikin_brc52a61/test.esp32-ard.yaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
remote_transmitter:
|
||||
pin: 2
|
||||
carrier_duty_percent: 50%
|
||||
id: tsvr
|
||||
|
||||
remote_receiver:
|
||||
id: rcvr
|
||||
pin:
|
||||
number: 27
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
tolerance: 40%
|
||||
|
||||
climate:
|
||||
- platform: daikin_brc52a61
|
||||
name: "AC"
|
||||
receiver_id: rcvr
|
19
tests/components/daikin_brc52a61/test.esp8266-ard.yaml
Normal file
19
tests/components/daikin_brc52a61/test.esp8266-ard.yaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
remote_transmitter:
|
||||
pin: 5
|
||||
carrier_duty_percent: 50%
|
||||
id: tsvr
|
||||
|
||||
remote_receiver:
|
||||
id: rcvr
|
||||
pin:
|
||||
number: 2
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
tolerance: 40%
|
||||
|
||||
climate:
|
||||
- platform: daikin_brc52a61
|
||||
name: "AC"
|
||||
receiver_id: rcvr
|
Loading…
Reference in a new issue