mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 14:38:10 +01:00
feat: Add Daikin ARC (tested on Daikin ARC472A62) (#6429)
This commit is contained in:
parent
92b3d94cc7
commit
6b7f9b15ea
7 changed files with 621 additions and 0 deletions
|
@ -87,6 +87,7 @@ esphome/components/cst816/* @clydebarrow
|
||||||
esphome/components/ct_clamp/* @jesserockz
|
esphome/components/ct_clamp/* @jesserockz
|
||||||
esphome/components/current_based/* @djwmarcx
|
esphome/components/current_based/* @djwmarcx
|
||||||
esphome/components/dac7678/* @NickB1
|
esphome/components/dac7678/* @NickB1
|
||||||
|
esphome/components/daikin_arc/* @MagicBear
|
||||||
esphome/components/daikin_brc/* @hagak
|
esphome/components/daikin_brc/* @hagak
|
||||||
esphome/components/daly_bms/* @s1lvi0
|
esphome/components/daly_bms/* @s1lvi0
|
||||||
esphome/components/dashboard_import/* @esphome/core
|
esphome/components/dashboard_import/* @esphome/core
|
||||||
|
|
1
esphome/components/daikin_arc/__init__.py
Normal file
1
esphome/components/daikin_arc/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@MagicBear"]
|
18
esphome/components/daikin_arc/climate.py
Normal file
18
esphome/components/daikin_arc/climate.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
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_arc_ns = cg.esphome_ns.namespace("daikin_arc")
|
||||||
|
DaikinArcClimate = daikin_arc_ns.class_("DaikinArcClimate", climate_ir.ClimateIR)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
|
||||||
|
{cv.GenerateID(): cv.declare_id(DaikinArcClimate)}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await climate_ir.register_climate_ir(var, config)
|
487
esphome/components/daikin_arc/daikin_arc.cpp
Normal file
487
esphome/components/daikin_arc/daikin_arc.cpp
Normal file
|
@ -0,0 +1,487 @@
|
||||||
|
#include "daikin_arc.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "esphome/components/remote_base/remote_base.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace daikin_arc {
|
||||||
|
|
||||||
|
static const char *const TAG = "daikin.climate";
|
||||||
|
|
||||||
|
void DaikinArcClimate::setup() {
|
||||||
|
climate_ir::ClimateIR::setup();
|
||||||
|
|
||||||
|
// Never send nan to HA
|
||||||
|
if (std::isnan(this->target_humidity))
|
||||||
|
this->target_humidity = 0;
|
||||||
|
if (std::isnan(this->current_temperature))
|
||||||
|
this->current_temperature = 0;
|
||||||
|
if (std::isnan(this->current_humidity))
|
||||||
|
this->current_humidity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaikinArcClimate::transmit_query_() {
|
||||||
|
uint8_t remote_header[8] = {0x11, 0xDA, 0x27, 0x00, 0x84, 0x87, 0x20, 0x00};
|
||||||
|
|
||||||
|
// Calculate checksum
|
||||||
|
for (int i = 0; i < sizeof(remote_header) - 1; i++) {
|
||||||
|
remote_header[sizeof(remote_header) - 1] += remote_header[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto transmit = this->transmitter_->transmit();
|
||||||
|
auto *data = transmit.get_data();
|
||||||
|
data->set_carrier_frequency(DAIKIN_IR_FREQUENCY);
|
||||||
|
|
||||||
|
data->mark(DAIKIN_ARC_PRE_MARK);
|
||||||
|
data->space(DAIKIN_ARC_PRE_SPACE);
|
||||||
|
|
||||||
|
data->mark(DAIKIN_HEADER_MARK);
|
||||||
|
data->space(DAIKIN_HEADER_SPACE);
|
||||||
|
|
||||||
|
for (uint8_t i : remote_header) {
|
||||||
|
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
|
||||||
|
data->mark(DAIKIN_BIT_MARK);
|
||||||
|
bool bit = i & mask;
|
||||||
|
data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data->mark(DAIKIN_BIT_MARK);
|
||||||
|
data->space(0);
|
||||||
|
|
||||||
|
transmit.perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaikinArcClimate::transmit_state() {
|
||||||
|
// 0x11, 0xDA, 0x27, 0x00, 0xC5, 0x00, 0x00, 0xD7, 0x11, 0xDA, 0x27, 0x00,
|
||||||
|
// 0x42, 0x49, 0x05, 0xA2,
|
||||||
|
uint8_t remote_header[20] = {0x11, 0xDA, 0x27, 0x00, 0x02, 0xd0, 0x02, 0x03, 0x80, 0x03, 0x82, 0x30, 0x41, 0x1f, 0x82,
|
||||||
|
0xf4,
|
||||||
|
/* とつど */
|
||||||
|
/* 0x13 */
|
||||||
|
0x00, 0x24, 0x00, 0x00};
|
||||||
|
|
||||||
|
// 05 0 [1:3]MODE 1 [OFF TMR] [ON TMR] Power
|
||||||
|
// 06-07 TEMP
|
||||||
|
// 08 [0:3] SPEED [4:7] Swing
|
||||||
|
// 09 00
|
||||||
|
// 10 00
|
||||||
|
// 11, 12: timer
|
||||||
|
// 13 [0:6] 0000000 [7] POWERMODE
|
||||||
|
// 14 0a
|
||||||
|
// 15 c4
|
||||||
|
// 16 [0:3] 8 00 [6:7] SENSOR WIND = 11 / NORMAL = 00
|
||||||
|
// 17 24
|
||||||
|
|
||||||
|
uint8_t remote_state[19] = {
|
||||||
|
0x11, 0xDA, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x0a, 0xC4,
|
||||||
|
/* MODE TEMP HUMD FANH FANL
|
||||||
|
パワフル音声応答 */
|
||||||
|
/* ON
|
||||||
|
0x01入 0x0a */
|
||||||
|
/* OF
|
||||||
|
0x00切 0x02 */
|
||||||
|
0x80, 0x24, 0x00
|
||||||
|
/* センサー風 */
|
||||||
|
/* ON 0x83 */
|
||||||
|
/* OF 0x80 */
|
||||||
|
};
|
||||||
|
|
||||||
|
remote_state[5] = this->operation_mode_() | 0x08;
|
||||||
|
remote_state[6] = this->temperature_();
|
||||||
|
remote_state[7] = this->humidity_();
|
||||||
|
static uint8_t last_humidity = 0x66;
|
||||||
|
if (remote_state[7] != last_humidity && this->mode != climate::CLIMATE_MODE_OFF) {
|
||||||
|
ESP_LOGD(TAG, "Set Humditiy: %d, %d\n", (int) this->target_humidity, (int) remote_state[7]);
|
||||||
|
remote_header[9] |= 0x10;
|
||||||
|
last_humidity = remote_state[7];
|
||||||
|
}
|
||||||
|
uint16_t fan_speed = this->fan_speed_();
|
||||||
|
remote_state[8] = fan_speed >> 8;
|
||||||
|
remote_state[9] = fan_speed & 0xff;
|
||||||
|
|
||||||
|
// Calculate checksum
|
||||||
|
for (int i = 0; i < sizeof(remote_header) - 1; i++) {
|
||||||
|
remote_header[sizeof(remote_header) - 1] += remote_header[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate checksum
|
||||||
|
for (int i = 0; i < DAIKIN_STATE_FRAME_SIZE - 1; i++) {
|
||||||
|
remote_state[DAIKIN_STATE_FRAME_SIZE - 1] += remote_state[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto transmit = this->transmitter_->transmit();
|
||||||
|
auto *data = transmit.get_data();
|
||||||
|
data->set_carrier_frequency(DAIKIN_IR_FREQUENCY);
|
||||||
|
|
||||||
|
data->mark(DAIKIN_ARC_PRE_MARK);
|
||||||
|
data->space(DAIKIN_ARC_PRE_SPACE);
|
||||||
|
|
||||||
|
data->mark(DAIKIN_HEADER_MARK);
|
||||||
|
data->space(DAIKIN_HEADER_SPACE);
|
||||||
|
|
||||||
|
for (uint8_t i : remote_header) {
|
||||||
|
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
|
||||||
|
data->mark(DAIKIN_BIT_MARK);
|
||||||
|
bool bit = i & mask;
|
||||||
|
data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data->mark(DAIKIN_BIT_MARK);
|
||||||
|
data->space(DAIKIN_MESSAGE_SPACE);
|
||||||
|
|
||||||
|
data->mark(DAIKIN_HEADER_MARK);
|
||||||
|
data->space(DAIKIN_HEADER_SPACE);
|
||||||
|
|
||||||
|
for (uint8_t i : remote_state) {
|
||||||
|
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
|
||||||
|
data->mark(DAIKIN_BIT_MARK);
|
||||||
|
bool bit = i & mask;
|
||||||
|
data->space(bit ? DAIKIN_ONE_SPACE : DAIKIN_ZERO_SPACE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data->mark(DAIKIN_BIT_MARK);
|
||||||
|
data->space(0);
|
||||||
|
|
||||||
|
transmit.perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t DaikinArcClimate::operation_mode_() {
|
||||||
|
uint8_t operating_mode = DAIKIN_MODE_ON;
|
||||||
|
switch (this->mode) {
|
||||||
|
case climate::CLIMATE_MODE_COOL:
|
||||||
|
operating_mode |= DAIKIN_MODE_COOL;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_DRY:
|
||||||
|
operating_mode |= DAIKIN_MODE_DRY;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_HEAT:
|
||||||
|
operating_mode |= DAIKIN_MODE_HEAT;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||||
|
operating_mode |= DAIKIN_MODE_AUTO;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||||
|
operating_mode |= DAIKIN_MODE_FAN;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_MODE_OFF:
|
||||||
|
default:
|
||||||
|
operating_mode = DAIKIN_MODE_OFF;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return operating_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t DaikinArcClimate::fan_speed_() {
|
||||||
|
uint16_t fan_speed;
|
||||||
|
switch (this->fan_mode.value()) {
|
||||||
|
case climate::CLIMATE_FAN_LOW:
|
||||||
|
fan_speed = DAIKIN_FAN_1 << 8;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_MEDIUM:
|
||||||
|
fan_speed = DAIKIN_FAN_3 << 8;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_HIGH:
|
||||||
|
fan_speed = DAIKIN_FAN_5 << 8;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_AUTO:
|
||||||
|
default:
|
||||||
|
fan_speed = DAIKIN_FAN_AUTO << 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If swing is enabled switch first 4 bits to 1111
|
||||||
|
switch (this->swing_mode) {
|
||||||
|
case climate::CLIMATE_SWING_VERTICAL:
|
||||||
|
fan_speed |= 0x0F00;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||||
|
fan_speed |= 0x000F;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_SWING_BOTH:
|
||||||
|
fan_speed |= 0x0F0F;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return fan_speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t DaikinArcClimate::temperature_() {
|
||||||
|
// Force special temperatures depending on the mode
|
||||||
|
switch (this->mode) {
|
||||||
|
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||||
|
return 0x32;
|
||||||
|
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||||
|
case climate::CLIMATE_MODE_DRY:
|
||||||
|
return 0xc0;
|
||||||
|
default:
|
||||||
|
float new_temp = clamp<float>(this->target_temperature, DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX);
|
||||||
|
uint8_t temperature = (uint8_t) floor(new_temp);
|
||||||
|
return temperature << 1 | (new_temp - temperature > 0 ? 0x01 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t DaikinArcClimate::humidity_() {
|
||||||
|
if (this->target_humidity == 39) {
|
||||||
|
return 0;
|
||||||
|
} else if (this->target_humidity <= 40 || this->target_humidity == 44) {
|
||||||
|
return 40;
|
||||||
|
} else if (this->target_humidity <= 45 || this->target_humidity == 49) // 41 - 45
|
||||||
|
{
|
||||||
|
return 45;
|
||||||
|
} else if (this->target_humidity <= 50 || this->target_humidity == 52) // 45 - 50
|
||||||
|
{
|
||||||
|
return 50;
|
||||||
|
} else {
|
||||||
|
return 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
climate::ClimateTraits DaikinArcClimate::traits() {
|
||||||
|
climate::ClimateTraits traits = climate_ir::ClimateIR::traits();
|
||||||
|
traits.set_supports_current_temperature(true);
|
||||||
|
traits.set_supports_current_humidity(false);
|
||||||
|
traits.set_supports_target_humidity(true);
|
||||||
|
traits.set_visual_min_humidity(38);
|
||||||
|
traits.set_visual_max_humidity(52);
|
||||||
|
return traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaikinArcClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||||
|
uint8_t checksum = 0;
|
||||||
|
for (int i = 0; i < (DAIKIN_STATE_FRAME_SIZE - 1); i++) {
|
||||||
|
checksum += frame[i];
|
||||||
|
}
|
||||||
|
if (frame[DAIKIN_STATE_FRAME_SIZE - 1] != checksum) {
|
||||||
|
ESP_LOGI(TAG, "checksum error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[DAIKIN_STATE_FRAME_SIZE * 3 + 1] = {0};
|
||||||
|
for (size_t i = 0; i < DAIKIN_STATE_FRAME_SIZE; i++) {
|
||||||
|
sprintf(buf, "%s%02x ", buf, frame[i]);
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "FRAME %s", buf);
|
||||||
|
|
||||||
|
uint8_t mode = frame[5];
|
||||||
|
if (mode & DAIKIN_MODE_ON) {
|
||||||
|
switch (mode & 0xF0) {
|
||||||
|
case DAIKIN_MODE_COOL:
|
||||||
|
this->mode = climate::CLIMATE_MODE_COOL;
|
||||||
|
break;
|
||||||
|
case DAIKIN_MODE_DRY:
|
||||||
|
this->mode = climate::CLIMATE_MODE_DRY;
|
||||||
|
break;
|
||||||
|
case DAIKIN_MODE_HEAT:
|
||||||
|
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||||
|
break;
|
||||||
|
case DAIKIN_MODE_AUTO:
|
||||||
|
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||||
|
break;
|
||||||
|
case DAIKIN_MODE_FAN:
|
||||||
|
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this->mode = climate::CLIMATE_MODE_OFF;
|
||||||
|
}
|
||||||
|
uint8_t temperature = frame[6];
|
||||||
|
if (!(temperature & 0xC0)) {
|
||||||
|
this->target_temperature = temperature >> 1;
|
||||||
|
this->target_temperature += (temperature & 0x1) ? 0.5 : 0;
|
||||||
|
}
|
||||||
|
this->target_humidity = frame[7]; // 0, 40, 45, 50, 0xff
|
||||||
|
uint8_t fan_mode = frame[8];
|
||||||
|
uint8_t swing_mode = frame[9];
|
||||||
|
if (fan_mode & 0xF && swing_mode & 0xF) {
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_BOTH;
|
||||||
|
} else if (fan_mode & 0xF) {
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
|
||||||
|
} else if (swing_mode & 0xF) {
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
|
||||||
|
} else {
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||||
|
}
|
||||||
|
switch (fan_mode & 0xF0) {
|
||||||
|
case DAIKIN_FAN_1:
|
||||||
|
case DAIKIN_FAN_2:
|
||||||
|
case DAIKIN_FAN_SILENT:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||||
|
break;
|
||||||
|
case DAIKIN_FAN_3:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||||
|
break;
|
||||||
|
case DAIKIN_FAN_4:
|
||||||
|
case DAIKIN_FAN_5:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||||
|
break;
|
||||||
|
case DAIKIN_FAN_AUTO:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
05 0 [1:3]MODE 1 [OFF TMR] [ON TMR] Power
|
||||||
|
06-07 TEMP
|
||||||
|
08 [0:3] SPEED [4:7] Swing
|
||||||
|
09 00
|
||||||
|
10 00
|
||||||
|
11, 12: timer
|
||||||
|
13 [0:6] 0000000 [7] POWERMODE
|
||||||
|
14 0a
|
||||||
|
15 c4
|
||||||
|
16 [0:3] 8 00 [6:7] SENSOR WIND = 11 / NORMAL = 00
|
||||||
|
17 24
|
||||||
|
05 06 07 08 09 10 11 12 13 14 15 16 17 18
|
||||||
|
None FRAME 11 da 27 00 00 49 2e 00 b0 00 00 06 60 00 0a c4 80 24 11
|
||||||
|
1H FRAME 11 da 27 00 00 4d 2e 00 b0 00 00 c6 30 00 2a c4 80 24 c5
|
||||||
|
1H30 FRAME 11 da 27 00 00 4d 2e 00 b0 00 00 a6 32 00 2a c4 80 24 a7
|
||||||
|
2H FRAME 11 da 27 00 00 4d 2e 00 b0 00 00 86 34 00 2a c4 80 24 89
|
||||||
|
|
||||||
|
*/
|
||||||
|
this->publish_state();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||||
|
uint8_t state_frame[DAIKIN_STATE_FRAME_SIZE] = {};
|
||||||
|
|
||||||
|
bool valid_daikin_frame = false;
|
||||||
|
if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) {
|
||||||
|
valid_daikin_frame = true;
|
||||||
|
int bytes_count = data.size() / 2 / 8;
|
||||||
|
std::unique_ptr<char[]> buf(new char[bytes_count * 3 + 1]);
|
||||||
|
buf[0] = '\0';
|
||||||
|
for (size_t i = 0; i < bytes_count; i++) {
|
||||||
|
uint8_t byte = 0;
|
||||||
|
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)) {
|
||||||
|
valid_daikin_frame = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sprintf(buf.get(), "%s%02x ", buf.get(), byte);
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "WHOLE FRAME %s size: %d", buf.get(), data.size());
|
||||||
|
}
|
||||||
|
if (!valid_daikin_frame) {
|
||||||
|
char sbuf[16 * 10 + 1];
|
||||||
|
sbuf[0] = '\0';
|
||||||
|
for (size_t j = 0; j < data.size(); j++) {
|
||||||
|
if ((j - 2) % 16 == 0) {
|
||||||
|
if (j > 0) {
|
||||||
|
ESP_LOGD(TAG, "DATA %04x: %s", (j - 16 > 0xffff ? 0 : j - 16), sbuf);
|
||||||
|
}
|
||||||
|
sbuf[0] = '\0';
|
||||||
|
}
|
||||||
|
char type_ch = ' ';
|
||||||
|
// debug_tolerance = 25%
|
||||||
|
|
||||||
|
if (DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_MARK))
|
||||||
|
type_ch = 'P';
|
||||||
|
if (DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_SPACE))
|
||||||
|
type_ch = 'a';
|
||||||
|
if (DAIKIN_DBG_LOWER(DAIKIN_HEADER_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_HEADER_MARK))
|
||||||
|
type_ch = 'H';
|
||||||
|
if (DAIKIN_DBG_LOWER(DAIKIN_HEADER_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_HEADER_SPACE))
|
||||||
|
type_ch = 'h';
|
||||||
|
if (DAIKIN_DBG_LOWER(DAIKIN_BIT_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_BIT_MARK))
|
||||||
|
type_ch = 'B';
|
||||||
|
if (DAIKIN_DBG_LOWER(DAIKIN_ONE_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ONE_SPACE))
|
||||||
|
type_ch = '1';
|
||||||
|
if (DAIKIN_DBG_LOWER(DAIKIN_ZERO_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ZERO_SPACE))
|
||||||
|
type_ch = '0';
|
||||||
|
|
||||||
|
if (abs(data[j]) > 100000) {
|
||||||
|
sprintf(sbuf, "%s%-5d[%c] ", sbuf, data[j] > 0 ? 99999 : -99999, type_ch);
|
||||||
|
} else {
|
||||||
|
sprintf(sbuf, "%s%-5d[%c] ", sbuf, (int) (round(data[j] / 10.) * 10), type_ch);
|
||||||
|
}
|
||||||
|
if (j == data.size() - 1) {
|
||||||
|
ESP_LOGD(TAG, "DATA %04x: %s", (j - 8 > 0xffff ? 0 : j - 8), sbuf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.reset();
|
||||||
|
|
||||||
|
if (!data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) {
|
||||||
|
ESP_LOGI(TAG, "non daikin_arc expect item");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t pos = 0; pos < DAIKIN_STATE_FRAME_SIZE; pos++) {
|
||||||
|
uint8_t byte = 0;
|
||||||
|
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)) {
|
||||||
|
ESP_LOGI(TAG, "non daikin_arc expect item pos: %d", pos);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state_frame[pos] = byte;
|
||||||
|
if (pos == 0) {
|
||||||
|
// frame header
|
||||||
|
if (byte != 0x11) {
|
||||||
|
ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (pos == 1) {
|
||||||
|
// frame header
|
||||||
|
if (byte != 0xDA) {
|
||||||
|
ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (pos == 2) {
|
||||||
|
// frame header
|
||||||
|
if (byte != 0x27) {
|
||||||
|
ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (pos == 3) { // NOLINT(bugprone-branch-clone)
|
||||||
|
// frame header
|
||||||
|
if (byte != 0x00) {
|
||||||
|
ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (pos == 4) {
|
||||||
|
// frame type
|
||||||
|
if (byte != 0x00) {
|
||||||
|
ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (pos == 5) {
|
||||||
|
if (data.size() == 385) {
|
||||||
|
/*
|
||||||
|
11 da 27 00 00 1a 0c 04 2c 21 61 07 00 07 0c 00 18 00 0e 3c 00 6c 1b 61
|
||||||
|
Inside Temp
|
||||||
|
Outside Temp
|
||||||
|
Humdidity
|
||||||
|
|
||||||
|
*/
|
||||||
|
this->current_temperature = state_frame[5]; // Inside temperature
|
||||||
|
// this->current_temperature = state_frame[6]; // Outside temperature
|
||||||
|
this->publish_state();
|
||||||
|
return true;
|
||||||
|
} else if ((byte & 0x40) != 0x40) {
|
||||||
|
ESP_LOGI(TAG, "non daikin_arc expect pos: %d header: %02x", pos, byte);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this->parse_state_frame_(state_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaikinArcClimate::control(const climate::ClimateCall &call) {
|
||||||
|
if (call.get_target_humidity().has_value()) {
|
||||||
|
this->target_humidity = *call.get_target_humidity();
|
||||||
|
}
|
||||||
|
climate_ir::ClimateIR::control(call);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace daikin_arc
|
||||||
|
} // namespace esphome
|
76
esphome/components/daikin_arc/daikin_arc.h
Normal file
76
esphome/components/daikin_arc/daikin_arc.h
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/climate_ir/climate_ir.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace daikin_arc {
|
||||||
|
|
||||||
|
// Values for Daikin ARC43XXX IR Controllers
|
||||||
|
// Temperature
|
||||||
|
const uint8_t DAIKIN_TEMP_MIN = 10; // Celsius
|
||||||
|
const uint8_t DAIKIN_TEMP_MAX = 30; // Celsius
|
||||||
|
|
||||||
|
// Modes
|
||||||
|
const uint8_t DAIKIN_MODE_AUTO = 0x00;
|
||||||
|
const uint8_t DAIKIN_MODE_COOL = 0x30;
|
||||||
|
const uint8_t DAIKIN_MODE_HEAT = 0x40;
|
||||||
|
const uint8_t DAIKIN_MODE_DRY = 0x20;
|
||||||
|
const uint8_t DAIKIN_MODE_FAN = 0x60;
|
||||||
|
const uint8_t DAIKIN_MODE_OFF = 0x00;
|
||||||
|
const uint8_t DAIKIN_MODE_ON = 0x01;
|
||||||
|
|
||||||
|
// Fan Speed
|
||||||
|
const uint8_t DAIKIN_FAN_AUTO = 0xA0;
|
||||||
|
const uint8_t DAIKIN_FAN_SILENT = 0xB0;
|
||||||
|
const uint8_t DAIKIN_FAN_1 = 0x30;
|
||||||
|
const uint8_t DAIKIN_FAN_2 = 0x40;
|
||||||
|
const uint8_t DAIKIN_FAN_3 = 0x50;
|
||||||
|
const uint8_t DAIKIN_FAN_4 = 0x60;
|
||||||
|
const uint8_t DAIKIN_FAN_5 = 0x70;
|
||||||
|
|
||||||
|
// IR Transmission
|
||||||
|
const uint32_t DAIKIN_IR_FREQUENCY = 38000;
|
||||||
|
const uint32_t DAIKIN_ARC_PRE_MARK = 9950;
|
||||||
|
const uint32_t DAIKIN_ARC_PRE_SPACE = 25100;
|
||||||
|
const uint32_t DAIKIN_HEADER_MARK = 3450;
|
||||||
|
const uint32_t DAIKIN_HEADER_SPACE = 1760;
|
||||||
|
const uint32_t DAIKIN_BIT_MARK = 400;
|
||||||
|
const uint32_t DAIKIN_ONE_SPACE = 1300;
|
||||||
|
const uint32_t DAIKIN_ZERO_SPACE = 480;
|
||||||
|
const uint32_t DAIKIN_MESSAGE_SPACE = 35000;
|
||||||
|
|
||||||
|
const uint8_t DAIKIN_DBG_TOLERANCE = 25;
|
||||||
|
#define DAIKIN_DBG_LOWER(x) ((100 - DAIKIN_DBG_TOLERANCE) * (x) / 100U)
|
||||||
|
#define DAIKIN_DBG_UPPER(x) ((100 + DAIKIN_DBG_TOLERANCE) * (x) / 100U)
|
||||||
|
|
||||||
|
// State Frame size
|
||||||
|
const uint8_t DAIKIN_STATE_FRAME_SIZE = 19;
|
||||||
|
|
||||||
|
class DaikinArcClimate : public climate_ir::ClimateIR {
|
||||||
|
public:
|
||||||
|
DaikinArcClimate()
|
||||||
|
: climate_ir::ClimateIR(DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 0.5f, 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,
|
||||||
|
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void control(const climate::ClimateCall &call) override;
|
||||||
|
// Transmit via IR the state of this climate controller.
|
||||||
|
void transmit_query_();
|
||||||
|
void transmit_state() override;
|
||||||
|
climate::ClimateTraits traits() override;
|
||||||
|
uint8_t operation_mode_();
|
||||||
|
uint16_t fan_speed_();
|
||||||
|
uint8_t temperature_();
|
||||||
|
uint8_t humidity_();
|
||||||
|
// Handle received IR Buffer
|
||||||
|
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||||
|
bool parse_state_frame_(const uint8_t frame[]);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace daikin_arc
|
||||||
|
} // namespace esphome
|
19
tests/components/daikin_arc/test.esp32.yaml
Normal file
19
tests/components/daikin_arc/test.esp32.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_arc
|
||||||
|
name: "AC"
|
||||||
|
receiver_id: rcvr
|
19
tests/components/daikin_arc/test.esp8266.yaml
Normal file
19
tests/components/daikin_arc/test.esp8266.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_arc
|
||||||
|
name: "AC"
|
||||||
|
receiver_id: rcvr
|
Loading…
Reference in a new issue