Просмотр исходного кода

Add new files for Minco Thermostat, cleanup, switch enhancement.

- Forgot to commit new files, previous commit only included changes to existing.
- Cleanup unused imports in top level platforms.
- Split out power into separate sensor for switches, so it can be an integration source for Energy dashboard.
- Add more entities for Grid Connect Double Outiet with USB, based on matching information from https://developer.tuya.com/en/docs/iot/product-standard-function-introduction?id=K9tp15ceh63gr
Jason Rumney 4 лет назад
Родитель
Сommit
23e26cd325

+ 0 - 1
custom_components/tuya_local/climate.py

@@ -5,7 +5,6 @@ import logging
 
 from . import DOMAIN
 from .const import (
-    CONF_CLIMATE,
     CONF_DEVICE_ID,
     CONF_TYPE,
 )

+ 83 - 13
custom_components/tuya_local/devices/grid_connect_usb_double_power_point.yaml

@@ -11,10 +11,6 @@ primary_entity:
   name: Master
   class: outlet
   dps:
-    - id: 17
-      name: unknown_17
-      type: integer
-      readonly: true
     - id: 18
       type: integer
       name: current_a
@@ -34,29 +30,25 @@ primary_entity:
       mapping:
         - scale: 10
     - id: 21
-      name: unknown_21
+      name: test_bit
       type: integer
       readonly: true
     - id: 22
-      name: unknown_22
+      name: voltage_calibration
       type: integer
       readonly: true
     - id: 23
-      name: unknown_23
+      name: current_calibration
       type: integer
       readonly: true
     - id: 24
-      name: unknown_24
+      name: power_calibration
       type: integer
       readonly: true
     - id: 25
-      name: unknown_25
+      name: energy_calibration
       type: integer
       readonly: true
-    - id: 38
-      name: unknown_38
-      type: string
-      readonly: true
     - id: 101
       name: switch
       type: boolean
@@ -111,3 +103,81 @@ secondary_entities:
         name: master
         type: boolean
         hidden: true
+  - entity: sensor
+    class: energy
+    category: diagnostic
+    name: Energy
+    dps:
+      - id: 17
+        name: sensor
+        type: integer
+        class: total_increasing
+        unit: Wh
+  - entity: sensor
+    class: current
+    category: diagnostic
+    name: Current
+    dps:
+      - id: 18
+        name: sensor
+        type: integer
+        class: measurement
+        unit: mA
+  - entity: sensor
+    class: power
+    category: diagnostic
+    name: Power
+    dps:
+      - id: 19
+        name: sensor
+        type: integer
+        class: measurement
+        unit: W
+        mapping:
+          - scale: 10
+  - entity: sensor
+    class: voltage
+    category: diagnostic
+    name: Voltage
+    dps:
+      - id: 20
+        name: sensor
+        type: integer
+        class: measurement
+        unit: V
+        mapping:
+          - scale: 10
+  - entity: select
+    category: config
+    name: Initial State
+    dps:
+      - id: 38
+        name: option
+        type: string
+        mapping:
+          - dps_val: "on"
+            value: "On"
+          - dps_val: "off"
+            value: "Off"
+          - dps_val: "memory"
+            value: "Last State"
+  - entity: number
+    name: Timer 1
+    category: config
+    dps:
+      - id: 9
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 86400
+  - entity: number
+    name: Timer 2
+    category: config
+    dps:
+      - id: 10
+        type: integer
+        name: value
+        range:
+          min: 0
+          max: 86400

+ 257 - 0
custom_components/tuya_local/devices/minco_mh1823d_thermostat.yaml

