mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Climate whirlpool (#1029)
* wip * transmitter ready * climate.whirlpool receiver implemented (#971) * receiver implemented * Support for two models of temp ranges * temperature type and lint * more lint Co-authored-by: Guillermo Ruffino <glm.net@gmail.com> * add test * not mess line endings Co-authored-by: mmanza <40872469+mmanza@users.noreply.github.com>
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							d447548893
						
					
				
				
					commit
					8040e3cf95
				
			
							
								
								
									
										0
									
								
								esphome/components/whirlpool/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/whirlpool/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										26
									
								
								esphome/components/whirlpool/climate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/whirlpool/climate.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import climate_ir | ||||||
|  | from esphome.const import CONF_ID, CONF_MODEL | ||||||
|  |  | ||||||
|  | AUTO_LOAD = ['climate_ir'] | ||||||
|  |  | ||||||
|  | whirlpool_ns = cg.esphome_ns.namespace('whirlpool') | ||||||
|  | WhirlpoolClimate = whirlpool_ns.class_('WhirlpoolClimate', climate_ir.ClimateIR) | ||||||
|  |  | ||||||
|  | Model = whirlpool_ns.enum('Model') | ||||||
|  | MODELS = { | ||||||
|  |     'DG11J1-3A': Model.MODEL_DG11J1_3A, | ||||||
|  |     'DG11J1-91': Model.MODEL_DG11J1_91, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ | ||||||
|  |     cv.GenerateID(): cv.declare_id(WhirlpoolClimate), | ||||||
|  |     cv.Optional(CONF_MODEL, default='DG11J1-3A'): cv.enum(MODELS, upper=True) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     yield climate_ir.register_climate_ir(var, config) | ||||||
|  |     cg.add(var.set_model(config[CONF_MODEL])) | ||||||
							
								
								
									
										289
									
								
								esphome/components/whirlpool/whirlpool.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								esphome/components/whirlpool/whirlpool.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,289 @@ | |||||||
|  | #include "whirlpool.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace whirlpool { | ||||||
|  |  | ||||||
|  | static const char *TAG = "whirlpool.climate"; | ||||||
|  |  | ||||||
|  | const uint16_t WHIRLPOOL_HEADER_MARK = 9000; | ||||||
|  | const uint16_t WHIRLPOOL_HEADER_SPACE = 4494; | ||||||
|  | const uint16_t WHIRLPOOL_BIT_MARK = 572; | ||||||
|  | const uint16_t WHIRLPOOL_ONE_SPACE = 1659; | ||||||
|  | const uint16_t WHIRLPOOL_ZERO_SPACE = 553; | ||||||
|  | const uint32_t WHIRLPOOL_GAP = 7960; | ||||||
|  |  | ||||||
|  | const uint32_t WHIRLPOOL_CARRIER_FREQUENCY = 38000; | ||||||
|  |  | ||||||
|  | const uint8_t WHIRLPOOL_STATE_LENGTH = 21; | ||||||
|  |  | ||||||
|  | const uint8_t WHIRLPOOL_HEAT = 0; | ||||||
|  | const uint8_t WHIRLPOOL_DRY = 3; | ||||||
|  | const uint8_t WHIRLPOOL_COOL = 2; | ||||||
|  | const uint8_t WHIRLPOOL_FAN = 4; | ||||||
|  | const uint8_t WHIRLPOOL_AUTO = 1; | ||||||
|  |  | ||||||
|  | const uint8_t WHIRLPOOL_FAN_AUTO = 0; | ||||||
|  | const uint8_t WHIRLPOOL_FAN_HIGH = 1; | ||||||
|  | const uint8_t WHIRLPOOL_FAN_MED = 2; | ||||||
|  | const uint8_t WHIRLPOOL_FAN_LOW = 3; | ||||||
|  |  | ||||||
|  | const uint8_t WHIRLPOOL_SWING_MASK = 128; | ||||||
|  |  | ||||||
|  | const uint8_t WHIRLPOOL_POWER = 0x04; | ||||||
|  |  | ||||||
|  | void WhirlpoolClimate::transmit_state() { | ||||||
|  |   uint8_t remote_state[WHIRLPOOL_STATE_LENGTH] = {0}; | ||||||
|  |   remote_state[0] = 0x83; | ||||||
|  |   remote_state[1] = 0x06; | ||||||
|  |   remote_state[6] = 0x80; | ||||||
|  |   // MODEL DG11J191 | ||||||
|  |   remote_state[18] = 0x08; | ||||||
|  |  | ||||||
|  |   auto powered_on = this->mode != climate::CLIMATE_MODE_OFF; | ||||||
|  |   if (powered_on != this->powered_on_assumed_) { | ||||||
|  |     // Set power toggle command | ||||||
|  |     remote_state[2] = 4; | ||||||
|  |     remote_state[15] = 1; | ||||||
|  |     this->powered_on_assumed_ = powered_on; | ||||||
|  |   } | ||||||
|  |   switch (this->mode) { | ||||||
|  |     case climate::CLIMATE_MODE_AUTO: | ||||||
|  |       // set fan auto | ||||||
|  |       // set temp auto temp | ||||||
|  |       // set sleep false | ||||||
|  |       remote_state[3] = WHIRLPOOL_AUTO; | ||||||
|  |       remote_state[15] = 0x17; | ||||||
|  |       break; | ||||||
|  |     case climate::CLIMATE_MODE_HEAT: | ||||||
|  |       remote_state[3] = WHIRLPOOL_HEAT; | ||||||
|  |       remote_state[15] = 6; | ||||||
|  |       break; | ||||||
|  |     case climate::CLIMATE_MODE_COOL: | ||||||
|  |       remote_state[3] = WHIRLPOOL_COOL; | ||||||
|  |       remote_state[15] = 6; | ||||||
|  |       break; | ||||||
|  |     case climate::CLIMATE_MODE_DRY: | ||||||
|  |       remote_state[3] = WHIRLPOOL_DRY; | ||||||
|  |       remote_state[15] = 6; | ||||||
|  |       break; | ||||||
|  |     case climate::CLIMATE_MODE_FAN_ONLY: | ||||||
|  |       remote_state[3] = WHIRLPOOL_FAN; | ||||||
|  |       remote_state[15] = 6; | ||||||
|  |       break; | ||||||
|  |     case climate::CLIMATE_MODE_OFF: | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Temperature | ||||||
|  |   auto temp = (uint8_t) roundf(clamp(this->target_temperature, this->temperature_min_(), this->temperature_max_())); | ||||||
|  |   remote_state[3] |= (uint8_t)(temp - this->temperature_min_()) << 4; | ||||||
|  |  | ||||||
|  |   // Fan speed | ||||||
|  |   switch (this->fan_mode) { | ||||||
|  |     case climate::CLIMATE_FAN_HIGH: | ||||||
|  |       remote_state[2] |= WHIRLPOOL_FAN_HIGH; | ||||||
|  |       break; | ||||||
|  |     case climate::CLIMATE_FAN_MEDIUM: | ||||||
|  |       remote_state[2] |= WHIRLPOOL_FAN_MED; | ||||||
|  |       break; | ||||||
|  |     case climate::CLIMATE_FAN_LOW: | ||||||
|  |       remote_state[2] |= WHIRLPOOL_FAN_LOW; | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Swing | ||||||
|  |   ESP_LOGV(TAG, "send swing %s", this->send_swing_cmd_ ? "true" : "false"); | ||||||
|  |   if (this->send_swing_cmd_) { | ||||||
|  |     if (this->swing_mode == climate::CLIMATE_SWING_VERTICAL || this->swing_mode == climate::CLIMATE_SWING_OFF) { | ||||||
|  |       remote_state[2] |= 128; | ||||||
|  |       remote_state[8] |= 64; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Checksum | ||||||
|  |   for (uint8_t i = 2; i < 12; i++) | ||||||
|  |     remote_state[13] ^= remote_state[i]; | ||||||
|  |   for (uint8_t i = 14; i < 20; i++) | ||||||
|  |     remote_state[20] ^= remote_state[i]; | ||||||
|  |  | ||||||
|  |   ESP_LOGV(TAG, | ||||||
|  |            "Sending: %02X %02X %02X %02X   %02X %02X %02X %02X   %02X %02X %02X %02X   %02X %02X %02X %02X   %02X %02X " | ||||||
|  |            "%02X %02X   %02X", | ||||||
|  |            remote_state[0], remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], | ||||||
|  |            remote_state[6], remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], | ||||||
|  |            remote_state[12], remote_state[13], remote_state[14], remote_state[15], remote_state[16], remote_state[17], | ||||||
|  |            remote_state[18], remote_state[19], remote_state[20]); | ||||||
|  |  | ||||||
|  |   // Send code | ||||||
|  |   auto transmit = this->transmitter_->transmit(); | ||||||
|  |   auto data = transmit.get_data(); | ||||||
|  |  | ||||||
|  |   data->set_carrier_frequency(38000); | ||||||
|  |  | ||||||
|  |   // Header | ||||||
|  |   data->mark(WHIRLPOOL_HEADER_MARK); | ||||||
|  |   data->space(WHIRLPOOL_HEADER_SPACE); | ||||||
|  |   // Data | ||||||
|  |   auto bytes_sent = 0; | ||||||
|  |   for (uint8_t i : remote_state) { | ||||||
|  |     for (uint8_t j = 0; j < 8; j++) { | ||||||
|  |       data->mark(WHIRLPOOL_BIT_MARK); | ||||||
|  |       bool bit = i & (1 << j); | ||||||
|  |       data->space(bit ? WHIRLPOOL_ONE_SPACE : WHIRLPOOL_ZERO_SPACE); | ||||||
|  |     } | ||||||
|  |     bytes_sent++; | ||||||
|  |     if (bytes_sent == 6 || bytes_sent == 14) { | ||||||
|  |       // Divider | ||||||
|  |       data->mark(WHIRLPOOL_BIT_MARK); | ||||||
|  |       data->space(WHIRLPOOL_GAP); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // Footer | ||||||
|  |   data->mark(WHIRLPOOL_BIT_MARK); | ||||||
|  |  | ||||||
|  |   transmit.perform(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { | ||||||
|  |   // Validate header | ||||||
|  |   if (!data.expect_item(WHIRLPOOL_HEADER_MARK, WHIRLPOOL_HEADER_SPACE)) { | ||||||
|  |     ESP_LOGV(TAG, "Header fail"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t remote_state[WHIRLPOOL_STATE_LENGTH] = {0}; | ||||||
|  |   // Read all bytes. | ||||||
|  |   for (int i = 0; i < WHIRLPOOL_STATE_LENGTH; i++) { | ||||||
|  |     // Read bit | ||||||
|  |     if (i == 6 || i == 14) { | ||||||
|  |       if (!data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_GAP)) | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     for (int j = 0; j < 8; j++) { | ||||||
|  |       if (data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ONE_SPACE)) | ||||||
|  |         remote_state[i] |= 1 << j; | ||||||
|  |  | ||||||
|  |       else if (!data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ZERO_SPACE)) { | ||||||
|  |         ESP_LOGV(TAG, "Byte %d bit %d fail", i, j); | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ESP_LOGVV(TAG, "Byte %d %02X", i, remote_state[i]); | ||||||
|  |   } | ||||||
|  |   // Validate footer | ||||||
|  |   if (!data.expect_mark(WHIRLPOOL_BIT_MARK)) { | ||||||
|  |     ESP_LOGV(TAG, "Footer fail"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t checksum13 = 0; | ||||||
|  |   uint8_t checksum20 = 0; | ||||||
|  |   // Calculate  checksum and compare with signal value. | ||||||
|  |   for (uint8_t i = 2; i < 12; i++) | ||||||
|  |     checksum13 ^= remote_state[i]; | ||||||
|  |   for (uint8_t i = 14; i < 20; i++) | ||||||
|  |     checksum20 ^= remote_state[i]; | ||||||
|  |  | ||||||
|  |   if (checksum13 != remote_state[13] || checksum20 != remote_state[20]) { | ||||||
|  |     ESP_LOGVV(TAG, "Checksum fail"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGV( | ||||||
|  |       TAG, | ||||||
|  |       "Received: %02X %02X %02X %02X   %02X %02X %02X %02X   %02X %02X %02X %02X   %02X %02X %02X %02X   %02X %02X " | ||||||
|  |       "%02X %02X   %02X", | ||||||
|  |       remote_state[0], remote_state[1], remote_state[2], remote_state[3], remote_state[4], remote_state[5], | ||||||
|  |       remote_state[6], remote_state[7], remote_state[8], remote_state[9], remote_state[10], remote_state[11], | ||||||
|  |       remote_state[12], remote_state[13], remote_state[14], remote_state[15], remote_state[16], remote_state[17], | ||||||
|  |       remote_state[18], remote_state[19], remote_state[20]); | ||||||
|  |  | ||||||
|  |   // verify header remote code | ||||||
|  |   if (remote_state[0] != 0x83 || remote_state[1] != 0x06) | ||||||
|  |     return false; | ||||||
|  |  | ||||||
|  |   // powr on/off button | ||||||
|  |   ESP_LOGV(TAG, "Power: %02X", (remote_state[2] & WHIRLPOOL_POWER)); | ||||||
|  |  | ||||||
|  |   if ((remote_state[2] & WHIRLPOOL_POWER) == WHIRLPOOL_POWER) { | ||||||
|  |     auto powered_on = this->mode != climate::CLIMATE_MODE_OFF; | ||||||
|  |  | ||||||
|  |     if (powered_on) { | ||||||
|  |       this->mode = climate::CLIMATE_MODE_OFF; | ||||||
|  |       this->powered_on_assumed_ = false; | ||||||
|  |     } else { | ||||||
|  |       this->powered_on_assumed_ = true; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Set received mode | ||||||
|  |   if (powered_on_assumed_) { | ||||||
|  |     auto mode = remote_state[3] & 0x7; | ||||||
|  |     ESP_LOGV(TAG, "Mode: %02X", mode); | ||||||
|  |     switch (mode) { | ||||||
|  |       case WHIRLPOOL_HEAT: | ||||||
|  |         this->mode = climate::CLIMATE_MODE_HEAT; | ||||||
|  |         break; | ||||||
|  |       case WHIRLPOOL_COOL: | ||||||
|  |         this->mode = climate::CLIMATE_MODE_COOL; | ||||||
|  |         break; | ||||||
|  |       case WHIRLPOOL_DRY: | ||||||
|  |         this->mode = climate::CLIMATE_MODE_DRY; | ||||||
|  |         break; | ||||||
|  |       case WHIRLPOOL_FAN: | ||||||
|  |         this->mode = climate::CLIMATE_MODE_FAN_ONLY; | ||||||
|  |         break; | ||||||
|  |       case WHIRLPOOL_AUTO: | ||||||
|  |         this->mode = climate::CLIMATE_MODE_AUTO; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Set received temp | ||||||
|  |   int temp = remote_state[3] & 0xF0; | ||||||
|  |   ESP_LOGVV(TAG, "Temperature Raw: %02X", temp); | ||||||
|  |   temp = (uint8_t) temp >> 4; | ||||||
|  |   temp += static_cast<int>(this->temperature_min_()); | ||||||
|  |   ESP_LOGVV(TAG, "Temperature Climate: %u", temp); | ||||||
|  |   this->target_temperature = temp; | ||||||
|  |  | ||||||
|  |   // Set received fan speed | ||||||
|  |   auto fan = remote_state[2] & 0x03; | ||||||
|  |   ESP_LOGVV(TAG, "Fan: %02X", fan); | ||||||
|  |   switch (fan) { | ||||||
|  |     case WHIRLPOOL_FAN_HIGH: | ||||||
|  |       this->fan_mode = climate::CLIMATE_FAN_HIGH; | ||||||
|  |       break; | ||||||
|  |     case WHIRLPOOL_FAN_MED: | ||||||
|  |       this->fan_mode = climate::CLIMATE_FAN_MEDIUM; | ||||||
|  |       break; | ||||||
|  |     case WHIRLPOOL_FAN_LOW: | ||||||
|  |       this->fan_mode = climate::CLIMATE_FAN_LOW; | ||||||
|  |       break; | ||||||
|  |     case WHIRLPOOL_FAN_AUTO: | ||||||
|  |     default: | ||||||
|  |       this->fan_mode = climate::CLIMATE_FAN_AUTO; | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Set received swing status | ||||||
|  |   if ((remote_state[2] & WHIRLPOOL_SWING_MASK) == WHIRLPOOL_SWING_MASK && remote_state[8] == 0x40) { | ||||||
|  |     ESP_LOGVV(TAG, "Swing toggle pressed "); | ||||||
|  |     if (this->swing_mode == climate::CLIMATE_SWING_OFF) { | ||||||
|  |       this->swing_mode = climate::CLIMATE_SWING_VERTICAL; | ||||||
|  |     } else { | ||||||
|  |       this->swing_mode = climate::CLIMATE_SWING_OFF; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->publish_state(); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace whirlpool | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										63
									
								
								esphome/components/whirlpool/whirlpool.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								esphome/components/whirlpool/whirlpool.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/climate_ir/climate_ir.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace whirlpool { | ||||||
|  |  | ||||||
|  | /// Simple enum to represent models. | ||||||
|  | enum Model { | ||||||
|  |   MODEL_DG11J1_3A = 0,  /// Temperature range is from 18 to 32 | ||||||
|  |   MODEL_DG11J1_91 = 1,  /// Temperature range is from 16 to 30 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Temperature | ||||||
|  | const float WHIRLPOOL_DG11J1_3A_TEMP_MAX = 32.0; | ||||||
|  | const float WHIRLPOOL_DG11J1_3A_TEMP_MIN = 18.0; | ||||||
|  | const float WHIRLPOOL_DG11J1_91_TEMP_MAX = 30.0; | ||||||
|  | const float WHIRLPOOL_DG11J1_91_TEMP_MIN = 16.0; | ||||||
|  |  | ||||||
|  | class WhirlpoolClimate : public climate_ir::ClimateIR { | ||||||
|  |  public: | ||||||
|  |   WhirlpoolClimate() | ||||||
|  |       : climate_ir::ClimateIR(temperature_min_(), temperature_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}) {} | ||||||
|  |  | ||||||
|  |   void setup() override { | ||||||
|  |     climate_ir::ClimateIR::setup(); | ||||||
|  |  | ||||||
|  |     this->powered_on_assumed_ = this->mode != climate::CLIMATE_MODE_OFF; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /// Override control to change settings of the climate device. | ||||||
|  |   void control(const climate::ClimateCall &call) override { | ||||||
|  |     send_swing_cmd_ = call.get_swing_mode().has_value(); | ||||||
|  |     climate_ir::ClimateIR::control(call); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void set_model(Model model) { this->model_ = model; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   /// Transmit via IR the state of this climate controller. | ||||||
|  |   void transmit_state() override; | ||||||
|  |   /// Handle received IR Buffer | ||||||
|  |   bool on_receive(remote_base::RemoteReceiveData data) override; | ||||||
|  |  | ||||||
|  |   // used to track when to send the power toggle command | ||||||
|  |   bool powered_on_assumed_; | ||||||
|  |  | ||||||
|  |   bool send_swing_cmd_{false}; | ||||||
|  |   Model model_; | ||||||
|  |  | ||||||
|  |   float temperature_min_() { | ||||||
|  |     return (model_ == MODEL_DG11J1_3A) ? WHIRLPOOL_DG11J1_3A_TEMP_MIN : WHIRLPOOL_DG11J1_91_TEMP_MIN; | ||||||
|  |   } | ||||||
|  |   float temperature_max_() { | ||||||
|  |     return (model_ == MODEL_DG11J1_3A) ? WHIRLPOOL_DG11J1_3A_TEMP_MAX : WHIRLPOOL_DG11J1_91_TEMP_MAX; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace whirlpool | ||||||
|  | }  // namespace esphome | ||||||
| @@ -1228,6 +1228,8 @@ climate: | |||||||
|     name: Yashima Climate |     name: Yashima Climate | ||||||
|   - platform: mitsubishi |   - platform: mitsubishi | ||||||
|     name: Mitsubishi |     name: Mitsubishi | ||||||
|  |   - platform: whirlpool | ||||||
|  |     name: Whirlpool Climate | ||||||
|  |  | ||||||
| switch: | switch: | ||||||
|   - platform: gpio |   - platform: gpio | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user