mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Added component Daikin BRC to support ceiling cassette heatpumps (#3743)
This commit is contained in:
		| @@ -58,6 +58,7 @@ esphome/components/cse7761/* @berfenger | ||||
| esphome/components/ct_clamp/* @jesserockz | ||||
| esphome/components/current_based/* @djwmarcx | ||||
| esphome/components/dac7678/* @NickB1 | ||||
| esphome/components/daikin_brc/* @hagak | ||||
| esphome/components/daly_bms/* @s1lvi0 | ||||
| esphome/components/dashboard_import/* @esphome/core | ||||
| esphome/components/debug/* @OttoWinter | ||||
|   | ||||
							
								
								
									
										1
									
								
								esphome/components/daikin_brc/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/daikin_brc/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@hagak"] | ||||
							
								
								
									
										24
									
								
								esphome/components/daikin_brc/climate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphome/components/daikin_brc/climate.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import climate_ir | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| AUTO_LOAD = ["climate_ir"] | ||||
|  | ||||
| daikin_brc_ns = cg.esphome_ns.namespace("daikin_brc") | ||||
| DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR) | ||||
|  | ||||
| CONF_USE_FAHRENHEIT = "use_fahrenheit" | ||||
|  | ||||
| CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(DaikinBrcClimate), | ||||
|         cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean, | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await climate_ir.register_climate_ir(var, config) | ||||
|     cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT])) | ||||
							
								
								
									
										273
									
								
								esphome/components/daikin_brc/daikin_brc.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										273
									
								
								esphome/components/daikin_brc/daikin_brc.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,273 @@ | ||||
| #include "daikin_brc.h" | ||||
| #include "esphome/components/remote_base/remote_base.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace daikin_brc { | ||||
|  | ||||
| static const char *const TAG = "daikin_brc.climate"; | ||||
|  | ||||
| void DaikinBrcClimate::control(const climate::ClimateCall &call) { | ||||
|   this->mode_button_ = 0x00; | ||||
|   if (call.get_mode().has_value()) { | ||||
|     // Need to determine if this is call due to Mode button pressed so that we can set the Mode button byte | ||||
|     this->mode_button_ = DAIKIN_BRC_IR_MODE_BUTTON; | ||||
|   } | ||||
|   ClimateIR::control(call); | ||||
| } | ||||
|  | ||||
| void DaikinBrcClimate::transmit_state() { | ||||
|   uint8_t remote_state[DAIKIN_BRC_TRANSMIT_FRAME_SIZE] = {0x11, 0xDA, 0x17, 0x18, 0x04, 0x00, 0x1E, 0x11, | ||||
|                                                           0xDA, 0x17, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|                                                           0x00, 0x00, 0x00, 0x00, 0x20, 0x00}; | ||||
|  | ||||
|   remote_state[12] = this->alt_mode_(); | ||||
|   remote_state[13] = this->mode_button_; | ||||
|   remote_state[14] = this->operation_mode_(); | ||||
|   remote_state[17] = this->temperature_(); | ||||
|   remote_state[18] = this->fan_speed_swing_(); | ||||
|  | ||||
|   // Calculate checksum | ||||
|   for (int i = DAIKIN_BRC_PREAMBLE_SIZE; i < DAIKIN_BRC_TRANSMIT_FRAME_SIZE - 1; i++) { | ||||
|     remote_state[DAIKIN_BRC_TRANSMIT_FRAME_SIZE - 1] += remote_state[i]; | ||||
|   } | ||||
|  | ||||
|   auto transmit = this->transmitter_->transmit(); | ||||
|   auto *data = transmit.get_data(); | ||||
|   data->set_carrier_frequency(DAIKIN_BRC_IR_FREQUENCY); | ||||
|  | ||||
|   data->mark(DAIKIN_BRC_HEADER_MARK); | ||||
|   data->space(DAIKIN_BRC_HEADER_SPACE); | ||||
|   for (int i = 0; i < DAIKIN_BRC_PREAMBLE_SIZE; i++) { | ||||
|     for (uint8_t mask = 1; mask > 0; mask <<= 1) {  // iterate through bit mask | ||||
|       data->mark(DAIKIN_BRC_BIT_MARK); | ||||
|       bool bit = remote_state[i] & mask; | ||||
|       data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE); | ||||
|     } | ||||
|   } | ||||
|   data->mark(DAIKIN_BRC_BIT_MARK); | ||||
|   data->space(DAIKIN_BRC_MESSAGE_SPACE); | ||||
|   data->mark(DAIKIN_BRC_HEADER_MARK); | ||||
|   data->space(DAIKIN_BRC_HEADER_SPACE); | ||||
|  | ||||
|   for (int i = DAIKIN_BRC_PREAMBLE_SIZE; i < DAIKIN_BRC_TRANSMIT_FRAME_SIZE; i++) { | ||||
|     for (uint8_t mask = 1; mask > 0; mask <<= 1) {  // iterate through bit mask | ||||
|       data->mark(DAIKIN_BRC_BIT_MARK); | ||||
|       bool bit = remote_state[i] & mask; | ||||
|       data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   data->mark(DAIKIN_BRC_BIT_MARK); | ||||
|   data->space(0); | ||||
|  | ||||
|   transmit.perform(); | ||||
| } | ||||
|  | ||||
| uint8_t DaikinBrcClimate::alt_mode_() { | ||||
|   uint8_t alt_mode = 0x00; | ||||
|   switch (this->mode) { | ||||
|     case climate::CLIMATE_MODE_DRY: | ||||
|       alt_mode = 0x23; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_FAN_ONLY: | ||||
|       alt_mode = 0x63; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_HEAT_COOL: | ||||
|     case climate::CLIMATE_MODE_COOL: | ||||
|     case climate::CLIMATE_MODE_HEAT: | ||||
|     default: | ||||
|       alt_mode = 0x73; | ||||
|       break; | ||||
|   } | ||||
|   return alt_mode; | ||||
| } | ||||
|  | ||||
| uint8_t DaikinBrcClimate::operation_mode_() { | ||||
|   uint8_t operating_mode = DAIKIN_BRC_MODE_ON; | ||||
|   switch (this->mode) { | ||||
|     case climate::CLIMATE_MODE_COOL: | ||||
|       operating_mode |= DAIKIN_BRC_MODE_COOL; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_DRY: | ||||
|       operating_mode |= DAIKIN_BRC_MODE_DRY; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_HEAT: | ||||
|       operating_mode |= DAIKIN_BRC_MODE_HEAT; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_HEAT_COOL: | ||||
|       operating_mode |= DAIKIN_BRC_MODE_AUTO; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_FAN_ONLY: | ||||
|       operating_mode |= DAIKIN_BRC_MODE_FAN; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_OFF: | ||||
|     default: | ||||
|       operating_mode = DAIKIN_BRC_MODE_OFF; | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   return operating_mode; | ||||
| } | ||||
|  | ||||
| uint8_t DaikinBrcClimate::fan_speed_swing_() { | ||||
|   uint16_t fan_speed; | ||||
|   switch (this->fan_mode.value()) { | ||||
|     case climate::CLIMATE_FAN_LOW: | ||||
|       fan_speed = DAIKIN_BRC_FAN_1; | ||||
|       break; | ||||
|     case climate::CLIMATE_FAN_MEDIUM: | ||||
|       fan_speed = DAIKIN_BRC_FAN_2; | ||||
|       break; | ||||
|     case climate::CLIMATE_FAN_HIGH: | ||||
|       fan_speed = DAIKIN_BRC_FAN_3; | ||||
|       break; | ||||
|     default: | ||||
|       fan_speed = DAIKIN_BRC_FAN_1; | ||||
|   } | ||||
|  | ||||
|   // If swing is enabled switch first 4 bits to 1111 | ||||
|   switch (this->swing_mode) { | ||||
|     case climate::CLIMATE_SWING_BOTH: | ||||
|       fan_speed |= DAIKIN_BRC_IR_SWING_ON; | ||||
|       break; | ||||
|     default: | ||||
|       fan_speed |= DAIKIN_BRC_IR_SWING_OFF; | ||||
|       break; | ||||
|   } | ||||
|   return fan_speed; | ||||
| } | ||||
|  | ||||
| uint8_t DaikinBrcClimate::temperature_() { | ||||
|   // Force special temperatures depending on the mode | ||||
|   switch (this->mode) { | ||||
|     case climate::CLIMATE_MODE_FAN_ONLY: | ||||
|     case climate::CLIMATE_MODE_DRY: | ||||
|       if (this->fahrenheit_) { | ||||
|         return DAIKIN_BRC_IR_DRY_FAN_TEMP_F; | ||||
|       } | ||||
|       return DAIKIN_BRC_IR_DRY_FAN_TEMP_C; | ||||
|     case climate::CLIMATE_MODE_HEAT_COOL: | ||||
|     default: | ||||
|       uint8_t temperature; | ||||
|       // Temperature in remote is in F | ||||
|       if (this->fahrenheit_) { | ||||
|         temperature = (uint8_t) roundf( | ||||
|             clamp<float>(((this->target_temperature * 1.8) + 32), DAIKIN_BRC_TEMP_MIN_F, DAIKIN_BRC_TEMP_MAX_F)); | ||||
|       } else { | ||||
|         temperature = ((uint8_t) roundf(this->target_temperature) - 9) << 1; | ||||
|       } | ||||
|       return temperature; | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool DaikinBrcClimate::parse_state_frame_(const uint8_t frame[]) { | ||||
|   uint8_t checksum = 0; | ||||
|   for (int i = 0; i < (DAIKIN_BRC_STATE_FRAME_SIZE - 1); i++) { | ||||
|     checksum += frame[i]; | ||||
|   } | ||||
|   if (frame[DAIKIN_BRC_STATE_FRAME_SIZE - 1] != checksum) { | ||||
|     ESP_LOGCONFIG(TAG, "Bad CheckSum %x", checksum); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   uint8_t mode = frame[7]; | ||||
|   if (mode & DAIKIN_BRC_MODE_ON) { | ||||
|     switch (mode & 0xF0) { | ||||
|       case DAIKIN_BRC_MODE_COOL: | ||||
|         this->mode = climate::CLIMATE_MODE_COOL; | ||||
|         break; | ||||
|       case DAIKIN_BRC_MODE_DRY: | ||||
|         this->mode = climate::CLIMATE_MODE_DRY; | ||||
|         break; | ||||
|       case DAIKIN_BRC_MODE_HEAT: | ||||
|         this->mode = climate::CLIMATE_MODE_HEAT; | ||||
|         break; | ||||
|       case DAIKIN_BRC_MODE_AUTO: | ||||
|         this->mode = climate::CLIMATE_MODE_HEAT_COOL; | ||||
|         break; | ||||
|       case DAIKIN_BRC_MODE_FAN: | ||||
|         this->mode = climate::CLIMATE_MODE_FAN_ONLY; | ||||
|         break; | ||||
|     } | ||||
|   } else { | ||||
|     this->mode = climate::CLIMATE_MODE_OFF; | ||||
|   } | ||||
|  | ||||
|   uint8_t temperature = frame[10]; | ||||
|   float temperature_c; | ||||
|   if (this->fahrenheit_) { | ||||
|     temperature_c = clamp<float>(((temperature - 32) / 1.8), DAIKIN_BRC_TEMP_MIN_C, DAIKIN_BRC_TEMP_MAX_C); | ||||
|   } else { | ||||
|     temperature_c = (temperature >> 1) + 9; | ||||
|   } | ||||
|  | ||||
|   this->target_temperature = temperature_c; | ||||
|  | ||||
|   uint8_t fan_mode = frame[11]; | ||||
|   uint8_t swing_mode = frame[11]; | ||||
|   switch (swing_mode & 0xF) { | ||||
|     case DAIKIN_BRC_IR_SWING_ON: | ||||
|       this->swing_mode = climate::CLIMATE_SWING_BOTH; | ||||
|       break; | ||||
|     case DAIKIN_BRC_IR_SWING_OFF: | ||||
|       this->swing_mode = climate::CLIMATE_SWING_OFF; | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   switch (fan_mode & 0xF0) { | ||||
|     case DAIKIN_BRC_FAN_1: | ||||
|       this->fan_mode = climate::CLIMATE_FAN_LOW; | ||||
|       break; | ||||
|     case DAIKIN_BRC_FAN_2: | ||||
|       this->fan_mode = climate::CLIMATE_FAN_MEDIUM; | ||||
|       break; | ||||
|     case DAIKIN_BRC_FAN_3: | ||||
|       this->fan_mode = climate::CLIMATE_FAN_HIGH; | ||||
|       break; | ||||
|   } | ||||
|   this->publish_state(); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool DaikinBrcClimate::on_receive(remote_base::RemoteReceiveData data) { | ||||
|   uint8_t state_frame[DAIKIN_BRC_STATE_FRAME_SIZE] = {}; | ||||
|   if (!data.expect_item(DAIKIN_BRC_HEADER_MARK, DAIKIN_BRC_HEADER_SPACE)) { | ||||
|     return false; | ||||
|   } | ||||
|   for (uint8_t pos = 0; pos < DAIKIN_BRC_STATE_FRAME_SIZE; pos++) { | ||||
|     uint8_t byte = 0; | ||||
|     for (int8_t bit = 0; bit < 8; bit++) { | ||||
|       if (data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ONE_SPACE)) { | ||||
|         byte |= 1 << bit; | ||||
|       } else if (!data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ZERO_SPACE)) { | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
|     state_frame[pos] = byte; | ||||
|     if (pos == 0) { | ||||
|       // frame header | ||||
|       if (byte != 0x11) | ||||
|         return false; | ||||
|     } else if (pos == 1) { | ||||
|       // frame header | ||||
|       if (byte != 0xDA) | ||||
|         return false; | ||||
|     } else if (pos == 2) { | ||||
|       // frame header | ||||
|       if (byte != 0x17) | ||||
|         return false; | ||||
|     } else if (pos == 3) { | ||||
|       // frame header | ||||
|       if (byte != 0x18) | ||||
|         return false; | ||||
|     } else if (pos == 4) { | ||||
|       // frame type | ||||
|       if (byte != 0x00) | ||||
|         return false; | ||||
|     } | ||||
|   } | ||||
|   return this->parse_state_frame_(state_frame); | ||||
| } | ||||
|  | ||||
| }  // namespace daikin_brc | ||||
| }  // namespace esphome | ||||
							
								
								
									
										82
									
								
								esphome/components/daikin_brc/daikin_brc.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								esphome/components/daikin_brc/daikin_brc.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/climate_ir/climate_ir.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace daikin_brc { | ||||
|  | ||||
| // Values for Daikin BRC4CXXX IR Controllers | ||||
| // Temperature | ||||
| const uint8_t DAIKIN_BRC_TEMP_MIN_F = 60;                                // fahrenheit | ||||
| const uint8_t DAIKIN_BRC_TEMP_MAX_F = 90;                                // fahrenheit | ||||
| const float DAIKIN_BRC_TEMP_MIN_C = (DAIKIN_BRC_TEMP_MIN_F - 32) / 1.8;  // fahrenheit | ||||
| const float DAIKIN_BRC_TEMP_MAX_C = (DAIKIN_BRC_TEMP_MAX_F - 32) / 1.8;  // fahrenheit | ||||
|  | ||||
| // Modes | ||||
| const uint8_t DAIKIN_BRC_MODE_AUTO = 0x30; | ||||
| const uint8_t DAIKIN_BRC_MODE_COOL = 0x20; | ||||
| const uint8_t DAIKIN_BRC_MODE_HEAT = 0x10; | ||||
| const uint8_t DAIKIN_BRC_MODE_DRY = 0x70; | ||||
| const uint8_t DAIKIN_BRC_MODE_FAN = 0x00; | ||||
| const uint8_t DAIKIN_BRC_MODE_OFF = 0x00; | ||||
| const uint8_t DAIKIN_BRC_MODE_ON = 0x01; | ||||
|  | ||||
| // Fan Speed | ||||
| const uint8_t DAIKIN_BRC_FAN_1 = 0x10; | ||||
| const uint8_t DAIKIN_BRC_FAN_2 = 0x30; | ||||
| const uint8_t DAIKIN_BRC_FAN_3 = 0x50; | ||||
| const uint8_t DAIKIN_BRC_FAN_AUTO = 0xA0; | ||||
|  | ||||
| // IR Transmission | ||||
| const uint32_t DAIKIN_BRC_IR_FREQUENCY = 38000; | ||||
| const uint32_t DAIKIN_BRC_HEADER_MARK = 5070; | ||||
| const uint32_t DAIKIN_BRC_HEADER_SPACE = 2140; | ||||
| const uint32_t DAIKIN_BRC_BIT_MARK = 370; | ||||
| const uint32_t DAIKIN_BRC_ONE_SPACE = 1780; | ||||
| const uint32_t DAIKIN_BRC_ZERO_SPACE = 710; | ||||
| const uint32_t DAIKIN_BRC_MESSAGE_SPACE = 29410; | ||||
|  | ||||
| const uint8_t DAIKIN_BRC_IR_DRY_FAN_TEMP_F = 72;            // Dry/Fan mode is always 17 Celsius. | ||||
| const uint8_t DAIKIN_BRC_IR_DRY_FAN_TEMP_C = (17 - 9) * 2;  // Dry/Fan mode is always 17 Celsius. | ||||
| const uint8_t DAIKIN_BRC_IR_SWING_ON = 0x5; | ||||
| const uint8_t DAIKIN_BRC_IR_SWING_OFF = 0x6; | ||||
| const uint8_t DAIKIN_BRC_IR_MODE_BUTTON = 0x4;  // This is set after a mode action | ||||
|  | ||||
| // State Frame size | ||||
| const uint8_t DAIKIN_BRC_STATE_FRAME_SIZE = 15; | ||||
| // Preamble size | ||||
| const uint8_t DAIKIN_BRC_PREAMBLE_SIZE = 7; | ||||
| // Transmit Frame size - includes a preamble | ||||
| const uint8_t DAIKIN_BRC_TRANSMIT_FRAME_SIZE = DAIKIN_BRC_PREAMBLE_SIZE + DAIKIN_BRC_STATE_FRAME_SIZE; | ||||
|  | ||||
| class DaikinBrcClimate : public climate_ir::ClimateIR { | ||||
|  public: | ||||
|   DaikinBrcClimate() | ||||
|       : climate_ir::ClimateIR(DAIKIN_BRC_TEMP_MIN_C, DAIKIN_BRC_TEMP_MAX_C, 0.5f, true, true, | ||||
|                               {climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, | ||||
|                               {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH}) {} | ||||
|  | ||||
|   /// Set use of Fahrenheit units | ||||
|   void set_fahrenheit(bool value) { | ||||
|     this->fahrenheit_ = value; | ||||
|     this->temperature_step_ = value ? 0.5f : 1.0f; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   uint8_t mode_button_ = 0x00; | ||||
|   // Capture if the MODE was changed | ||||
|   void control(const climate::ClimateCall &call) override; | ||||
|   // Transmit via IR the state of this climate controller. | ||||
|   void transmit_state() override; | ||||
|   uint8_t alt_mode_(); | ||||
|   uint8_t operation_mode_(); | ||||
|   uint8_t fan_speed_swing_(); | ||||
|   uint8_t temperature_(); | ||||
|   // Handle received IR Buffer | ||||
|   bool on_receive(remote_base::RemoteReceiveData data) override; | ||||
|   bool parse_state_frame_(const uint8_t frame[]); | ||||
|   bool fahrenheit_{false}; | ||||
| }; | ||||
|  | ||||
| }  // namespace daikin_brc | ||||
| }  // namespace esphome | ||||
| @@ -1862,6 +1862,9 @@ climate: | ||||
|     name: Fujitsu General Climate | ||||
|   - platform: daikin | ||||
|     name: Daikin Climate | ||||
|   - platform: daikin_brc | ||||
|     name: Daikin BRC Climate | ||||
|     use_fahrenheit: true | ||||
|   - platform: delonghi | ||||
|     name: Delonghi Climate | ||||
|   - platform: yashima | ||||
|   | ||||
		Reference in New Issue
	
	Block a user