@@ -0,0 +1,257 @@
+name: Minco MH-1823D Thermostat
+primary_entity:
+  entity: climate
+  dps:
+    - id: 1
+      type: boolean
+      name: hvac_mode
+      mapping:
+        - dps_val: false
+          value: "off"
+        - dps_val: true
+          value: heat
+    - id: 2
+      type: string
+      name: unknown_2
+    - id: 3
+      type: string
+      name: hvac_action
+      mapping:
+        - dps_val: stop
+          icon: "mdi:thermometer-off"
+          constraint: hvac_mode
+          conditions:
+            - dps_val: false
+              value: "off"
+              icon_priority: 1
+            - dps_val: true
+              value: idle
+              icon_priority: 3
+        - dps_val: start
+          constraint: hvac_mode
+          conditions:
+            - dps_val: false
+              value: "off"
+              icon: "mdi:thermometer-off"
+              icon_priority: 1
+            - dps_val: true
+              value: heating
+              icon: "mdi:thermometer"
+              icon_priority: 3
+    - id: 5
+      type: boolean
+      name: unknown_5
+    - id: 9
+      type: boolean
+      name: preset_mode
+      mapping:
+        - dps_val: true
+          value: away
+          icon: "mdi:snowflake"
+          icon_priority: 2
+        - dps_val: false
+          value: home
+    - id: 12
+      type: boolean
+      name: unknown_12
+    - id: 19
+      type: string
+      name: temperature_unit
+      mapping:
+        - dps_val: c
+          value: C
+        - dps_val: f
+          value: F
+    - id: 22
+      type: integer
+      name: temperature
+      range:
+        min: 5
+        max: 50
+      mapping:
+        - constraint: temperature_unit
+          conditions:
+            - dps_val: f
+              value_redirect: temp_f
+              range:
+                min: 41
+                max: 99
+    - id: 23
+      type: integer
+      name: temp_f
+      range:
+        min: 41
+        max: 99
+      hidden: true
+    - id: 32
+      type: integer
+      name: unknown_32
+    - id: 33
+      type: integer
+      name: current_temperature
+      mapping:
+        - scale: 10
+          constraint: temperature_unit
+          conditions:
+            - dps_val: f
+              value_redirect: current_temp_f
+    - id: 35
+      type: integer
+      name: unknown_35
+    - id: 37
+      type: integer
+      name: current_temp_f
+      mapping:
+        - scale: 10
+      hidden: true
+    - id: 45
+      type: integer
+      name: unknown_45
+    - id: 105
+      type: string
+      name: unknown_105
+secondary_entities:
+  - entity: select
+    name: Sensor
+    category: config
+    icon: "mdi:thermometer"
+    dps:
+      - id: 18
+        type: string
+        name: option
+        mapping:
+          - dps_val: in
+            value: Internal
+          - dps_val: out
+            value: External
+  - entity: select
+    name: Temperature Unit
+    category: config
+    dps:
+      - id: 19
+        type: string
+        name: option
+        mapping:
+          - dps_val: c
+            value: Celsius
+            icon: "mdi:temperature-celsius"
+          - dps_val: f
+            value: Fahrenheit
+            icon: "mdi:temperature-fahrenheit"
+  - entity: number
+    name: Calibration Offset
+    category: config
+    icon: "mdi: arrow-collapse-up"
+    dps:
+      - id: 103
+        type: integer
+        name: value
+        range:
+          min: -9
+          max: 9
+        mapping:
+          - constraint: unit
+            conditions:
+              - dps_val: f
+                range:
+                  min: -15
+                  max: 15
+      - id: 19
+        type: string
+        name: unit
+        hidden: true
+  - entity: number
+    name: Calibration Swing
+    category: config
+    icon: "mdi:arrow-expand-vertical"
+    dps:
+      - id: 104
+        type: integer
+        name: value
+        range:
+          min: 1
+          max: 9
+        mapping:
+          - constraint: unit
+            conditions:
+              - dps_val: f
+                range:
+                  min: 2
+                  max: 15
+      - id: 19
+        type: string
+        name: unit
+        hidden: true
+  - entity: number
+    category: config
+    name: High Temperature Limit
+    icon: "mdi:thermometer-alert"
+    dps:
+      - id: 106
+        type: integer
+        name: value
+        range:
+          min: 5
+          max: 65
+        constraint: unit
+        conditions:
+          - dps_val: f
+            value_redirect: max_temp_f
+            range:
+              min: 41
+              max: 150
+      - id: 107
+        type: integer
+        name: max_temp_f
+        range:
+          min: 41
+          max: 150
+        hidden: true
+      - id: 19
+        type: string
+        name: unit
+        hidden: true        
+  - entity: select
+    name: Schedule
+    category: config
+    icon: "mdi:calendar-clock"
+    dps:
+      - id: 39
+        type: string
+        name: option
+        mapping:
+          - dps_val: "7"
+            value: "7 day"
+          - dps_val: "6_1"
+            value: "6+1 day"
+          - dps_val: "5_2"
+            value: "5+2 day"
+  - entity: sensor
+    name: External Temperature
+    category: diagnostic
+    class: temperature
+    dps:
+      - id: 101
+        type: integer
+        name: sensor
+        class: measurement
+        mapping:
+          - scale: 10
+            constraint: unit
+            conditions:
+              - dps_val: f
+                value_redirect: extern_temp_f
+      - id: 102
+        type: integer
+        name: extern_temp_f
+        hidden: true
+        mapping:
+          - scale: 10
+      - id: 19
+        type: string
+        name: unit
+        mapping:
+          - dps_val: c
+            value: C
+          - dps_val: f
+            value: F

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

