mirror of
https://github.com/esphome/esphome.git
synced 2024-11-10 09:17:46 +01:00
Added receive for Fujitsu ACs (#1577)
This commit is contained in:
parent
28a72fa56b
commit
2e7c1d7345
3 changed files with 317 additions and 143 deletions
|
@ -10,7 +10,7 @@ FujitsuGeneralClimate = fujitsu_general_ns.class_(
|
||||||
"FujitsuGeneralClimate", climate_ir.ClimateIR
|
"FujitsuGeneralClimate", climate_ir.ClimateIR
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_SCHEMA.extend(
|
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(FujitsuGeneralClimate),
|
cv.GenerateID(): cv.declare_id(FujitsuGeneralClimate),
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,224 +3,207 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace fujitsu_general {
|
namespace fujitsu_general {
|
||||||
|
|
||||||
|
// bytes' bits are reversed for fujitsu, so nibbles are ordered 1, 0, 3, 2, 5, 4, etc...
|
||||||
|
|
||||||
|
#define SET_NIBBLE(message, nibble, value) (message[nibble / 2] |= (value & 0b00001111) << ((nibble % 2) ? 0 : 4))
|
||||||
|
#define GET_NIBBLE(message, nibble) ((message[nibble / 2] >> ((nibble % 2) ? 0 : 4)) & 0b00001111)
|
||||||
|
|
||||||
static const char* TAG = "fujitsu_general.climate";
|
static const char* TAG = "fujitsu_general.climate";
|
||||||
|
|
||||||
// Control packet
|
// Common header
|
||||||
const uint16_t FUJITSU_GENERAL_STATE_LENGTH = 16;
|
const uint8_t FUJITSU_GENERAL_COMMON_LENGTH = 6;
|
||||||
|
const uint8_t FUJITSU_GENERAL_COMMON_BYTE0 = 0x14;
|
||||||
|
const uint8_t FUJITSU_GENERAL_COMMON_BYTE1 = 0x63;
|
||||||
|
const uint8_t FUJITSU_GENERAL_COMMON_BYTE2 = 0x00;
|
||||||
|
const uint8_t FUJITSU_GENERAL_COMMON_BYTE3 = 0x10;
|
||||||
|
const uint8_t FUJITSU_GENERAL_COMMON_BYTE4 = 0x10;
|
||||||
|
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_BYTE = 5;
|
||||||
|
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE0 = 0x14;
|
// State message - temp & fan etc.
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE1 = 0x63;
|
const uint8_t FUJITSU_GENERAL_STATE_MESSAGE_LENGTH = 16;
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE2 = 0x00;
|
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_STATE = 0xFE;
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE3 = 0x10;
|
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE4 = 0x10;
|
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE5 = 0xFE;
|
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE6 = 0x09;
|
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE7 = 0x30;
|
|
||||||
|
|
||||||
// Temperature and POWER ON
|
// Util messages - off & eco etc.
|
||||||
const uint8_t FUJITSU_GENERAL_POWER_ON_MASK_BYTE8 = 0b00000001;
|
const uint8_t FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH = 7;
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE8 = 0x40;
|
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_OFF = 0x02;
|
||||||
|
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_ECONOMY = 0x09;
|
||||||
|
const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_NUDGE = 0x6C;
|
||||||
|
|
||||||
|
// State header
|
||||||
|
const uint8_t FUJITSU_GENERAL_STATE_HEADER_BYTE0 = 0x09;
|
||||||
|
const uint8_t FUJITSU_GENERAL_STATE_HEADER_BYTE1 = 0x30;
|
||||||
|
|
||||||
|
// State footer
|
||||||
|
const uint8_t FUJITSU_GENERAL_STATE_FOOTER_BYTE0 = 0x20;
|
||||||
|
|
||||||
|
// Temperature
|
||||||
|
const uint8_t FUJITSU_GENERAL_TEMPERATURE_NIBBLE = 16;
|
||||||
|
|
||||||
|
// Power on
|
||||||
|
const uint8_t FUJITSU_GENERAL_POWER_ON_NIBBLE = 17;
|
||||||
|
const uint8_t FUJITSU_GENERAL_POWER_ON = 0x01;
|
||||||
|
const uint8_t FUJITSU_GENERAL_POWER_OFF = 0x00;
|
||||||
|
|
||||||
// Mode
|
// Mode
|
||||||
const uint8_t FUJITSU_GENERAL_MODE_AUTO_BYTE9 = 0x00;
|
const uint8_t FUJITSU_GENERAL_MODE_NIBBLE = 19;
|
||||||
const uint8_t FUJITSU_GENERAL_MODE_HEAT_BYTE9 = 0x04;
|
const uint8_t FUJITSU_GENERAL_MODE_AUTO = 0x00;
|
||||||
const uint8_t FUJITSU_GENERAL_MODE_COOL_BYTE9 = 0x01;
|
const uint8_t FUJITSU_GENERAL_MODE_HEAT = 0x04;
|
||||||
const uint8_t FUJITSU_GENERAL_MODE_DRY_BYTE9 = 0x02;
|
const uint8_t FUJITSU_GENERAL_MODE_COOL = 0x01;
|
||||||
const uint8_t FUJITSU_GENERAL_MODE_FAN_BYTE9 = 0x03;
|
const uint8_t FUJITSU_GENERAL_MODE_DRY = 0x02;
|
||||||
const uint8_t FUJITSU_GENERAL_MODE_10C_BYTE9 = 0x0B;
|
const uint8_t FUJITSU_GENERAL_MODE_FAN = 0x03;
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE9 = 0x01;
|
// const uint8_t FUJITSU_GENERAL_MODE_10C = 0x0B;
|
||||||
|
|
||||||
// Fan speed and swing
|
// Swing
|
||||||
const uint8_t FUJITSU_GENERAL_FAN_AUTO_BYTE10 = 0x00;
|
const uint8_t FUJITSU_GENERAL_FAN_NIBBLE = 20;
|
||||||
const uint8_t FUJITSU_GENERAL_FAN_HIGH_BYTE10 = 0x01;
|
const uint8_t FUJITSU_GENERAL_FAN_AUTO = 0x00;
|
||||||
const uint8_t FUJITSU_GENERAL_FAN_MEDIUM_BYTE10 = 0x02;
|
const uint8_t FUJITSU_GENERAL_FAN_HIGH = 0x01;
|
||||||
const uint8_t FUJITSU_GENERAL_FAN_LOW_BYTE10 = 0x03;
|
const uint8_t FUJITSU_GENERAL_FAN_MEDIUM = 0x02;
|
||||||
const uint8_t FUJITSU_GENERAL_FAN_SILENT_BYTE10 = 0x04;
|
const uint8_t FUJITSU_GENERAL_FAN_LOW = 0x03;
|
||||||
const uint8_t FUJITSU_GENERAL_SWING_NONE_BYTE10 = 0x00;
|
const uint8_t FUJITSU_GENERAL_FAN_SILENT = 0x04;
|
||||||
const uint8_t FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 = 0x01;
|
|
||||||
const uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 = 0x02;
|
|
||||||
const uint8_t FUJITSU_GENERAL_SWING_BOTH_BYTE10 = 0x03;
|
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE10 = 0x00;
|
|
||||||
|
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE11 = 0x00;
|
// Fan speed
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE12 = 0x00;
|
const uint8_t FUJITSU_GENERAL_SWING_NIBBLE = 21;
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE13 = 0x00;
|
const uint8_t FUJITSU_GENERAL_SWING_NONE = 0x00;
|
||||||
|
const uint8_t FUJITSU_GENERAL_SWING_VERTICAL = 0x01;
|
||||||
|
const uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL = 0x02;
|
||||||
|
const uint8_t FUJITSU_GENERAL_SWING_BOTH = 0x03;
|
||||||
|
|
||||||
// Outdoor Unit Low Noise
|
// TODO Outdoor Unit Low Noise
|
||||||
const uint8_t FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14 = 0xA0;
|
// const uint8_t FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14 = 0xA0;
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE14 = 0x20;
|
// const uint8_t FUJITSU_GENERAL_STATE_BYTE14 = 0x20;
|
||||||
|
|
||||||
// CRC
|
|
||||||
const uint8_t FUJITSU_GENERAL_BASE_BYTE15 = 0x6F;
|
|
||||||
|
|
||||||
// Power off packet is specific
|
|
||||||
const uint16_t FUJITSU_GENERAL_OFF_LENGTH = 7;
|
|
||||||
|
|
||||||
const uint8_t FUJITSU_GENERAL_OFF_BYTE0 = FUJITSU_GENERAL_BASE_BYTE0;
|
|
||||||
const uint8_t FUJITSU_GENERAL_OFF_BYTE1 = FUJITSU_GENERAL_BASE_BYTE1;
|
|
||||||
const uint8_t FUJITSU_GENERAL_OFF_BYTE2 = FUJITSU_GENERAL_BASE_BYTE2;
|
|
||||||
const uint8_t FUJITSU_GENERAL_OFF_BYTE3 = FUJITSU_GENERAL_BASE_BYTE3;
|
|
||||||
const uint8_t FUJITSU_GENERAL_OFF_BYTE4 = FUJITSU_GENERAL_BASE_BYTE4;
|
|
||||||
const uint8_t FUJITSU_GENERAL_OFF_BYTE5 = 0x02;
|
|
||||||
const uint8_t FUJITSU_GENERAL_OFF_BYTE6 = 0xFD;
|
|
||||||
|
|
||||||
const uint8_t FUJITSU_GENERAL_TEMP_MAX = 30; // Celsius
|
|
||||||
const uint8_t FUJITSU_GENERAL_TEMP_MIN = 16; // Celsius
|
|
||||||
|
|
||||||
const uint16_t FUJITSU_GENERAL_HEADER_MARK = 3300;
|
const uint16_t FUJITSU_GENERAL_HEADER_MARK = 3300;
|
||||||
const uint16_t FUJITSU_GENERAL_HEADER_SPACE = 1600;
|
const uint16_t FUJITSU_GENERAL_HEADER_SPACE = 1600;
|
||||||
|
|
||||||
const uint16_t FUJITSU_GENERAL_BIT_MARK = 420;
|
const uint16_t FUJITSU_GENERAL_BIT_MARK = 420;
|
||||||
const uint16_t FUJITSU_GENERAL_ONE_SPACE = 1200;
|
const uint16_t FUJITSU_GENERAL_ONE_SPACE = 1200;
|
||||||
const uint16_t FUJITSU_GENERAL_ZERO_SPACE = 420;
|
const uint16_t FUJITSU_GENERAL_ZERO_SPACE = 420;
|
||||||
|
|
||||||
const uint16_t FUJITSU_GENERAL_TRL_MARK = 420;
|
const uint16_t FUJITSU_GENERAL_TRL_MARK = 420;
|
||||||
const uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000;
|
const uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000;
|
||||||
|
|
||||||
const uint32_t FUJITSU_GENERAL_CARRIER_FREQUENCY = 38000;
|
const uint32_t FUJITSU_GENERAL_CARRIER_FREQUENCY = 38000;
|
||||||
|
|
||||||
FujitsuGeneralClimate::FujitsuGeneralClimate()
|
|
||||||
: ClimateIR(
|
|
||||||
FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_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, climate::CLIMATE_SWING_HORIZONTAL,
|
|
||||||
climate::CLIMATE_SWING_BOTH}) {}
|
|
||||||
|
|
||||||
void FujitsuGeneralClimate::transmit_state() {
|
void FujitsuGeneralClimate::transmit_state() {
|
||||||
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
||||||
this->transmit_off_();
|
this->transmit_off_();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint8_t remote_state[FUJITSU_GENERAL_STATE_LENGTH] = {0};
|
|
||||||
|
|
||||||
remote_state[0] = FUJITSU_GENERAL_BASE_BYTE0;
|
ESP_LOGV(TAG, "Transmit state");
|
||||||
remote_state[1] = FUJITSU_GENERAL_BASE_BYTE1;
|
|
||||||
remote_state[2] = FUJITSU_GENERAL_BASE_BYTE2;
|
uint8_t remote_state[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH] = {0};
|
||||||
remote_state[3] = FUJITSU_GENERAL_BASE_BYTE3;
|
|
||||||
remote_state[4] = FUJITSU_GENERAL_BASE_BYTE4;
|
// Common message header
|
||||||
remote_state[5] = FUJITSU_GENERAL_BASE_BYTE5;
|
remote_state[0] = FUJITSU_GENERAL_COMMON_BYTE0;
|
||||||
remote_state[6] = FUJITSU_GENERAL_BASE_BYTE6;
|
remote_state[1] = FUJITSU_GENERAL_COMMON_BYTE1;
|
||||||
remote_state[7] = FUJITSU_GENERAL_BASE_BYTE7;
|
remote_state[2] = FUJITSU_GENERAL_COMMON_BYTE2;
|
||||||
remote_state[8] = FUJITSU_GENERAL_BASE_BYTE8;
|
remote_state[3] = FUJITSU_GENERAL_COMMON_BYTE3;
|
||||||
remote_state[9] = FUJITSU_GENERAL_BASE_BYTE9;
|
remote_state[4] = FUJITSU_GENERAL_COMMON_BYTE4;
|
||||||
remote_state[10] = FUJITSU_GENERAL_BASE_BYTE10;
|
remote_state[5] = FUJITSU_GENERAL_MESSAGE_TYPE_STATE;
|
||||||
remote_state[11] = FUJITSU_GENERAL_BASE_BYTE11;
|
remote_state[6] = FUJITSU_GENERAL_STATE_HEADER_BYTE0;
|
||||||
remote_state[12] = FUJITSU_GENERAL_BASE_BYTE12;
|
remote_state[7] = FUJITSU_GENERAL_STATE_HEADER_BYTE1;
|
||||||
remote_state[13] = FUJITSU_GENERAL_BASE_BYTE13;
|
|
||||||
remote_state[14] = FUJITSU_GENERAL_BASE_BYTE14;
|
// unknown, does not appear to change with any remote settings
|
||||||
remote_state[15] = FUJITSU_GENERAL_BASE_BYTE15;
|
remote_state[14] = FUJITSU_GENERAL_STATE_FOOTER_BYTE0;
|
||||||
|
|
||||||
// Set temperature
|
// Set temperature
|
||||||
auto safecelsius =
|
uint8_t temperature_clamped =
|
||||||
(uint8_t) roundf(clamp(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX));
|
(uint8_t) roundf(clamp(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX));
|
||||||
remote_state[8] = (byte) safecelsius - 16;
|
uint8_t temperature_offset = temperature_clamped - FUJITSU_GENERAL_TEMP_MIN;
|
||||||
remote_state[8] = remote_state[8] << 4;
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_TEMPERATURE_NIBBLE, temperature_offset);
|
||||||
|
|
||||||
// If not powered - set power on flag
|
// Set power on
|
||||||
if (!this->power_) {
|
if (!this->power_) {
|
||||||
remote_state[8] = (byte) remote_state[8] | FUJITSU_GENERAL_POWER_ON_MASK_BYTE8;
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_POWER_ON_NIBBLE, FUJITSU_GENERAL_POWER_ON);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set mode
|
// Set mode
|
||||||
switch (this->mode) {
|
switch (this->mode) {
|
||||||
case climate::CLIMATE_MODE_COOL:
|
case climate::CLIMATE_MODE_COOL:
|
||||||
remote_state[9] = FUJITSU_GENERAL_MODE_COOL_BYTE9;
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_COOL);
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_MODE_HEAT:
|
case climate::CLIMATE_MODE_HEAT:
|
||||||
remote_state[9] = FUJITSU_GENERAL_MODE_HEAT_BYTE9;
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_HEAT);
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_MODE_DRY:
|
case climate::CLIMATE_MODE_DRY:
|
||||||
remote_state[9] = FUJITSU_GENERAL_MODE_DRY_BYTE9;
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_DRY);
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||||
remote_state[9] = FUJITSU_GENERAL_MODE_FAN_BYTE9;
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_FAN);
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_MODE_AUTO:
|
case climate::CLIMATE_MODE_AUTO:
|
||||||
default:
|
default:
|
||||||
remote_state[9] = FUJITSU_GENERAL_MODE_AUTO_BYTE9;
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_AUTO);
|
||||||
break;
|
break;
|
||||||
// TODO: CLIMATE_MODE_10C are missing in esphome
|
// TODO: CLIMATE_MODE_10C is missing from esphome
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set fan
|
// Set fan
|
||||||
switch (this->fan_mode) {
|
switch (this->fan_mode) {
|
||||||
case climate::CLIMATE_FAN_HIGH:
|
case climate::CLIMATE_FAN_HIGH:
|
||||||
remote_state[10] = FUJITSU_GENERAL_FAN_HIGH_BYTE10;
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_HIGH);
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_FAN_MEDIUM:
|
case climate::CLIMATE_FAN_MEDIUM:
|
||||||
remote_state[10] = FUJITSU_GENERAL_FAN_MEDIUM_BYTE10;
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_MEDIUM);
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_FAN_LOW:
|
case climate::CLIMATE_FAN_LOW:
|
||||||
remote_state[10] = FUJITSU_GENERAL_FAN_LOW_BYTE10;
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_LOW);
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_FAN_AUTO:
|
case climate::CLIMATE_FAN_AUTO:
|
||||||
default:
|
default:
|
||||||
remote_state[10] = FUJITSU_GENERAL_FAN_AUTO_BYTE10;
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_AUTO);
|
||||||
break;
|
break;
|
||||||
|
// TODO Quiet / Silent
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set swing
|
// Set swing
|
||||||
switch (this->swing_mode) {
|
switch (this->swing_mode) {
|
||||||
case climate::CLIMATE_SWING_VERTICAL:
|
case climate::CLIMATE_SWING_VERTICAL:
|
||||||
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 << 4);
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_VERTICAL);
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_SWING_HORIZONTAL:
|
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||||
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 << 4);
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_HORIZONTAL);
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_SWING_BOTH:
|
case climate::CLIMATE_SWING_BOTH:
|
||||||
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_BOTH_BYTE10 << 4);
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_BOTH);
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_SWING_OFF:
|
case climate::CLIMATE_SWING_OFF:
|
||||||
default:
|
default:
|
||||||
remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_NONE_BYTE10 << 4);
|
SET_NIBBLE(remote_state, FUJITSU_GENERAL_SWING_NIBBLE, FUJITSU_GENERAL_SWING_NONE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: missing support for outdoor unit low noise
|
// TODO: missing support for outdoor unit low noise
|
||||||
// remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14;
|
// remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14;
|
||||||
|
|
||||||
// CRC
|
remote_state[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1] = this->checksum_state_(remote_state);
|
||||||
remote_state[15] = 0;
|
|
||||||
for (int i = 7; i < 15; i++) {
|
|
||||||
remote_state[15] += (byte) remote_state[i]; // Addiction
|
|
||||||
}
|
|
||||||
remote_state[15] = 0x100 - remote_state[15]; // mod 256
|
|
||||||
|
|
||||||
auto transmit = this->transmitter_->transmit();
|
this->transmit_(remote_state, FUJITSU_GENERAL_STATE_MESSAGE_LENGTH);
|
||||||
auto data = transmit.get_data();
|
|
||||||
|
|
||||||
data->set_carrier_frequency(FUJITSU_GENERAL_CARRIER_FREQUENCY);
|
|
||||||
|
|
||||||
// Header
|
|
||||||
data->mark(FUJITSU_GENERAL_HEADER_MARK);
|
|
||||||
data->space(FUJITSU_GENERAL_HEADER_SPACE);
|
|
||||||
// Data
|
|
||||||
for (uint8_t i : remote_state) {
|
|
||||||
// Send all Bits from Byte Data in Reverse Order
|
|
||||||
for (uint8_t mask = 00000001; mask > 0; mask <<= 1) { // iterate through bit mask
|
|
||||||
data->mark(FUJITSU_GENERAL_BIT_MARK);
|
|
||||||
bool bit = i & mask;
|
|
||||||
data->space(bit ? FUJITSU_GENERAL_ONE_SPACE : FUJITSU_GENERAL_ZERO_SPACE);
|
|
||||||
// Next bits
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Footer
|
|
||||||
data->mark(FUJITSU_GENERAL_TRL_MARK);
|
|
||||||
data->space(FUJITSU_GENERAL_TRL_SPACE);
|
|
||||||
|
|
||||||
transmit.perform();
|
|
||||||
|
|
||||||
this->power_ = true;
|
this->power_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FujitsuGeneralClimate::transmit_off_() {
|
void FujitsuGeneralClimate::transmit_off_() {
|
||||||
uint8_t remote_state[FUJITSU_GENERAL_OFF_LENGTH] = {0};
|
ESP_LOGV(TAG, "Transmit off");
|
||||||
|
|
||||||
remote_state[0] = FUJITSU_GENERAL_OFF_BYTE0;
|
uint8_t remote_state[FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH] = {0};
|
||||||
remote_state[1] = FUJITSU_GENERAL_OFF_BYTE1;
|
|
||||||
remote_state[2] = FUJITSU_GENERAL_OFF_BYTE2;
|
remote_state[0] = FUJITSU_GENERAL_COMMON_BYTE0;
|
||||||
remote_state[3] = FUJITSU_GENERAL_OFF_BYTE3;
|
remote_state[1] = FUJITSU_GENERAL_COMMON_BYTE1;
|
||||||
remote_state[4] = FUJITSU_GENERAL_OFF_BYTE4;
|
remote_state[2] = FUJITSU_GENERAL_COMMON_BYTE2;
|
||||||
remote_state[5] = FUJITSU_GENERAL_OFF_BYTE5;
|
remote_state[3] = FUJITSU_GENERAL_COMMON_BYTE3;
|
||||||
remote_state[6] = FUJITSU_GENERAL_OFF_BYTE6;
|
remote_state[4] = FUJITSU_GENERAL_COMMON_BYTE4;
|
||||||
|
remote_state[5] = FUJITSU_GENERAL_MESSAGE_TYPE_OFF;
|
||||||
|
remote_state[6] = this->checksum_util_(remote_state);
|
||||||
|
|
||||||
|
this->transmit_(remote_state, FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH);
|
||||||
|
|
||||||
|
this->power_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FujitsuGeneralClimate::transmit_(uint8_t const* message, uint8_t length) {
|
||||||
|
ESP_LOGV(TAG, "Transmit message length %d", length);
|
||||||
|
|
||||||
auto transmit = this->transmitter_->transmit();
|
auto transmit = this->transmitter_->transmit();
|
||||||
auto data = transmit.get_data();
|
auto data = transmit.get_data();
|
||||||
|
@ -232,23 +215,192 @@ void FujitsuGeneralClimate::transmit_off_() {
|
||||||
data->space(FUJITSU_GENERAL_HEADER_SPACE);
|
data->space(FUJITSU_GENERAL_HEADER_SPACE);
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
for (uint8_t i : remote_state) {
|
for (uint8_t i = 0; i < length; ++i) {
|
||||||
// Send all Bits from Byte Data in Reverse Order
|
const uint8_t byte = message[i];
|
||||||
for (uint8_t mask = 00000001; mask > 0; mask <<= 1) { // iterate through bit mask
|
for (uint8_t mask = 0b00000001; mask > 0; mask <<= 1) { // write from right to left
|
||||||
data->mark(FUJITSU_GENERAL_BIT_MARK);
|
data->mark(FUJITSU_GENERAL_BIT_MARK);
|
||||||
bool bit = i & mask;
|
bool bit = byte & mask;
|
||||||
data->space(bit ? FUJITSU_GENERAL_ONE_SPACE : FUJITSU_GENERAL_ZERO_SPACE);
|
data->space(bit ? FUJITSU_GENERAL_ONE_SPACE : FUJITSU_GENERAL_ZERO_SPACE);
|
||||||
// Next bits
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footer
|
// Footer
|
||||||
data->mark(FUJITSU_GENERAL_TRL_MARK);
|
data->mark(FUJITSU_GENERAL_TRL_MARK);
|
||||||
data->space(FUJITSU_GENERAL_TRL_SPACE);
|
data->space(FUJITSU_GENERAL_TRL_SPACE);
|
||||||
|
|
||||||
transmit.perform();
|
transmit.perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t FujitsuGeneralClimate::checksum_state_(uint8_t const* message) {
|
||||||
|
uint8_t checksum = 0;
|
||||||
|
for (uint8_t i = 7; i < FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1; ++i) {
|
||||||
|
checksum += message[i];
|
||||||
|
}
|
||||||
|
return 256 - checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t FujitsuGeneralClimate::checksum_util_(uint8_t const* message) { return 255 - message[5]; }
|
||||||
|
|
||||||
|
bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||||
|
ESP_LOGV(TAG, "Received IR message");
|
||||||
|
|
||||||
|
// Validate header
|
||||||
|
if (!data.expect_item(FUJITSU_GENERAL_HEADER_MARK, FUJITSU_GENERAL_HEADER_SPACE)) {
|
||||||
|
ESP_LOGV(TAG, "Header fail");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t recv_message[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH] = {0};
|
||||||
|
|
||||||
|
// Read header
|
||||||
|
for (uint8_t byte = 0; byte < FUJITSU_GENERAL_COMMON_LENGTH; ++byte) {
|
||||||
|
// Read bit
|
||||||
|
for (uint8_t bit = 0; bit < 8; ++bit) {
|
||||||
|
if (data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ONE_SPACE)) {
|
||||||
|
recv_message[byte] |= 1 << bit; // read from right to left
|
||||||
|
} else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) {
|
||||||
|
ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t recv_message_type = recv_message[FUJITSU_GENERAL_MESSAGE_TYPE_BYTE];
|
||||||
|
uint8_t recv_message_length;
|
||||||
|
|
||||||
|
switch (recv_message_type) {
|
||||||
|
case FUJITSU_GENERAL_MESSAGE_TYPE_STATE:
|
||||||
|
ESP_LOGV(TAG, "Received state message");
|
||||||
|
recv_message_length = FUJITSU_GENERAL_STATE_MESSAGE_LENGTH;
|
||||||
|
break;
|
||||||
|
case FUJITSU_GENERAL_MESSAGE_TYPE_OFF:
|
||||||
|
case FUJITSU_GENERAL_MESSAGE_TYPE_ECONOMY:
|
||||||
|
case FUJITSU_GENERAL_MESSAGE_TYPE_NUDGE:
|
||||||
|
ESP_LOGV(TAG, "Received util message");
|
||||||
|
recv_message_length = FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ESP_LOGV(TAG, "Unknown message type %X", recv_message_type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read message body
|
||||||
|
for (uint8_t byte = FUJITSU_GENERAL_COMMON_LENGTH; byte < recv_message_length; ++byte) {
|
||||||
|
for (uint8_t bit = 0; bit < 8; ++bit) {
|
||||||
|
if (data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ONE_SPACE)) {
|
||||||
|
recv_message[byte] |= 1 << bit; // read from right to left
|
||||||
|
} else if (!data.expect_item(FUJITSU_GENERAL_BIT_MARK, FUJITSU_GENERAL_ZERO_SPACE)) {
|
||||||
|
ESP_LOGV(TAG, "Byte %d bit %d fail", byte, bit);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate footer
|
||||||
|
if (!data.expect_mark(FUJITSU_GENERAL_BIT_MARK)) {
|
||||||
|
ESP_LOGV(TAG, "Footer fail");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t byte = 0; byte < recv_message_length; ++byte) {
|
||||||
|
ESP_LOGVV(TAG, "%02X", recv_message[byte]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t recv_checksum = recv_message[recv_message_length - 1];
|
||||||
|
uint8_t calculated_checksum;
|
||||||
|
if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_STATE) {
|
||||||
|
calculated_checksum = this->checksum_state_(recv_message);
|
||||||
|
} else {
|
||||||
|
calculated_checksum = this->checksum_util_(recv_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recv_checksum != calculated_checksum) {
|
||||||
|
ESP_LOGV(TAG, "Checksum fail - expected %X - got %X", calculated_checksum, recv_checksum);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_STATE) {
|
||||||
|
const uint8_t recv_tempertature = GET_NIBBLE(recv_message, FUJITSU_GENERAL_TEMPERATURE_NIBBLE);
|
||||||
|
const uint8_t offset_temperature = recv_tempertature + FUJITSU_GENERAL_TEMP_MIN;
|
||||||
|
this->target_temperature = offset_temperature;
|
||||||
|
ESP_LOGV(TAG, "Received temperature %d", offset_temperature);
|
||||||
|
|
||||||
|
const uint8_t recv_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_MODE_NIBBLE);
|
||||||
|
ESP_LOGV(TAG, "Received mode %X", recv_mode);
|
||||||
|
switch (recv_mode) {
|
||||||
|
case FUJITSU_GENERAL_MODE_COOL:
|
||||||
|
this->mode = climate::CLIMATE_MODE_COOL;
|
||||||
|
break;
|
||||||
|
case FUJITSU_GENERAL_MODE_HEAT:
|
||||||
|
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||||
|
break;
|
||||||
|
case FUJITSU_GENERAL_MODE_DRY:
|
||||||
|
this->mode = climate::CLIMATE_MODE_DRY;
|
||||||
|
break;
|
||||||
|
case FUJITSU_GENERAL_MODE_FAN:
|
||||||
|
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||||
|
break;
|
||||||
|
case FUJITSU_GENERAL_MODE_AUTO:
|
||||||
|
default:
|
||||||
|
// TODO: CLIMATE_MODE_10C is missing from esphome
|
||||||
|
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t recv_fan_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_FAN_NIBBLE);
|
||||||
|
ESP_LOGV(TAG, "Received fan mode %X", recv_fan_mode);
|
||||||
|
switch (recv_fan_mode) {
|
||||||
|
// TODO No Quiet / Silent in ESPH
|
||||||
|
case FUJITSU_GENERAL_FAN_SILENT:
|
||||||
|
case FUJITSU_GENERAL_FAN_LOW:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||||
|
break;
|
||||||
|
case FUJITSU_GENERAL_FAN_MEDIUM:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||||
|
break;
|
||||||
|
case FUJITSU_GENERAL_FAN_HIGH:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||||
|
break;
|
||||||
|
case FUJITSU_GENERAL_FAN_AUTO:
|
||||||
|
default:
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t recv_swing_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_SWING_NIBBLE);
|
||||||
|
ESP_LOGV(TAG, "Received swing mode %X", recv_swing_mode);
|
||||||
|
switch (recv_swing_mode) {
|
||||||
|
case FUJITSU_GENERAL_SWING_VERTICAL:
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
|
||||||
|
break;
|
||||||
|
case FUJITSU_GENERAL_SWING_HORIZONTAL:
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
|
||||||
|
break;
|
||||||
|
case FUJITSU_GENERAL_SWING_BOTH:
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_BOTH;
|
||||||
|
break;
|
||||||
|
case FUJITSU_GENERAL_SWING_NONE:
|
||||||
|
default:
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->power_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (recv_message_type == FUJITSU_GENERAL_MESSAGE_TYPE_OFF) {
|
||||||
|
ESP_LOGV(TAG, "Received off message");
|
||||||
|
this->mode = climate::CLIMATE_MODE_OFF;
|
||||||
this->power_ = false;
|
this->power_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
ESP_LOGV(TAG, "Received unsupprted message type %X", recv_message_type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->publish_state();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace fujitsu_general
|
} // namespace fujitsu_general
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/components/climate_ir/climate_ir.h"
|
#include "esphome/components/climate_ir/climate_ir.h"
|
||||||
|
@ -7,9 +8,17 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace fujitsu_general {
|
namespace fujitsu_general {
|
||||||
|
|
||||||
|
const uint8_t FUJITSU_GENERAL_TEMP_MIN = 16; // Celsius // TODO 16 for heating, 18 for cooling, unsupported in ESPH
|
||||||
|
const uint8_t FUJITSU_GENERAL_TEMP_MAX = 30; // Celsius
|
||||||
|
|
||||||
class FujitsuGeneralClimate : public climate_ir::ClimateIR {
|
class FujitsuGeneralClimate : public climate_ir::ClimateIR {
|
||||||
public:
|
public:
|
||||||
FujitsuGeneralClimate();
|
FujitsuGeneralClimate()
|
||||||
|
: ClimateIR(FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_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, climate::CLIMATE_SWING_HORIZONTAL,
|
||||||
|
climate::CLIMATE_SWING_BOTH}) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Transmit via IR the state of this climate controller.
|
/// Transmit via IR the state of this climate controller.
|
||||||
|
@ -17,6 +26,19 @@ class FujitsuGeneralClimate : public climate_ir::ClimateIR {
|
||||||
/// Transmit via IR power off command.
|
/// Transmit via IR power off command.
|
||||||
void transmit_off_();
|
void transmit_off_();
|
||||||
|
|
||||||
|
/// Parse incomming message
|
||||||
|
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||||
|
|
||||||
|
/// Transmit message as IR pulses
|
||||||
|
void transmit_(uint8_t const* message, uint8_t length);
|
||||||
|
|
||||||
|
/// Calculate checksum for a state message
|
||||||
|
uint8_t checksum_state_(uint8_t const* message);
|
||||||
|
|
||||||
|
/// Calculate cecksum for a util message
|
||||||
|
uint8_t checksum_util_(uint8_t const* message);
|
||||||
|
|
||||||
|
// true if currently on - fujitsus transmit an on flag on when the remote moves from off to on
|
||||||
bool power_{false};
|
bool power_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue