Parcourir la source

Add more secondary entities to devices to expose more of their functionality.

Refactor tests for simple entities into more mixins to make adding tests easier.

There are still some timers on devices where the spec is unknown that haven't been separated out, and some of the definitions for timers that were separated out are potentially wrong.
Jason Rumney il y a 4 ans
Parent
commit
45c3a36ae6
95 fichiers modifiés avec 2557 ajouts et 578 suppressions
  1. 35 7
      custom_components/tuya_local/devices/README.md
  2. 11 0
      custom_components/tuya_local/devices/anko_fan.yaml
  3. 1 0
      custom_components/tuya_local/devices/arlec_fan.yaml
  4. 45 0
      custom_components/tuya_local/devices/awow_th213_thermostat.yaml
  5. 2 0
      custom_components/tuya_local/devices/beca_bhp6000_thermostat_c.yaml
  6. 2 0
      custom_components/tuya_local/devices/beca_bhp6000_thermostat_f.yaml
  7. 13 0
      custom_components/tuya_local/devices/beca_bht002_thermostat_c.yaml
  8. 13 0
      custom_components/tuya_local/devices/beca_bht6000_thermostat_c.yaml
  9. 13 0
      custom_components/tuya_local/devices/bwt_heatpump.yaml
  10. 1 0
      custom_components/tuya_local/devices/deta_fan.yaml
  11. 54 0
      custom_components/tuya_local/devices/eanons_humidifier.yaml
  12. 11 0
      custom_components/tuya_local/devices/eberg_qubo_q40hd_heatpump.yaml
  13. 1 0
      custom_components/tuya_local/devices/electriq_12wminv_heatpump.yaml
  14. 21 0
      custom_components/tuya_local/devices/electriq_cd12pw_dehumidifier.yaml
  15. 28 0
      custom_components/tuya_local/devices/electriq_cd20pro_dehumidifier.yaml
  16. 20 0
      custom_components/tuya_local/devices/electriq_cd25pro_dehumidifier.yaml
  17. 10 0
      custom_components/tuya_local/devices/electriq_desd9lw_dehumidifier.yaml
  18. 13 1
      custom_components/tuya_local/devices/eurom_600_heater.yaml
  19. 21 2
      custom_components/tuya_local/devices/gardenpac_heatpump.yaml
  20. 17 9
      custom_components/tuya_local/devices/goldair_dehumidifier.yaml
  21. 1 0
      custom_components/tuya_local/devices/goldair_fan.yaml
  22. 23 1
      custom_components/tuya_local/devices/goldair_geco_heater.yaml
  23. 23 0
      custom_components/tuya_local/devices/goldair_gpcv_heater.yaml
  24. 37 0
      custom_components/tuya_local/devices/goldair_gpph_heater.yaml
  25. 10 0
      custom_components/tuya_local/devices/greenwind_dehumidifier.yaml
  26. 2 0
      custom_components/tuya_local/devices/grid_connect_usb_double_power_point.yaml
  27. 140 15
      custom_components/tuya_local/devices/inkbird_itc306a_thermostat.yaml
  28. 22 0
      custom_components/tuya_local/devices/kogan_dehumidifier.yaml
  29. 11 0
      custom_components/tuya_local/devices/kogan_kahtp_heater.yaml
  30. 11 0
      custom_components/tuya_local/devices/kogan_kawfhtp_heater.yaml
  31. 13 0
      custom_components/tuya_local/devices/lexy_f501_fan.yaml
  32. 20 1
      custom_components/tuya_local/devices/madimack_heatpump.yaml
  33. 1 0
      custom_components/tuya_local/devices/moes_bht002_thermostat_c.yaml
  34. 13 0
      custom_components/tuya_local/devices/poolex_silverline_heatpump.yaml
  35. 14 1
      custom_components/tuya_local/devices/poolex_vertigo_heatpump.yaml
  36. 2 0
      custom_components/tuya_local/devices/purline_m100_heater.yaml
  37. 13 0
      custom_components/tuya_local/devices/remora_heatpump.yaml
  38. 38 0
      custom_components/tuya_local/devices/renpho_rp_ap001s.yaml
  39. 67 0
      custom_components/tuya_local/devices/saswell_c16_thermostat.yaml
  40. 29 2
      custom_components/tuya_local/devices/saswell_t29utk_thermostat.yaml
  41. 33 0
      custom_components/tuya_local/devices/smartplugv1.yaml
  42. 33 0
      custom_components/tuya_local/devices/smartplugv2.yaml
  43. 1 0
      custom_components/tuya_local/devices/stirling_fs140dc_fan.yaml
  44. 71 0
      custom_components/tuya_local/devices/wetair_wch750_heater.yaml
  45. 56 2
      custom_components/tuya_local/translations/en.json
  46. 0 204
      tests/devices/base_device_tests.py
  47. 5 2
      tests/devices/test_anko_fan.py
  48. 14 20
      tests/devices/test_arlec_fan.py
  49. 49 4
      tests/devices/test_awow_th213_thermostat.py
  50. 3 1
      tests/devices/test_beca_bhp6000_thermostat.py
  51. 20 3
      tests/devices/test_beca_bht002_thermostat.py
  52. 20 3
      tests/devices/test_beca_bht6000_thermostat.py
  53. 9 1
      tests/devices/test_bwt_heatpump.py
  54. 3 6
      tests/devices/test_deta_fan.py
  55. 48 6
      tests/devices/test_eanons_humidifier.py
  56. 3 1
      tests/devices/test_eberg_qubo_q40hd_heatpump.py
  57. 3 2
      tests/devices/test_electriq_12wminv_heatpump.py
  58. 29 3
      tests/devices/test_electriq_cd12_dehumidifier.py
  59. 47 10
      tests/devices/test_electriq_cd20_dehumidifier.py
  60. 30 8
      tests/devices/test_electriq_cd25_dehumidifier.py
  61. 17 3
      tests/devices/test_electriq_desd9lw_dehumidifier.py
  62. 9 1
      tests/devices/test_eurom_600_heater.py
  63. 23 3
      tests/devices/test_gardenpac_heatpump.py
  64. 45 45
      tests/devices/test_goldair_dehumidifier.py
  65. 3 2
      tests/devices/test_goldair_fan.py
  66. 15 3
      tests/devices/test_goldair_geco_heater.py
  67. 15 3
      tests/devices/test_goldair_gpcv_heater.py
  68. 32 30
      tests/devices/test_goldair_gpph_heater.py
  69. 39 113
      tests/devices/test_grid_connect_double_power_point.py
  70. 96 11
      tests/devices/test_inkbird_itc306a_thermostat.py
  71. 25 3
      tests/devices/test_kogan_dehumidifier.py
  72. 5 3
      tests/devices/test_kogan_kahtp_heater.py
  73. 5 3
      tests/devices/test_kogan_kawfhtp_heater.py
  74. 7 7
      tests/devices/test_lexy_f501_fan.py
  75. 22 3
      tests/devices/test_madimack_heatpump.py
  76. 2 1
      tests/devices/test_moes_bht002_thermostat.py
  77. 9 1
      tests/devices/test_poolex_silverline_heatpump.py
  78. 9 1
      tests/devices/test_poolex_vertigo_heatpump.py
  79. 2 1
      tests/devices/test_purline_m100_heater.py
  80. 9 1
      tests/devices/test_remora_heatpump.py
  81. 32 10
      tests/devices/test_renpho_rp_ap001s.py
  82. 61 4
      tests/devices/test_saswell_c16_thermostat.py
  83. 24 2
      tests/devices/test_saswell_t29utk_thermostat.py
  84. 28 3
      tests/devices/test_smartplugv1.py
  85. 28 3
      tests/devices/test_smartplugv2.py
  86. 2 1
      tests/devices/test_stirling_fs140dc_fan.py
  87. 38 2
      tests/devices/test_wetair_wch750_heater.py
  88. 65 0
      tests/mixins/binary_sensor.py
  89. 66 0
      tests/mixins/light.py
  90. 48 0
      tests/mixins/lock.py
  91. 115 0
      tests/mixins/number.py
  92. 74 0
      tests/mixins/select.py
  93. 85 0
      tests/mixins/sensor.py
  94. 198 0
      tests/mixins/switch.py
  95. 5 0
      tests/test_config_flow.py

+ 35 - 7
custom_components/tuya_local/devices/README.md

@@ -99,8 +99,19 @@ The value of this should indicated what to use instead.
 
 
 //Optional.//
 //Optional.//
 
 
-For some entity types, a `class` can be set, for example `switch` entities
-can have a class of `outlet`.  This may slightly alter the UI behaviour.
+For some entity types, a device `class` can be set, for example `switch`
+entities can have a class of `outlet`.  This may slightly alter the UI
+behaviour. 
+For most entities, it will alter the default icon, and for binary sensors
+also the state that off and on values translate to in the UI.
+
+### `category`
+
+//Optional.//
+
+This specifies the `entity category` of the entity.  Entities can be categorized
+as `config` or `diagnostic` to restrict where they appear automatically in
+Home Assistant.
 
 
 ### `dps`
 ### `dps`
 
 
@@ -119,7 +130,15 @@ it will inherit the name at the top level. This is mostly useful for
 overriding the name of secondary entities to give more information
 overriding the name of secondary entities to give more information
 about the purpose of the entity, as the generic type with the top level
 about the purpose of the entity, as the generic type with the top level
 name may not be sufficient to describe the function.
 name may not be sufficient to describe the function.
- 
+
+### `mode`
+
+//Optional.  For number entities, default="auto", for others, None
+
+For number entities, this can be used to force `slider` or `box` as the
+input method.  The default `auto` uses a slider if the range is small enough,
+or a box otherwise.
+
 ## DPS configuration
 ## DPS configuration
  
  
 ### `id`
 ### `id`
@@ -190,12 +209,21 @@ Home Assistant UI.  This can also be set in a `mapping` or `conditions` block.
 
 
 ### `unit`
 ### `unit`
 
 
-//Optional. default="C" for temperature dps//
+//Optional. default="C" for temperature dps on climate devices, None for sensors.//
 
 
 For temperature dps, some devices will use Fahrenhiet.  This needs to be
 For temperature dps, some devices will use Fahrenhiet.  This needs to be
-indicated back to HomeAssistant by defining `unit` as "F".  In future `unit`
-will also be used for other sensor types, with a bigger range of possible
-values.
+indicated back to HomeAssistant by defining `unit` as "F".  For sensor 
+entities, see the HomeAssistant developer documentation for the full list
+of possible units (C and F are automatically translated to their Unicode 
+equivalents, other units are currently ASCII so can be easily entered directly).
+
+### `class`
+
+//Optional.  default=None.//
+
+For sensors, this sets the state class of the sensor (measurement, total
+or total_increasing)
+
 
 
 ## Mapping Rules
 ## Mapping Rules
 
 

+ 11 - 0
custom_components/tuya_local/devices/anko_fan.yaml

@@ -34,3 +34,14 @@ primary_entity:
     - id: 6
     - id: 6
       type: integer
       type: integer
       name: timer
       name: timer
+secondary_entities:
+  - entity: number
+    name: Timer
+    category: config
+    dps:
+      - id: 6
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 9

+ 1 - 0
custom_components/tuya_local/devices/arlec_fan.yaml

@@ -32,6 +32,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: select
   - entity: select
     name: timer
     name: timer
+    category: config
     dps:
     dps:
       - id: 103
       - id: 103
         name: option
         name: option

+ 45 - 0
custom_components/tuya_local/devices/awow_th213_thermostat.yaml

@@ -92,6 +92,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: lock
   - entity: lock
     name: Child Lock
     name: Child Lock
+    category: config
     dps:
     dps:
       - id: 6
       - id: 6
         type: boolean
         type: boolean
@@ -101,3 +102,47 @@ secondary_entities:
             icon: "mdi:hand-back-right-off"
             icon: "mdi:hand-back-right-off"
           - dps_val: false
           - dps_val: false
             icon: "mdi:hand-back-right"
             icon: "mdi:hand-back-right"
+  - entity: sensor
+    name: External Temperature
+    class: temperature
+    dps:
+      - id: 101
+        type: integer
+        name: sensor
+        class: measurement
+        unit: C
+        readonly: true
+  - entity: select
+    name: Temperature Sensor
+    category: config
+    dps:
+      - id: 102
+        type: integer
+        name: option
+        mapping:
+          - dps_val: 0
+            value: Internal
+          - dps_val: 1
+            value: External
+          - dps_val: 2
+            value: Both
+  - entity: number
+    name: Calibration Offset
+    category: config
+    dps:
+      - id: 103
+        type: integer
+        name: value
+        range:
+          min: -9
+          max: 9
+  - entity: number
+    name: Calibration Swing
+    category: config
+    dps:
+      - id: 104
+        type: integer
+        name: value
+        range:
+          min: 1
+          max: 9

+ 2 - 0
custom_components/tuya_local/devices/beca_bhp6000_thermostat_c.yaml

@@ -56,6 +56,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: lock
   - entity: lock
     name: Child Lock
     name: Child Lock
+    category: config
     dps:
     dps:
       - id: 7
       - id: 7
         name: lock
         name: lock
@@ -67,6 +68,7 @@ secondary_entities:
             icon: "mdi:hand-back-right"
             icon: "mdi:hand-back-right"
   - entity: light
   - entity: light
     name: Display
     name: Display
+    category: config
     dps:
     dps:
       - id: 1
       - id: 1
         name: switch
         name: switch

+ 2 - 0
custom_components/tuya_local/devices/beca_bhp6000_thermostat_f.yaml

@@ -57,6 +57,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: lock
   - entity: lock
     name: Child Lock
     name: Child Lock
+    category: config
     dps:
     dps:
       - id: 7
       - id: 7
         name: lock
         name: lock
@@ -68,6 +69,7 @@ secondary_entities:
             icon: "mdi:hand-back-right"
             icon: "mdi:hand-back-right"
   - entity: light
   - entity: light
     name: Display
     name: Display
+    category: config
     dps:
     dps:
       - id: 1
       - id: 1
         name: switch
         name: switch

+ 13 - 0
custom_components/tuya_local/devices/beca_bht002_thermostat_c.yaml

@@ -60,6 +60,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: light
   - entity: light
     name: Display
     name: Display
+    category: config
     deprecated: climate hvac_mode
     deprecated: climate hvac_mode
     dps:
     dps:
       - id: 1
       - id: 1
@@ -72,6 +73,7 @@ secondary_entities:
             icon: "mdi:led-off"
             icon: "mdi:led-off"
   - entity: lock
   - entity: lock
     name: Child Lock
     name: Child Lock
+    category: config
     dps:
     dps:
       - id: 6
       - id: 6
         type: boolean
         type: boolean
@@ -81,3 +83,14 @@ secondary_entities:
             icon: "mdi:hand-back-right-off"
             icon: "mdi:hand-back-right-off"
           - dps_val: false
           - dps_val: false
             icon: "mdi:hand-back-right"
             icon: "mdi:hand-back-right"
+  - entity: sensor
+    name: External Temperature
+    class: temperature
+    dps:
+      - id: 102
+        type: integer
+        name: sensor
+        unit: C
+        class: measurement
+        mapping:
+          - scale: 2

+ 13 - 0
custom_components/tuya_local/devices/beca_bht6000_thermostat_c.yaml

@@ -64,6 +64,7 @@ secondary_entities:
   - entity: light
   - entity: light
     deprecated: climate hvac_mode
     deprecated: climate hvac_mode
     name: Display
     name: Display
+    category: config
     dps:
     dps:
       - id: 1
       - id: 1
         type: boolean
         type: boolean
@@ -75,6 +76,7 @@ secondary_entities:
             icon: "mdi:led-off"
             icon: "mdi:led-off"
   - entity: lock
   - entity: lock
     name: Child Lock
     name: Child Lock
+    category: config
     dps:
     dps:
       - id: 6
       - id: 6
         type: boolean
         type: boolean
@@ -84,3 +86,14 @@ secondary_entities:
             icon: "mdi:hand-back-right-off"
             icon: "mdi:hand-back-right-off"
           - dps_val: false
           - dps_val: false
             icon: "mdi:hand-back-right"
             icon: "mdi:hand-back-right"
+  - entity: sensor
+    name: External Temperature
+    class: temperature
+    dps:
+      - id: 102
+        type: integer
+        name: sensor
+        unit: C
+        class: measurement
+        mapping:
+          - scale: 2

+ 13 - 0
custom_components/tuya_local/devices/bwt_heatpump.yaml

@@ -51,3 +51,16 @@ primary_entity:
           value: "Water Flow Protection"
           value: "Water Flow Protection"
           icon: "mdi:water-pump-off"
           icon: "mdi:water-pump-off"
           icon_priority: 2
           icon_priority: 2
+secondary_entities:
+  - entity: binary_sensor
+    class: problem
+    name: Water Flow
+    category: diagnostic
+    dps:
+      - id: 9
+        type: bitfield
+        name: sensor
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true

+ 1 - 0
custom_components/tuya_local/devices/deta_fan.yaml

@@ -27,6 +27,7 @@ secondary_entities:
         name: timer
         name: timer
   - entity: switch
   - entity: switch
     name: Master
     name: Master
+    category: config
     dps:
     dps:
       - id: 101
       - id: 101
         type: boolean
         type: boolean

+ 54 - 0
custom_components/tuya_local/devices/eanons_humidifier.yaml

@@ -123,3 +123,57 @@ secondary_entities:
       - id: 22
       - id: 22
         name: switch
         name: switch
         type: boolean
         type: boolean
+  - entity: select
+    name: Timer
+    category: config
+    dps:
+      - id: 3
+        name: option
+        type: string
+        mapping:
+          - dps_val: "cancel"
+            value: "Off"
+          - dps_val: "1"
+            value: "1 hour"
+          - dps_val: "2"
+            value: "2 hours"
+          - dps_val: "3"
+            value: "3 hours"
+          - dps_val: "4"
+            value: "4 hours"
+          - dps_val: "5"
+            value: "5 hours"
+          - dps_val: "6"
+            value: "6 hours"
+          - dps_val: "7"
+            value: "7 hours"
+          - dps_val: "8"
+            value: "8 hours"
+          - dps_val: "9"
+            value: "9 hours"
+          - dps_val: "10"
+            value: "10 hours"
+          - dps_val: "11"
+            value: "11 hours"
+          - dps_val: "12"
+            value: "12 hours"
+  - entity: sensor
+    name: Timer
+    category: diagnostic
+    dps:
+      - id: 4
+        name: sensor
+        type: integer
+        unit: min
+  - entity: binary_sensor
+    name: Tank
+    class: problem
+    category: diagnostic
+    dps:
+      - id: 9
+        name: sensor
+        type: bitfield
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true

+ 11 - 0
custom_components/tuya_local/devices/eberg_qubo_q40hd_heatpump.yaml

@@ -123,3 +123,14 @@ primary_entity:
               value: "off"
               value: "off"
             - dps_val: True
             - dps_val: True
               value: idle
               value: idle
+secondary_entities:
+  - entity: number
+    name: Timer
+    category: config
+    dps:
+      - id: 22
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 24

+ 1 - 0
custom_components/tuya_local/devices/electriq_12wminv_heatpump.yaml

@@ -157,6 +157,7 @@ secondary_entities:
         type: boolean
         type: boolean
   - entity: light
   - entity: light
     name: Display
     name: Display
+    category: config
     dps:
     dps:
       - id: 104
       - id: 104
         name: switch
         name: switch

+ 21 - 0
custom_components/tuya_local/devices/electriq_cd12pw_dehumidifier.yaml

@@ -42,6 +42,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: light
   - entity: light
     name: Display
     name: Display
+    category: config
     dps:
     dps:
       - id: 101
       - id: 101
         type: boolean
         type: boolean
@@ -51,3 +52,23 @@ secondary_entities:
             icon: "mdi:led-on"
             icon: "mdi:led-on"
           - dps_val: false
           - dps_val: false
             icon: "mdi:led-off"
             icon: "mdi:led-off"
+  - entity: sensor
+    class: humidity
+    name: Current Humidity
+    dps:
+      - id: 3
+        type: integer
+        name: sensor
+        unit: "%"
+        class: measurement
+        readonly: true
+  - entity: sensor
+    class: temperature
+    name: Current Temperature
+    dps:
+      - id: 103
+        type: integer
+        name: sensor
+        unit: C
+        class: measurement
+        readonly: true

+ 28 - 0
custom_components/tuya_local/devices/electriq_cd20pro_dehumidifier.yaml

@@ -73,6 +73,7 @@ secondary_entities:
         type: boolean
         type: boolean
   - entity: light
   - entity: light
     name: Display
     name: Display
+    category: config
     dps:
     dps:
       - id: 101
       - id: 101
         type: boolean
         type: boolean
@@ -82,3 +83,30 @@ secondary_entities:
             icon: "mdi:led-on"
             icon: "mdi:led-on"
           - dps_val: false
           - dps_val: false
             icon: "mdi:led-off"
             icon: "mdi:led-off"
+  - entity: switch
+    name: Ionizer
+    icon: mdi:creation
+    dps:
+      - id: 5
+        name: switch
+        type: boolean
+  - entity: sensor
+    class: humidity
+    name: Current Humidity
+    dps:
+      - id: 3
+        type: integer
+        name: sensor
+        unit: "%"
+        class: measurement
+        readonly: true
+  - entity: sensor
+    class: temperature
+    name: Current Temperature
+    dps:
+      - id: 103
+        type: integer
+        name: sensor
+        unit: C
+        class: measurement
+        readonly: true

+ 20 - 0
custom_components/tuya_local/devices/electriq_cd25pro_dehumidifier.yaml

@@ -87,3 +87,23 @@ secondary_entities:
             icon: "mdi:hand-back-right-off"
             icon: "mdi:hand-back-right-off"
           - dps_val: false
           - dps_val: false
             icon: "mdi:hand-back-right"
             icon: "mdi:hand-back-right"
+  - entity: sensor
+    class: humidity
+    name: Current Humidity
+    dps:
+      - id: 3
+        type: integer
+        name: sensor
+        unit: "%"
+        class: measurement
+        readonly: true
+  - entity: sensor
+    class: temperature
+    name: Current Temperature
+    dps:
+      - id: 103
+        type: integer
+        name: sensor
+        unit: C
+        class: measurement
+        readonly: true

+ 10 - 0
custom_components/tuya_local/devices/electriq_desd9lw_dehumidifier.yaml

@@ -119,3 +119,13 @@ secondary_entities:
       - id: 12
       - id: 12
         name: switch
         name: switch
         type: boolean
         type: boolean
+  - entity: sensor
+    name: Current Humidity
+    class: humidity
+    dps:
+      - id: 6
+        type: integer
+        name: sensor
+        class: measurement
+        unit: "%"
+        readonly: true

+ 13 - 1
custom_components/tuya_local/devices/eurom_600_heater.yaml

@@ -32,4 +32,16 @@ primary_entity:
           value: "OK"
           value: "OK"
       readonly: true
       readonly: true
       name: error
       name: error
-      
+secondary_entities:
+  - entity: binary_sensor
+    name: Error
+    category: diagnostic
+    class: problem
+    dps:
+      - id: 6
+        type: bitfield
+        name: sensor
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true

+ 21 - 2
custom_components/tuya_local/devices/gardenpac_heatpump.yaml

@@ -31,9 +31,17 @@ primary_entity:
       type: integer
       type: integer
       readonly: true
       readonly: true
     - id: 105
     - id: 105
-      name: operating_mode
+      name: hvac_action
       type: string
       type: string
       readonly: true
       readonly: true
+      mapping:
+        - dps_val: warm
+          constraint: hvac_mode
+          conditions:
+            - dps_val: false
+              value: "off"
+            - dps_val: true
+              value: idle
     - id: 106
     - id: 106
       name: temperature
       name: temperature
       type: integer
       type: integer
@@ -67,4 +75,15 @@ primary_entity:
           value: Silent
           value: Silent
         - dps_val: true
         - dps_val: true
           value: Smart
           value: Smart
-        
+secondary_entities:
+  - entity: sensor
+    name: Power Level
+    class: power_factor
+    category: diagnostic
+    dps:
+      - id: 104
+        name: sensor
+        type: integer
+        unit: "%"
+        class: measurement
+        readonly: true

+ 17 - 9
custom_components/tuya_local/devices/goldair_dehumidifier.yaml

@@ -55,9 +55,6 @@ primary_entity:
         - dps_val: 0
         - dps_val: 0
           value: "OK"
           value: "OK"
       readonly: true
       readonly: true
-    - id: 12
-      type: string
-      name: unknown_12
     - id: 101
     - id: 101
       type: boolean
       type: boolean
       name: unknown_101
       name: unknown_101
@@ -199,9 +196,6 @@ secondary_entities:
           - dps_val: 0
           - dps_val: 0
             value: "OK"
             value: "OK"
         readonly: true
         readonly: true
-      - id: 12
-        type: string
-        name: unknown_12
       - id: 101
       - id: 101
         type: boolean
         type: boolean
         name: unknown_101
         name: unknown_101
@@ -224,6 +218,7 @@ secondary_entities:
             readonly: true
             readonly: true
   - entity: light
   - entity: light
     name: Display
     name: Display
+    category: config
     dps:
     dps:
       - id: 102
       - id: 102
         type: boolean
         type: boolean
@@ -237,6 +232,7 @@ secondary_entities:
             icon: "mdi:led-off"
             icon: "mdi:led-off"
   - entity: lock
   - entity: lock
     name: Child Lock
     name: Child Lock
+    category: config
     dps:
     dps:
       - id: 7
       - id: 7
         type: boolean
         type: boolean
@@ -276,18 +272,30 @@ secondary_entities:
   - entity: binary_sensor
   - entity: binary_sensor
     name: Tank
     name: Tank
     class: problem
     class: problem
+    category: diagnostic
     dps:
     dps:
       - id: 11
       - id: 11
         type: bitfield
         type: bitfield
         name: sensor
         name: sensor
         mapping:
         mapping:
-          - dps_val: 8
-            value: true
-          - value: false
+          - dps_val: 0
+            value: false
+          - value: true
   - entity: binary_sensor
   - entity: binary_sensor
     name: defrost
     name: defrost
     class: cold
     class: cold
+    category: diagnostic
     dps:
     dps:
       - id: 105
       - id: 105
         type: boolean
         type: boolean
         name: sensor
         name: sensor
+  - entity: number
+    name: timer
+    category: config
+    dps:
+      - id: 12
+        name: value
+        type: integer
+        range:
+          min: 0
+          max: 24

+ 1 - 0
custom_components/tuya_local/devices/goldair_fan.yaml

@@ -122,6 +122,7 @@ secondary_entities:
         name: timer
         name: timer
   - entity: light
   - entity: light
     name: Display
     name: Display
+    category: config
     dps:
     dps:
       - id: 101
       - id: 101
         type: boolean
         type: boolean

+ 23 - 1
custom_components/tuya_local/devices/goldair_geco_heater.yaml

@@ -36,6 +36,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: lock
   - entity: lock
     name: "Child Lock"
     name: "Child Lock"
+    category: config
     dps:
     dps:
       - id: 2
       - id: 2
         type: boolean
         type: boolean
@@ -45,4 +46,25 @@ secondary_entities:
             icon: "mdi:hand-back-right-off"
             icon: "mdi:hand-back-right-off"
           - dps_val: false
           - dps_val: false
             icon: "mdi:hand-back-right"
             icon: "mdi:hand-back-right"
-
+  - entity: number
+    name: Timer
+    category: config
+    dps:
+      - id: 5
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 24
+  - entity: binary_sensor
+    name: Error
+    class: problem
+    category: diagnostic
+    dps:
+      - id: 6
+        name: sensor
+        type: bitfield
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true

+ 23 - 0
custom_components/tuya_local/devices/goldair_gpcv_heater.yaml

@@ -44,6 +44,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: lock
   - entity: lock
     name: "Child Lock"
     name: "Child Lock"
+    category: config
     dps:
     dps:
       - id: 2
       - id: 2
         type: boolean
         type: boolean
@@ -53,3 +54,25 @@ secondary_entities:
             icon: "mdi:hand-back-right-off"
             icon: "mdi:hand-back-right-off"
           - dps_val: false
           - dps_val: false
             icon: "mdi:hand-back-right"
             icon: "mdi:hand-back-right"
+  - entity: number
+    name: Timer
+    category: config
+    dps:
+      - id: 5
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 24
+  - entity: binary_sensor
+    name: Error
+    class: problem
+    category: diagnostic
+    dps:
+      - id: 6
+        name: sensor
+        type: bitfield
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true

+ 37 - 0
custom_components/tuya_local/devices/goldair_gpph_heater.yaml

@@ -121,6 +121,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: light
   - entity: light
     name: Display
     name: Display
+    category: config
     dps:
     dps:
       - id: 104
       - id: 104
         type: boolean
         type: boolean
@@ -132,6 +133,7 @@ secondary_entities:
         name: switch
         name: switch
   - entity: lock
   - entity: lock
     name: Child Lock
     name: Child Lock
+    category: config
     dps:
     dps:
       - id: 6
       - id: 6
         type: boolean
         type: boolean
@@ -143,6 +145,7 @@ secondary_entities:
             icon: "mdi:hand-back-right"
             icon: "mdi:hand-back-right"
   - entity: number
   - entity: number
     name: Timer
     name: Timer
+    category: config
     dps:
     dps:
       - id: 102
       - id: 102
         type: integer
         type: integer
@@ -152,3 +155,37 @@ secondary_entities:
           max: 1440
           max: 1440
         mapping:
         mapping:
           - step: 60
           - step: 60
+  - entity: sensor
+    name: Power Level
+    class: power_factor
+    category: diagnostic
+    dps:
+      - id: 101
+        type: string
+        name: sensor
+        unit: "%"
+        mapping:
+          - dps_val: "stop"
+            value: 0
+          - dps_val: "1"
+            value: 20
+          - dps_val: "2"
+            value: 40
+          - dps_val: "3"
+            value: 60
+          - dps_val: "4"
+            value: 80
+          - dps_val: "5"
+            value: 100
+  - entity: binary_sensor
+    name: Error
+    class: problem
+    category: diagnostic
+    dps:
+      - id: 12
+        name: sensor
+        type: bitfield
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true

+ 10 - 0
custom_components/tuya_local/devices/greenwind_dehumidifier.yaml

@@ -30,4 +30,14 @@ primary_entity:
     - id: 18
     - id: 18
       name: current_humidity
       name: current_humidity
       type: integer
       type: integer
+secondary_entities:
+  - entity: sensor
+    name: Current Humidity
+    class: humidity
+    dps:
+      - id: 18
+        type: integer
+        name: sensor
+        unit: "%"
+        class: measurement
 
 

+ 2 - 0
custom_components/tuya_local/devices/grid_connect_usb_double_power_point.yaml

@@ -7,6 +7,7 @@
 name: Grid Connnect power metered double outlet with USB
 name: Grid Connnect power metered double outlet with USB
 primary_entity:
 primary_entity:
   entity: switch
   entity: switch
+  category: config
   name: Master
   name: Master
   class: outlet
   class: outlet
   dps:
   dps:
@@ -62,6 +63,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: lock
   - entity: lock
     name: Child Lock
     name: Child Lock
+    category: config
     dps:
     dps:
       - id: 40
       - id: 40
         name: lock
         name: lock

+ 140 - 15
custom_components/tuya_local/devices/inkbird_itc306a_thermostat.yaml

@@ -35,18 +35,17 @@ primary_entity:
     - id: 106
     - id: 106
       type: integer
       type: integer
       name: target_temp_low
       name: target_temp_low
+      range:
+        min: 0
+        max: 450
       mapping:
       mapping:
         - scale: 10
         - scale: 10
           constraint: temperature_unit
           constraint: temperature_unit
           conditions:
           conditions:
-            - dps_val: "C"
+            - dps_val: F
               range:
               range:
-                min: 200
-                max: 350
-            - dps_val: "F"
-              range:
-                min: 680
-                max: 950
+                min: 320
+                max: 1130
     - id: 108
     - id: 108
       type: integer
       type: integer
       name: heat_time_alarm_threshold_hours
       name: heat_time_alarm_threshold_hours
@@ -84,28 +83,29 @@ primary_entity:
     - id: 114
     - id: 114
       type: integer
       type: integer
       name: target_temp_high
       name: target_temp_high
+      range:
+        min: 0
+        max: 450
       mapping:
       mapping:
         - scale: 10
         - scale: 10
           constraint: temperature_unit
           constraint: temperature_unit
           conditions:
           conditions:
-            - dps_val: "C"
-              range:
-                min: 200
-                max: 350
-            - dps_val: "F"
+            - dps_val: F
               range:
               range:
-                min: 680
-                max: 950
+                min: 320
+                max: 1130
     - id: 115
     - id: 115
       type: boolean
       type: boolean
-      name: switch_state
+      name: hvac_action
       mapping:
       mapping:
         - dps_val: true
         - dps_val: true
           icon: "mdi:thermometer"
           icon: "mdi:thermometer"
           icon_priority: 5
           icon_priority: 5
+          value: heating
         - dps_val: false
         - dps_val: false
           icon: "mdi:thermometer-off"
           icon: "mdi:thermometer-off"
           icon_priority: 4
           icon_priority: 4
+          value: idle
     - id: 116
     - id: 116
       type: integer
       type: integer
       name: current_temperature_f
       name: current_temperature_f
@@ -123,3 +123,128 @@ primary_entity:
     - id: 120
     - id: 120
       type: boolean
       type: boolean
       name: unknown_120
       name: unknown_120
+secondary_entities:
+  - entity: number
+    category: config
+    name: Calibration Offset
+    dps:
+      - id: 102
+        name: value
+        type: integer
+        range:
+          min: -99
+          max: 99
+        mapping:
+          - scale: 10
+            constraint: unit
+            conditions:
+              - dps_val: F
+                range:
+                  min: -150
+                  max: 150
+      - id: 101
+        name: unit
+        type: string
+        hidden: true
+  - entity: number
+    name: Continuous Heat Hours
+    category: config
+    dps:
+      - id: 108
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 96
+  - entity: number
+    name: High Temperature Limit
+    category: config
+    dps:
+      - id: 109
+        name: value
+        type: integer
+        range:
+          min: -400
+          max: 1000
+        mapping:
+          - scale: 10
+            constraint: unit
+            conditions:
+              - dps_val: F
+                range:
+                  min: -400
+                  max: 2120
+      - id: 101
+        name: unit
+        type: string
+        hidden: true
+  - entity: number
+    name: Low Temperature Limit
+    category: config
+    dps:
+      - id: 110
+        name: value
+        type: integer
+        range:
+          min: -400
+          max: 1000
+        mapping:
+          - scale: 10
+            constraint: unit
+            conditions:
+              - dps_val: F
+                range:
+                  min: -400
+                  max: 2120
+      - id: 101
+        name: unit
+        type: string
+        hidden: true
+  - entity: select
+    category: config
+    name: Temperature Unit
+    dps:
+      - id: 101
+        name: option
+        type: string
+        mapping:
+          - dps_val: C
+            value: Celsius
+          - dps_val: F
+            value: Fahrenheit
+  - entity: binary_sensor
+    class: heat
+    category: diagnostic
+    name: High Temperature
+    dps:
+      - id: 111
+        type: boolean
+        name: sensor
+  - entity: binary_sensor
+    class: cold
+    category: diagnostic
+    name: Low Temperature
+    dps:
+      - id: 112
+        type: boolean
+        name: sensor
+  - entity: binary_sensor
+    class: problem
+    category: diagnostic
+    name: Continuous Heat
+    dps:
+      - id: 113
+        type: boolean
+        name: sensor
+  - entity: binary_sensor
+    name: Error
+    class: problem
+    category: diagnostic
+    dps:
+      - id: 12
+        name: sensor
+        type: bitfield
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true

+ 22 - 0
custom_components/tuya_local/devices/kogan_dehumidifier.yaml

@@ -76,3 +76,25 @@ secondary_entities:
       - id: 8
       - id: 8
         name: oscillate
         name: oscillate
         type: boolean
         type: boolean
+  - entity: sensor
+    class: humidity
+    name: Current Humidity
+    dps:
+      - id: 3
+        type: integer
+        name: sensor
+        class: measurement
+        unit: "%"
+  - entity: binary_sensor
+    class: problem
+    name: Tank
+    dps:
+      - id: 11
+        name: sensor
+        type: integer
+        readonly: true
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true
+     

+ 11 - 0
custom_components/tuya_local/devices/kogan_kahtp_heater.yaml

@@ -37,6 +37,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: lock
   - entity: lock
     name: "Child Lock"
     name: "Child Lock"
+    category: config
     dps:
     dps:
       - id: 6
       - id: 6
         type: boolean
         type: boolean
@@ -46,3 +47,13 @@ secondary_entities:
             icon: "mdi:hand-back-right-off"
             icon: "mdi:hand-back-right-off"
           - dps_val: false
           - dps_val: false
             icon: "mdi:hand-back-right"
             icon: "mdi:hand-back-right"
+  - entity: number
+    name: Timer
+    category: config
+    dps:
+      - id: 8
+        name: value
+        type: integer
+        range:
+          min: 0
+          max: 24

+ 11 - 0
custom_components/tuya_local/devices/kogan_kawfhtp_heater.yaml

@@ -36,6 +36,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: lock
   - entity: lock
     name: "Child Lock"
     name: "Child Lock"
+    category: config
     dps:
     dps:
       - id: 2
       - id: 2
         type: boolean
         type: boolean
@@ -45,3 +46,13 @@ secondary_entities:
             icon: "mdi:hand-back-right-off"
             icon: "mdi:hand-back-right-off"
           - dps_val: false
           - dps_val: false
             icon: "mdi:hand-back-right"
             icon: "mdi:hand-back-right"
+  - entity: number
+    name: Timer
+    category: config
+    dps:
+      - id: 5
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 24

+ 13 - 0
custom_components/tuya_local/devices/lexy_f501_fan.yaml

@@ -48,6 +48,7 @@ primary_entity:
         - scale: 0.15
         - scale: 0.15
 secondary_entities:
 secondary_entities:
   - entity: light
   - entity: light
+    category: config
     dps:
     dps:
       - id: 9
       - id: 9
         name: switch
         name: switch
@@ -59,6 +60,7 @@ secondary_entities:
             icon: "mdi:led-off"
             icon: "mdi:led-off"
   - entity: lock
   - entity: lock
     name: Child Lock
     name: Child Lock
+    category: config
     dps:
     dps:
       - id: 16
       - id: 16
         name: lock
         name: lock
@@ -70,6 +72,7 @@ secondary_entities:
             icon: "mdi:hand-back-right"
             icon: "mdi:hand-back-right"
   - entity: switch
   - entity: switch
     name: Sound
     name: Sound
+    category: config
     dps:
     dps:
       - id: 17
       - id: 17
         name: switch
         name: switch
@@ -79,3 +82,13 @@ secondary_entities:
             icon: "mdi:volume-high"
             icon: "mdi:volume-high"
           - dps_val: false
           - dps_val: false
             icon: "mdi:volume-mute"
             icon: "mdi:volume-mute"
+  - entity: number
+    name: Timer
+    category: config
+    dps:
+      - id: 6
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 7

+ 20 - 1
custom_components/tuya_local/devices/madimack_heatpump.yaml

@@ -31,9 +31,17 @@ primary_entity:
       type: integer
       type: integer
       readonly: true
       readonly: true
     - id: 105
     - id: 105
-      name: operating_mode
+      name: hvac_action
       type: string
       type: string
       readonly: true
       readonly: true
+      mapping:
+        - dps_val: "warm"
+          constraint: hvac_mode
+          conditions:
+            - dps_val: false
+              value: "off"
+            - dps_val: true
+              value: idle
     - id: 106
     - id: 106
       name: temperature
       name: temperature
       type: integer
       type: integer
@@ -112,3 +120,14 @@ primary_entity:
     - id: 140
     - id: 140
       name: unknown_140
       name: unknown_140
       type: string
       type: string
+secondary_entities:
+  - entity: sensor
+    category: disagnostic
+    name: Power Level
+    class: power_factor
+    dps:
+      - id: 104
+        type: integer
+        name: sensor
+        unit: "%"
+        readonly: true

+ 1 - 0
custom_components/tuya_local/devices/moes_bht002_thermostat_c.yaml

@@ -55,6 +55,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: lock
   - entity: lock
     name: Child Lock
     name: Child Lock
+    category: config
     dps:
     dps:
       - id: 6
       - id: 6
         type: boolean
         type: boolean

+ 13 - 0
custom_components/tuya_local/devices/poolex_silverline_heatpump.yaml

@@ -45,3 +45,16 @@ primary_entity:
           value: "Water Flow Protection"
           value: "Water Flow Protection"
           icon: "mdi:water-pump-off"
           icon: "mdi:water-pump-off"
           icon_priority: 2
           icon_priority: 2
+secondary_entities:
+  - entity: binary_sensor
+    name: Water Flow
+    class: problem
+    category: diagnostic
+    dps:
+      - id: 13
+        type: bitfield
+        name: sensor
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true

+ 14 - 1
custom_components/tuya_local/devices/poolex_vertigo_heatpump.yaml

@@ -42,4 +42,17 @@ primary_entity:
         - dps_val: 4
         - dps_val: 4
           value: "Water Flow Protection"
           value: "Water Flow Protection"
           icon: "mdi:water-pump-off"
           icon: "mdi:water-pump-off"
-          icon_priority: 2
+          icon_priority: 2
+secondary_entities:
+  - entity: binary_sensor
+    name: Water Flow
+    class: problem
+    category: diagnostic
+    dps:
+      - id: 9
+        type: integer
+        name: sensor
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true

+ 2 - 0
custom_components/tuya_local/devices/purline_m100_heater.yaml

@@ -66,6 +66,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: light
   - entity: light
     name: Display
     name: Display
+    category: config
     dps:
     dps:
       - id: 10
       - id: 10
         type: boolean
         type: boolean
@@ -79,6 +80,7 @@ secondary_entities:
         name: switch
         name: switch
   - entity: switch
   - entity: switch
     name: "Open Window Detector"
     name: "Open Window Detector"
+    category: config
     class: switch
     class: switch
     dps:
     dps:
       - id: 101
       - id: 101

+ 13 - 0
custom_components/tuya_local/devices/remora_heatpump.yaml

@@ -49,3 +49,16 @@ primary_entity:
           value: "Water Flow Protection"
           value: "Water Flow Protection"
           icon: "mdi:water-pump-off"
           icon: "mdi:water-pump-off"
           icon_priority: 2
           icon_priority: 2
+secondary_entities:
+  - entity: binary_sensor
+    name: Water Flow
+    class: problem
+    category: diagnostic
+    dps:
+      - id: 9
+        name: sensor
+        type: integer
+        mapping:
+          - dps_val: 0
+            value: false
+          - value: true

+ 38 - 0
custom_components/tuya_local/devices/renpho_rp_ap001s.yaml

@@ -41,6 +41,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: lock
   - entity: lock
     name: Child Lock
     name: Child Lock
+    category: config
     dps:
     dps:
       - id: 7
       - id: 7
         name: lock
         name: lock
@@ -52,6 +53,7 @@ secondary_entities:
             icon: "mdi:hand-back-right-off"
             icon: "mdi:hand-back-right-off"
   - entity: light
   - entity: light
     name: AQ indicator
     name: AQ indicator
+    category: config
     class: switch
     class: switch
     dps:
     dps:
       - id: 8
       - id: 8
@@ -70,3 +72,39 @@ secondary_entities:
       - id: 101
       - id: 101
         name: switch
         name: switch
         type: boolean
         type: boolean
+  - entity: sensor
+    name: Air Quality
+    class: aqi
+    category: diagnostic
+    dps:
+      - id: 22
+        type: string
+        name: sensor
+  - entity: sensor
+    name: Prefilter Life
+    category: diagnostic
+    dps:
+      - id: 102
+        type: integer
+        name: sensor
+  - entity: sensor
+    name: Charcoal Filter Life
+    category: diagnostic
+    dps:
+      - id: 103
+        type: integer
+        name: sensor
+  - entity: sensor
+    name: Active Filter Life
+    category: diagnostic
+    dps:
+      - id: 104
+        type: integer
+        name: sensor
+  - entity: sensor
+    name: HEPA Filter Life
+    category: diagnostic
+    dps:
+      - id: 105
+        type: integer
+        name: sensor

+ 67 - 0
custom_components/tuya_local/devices/saswell_c16_thermostat.yaml

@@ -101,6 +101,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: lock
   - entity: lock
     name: Child Lock
     name: Child Lock
+    category: config
     dps:
     dps:
       - id: 11
       - id: 11
         name: lock
         name: lock
@@ -110,3 +111,69 @@ secondary_entities:
             icon: "mdi:hand-back-right-off"
             icon: "mdi:hand-back-right-off"
           - dps_val: false
           - dps_val: false
             icon: "mdi:hand-back-right"
             icon: "mdi:hand-back-right"
+  - entity: number
+    name: Floor Temperature Limit
+    category: config
+    dps:
+      - id: 6
+        name: value
+        type: integer
+        range:
+          min: 200
+          max: 500
+        mapping:
+          - scale: 10
+            step: 5
+  - entity: select
+    name: Installation
+    category: config
+    dps:
+      - id: 7
+        name: option
+        type: boolean
+        mapping:
+          - dps_val: true
+            value: Office
+          - dps_val: false
+            value: Home
+  - entity: sensor
+    name: Floor Temperature
+    class: temperature
+    category: diagnostic
+    dps:
+      - id: 8
+        name: sensor
+        type: integer
+        class: measurement
+        unit: C
+        mapping:
+          - scale: 10
+  - entity: switch
+    name: Adaptive
+    category: config
+    dps:
+      - id: 10
+        name: switch
+        type: boolean
+  - entity: select
+    name: Schedule
+    category: config
+    dps:
+      - id: 12
+        name: option
+        type: string
+        mapping:
+          - dps_val: "5_1_1"
+            value: "Weekdays+Sat+Sun"
+          - dps_val: "7"
+            value: "Daily"
+  - entity: number
+    name: Power Rating
+    category: config
+    dps:
+      - id: 22
+        name: value
+        type: integer
+        range:
+          min: 0
+          max: 3500

+ 29 - 2
custom_components/tuya_local/devices/saswell_t29utk_thermostat.yaml

@@ -125,5 +125,32 @@ primary_entity:
     - id: 117
     - id: 117
       name: current_temperature_f
       name: current_temperature_f
       type: integer
       type: integer
-
-      
+secondary_entities:
+  - entity: select
+    name: Temperature Unit
+    category: config
+    dps:
+      - id: 19
+        name: option
+        type: string
+        mapping:
+          - dps_val: F
+            value: Fahrenheit
+          - dps_val: C
+            value: Celsius
+  - entity: select
+    name: Configuration
+    category: config
+    dps:
+      - id: 112
+        name: option
+        type: string
+        mapping:
+          - dps_val: "1"
+            value: "cooling"
+          - dps_val: "2"
+            value: "heating"
+          - dps_val: "3"
+            value: "heat/cool"
+          - dps_val: "5"
+            value: "heatpump"

+ 33 - 0
custom_components/tuya_local/devices/smartplugv1.yaml

@@ -28,3 +28,36 @@ primary_entity:
       readonly: true
       readonly: true
       mapping:
       mapping:
         - scale: 10
         - scale: 10
+secondary_entities:
+  - entity: sensor
+    category: diagnostic
+    class: voltage
+    name: Voltage
+    dps:
+      - id: 6
+        name: sensor
+        type: integer
+        class: measurement
+        unit: V
+        mapping:
+          - scale: 10
+  - entity: sensor
+    category: diagnostic
+    class: current
+    name: Current
+    dps:
+      - id: 4
+        name: sensor
+        type: integer
+        class: measurement
+        unit: mA
+  - entity: number
+    category: config
+    name: Timer
+    dps:
+      - id: 2
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 1440

+ 33 - 0
custom_components/tuya_local/devices/smartplugv2.yaml

@@ -28,3 +28,36 @@ primary_entity:
       read-only: true
       read-only: true
       mapping:
       mapping:
         - scale: 10
         - scale: 10
+secondary_entities:
+  - entity: sensor
+    category: diagnostic
+    class: voltage
+    name: Voltage
+    dps:
+      - id: 20
+        name: sensor
+        type: integer
+        class: measurement
+        unit: V
+        mapping:
+          - scale: 10
+  - entity: sensor
+    category: diagnostic
+    class: current
+    name: Current
+    dps:
+      - id: 18
+        name: sensor
+        type: integer
+        class: measurement
+        unit: mA
+  - entity: number
+    category: config
+    name: Timer
+    dps:
+      - id: 9
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 1440

+ 1 - 0
custom_components/tuya_local/devices/stirling_fs140dc_fan.yaml

@@ -29,6 +29,7 @@ primary_entity:
 secondary_entities:
 secondary_entities:
   - entity: select
   - entity: select
     name: timer
     name: timer
+    category: config
     dps:
     dps:
       - id: 22
       - id: 22
         name: option
         name: option

+ 71 - 0
custom_components/tuya_local/devices/wetair_wch750_heater.yaml

@@ -63,6 +63,7 @@ primary_entity:
       name: unknown_21
       name: unknown_21
 secondary_entities:
 secondary_entities:
   - entity: light
   - entity: light
+    category: config
     name: Display
     name: Display
     dps:
     dps:
       - id: 101
       - id: 101