@@ -51,6 +51,18 @@ secondary_entities:
         type: integer
         class: measurement
         unit: mA
+  - entity: sensor
+    category: diagnostic
+    class: power
+    name: Power
+    dps:
+      - id: 5
+        name: sensor
+        type: integer
+        class: measurement
+        unit: W
+        mapping:
+          - scale: 10
   - entity: number
     category: config
     name: Timer

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

@@ -51,6 +51,18 @@ secondary_entities:
         type: integer
         class: measurement
         unit: mA
+  - entity: sensor
+    category: diagnostic
+    class: power
+    name: Power
+    dps:
+      - id: 19
+        name: sensor
+        type: integer
+        class: measurement
+        unit: W
+        mapping:
+          - scale: 10
   - entity: number
     category: config
     name: Timer

+ 0 - 1
custom_components/tuya_local/fan.py

@@ -5,7 +5,6 @@ import logging
 
 from . import DOMAIN
 from .const import (
-    CONF_FAN,
     CONF_DEVICE_ID,
     CONF_TYPE,
 )

+ 0 - 1
custom_components/tuya_local/humidifier.py

@@ -5,7 +5,6 @@ import logging
 
 from . import DOMAIN
 from .const import (
-    CONF_HUMIDIFIER,
     CONF_DEVICE_ID,
     CONF_TYPE,
 )

+ 0 - 1
custom_components/tuya_local/light.py

@@ -6,7 +6,6 @@ import logging
 from . import DOMAIN
 from .const import (
     CONF_DEVICE_ID,
-    CONF_LIGHT,
     CONF_TYPE,
 )
 from .generic.light import TuyaLocalLight

+ 6 - 0
custom_components/tuya_local/translations/en.json

@@ -55,6 +55,9 @@
 		    "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_1": "Include timer 1 as a number entity",
+		    "number_timer_2": "Include timer 2 as a number entity",
+		    "select_initial_state": "Include initial state as a select 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",
@@ -135,6 +138,9 @@
 		"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_1": "Include timer 1 as a number entity",
+		"number_timer_2": "Include timer 2 as a number entity",
+		"select_initial_state": "Include initial state as a select 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",

+ 75 - 21
tests/devices/test_grid_connect_double_power_point.py

@@ -1,8 +1,21 @@
 """Tests for the switch entity."""
 from homeassistant.components.switch import DEVICE_CLASS_OUTLET
+from homeassistant.const import (
+    DEVICE_CLASS_CURRENT,
+    DEVICE_CLASS_ENERGY,
+    DEVICE_CLASS_POWER,
+    DEVICE_CLASS_VOLTAGE,
+    ELECTRIC_CURRENT_MILLIAMPERE,
+    ELECTRIC_POTENTIAL_VOLT,
+    ENERGY_WATT_HOUR,
+    POWER_WATT,
+)
 
 from ..const import GRIDCONNECT_2SOCKET_PAYLOAD
 from ..mixins.lock import BasicLockTests
+from ..mixins.number import MultiNumberTests
+from ..mixins.select import BasicSelectTests
+from ..mixins.sensor import MultiSensorTests
 from ..mixins.switch import MultiSwitchTests
 from .base_device_tests import TuyaDeviceTestCase
 
