@@ -82,9 +82,12 @@ async def async_setup_entry(
82
82
83
83
new_entities = []
84
84
for miot_device in device_list :
85
- for data in miot_device .entity_list .get ('climate ' , []):
85
+ for data in miot_device .entity_list .get ('air-conditioner ' , []):
86
86
new_entities .append (
87
87
AirConditioner (miot_device = miot_device , entity_data = data ))
88
+ for data in miot_device .entity_list .get ('heater' , []):
89
+ new_entities .append (
90
+ Heater (miot_device = miot_device , entity_data = data ))
88
91
89
92
if new_entities :
90
93
async_add_entities (new_entities )
@@ -115,7 +118,7 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity):
115
118
def __init__ (
116
119
self , miot_device : MIoTDevice , entity_data : MIoTEntityData
117
120
) -> None :
118
- """Initialize the Climate ."""
121
+ """Initialize the Air conditioner ."""
119
122
super ().__init__ (miot_device = miot_device , entity_data = entity_data )
120
123
self ._attr_icon = 'mdi:air-conditioner'
121
124
self ._attr_supported_features = ClimateEntityFeature (0 )
@@ -344,31 +347,31 @@ async def async_set_fan_mode(self, fan_mode):
344
347
f'set climate prop.fan_mode failed, { fan_mode } , '
345
348
f'{ self .entity_id } ' )
346
349
347
- @ property
350
+ @property
348
351
def target_temperature (self ) -> Optional [float ]:
349
352
"""Return the target temperature."""
350
353
return self .get_prop_value (
351
354
prop = self ._prop_target_temp ) if self ._prop_target_temp else None
352
355
353
- @ property
356
+ @property
354
357
def target_humidity (self ) -> Optional [int ]:
355
358
"""Return the target humidity."""
356
359
return self .get_prop_value (
357
360
prop = self ._prop_target_humi ) if self ._prop_target_humi else None
358
361
359
- @ property
362
+ @property
360
363
def current_temperature (self ) -> Optional [float ]:
361
364
"""Return the current temperature."""
362
365
return self .get_prop_value (
363
366
prop = self ._prop_env_temp ) if self ._prop_env_temp else None
364
367
365
- @ property
368
+ @property
366
369
def current_humidity (self ) -> Optional [int ]:
367
370
"""Return the current humidity."""
368
371
return self .get_prop_value (
369
372
prop = self ._prop_env_humi ) if self ._prop_env_humi else None
370
373
371
- @ property
374
+ @property
372
375
def hvac_mode (self ) -> Optional [HVACMode ]:
373
376
"""Return the hvac mode. e.g., heat, cool mode."""
374
377
if self .get_prop_value (prop = self ._prop_on ) is False :
@@ -377,7 +380,7 @@ def hvac_mode(self) -> Optional[HVACMode]:
377
380
map_ = self ._hvac_mode_map ,
378
381
key = self .get_prop_value (prop = self ._prop_mode ))
379
382
380
- @ property
383
+ @property
381
384
def fan_mode (self ) -> Optional [str ]:
382
385
"""Return the fan mode.
383
386
@@ -387,7 +390,7 @@ def fan_mode(self) -> Optional[str]:
387
390
map_ = self ._fan_mode_map ,
388
391
key = self .get_prop_value (prop = self ._prop_fan_level ))
389
392
390
- @ property
393
+ @property
391
394
def swing_mode (self ) -> Optional [str ]:
392
395
"""Return the swing mode.
393
396
@@ -473,3 +476,144 @@ def __ac_state_changed(self, prop: MIoTSpecProperty, value: any) -> None:
473
476
self ._value_ac_state .update (v_ac_state )
474
477
_LOGGER .debug (
475
478
'ac_state update, %s' , self ._value_ac_state )
479
+
480
+
481
+ class Heater (MIoTServiceEntity , ClimateEntity ):
482
+ """Heater entities for Xiaomi Home."""
483
+ # service: heater
484
+ _prop_on : Optional [MIoTSpecProperty ]
485
+ _prop_mode : Optional [MIoTSpecProperty ]
486
+ _prop_target_temp : Optional [MIoTSpecProperty ]
487
+ _prop_heat_level : Optional [MIoTSpecProperty ]
488
+ # service: environment
489
+ _prop_env_temp : Optional [MIoTSpecProperty ]
490
+ _prop_env_humi : Optional [MIoTSpecProperty ]
491
+
492
+ _heat_level_map : Optional [dict [int , str ]]
493
+
494
+ def __init__ (
495
+ self , miot_device : MIoTDevice , entity_data : MIoTEntityData
496
+ ) -> None :
497
+ """Initialize the Heater."""
498
+ super ().__init__ (miot_device = miot_device , entity_data = entity_data )
499
+ self ._attr_icon = 'mdi:air-conditioner'
500
+ self ._attr_supported_features = ClimateEntityFeature (0 )
501
+ self ._attr_preset_modes = []
502
+
503
+ self ._prop_on = None
504
+ self ._prop_mode = None
505
+ self ._prop_target_temp = None
506
+ self ._prop_heat_level = None
507
+ self ._prop_env_temp = None
508
+ self ._prop_env_humi = None
509
+ self ._heat_level_map = None
510
+
511
+ # properties
512
+ for prop in entity_data .props :
513
+ if prop .name == 'on' :
514
+ self ._attr_supported_features |= (
515
+ ClimateEntityFeature .TURN_ON )
516
+ self ._attr_supported_features |= (
517
+ ClimateEntityFeature .TURN_OFF )
518
+ self ._prop_on = prop
519
+ elif prop .name == 'target-temperature' :
520
+ if not isinstance (prop .value_range , dict ):
521
+ _LOGGER .error (
522
+ 'invalid target-temperature value_range format, %s' ,
523
+ self .entity_id )
524
+ continue
525
+ self ._attr_min_temp = prop .value_range ['min' ]
526
+ self ._attr_max_temp = prop .value_range ['max' ]
527
+ self ._attr_target_temperature_step = prop .value_range ['step' ]
528
+ self ._attr_temperature_unit = prop .external_unit
529
+ self ._attr_supported_features |= (
530
+ ClimateEntityFeature .TARGET_TEMPERATURE )
531
+ self ._prop_target_temp = prop
532
+ elif prop .name == 'heat-level' :
533
+ if (
534
+ not isinstance (prop .value_list , list )
535
+ or not prop .value_list
536
+ ):
537
+ _LOGGER .error (
538
+ 'invalid heat-level value_list, %s' , self .entity_id )
539
+ continue
540
+ self ._heat_level_map = {
541
+ item ['value' ]: item ['description' ]
542
+ for item in prop .value_list }
543
+ self ._attr_preset_modes = list (self ._heat_level_map .values ())
544
+ self ._attr_supported_features |= (
545
+ ClimateEntityFeature .PRESET_MODE )
546
+ self ._prop_heat_level = prop
547
+ elif prop .name == 'temperature' :
548
+ self ._prop_env_temp = prop
549
+ elif prop .name == 'relative-humidity' :
550
+ self ._prop_env_humi = prop
551
+
552
+ # hvac modes
553
+ self ._attr_hvac_modes = [HVACMode .HEAT , HVACMode .OFF ]
554
+
555
+ async def async_turn_on (self ) -> None :
556
+ """Turn the entity on."""
557
+ await self .set_property_async (prop = self ._prop_on , value = True )
558
+
559
+ async def async_turn_off (self ) -> None :
560
+ """Turn the entity off."""
561
+ await self .set_property_async (prop = self ._prop_on , value = False )
562
+
563
+ async def async_set_hvac_mode (self , hvac_mode : HVACMode ) -> None :
564
+ """Set new target hvac mode."""
565
+ await self .set_property_async (
566
+ prop = self ._prop_on , value = False
567
+ if hvac_mode == HVACMode .OFF else True )
568
+
569
+ async def async_set_temperature (self , ** kwargs ):
570
+ """Set new target temperature."""
571
+ if ATTR_TEMPERATURE in kwargs :
572
+ temp = kwargs [ATTR_TEMPERATURE ]
573
+ if temp > self .max_temp :
574
+ temp = self .max_temp
575
+ elif temp < self .min_temp :
576
+ temp = self .min_temp
577
+
578
+ await self .set_property_async (
579
+ prop = self ._prop_target_temp , value = temp )
580
+
581
+ async def async_set_preset_mode (self , preset_mode : str ) -> None :
582
+ """Set the preset mode."""
583
+ await self .set_property_async (
584
+ self ._prop_heat_level ,
585
+ value = self .get_map_value (
586
+ map_ = self ._heat_level_map , description = preset_mode ))
587
+
588
+ @property
589
+ def target_temperature (self ) -> Optional [float ]:
590
+ """Return the target temperature."""
591
+ return self .get_prop_value (
592
+ prop = self ._prop_target_temp ) if self ._prop_target_temp else None
593
+
594
+ @property
595
+ def current_temperature (self ) -> Optional [float ]:
596
+ """Return the current temperature."""
597
+ return self .get_prop_value (
598
+ prop = self ._prop_env_temp ) if self ._prop_env_temp else None
599
+
600
+ @property
601
+ def current_humidity (self ) -> Optional [int ]:
602
+ """Return the current humidity."""
603
+ return self .get_prop_value (
604
+ prop = self ._prop_env_humi ) if self ._prop_env_humi else None
605
+
606
+ @property
607
+ def hvac_mode (self ) -> Optional [HVACMode ]:
608
+ """Return the hvac mode."""
609
+ return (
610
+ HVACMode .HEAT if self .get_prop_value (prop = self ._prop_on )
611
+ else HVACMode .OFF )
612
+
613
+ @property
614
+ def preset_mode (self ) -> Optional [str ]:
615
+ return (
616
+ self .get_map_description (
617
+ map_ = self ._heat_level_map ,
618
+ key = self .get_prop_value (prop = self ._prop_heat_level ))
619
+ if self ._prop_heat_level else None )
0 commit comments