@@ -71,10 +72,80 @@ secondary_entities:
         mapping:
         mapping:
           - dps_val: level0
           - dps_val: level0
             value: 0
             value: 0
+            step: 85
           - dps_val: level1
           - dps_val: level1
             value: 85
             value: 85
+            step: 85
           - dps_val: level2
           - dps_val: level2
             value: 170
             value: 170
+            step: 85
           - dps_val: level3
           - dps_val: level3
             value: 255
             value: 255
+            step: 85
           - step: 85
           - step: 85
+  - entity: select
+    name: Timer
+    category: config
+    dps:
+      - id: 19
+        type: string
+        name: option
+        mapping:
+          - dps_val: "0h"
+            value: "Off"
+          - dps_val: "1h"
+            value: "1 hour"
+          - dps_val: "2h"
+            value: "2 hours"
+          - dps_val: "3h"
+            value: "3 hours"
+          - dps_val: "4h"
+            value: "4 hours"
+          - dps_val: "5h"
+            value: "5 hours"
+          - dps_val: "6h"
+            value: "6 hours"
+          - dps_val: "7h"
+            value: "7 hours"
+          - dps_val: "8h"
+            value: "8 hours"
+          - dps_val: "9h"
+            value: "9 hours"
+          - dps_val: "10h"
+            value: "10 hours"
+          - dps_val: "11h"
+            value: "11 hours"
+          - dps_val: "12h"
+            value: "12 hours"
+          - dps_val: "13h"
+            value: "13 hours"
+          - dps_val: "14h"
+            value: "14 hours"
+          - dps_val: "15h"
+            value: "15 hours"
+          - dps_val: "16h"
+            value: "16 hours"
+          - dps_val: "17h"
+            value: "17 hours"
+          - dps_val: "18h"
+            value: "18 hours"
+          - dps_val: "19h"
+            value: "19 hours"
+          - dps_val: "20h"
+            value: "20 hours"
+          - dps_val: "21h"
+            value: "21 hours"
+          - dps_val: "22h"
+            value: "22 hours"
+          - dps_val: "23h"
+            value: "23 hours"
+          - dps_val: "24h"
+            value: "24 hours"          
+  - entity: sensor
+    category: diagnostic
+    name: Timer
+    dps:
+      - id: 20
+        type: integer
+        name: sensor
+        unit: min

+ 56 - 2
custom_components/tuya_local/translations/en.json

@@ -33,8 +33,12 @@
 		    "select": "Include a select entity",
 		    "select": "Include a select entity",
 		    "sensor": "Include a sensor entity",
 		    "sensor": "Include a sensor entity",
 		    "switch": "Include a switch entity",
 		    "switch": "Include a switch entity",
-		    "binary_sensor_tank": "Include tank as a binary_sensor entity.",
+		    "binary_sensor_continuous_heat", "Include continuous heat alarm as a binary_sensor entity",
 		    "binary_sensor_defrost": "Include defrost as a binary_sensor entity.",
 		    "binary_sensor_defrost": "Include defrost as a binary_sensor entity.",
+		    "binary_sensor_high_temperature", "Include high temperature alarm as a binary_sensor entity",
+		    "binary_sensor_low_temperature", "Include low temperature alarm as a binary_sensor entity",
+		    "binary_sensor_tank": "Include tank as a binary_sensor entity",
+		    "binary_sensor_water_flow": "Include water flow warning as a binary_sensor entity",
 		    "climate_dehumidifier_as_climate": "Include a climate entity for the dehumidifier (deprecated, recommend using humidifier and fan instead)",
 		    "climate_dehumidifier_as_climate": "Include a climate entity for the dehumidifier (deprecated, recommend using humidifier and fan instead)",
 		    "fan_intensity": "Include intensity as a fan entitiy",
 		    "fan_intensity": "Include intensity as a fan entitiy",
 		    "light_aq_indicator": "Include AQ indicator as a light entity",
 		    "light_aq_indicator": "Include AQ indicator as a light entity",
@@ -43,10 +47,33 @@
 		    "light_flame": "Include flame as a light entity",
 		    "light_flame": "Include flame as a light entity",
 		    "light_uv_sterilization": "Include UV sterilization as a light entitiy",
 		    "light_uv_sterilization": "Include UV sterilization as a light entitiy",
 		    "lock_child_lock": "Include child lock as a lock entity",
 		    "lock_child_lock": "Include child lock as a lock entity",
+		    "number_calibration_offset": "Include calibration offset as a number entitiy",
+		    "number_calibration_swing": "Include calibration swing as a number entity",
+		    "number_continuous_heat_hours": "Include Continuous Heating Time as a number entity",
+		    "number_high_temperature_limit": "Include High Temperature Limit as a number entity",
+		    "number_low_temperature_limit": "Include Low Temperature Limit as a number entity",
+		    "number_floor_temperature_limit": "Include Floor Temperature Limit as a number entity",
+		    "number_power_rating": "Include Power Rating as a number entity",
 		    "number_timer": "Include timer as a number entity",
 		    "number_timer": "Include timer as a number entity",
+		    "select_installation": "Include installation as a select entity.",
+		    "select_schedule": "Include schedule as a select entity",
 		    "select_timer": "Include timer as a select entity",
 		    "select_timer": "Include timer as a select entity",
+		    "select_temperature_sensor": "Include temperature sensor config as a select entity",
+		    "select_temperature_unit": "Include temerature unit as a select entity",
+		    "sensor_air_quality": "Include air quality as a sensor entity",
+		    "sensor_prefilter_life": "Include prefilter life as a sensor entity",
+		    "sensor_charcoal_filter_life": "Include charcoal filter life as a sensor entity",
+		    "sensor_active_filter_life": "Include active filter life as a sensor entity",
+		    "sensor_hepa_filter_life": "Include HEPA filter life as a sensor entity",
 		    "sensor_current_humidity": "Include current humidity as a sensor entity",
 		    "sensor_current_humidity": "Include current humidity as a sensor entity",
 		    "sensor_current_temperature": "Include current temperature as a sensor entity",
 		    "sensor_current_temperature": "Include current temperature as a sensor entity",
+		    "sensor_external_temperature": "Include external temperature as a sensor entity",
+		    "sensor_power_level": "Include power level as a sensor entity",
+		    "sensor_timer": "Include time remaining as a sensor entity",
+		    "sensor_floor_temperature": "Include floor temperature as a sensor entity",
+		    "sensor_current": "Include current as a sensor entity",
+		    "sensor_voltage": "Include voltage as a sensor entity",
+		    "switch_adaptive": "Include adaptive as a switch entity",
 		    "switch_air_clean": "Include air clean as a switch entity",
 		    "switch_air_clean": "Include air clean as a switch entity",
 		    "switch_ionizer": "Include ionizer as a switch entity",
 		    "switch_ionizer": "Include ionizer as a switch entity",
 		    "switch_master": "Include master switch as a switch entity",
 		    "switch_master": "Include master switch as a switch entity",
@@ -85,8 +112,12 @@
 		"select": "Include a select entity",
 		"select": "Include a select entity",
 		"sensor": "Include a sensor entity",
 		"sensor": "Include a sensor entity",
 		"switch": "Include a switch entity",
 		"switch": "Include a switch entity",
-		"binary_sensor_tank": "Include tank as a binary_sensor entity.",
+		"binary_sensor_continuous_heat", "Include continuous heat alarm as a binary_sensor entity",
 		"binary_sensor_defrost": "Include defrost as a binary_sensor entity.",
 		"binary_sensor_defrost": "Include defrost as a binary_sensor entity.",
+		"binary_sensor_high_temperature", "Include high temperature alarm as a binary_sensor entity",
+		"binary_sensor_low_temperature", "Include low temperature alarm as a binary_sensor entity",
+		"binary_sensor_tank": "Include tank as a binary_sensor entity.",
+		"binary_sensor_water_flow": "Include water flow warning as a binary_sensor entity",
 		"climate_dehumidifier_as_climate": "Include a climate entity for the dehumidifier (deprecated, recommend using humidifier and fan instead)",
 		"climate_dehumidifier_as_climate": "Include a climate entity for the dehumidifier (deprecated, recommend using humidifier and fan instead)",
 		"fan_intensity": "Include intensity as a fan entitiy",
 		"fan_intensity": "Include intensity as a fan entitiy",
 		"light_aq_indicator": "Include AQ indicator as a light entity",
 		"light_aq_indicator": "Include AQ indicator as a light entity",
@@ -95,10 +126,33 @@
 		"light_flame": "Include flame as a light entity",
 		"light_flame": "Include flame as a light entity",
 		"light_uv_sterilization": "Include UV sterilization as a light entitiy",
 		"light_uv_sterilization": "Include UV sterilization as a light entitiy",
 		"lock_child_lock": "Include child lock as a lock entity",
 		"lock_child_lock": "Include child lock as a lock entity",
+		"number_calibration_offset": "Include calibration offset as a number entitiy",
+		"number_calibration_swing": "Include calibration swing as a number entity",
+		"number_continuous_heat_hours": "Include Continuous Heating Time as a number entity",
+		"number_high_temperature_limit": "Include High Temperature Limit as a number entity",
+		"number_low_temperature_limit": "Include Low Temperature Limit as a number entity",
+		"number_floor_temperature_limit": "Include Floor Temperature Limit as a number entity",
+		"number_power_rating": "Include Power Rating as a number entity",
 		"number_timer": "Include timer as a number entity",
 		"number_timer": "Include timer as a number entity",
+		"select_installation": "Include installation as a select entity.",
+		"select_schedule": "Include schedule as a select entity",
 		"select_timer": "Include timer as a select entity",
 		"select_timer": "Include timer as a select entity",
+		"select_temperature_sensor": "Include temperature sensor config as a select entity",
+		"select_temperature_unit": "Include temerature unit as a select entity",
+		"sensor_air_quality": "Include air quality as a sensor entity",
+		"sensor_prefilter_life": "Include prefilter life as a sensor entity",
+		"sensor_charcoal_filter_life": "Include charcoal filter life as a sensor entity",
+		"sensor_active_filter_life": "Include active filter life as a sensor entity",
+		"sensor_hepa_filter_life": "Include HEPA filter life as a sensor entity",
 		"sensor_current_humidity": "Include current humidity as a sensor entity",
 		"sensor_current_humidity": "Include current humidity as a sensor entity",
 		"sensor_current_temperature": "Include current temperature as a sensor entity",
 		"sensor_current_temperature": "Include current temperature as a sensor entity",
+		"sensor_external_temperature": "Include external temperature as a sensor entity",
+		"sensor_power_level": "Include power level as a sensor entity",
+		"sensor_timer": "Include time remaining as a sensor entity",
+		"sensor_floor_temperature": "Include floor temperature as a sensor entity",
+		"sensor_current": "Include current as a sensor entity",
+		"sensor_voltage": "Include voltage as a sensor entity",
+		"switch_adaptive": "Include adaptive as a switch entity",
 		"switch_air_clean": "Include air clean as a switch entity",
 		"switch_air_clean": "Include air clean as a switch entity",
 		"switch_ionizer": "Include ionizer as a switch entity",
 		"switch_ionizer": "Include ionizer as a switch entity",
 		"switch_master": "Include master switch as a switch entity",
 		"switch_master": "Include master switch as a switch entity",

+ 0 - 204
tests/devices/base_device_tests.py

@@ -2,11 +2,6 @@ from unittest import IsolatedAsyncioTestCase
 from unittest.mock import AsyncMock, patch, PropertyMock
 from unittest.mock import AsyncMock, patch, PropertyMock
 from uuid import uuid4
 from uuid import uuid4
 
 
-from homeassistant.components.light import COLOR_MODE_ONOFF
-from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
-from homeassistant.components.switch import DEVICE_CLASS_SWITCH
-from homeassistant.const import STATE_UNAVAILABLE
-
 from custom_components.tuya_local.generic.binary_sensor import TuyaLocalBinarySensor
 from custom_components.tuya_local.generic.binary_sensor import TuyaLocalBinarySensor
 from custom_components.tuya_local.generic.climate import TuyaLocalClimate
 from custom_components.tuya_local.generic.climate import TuyaLocalClimate
 from custom_components.tuya_local.generic.fan import TuyaLocalFan
 from custom_components.tuya_local.generic.fan import TuyaLocalFan
@@ -124,202 +119,3 @@ class TuyaDeviceTestCase(IsolatedAsyncioTestCase):
             await e.async_update()
             await e.async_update()
             self.mock_device.async_refresh.assert_called_once()
             self.mock_device.async_refresh.assert_called_once()
             result.assert_awaited()
             result.assert_awaited()
-
-
-# Mixins for common test functions
-
-
-class SwitchableTests:
-    def setUpSwitchable(self, dps, subject):
-        self.switch_dps = dps
-        self.switch_subject = subject
-
-    def test_switchable_is_on(self):
-        self.dps[self.switch_dps] = True
-        self.assertTrue(self.switch_subject.is_on)
-
-        self.dps[self.switch_dps] = False
-        self.assertFalse(self.switch_subject.is_on)
-
-        self.dps[self.switch_dps] = None
-        self.assertIsNone(self.switch_subject.is_on)
-
-    async def test_switchable_turn_on(self):
-        async with assert_device_properties_set(
-            self.switch_subject._device, {self.switch_dps: True}
-        ):
-            await self.switch_subject.async_turn_on()
-
-    async def test_switchable_turn_off(self):
-        async with assert_device_properties_set(
-            self.switch_subject._device, {self.switch_dps: False}
-        ):
-            await self.switch_subject.async_turn_off()
-
-    async def test_switchable_toggle(self):
-        self.dps[self.switch_dps] = False
-        async with assert_device_properties_set(
-            self.switch_subject._device, {self.switch_dps: True}
-        ):
-            await self.switch_subject.async_toggle()
-
-        self.dps[self.switch_dps] = True
-        async with assert_device_properties_set(
-            self.switch_subject._device, {self.switch_dps: False}
-        ):
-            await self.switch_subject.async_toggle()
-
-
-class BasicLightTests:
-    def setUpBasicLight(self, dps, subject):
-        self.basicLight = subject
-        self.basicLightDps = dps
-
-    def test_basic_light_supported_features(self):
-        self.assertEqual(self.basicLight.supported_features, 0)
-
-    def test_basic_light_supported_color_modes(self):
-        self.assertCountEqual(
-            self.basicLight.supported_color_modes,
-            [COLOR_MODE_ONOFF],
-        )
-
-    def test_basic_light_color_mode(self):
-        self.assertEqual(self.basicLight.color_mode, COLOR_MODE_ONOFF)
-
-    def test_light_has_no_brightness(self):
-        self.assertIsNone(self.basicLight.brightness)
-
-    def test_light_has_no_effects(self):
-        self.assertIsNone(self.basicLight.effect_list)
-        self.assertIsNone(self.basicLight.effect)
-
-    def test_basic_light_is_on(self):
-        self.dps[self.basicLightDps] = True
-        self.assertTrue(self.basicLight.is_on)
-        self.dps[self.basicLightDps] = False
-        self.assertFalse(self.basicLight.is_on)
-
-    async def test_basic_light_turn_on(self):
-        async with assert_device_properties_set(
-            self.basicLight._device, {self.basicLightDps: True}
-        ):
-            await self.basicLight.async_turn_on()
-
-    async def test_basic_light_turn_off(self):
-        async with assert_device_properties_set(
-            self.basicLight._device, {self.basicLightDps: False}
-        ):
-            await self.basicLight.async_turn_off()
-
-    async def test_basic_light_toggle_turns_on_when_it_was_off(self):
-        self.dps[self.basicLightDps] = False
-        async with assert_device_properties_set(
-            self.basicLight._device,
-            {self.basicLightDps: True},
-        ):
-            await self.basicLight.async_toggle()
-
-    async def test_basic_light_toggle_turns_off_when_it_was_on(self):
-        self.dps[self.basicLightDps] = True
-        async with assert_device_properties_set(
-            self.basicLight._device,
-            {self.basicLightDps: False},
-        ):
-            await self.basicLight.async_toggle()
-
-    def test_basic_light_state_attributes(self):
-        self.assertEqual(self.basicLight.device_state_attributes, {})
-
-
-class BasicLockTests:
-    def setUpBasicLock(self, dps, subject):
-        self.basicLock = subject
-        self.basicLockDps = dps
-
-    def test_basic_lock_state(self):
-        self.dps[self.basicLockDps] = True
-        self.assertEqual(self.basicLock.state, STATE_LOCKED)
-
-        self.dps[self.basicLockDps] = False
-        self.assertEqual(self.basicLock.state, STATE_UNLOCKED)
-
-        self.dps[self.basicLockDps] = None
-        self.assertEqual(self.basicLock.state, STATE_UNAVAILABLE)
-
-    def test_basic_lock_is_locked(self):
-        self.dps[self.basicLockDps] = True
-        self.assertTrue(self.basicLock.is_locked)
-
-        self.dps[self.basicLockDps] = False
-        self.assertFalse(self.basicLock.is_locked)
-
-        self.dps[self.basicLockDps] = None
-        self.assertFalse(self.basicLock.is_locked)
-
-    async def test_basic_lock_locks(self):
-        async with assert_device_properties_set(
-            self.basicLock._device,
-            {self.basicLockDps: True},
-        ):
-            await self.basicLock.async_lock()
-
-    async def test_basic_lock_unlocks(self):
-        async with assert_device_properties_set(
-            self.basicLock._device,
-            {self.basicLockDps: False},
-        ):
-            await self.basicLock.async_unlock()
-
-    def test_basic_lock_state_attributes(self):
-        self.assertEqual(self.basicLock.device_state_attributes, {})
-
-
-class BasicSwitchTests:
-    def setUpBasicSwitch(self, dps, subject):
-        self.basicSwitch = subject
-        self.basicSwitchDps = dps
-
-    def test_basic_switch_is_on(self):
-        self.dps[self.basicSwitchDps] = True
-        self.assertEqual(self.basicSwitch.is_on, True)
-
-        self.dps[self.basicSwitchDps] = False
-        self.assertEqual(self.basicSwitch.is_on, False)
-
-    async def test_basic_switch_turn_on(self):
-        async with assert_device_properties_set(
-            self.basicSwitch._device, {self.basicSwitchDps: True}
-        ):
-            await self.basicSwitch.async_turn_on()
-
-    async def test_basic_switch_turn_off(self):
-        async with assert_device_properties_set(
-            self.basicSwitch._device, {self.basicSwitchDps: False}
-        ):
-            await self.basicSwitch.async_turn_off()
-
-    async def test_basic_switch_toggle_turns_on_when_it_was_off(self):
-        self.dps[self.basicSwitchDps] = False
-
-        async with assert_device_properties_set(
-            self.basicSwitch._device, {self.basicSwitchDps: True}
-        ):
-            await self.basicSwitch.async_toggle()
-
-    async def test_basic_switch_toggle_turns_off_when_it_was_on(self):
-        self.dps[self.basicSwitchDps] = True
-
-        async with assert_device_properties_set(
-            self.basicSwitch._device, {self.basicSwitchDps: False}
-        ):
-            await self.basicSwitch.async_toggle()
-
-    def test_basic_switch_class_is_switch(self):
-        self.assertEqual(self.basicSwitch.device_class, DEVICE_CLASS_SWITCH)
-
-    def test_basic_switch_has_no_power_monitoring(self):
-        self.assertIsNone(self.basicSwitch.current_power_w)
-
-    def test_basic_switch_state_attributes(self):
-        self.assertEqual(self.basicSwitch.device_state_attributes, {})

+ 5 - 2
tests/devices/test_anko_fan.py