@@ -10,22 +23,24 @@ SWITCH1_DPS = "1"
 SWITCH2_DPS = "2"
 COUNTDOWN1_DPS = "9"
 COUNTDOWN2_DPS = "10"
-UNKNOWN17_DPS = "17"
+ENERGY_DPS = "17"
 CURRENT_DPS = "18"
 POWER_DPS = "19"
 VOLTAGE_DPS = "20"
-UNKNOWN21_DPS = "21"
-UNKNOWN22_DPS = "22"
-UNKNOWN23_DPS = "23"
-UNKNOWN24_DPS = "24"
-UNKNOWN25_DPS = "25"
-UNKNOWN38_DPS = "38"
+TEST_DPS = "21"
+CALIBV_DPS = "22"
+CALIBA_DPS = "23"
+CALIBW_DPS = "24"
+CALIBE_DPS = "25"
+INITIAL_DPS = "38"
 LOCK_DPS = "40"
 MASTER_DPS = "101"
 
 
 class TestGridConnectDoubleSwitch(
     BasicLockTests,
+    BasicSelectTests,
+    MultiSensorTests,
     MultiSwitchTests,
     TuyaDeviceTestCase,
 ):
@@ -37,6 +52,15 @@ class TestGridConnectDoubleSwitch(
             GRIDCONNECT_2SOCKET_PAYLOAD,
         )
         self.setUpBasicLock(LOCK_DPS, self.entities.get("lock_child_lock"))
+        self.setUpBasicSelect(
+            INITIAL_DPS,
+            self.entities.get("select_initial_state"),
+            {
+                "on": "On",
+                "off": "Off",
+                "memory": "Last State",
+            },
+        )
         # 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.
@@ -61,6 +85,40 @@ class TestGridConnectDoubleSwitch(
                 },
             ]
         )
+        self.setUpMultiSensors(
+            [
+                {
+                    "name": "sensor_energy",
+                    "dps": ENERGY_DPS,
+                    "device_class": DEVICE_CLASS_ENERGY,
+                    "unit": ENERGY_WATT_HOUR,
+                    "state_class": "total_increasing",
+                },
+                {
+                    "name": "sensor_current",
+                    "dps": CURRENT_DPS,
+                    "device_class": DEVICE_CLASS_CURRENT,
+                    "unit": ELECTRIC_CURRENT_MILLIAMPERE,
+                    "state_class": "measurement",
+                },
+                {
+                    "name": "sensor_power",
+                    "dps": POWER_DPS,
+                    "device_class": DEVICE_CLASS_POWER,
+                    "unit": POWER_WATT,
+                    "state_class": "measurement",
+                    "testdata": (1234, 123.4),
+                },
+                {
+                    "name": "sensor_voltage",
+                    "dps": VOLTAGE_DPS,
+                    "device_class": DEVICE_CLASS_VOLTAGE,
+                    "unit": ELECTRIC_POTENTIAL_VOLT,
+                    "state_class": "measurement",
+                    "testdata": (2345, 234.5),
+                },
+            ]
+        )
 
     async def test_turn_on_fails_when_master_is_off(self):
         self.dps[MASTER_DPS] = False
