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 |     "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 { | ||||||
|  |  | ||||||
| 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 | #define SET_NIBBLE(message, nibble, value) (message[nibble / 2] |= (value & 0b00001111) << ((nibble % 2) ? 0 : 4)) | ||||||
| const uint16_t FUJITSU_GENERAL_STATE_LENGTH = 16; | #define GET_NIBBLE(message, nibble) ((message[nibble / 2] >> ((nibble % 2) ? 0 : 4)) & 0b00001111) | ||||||
|  |  | ||||||
| const uint8_t FUJITSU_GENERAL_BASE_BYTE0 = 0x14; | static const char* TAG = "fujitsu_general.climate"; | ||||||
| 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; |  | ||||||
|  |  | ||||||
| // Temperature and POWER ON | // Common header | ||||||
| const uint8_t FUJITSU_GENERAL_POWER_ON_MASK_BYTE8 = 0b00000001; | const uint8_t FUJITSU_GENERAL_COMMON_LENGTH = 6; | ||||||
| const uint8_t FUJITSU_GENERAL_BASE_BYTE8 = 0x40; | 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 | // 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,22 +215,191 @@ 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(); | ||||||
|  | } | ||||||
|  |  | ||||||
|   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 | }  // namespace fujitsu_general | ||||||
|   | |||||||
| @@ -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}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user