Add support for Daikin BRC52A61

This commit is contained in:
Andrew Wong 2024-07-09 14:49:48 +08:00
parent 7a93dde5d4
commit a2f1e64dab
7 changed files with 400 additions and 0 deletions

View file

@ -103,6 +103,7 @@ esphome/components/current_based/* @djwmarcx
esphome/components/dac7678/* @NickB1 esphome/components/dac7678/* @NickB1
esphome/components/daikin_arc/* @MagicBear esphome/components/daikin_arc/* @MagicBear
esphome/components/daikin_brc/* @hagak esphome/components/daikin_brc/* @hagak
esphome/components/daikin_brc52a61/* @andrewkww
esphome/components/dallas_temp/* @ssieb esphome/components/dallas_temp/* @ssieb
esphome/components/daly_bms/* @s1lvi0 esphome/components/daly_bms/* @s1lvi0
esphome/components/dashboard_import/* @esphome/core esphome/components/dashboard_import/* @esphome/core

View file

@ -0,0 +1 @@
CODEOWNERS = ["@andrewkww"]

View 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)

View 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

View 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

View 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

View 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