@@ -6,7 +6,9 @@ from homeassistant.components.fan import (
 
 
 from ..const import ANKO_FAN_PAYLOAD
 from ..const import ANKO_FAN_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import SwitchableTests, TuyaDeviceTestCase
+from ..mixins.number import BasicNumberTests
+from ..mixins.switch import SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 SWITCH_DPS = "1"
 SWITCH_DPS = "1"
 PRESET_DPS = "2"
 PRESET_DPS = "2"
@@ -15,13 +17,14 @@ OSCILLATE_DPS = "4"
 TIMER_DPS = "6"
 TIMER_DPS = "6"
 
 
 
 
-class TestAnkoFan(SwitchableTests, TuyaDeviceTestCase):
+class TestAnkoFan(SwitchableTests, BasicNumberTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("anko_fan.yaml", ANKO_FAN_PAYLOAD)
         self.setUpForConfig("anko_fan.yaml", ANKO_FAN_PAYLOAD)
         self.subject = self.entities["fan"]
         self.subject = self.entities["fan"]
         self.setUpSwitchable(SWITCH_DPS, self.subject)
         self.setUpSwitchable(SWITCH_DPS, self.subject)
+        self.setUpBasicNumber(TIMER_DPS, self.entities.get("number_timer"), max=9)
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 14 - 20
tests/devices/test_arlec_fan.py

@@ -10,7 +10,9 @@ from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import ARLEC_FAN_PAYLOAD
 from ..const import ARLEC_FAN_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import SwitchableTests, TuyaDeviceTestCase
+from ..mixins.select import BasicSelectTests
+from ..mixins.switch import SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 SWITCH_DPS = "1"
 SWITCH_DPS = "1"
 SPEED_DPS = "3"
 SPEED_DPS = "3"
@@ -19,7 +21,7 @@ PRESET_DPS = "102"
 TIMER_DPS = "103"
 TIMER_DPS = "103"
 
 
 
 
-class TestArlecFan(SwitchableTests, TuyaDeviceTestCase):
+class TestArlecFan(SwitchableTests, BasicSelectTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
@@ -27,6 +29,16 @@ class TestArlecFan(SwitchableTests, TuyaDeviceTestCase):
         self.subject = self.entities["fan"]
         self.subject = self.entities["fan"]
         self.timer = self.entities["select_timer"]
         self.timer = self.entities["select_timer"]
         self.setUpSwitchable(SWITCH_DPS, self.subject)
         self.setUpSwitchable(SWITCH_DPS, self.subject)
+        self.setUpBasicSelect(
+            TIMER_DPS,
+            self.entities["select_timer"],
+            {
+                "off": "Off",
+                "2hour": "2 hours",
+                "4hour": "4 hours",
+                "8hour": "8 hours",
+            },
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(
@@ -109,21 +121,3 @@ class TestArlecFan(SwitchableTests, TuyaDeviceTestCase):
     def test_device_state_attributes(self):
     def test_device_state_attributes(self):
         self.dps[TIMER_DPS] = "2hour"
         self.dps[TIMER_DPS] = "2hour"
         self.assertEqual(self.subject.device_state_attributes, {"timer": "2hour"})
         self.assertEqual(self.subject.device_state_attributes, {"timer": "2hour"})
-        self.assertEqual(self.timer.device_state_attributes, {})
-
-    def test_timer_options(self):
-        self.assertCountEqual(
-            self.timer.options,
-            ["Off", "2 hours", "4 hours", "8 hours"],
-        )
-
-    def test_timer_current_option(self):
-        self.dps[TIMER_DPS] = "2hour"
-        self.assertEqual(self.timer.current_option, "2 hours")
-
-    async def test_select_option(self):
-        async with assert_device_properties_set(
-            self.timer._device,
-            {TIMER_DPS: "4hour"},
-        ):
-            await self.timer.async_select_option("4 hours")

+ 49 - 4
tests/devices/test_awow_th213_thermostat.py

@@ -7,12 +7,19 @@ from homeassistant.components.climate.const import (
     SUPPORT_PRESET_MODE,
     SUPPORT_PRESET_MODE,
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
-from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
-from homeassistant.const import STATE_UNAVAILABLE
+from homeassistant.const import (
+    DEVICE_CLASS_TEMPERATURE,
+    STATE_UNAVAILABLE,
+    TEMP_CELSIUS,
+)
 
 
 from ..const import TH213_THERMOSTAT_PAYLOAD
 from ..const import TH213_THERMOSTAT_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLockTests, TuyaDeviceTestCase
+from ..mixins.lock import BasicLockTests
+from ..mixins.number import MultiNumberTests
+from ..mixins.select import BasicSelectTests
+from ..mixins.sensor import BasicSensorTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
 TEMPERATURE_DPS = "2"
 TEMPERATURE_DPS = "2"
@@ -30,13 +37,51 @@ UNKNOWN108_DPS = "108"
 UNKNOWN110_DPS = "110"
 UNKNOWN110_DPS = "110"
 
 
 
 
-class TestAwowTH213Thermostat(BasicLockTests, TuyaDeviceTestCase):
+class TestAwowTH213Thermostat(
+    BasicLockTests,
+    BasicSelectTests,
+    BasicSensorTests,
+    MultiNumberTests,
+    TuyaDeviceTestCase,
+):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("awow_th213_thermostat.yaml", TH213_THERMOSTAT_PAYLOAD)
         self.setUpForConfig("awow_th213_thermostat.yaml", TH213_THERMOSTAT_PAYLOAD)
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
+        self.setUpBasicSensor(
+            EXTERNTEMP_DPS,
+            self.entities.get("sensor_external_temperature"),
+            unit=TEMP_CELSIUS,
+            device_class=DEVICE_CLASS_TEMPERATURE,
+            state_class="measurement",
+        )
+        self.setUpBasicSelect(
+            SENSOR_DPS,
+            self.entities.get("select_temperature_sensor"),
+            {
+                0: "Internal",
+                1: "External",
+                2: "Both",
+            },
+        )
+        self.setUpMultiNumber(
+            [
+                {
+                    "name": "number_calibration_offset",
+                    "dps": CALIBRATE_DPS,
+                    "min": -9,
+                    "max": 9,
+                },
+                {
+                    "name": "number_calibration_swing",
+                    "dps": CALIBSWING_DPS,
+                    "min": 1,
+                    "max": 9,
+                },
+            ]
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 3 - 1
tests/devices/test_beca_bhp6000_thermostat.py

@@ -12,7 +12,9 @@ from homeassistant.const import STATE_UNAVAILABLE, TEMP_CELSIUS, TEMP_FAHRENHEIT
 
 
 from ..const import BECA_BHP6000_PAYLOAD
 from ..const import BECA_BHP6000_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLightTests, BasicLockTests, TuyaDeviceTestCase
+from ..mixins.light import BasicLightTests
+from ..mixins.lock import BasicLockTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 LIGHT_DPS = "1"
 LIGHT_DPS = "1"
 TEMPERATURE_DPS = "2"
 TEMPERATURE_DPS = "2"

+ 20 - 3
tests/devices/test_beca_bht002_thermostat.py

@@ -7,11 +7,18 @@ from homeassistant.components.climate.const import (
     SUPPORT_PRESET_MODE,
     SUPPORT_PRESET_MODE,
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
-from homeassistant.const import STATE_UNAVAILABLE
+from homeassistant.const import (
+    DEVICE_CLASS_TEMPERATURE,
+    STATE_UNAVAILABLE,
+    TEMP_CELSIUS,
+)
 
 
 from ..const import BECA_BHT002_PAYLOAD
 from ..const import BECA_BHT002_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLightTests, BasicLockTests, TuyaDeviceTestCase
+from ..mixins.light import BasicLightTests
+from ..mixins.lock import BasicLockTests
+from ..mixins.sensor import BasicSensorTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 POWER_DPS = "1"
 POWER_DPS = "1"
 TEMPERATURE_DPS = "2"
 TEMPERATURE_DPS = "2"
@@ -23,7 +30,9 @@ FLOOR_DPS = "102"
 UNKNOWN104_DPS = "104"
 UNKNOWN104_DPS = "104"
 
 
 
 
-class TestBecaBHT002Thermostat(BasicLightTests, BasicLockTests, TuyaDeviceTestCase):
+class TestBecaBHT002Thermostat(
+    BasicLightTests, BasicLockTests, BasicSensorTests, TuyaDeviceTestCase
+):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
@@ -34,6 +43,14 @@ class TestBecaBHT002Thermostat(BasicLightTests, BasicLockTests, TuyaDeviceTestCa
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
         self.setUpBasicLight(POWER_DPS, self.entities.get("light_display"))
         self.setUpBasicLight(POWER_DPS, self.entities.get("light_display"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
+        self.setUpBasicSensor(
+            FLOOR_DPS,
+            self.entities.get("sensor_external_temperature"),
+            unit=TEMP_CELSIUS,
+            device_class=DEVICE_CLASS_TEMPERATURE,
+            state_class="measurement",
+            testdata=(30, 15),
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 20 - 3
tests/devices/test_beca_bht6000_thermostat.py

@@ -7,11 +7,18 @@ from homeassistant.components.climate.const import (
     SUPPORT_PRESET_MODE,
     SUPPORT_PRESET_MODE,
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
-from homeassistant.const import STATE_UNAVAILABLE
+from homeassistant.const import (
+    DEVICE_CLASS_TEMPERATURE,
+    STATE_UNAVAILABLE,
+    TEMP_CELSIUS,
+)
 
 
 from ..const import BECA_BHT6000_PAYLOAD
 from ..const import BECA_BHT6000_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLightTests, BasicLockTests, TuyaDeviceTestCase
+from ..mixins.light import BasicLightTests
+from ..mixins.lock import BasicLockTests
+from ..mixins.sensor import BasicSensorTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 POWER_DPS = "1"
 POWER_DPS = "1"
 TEMPERATURE_DPS = "2"
 TEMPERATURE_DPS = "2"
@@ -24,7 +31,9 @@ UNKNOWN103_DPS = "103"
 UNKNOWN104_DPS = "104"
 UNKNOWN104_DPS = "104"
 
 
 
 
-class TestBecaBHT6000Thermostat(BasicLightTests, BasicLockTests, TuyaDeviceTestCase):
+class TestBecaBHT6000Thermostat(
+    BasicLightTests, BasicLockTests, BasicSensorTests, TuyaDeviceTestCase
+):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
@@ -35,6 +44,14 @@ class TestBecaBHT6000Thermostat(BasicLightTests, BasicLockTests, TuyaDeviceTestC
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
         self.setUpBasicLight(POWER_DPS, self.entities.get("light_display"))
         self.setUpBasicLight(POWER_DPS, self.entities.get("light_display"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
+        self.setUpBasicSensor(
+            FLOOR_DPS,
+            self.entities.get("sensor_external_temperature"),
+            unit=TEMP_CELSIUS,
+            device_class=DEVICE_CLASS_TEMPERATURE,
+            state_class="measurement",
+            testdata=(36, 18),
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 9 - 1
tests/devices/test_bwt_heatpump.py

@@ -1,3 +1,4 @@
+from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM
 from homeassistant.components.climate.const import (
 from homeassistant.components.climate.const import (
     HVAC_MODE_HEAT,
     HVAC_MODE_HEAT,
     HVAC_MODE_OFF,
     HVAC_MODE_OFF,
@@ -8,6 +9,7 @@ from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import BWT_HEATPUMP_PAYLOAD
 from ..const import BWT_HEATPUMP_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
+from ..mixins.binary_sensor import BasicBinarySensorTests
 from .base_device_tests import TuyaDeviceTestCase
 from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
@@ -17,12 +19,18 @@ PRESET_DPS = "4"
 ERROR_DPS = "9"
 ERROR_DPS = "9"
 
 
 
 
-class TestBWTHeatpump(TuyaDeviceTestCase):
+class TestBWTHeatpump(BasicBinarySensorTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("bwt_heatpump.yaml", BWT_HEATPUMP_PAYLOAD)
         self.setUpForConfig("bwt_heatpump.yaml", BWT_HEATPUMP_PAYLOAD)
         self.subject = self.entities["climate"]
         self.subject = self.entities["climate"]
+        self.setUpBasicBinarySensor(
+            ERROR_DPS,
+            self.entities.get("binary_sensor_water_flow"),
+            device_class=DEVICE_CLASS_PROBLEM,
+            testdata=(1, 0),
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 3 - 6
tests/devices/test_deta_fan.py

@@ -3,12 +3,9 @@ from homeassistant.components.light import COLOR_MODE_ONOFF
 
 
 from ..const import DETA_FAN_PAYLOAD
 from ..const import DETA_FAN_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import (
-    BasicLightTests,
-    BasicSwitchTests,
-    SwitchableTests,
-    TuyaDeviceTestCase,
-)
+from ..mixins.light import BasicLightTests
+from ..mixins.switch import BasicSwitchTests, SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 SWITCH_DPS = "1"
 SWITCH_DPS = "1"
 SPEED_DPS = "3"
 SPEED_DPS = "3"

+ 48 - 6
tests/devices/test_eanons_humidifier.py

@@ -1,3 +1,4 @@
+from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM
 from homeassistant.components.climate.const import (
 from homeassistant.components.climate.const import (
     FAN_HIGH,
     FAN_HIGH,
     FAN_MEDIUM,
     FAN_MEDIUM,
@@ -21,7 +22,11 @@ from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import EANONS_HUMIDIFIER_PAYLOAD
 from ..const import EANONS_HUMIDIFIER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicSwitchTests, SwitchableTests, TuyaDeviceTestCase
+from ..mixins.binary_sensor import BasicBinarySensorTests
+from ..mixins.select import BasicSelectTests
+from ..mixins.sensor import BasicSensorTests
+from ..mixins.switch import BasicSwitchTests, SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 FANMODE_DPS = "2"
 FANMODE_DPS = "2"
 TIMERHR_DPS = "3"
 TIMERHR_DPS = "3"
@@ -34,16 +39,53 @@ CURRENTHUMID_DPS = "16"
 SWITCH_DPS = "22"
 SWITCH_DPS = "22"
 
 
 
 
-class TestEanonsHumidifier(BasicSwitchTests, SwitchableTests, TuyaDeviceTestCase):
+class TestEanonsHumidifier(
+    BasicBinarySensorTests,
+    BasicSelectTests,
+    BasicSensorTests,
+    BasicSwitchTests,
+    SwitchableTests,
+    TuyaDeviceTestCase,
+):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("eanons_humidifier.yaml", EANONS_HUMIDIFIER_PAYLOAD)
         self.setUpForConfig("eanons_humidifier.yaml", EANONS_HUMIDIFIER_PAYLOAD)
-        self.subject = self.entities["humidifier"]
+        self.subject = self.entities.get("humidifier")
         self.setUpSwitchable(HVACMODE_DPS, self.subject)
         self.setUpSwitchable(HVACMODE_DPS, self.subject)
-        self.climate = self.entities["climate"]
-        self.fan = self.entities["fan_intensity"]
-        self.setUpBasicSwitch(SWITCH_DPS, self.entities["switch_uv_sterilization"])
+        self.climate = self.entities.get("climate")
+        self.fan = self.entities.get("fan_intensity")
+        self.setUpBasicSwitch(SWITCH_DPS, self.entities.get("switch_uv_sterilization"))
+        self.setUpBasicSelect(
+            TIMERHR_DPS,
+            self.entities.get("select_timer"),
+            {
+                "cancel": "Off",
+                "1": "1 hour",
+                "2": "2 hours",
+                "3": "3 hours",
+                "4": "4 hours",
+                "5": "5 hours",
+                "6": "6 hours",
+                "7": "7 hours",
+                "8": "8 hours",
+                "9": "9 hours",
+                "10": "10 hours",
+                "11": "11 hours",
+                "12": "12 hours",
+            },
+        )
+        self.setUpBasicSensor(
+            TIMER_DPS,
+            self.entities.get("sensor_timer"),
+            unit="min",
+        )
+        self.setUpBasicBinarySensor(
+            ERROR_DPS,
+            self.entities.get("binary_sensor_tank"),
+            device_class=DEVICE_CLASS_PROBLEM,
+            testdata=(1, 0),
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 3 - 1
tests/devices/test_eberg_qubo_q40hd_heatpump.py

@@ -21,6 +21,7 @@ from homeassistant.const import STATE_UNAVAILABLE, TEMP_CELSIUS, TEMP_FAHRENHEIT
 
 
 from ..const import EBERG_QUBO_Q40HD_PAYLOAD
 from ..const import EBERG_QUBO_Q40HD_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
+from ..mixins.number import BasicNumberTests
 from .base_device_tests import TuyaDeviceTestCase
 from .base_device_tests import TuyaDeviceTestCase
 
 
 POWER_DPS = "1"
 POWER_DPS = "1"
@@ -35,7 +36,7 @@ SWING_DPS = "30"
 HVACACTION_DPS = "101"
 HVACACTION_DPS = "101"
 
 
 
 
-class TestEbergQuboQ40HDHeatpump(TuyaDeviceTestCase):
+class TestEbergQuboQ40HDHeatpump(BasicNumberTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
@@ -44,6 +45,7 @@ class TestEbergQuboQ40HDHeatpump(TuyaDeviceTestCase):
             EBERG_QUBO_Q40HD_PAYLOAD,
             EBERG_QUBO_Q40HD_PAYLOAD,
         )
         )
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
+        self.setUpBasicNumber(TIMER_DPS, self.entities.get("number_timer"), max=24)
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 3 - 2
tests/devices/test_electriq_12wminv_heatpump.py

@@ -9,12 +9,13 @@ from homeassistant.components.climate.const import (
     SUPPORT_SWING_MODE,
     SUPPORT_SWING_MODE,
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
-from homeassistant.components.light import COLOR_MODE_ONOFF
 from homeassistant.const import STATE_UNAVAILABLE
 from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import ELECTRIQ_12WMINV_HEATPUMP_PAYLOAD
 from ..const import ELECTRIQ_12WMINV_HEATPUMP_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLightTests, BasicSwitchTests, TuyaDeviceTestCase
+from ..mixins.light import BasicLightTests
+from ..mixins.switch import BasicSwitchTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 POWER_DPS = "1"
 POWER_DPS = "1"
 TEMPERATURE_DPS = "2"
 TEMPERATURE_DPS = "2"

+ 29 - 3
tests/devices/test_electriq_cd12_dehumidifier.py

@@ -1,9 +1,17 @@
 from homeassistant.components.humidifier import SUPPORT_MODES
 from homeassistant.components.humidifier import SUPPORT_MODES
-from homeassistant.components.light import COLOR_MODE_ONOFF
+from homeassistant.const import (
+    DEVICE_CLASS_HUMIDITY,
+    DEVICE_CLASS_TEMPERATURE,
+    PERCENTAGE,
+    TEMP_CELSIUS,
+)
 
 
 from ..const import ELECTRIQ_CD12PW_DEHUMIDIFIER_PAYLOAD
 from ..const import ELECTRIQ_CD12PW_DEHUMIDIFIER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLightTests, SwitchableTests, TuyaDeviceTestCase
+from ..mixins.light import BasicLightTests
+from ..mixins.sensor import MultiSensorTests
+from ..mixins.switch import SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 SWITCH_DPS = "1"
 SWITCH_DPS = "1"
 MODE_DPS = "2"
 MODE_DPS = "2"
@@ -14,7 +22,7 @@ CURRENTTEMP_DPS = "103"
 
 
 
 
 class TestElectriqCD20ProDehumidifier(
 class TestElectriqCD20ProDehumidifier(
-    BasicLightTests, SwitchableTests, TuyaDeviceTestCase
+    BasicLightTests, MultiSensorTests, SwitchableTests, TuyaDeviceTestCase
 ):
 ):
     __test__ = True
     __test__ = True
 
 
@@ -25,6 +33,24 @@ class TestElectriqCD20ProDehumidifier(
         self.subject = self.entities.get("humidifier")
         self.subject = self.entities.get("humidifier")
         self.setUpSwitchable(SWITCH_DPS, self.subject)
         self.setUpSwitchable(SWITCH_DPS, self.subject)
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light_display"))
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light_display"))
+        self.setUpMultiSensors(
+            [
+                {
+                    "name": "sensor_current_temperature",
+                    "dps": CURRENTTEMP_DPS,
+                    "unit": TEMP_CELSIUS,
+                    "device_class": DEVICE_CLASS_TEMPERATURE,
+                    "state_class": "measurement",
+                },
+                {
+                    "name": "sensor_current_humidity",
+                    "dps": CURRENTHUMID_DPS,
+                    "unit": PERCENTAGE,
+                    "device_class": DEVICE_CLASS_HUMIDITY,
+                    "state_class": "measurement",
+                },
+            ]
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(self.subject.supported_features, SUPPORT_MODES)
         self.assertEqual(self.subject.supported_features, SUPPORT_MODES)

+ 47 - 10
tests/devices/test_electriq_cd20_dehumidifier.py

@@ -1,15 +1,18 @@
 from homeassistant.components.fan import SUPPORT_PRESET_MODE
 from homeassistant.components.fan import SUPPORT_PRESET_MODE
 from homeassistant.components.humidifier import SUPPORT_MODES
 from homeassistant.components.humidifier import SUPPORT_MODES
-from homeassistant.components.light import COLOR_MODE_ONOFF
+from homeassistant.const import (
+    DEVICE_CLASS_HUMIDITY,
+    DEVICE_CLASS_TEMPERATURE,
+    PERCENTAGE,
+    TEMP_CELSIUS,
+)
 
 
 from ..const import ELECTRIQ_CD20PRO_DEHUMIDIFIER_PAYLOAD
 from ..const import ELECTRIQ_CD20PRO_DEHUMIDIFIER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import (
-    BasicLightTests,
-    BasicSwitchTests,
-    SwitchableTests,
-    TuyaDeviceTestCase,
-)
+from ..mixins.light import BasicLightTests
+from ..mixins.sensor import MultiSensorTests
+from ..mixins.switch import MultiSwitchTests, SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 SWITCH_DPS = "1"
 SWITCH_DPS = "1"
 MODE_DPS = "2"
 MODE_DPS = "2"
@@ -23,7 +26,11 @@ CURRENTTEMP_DPS = "103"
 
 
 
 
 class TestElectriqCD20ProDehumidifier(
 class TestElectriqCD20ProDehumidifier(
-    BasicLightTests, BasicSwitchTests, SwitchableTests, TuyaDeviceTestCase
+    BasicLightTests,
+    MultiSensorTests,
+    MultiSwitchTests,
+    SwitchableTests,
+    TuyaDeviceTestCase,
 ):
 ):
     __test__ = True
     __test__ = True
 
 
@@ -35,7 +42,30 @@ class TestElectriqCD20ProDehumidifier(
         self.fan = self.entities.get("fan")
         self.fan = self.entities.get("fan")
         self.setUpSwitchable(SWITCH_DPS, self.subject)
         self.setUpSwitchable(SWITCH_DPS, self.subject)
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light_display"))
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light_display"))
-        self.setUpBasicSwitch(UV_DPS, self.entities.get("switch_uv_sterilization"))
+        self.setUpMultiSwitch(
+            [
+                {"dps": UV_DPS, "name": "switch_uv_sterilization"},
+                {"dps": ANION_DPS, "name": "switch_ionizer"},
+            ]
+        )
+        self.setUpMultiSensors(
+            [
+                {
+                    "name": "sensor_current_temperature",
+                    "dps": CURRENTTEMP_DPS,
+                    "unit": TEMP_CELSIUS,
+                    "device_class": DEVICE_CLASS_TEMPERATURE,
+                    "state_class": "measurement",
+                },
+                {
+                    "name": "sensor_current_humidity",
+                    "dps": CURRENTHUMID_DPS,
+                    "unit": PERCENTAGE,
+                    "device_class": DEVICE_CLASS_HUMIDITY,
+                    "state_class": "measurement",
+                },
+            ]
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(self.subject.supported_features, SUPPORT_MODES)
         self.assertEqual(self.subject.supported_features, SUPPORT_MODES)
@@ -59,7 +89,14 @@ class TestElectriqCD20ProDehumidifier(
         self.dps[MODE_DPS] = "high"
         self.dps[MODE_DPS] = "high"
         self.assertEqual(self.subject.icon, "mdi:tshirt-crew-outline")
         self.assertEqual(self.subject.icon, "mdi:tshirt-crew-outline")
 
 
-        self.assertEqual(self.basicSwitch.icon, "mdi:solar-power")
+        self.assertEqual(
+            self.multiSwitch["switch_uv_sterilization"].icon,
+            "mdi:solar-power",
+        )
+        self.assertEqual(
+            self.multiSwitch["switch_ionizer"].icon,
+            "mdi:creation",
+        )
         self.dps[LIGHT_DPS] = True
         self.dps[LIGHT_DPS] = True
         self.assertEqual(self.basicLight.icon, "mdi:led-on")
         self.assertEqual(self.basicLight.icon, "mdi:led-on")
         self.dps[LIGHT_DPS] = False
         self.dps[LIGHT_DPS] = False

+ 30 - 8
tests/devices/test_electriq_cd25_dehumidifier.py

@@ -1,16 +1,19 @@
 from homeassistant.components.fan import SUPPORT_PRESET_MODE
 from homeassistant.components.fan import SUPPORT_PRESET_MODE
 from homeassistant.components.humidifier import SUPPORT_MODES
 from homeassistant.components.humidifier import SUPPORT_MODES
-from homeassistant.components.light import COLOR_MODE_ONOFF
+from homeassistant.const import (
+    DEVICE_CLASS_HUMIDITY,
+    DEVICE_CLASS_TEMPERATURE,
+    PERCENTAGE,
+    TEMP_CELSIUS,
+)
 
 
 from ..const import ELECTRIQ_DEHUMIDIFIER_PAYLOAD
 from ..const import ELECTRIQ_DEHUMIDIFIER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import (
-    BasicLightTests,
-    BasicLockTests,
-    BasicSwitchTests,
-    SwitchableTests,
-    TuyaDeviceTestCase,
-)
+from ..mixins.light import BasicLightTests
+from ..mixins.lock import BasicLockTests
+from ..mixins.sensor import MultiSensorTests
+from ..mixins.switch import BasicSwitchTests, SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 SWITCH_DPS = "1"
 SWITCH_DPS = "1"
 MODE_DPS = "2"
 MODE_DPS = "2"
@@ -27,6 +30,7 @@ class TestElectriqCD25ProDehumidifier(
     BasicLightTests,
     BasicLightTests,
     BasicLockTests,
     BasicLockTests,
     BasicSwitchTests,
     BasicSwitchTests,
+    MultiSensorTests,
     SwitchableTests,
     SwitchableTests,
     TuyaDeviceTestCase,
     TuyaDeviceTestCase,
 ):
 ):
@@ -42,6 +46,24 @@ class TestElectriqCD25ProDehumidifier(
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light_uv_sterilization"))
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light_uv_sterilization"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicSwitch(IONIZER_DPS, self.entities.get("switch_ionizer"))
         self.setUpBasicSwitch(IONIZER_DPS, self.entities.get("switch_ionizer"))
+        self.setUpMultiSensors(
+            [
+                {
+                    "name": "sensor_current_temperature",
+                    "dps": CURRENTTEMP_DPS,
+                    "unit": TEMP_CELSIUS,
+                    "device_class": DEVICE_CLASS_TEMPERATURE,
+                    "state_class": "measurement",
+                },
+                {
+                    "name": "sensor_current_humidity",
+                    "dps": CURRENTHUMID_DPS,
+                    "unit": PERCENTAGE,
+                    "device_class": DEVICE_CLASS_HUMIDITY,
+                    "state_class": "measurement",
+                },
+            ]
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(self.subject.supported_features, SUPPORT_MODES)
         self.assertEqual(self.subject.supported_features, SUPPORT_MODES)

+ 17 - 3
tests/devices/test_electriq_desd9lw_dehumidifier.py

@@ -9,11 +9,18 @@ from homeassistant.components.climate.const import (
     SUPPORT_TARGET_HUMIDITY,
     SUPPORT_TARGET_HUMIDITY,
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
-from homeassistant.const import STATE_UNAVAILABLE
+from homeassistant.const import (
+    DEVICE_CLASS_HUMIDITY,
+    PERCENTAGE,
+    STATE_UNAVAILABLE,
+)
 
 
 from ..const import ELECTRIQ_DESD9LW_DEHUMIDIFIER_PAYLOAD
 from ..const import ELECTRIQ_DESD9LW_DEHUMIDIFIER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLightTests, BasicSwitchTests, TuyaDeviceTestCase
+from ..mixins.light import BasicLightTests
+from ..mixins.sensor import BasicSensorTests
+from ..mixins.switch import BasicSwitchTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 POWER_DPS = "1"
 POWER_DPS = "1"
 HUMIDITY_DPS = "2"
 HUMIDITY_DPS = "2"
@@ -28,7 +35,7 @@ TEMPERATURE_DPS = "101"
 
 
 
 
 class TestElectriqDESD9LWDehumidifier(
 class TestElectriqDESD9LWDehumidifier(
-    BasicLightTests, BasicSwitchTests, TuyaDeviceTestCase
+    BasicLightTests, BasicSensorTests, BasicSwitchTests, TuyaDeviceTestCase
 ):
 ):
     __test__ = True
     __test__ = True
 
 
@@ -40,6 +47,13 @@ class TestElectriqDESD9LWDehumidifier(
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light_uv_sterilization"))
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light_uv_sterilization"))
         self.setUpBasicSwitch(SWITCH_DPS, self.entities.get("switch_ionizer"))
         self.setUpBasicSwitch(SWITCH_DPS, self.entities.get("switch_ionizer"))
+        self.setUpBasicSensor(
+            CURRENTHUM_DPS,
+            self.entities.get("sensor_current_humidity"),
+            unit=PERCENTAGE,
+            device_class=DEVICE_CLASS_HUMIDITY,
+            state_class="measurement",
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 9 - 1
tests/devices/test_eurom_600_heater.py

@@ -1,3 +1,4 @@
+from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM
 from homeassistant.components.climate.const import (
 from homeassistant.components.climate.const import (
     HVAC_MODE_HEAT,
     HVAC_MODE_HEAT,
     HVAC_MODE_OFF,
     HVAC_MODE_OFF,
@@ -7,6 +8,7 @@ from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import EUROM_600_HEATER_PAYLOAD
 from ..const import EUROM_600_HEATER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
+from ..mixins.binary_sensor import BasicBinarySensorTests
 from .base_device_tests import TuyaDeviceTestCase
 from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
@@ -15,12 +17,18 @@ CURRENTTEMP_DPS = "5"
 ERROR_DPS = "6"
 ERROR_DPS = "6"
 
 
 
 
-class TestEurom600Heater(TuyaDeviceTestCase):
+class TestEurom600Heater(BasicBinarySensorTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("eurom_600_heater.yaml", EUROM_600_HEATER_PAYLOAD)
         self.setUpForConfig("eurom_600_heater.yaml", EUROM_600_HEATER_PAYLOAD)
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
+        self.setUpBasicBinarySensor(
+            ERROR_DPS,
+            self.entities.get("binary_sensor_error"),
+            device_class=DEVICE_CLASS_PROBLEM,
+            testdata=(1, 0),
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 23 - 3
tests/devices/test_gardenpac_heatpump.py

@@ -1,10 +1,15 @@
 from homeassistant.components.climate.const import (
 from homeassistant.components.climate.const import (
     HVAC_MODE_HEAT,
     HVAC_MODE_HEAT,
     HVAC_MODE_OFF,
     HVAC_MODE_OFF,
+    CURRENT_HVAC_HEAT,
+    CURRENT_HVAC_IDLE,
+    CURRENT_HVAC_OFF,
     SUPPORT_PRESET_MODE,
     SUPPORT_PRESET_MODE,
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
 from homeassistant.const import (
 from homeassistant.const import (
+    DEVICE_CLASS_POWER_FACTOR,
+    PERCENTAGE,
     STATE_UNAVAILABLE,
     STATE_UNAVAILABLE,
     TEMP_CELSIUS,
     TEMP_CELSIUS,
     TEMP_FAHRENHEIT,
     TEMP_FAHRENHEIT,
@@ -12,6 +17,7 @@ from homeassistant.const import (
 
 
 from ..const import GARDENPAC_HEATPUMP_PAYLOAD
 from ..const import GARDENPAC_HEATPUMP_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
+from ..mixins.sensor import BasicSensorTests
 from .base_device_tests import TuyaDeviceTestCase
 from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
@@ -27,12 +33,19 @@ UNKNOWN116_DPS = "116"
 PRESET_DPS = "117"
 PRESET_DPS = "117"
 
 
 
 
-class TestGardenPACPoolHeatpump(TuyaDeviceTestCase):
+class TestGardenPACPoolHeatpump(BasicSensorTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("gardenpac_heatpump.yaml", GARDENPAC_HEATPUMP_PAYLOAD)
         self.setUpForConfig("gardenpac_heatpump.yaml", GARDENPAC_HEATPUMP_PAYLOAD)
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
+        self.setUpBasicSensor(
+            POWERLEVEL_DPS,
+            self.entities.get("sensor_power_level"),
+            unit=PERCENTAGE,
+            device_class=DEVICE_CLASS_POWER_FACTOR,
+            state_class="measurement",
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(
@@ -164,9 +177,17 @@ class TestGardenPACPoolHeatpump(TuyaDeviceTestCase):
         ):
         ):
             await self.subject.async_set_preset_mode("Smart")
             await self.subject.async_set_preset_mode("Smart")
 
 
+    def test_hvac_action(self):
+        self.dps[HVACMODE_DPS] = True
+        self.dps[OPMODE_DPS] = "heating"
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_HEAT)
+        self.dps[OPMODE_DPS] = "warm"
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_IDLE)
+        self.dps[HVACMODE_DPS] = False
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_OFF)
+
     def test_device_state_attributes(self):
     def test_device_state_attributes(self):
         self.dps[POWERLEVEL_DPS] = 50
         self.dps[POWERLEVEL_DPS] = 50
-        self.dps[OPMODE_DPS] = "cool"
         self.dps[UNKNOWN107_DPS] = 1
         self.dps[UNKNOWN107_DPS] = 1
         self.dps[UNKNOWN108_DPS] = 2
         self.dps[UNKNOWN108_DPS] = 2
         self.dps[UNKNOWN115_DPS] = 3
         self.dps[UNKNOWN115_DPS] = 3
@@ -175,7 +196,6 @@ class TestGardenPACPoolHeatpump(TuyaDeviceTestCase):
             self.subject.device_state_attributes,
             self.subject.device_state_attributes,
             {
             {
                 "power_level": 50,
                 "power_level": 50,
-                "operating_mode": "cool",
                 "unknown_107": 1,
                 "unknown_107": 1,
                 "unknown_108": 2,
                 "unknown_108": 2,
                 "unknown_115": 3,
                 "unknown_115": 3,

+ 45 - 45
tests/devices/test_goldair_dehumidifier.py

@@ -24,12 +24,12 @@ from homeassistant.const import (
 
 
 from ..const import DEHUMIDIFIER_PAYLOAD
 from ..const import DEHUMIDIFIER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import (
-    BasicLockTests,
-    BasicSwitchTests,
-    SwitchableTests,
-    TuyaDeviceTestCase,
-)
+from ..mixins.binary_sensor import MultiBinarySensorTests
+from ..mixins.lock import BasicLockTests
+from ..mixins.number import BasicNumberTests
+from ..mixins.sensor import MultiSensorTests
+from ..mixins.switch import BasicSwitchTests, SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
 PRESET_DPS = "2"
 PRESET_DPS = "2"
@@ -38,7 +38,7 @@ AIRCLEAN_DPS = "5"
 FANMODE_DPS = "6"
 FANMODE_DPS = "6"
 LOCK_DPS = "7"
 LOCK_DPS = "7"
 ERROR_DPS = "11"
 ERROR_DPS = "11"
-UNKNOWN12_DPS = "12"
+TIMER_DPS = "12"
 UNKNOWN101_DPS = "101"
 UNKNOWN101_DPS = "101"
 LIGHTOFF_DPS = "102"
 LIGHTOFF_DPS = "102"
 CURRENTTEMP_DPS = "103"
 CURRENTTEMP_DPS = "103"
@@ -55,7 +55,10 @@ ERROR_TANK = "Tank full or missing"
 
 
 class TestGoldairDehumidifier(
 class TestGoldairDehumidifier(
     BasicLockTests,
     BasicLockTests,
+    BasicNumberTests,
     BasicSwitchTests,
     BasicSwitchTests,
+    MultiBinarySensorTests,
+    MultiSensorTests,
     SwitchableTests,
     SwitchableTests,
     TuyaDeviceTestCase,
     TuyaDeviceTestCase,
 ):
 ):
@@ -71,10 +74,41 @@ class TestGoldairDehumidifier(
         self.light = self.entities.get("light_display")
         self.light = self.entities.get("light_display")
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicSwitch(AIRCLEAN_DPS, self.entities.get("switch_air_clean"))
         self.setUpBasicSwitch(AIRCLEAN_DPS, self.entities.get("switch_air_clean"))
-        self.temperature = self.entities.get("sensor_current_temperature")
-        self.humidity = self.entities.get("sensor_current_humidity")
-        self.tank = self.entities.get("binary_sensor_tank")
-        self.defrost = self.entities.get("binary_sensor_defrost")
+        self.setUpBasicNumber(TIMER_DPS, self.entities.get("number_timer"), max=24)
+
+        self.setUpMultiSensors(
+            [
+                {
+                    "name": "sensor_current_temperature",
+                    "dps": CURRENTTEMP_DPS,
+                    "unit": TEMP_CELSIUS,
+                    "device_class": DEVICE_CLASS_TEMPERATURE,
+                    "state_class": "measurement",
+                },
+                {
+                    "name": "sensor_current_humidity",
+                    "dps": CURRENTHUMID_DPS,
+                    "unit": "%",
+                    "device_class": DEVICE_CLASS_HUMIDITY,
+                    "state_class": "measurement",
+                },
+            ]
+        )
+        self.setUpMultiBinarySensors(
+            [
+                {
+                    "name": "binary_sensor_tank",
+                    "dps": ERROR_DPS,
+                    "device_class": DEVICE_CLASS_PROBLEM,
+                    "testdata": (8, 0),
+                },
+                {
+                    "name": "binary_sensor_defrost",
+                    "dps": DEFROST_DPS,
+                    "device_class": DEVICE_CLASS_COLD,
+                },
+            ]
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(
@@ -144,7 +178,6 @@ class TestGoldairDehumidifier(
     def test_current_humidity(self):
     def test_current_humidity(self):
         self.dps[CURRENTHUMID_DPS] = 47
         self.dps[CURRENTHUMID_DPS] = 47
         self.assertEqual(self.climate.current_humidity, 47)
         self.assertEqual(self.climate.current_humidity, 47)
-        self.assertEqual(self.humidity.native_value, 47)
 
 
     def test_min_target_humidity(self):
     def test_min_target_humidity(self):
         self.assertEqual(self.climate.min_humidity, 30)
         self.assertEqual(self.climate.min_humidity, 30)
@@ -252,10 +285,6 @@ class TestGoldairDehumidifier(
             self.climate.temperature_unit,
             self.climate.temperature_unit,
             self.climate._device.temperature_unit,
             self.climate._device.temperature_unit,
         )
         )
-        self.assertEqual(
-            self.temperature.native_unit_of_measurement,
-            TEMP_CELSIUS,
-        )
 
 
     def test_minimum_target_temperature(self):
     def test_minimum_target_temperature(self):
         self.assertIs(self.climate.min_temp, None)
         self.assertIs(self.climate.min_temp, None)
@@ -266,7 +295,6 @@ class TestGoldairDehumidifier(
     def test_current_temperature(self):
     def test_current_temperature(self):
         self.dps[CURRENTTEMP_DPS] = 25
         self.dps[CURRENTTEMP_DPS] = 25
         self.assertEqual(self.climate.current_temperature, 25)
         self.assertEqual(self.climate.current_temperature, 25)
-        self.assertEqual(self.temperature.native_value, 25)
 
 
     def test_climate_hvac_mode(self):
     def test_climate_hvac_mode(self):
         self.dps[HVACMODE_DPS] = True
         self.dps[HVACMODE_DPS] = True
@@ -538,7 +566,6 @@ class TestGoldairDehumidifier(
         self.dps[ERROR_DPS] = None
         self.dps[ERROR_DPS] = None
         self.dps[DEFROST_DPS] = False
         self.dps[DEFROST_DPS] = False
         self.dps[AIRCLEAN_DPS] = False
         self.dps[AIRCLEAN_DPS] = False
-        self.dps[UNKNOWN12_DPS] = "something"
         self.dps[UNKNOWN101_DPS] = False
         self.dps[UNKNOWN101_DPS] = False
         self.assertDictEqual(
         self.assertDictEqual(
             self.climate.device_state_attributes,
             self.climate.device_state_attributes,
@@ -546,7 +573,6 @@ class TestGoldairDehumidifier(
                 "error": None,
                 "error": None,
                 "defrosting": False,
                 "defrosting": False,
                 "air_clean_on": False,
                 "air_clean_on": False,
-                "unknown_12": "something",
                 "unknown_101": False,
                 "unknown_101": False,
             },
             },
         )
         )
@@ -554,7 +580,6 @@ class TestGoldairDehumidifier(
         self.dps[ERROR_DPS] = 8
         self.dps[ERROR_DPS] = 8
         self.dps[DEFROST_DPS] = True
         self.dps[DEFROST_DPS] = True
         self.dps[AIRCLEAN_DPS] = True
         self.dps[AIRCLEAN_DPS] = True
-        self.dps[UNKNOWN12_DPS] = "something else"
         self.dps[UNKNOWN101_DPS] = True
         self.dps[UNKNOWN101_DPS] = True
         self.assertDictEqual(
         self.assertDictEqual(
             self.climate.device_state_attributes,
             self.climate.device_state_attributes,
@@ -562,7 +587,6 @@ class TestGoldairDehumidifier(
                 "error": ERROR_TANK,
                 "error": ERROR_TANK,
                 "defrosting": True,
                 "defrosting": True,
                 "air_clean_on": True,
                 "air_clean_on": True,
-                "unknown_12": "something else",
                 "unknown_101": True,
                 "unknown_101": True,
             },
             },
         )
         )
@@ -623,27 +647,3 @@ class TestGoldairDehumidifier(
 
 
     def test_switch_icon(self):
     def test_switch_icon(self):
         self.assertEqual(self.basicSwitch.icon, "mdi:air-purifier")
         self.assertEqual(self.basicSwitch.icon, "mdi:air-purifier")
-
-    def test_sensor_state_class(self):
-        self.assertEqual(self.temperature.state_class, "measurement")
-        self.assertEqual(self.humidity.state_class, "measurement")
-
-    def test_sensor_device_class(self):
-        self.assertEqual(self.temperature.device_class, DEVICE_CLASS_TEMPERATURE)
-        self.assertEqual(self.humidity.device_class, DEVICE_CLASS_HUMIDITY)
-
-    def test_binary_sensor_device_class(self):
-        self.assertEqual(self.tank.device_class, DEVICE_CLASS_PROBLEM)
-        self.assertEqual(self.defrost.device_class, DEVICE_CLASS_COLD)
-
-    def test_binary_sensor_is_on(self):
-        self.dps[ERROR_DPS] = 0
-        self.dps[DEFROST_DPS] = False
-        self.assertFalse(self.tank.is_on)
-        self.assertFalse(self.defrost.is_on)
-        self.dps[ERROR_DPS] = 8
-        self.dps[DEFROST_DPS] = True
-        self.assertTrue(self.tank.is_on)
-        self.assertTrue(self.defrost.is_on)
-        self.dps[ERROR_DPS] = 1
-        self.assertFalse(self.tank.is_on)

+ 3 - 2
tests/devices/test_goldair_fan.py

@@ -14,13 +14,14 @@ from homeassistant.components.fan import (
     SUPPORT_PRESET_MODE,
     SUPPORT_PRESET_MODE,
     SUPPORT_SET_SPEED,
     SUPPORT_SET_SPEED,
 )
 )
-from homeassistant.components.light import COLOR_MODE_ONOFF
 
 
 from homeassistant.const import STATE_UNAVAILABLE
 from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import FAN_PAYLOAD
 from ..const import FAN_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLightTests, SwitchableTests, TuyaDeviceTestCase
+from ..mixins.light import BasicLightTests
+from ..mixins.switch import SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
 FANMODE_DPS = "2"
 FANMODE_DPS = "2"

+ 15 - 3
tests/devices/test_goldair_geco_heater.py

@@ -1,14 +1,17 @@
+from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM
 from homeassistant.components.climate.const import (
 from homeassistant.components.climate.const import (
     HVAC_MODE_HEAT,
     HVAC_MODE_HEAT,
     HVAC_MODE_OFF,
     HVAC_MODE_OFF,
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
-from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
 from homeassistant.const import STATE_UNAVAILABLE
 from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import GECO_HEATER_PAYLOAD
 from ..const import GECO_HEATER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLockTests, TuyaDeviceTestCase
+from ..mixins.binary_sensor import BasicBinarySensorTests
+from ..mixins.lock import BasicLockTests
+from ..mixins.number import BasicNumberTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
 LOCK_DPS = "2"
 LOCK_DPS = "2"
@@ -18,13 +21,22 @@ TIMER_DPS = "5"
 ERROR_DPS = "6"
 ERROR_DPS = "6"
 
 
 
 
-class TestGoldairGECOHeater(BasicLockTests, TuyaDeviceTestCase):
+class TestGoldairGECOHeater(
+    BasicBinarySensorTests, BasicLockTests, BasicNumberTests, TuyaDeviceTestCase
+):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("goldair_geco_heater.yaml", GECO_HEATER_PAYLOAD)
         self.setUpForConfig("goldair_geco_heater.yaml", GECO_HEATER_PAYLOAD)
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
+        self.setUpBasicNumber(TIMER_DPS, self.entities.get("number_timer"), max=24)
+        self.setUpBasicBinarySensor(
+            ERROR_DPS,
+            self.entities.get("binary_sensor_error"),
+            device_class=DEVICE_CLASS_PROBLEM,
+            testdata=(1, 0),
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 15 - 3
tests/devices/test_goldair_gpcv_heater.py

@@ -1,15 +1,18 @@
+from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM
 from homeassistant.components.climate.const import (
 from homeassistant.components.climate.const import (
     HVAC_MODE_HEAT,
     HVAC_MODE_HEAT,
     HVAC_MODE_OFF,
     HVAC_MODE_OFF,
     SUPPORT_PRESET_MODE,
     SUPPORT_PRESET_MODE,
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
-from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
 from homeassistant.const import STATE_UNAVAILABLE
 from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import GPCV_HEATER_PAYLOAD
 from ..const import GPCV_HEATER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLockTests, TuyaDeviceTestCase
+from ..mixins.binary_sensor import BasicBinarySensorTests
+from ..mixins.lock import BasicLockTests
+from ..mixins.number import BasicNumberTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
 LOCK_DPS = "2"
 LOCK_DPS = "2"
@@ -20,13 +23,22 @@ ERROR_DPS = "6"
 PRESET_DPS = "7"
 PRESET_DPS = "7"
 
 
 
 
-class TestGoldairGPCVHeater(BasicLockTests, TuyaDeviceTestCase):
+class TestGoldairGPCVHeater(
+    BasicBinarySensorTests, BasicLockTests, BasicNumberTests, TuyaDeviceTestCase
+):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("goldair_gpcv_heater.yaml", GPCV_HEATER_PAYLOAD)
         self.setUpForConfig("goldair_gpcv_heater.yaml", GPCV_HEATER_PAYLOAD)
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
+        self.setUpBasicNumber(TIMER_DPS, self.entities.get("number_timer"), max=24)
+        self.setUpBasicBinarySensor(
+            ERROR_DPS,
+            self.entities.get("binary_sensor_error"),
+            device_class=DEVICE_CLASS_PROBLEM,
+            testdata=(1, 0),
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 32 - 30
tests/devices/test_goldair_gpph_heater.py

@@ -1,5 +1,6 @@
 from unittest import skip
 from unittest import skip
 
 
+from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM
 from homeassistant.components.climate.const import (
 from homeassistant.components.climate.const import (
     HVAC_MODE_HEAT,
     HVAC_MODE_HEAT,
     HVAC_MODE_OFF,
     HVAC_MODE_OFF,
@@ -8,11 +9,16 @@ from homeassistant.components.climate.const import (
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
 
 
-from homeassistant.const import STATE_UNAVAILABLE
+from homeassistant.const import DEVICE_CLASS_POWER_FACTOR, PERCENTAGE, STATE_UNAVAILABLE
 
 
 from ..const import GPPH_HEATER_PAYLOAD
 from ..const import GPPH_HEATER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLightTests, BasicLockTests, TuyaDeviceTestCase
+from ..mixins.binary_sensor import BasicBinarySensorTests
+from ..mixins.light import BasicLightTests
+from ..mixins.lock import BasicLockTests
+from ..mixins.number import BasicNumberTests
+from ..mixins.sensor import BasicSensorTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
 TEMPERATURE_DPS = "2"
 TEMPERATURE_DPS = "2"
@@ -28,7 +34,14 @@ SWING_DPS = "105"
 ECOTEMP_DPS = "106"
 ECOTEMP_DPS = "106"
 
 
 
 
-class TestGoldairHeater(BasicLightTests, BasicLockTests, TuyaDeviceTestCase):
+class TestGoldairHeater(
+    BasicBinarySensorTests,
+    BasicLightTests,
+    BasicLockTests,
+    BasicNumberTests,
+    BasicSensorTests,
+    TuyaDeviceTestCase,
+):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
@@ -36,7 +49,22 @@ class TestGoldairHeater(BasicLightTests, BasicLockTests, TuyaDeviceTestCase):
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light_display"))
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light_display"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
-        self.timer = self.entities.get("number_timer")
+        self.setUpBasicNumber(
+            TIMER_DPS, self.entities.get("number_timer"), min=0, max=1440, step=60
+        )
+        self.setUpBasicSensor(
+            POWERLEVEL_DPS,
+            self.entities.get("sensor_power_level"),
+            unit=PERCENTAGE,
+            device_class=DEVICE_CLASS_POWER_FACTOR,
+            testdata=("2", 40),
+        )
+        self.setUpBasicBinarySensor(
+            ERROR_DPS,
+            self.entities.get("binary_sensor_error"),
+            device_class=DEVICE_CLASS_PROBLEM,
+            testdata=(1, 0),
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(
@@ -334,29 +362,3 @@ class TestGoldairHeater(BasicLightTests, BasicLockTests, TuyaDeviceTestCase):
 
 
         self.dps[LIGHT_DPS] = False
         self.dps[LIGHT_DPS] = False
         self.assertEqual(self.basicLight.icon, "mdi:led-off")
         self.assertEqual(self.basicLight.icon, "mdi:led-off")
-
-    def test_timer_min_value(self):
-        self.assertEqual(self.timer.min_value, 0)
-
-    def test_timer_max_value(self):
-        self.assertEqual(self.timer.max_value, 1440)
-
-    def test_timer_step(self):
-        self.assertEqual(self.timer.step, 60)
-
-    def test_timer_mode(self):
-        self.assertEqual(self.timer.mode, "auto")
-
-    def test_timer_value(self):
-        self.dps[TIMER_DPS] = 1234
-        self.assertEqual(self.timer.value, 1234)
-
-    async def test_timer_set_value(self):
-        async with assert_device_properties_set(
-            self.timer._device,
-            {TIMER_DPS: 120},
-        ):
-            await self.timer.async_set_value(120)
-
-    def test_number_device_state_attributes(self):
-        self.assertEqual(self.timer.device_state_attributes, {})

+ 39 - 113
tests/devices/test_grid_connect_double_power_point.py

@@ -2,7 +2,8 @@
 from homeassistant.components.switch import DEVICE_CLASS_OUTLET
 from homeassistant.components.switch import DEVICE_CLASS_OUTLET
 
 
 from ..const import GRIDCONNECT_2SOCKET_PAYLOAD
 from ..const import GRIDCONNECT_2SOCKET_PAYLOAD
-from ..helpers import assert_device_properties_set
+from ..mixins.lock import BasicLockTests
+from ..mixins.switch import MultiSwitchTests
 from .base_device_tests import TuyaDeviceTestCase
 from .base_device_tests import TuyaDeviceTestCase
 
 
 SWITCH1_DPS = "1"
 SWITCH1_DPS = "1"
@@ -23,7 +24,11 @@ LOCK_DPS = "40"
 MASTER_DPS = "101"
 MASTER_DPS = "101"
 
 
 
 
-class TestGridConnectDoubleSwitch(TuyaDeviceTestCase):
+class TestGridConnectDoubleSwitch(
+    BasicLockTests,
+    MultiSwitchTests,
+    TuyaDeviceTestCase,
+):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
@@ -31,122 +36,43 @@ class TestGridConnectDoubleSwitch(TuyaDeviceTestCase):
             "grid_connect_usb_double_power_point.yaml",
             "grid_connect_usb_double_power_point.yaml",
             GRIDCONNECT_2SOCKET_PAYLOAD,
             GRIDCONNECT_2SOCKET_PAYLOAD,
         )
         )
-        self.subject = self.entities.get("switch_master")
-        self.switch1 = self.entities.get("switch_outlet_1")
-        self.switch2 = self.entities.get("switch_outlet_2")
-        self.lock = self.entities.get("lock_child_lock")
-
-    def test_device_class_is_outlet(self):
-        self.assertEqual(self.subject.device_class, DEVICE_CLASS_OUTLET)
-
-    def test_is_on(self):
-        self.dps[MASTER_DPS] - True
-        self.assertTrue(self.subject.is_on)
-
-        self.dps[MASTER_DPS] = False
-        self.assertFalse(self.subject.is_on)
-
-        self.assertIsNone(self.switch1.is_on)
-        self.assertIsNone(self.switch1.is_on)
-
-        self.dps[MASTER_DPS] = True
-        self.dps[SWITCH1_DPS] = True
-        self.dps[SWITCH2_DPS] = False
-        self.assertTrue(self.switch1.is_on)
-        self.assertFalse(self.switch2.is_on)
-
-        self.dps[SWITCH1_DPS] = False
-        self.dps[SWITCH2_DPS] = True
-        self.assertFalse(self.switch1.is_on)
-        self.assertTrue(self.switch2.is_on)
-
-    def test_is_on_when_unavailable(self):
-        self.dps[MASTER_DPS] = None
-        self.assertIsNone(self.subject.is_on)
-
-    async def test_turn_on(self):
-        async with assert_device_properties_set(
-            self.subject._device, {MASTER_DPS: True}
-        ):
-            await self.subject.async_turn_on()
-        async with assert_device_properties_set(
-            self.switch1._device, {SWITCH1_DPS: True}
-        ):
-            await self.switch1.async_turn_on()
-        async with assert_device_properties_set(
-            self.switch1._device, {SWITCH2_DPS: True}
-        ):
-            await self.switch2.async_turn_on()
-
-    async def test_turn_off(self):
-        async with assert_device_properties_set(
-            self.subject._device, {MASTER_DPS: False}
-        ):
-            await self.subject.async_turn_off()
-        async with assert_device_properties_set(
-            self.switch1._device, {SWITCH1_DPS: False}
-        ):
-            await self.switch1.async_turn_off()
-        async with assert_device_properties_set(
-            self.switch1._device, {SWITCH2_DPS: False}
-        ):
-            await self.switch2.async_turn_off()
-
-    async def test_toggle_turns_the_switch_on_when_it_was_off(self):
-        self.dps[MASTER_DPS] = False
-        self.dps[SWITCH1_DPS] = False
-        self.dps[SWITCH2_DPS] = False
-
-        async with assert_device_properties_set(
-            self.subject._device, {MASTER_DPS: True}
-        ):
-            await self.subject.async_toggle()
-
-        self.dps[MASTER_DPS] = True
-
-        async with assert_device_properties_set(
-            self.subject._device, {SWITCH1_DPS: True}
-        ):
-            await self.switch1.async_toggle()
-
-        async with assert_device_properties_set(
-            self.subject._device, {SWITCH2_DPS: True}
-        ):
-            await self.switch2.async_toggle()
-
-    async def test_toggle_turns_the_switch_off_when_it_was_on(self):
-        self.dps[MASTER_DPS] = True
-        self.dps[SWITCH1_DPS] = True
-        self.dps[SWITCH2_DPS] = True
-        async with assert_device_properties_set(
-            self.subject._device, {SWITCH1_DPS: False}
-        ):
-            await self.switch1.async_toggle()
-
-        async with assert_device_properties_set(
-            self.subject._device, {SWITCH2_DPS: False}
-        ):
-            await self.switch2.async_toggle()
-
-        async with assert_device_properties_set(
-            self.subject._device, {MASTER_DPS: False}
-        ):
-            await self.subject.async_toggle()
+        self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
+        # Master switch must go last, otherwise its tests interfere with
+        # the tests for the other switches since it overrides them.
+        # Tests for the specific override behaviour are below.
+        self.setUpMultiSwitch(
+            [
+                {
+                    "name": "switch_outlet_1",
+                    "dps": SWITCH1_DPS,
+                    "device_class": DEVICE_CLASS_OUTLET,
+                },
+                {
+                    "name": "switch_outlet_2",
+                    "dps": SWITCH2_DPS,
+                    "device_class": DEVICE_CLASS_OUTLET,
+                },
+                {
+                    "name": "switch_master",
+                    "dps": MASTER_DPS,
+                    "device_class": DEVICE_CLASS_OUTLET,
+                    "power_dps": POWER_DPS,
+                    "power_scale": 10,
+                },
+            ]
+        )
 
 
     async def test_turn_on_fails_when_master_is_off(self):
     async def test_turn_on_fails_when_master_is_off(self):
         self.dps[MASTER_DPS] = False
         self.dps[MASTER_DPS] = False
         self.dps[SWITCH1_DPS] = False
         self.dps[SWITCH1_DPS] = False
         self.dps[SWITCH2_DPS] = False
         self.dps[SWITCH2_DPS] = False
         with self.assertRaises(AttributeError):
         with self.assertRaises(AttributeError):
-            await self.switch1.async_turn_on()
+            await self.multiSwitch["switch_outlet_1"].async_turn_on()
         with self.assertRaises(AttributeError):
         with self.assertRaises(AttributeError):
-            await self.switch2.async_turn_on()
-
-    def test_current_power_w(self):
-        self.dps[POWER_DPS] = 1234
-        self.assertEqual(self.subject.current_power_w, 123.4)
+            await self.multiSwitch["switch_outlet_2"].async_turn_on()
 
 
-    def test_device_state_attributes_set(self):
+    # Since we have attributes, override the default test which expects none.
+    def test_multi_switch_state_attributes(self):
         self.dps[COUNTDOWN1_DPS] = 9
         self.dps[COUNTDOWN1_DPS] = 9
         self.dps[COUNTDOWN2_DPS] = 10
         self.dps[COUNTDOWN2_DPS] = 10
         self.dps[UNKNOWN17_DPS] = 17
         self.dps[UNKNOWN17_DPS] = 17
@@ -160,7 +86,7 @@ class TestGridConnectDoubleSwitch(TuyaDeviceTestCase):
         self.dps[UNKNOWN25_DPS] = 25
         self.dps[UNKNOWN25_DPS] = 25
         self.dps[UNKNOWN38_DPS] = "38"
         self.dps[UNKNOWN38_DPS] = "38"
         self.assertDictEqual(
         self.assertDictEqual(
-            self.subject.device_state_attributes,
+            self.multiSwitch["switch_master"].device_state_attributes,
             {
             {
                 "current_a": 1.234,
                 "current_a": 1.234,
                 "voltage_v": 235.0,
                 "voltage_v": 235.0,
@@ -175,13 +101,13 @@ class TestGridConnectDoubleSwitch(TuyaDeviceTestCase):
             },
             },
         )
         )
         self.assertDictEqual(
         self.assertDictEqual(
-            self.switch1.device_state_attributes,
+            self.multiSwitch["switch_outlet_1"].device_state_attributes,
             {
             {
                 "countdown": 9,
                 "countdown": 9,
             },
             },
         )
         )
         self.assertDictEqual(
         self.assertDictEqual(
-            self.switch2.device_state_attributes,
+            self.multiSwitch["switch_outlet_2"].device_state_attributes,
             {
             {
                 "countdown": 10,
                 "countdown": 10,
             },
             },

+ 96 - 11
tests/devices/test_inkbird_itc306a_thermostat.py

@@ -1,4 +1,11 @@
+from homeassistant.components.binary_sensor import (
+    DEVICE_CLASS_COLD,
+    DEVICE_CLASS_HEAT,
+    DEVICE_CLASS_PROBLEM,
+)
 from homeassistant.components.climate.const import (
 from homeassistant.components.climate.const import (
+    CURRENT_HVAC_HEAT,
+    CURRENT_HVAC_IDLE,
     SUPPORT_PRESET_MODE,
     SUPPORT_PRESET_MODE,
     SUPPORT_TARGET_TEMPERATURE_RANGE,
     SUPPORT_TARGET_TEMPERATURE_RANGE,
 )
 )
@@ -7,6 +14,9 @@ from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
 
 
 from ..const import INKBIRD_THERMOSTAT_PAYLOAD
 from ..const import INKBIRD_THERMOSTAT_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
+from ..mixins.binary_sensor import MultiBinarySensorTests
+from ..mixins.number import MultiNumberTests
+from ..mixins.select import BasicSelectTests
 from .base_device_tests import TuyaDeviceTestCase
 from .base_device_tests import TuyaDeviceTestCase
 
 
 ERROR_DPS = "12"
 ERROR_DPS = "12"
@@ -30,7 +40,12 @@ UNKNOWN119_DPS = "119"
 UNKNOWN120_DPS = "120"
 UNKNOWN120_DPS = "120"
 
 
 
 
-class TestInkbirdThermostat(TuyaDeviceTestCase):
+class TestInkbirdThermostat(
+    BasicSelectTests,
+    MultiBinarySensorTests,
+    MultiNumberTests,
+    TuyaDeviceTestCase,
+):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
@@ -38,6 +53,72 @@ class TestInkbirdThermostat(TuyaDeviceTestCase):
             "inkbird_itc306a_thermostat.yaml", INKBIRD_THERMOSTAT_PAYLOAD
             "inkbird_itc306a_thermostat.yaml", INKBIRD_THERMOSTAT_PAYLOAD
         )
         )
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
+        self.setUpBasicSelect(
+            UNIT_DPS,
+            self.entities.get("select_temperature_unit"),
+            {
+                "C": "Celsius",
+                "F": "Fahrenheit",
+            },
+        )
+        self.setUpMultiBinarySensors(
+            [
+                {
+                    "name": "binary_sensor_high_temperature",
+                    "dps": ALARM_HIGH_DPS,
+                    "device_class": DEVICE_CLASS_HEAT,
+                },
+                {
+                    "name": "binary_sensor_low_temperature",
+                    "dps": ALARM_LOW_DPS,
+                    "device_class": DEVICE_CLASS_COLD,
+                },
+                {
+                    "name": "binary_sensor_continuous_heat",
+                    "dps": ALARM_TIME_DPS,
+                    "device_class": DEVICE_CLASS_PROBLEM,
+                },
+                {
+                    "name": "binary_sensor_error",
+                    "dps": ERROR_DPS,
+                    "device_class": DEVICE_CLASS_PROBLEM,
+                    "testdata": (1, 0),
+                },
+            ]
+        )
+        self.setUpMultiNumber(
+            [
+                {
+                    "name": "number_calibration_offset",
+                    "dps": CALIBRATE_DPS,
+                    "scale": 10,
+                    "step": 0.1,
+                    "min": -9.9,
+                    "max": 9.9,
+                },
+                {
+                    "name": "number_continuous_heat_hours",
+                    "dps": TIME_THRES_DPS,
+                    "max": 96,
+                },
+                {
+                    "name": "number_high_temperature_limit",
+                    "dps": HIGH_THRES_DPS,
+                    "scale": 10,
+                    "step": 0.1,
+                    "min": -40,
+                    "max": 100,
+                },
+                {
+                    "name": "number_low_temperature_limit",
+                    "dps": LOW_THRES_DPS,
+                    "scale": 10,
+                    "step": 0.1,
+                    "min": -40,
+                    "max": 100,
+                },
+            ]
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(
@@ -135,15 +216,15 @@ class TestInkbirdThermostat(TuyaDeviceTestCase):
 
 
     def test_minimum_target_temperature(self):
     def test_minimum_target_temperature(self):
         self.dps[UNIT_DPS] = "C"
         self.dps[UNIT_DPS] = "C"
-        self.assertEqual(self.subject.min_temp, 20.0)
+        self.assertEqual(self.subject.min_temp, 0.0)
         self.dps[UNIT_DPS] = "F"
         self.dps[UNIT_DPS] = "F"
-        self.assertEqual(self.subject.min_temp, 68.0)
+        self.assertEqual(self.subject.min_temp, 32.0)
 
 
     def test_maximum_target_temperature(self):
     def test_maximum_target_temperature(self):
         self.dps[UNIT_DPS] = "C"
         self.dps[UNIT_DPS] = "C"
-        self.assertEqual(self.subject.max_temp, 35.0)
+        self.assertEqual(self.subject.max_temp, 45.0)
         self.dps[UNIT_DPS] = "F"
         self.dps[UNIT_DPS] = "F"
-        self.assertEqual(self.subject.max_temp, 95.0)
+        self.assertEqual(self.subject.max_temp, 113.0)
 
 
     def test_temperature_range(self):
     def test_temperature_range(self):
         self.dps[TEMPHIGH_DPS] = 301
         self.dps[TEMPHIGH_DPS] = 301
@@ -166,20 +247,26 @@ class TestInkbirdThermostat(TuyaDeviceTestCase):
     async def test_set_target_temperature_fails_outside_valid_range(self):
     async def test_set_target_temperature_fails_outside_valid_range(self):
         self.dps[UNIT_DPS] = "C"
         self.dps[UNIT_DPS] = "C"
         with self.assertRaisesRegex(
         with self.assertRaisesRegex(
-            ValueError, "target_temp_low \\(19.9\\) must be between 20.0 and 35.0"
+            ValueError, "target_temp_low \\(-0.1\\) must be between 0.0 and 45.0"
         ):
         ):
             await self.subject.async_set_temperature(
             await self.subject.async_set_temperature(
-                target_temp_high=32.2, target_temp_low=19.9
+                target_temp_high=32.2, target_temp_low=-0.1
             )
             )
 
 
         self.dps[UNIT_DPS] = "F"
         self.dps[UNIT_DPS] = "F"
         with self.assertRaisesRegex(
         with self.assertRaisesRegex(
-            ValueError, "target_temp_high \\(95.1\\) must be between 68.0 and 95.0"
+            ValueError, "target_temp_high \\(113.1\\) must be between 32.0 and 113.0"
         ):
         ):
             await self.subject.async_set_temperature(
             await self.subject.async_set_temperature(
-                target_temp_low=70.0, target_temp_high=95.1
+                target_temp_low=70.0, target_temp_high=113.1
             )
             )
 
 
+    def test_hvac_action(self):
+        self.dps[SWITCH_DPS] = False
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_IDLE)
+        self.dps[SWITCH_DPS] = True
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_HEAT)
+
     def test_device_state_attributes(self):
     def test_device_state_attributes(self):
         self.dps[ERROR_DPS] = 1
         self.dps[ERROR_DPS] = 1
         self.dps[CALIBRATE_DPS] = 1
         self.dps[CALIBRATE_DPS] = 1
@@ -189,7 +276,6 @@ class TestInkbirdThermostat(TuyaDeviceTestCase):
         self.dps[ALARM_HIGH_DPS] = True
         self.dps[ALARM_HIGH_DPS] = True
         self.dps[ALARM_LOW_DPS] = False
         self.dps[ALARM_LOW_DPS] = False
         self.dps[ALARM_TIME_DPS] = True
         self.dps[ALARM_TIME_DPS] = True
-        self.dps[SWITCH_DPS] = False
         self.dps[TEMPF_DPS] = 999
         self.dps[TEMPF_DPS] = 999
         self.dps[UNKNOWN117_DPS] = True
         self.dps[UNKNOWN117_DPS] = True
         self.dps[UNKNOWN118_DPS] = False
         self.dps[UNKNOWN118_DPS] = False
@@ -207,7 +293,6 @@ class TestInkbirdThermostat(TuyaDeviceTestCase):
                 "high_temp_alarm": True,
                 "high_temp_alarm": True,
                 "low_temp_alarm": False,
                 "low_temp_alarm": False,
                 "heat_time_alarm": True,
                 "heat_time_alarm": True,
-                "switch_state": False,
                 "current_temperature_f": 99.9,
                 "current_temperature_f": 99.9,
                 "unknown_117": True,
                 "unknown_117": True,
                 "unknown_118": False,
                 "unknown_118": False,

+ 25 - 3
tests/devices/test_kogan_dehumidifier.py

@@ -1,9 +1,16 @@
+from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM
 from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED
 from homeassistant.components.fan import SUPPORT_OSCILLATE, SUPPORT_SET_SPEED
 from homeassistant.components.humidifier import SUPPORT_MODES
 from homeassistant.components.humidifier import SUPPORT_MODES
-
+from homeassistant.const import (
+    DEVICE_CLASS_HUMIDITY,
+    PERCENTAGE,
+)
 from ..const import KOGAN_DEHUMIDIFIER_PAYLOAD
 from ..const import KOGAN_DEHUMIDIFIER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import SwitchableTests, TuyaDeviceTestCase
+from ..mixins.binary_sensor import BasicBinarySensorTests
+from ..mixins.sensor import BasicSensorTests
+from ..mixins.switch import SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 SWITCH_DPS = "1"
 SWITCH_DPS = "1"
 MODE_DPS = "2"
 MODE_DPS = "2"
@@ -15,7 +22,9 @@ COUNTDOWN_DPS = "13"
 HUMIDITY_DPS = "101"
 HUMIDITY_DPS = "101"
 
 
 
 
-class TestKoganDehumidifier(SwitchableTests, TuyaDeviceTestCase):
+class TestKoganDehumidifier(
+    BasicBinarySensorTests, BasicSensorTests, SwitchableTests, TuyaDeviceTestCase
+):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
@@ -23,6 +32,19 @@ class TestKoganDehumidifier(SwitchableTests, TuyaDeviceTestCase):
         self.subject = self.entities.get("humidifier")
         self.subject = self.entities.get("humidifier")
         self.setUpSwitchable(SWITCH_DPS, self.subject)
         self.setUpSwitchable(SWITCH_DPS, self.subject)
         self.fan = self.entities.get("fan")
         self.fan = self.entities.get("fan")
+        self.setUpBasicBinarySensor(
+            ERROR_DPS,
+            self.entities.get("binary_sensor_tank"),
+            device_class=DEVICE_CLASS_PROBLEM,
+            testdata=(1, 0),
+        )
+        self.setUpBasicSensor(
+            CURRENTHUMID_DPS,
+            self.entities.get("sensor_current_humidity"),
+            device_class=DEVICE_CLASS_HUMIDITY,
+            state_class="measurement",
+            unit=PERCENTAGE,
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(self.subject.supported_features, SUPPORT_MODES)
         self.assertEqual(self.subject.supported_features, SUPPORT_MODES)

+ 5 - 3
tests/devices/test_kogan_kahtp_heater.py

@@ -4,12 +4,13 @@ from homeassistant.components.climate.const import (
     SUPPORT_PRESET_MODE,
     SUPPORT_PRESET_MODE,
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
-from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
 from homeassistant.const import STATE_UNAVAILABLE
 from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import KOGAN_HEATER_PAYLOAD
 from ..const import KOGAN_HEATER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLockTests, TuyaDeviceTestCase
+from ..mixins.lock import BasicLockTests
+from ..mixins.number import BasicNumberTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 TEMPERATURE_DPS = "2"
 TEMPERATURE_DPS = "2"
 CURRENTTEMP_DPS = "3"
 CURRENTTEMP_DPS = "3"
@@ -19,13 +20,14 @@ HVACMODE_DPS = "7"
 TIMER_DPS = "8"
 TIMER_DPS = "8"
 
 
 
 
-class TestGoldairKoganKAHTPHeater(BasicLockTests, TuyaDeviceTestCase):
+class TestGoldairKoganKAHTPHeater(BasicLockTests, BasicNumberTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("kogan_kahtp_heater.yaml", KOGAN_HEATER_PAYLOAD)
         self.setUpForConfig("kogan_kahtp_heater.yaml", KOGAN_HEATER_PAYLOAD)
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
+        self.setUpBasicNumber(TIMER_DPS, self.entities.get("number_timer"), max=24)
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 5 - 3
tests/devices/test_kogan_kawfhtp_heater.py

@@ -4,12 +4,13 @@ from homeassistant.components.climate.const import (
     SUPPORT_PRESET_MODE,
     SUPPORT_PRESET_MODE,
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
-from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
 from homeassistant.const import STATE_UNAVAILABLE
 from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import KOGAN_KAWFHTP_HEATER_PAYLOAD
 from ..const import KOGAN_KAWFHTP_HEATER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLockTests, TuyaDeviceTestCase
+from ..mixins.lock import BasicLockTests
+from ..mixins.number import BasicNumberTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
 TEMPERATURE_DPS = "3"
 TEMPERATURE_DPS = "3"
@@ -19,13 +20,14 @@ PRESET_DPS = "7"
 LOCK_DPS = "2"
 LOCK_DPS = "2"
 
 
 
 
-class TestGoldairKoganKAHTPHeater(BasicLockTests, TuyaDeviceTestCase):
+class TestGoldairKoganKAHTPHeater(BasicLockTests, BasicNumberTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("kogan_kawfhtp_heater.yaml", KOGAN_KAWFHTP_HEATER_PAYLOAD)
         self.setUpForConfig("kogan_kawfhtp_heater.yaml", KOGAN_KAWFHTP_HEATER_PAYLOAD)
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
+        self.setUpBasicNumber(TIMER_DPS, self.entities.get("number_timer"), max=24)
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 7 - 7
tests/devices/test_lexy_f501_fan.py

@@ -6,13 +6,11 @@ from homeassistant.components.fan import (
 
 
 from ..const import LEXY_F501_PAYLOAD
 from ..const import LEXY_F501_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import (
-    BasicLightTests,
-    BasicLockTests,
-    BasicSwitchTests,
-    SwitchableTests,
-    TuyaDeviceTestCase,
-)
+from ..mixins.light import BasicLightTests
+from ..mixins.lock import BasicLockTests
+from ..mixins.number import BasicNumberTests
+from ..mixins.switch import BasicSwitchTests, SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 POWER_DPS = "1"
 POWER_DPS = "1"
 PRESET_DPS = "2"
 PRESET_DPS = "2"
@@ -28,6 +26,7 @@ class TestLexyF501Fan(
     SwitchableTests,
     SwitchableTests,
     BasicLightTests,
     BasicLightTests,
     BasicLockTests,
     BasicLockTests,
+    BasicNumberTests,
     BasicSwitchTests,
     BasicSwitchTests,
     TuyaDeviceTestCase,
     TuyaDeviceTestCase,
 ):
 ):
@@ -39,6 +38,7 @@ class TestLexyF501Fan(
         self.setUpSwitchable(POWER_DPS, self.subject)
         self.setUpSwitchable(POWER_DPS, self.subject)
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light"))
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
+        self.setUpBasicNumber(TIMER_DPS, self.entities.get("number_timer"), max=7)
         self.setUpBasicSwitch(SWITCH_DPS, self.entities.get("switch_sound"))
         self.setUpBasicSwitch(SWITCH_DPS, self.entities.get("switch_sound"))
 
 
     def test_supported_features(self):
     def test_supported_features(self):

+ 22 - 3
tests/devices/test_madimack_heatpump.py

@@ -1,10 +1,15 @@
 from homeassistant.components.climate.const import (
 from homeassistant.components.climate.const import (
+    CURRENT_HVAC_HEAT,
+    CURRENT_HVAC_IDLE,
+    CURRENT_HVAC_OFF,
     HVAC_MODE_HEAT,
     HVAC_MODE_HEAT,
     HVAC_MODE_OFF,
     HVAC_MODE_OFF,
     SUPPORT_PRESET_MODE,
     SUPPORT_PRESET_MODE,
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
 from homeassistant.const import (
 from homeassistant.const import (
+    DEVICE_CLASS_POWER_FACTOR,
+    PERCENTAGE,
     STATE_UNAVAILABLE,
     STATE_UNAVAILABLE,
     TEMP_CELSIUS,
     TEMP_CELSIUS,
     TEMP_FAHRENHEIT,
     TEMP_FAHRENHEIT,
@@ -12,6 +17,7 @@ from homeassistant.const import (
 
 
 from ..const import MADIMACK_HEATPUMP_PAYLOAD
 from ..const import MADIMACK_HEATPUMP_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
+from ..mixins.sensor import BasicSensorTests
 from .base_device_tests import TuyaDeviceTestCase
 from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
@@ -42,12 +48,18 @@ UNKNOWN140_DPS = "140"
 PRESET_DPS = "117"
 PRESET_DPS = "117"
 
 
 
 
-class TestMadimackPoolHeatpump(TuyaDeviceTestCase):
+class TestMadimackPoolHeatpump(BasicSensorTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("madimack_heatpump.yaml", MADIMACK_HEATPUMP_PAYLOAD)
         self.setUpForConfig("madimack_heatpump.yaml", MADIMACK_HEATPUMP_PAYLOAD)
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
+        self.setUpBasicSensor(
+            POWERLEVEL_DPS,
+            self.entities.get("sensor_power_level"),
+            device_class=DEVICE_CLASS_POWER_FACTOR,
+            unit=PERCENTAGE,
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(
@@ -179,9 +191,17 @@ class TestMadimackPoolHeatpump(TuyaDeviceTestCase):
         ):
         ):
             await self.subject.async_set_preset_mode("Boost")
             await self.subject.async_set_preset_mode("Boost")
 
 
+    def test_hvac_action(self):
+        self.dps[HVACMODE_DPS] = True
+        self.dps[OPMODE_DPS] = "heating"
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_HEAT)
+        self.dps[OPMODE_DPS] = "warm"
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_IDLE)
+        self.dps[HVACMODE_DPS] = False
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_OFF)
+
     def test_device_state_attributes(self):
     def test_device_state_attributes(self):
         self.dps[POWERLEVEL_DPS] = 50
         self.dps[POWERLEVEL_DPS] = 50
-        self.dps[OPMODE_DPS] = "cool"
         self.dps[UNKNOWN107_DPS] = 1
         self.dps[UNKNOWN107_DPS] = 1
         self.dps[UNKNOWN108_DPS] = 2
         self.dps[UNKNOWN108_DPS] = 2
         self.dps[UNKNOWN115_DPS] = 3
         self.dps[UNKNOWN115_DPS] = 3
@@ -205,7 +225,6 @@ class TestMadimackPoolHeatpump(TuyaDeviceTestCase):
             self.subject.device_state_attributes,
             self.subject.device_state_attributes,
             {
             {
                 "power_level": 50,
                 "power_level": 50,
-                "operating_mode": "cool",
                 "unknown_107": 1,
                 "unknown_107": 1,
                 "unknown_108": 2,
                 "unknown_108": 2,
                 "unknown_115": 3,
                 "unknown_115": 3,

+ 2 - 1
tests/devices/test_moes_bht002_thermostat.py

@@ -11,7 +11,8 @@ from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import MOES_BHT002_PAYLOAD
 from ..const import MOES_BHT002_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLockTests, TuyaDeviceTestCase
+from ..mixins.lock import BasicLockTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 POWER_DPS = "1"
 POWER_DPS = "1"
 TEMPERATURE_DPS = "2"
 TEMPERATURE_DPS = "2"

+ 9 - 1
tests/devices/test_poolex_silverline_heatpump.py

@@ -1,3 +1,4 @@
+from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM
 from homeassistant.components.climate.const import (
 from homeassistant.components.climate.const import (
     HVAC_MODE_HEAT,
     HVAC_MODE_HEAT,
     HVAC_MODE_OFF,
     HVAC_MODE_OFF,
@@ -8,6 +9,7 @@ from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import POOLEX_SILVERLINE_HEATPUMP_PAYLOAD
 from ..const import POOLEX_SILVERLINE_HEATPUMP_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
+from ..mixins.binary_sensor import BasicBinarySensorTests
 from .base_device_tests import TuyaDeviceTestCase
 from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
@@ -17,7 +19,7 @@ PRESET_DPS = "4"
 ERROR_DPS = "13"
 ERROR_DPS = "13"
 
 
 
 
-class TestPoolexSilverlineHeatpump(TuyaDeviceTestCase):
+class TestPoolexSilverlineHeatpump(BasicBinarySensorTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
@@ -25,6 +27,12 @@ class TestPoolexSilverlineHeatpump(TuyaDeviceTestCase):
             "poolex_silverline_heatpump.yaml", POOLEX_SILVERLINE_HEATPUMP_PAYLOAD
             "poolex_silverline_heatpump.yaml", POOLEX_SILVERLINE_HEATPUMP_PAYLOAD
         )
         )
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
+        self.setUpBasicBinarySensor(
+            ERROR_DPS,
+            self.entities.get("binary_sensor_water_flow"),
+            device_class=DEVICE_CLASS_PROBLEM,
+            testdata=(256, 0),
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 9 - 1
tests/devices/test_poolex_vertigo_heatpump.py

@@ -1,3 +1,4 @@
+from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM
 from homeassistant.components.climate.const import (
 from homeassistant.components.climate.const import (
     HVAC_MODE_HEAT,
     HVAC_MODE_HEAT,
     HVAC_MODE_OFF,
     HVAC_MODE_OFF,
@@ -8,6 +9,7 @@ from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import POOLEX_VERTIGO_HEATPUMP_PAYLOAD
 from ..const import POOLEX_VERTIGO_HEATPUMP_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
+from ..mixins.binary_sensor import BasicBinarySensorTests
 from .base_device_tests import TuyaDeviceTestCase
 from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
@@ -17,7 +19,7 @@ PRESET_DPS = "4"
 ERROR_DPS = "9"
 ERROR_DPS = "9"
 
 
 
 
-class TestPoolexVertigoHeatpump(TuyaDeviceTestCase):
+class TestPoolexVertigoHeatpump(BasicBinarySensorTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
@@ -25,6 +27,12 @@ class TestPoolexVertigoHeatpump(TuyaDeviceTestCase):
             "poolex_vertigo_heatpump.yaml", POOLEX_VERTIGO_HEATPUMP_PAYLOAD
             "poolex_vertigo_heatpump.yaml", POOLEX_VERTIGO_HEATPUMP_PAYLOAD
         )
         )
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
+        self.setUpBasicBinarySensor(
+            ERROR_DPS,
+            self.entities.get("binary_sensor_water_flow"),
+            device_class=DEVICE_CLASS_PROBLEM,
+            testdata=(4, 0),
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 2 - 1
tests/devices/test_purline_m100_heater.py

@@ -17,7 +17,8 @@ from ..helpers import (
     assert_device_properties_set,
     assert_device_properties_set,
     assert_device_properties_set_optional,
     assert_device_properties_set_optional,
 )
 )
-from .base_device_tests import BasicSwitchTests, TuyaDeviceTestCase
+from ..mixins.switch import BasicSwitchTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
 TEMPERATURE_DPS = "2"
 TEMPERATURE_DPS = "2"

+ 9 - 1
tests/devices/test_remora_heatpump.py

@@ -1,3 +1,4 @@
+from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM
 from homeassistant.components.climate.const import (
 from homeassistant.components.climate.const import (
     HVAC_MODE_HEAT,
     HVAC_MODE_HEAT,
     HVAC_MODE_OFF,
     HVAC_MODE_OFF,
@@ -8,6 +9,7 @@ from homeassistant.const import STATE_UNAVAILABLE
 
 
 from ..const import REMORA_HEATPUMP_PAYLOAD
 from ..const import REMORA_HEATPUMP_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
+from ..mixins.binary_sensor import BasicBinarySensorTests
 from .base_device_tests import TuyaDeviceTestCase
 from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
@@ -17,12 +19,18 @@ PRESET_DPS = "4"
 ERROR_DPS = "9"
 ERROR_DPS = "9"
 
 
 
 
-class TestRemoraHeatpump(TuyaDeviceTestCase):
+class TestRemoraHeatpump(BasicBinarySensorTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("remora_heatpump.yaml", REMORA_HEATPUMP_PAYLOAD)
         self.setUpForConfig("remora_heatpump.yaml", REMORA_HEATPUMP_PAYLOAD)
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
+        self.setUpBasicBinarySensor(
+            ERROR_DPS,
+            self.entities.get("binary_sensor_water_flow"),
+            device_class=DEVICE_CLASS_PROBLEM,
+            testdata=(1, 0),
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 32 - 10
tests/devices/test_renpho_rp_ap001s.py

@@ -1,17 +1,13 @@
 from homeassistant.components.fan import SUPPORT_PRESET_MODE
 from homeassistant.components.fan import SUPPORT_PRESET_MODE
-from homeassistant.components.light import COLOR_MODE_ONOFF
-from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
-from homeassistant.const import STATE_UNAVAILABLE
+from homeassistant.const import DEVICE_CLASS_AQI
 
 
 from ..const import RENPHO_PURIFIER_PAYLOAD
 from ..const import RENPHO_PURIFIER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import (
-    BasicLightTests,
-    BasicLockTests,
-    BasicSwitchTests,
-    SwitchableTests,
-    TuyaDeviceTestCase,
-)
+from ..mixins.light import BasicLightTests
+from ..mixins.lock import BasicLockTests
+from ..mixins.sensor import MultiSensorTests
+from ..mixins.switch import BasicSwitchTests, SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 SWITCH_DPS = "1"
 SWITCH_DPS = "1"
 PRESET_DPS = "4"
 PRESET_DPS = "4"
@@ -30,6 +26,7 @@ class TestRenphoPurifier(
     BasicLightTests,
     BasicLightTests,
     BasicLockTests,
     BasicLockTests,
     BasicSwitchTests,
     BasicSwitchTests,
+    MultiSensorTests,
     SwitchableTests,
     SwitchableTests,
     TuyaDeviceTestCase,
     TuyaDeviceTestCase,
 ):
 ):
@@ -42,6 +39,31 @@ class TestRenphoPurifier(
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light_aq_indicator"))
         self.setUpBasicLight(LIGHT_DPS, self.entities.get("light_aq_indicator"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicSwitch(SLEEP_DPS, self.entities.get("switch_sleep"))
         self.setUpBasicSwitch(SLEEP_DPS, self.entities.get("switch_sleep"))
+        self.setUpMultiSensors(
+            [
+                {
+                    "name": "sensor_air_quality",
+                    "dps": QUALITY_DPS,
+                    "device_class": DEVICE_CLASS_AQI,
+                },
+                {
+                    "name": "sensor_prefilter_life",
+                    "dps": PREFILTER_DPS,
+                },
+                {
+                    "name": "sensor_charcoal_filter_life",
+                    "dps": CHARCOAL_DPS,
+                },
+                {
+                    "name": "sensor_active_filter_life",
+                    "dps": ACTIVATED_DPS,
+                },
+                {
+                    "name": "sensor_hepa_filter_life",
+                    "dps": HEPA_DPS,
+                },
+            ]
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(self.subject.supported_features, SUPPORT_PRESET_MODE)
         self.assertEqual(self.subject.supported_features, SUPPORT_PRESET_MODE)

+ 61 - 4
tests/devices/test_saswell_c16_thermostat.py

@@ -9,12 +9,16 @@ from homeassistant.components.climate.const import (
     SUPPORT_PRESET_MODE,
     SUPPORT_PRESET_MODE,
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
-from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
-from homeassistant.const import STATE_UNAVAILABLE
+from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS
 
 
 from ..const import SASWELL_C16_THERMOSTAT_PAYLOAD
 from ..const import SASWELL_C16_THERMOSTAT_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import BasicLockTests, TuyaDeviceTestCase
+from ..mixins.lock import BasicLockTests
+from ..mixins.number import MultiNumberTests
+from ..mixins.select import MultiSelectTests
+from ..mixins.sensor import BasicSensorTests
+from ..mixins.switch import BasicSwitchTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 TEMPERATURE_DPS = "2"
 TEMPERATURE_DPS = "2"
 PRESET_DPS = "3"
 PRESET_DPS = "3"
@@ -37,7 +41,14 @@ HVACACTION_DPS = "24"
 UNKNOWN26_DPS = "26"
 UNKNOWN26_DPS = "26"
 
 
 
 
-class TestSaswellC16Thermostat(BasicLockTests, TuyaDeviceTestCase):
+class TestSaswellC16Thermostat(
+    BasicLockTests,
+    BasicSensorTests,
+    BasicSwitchTests,
+    MultiNumberTests,
+    MultiSelectTests,
+    TuyaDeviceTestCase,
+):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
@@ -46,6 +57,52 @@ class TestSaswellC16Thermostat(BasicLockTests, TuyaDeviceTestCase):
         )
         )
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
+        self.setUpBasicSensor(
+            FLOORTEMP_DPS,
+            self.entities.get("sensor_floor_temperature"),
+            device_class=DEVICE_CLASS_TEMPERATURE,
+            state_class="measurement",
+            unit=TEMP_CELSIUS,
+            testdata=(218, 21.8),
+        )
+        self.setUpBasicSwitch(ADAPTIVE_DPS, self.entities.get("switch_adaptive"))
+        self.setUpMultiNumber(
+            [
+                {
+                    "name": "number_floor_temperature_limit",
+                    "dps": FLOORTEMPLIMIT_DPS,
+                    "min": 20.0,
+                    "max": 50.0,
+                    "scale": 10,
+                    "step": 0.5,
+                },
+                {
+                    "name": "number_power_rating",
+                    "dps": POWERRATING_DPS,
+                    "max": 3500,
+                },
+            ]
+        )
+        self.setUpMultiSelect(
+            [
+                {
+                    "name": "select_installation",
+                    "dps": INSTALL_DPS,
+                    "options": {
+                        True: "Office",
+                        False: "Home",
+                    },
+                },
+                {
+                    "name": "select_schedule",
+                    "dps": SCHED_DPS,
+                    "options": {
+                        "5_1_1": "Weekdays+Sat+Sun",
+                        "7": "Daily",
+                    },
+                },
+            ]
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 24 - 2
tests/devices/test_saswell_t29utk_thermostat.py

@@ -1,7 +1,6 @@
 from homeassistant.components.climate.const import (
 from homeassistant.components.climate.const import (
     CURRENT_HVAC_COOL,
     CURRENT_HVAC_COOL,
     CURRENT_HVAC_HEAT,
     CURRENT_HVAC_HEAT,
-    CURRENT_HVAC_IDLE,
     CURRENT_HVAC_OFF,
     CURRENT_HVAC_OFF,
     FAN_AUTO,
     FAN_AUTO,
     FAN_ON,
     FAN_ON,
@@ -18,6 +17,7 @@ from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
 
 
 from ..const import SASWELL_T29UTK_THERMOSTAT_PAYLOAD
 from ..const import SASWELL_T29UTK_THERMOSTAT_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
+from ..mixins.select import MultiSelectTests
 from .base_device_tests import TuyaDeviceTestCase
 from .base_device_tests import TuyaDeviceTestCase
 
 
 POWER_DPS = "1"
 POWER_DPS = "1"
@@ -37,7 +37,7 @@ TEMPF_DPS = "116"
 CURTEMPF_DPS = "117"
 CURTEMPF_DPS = "117"
 
 
 
 
-class TestSaswellT29UTKThermostat(TuyaDeviceTestCase):
+class TestSaswellT29UTKThermostat(MultiSelectTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
@@ -45,6 +45,28 @@ class TestSaswellT29UTKThermostat(TuyaDeviceTestCase):
             "saswell_t29utk_thermostat.yaml", SASWELL_T29UTK_THERMOSTAT_PAYLOAD
             "saswell_t29utk_thermostat.yaml", SASWELL_T29UTK_THERMOSTAT_PAYLOAD
         )
         )
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
+        self.setUpMultiSelect(
+            [
+                {
+                    "name": "select_temperature_unit",
+                    "dps": UNITS_DPS,
+                    "options": {
+                        "C": "Celsius",
+                        "F": "Fahrenheit",
+                    },
+                },
+                {
+                    "name": "select_configuration",
+                    "dps": CONFIG_DPS,
+                    "options": {
+                        "1": "cooling",
+                        "2": "heating",
+                        "3": "heat/cool",
+                        "5": "heatpump",
+                    },
+                },
+            ]
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 28 - 3
tests/devices/test_smartplugv1.py

@@ -1,9 +1,12 @@
 """Tests for the switch entity."""
 """Tests for the switch entity."""
 from homeassistant.components.switch import DEVICE_CLASS_OUTLET
 from homeassistant.components.switch import DEVICE_CLASS_OUTLET
+from homeassistant.const import DEVICE_CLASS_CURRENT, DEVICE_CLASS_VOLTAGE
 
 
 from ..const import KOGAN_SOCKET_PAYLOAD
 from ..const import KOGAN_SOCKET_PAYLOAD
-from ..helpers import assert_device_properties_set
-from .base_device_tests import SwitchableTests, TuyaDeviceTestCase
+from ..mixins.number import BasicNumberTests
+from ..mixins.sensor import MultiSensorTests
+from ..mixins.switch import SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 SWITCH_DPS = "1"
 SWITCH_DPS = "1"
 TIMER_DPS = "2"
 TIMER_DPS = "2"
@@ -12,13 +15,35 @@ POWER_DPS = "5"
 VOLTAGE_DPS = "6"
 VOLTAGE_DPS = "6"
 
 
 
 
-class TestKoganSwitch(SwitchableTests, TuyaDeviceTestCase):
+class TestKoganSwitch(
+    BasicNumberTests, MultiSensorTests, SwitchableTests, TuyaDeviceTestCase
+):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("smartplugv1.yaml", KOGAN_SOCKET_PAYLOAD)
         self.setUpForConfig("smartplugv1.yaml", KOGAN_SOCKET_PAYLOAD)
         self.subject = self.entities.get("switch")
         self.subject = self.entities.get("switch")
         self.setUpSwitchable(SWITCH_DPS, self.subject)
         self.setUpSwitchable(SWITCH_DPS, self.subject)
+        self.setUpBasicNumber(TIMER_DPS, self.entities.get("number_timer"), max=1440)
+        self.setUpMultiSensors(
+            [
+                {
+                    "name": "sensor_voltage",
+                    "dps": VOLTAGE_DPS,
+                    "unit": "V",
+                    "device_class": DEVICE_CLASS_VOLTAGE,
+                    "state_class": "measurement",
+                    "testdata": (2300, 230.0),
+                },
+                {
+                    "name": "sensor_current",
+                    "dps": CURRENT_DPS,
+                    "unit": "mA",
+                    "device_class": DEVICE_CLASS_CURRENT,
+                    "state_class": "measurement",
+                },
+            ]
+        )
 
 
     def test_device_class_is_outlet(self):
     def test_device_class_is_outlet(self):
         self.assertEqual(self.subject.device_class, DEVICE_CLASS_OUTLET)
         self.assertEqual(self.subject.device_class, DEVICE_CLASS_OUTLET)

+ 28 - 3
tests/devices/test_smartplugv2.py

@@ -1,9 +1,12 @@
 """Tests for the switch entity."""
 """Tests for the switch entity."""
 from homeassistant.components.switch import DEVICE_CLASS_OUTLET
 from homeassistant.components.switch import DEVICE_CLASS_OUTLET
+from homeassistant.const import DEVICE_CLASS_CURRENT, DEVICE_CLASS_VOLTAGE
 
 
 from ..const import KOGAN_SOCKET_PAYLOAD2
 from ..const import KOGAN_SOCKET_PAYLOAD2
-from ..helpers import assert_device_properties_set
-from .base_device_tests import SwitchableTests, TuyaDeviceTestCase
+from ..mixins.number import BasicNumberTests
+from ..mixins.sensor import MultiSensorTests
+from ..mixins.switch import SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 SWITCH_DPS = "1"
 SWITCH_DPS = "1"
 TIMER_DPS = "9"
 TIMER_DPS = "9"
@@ -12,13 +15,35 @@ POWER_DPS = "19"
 VOLTAGE_DPS = "20"
 VOLTAGE_DPS = "20"
 
 
 
 
-class TestSwitchV2(SwitchableTests, TuyaDeviceTestCase):
+class TestSwitchV2(
+    BasicNumberTests, MultiSensorTests, SwitchableTests, TuyaDeviceTestCase
+):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("smartplugv2.yaml", KOGAN_SOCKET_PAYLOAD2)
         self.setUpForConfig("smartplugv2.yaml", KOGAN_SOCKET_PAYLOAD2)
         self.subject = self.entities.get("switch")
         self.subject = self.entities.get("switch")
         self.setUpSwitchable(SWITCH_DPS, self.subject)
         self.setUpSwitchable(SWITCH_DPS, self.subject)
+        self.setUpBasicNumber(TIMER_DPS, self.entities.get("number_timer"), max=1440)
+        self.setUpMultiSensors(
+            [
+                {
+                    "name": "sensor_voltage",
+                    "dps": VOLTAGE_DPS,
+                    "unit": "V",
+                    "device_class": DEVICE_CLASS_VOLTAGE,
+                    "state_class": "measurement",
+                    "testdata": (2300, 230.0),
+                },
+                {
+                    "name": "sensor_current",
+                    "dps": CURRENT_DPS,
+                    "unit": "mA",
+                    "device_class": DEVICE_CLASS_CURRENT,
+                    "state_class": "measurement",
+                },
+            ]
+        )
 
 
     def test_device_class_is_outlet(self):
     def test_device_class_is_outlet(self):
         self.assertEqual(self.subject.device_class, DEVICE_CLASS_OUTLET)
         self.assertEqual(self.subject.device_class, DEVICE_CLASS_OUTLET)

+ 2 - 1
tests/devices/test_stirling_fs140dc_fan.py

@@ -6,7 +6,8 @@ from homeassistant.components.fan import (
 
 
 from ..const import STIRLING_FS1_FAN_PAYLOAD
 from ..const import STIRLING_FS1_FAN_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
-from .base_device_tests import SwitchableTests, TuyaDeviceTestCase
+from ..mixins.switch import SwitchableTests
+from .base_device_tests import TuyaDeviceTestCase
 
 
 SWITCH_DPS = "1"
 SWITCH_DPS = "1"
 PRESET_DPS = "2"
 PRESET_DPS = "2"

+ 38 - 2
tests/devices/test_wetair_wch750_heater.py

@@ -8,10 +8,12 @@ from homeassistant.components.climate.const import (
     SUPPORT_TARGET_TEMPERATURE,
     SUPPORT_TARGET_TEMPERATURE,
 )
 )
 from homeassistant.components.light import COLOR_MODE_BRIGHTNESS
 from homeassistant.components.light import COLOR_MODE_BRIGHTNESS
-from homeassistant.const import STATE_UNAVAILABLE
+from homeassistant.const import STATE_UNAVAILABLE, TIME_MINUTES
 
 
 from ..const import WETAIR_WCH750_HEATER_PAYLOAD
 from ..const import WETAIR_WCH750_HEATER_PAYLOAD
 from ..helpers import assert_device_properties_set
 from ..helpers import assert_device_properties_set
+from ..mixins.select import BasicSelectTests
+from ..mixins.sensor import BasicSensorTests
 from .base_device_tests import TuyaDeviceTestCase
 from .base_device_tests import TuyaDeviceTestCase
 
 
 HVACMODE_DPS = "1"
 HVACMODE_DPS = "1"
@@ -24,13 +26,47 @@ UNKNOWN21_DPS = "21"
 BRIGHTNESS_DPS = "101"
 BRIGHTNESS_DPS = "101"
 
 
 
 
-class TestWetairWCH750Heater(TuyaDeviceTestCase):
+class TestWetairWCH750Heater(BasicSelectTests, BasicSensorTests, TuyaDeviceTestCase):
     __test__ = True
     __test__ = True
 
 
     def setUp(self):
     def setUp(self):
         self.setUpForConfig("wetair_wch750_heater.yaml", WETAIR_WCH750_HEATER_PAYLOAD)
         self.setUpForConfig("wetair_wch750_heater.yaml", WETAIR_WCH750_HEATER_PAYLOAD)
         self.subject = self.entities.get("climate")
         self.subject = self.entities.get("climate")
         self.light = self.entities.get("light_display")
         self.light = self.entities.get("light_display")
+        self.setUpBasicSelect(
+            TIMER_DPS,
+            self.entities.get("select_timer"),
+            {
+                "0h": "Off",
+                "1h": "1 hour",
+                "2h": "2 hours",
+                "3h": "3 hours",
+                "4h": "4 hours",
+                "5h": "5 hours",
+                "6h": "6 hours",
+                "7h": "7 hours",
+                "8h": "8 hours",
+                "9h": "9 hours",
+                "10h": "10 hours",
+                "11h": "11 hours",
+                "12h": "12 hours",
+                "13h": "13 hours",
+                "14h": "14 hours",
+                "15h": "15 hours",
+                "16h": "16 hours",
+                "17h": "17 hours",
+                "18h": "18 hours",
+                "19h": "19 hours",
+                "20h": "20 hours",
+                "21h": "21 hours",
+                "22h": "22 hours",
+                "23h": "23 hours",
+                "24h": "24 hours",
+            },
+        )
+        self.setUpBasicSensor(
+            COUNTDOWN_DPS, self.entities.get("sensor_timer"), unit=TIME_MINUTES
+        )
 
 
     def test_supported_features(self):
     def test_supported_features(self):
         self.assertEqual(
         self.assertEqual(

+ 65 - 0
tests/mixins/binary_sensor.py

@@ -0,0 +1,65 @@
+# Mixins for testing sensor entities
+
+
+class BasicBinarySensorTests:
+    def setUpBasicBinarySensor(
+        self,
+        dps,
+        subject,
+        device_class=None,
+        testdata=(True, False),
+    ):
+        self.basicBSensor = subject
+        self.basicBSensorDps = dps
+        self.basicBSensorDeviceClass = device_class
+        self.basicBSensorTestData = testdata
+
+    def test_basic_bsensor_device_class(self):
+        self.assertEqual(self.basicBSensor.device_class, self.basicBSensorDeviceClass)
+
+    def test_basic_bsensor_is_on(self):
+        onval, offval = self.basicBSensorTestData
+        self.dps[self.basicBSensorDps] = onval
+        self.assertTrue(self.basicBSensor.is_on)
+        self.dps[self.basicBSensorDps] = offval
+        self.assertFalse(self.basicBSensor.is_on)
+
+    def test_basic_bsensor_device_state_attributes(self):
+        self.assertEqual(self.basicBSensor.device_state_attributes, {})
+
+
+class MultiBinarySensorTests:
+    def setUpMultiBinarySensors(self, sensors):
+        self.multiBSensor = {}
+        self.multiBSensorDps = {}
+        self.multiBSensorDevClass = {}
+        self.multiBSensorTestData = {}
+        for s in sensors:
+            name = s.get("name")
+            subject = self.entities.get(name)
+            if subject is None:
+                raise AttributeError(f"No binary sensor for {name} found.")
+            self.multiBSensor[name] = subject
+            self.multiBSensorDps[name] = s.get("dps")
+            self.multiBSensorDevClass[name] = s.get("device_class")
+            self.multiBSensorTestData[name] = s.get("testdata", (True, False))
+
+    def test_multi_bsensor_device_class(self):
+        for key, subject in self.multiBSensor.items():
+            with self.subTest(key):
+                self.assertEqual(subject.device_class, self.multiBSensorDevClass[key])
+
+    def test_multi_bsensor_is_on(self):
+        for key, subject in self.multiBSensor.items():
+            with self.subTest(key):
+                dps = self.multiBSensorDps[key]
+                onval, offval = self.multiBSensorTestData[key]
+                self.dps[dps] = onval
+                self.assertTrue(subject.is_on)
+                self.dps[dps] = offval
+                self.assertFalse(subject.is_on)
+
+    def test_multi_bsensor_device_state_attributes(self):
+        for key, subject in self.multiBSensor.items():
+            with self.subTest(key):
+                self.assertEqual(subject.device_state_attributes, {})

+ 66 - 0
tests/mixins/light.py

@@ -0,0 +1,66 @@
+# Mixins for testing lights
+from homeassistant.components.light import COLOR_MODE_ONOFF
+
+from ..helpers import assert_device_properties_set
+
+
+class BasicLightTests:
+    def setUpBasicLight(self, dps, subject):
+        self.basicLight = subject
+        self.basicLightDps = dps
+
+    def test_basic_light_supported_features(self):
+        self.assertEqual(self.basicLight.supported_features, 0)
+
+    def test_basic_light_supported_color_modes(self):
+        self.assertCountEqual(
+            self.basicLight.supported_color_modes,
+            [COLOR_MODE_ONOFF],
+        )
+
+    def test_basic_light_color_mode(self):
+        self.assertEqual(self.basicLight.color_mode, COLOR_MODE_ONOFF)
+
+    def test_light_has_no_brightness(self):
+        self.assertIsNone(self.basicLight.brightness)
+
+    def test_light_has_no_effects(self):
+        self.assertIsNone(self.basicLight.effect_list)
+        self.assertIsNone(self.basicLight.effect)
+
+    def test_basic_light_is_on(self):
+        self.dps[self.basicLightDps] = True
+        self.assertTrue(self.basicLight.is_on)
+        self.dps[self.basicLightDps] = False
+        self.assertFalse(self.basicLight.is_on)
+
+    async def test_basic_light_turn_on(self):
+        async with assert_device_properties_set(
+            self.basicLight._device, {self.basicLightDps: True}
+        ):
+            await self.basicLight.async_turn_on()
+
+    async def test_basic_light_turn_off(self):
+        async with assert_device_properties_set(
+            self.basicLight._device, {self.basicLightDps: False}
+        ):
+            await self.basicLight.async_turn_off()
+
+    async def test_basic_light_toggle_turns_on_when_it_was_off(self):
+        self.dps[self.basicLightDps] = False
+        async with assert_device_properties_set(
+            self.basicLight._device,
+            {self.basicLightDps: True},
+        ):
+            await self.basicLight.async_toggle()
+
+    async def test_basic_light_toggle_turns_off_when_it_was_on(self):
+        self.dps[self.basicLightDps] = True
+        async with assert_device_properties_set(
+            self.basicLight._device,
+            {self.basicLightDps: False},
+        ):
+            await self.basicLight.async_toggle()
+
+    def test_basic_light_state_attributes(self):
+        self.assertEqual(self.basicLight.device_state_attributes, {})

+ 48 - 0
tests/mixins/lock.py

@@ -0,0 +1,48 @@
+# Mixins for testing locks
+from homeassistant.components.lock import STATE_LOCKED, STATE_UNLOCKED
+from homeassistant.const import STATE_UNAVAILABLE
+
+from ..helpers import assert_device_properties_set
+
+
+class BasicLockTests:
+    def setUpBasicLock(self, dps, subject):
+        self.basicLock = subject
+        self.basicLockDps = dps
+
+    def test_basic_lock_state(self):
+        self.dps[self.basicLockDps] = True
+        self.assertEqual(self.basicLock.state, STATE_LOCKED)
+
+        self.dps[self.basicLockDps] = False
+        self.assertEqual(self.basicLock.state, STATE_UNLOCKED)
+
+        self.dps[self.basicLockDps] = None
+        self.assertEqual(self.basicLock.state, STATE_UNAVAILABLE)
+
+    def test_basic_lock_is_locked(self):
+        self.dps[self.basicLockDps] = True
+        self.assertTrue(self.basicLock.is_locked)
+
+        self.dps[self.basicLockDps] = False
+        self.assertFalse(self.basicLock.is_locked)
+
+        self.dps[self.basicLockDps] = None
+        self.assertFalse(self.basicLock.is_locked)
+
+    async def test_basic_lock_locks(self):
+        async with assert_device_properties_set(
+            self.basicLock._device,
+            {self.basicLockDps: True},
+        ):
+            await self.basicLock.async_lock()
+
+    async def test_basic_lock_unlocks(self):
+        async with assert_device_properties_set(
+            self.basicLock._device,
+            {self.basicLockDps: False},
+        ):
+            await self.basicLock.async_unlock()
+
+    def test_basic_lock_state_attributes(self):
+        self.assertEqual(self.basicLock.device_state_attributes, {})

+ 115 - 0
tests/mixins/number.py

@@ -0,0 +1,115 @@
+# Mixins for testing number entities
+from ..helpers import assert_device_properties_set
+
+
+class BasicNumberTests:
+    def setUpBasicNumber(self, dps, subject, max, min=0, step=1, mode="auto", scale=1):
+        self.basicNumber = subject
+        self.basicNumberDps = dps
+        self.basicNumberMin = min
+        self.basicNumberMax = max
+        self.basicNumberStep = step
+        self.basicNumberMode = mode
+        self.basicNumberScale = scale
+
+    def test_number_min_value(self):
+        self.assertEqual(self.basicNumber.min_value, self.basicNumberMin)
+
+    def test_number_max_value(self):
+        self.assertEqual(self.basicNumber.max_value, self.basicNumberMax)
+
+    def test_number_step(self):
+        self.assertEqual(self.basicNumber.step, self.basicNumberStep)
+
+    def test_number_mode(self):
+        self.assertEqual(self.basicNumber.mode, self.basicNumberMode)
+
+    def test_number_value(self):
+        val = min(max(self.basicNumberMin, self.basicNumberStep), self.basicNumberMax)
+        dps_val = val * self.basicNumberScale
+        self.dps[self.basicNumberDps] = dps_val
+        self.assertEqual(self.basicNumber.value, val)
+
+    async def test_number_set_value(self):
+        val = min(max(self.basicNumberMin, self.basicNumberStep), self.basicNumberMax)
+        dps_val = val * self.basicNumberScale
+        async with assert_device_properties_set(
+            self.basicNumber._device, {self.basicNumberDps: dps_val}
+        ):
+            await self.basicNumber.async_set_value(val)
+
+    def test_number_device_state_attributes(self):
+        self.assertEqual(self.basicNumber.device_state_attributes, {})
+
+
+class MultiNumberTests:
+    def setUpMultiNumber(self, numbers):
+        self.multiNumber = {}
+        self.multiNumberDps = {}
+        self.multiNumberMin = {}
+        self.multiNumberMax = {}
+        self.multiNumberStep = {}
+        self.multiNumberMode = {}
+        self.multiNumberScale = {}
+
+        for n in numbers:
+            name = n.get("name")
+            subject = self.entities.get(name)
+            if subject is None:
+                raise AttributeError(f"No number for {name} found.")
+            self.multiNumber[name] = subject
+            self.multiNumberDps[name] = n.get("dps")
+            self.multiNumberMin[name] = n.get("min", 0)
+            self.multiNumberMax[name] = n.get("max")
+            self.multiNumberStep[name] = n.get("step", 1)
+            self.multiNumberMode[name] = n.get("mode", "auto")
+            self.multiNumberScale[name] = n.get("scale", 1)
+
+    def test_multi_number_min_value(self):
+        for key, subject in self.multiNumber.items():
+            with self.subTest(key):
+                self.assertEqual(subject.min_value, self.multiNumberMin[key])
+
+    def test_multi_number_max_value(self):
+        for key, subject in self.multiNumber.items():
+            with self.subTest(key):
+                self.assertEqual(subject.max_value, self.multiNumberMax[key])
+
+    def test_multi_number_step(self):
+        for key, subject in self.multiNumber.items():
+            with self.subTest(key):
+                self.assertEqual(subject.step, self.multiNumberStep[key])
+
+    def test_multi_number_mode(self):
+        for key, subject in self.multiNumber.items():
+            with self.subTest(key):
+                self.assertEqual(subject.mode, self.multiNumberMode[key])
+
+    def test_multi_number_value(self):
+        for key, subject in self.multiNumber.items():
+            with self.subTest(key):
+                val = min(
+                    max(self.multiNumberMin[key], self.multiNumberStep[key]),
+                    self.multiNumberMax[key],
+                )
+                dps_val = val * self.multiNumberScale[key]
+                self.dps[self.multiNumberDps[key]] = dps_val
+                self.assertEqual(subject.value, val)
+
+    async def test_multi_number_set_value(self):
+        for key, subject in self.multiNumber.items():
+            with self.subTest(key):
+                val = min(
+                    max(self.multiNumberMin[key], self.multiNumberStep[key]),
+                    self.multiNumberMax[key],
+                )
+                dps_val = val * self.multiNumberScale[key]
+                async with assert_device_properties_set(
+                    subject._device, {self.multiNumberDps[key]: dps_val}
+                ):
+                    await subject.async_set_value(val)
+
+    def test_multi_number_device_state_attributes(self):
+        for key, subject in self.multiNumber.items():
+            with self.subTest(key):
+                self.assertEqual(subject.device_state_attributes, {})

+ 74 - 0
tests/mixins/select.py

@@ -0,0 +1,74 @@
+# Mixins for testing select entities
+from ..helpers import assert_device_properties_set
+
+
+class BasicSelectTests:
+    def setUpBasicSelect(self, dps, subject, options):
+        self.basicSelect = subject
+        self.basicSelectDps = dps
+        self.basicSelectOptions = options
+
+    def test_basicSelect_options(self):
+        self.assertCountEqual(
+            self.basicSelect.options,
+            self.basicSelectOptions.values(),
+        )
+
+    def test_basicSelect_current_option(self):
+        for dpsVal, val in self.basicSelectOptions.items():
+            self.dps[self.basicSelectDps] = dpsVal
+            self.assertEqual(self.basicSelect.current_option, val)
+
+    async def test_basicSelect_select_option(self):
+        for dpsVal, val in self.basicSelectOptions.items():
+            async with assert_device_properties_set(
+                self.basicSelect._device, {self.basicSelectDps: dpsVal}
+            ):
+                await self.basicSelect.async_select_option(val)
+
+    def test_basicSelect_device_state_attributes(self):
+        self.assertEqual(self.basicSelect.device_state_attributes, {})
+
+
+class MultiSelectTests:
+    def setUpMultiSelect(self, selects):
+        self.multiSelect = {}
+        self.multiSelectDps = {}
+        self.multiSelectOptions = {}
+        for s in selects:
+            name = s.get("name")
+            subject = self.entities.get(name)
+            if subject is None:
+                raise AttributeError(f"No select for {name} found.")
+            self.multiSelect[name] = subject
+            self.multiSelectDps[name] = s.get("dps")
+            self.multiSelectOptions[name] = s.get("options")
+
+    def test_multiSelect_options(self):
+        for key, subject in self.multiSelect.items():
+            with self.subTest(key):
+                self.assertCountEqual(
+                    subject.options,
+                    self.multiSelectOptions[key].values(),
+                )
+
+    def test_multiSelect_current_option(self):
+        for key, subject in self.multiSelect.items():
+            with self.subTest(key):
+                for dpsVal, val in self.multiSelectOptions[key].items():
+                    self.dps[self.multiSelectDps[key]] = dpsVal
+                    self.assertEqual(subject.current_option, val)
+
+    async def test_multiSelect_select_option(self):
+        for key, subject in self.multiSelect.items():
+            with self.subTest(key):
+                for dpsVal, val in self.multiSelectOptions[key].items():
+                    async with assert_device_properties_set(
+                        subject._device, {self.multiSelectDps[key]: dpsVal}
+                    ):
+                        await subject.async_select_option(val)
+
+    def test_multiSelect_device_state_attributes(self):
+        for key, subject in self.multiSelect.items():
+            with self.subTest(key):
+                self.assertEqual(subject.device_state_attributes, {})

+ 85 - 0
tests/mixins/sensor.py

@@ -0,0 +1,85 @@
+# Mixins for testing sensor entities
+
+
+class BasicSensorTests:
+    def setUpBasicSensor(
+        self,
+        dps,
+        subject,
+        unit=None,
+        state_class=None,
+        device_class=None,
+        testdata=(30, 30),
+    ):
+        self.basicSensor = subject
+        self.basicSensorDps = dps
+        self.basicSensorUnit = unit
+        self.basicSensorStateClass = state_class
+        self.basicSensorDeviceClass = device_class
+        self.basicSensorTestData = testdata
+
+    def test_basic_sensor_units(self):
+        self.assertEqual(
+            self.basicSensor.native_unit_of_measurement, self.basicSensorUnit
+        )
+
+    def test_basic_sensor_device_class(self):
+        self.assertEqual(self.basicSensor.device_class, self.basicSensorDeviceClass)
+
+    def test_basic_sensor_state_class(self):
+        self.assertEqual(self.basicSensor.state_class, self.basicSensorStateClass)
+
+    def test_basic_sensor_value(self):
+        dpval, val = self.basicSensorTestData
+        self.dps[self.basicSensorDps] = dpval
+        self.assertEqual(self.basicSensor.native_value, val)
+
+
+class MultiSensorTests:
+    def setUpMultiSensors(self, sensors):
+        self.multiSensor = {}
+        self.multiSensorDps = {}
+        self.multiSensorUnit = {}
+        self.multiSensorDevClass = {}
+        self.multiSensorStateClass = {}
+        self.multiSensorTestData = {}
+        for s in sensors:
+            name = s.get("name")
+            subject = self.entities.get(name)
+            if subject is None:
+                raise AttributeError(f"No sensor for {name} found.")
+            self.multiSensor[name] = subject
+            self.multiSensorDps[name] = s.get("dps")
+            self.multiSensorUnit[name] = s.get("unit")
+            self.multiSensorStateClass[name] = s.get("state_class")
+            self.multiSensorDevClass[name] = s.get("device_class")
+            self.multiSensorTestData[name] = s.get("testdata", (30, 30))
+
+    def test_multi_sensor_units(self):
+        for key, subject in self.multiSensor.items():
+            with self.subTest(key):
+                self.assertEqual(
+                    subject.native_unit_of_measurement, self.multiSensorUnit[key]
+                )
+
+    def test_multi_sensor_device_class(self):
+        for key, subject in self.multiSensor.items():
+            with self.subTest(key):
+                self.assertEqual(subject.device_class, self.multiSensorDevClass[key])
+
+    def test_multi_sensor_state_class(self):
+        for key, subject in self.multiSensor.items():
+            with self.subTest(key):
+                self.assertEqual(subject.state_class, self.multiSensorStateClass[key])
+
+    def test_multi_sensor_value(self):
+        for key, subject in self.multiSensor.items():
+            with self.subTest(key):
+                dpsval, val = self.multiSensorTestData[key]
+                self.dps[self.multiSensorDps[key]] = dpsval
+                self.assertEqual(subject.native_value, val)
+
+    def test_multi_sensor_device_state_attributes(self):
+        for key, subject in self.multiSensor.items():
+            with self.subTest(key):
+                self.assertEqual(subject.device_state_attributes, {})

+ 198 - 0
tests/mixins/switch.py

@@ -0,0 +1,198 @@
+# Mixins for testing switches
+from homeassistant.components.switch import DEVICE_CLASS_SWITCH
+
+from ..helpers import assert_device_properties_set
+
+
+class SwitchableTests:
+    def setUpSwitchable(self, dps, subject):
+        self.switch_dps = dps
+        self.switch_subject = subject
+
+    def test_switchable_is_on(self):
+        self.dps[self.switch_dps] = True
+        self.assertTrue(self.switch_subject.is_on)
+
+        self.dps[self.switch_dps] = False
+        self.assertFalse(self.switch_subject.is_on)
+
+        self.dps[self.switch_dps] = None
+        self.assertIsNone(self.switch_subject.is_on)
+
+    async def test_switchable_turn_on(self):
+        async with assert_device_properties_set(
+            self.switch_subject._device, {self.switch_dps: True}
+        ):
+            await self.switch_subject.async_turn_on()
+
+    async def test_switchable_turn_off(self):
+        async with assert_device_properties_set(
+            self.switch_subject._device, {self.switch_dps: False}
+        ):
+            await self.switch_subject.async_turn_off()
+
+    async def test_switchable_toggle(self):
+        self.dps[self.switch_dps] = False
+        async with assert_device_properties_set(
+            self.switch_subject._device, {self.switch_dps: True}
+        ):
+            await self.switch_subject.async_toggle()
+
+        self.dps[self.switch_dps] = True
+        async with assert_device_properties_set(
+            self.switch_subject._device, {self.switch_dps: False}
+        ):
+            await self.switch_subject.async_toggle()
+
+
+class BasicSwitchTests:
+    def setUpBasicSwitch(
+        self,
+        dps,
+        subject,
+        device_class=DEVICE_CLASS_SWITCH,
+        power_dps=None,
+        power_scale=1,
+    ):
+        self.basicSwitch = subject
+        self.basicSwitchDps = dps
+        self.basicSwitchDevClass = device_class
+        self.basicSwitchPowerDps = power_dps
+        self.basicSwitchPowerScale = power_scale
+
+    def test_basic_switch_is_on(self):
+        self.dps[self.basicSwitchDps] = True
+        self.assertEqual(self.basicSwitch.is_on, True)
+
+        self.dps[self.basicSwitchDps] = False
+        self.assertEqual(self.basicSwitch.is_on, False)
+
+    async def test_basic_switch_turn_on(self):
+        async with assert_device_properties_set(
+            self.basicSwitch._device, {self.basicSwitchDps: True}
+        ):
+            await self.basicSwitch.async_turn_on()
+
+    async def test_basic_switch_turn_off(self):
+        async with assert_device_properties_set(
+            self.basicSwitch._device, {self.basicSwitchDps: False}
+        ):
+            await self.basicSwitch.async_turn_off()
+
+    async def test_basic_switch_toggle_turns_on_when_it_was_off(self):
+        self.dps[self.basicSwitchDps] = False
+
+        async with assert_device_properties_set(
+            self.basicSwitch._device, {self.basicSwitchDps: True}
+        ):
+            await self.basicSwitch.async_toggle()
+
+    async def test_basic_switch_toggle_turns_off_when_it_was_on(self):
+        self.dps[self.basicSwitchDps] = True
+
+        async with assert_device_properties_set(
+            self.basicSwitch._device, {self.basicSwitchDps: False}
+        ):
+            await self.basicSwitch.async_toggle()
+
+    def test_basic_switch_class_device_class(self):
+        self.assertEqual(self.basicSwitch.device_class, self.basicSwitchDevClass)
+
+    def test_basic_switch_current_power_w(self):
+        if self.basicSwitchPowerDps is None:
+            self.assertIsNone(self.basicSwitch.current_power_w)
+        else:
+            self.dps[self.basicSwitchPowerDps] = 123
+            self.assertEqual(
+                self.basicSwitch.current_power_w, 123 / self.basicSwitchPowerScale
+            )
+
+    def test_basic_switch_state_attributes(self):
+        self.assertEqual(self.basicSwitch.device_state_attributes, {})
+
+
+class MultiSwitchTests:
+    def setUpMultiSwitch(self, switches):
+        self.multiSwitch = {}
+        self.multiSwitchDps = {}
+        self.multiSwitchDevClass = {}
+        self.multiSwitchPowerDps = {}
+        self.multiSwitchPowerScale = {}
+
+        for s in switches:
+            name = s.get("name")
+            subject = self.entities.get(name)
+            if subject is None:
+                raise AttributeError(f"No switch for {name} found.")
+            self.multiSwitch[name] = subject
+            self.multiSwitchDps[name] = s.get("dps")
+            self.multiSwitchDevClass[name] = s.get("device_class", DEVICE_CLASS_SWITCH)
+            self.multiSwitchPowerDps[name] = s.get("power_dps")
+            self.multiSwitchPowerScale[name] = s.get("power_scale", 1)
+
+    def test_multi_switch_is_on(self):
+        for key, subject in self.multiSwitch.items():
+            with self.subTest(key):
+                dp = self.multiSwitchDps[key]
+                self.dps[dp] = True
+                self.assertEqual(subject.is_on, True)
+
+                self.dps[dp] = False
+                self.assertEqual(subject.is_on, False)
+
+    async def test_multi_switch_turn_on(self):
+        for key, subject in self.multiSwitch.items():
+            with self.subTest(key):
+                async with assert_device_properties_set(
+                    subject._device, {self.multiSwitchDps[key]: True}
+                ):
+                    await subject.async_turn_on()
+
+    async def test_multi_switch_turn_off(self):
+        for key, subject in self.multiSwitch.items():
+            with self.subTest(key):
+                async with assert_device_properties_set(
+                    subject._device, {self.multiSwitchDps[key]: False}
+                ):
+                    await subject.async_turn_off()
+
+    async def test_multi_switch_toggle_turns_on_when_it_was_off(self):
+        for key, subject in self.multiSwitch.items():
+            with self.subTest(key):
+                dp = self.multiSwitchDps[key]
+                self.dps[dp] = False
+
+                async with assert_device_properties_set(subject._device, {dp: True}):
+                    await subject.async_toggle()
+
+    async def test_multi_switch_toggle_turns_off_when_it_was_on(self):
+        for key, subject in self.multiSwitch.items():
+            with self.subTest(key):
+                dp = self.multiSwitchDps[key]
+                self.dps[dp] = True
+
+                async with assert_device_properties_set(subject._device, {dp: False}):
+                    await subject.async_toggle()
+
+    def test_multi_switch_device_class(self):
+        for key, subject in self.multiSwitch.items():
+            with self.subTest(key):
+                self.assertEqual(subject.device_class, self.multiSwitchDevClass[key])
+
+    def test_multi_switch_current_power_w(self):
+        for key, subject in self.multiSwitch.items():
+            with self.subTest(key):
+                dp = self.multiSwitchPowerDps.get(key)
+                if dp is None:
+                    self.assertIsNone(subject.current_power_w)
+                else:
+                    self.dps[dp] = 1234
+                    self.assertEqual(
+                        subject.current_power_w,
+                        1234 / self.multiSwitchPowerScale.get(key, 1),
+                    )
+
+    def test_multi_switch_state_attributes(self):
+        for key, subject in self.multiSwitch.items():
+            with self.subTest(key):
+                self.assertEqual(subject.device_state_attributes, {})

+ 5 - 0
tests/test_config_flow.py

@@ -432,6 +432,7 @@ async def test_flow_choose_entities_creates_config_entry(hass, bypass_setup):
                 CONF_NAME: "test",
                 CONF_NAME: "test",
                 CONF_CLIMATE: True,
                 CONF_CLIMATE: True,
                 "lock_child_lock": False,
                 "lock_child_lock": False,
+                "number_timer": False,
             },
             },
         )
         )
         expected = {
         expected = {
@@ -450,6 +451,7 @@ async def test_flow_choose_entities_creates_config_entry(hass, bypass_setup):
                 CONF_HOST: "hostname",
                 CONF_HOST: "hostname",
                 CONF_LOCAL_KEY: "localkey",
                 CONF_LOCAL_KEY: "localkey",
                 "lock_child_lock": False,
                 "lock_child_lock": False,
+                "number_timer": False,
                 CONF_TYPE: "kogan_kahtp_heater",
                 CONF_TYPE: "kogan_kahtp_heater",
             },
             },
         }
         }
@@ -505,6 +507,7 @@ async def test_options_flow_modifies_config(mock_test, hass):
             CONF_HOST: "hostname",
             CONF_HOST: "hostname",
             CONF_LOCAL_KEY: "localkey",
             CONF_LOCAL_KEY: "localkey",
             "lock_child_lock": True,
             "lock_child_lock": True,
+            "number_timer": False,
             CONF_NAME: "test",
             CONF_NAME: "test",
             CONF_TYPE: "kogan_kahtp_heater",
             CONF_TYPE: "kogan_kahtp_heater",
         },
         },
@@ -523,6 +526,7 @@ async def test_options_flow_modifies_config(mock_test, hass):
             CONF_HOST: "new_hostname",
             CONF_HOST: "new_hostname",
             CONF_LOCAL_KEY: "new_key",
             CONF_LOCAL_KEY: "new_key",
             "lock_child_lock": False,
             "lock_child_lock": False,
+            "number_timer": True,
         },
         },
     )
     )
     expected = {
     expected = {
@@ -530,6 +534,7 @@ async def test_options_flow_modifies_config(mock_test, hass):
         CONF_HOST: "new_hostname",
         CONF_HOST: "new_hostname",
         CONF_LOCAL_KEY: "new_key",
         CONF_LOCAL_KEY: "new_key",
         "lock_child_lock": False,
         "lock_child_lock": False,
+        "number_timer": True,
     }
     }
     assert "create_entry" == result["type"]
     assert "create_entry" == result["type"]
     assert "" == result["title"]
     assert "" == result["title"]