mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Added receive for Fujitsu ACs (#1577)
This commit is contained in:
		| @@ -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), | ||||
|     } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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}; | ||||
| }; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user