From 2e7c1d73451d09156a749974478fa9889716ef78 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 14 Mar 2021 10:45:01 +1300 Subject: [PATCH] Added receive for Fujitsu ACs (#1577) --- esphome/components/fujitsu_general/climate.py | 2 +- .../fujitsu_general/fujitsu_general.cpp | 434 ++++++++++++------ .../fujitsu_general/fujitsu_general.h | 24 +- 3 files changed, 317 insertions(+), 143 deletions(-) diff --git a/esphome/components/fujitsu_general/climate.py b/esphome/components/fujitsu_general/climate.py index 64319eff20..d58049e3f2 100644 --- a/esphome/components/fujitsu_general/climate.py +++ b/esphome/components/fujitsu_general/climate.py @@ -10,7 +10,7 @@ FujitsuGeneralClimate = fujitsu_general_ns.class_( "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), } diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index f611464248..75ee3f708b 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -3,224 +3,207 @@ namespace esphome { namespace fujitsu_general { -static const char *TAG = "fujitsu_general.climate"; +// bytes' bits are reversed for fujitsu, so nibbles are ordered 1, 0, 3, 2, 5, 4, etc... -// Control packet -const uint16_t FUJITSU_GENERAL_STATE_LENGTH = 16; +#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) -const uint8_t FUJITSU_GENERAL_BASE_BYTE0 = 0x14; -const uint8_t FUJITSU_GENERAL_BASE_BYTE1 = 0x63; -const uint8_t FUJITSU_GENERAL_BASE_BYTE2 = 0x00; -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; +static const char* TAG = "fujitsu_general.climate"; -// Temperature and POWER ON -const uint8_t FUJITSU_GENERAL_POWER_ON_MASK_BYTE8 = 0b00000001; -const uint8_t FUJITSU_GENERAL_BASE_BYTE8 = 0x40; +// Common header +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; + +// State message - temp & fan etc. +const uint8_t FUJITSU_GENERAL_STATE_MESSAGE_LENGTH = 16; +const uint8_t FUJITSU_GENERAL_MESSAGE_TYPE_STATE = 0xFE; + +// Util messages - off & eco etc. +const uint8_t FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH = 7; +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 -const uint8_t FUJITSU_GENERAL_MODE_AUTO_BYTE9 = 0x00; -const uint8_t FUJITSU_GENERAL_MODE_HEAT_BYTE9 = 0x04; -const uint8_t FUJITSU_GENERAL_MODE_COOL_BYTE9 = 0x01; -const uint8_t FUJITSU_GENERAL_MODE_DRY_BYTE9 = 0x02; -const uint8_t FUJITSU_GENERAL_MODE_FAN_BYTE9 = 0x03; -const uint8_t FUJITSU_GENERAL_MODE_10C_BYTE9 = 0x0B; -const uint8_t FUJITSU_GENERAL_BASE_BYTE9 = 0x01; +const uint8_t FUJITSU_GENERAL_MODE_NIBBLE = 19; +const uint8_t FUJITSU_GENERAL_MODE_AUTO = 0x00; +const uint8_t FUJITSU_GENERAL_MODE_HEAT = 0x04; +const uint8_t FUJITSU_GENERAL_MODE_COOL = 0x01; +const uint8_t FUJITSU_GENERAL_MODE_DRY = 0x02; +const uint8_t FUJITSU_GENERAL_MODE_FAN = 0x03; +// const uint8_t FUJITSU_GENERAL_MODE_10C = 0x0B; -// Fan speed and swing -const uint8_t FUJITSU_GENERAL_FAN_AUTO_BYTE10 = 0x00; -const uint8_t FUJITSU_GENERAL_FAN_HIGH_BYTE10 = 0x01; -const uint8_t FUJITSU_GENERAL_FAN_MEDIUM_BYTE10 = 0x02; -const uint8_t FUJITSU_GENERAL_FAN_LOW_BYTE10 = 0x03; -const uint8_t FUJITSU_GENERAL_FAN_SILENT_BYTE10 = 0x04; -const uint8_t FUJITSU_GENERAL_SWING_NONE_BYTE10 = 0x00; -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; +// Swing +const uint8_t FUJITSU_GENERAL_FAN_NIBBLE = 20; +const uint8_t FUJITSU_GENERAL_FAN_AUTO = 0x00; +const uint8_t FUJITSU_GENERAL_FAN_HIGH = 0x01; +const uint8_t FUJITSU_GENERAL_FAN_MEDIUM = 0x02; +const uint8_t FUJITSU_GENERAL_FAN_LOW = 0x03; +const uint8_t FUJITSU_GENERAL_FAN_SILENT = 0x04; -const uint8_t FUJITSU_GENERAL_BASE_BYTE11 = 0x00; -const uint8_t FUJITSU_GENERAL_BASE_BYTE12 = 0x00; -const uint8_t FUJITSU_GENERAL_BASE_BYTE13 = 0x00; +// Fan speed +const uint8_t FUJITSU_GENERAL_SWING_NIBBLE = 21; +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 -const uint8_t FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14 = 0xA0; -const uint8_t FUJITSU_GENERAL_BASE_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 +// TODO Outdoor Unit Low Noise +// const uint8_t FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14 = 0xA0; +// const uint8_t FUJITSU_GENERAL_STATE_BYTE14 = 0x20; const uint16_t FUJITSU_GENERAL_HEADER_MARK = 3300; const uint16_t FUJITSU_GENERAL_HEADER_SPACE = 1600; + const uint16_t FUJITSU_GENERAL_BIT_MARK = 420; const uint16_t FUJITSU_GENERAL_ONE_SPACE = 1200; const uint16_t FUJITSU_GENERAL_ZERO_SPACE = 420; + const uint16_t FUJITSU_GENERAL_TRL_MARK = 420; const uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000; 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() { if (this->mode == climate::CLIMATE_MODE_OFF) { this->transmit_off_(); return; } - uint8_t remote_state[FUJITSU_GENERAL_STATE_LENGTH] = {0}; - remote_state[0] = FUJITSU_GENERAL_BASE_BYTE0; - remote_state[1] = FUJITSU_GENERAL_BASE_BYTE1; - remote_state[2] = FUJITSU_GENERAL_BASE_BYTE2; - remote_state[3] = FUJITSU_GENERAL_BASE_BYTE3; - remote_state[4] = FUJITSU_GENERAL_BASE_BYTE4; - remote_state[5] = FUJITSU_GENERAL_BASE_BYTE5; - remote_state[6] = FUJITSU_GENERAL_BASE_BYTE6; - remote_state[7] = FUJITSU_GENERAL_BASE_BYTE7; - remote_state[8] = FUJITSU_GENERAL_BASE_BYTE8; - remote_state[9] = FUJITSU_GENERAL_BASE_BYTE9; - remote_state[10] = FUJITSU_GENERAL_BASE_BYTE10; - remote_state[11] = FUJITSU_GENERAL_BASE_BYTE11; - remote_state[12] = FUJITSU_GENERAL_BASE_BYTE12; - remote_state[13] = FUJITSU_GENERAL_BASE_BYTE13; - remote_state[14] = FUJITSU_GENERAL_BASE_BYTE14; - remote_state[15] = FUJITSU_GENERAL_BASE_BYTE15; + ESP_LOGV(TAG, "Transmit state"); + + uint8_t remote_state[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH] = {0}; + + // Common message header + remote_state[0] = FUJITSU_GENERAL_COMMON_BYTE0; + remote_state[1] = FUJITSU_GENERAL_COMMON_BYTE1; + remote_state[2] = FUJITSU_GENERAL_COMMON_BYTE2; + remote_state[3] = FUJITSU_GENERAL_COMMON_BYTE3; + remote_state[4] = FUJITSU_GENERAL_COMMON_BYTE4; + remote_state[5] = FUJITSU_GENERAL_MESSAGE_TYPE_STATE; + remote_state[6] = FUJITSU_GENERAL_STATE_HEADER_BYTE0; + remote_state[7] = FUJITSU_GENERAL_STATE_HEADER_BYTE1; + + // unknown, does not appear to change with any remote settings + remote_state[14] = FUJITSU_GENERAL_STATE_FOOTER_BYTE0; // Set temperature - auto safecelsius = + uint8_t temperature_clamped = (uint8_t) roundf(clamp(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX)); - remote_state[8] = (byte) safecelsius - 16; - remote_state[8] = remote_state[8] << 4; + uint8_t temperature_offset = temperature_clamped - FUJITSU_GENERAL_TEMP_MIN; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_TEMPERATURE_NIBBLE, temperature_offset); - // If not powered - set power on flag + // Set power on 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 switch (this->mode) { 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; 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; 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; 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; case climate::CLIMATE_MODE_AUTO: default: - remote_state[9] = FUJITSU_GENERAL_MODE_AUTO_BYTE9; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_AUTO); break; - // TODO: CLIMATE_MODE_10C are missing in esphome + // TODO: CLIMATE_MODE_10C is missing from esphome } // Set fan switch (this->fan_mode) { 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; 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; 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; case climate::CLIMATE_FAN_AUTO: default: - remote_state[10] = FUJITSU_GENERAL_FAN_AUTO_BYTE10; + SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_AUTO); break; + // TODO Quiet / Silent } // Set swing switch (this->swing_mode) { 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; 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; 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; case climate::CLIMATE_SWING_OFF: 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; } // TODO: missing support for outdoor unit low noise // remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14; - // CRC - 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 + remote_state[FUJITSU_GENERAL_STATE_MESSAGE_LENGTH - 1] = this->checksum_state_(remote_state); - auto transmit = this->transmitter_->transmit(); - 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->transmit_(remote_state, FUJITSU_GENERAL_STATE_MESSAGE_LENGTH); this->power_ = true; } 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; - remote_state[1] = FUJITSU_GENERAL_OFF_BYTE1; - remote_state[2] = FUJITSU_GENERAL_OFF_BYTE2; - remote_state[3] = FUJITSU_GENERAL_OFF_BYTE3; - remote_state[4] = FUJITSU_GENERAL_OFF_BYTE4; - remote_state[5] = FUJITSU_GENERAL_OFF_BYTE5; - remote_state[6] = FUJITSU_GENERAL_OFF_BYTE6; + uint8_t remote_state[FUJITSU_GENERAL_UTIL_MESSAGE_LENGTH] = {0}; + + remote_state[0] = FUJITSU_GENERAL_COMMON_BYTE0; + remote_state[1] = FUJITSU_GENERAL_COMMON_BYTE1; + remote_state[2] = FUJITSU_GENERAL_COMMON_BYTE2; + remote_state[3] = FUJITSU_GENERAL_COMMON_BYTE3; + 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 data = transmit.get_data(); @@ -232,22 +215,191 @@ void FujitsuGeneralClimate::transmit_off_() { 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 + for (uint8_t i = 0; i < length; ++i) { + const uint8_t byte = message[i]; + for (uint8_t mask = 0b00000001; mask > 0; mask <<= 1) { // write from right to left 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); - // Next bits } } + // Footer data->mark(FUJITSU_GENERAL_TRL_MARK); data->space(FUJITSU_GENERAL_TRL_SPACE); transmit.perform(); +} - this->power_ = false; +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; + } + + else { + ESP_LOGV(TAG, "Received unsupprted message type %X", recv_message_type); + return false; + } + + this->publish_state(); + return true; } } // namespace fujitsu_general diff --git a/esphome/components/fujitsu_general/fujitsu_general.h b/esphome/components/fujitsu_general/fujitsu_general.h index 80db81a167..8154d7a1d2 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.h +++ b/esphome/components/fujitsu_general/fujitsu_general.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/log.h" #include "esphome/core/component.h" #include "esphome/core/automation.h" #include "esphome/components/climate_ir/climate_ir.h" @@ -7,9 +8,17 @@ namespace esphome { 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 { 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: /// 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. 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}; };