@@ -75,29 +133,25 @@ class TestGridConnectDoubleSwitch(
     def test_multi_switch_state_attributes(self):
         self.dps[COUNTDOWN1_DPS] = 9
         self.dps[COUNTDOWN2_DPS] = 10
-        self.dps[UNKNOWN17_DPS] = 17
         self.dps[VOLTAGE_DPS] = 2350
         self.dps[CURRENT_DPS] = 1234
         self.dps[POWER_DPS] = 5678
-        self.dps[UNKNOWN21_DPS] = 21
-        self.dps[UNKNOWN22_DPS] = 22
-        self.dps[UNKNOWN23_DPS] = 23
-        self.dps[UNKNOWN24_DPS] = 24
-        self.dps[UNKNOWN25_DPS] = 25
-        self.dps[UNKNOWN38_DPS] = "38"
+        self.dps[TEST_DPS] = 21
+        self.dps[CALIBV_DPS] = 22
+        self.dps[CALIBA_DPS] = 23
+        self.dps[CALIBW_DPS] = 24
+        self.dps[CALIBE_DPS] = 25
         self.assertDictEqual(
             self.multiSwitch["switch_master"].device_state_attributes,
             {
                 "current_a": 1.234,
                 "voltage_v": 235.0,
                 "current_power_w": 567.8,
-                "unknown_17": 17,
-                "unknown_21": 21,
-                "unknown_22": 22,
-                "unknown_23": 23,
-                "unknown_24": 24,
-                "unknown_25": 25,
-                "unknown_38": "38",
+                "test_bit": 21,
+                "voltage_calibration": 22,
+                "current_calibration": 23,
+                "power_calibration": 24,
+                "energy_calibration": 25,
             },
         )
         self.assertDictEqual(

+ 327 - 0
tests/devices/test_minco_mh1823d_thermostat.py

@@ -0,0 +1,327 @@
+from homeassistant.components.climate.const import (
+    CURRENT_HVAC_HEAT,
+    CURRENT_HVAC_IDLE,
+    CURRENT_HVAC_OFF,
+    HVAC_MODE_HEAT,
+    HVAC_MODE_OFF,
+    SUPPORT_PRESET_MODE,
+    SUPPORT_TARGET_TEMPERATURE,
+)
+from homeassistant.const import (
+    DEVICE_CLASS_TEMPERATURE,
+    STATE_UNAVAILABLE,
+    TEMP_CELSIUS,
+    TEMP_FAHRENHEIT,
+)
+
+from ..const import MINCO_MH1823D_THERMOSTAT_PAYLOAD
+from ..helpers import assert_device_properties_set
+from ..mixins.number import MultiNumberTests
+from ..mixins.select import MultiSelectTests
+from ..mixins.sensor import BasicSensorTests
+from .base_device_tests import TuyaDeviceTestCase
+
+HVACMODE_DPS = "1"
+UNKNOWN2_DPS = "2"
+HVACACTION_DPS = "3"
+UNKNOWN5_DPS = "5"
+PRESET_DPS = "9"
+UNKNOWN12_DPS = "12"
+SELECT_DPS = "18"
+UNIT_DPS = "19"
+TEMPERATURE_DPS = "22"
+TEMPF_DPS = "23"
+UNKNOWN32_DPS = "32"
+CURRENTTEMP_DPS = "33"
+UNKNOWN35_DPS = "35"
+CURTEMPF_DPS = "37"
+SCHEDULE_DPS = "39"
+UNKNOWN45_DPS = "45"
+EXTERNTEMP_DPS = "101"
+EXTTEMPF_DPS = "102"
+CALIBRATE_DPS = "103"
+CALIBSWING_DPS = "104"
+UNKNOWN105_DPS = "105"
+TEMPLIMIT_DPS = "106"
+TEMPLIMITF_DPS = "107"
+
+
+class TestMincoMH1823DThermostat(
+    BasicSensorTests,
+    MultiNumberTests,
+    MultiSelectTests,
+    TuyaDeviceTestCase,
+):
+    __test__ = True
+
+    def setUp(self):
+        self.setUpForConfig(
+            "minco_mh1823d_thermostat.yaml",
+            MINCO_MH1823D_THERMOSTAT_PAYLOAD,
+        )
+        self.subject = self.entities.get("climate")
+        self.setUpBasicSensor(
+            EXTERNTEMP_DPS,
+            self.entities.get("sensor_external_temperature"),
+            unit=TEMP_CELSIUS,
+            device_class=DEVICE_CLASS_TEMPERATURE,
+            state_class="measurement",
+            testdata=(300, 30.0),
+        )
+        self.setUpMultiNumber(
+            [
+                {
+                    "name": "number_calibration_offset",
+                    "dps": CALIBRATE_DPS,
+                    "min": -9,
+                    "max": 9,
+                },
+                {
+                    "name": "number_calibration_swing",
+                    "dps": CALIBSWING_DPS,
+                    "min": 1,
+                    "max": 9,
+                },
+                {
+                    "name": "number_high_temperature_limit",
+                    "dps": TEMPLIMIT_DPS,
+                    "min": 5,
+                    "max": 65,
+                },
+            ]
+        )
+        self.setUpMultiSelect(
+            [
+                {
+                    "name": "select_sensor",
+                    "dps": SELECT_DPS,
+                    "options": {
+                        "in": "Internal",
+                        "out": "External",
+                    },
+                },
+                {
+                    "name": "select_temperature_unit",
+                    "dps": UNIT_DPS,
+                    "options": {
+                        "c": "Celsius",
+                        "f": "Fahrenheit",
+                    },
+                },
+                {
+                    "name": "select_schedule",
+                    "dps": SCHEDULE_DPS,
+                    "options": {
+                        "7": "7 day",
+                        "5_2": "5+2 day",
+                        "6_1": "6+1 day",
+                    },
+                },
+            ]
+        )
+
+    def test_supported_features(self):
+        self.assertEqual(
+            self.subject.supported_features,
+            SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE,
+        )
+
+    def test_icon(self):
+        self.dps[HVACMODE_DPS] = True
+        self.dps[HVACACTION_DPS] = "start"
+        self.dps[PRESET_DPS] = False
+        self.assertEqual(self.subject.icon, "mdi:thermometer")
+
+        self.dps[HVACACTION_DPS] = "stop"
+        self.assertEqual(self.subject.icon, "mdi:thermometer-off")
+
+        self.dps[PRESET_DPS] = True
+        self.assertEqual(self.subject.icon, "mdi:snowflake")
+
+    def test_temperature_unit(self):
+        self.dps[UNIT_DPS] = "c"
+        self.assertEqual(self.subject.temperature_unit, TEMP_CELSIUS)
+        self.dps[UNIT_DPS] = "f"
+        self.assertEqual(self.subject.temperature_unit, TEMP_FAHRENHEIT)
+
+    def test_target_temperature(self):
+        self.dps[TEMPERATURE_DPS] = 25
+        self.dps[TEMPF_DPS] = 70
+
+        self.dps[UNIT_DPS] = "c"
+        self.assertEqual(self.subject.target_temperature, 25)
+        self.dps[UNIT_DPS] = "f"
+        self.assertEqual(self.subject.target_temperature, 70)
+
+    def test_target_temperature_step(self):
+        self.assertEqual(self.subject.target_temperature_step, 1)
+
+    def test_minimum_target_temperature(self):
+        self.dps[UNIT_DPS] = "c"
+        self.assertEqual(self.subject.min_temp, 5)
+        self.dps[UNIT_DPS] = "f"
+        self.assertEqual(self.subject.min_temp, 41)
+
+    def test_maximum_target_temperature(self):
+        self.dps[UNIT_DPS] = "c"
+        self.assertEqual(self.subject.max_temp, 50)
+        self.dps[UNIT_DPS] = "f"
+        self.assertEqual(self.subject.max_temp, 99)
+
+    async def test_legacy_set_temperature_with_temperature(self):
+        async with assert_device_properties_set(
+            self.subject._device, {TEMPERATURE_DPS: 24}
+        ):
+            await self.subject.async_set_temperature(temperature=24)
+
+    async def test_legacy_set_temperature_with_preset_mode(self):
+        async with assert_device_properties_set(
+            self.subject._device, {PRESET_DPS: True}
+        ):
+            await self.subject.async_set_temperature(preset_mode="away")
+
+    async def test_legacy_set_temperature_with_both_properties(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {
+                TEMPERATURE_DPS: 25,
+                PRESET_DPS: False,
+            },
+        ):
+            await self.subject.async_set_temperature(temperature=25, preset_mode="home")
+
+    async def test_legacy_set_temperature_with_no_valid_properties(self):
+        await self.subject.async_set_temperature(something="else")
+        self.subject._device.async_set_property.assert_not_called()
+
+    async def test_set_target_temperature(self):
+        async with assert_device_properties_set(
+            self.subject._device, {TEMPERATURE_DPS: 25}
+        ):
+            await self.subject.async_set_target_temperature(25)
+
+    async def test_set_target_temperature_rounds_value_to_closest_integer(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {TEMPERATURE_DPS: 25},
+        ):
+            await self.subject.async_set_target_temperature(24.6)
+
+    async def test_set_target_temperature_fails_outside_valid_range(self):
+        self.dps[UNIT_DPS] = "c"
+        with self.assertRaisesRegex(
+            ValueError, "temperature \\(4\\) must be between 5 and 50"
+        ):
+            await self.subject.async_set_target_temperature(4)
+
+        with self.assertRaisesRegex(
+            ValueError, "temperature \\(51\\) must be between 5 and 50"
+        ):
+            await self.subject.async_set_target_temperature(51)
+
+        self.dps[UNIT_DPS] = "f"
+        with self.assertRaisesRegex(
+            ValueError, "temp_f \\(40\\) must be between 41 and 99"
+        ):
+            await self.subject.async_set_target_temperature(40)
+
+        with self.assertRaisesRegex(
+            ValueError, "temp_f \\(100\\) must be between 41 and 99"
+        ):
+            await self.subject.async_set_target_temperature(100)
+
+    def test_current_temperature(self):
+        self.dps[CURRENTTEMP_DPS] = 251
+        self.dps[CURTEMPF_DPS] = 783
+        self.dps[UNIT_DPS] = "c"
+        self.assertEqual(self.subject.current_temperature, 25.1)
+        self.dps[UNIT_DPS] = "f"
+        self.assertEqual(self.subject.current_temperature, 78.3)
+
+    def test_hvac_mode(self):
+        self.dps[HVACMODE_DPS] = True
+        self.assertEqual(self.subject.hvac_mode, HVAC_MODE_HEAT)
+
+        self.dps[HVACMODE_DPS] = False
+        self.assertEqual(self.subject.hvac_mode, HVAC_MODE_OFF)
+
+        self.dps[HVACMODE_DPS] = None
+        self.assertEqual(self.subject.hvac_mode, STATE_UNAVAILABLE)
+
+    def test_hvac_modes(self):
+        self.assertCountEqual(self.subject.hvac_modes, [HVAC_MODE_OFF, HVAC_MODE_HEAT])
+
+    async def test_turn_on(self):
+        async with assert_device_properties_set(
+            self.subject._device, {HVACMODE_DPS: True}
+        ):
+            await self.subject.async_set_hvac_mode(HVAC_MODE_HEAT)
+
+    async def test_turn_off(self):
+        async with assert_device_properties_set(
+            self.subject._device, {HVACMODE_DPS: False}
+        ):
+            await self.subject.async_set_hvac_mode(HVAC_MODE_OFF)
+
+    def test_preset_mode(self):
+        self.dps[PRESET_DPS] = False
+        self.assertEqual(self.subject.preset_mode, "home")
+
+        self.dps[PRESET_DPS] = True
+        self.assertEqual(self.subject.preset_mode, "away")
+
+        self.dps[PRESET_DPS] = None
+        self.assertEqual(self.subject.preset_mode, None)
+
+    def test_preset_modes(self):
+        self.assertCountEqual(
+            self.subject.preset_modes,
+            ["home", "away"],
+        )
+
+    async def test_set_preset_mode_to_home(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {PRESET_DPS: False},
+        ):
+            await self.subject.async_set_preset_mode("home")
+
+    async def test_set_preset_mode_to_away(self):
+        async with assert_device_properties_set(
+            self.subject._device,
+            {PRESET_DPS: True},
+        ):
+            await self.subject.async_set_preset_mode("away")
+
+    def test_hvac_action(self):
+        self.dps[HVACMODE_DPS] = True
+        self.dps[HVACACTION_DPS] = "start"
+        self.assertEqual(self.subject.hvac_action, CURRENT_HVAC_HEAT)
+
+        self.dps[HVACACTION_DPS] = "stop"
+        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):
+        self.dps[UNKNOWN2_DPS] = "unknown 2"
+        self.dps[UNKNOWN5_DPS] = True
+        self.dps[UNKNOWN12_DPS] = False
+        self.dps[UNKNOWN32_DPS] = 32
+        self.dps[UNKNOWN35_DPS] = 35
+        self.dps[UNKNOWN45_DPS] = 45
+        self.dps[UNKNOWN105_DPS] = "unknown 105"
+
+        self.assertDictEqual(
+            self.subject.device_state_attributes,
+            {
+                "unknown_2": "unknown 2",
+                "unknown_5": True,
+                "unknown_12": False,
+                "unknown_32": 32,
+                "unknown_35": 35,
+                "unknown_45": 45,
+                "unknown_105": "unknown 105",
+            },
+        )

+ 18 - 3
tests/devices/test_smartplugv1.py

@@ -1,6 +1,13 @@
 """Tests for the switch entity."""
 from homeassistant.components.switch import DEVICE_CLASS_OUTLET
-from homeassistant.const import DEVICE_CLASS_CURRENT, DEVICE_CLASS_VOLTAGE
+from homeassistant.const import (
+    DEVICE_CLASS_CURRENT,
+    DEVICE_CLASS_POWER,
+    DEVICE_CLASS_VOLTAGE,
+    ELECTRIC_CURRENT_MILLIAMPERE,
+    ELECTRIC_POTENTIAL_VOLT,
+    POWER_WATT,
+)
 
 from ..const import KOGAN_SOCKET_PAYLOAD
 from ..mixins.number import BasicNumberTests
@@ -30,7 +37,7 @@ class TestKoganSwitch(
                 {
                     "name": "sensor_voltage",
                     "dps": VOLTAGE_DPS,
-                    "unit": "V",
+                    "unit": ELECTRIC_POTENTIAL_VOLT,
                     "device_class": DEVICE_CLASS_VOLTAGE,
                     "state_class": "measurement",
                     "testdata": (2300, 230.0),
@@ -38,10 +45,18 @@ class TestKoganSwitch(
                 {
                     "name": "sensor_current",
                     "dps": CURRENT_DPS,
-                    "unit": "mA",
+                    "unit": ELECTRIC_CURRENT_MILLIAMPERE,
                     "device_class": DEVICE_CLASS_CURRENT,
                     "state_class": "measurement",
                 },
+                {
+                    "name": "sensor_power",
+                    "dps": POWER_DPS,
+                    "unit": POWER_WATT,
+                    "device_class": DEVICE_CLASS_POWER,
+                    "state_class": "measurement",
+                    "testdata": (1234, 123.4),
+                },
             ]
         )
 

+ 18 - 3
tests/devices/test_smartplugv2.py

@@ -1,6 +1,13 @@
 """Tests for the switch entity."""
 from homeassistant.components.switch import DEVICE_CLASS_OUTLET
-from homeassistant.const import DEVICE_CLASS_CURRENT, DEVICE_CLASS_VOLTAGE
+from homeassistant.const import (
+    DEVICE_CLASS_CURRENT,
+    DEVICE_CLASS_POWER,
+    DEVICE_CLASS_VOLTAGE,
+    ELECTRIC_CURRENT_MILLIAMPERE,
+    ELECTRIC_POTENTIAL_VOLT,
+    POWER_WATT,
+)
 
 from ..const import KOGAN_SOCKET_PAYLOAD2
 from ..mixins.number import BasicNumberTests
@@ -30,7 +37,7 @@ class TestSwitchV2(
                 {
                     "name": "sensor_voltage",
                     "dps": VOLTAGE_DPS,
-                    "unit": "V",
+                    "unit": ELECTRIC_POTENTIAL_VOLT,
                     "device_class": DEVICE_CLASS_VOLTAGE,
                     "state_class": "measurement",
                     "testdata": (2300, 230.0),
@@ -38,10 +45,18 @@ class TestSwitchV2(
                 {
                     "name": "sensor_current",
                     "dps": CURRENT_DPS,
-                    "unit": "mA",
+                    "unit": ELECTRIC_CURRENT_MILLIAMPERE,
                     "device_class": DEVICE_CLASS_CURRENT,
                     "state_class": "measurement",
                 },
+                {
+                    "name": "sensor_power",
+                    "dps": POWER_DPS,
+                    "unit": POWER_WATT,
+                    "device_class": DEVICE_CLASS_POWER,
+                    "state_class": "measurement",
+                    "testdata": (1234, 123.4),
+                },
             ]
         )