From 06eec7adfcefeba7267abf83d9145116c8b814c2 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Wed, 22 Dec 2021 18:49:58 +0000 Subject: [PATCH] Allow adding new devices to an Aqara hub via homekit_controller (#62600) --- .../components/homekit_controller/const.py | 3 + .../components/homekit_controller/number.py | 7 +- .../components/homekit_controller/switch.py | 90 ++- .../homekit_controller/fixtures/aqara_e1.json | 646 ++++++++++++++++++ .../specific_devices/test_aqara_gateway.py | 72 +- .../specific_devices/test_eve_degree.py | 2 +- .../test_vocolinc_flowerbud.py | 3 +- .../homekit_controller/test_switch.py | 56 ++ 8 files changed, 869 insertions(+), 10 deletions(-) create mode 100644 tests/components/homekit_controller/fixtures/aqara_e1.json diff --git a/homeassistant/components/homekit_controller/const.py b/homeassistant/components/homekit_controller/const.py index 2de7156482e..7fab8222ae2 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -48,6 +48,9 @@ HOMEKIT_ACCESSORY_DISPATCH = { CHARACTERISTIC_PLATFORMS = { CharacteristicsTypes.Vendor.AQARA_GATEWAY_VOLUME: "number", + CharacteristicsTypes.Vendor.AQARA_E1_GATEWAY_VOLUME: "number", + CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE: "switch", + CharacteristicsTypes.Vendor.AQARA_E1_PAIRING_MODE: "switch", CharacteristicsTypes.Vendor.EVE_ENERGY_WATT: "sensor", CharacteristicsTypes.Vendor.EVE_DEGREE_AIR_PRESSURE: "sensor", CharacteristicsTypes.Vendor.EVE_DEGREE_ELEVATION: "number", diff --git a/homeassistant/components/homekit_controller/number.py b/homeassistant/components/homekit_controller/number.py index f646072425b..26ec7e6b9e0 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -75,10 +75,9 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity): @property def name(self) -> str: """Return the name of the device if any.""" - prefix = "" - if name := super().name: - prefix = f"{name} -" - return f"{prefix} {self.entity_description.name}" + if prefix := super().name: + return f"{prefix} {self.entity_description.name}" + return self.entity_description.name def get_characteristic_types(self): """Define the homekit characteristics the entity is tracking.""" diff --git a/homeassistant/components/homekit_controller/switch.py b/homeassistant/components/homekit_controller/switch.py index 4ae9ed5a5f0..1f128bd4d26 100644 --- a/homeassistant/components/homekit_controller/switch.py +++ b/homeassistant/components/homekit_controller/switch.py @@ -1,15 +1,21 @@ """Support for Homekit switches.""" +from __future__ import annotations + +from dataclasses import dataclass + from aiohomekit.model.characteristics import ( + Characteristic, CharacteristicsTypes, InUseValues, IsConfiguredValues, ) from aiohomekit.model.services import ServicesTypes -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.core import callback +from homeassistant.helpers.entity import EntityCategory -from . import KNOWN_DEVICES, HomeKitEntity +from . import KNOWN_DEVICES, CharacteristicEntity, HomeKitEntity OUTLET_IN_USE = "outlet_in_use" @@ -18,6 +24,30 @@ ATTR_IS_CONFIGURED = "is_configured" ATTR_REMAINING_DURATION = "remaining_duration" +@dataclass +class DeclarativeSwitchEntityDescription(SwitchEntityDescription): + """Describes Homekit button.""" + + true_value: bool = True + false_value: bool = False + + +SWITCH_ENTITIES: dict[str, DeclarativeSwitchEntityDescription] = { + CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE: DeclarativeSwitchEntityDescription( + key=CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE, + name="Pairing Mode", + icon="mdi:lock-open", + entity_category=EntityCategory.CONFIG, + ), + CharacteristicsTypes.Vendor.AQARA_E1_PAIRING_MODE: DeclarativeSwitchEntityDescription( + key=CharacteristicsTypes.Vendor.AQARA_E1_PAIRING_MODE, + name="Pairing Mode", + icon="mdi:lock-open", + entity_category=EntityCategory.CONFIG, + ), +} + + class HomeKitSwitch(HomeKitEntity, SwitchEntity): """Representation of a Homekit switch.""" @@ -96,6 +126,49 @@ class HomeKitValve(HomeKitEntity, SwitchEntity): return attrs +class DeclarativeCharacteristicSwitch(CharacteristicEntity, SwitchEntity): + """Representation of a Homekit switch backed by a single characteristic.""" + + def __init__( + self, + conn, + info, + char, + description: DeclarativeSwitchEntityDescription, + ): + """Initialise a HomeKit switch.""" + self.entity_description = description + super().__init__(conn, info, char) + + @property + def name(self) -> str: + """Return the name of the device if any.""" + if prefix := super().name: + return f"{prefix} {self.entity_description.name}" + return self.entity_description.name + + def get_characteristic_types(self): + """Define the homekit characteristics the entity cares about.""" + return [self._char.type] + + @property + def is_on(self): + """Return true if device is on.""" + return self._char.value == self.entity_description.true_value + + async def async_turn_on(self, **kwargs): + """Turn the specified switch on.""" + await self.async_put_characteristics( + {self._char.type: self.entity_description.true_value} + ) + + async def async_turn_off(self, **kwargs): + """Turn the specified switch off.""" + await self.async_put_characteristics( + {self._char.type: self.entity_description.false_value} + ) + + ENTITY_TYPES = { ServicesTypes.SWITCH: HomeKitSwitch, ServicesTypes.OUTLET: HomeKitSwitch, @@ -117,3 +190,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): return True conn.add_listener(async_add_service) + + @callback + def async_add_characteristic(char: Characteristic): + if not (description := SWITCH_ENTITIES.get(char.type)): + return False + + info = {"aid": char.service.accessory.aid, "iid": char.service.iid} + async_add_entities( + [DeclarativeCharacteristicSwitch(conn, info, char, description)], True + ) + return True + + conn.add_char_factory(async_add_characteristic) diff --git a/tests/components/homekit_controller/fixtures/aqara_e1.json b/tests/components/homekit_controller/fixtures/aqara_e1.json new file mode 100644 index 00000000000..8c8ff326bd6 --- /dev/null +++ b/tests/components/homekit_controller/fixtures/aqara_e1.json @@ -0,0 +1,646 @@ +[ + { + "aid": 1, + "services": [ + { + "iid": 1, + "type": "0000003E-0000-1000-8000-0026BB765291", + "primary": false, + "hidden": false, + "characteristics": [ + { + "iid": 65537, + "type": "00000014-0000-1000-8000-0026BB765291", + "format": "bool", + "perms": [ + "pw" + ] + }, + { + "iid": 65538, + "type": "00000020-0000-1000-8000-0026BB765291", + "format": "string", + "value": "Aqara", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 65539, + "type": "00000021-0000-1000-8000-0026BB765291", + "format": "string", + "value": "HE1-G01", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 65540, + "type": "00000023-0000-1000-8000-0026BB765291", + "format": "string", + "value": "Aqara-Hub-E1-00A0", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 65541, + "type": "00000030-0000-1000-8000-0026BB765291", + "format": "string", + "value": "00aa00000a0", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 65542, + "type": "00000052-0000-1000-8000-0026BB765291", + "format": "string", + "value": "3.3.0", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 65543, + "type": "00000053-0000-1000-8000-0026BB765291", + "format": "string", + "value": "1.0", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 65544, + "type": "34AB8811-AC7F-4340-BAC3-FD6A85F9943B", + "format": "string", + "value": "5.0;dfeceb3a", + "perms": [ + "pr", + "hd" + ], + "ev": false + }, + { + "iid": 65545, + "type": "220", + "format": "data", + "value": "xDsGO4QdTEA=", + "perms": [ + "pr" + ], + "ev": false, + "maxDataLen": 8 + } + ] + }, + { + "iid": 2, + "type": "000000A2-0000-1000-8000-0026BB765291", + "primary": false, + "hidden": false, + "characteristics": [ + { + "iid": 131074, + "type": "00000037-0000-1000-8000-0026BB765291", + "format": "string", + "value": "1.1.0", + "perms": [ + "pr" + ], + "ev": false + } + ] + }, + { + "iid": 4, + "type": "22A", + "primary": false, + "hidden": false, + "characteristics": [ + { + "iid": 262145, + "type": "22B", + "format": "bool", + "value": 1, + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 262146, + "type": "22C", + "format": "uint32", + "value": 9, + "perms": [ + "pr" + ], + "ev": false, + "minValue": 0, + "maxValue": 15, + "minStep": 1 + }, + { + "iid": 262147, + "type": "22D", + "format": "tlv8", + "value": "", + "perms": [ + "pr", + "pw", + "ev", + "tw", + "wr" + ], + "ev": false + } + ] + }, + { + "iid": 16, + "type": "0000007E-0000-1000-8000-0026BB765291", + "primary": true, + "hidden": false, + "characteristics": [ + { + "iid": 1048578, + "type": "00000023-0000-1000-8000-0026BB765291", + "format": "string", + "value": "Security System", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 1048579, + "type": "00000066-0000-1000-8000-0026BB765291", + "format": "uint8", + "value": 3, + "perms": [ + "pr", + "ev" + ], + "ev": true, + "minValue": 0, + "maxValue": 4, + "minStep": 1, + "valid-values": [ + 0, + 1, + 2, + 3, + 4 + ] + }, + { + "iid": 1048580, + "type": "00000067-0000-1000-8000-0026BB765291", + "format": "uint8", + "value": 3, + "perms": [ + "pr", + "pw", + "ev" + ], + "ev": true, + "minValue": 0, + "maxValue": 3, + "minStep": 1, + "valid-values": [ + 0, + 1, + 2, + 3 + ] + }, + { + "iid": 1048581, + "type": "60CDDE6C-42B6-4C72-9719-AB2740EABE2A", + "format": "tlv8", + "value": "AAA=", + "perms": [ + "pr", + "pw" + ], + "ev": false, + "description": "Stay Arm Trigger Devices" + }, + { + "iid": 1048582, + "type": "4AB2460A-41E4-4F05-97C3-CCFDAE1BE324", + "format": "tlv8", + "value": "AAA=", + "perms": [ + "pr", + "pw" + ], + "ev": false, + "description": "Alarm Trigger Devices" + }, + { + "iid": 1048583, + "type": "F8296386-5A30-4AA7-838C-ED0DA9D807DF", + "format": "tlv8", + "value": "AAA=", + "perms": [ + "pr", + "pw" + ], + "ev": false, + "description": "Night Arm Trigger Devices" + } + ] + }, + { + "iid": 17, + "type": "9715BF53-AB63-4449-8DC7-2785D617390A", + "primary": false, + "hidden": true, + "characteristics": [ + { + "iid": 1114114, + "type": "00000023-0000-1000-8000-0026BB765291", + "format": "string", + "value": "Gateway", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 1114115, + "type": "4CB28907-66DF-4D9C-962C-9971ABF30EDC", + "format": "string", + "value": "1970-01-01 21:01:22+8", + "perms": [ + "pr", + "pw", + "hd" + ], + "ev": false, + "description": "Date and Time" + }, + { + "iid": 1114116, + "type": "EE56B186-B0D3-488E-8C79-C21FC9BCF437", + "format": "int", + "value": 40, + "perms": [ + "pr", + "pw", + "ev", + "hd" + ], + "ev": false, + "description": "Gateway Volume", + "unit": "percentage", + "minValue": 0, + "maxValue": 100, + "minStep": 1 + }, + { + "iid": 1114117, + "type": "B1C09E4C-E202-4827-B863-B0F32F727CFF", + "format": "bool", + "value": 0, + "perms": [ + "pr", + "pw", + "ev", + "hd" + ], + "ev": false, + "description": "New Accessory Permission" + }, + { + "iid": 1114118, + "type": "2CB22739-1E4C-4798-A761-BC2FAF51AFC3", + "format": "string", + "value": "", + "perms": [ + "pr", + "ev", + "hd" + ], + "ev": false, + "description": "Accessory Joined" + }, + { + "iid": 1114119, + "type": "75D19FA9-218B-4943-997E-341E5D1C60CC", + "format": "string", + "perms": [ + "pw", + "hd" + ], + "description": "Remove Accessory" + }, + { + "iid": 1114120, + "type": "7D943F6A-E052-4E96-A176-D17BF00E32CB", + "format": "int", + "value": -1, + "perms": [ + "pr", + "ev", + "hd" + ], + "ev": false, + "description": "Firmware Update Status", + "minValue": -65535, + "maxValue": 65535, + "minStep": 1 + }, + { + "iid": 1114121, + "type": "A45EFD52-0DB5-4C1A-9727-513FBCD8185F", + "format": "string", + "perms": [ + "pw", + "hd" + ], + "description": "Firmware Update URL", + "maxLen": 256 + }, + { + "iid": 1114122, + "type": "40F0124A-579D-40E4-865E-0EF6740EA64B", + "format": "string", + "perms": [ + "pw", + "hd" + ], + "description": "Firmware Update Checksum" + }, + { + "iid": 1114123, + "type": "E1C20B22-E3A7-4B92-8BA3-C16E778648A7", + "format": "string", + "value": "", + "perms": [ + "pr", + "ev", + "hd" + ], + "ev": false, + "description": "Identify Accessory" + }, + { + "iid": 1114124, + "type": "4CF1436A-755C-4377-BDB8-30BE29EB8620", + "format": "string", + "value": "Chinese", + "perms": [ + "pr", + "pw", + "ev", + "hd" + ], + "ev": false, + "description": "Language" + }, + { + "iid": 1114125, + "type": "25D889CB-7135-4A29-B5B4-C1FFD6D2DD5C", + "format": "string", + "value": "", + "perms": [ + "pr", + "pw", + "hd" + ], + "ev": false, + "description": "Country Domain" + }, + { + "iid": 1114126, + "type": "C7EECAA7-91D9-40EB-AD0C-FFDDE3143CB9", + "format": "string", + "value": "lumi1.00aa00000a0", + "perms": [ + "pr", + "hd" + ], + "ev": false, + "description": "Lumi Did" + }, + { + "iid": 1114127, + "type": "80FA747E-CB45-45A4-B7BE-AA7D9964859E", + "format": "string", + "perms": [ + "pw", + "hd" + ], + "description": "Lumi Bindkey" + }, + { + "iid": 1114128, + "type": "C3B8A329-EF0C-4739-B773-E5B7AEA52C71", + "format": "bool", + "value": 0, + "perms": [ + "pr", + "hd" + ], + "ev": false, + "description": "Lumi Bindstate" + } + ] + } + ] + }, + { + "aid": 33, + "services": [ + { + "iid": 1, + "type": "0000003E-0000-1000-8000-0026BB765291", + "primary": false, + "hidden": false, + "characteristics": [ + { + "iid": 65537, + "type": "00000014-0000-1000-8000-0026BB765291", + "format": "bool", + "perms": [ + "pw" + ] + }, + { + "iid": 65538, + "type": "00000020-0000-1000-8000-0026BB765291", + "format": "string", + "value": "Aqara", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 65539, + "type": "00000021-0000-1000-8000-0026BB765291", + "format": "string", + "value": "AS006", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 65540, + "type": "00000023-0000-1000-8000-0026BB765291", + "format": "string", + "value": "Contact Sensor", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 65541, + "type": "00000030-0000-1000-8000-0026BB765291", + "format": "string", + "value": "158d0007c59c6a", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 65542, + "type": "00000052-0000-1000-8000-0026BB765291", + "format": "string", + "value": "0", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 65543, + "type": "00000053-0000-1000-8000-0026BB765291", + "format": "string", + "value": "1.0", + "perms": [ + "pr" + ], + "ev": false + } + ] + }, + { + "iid": 4, + "type": "00000080-0000-1000-8000-0026BB765291", + "primary": true, + "hidden": false, + "characteristics": [ + { + "iid": 262146, + "type": "00000023-0000-1000-8000-0026BB765291", + "format": "string", + "value": "Contact Sensor", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 262147, + "type": "0000006A-0000-1000-8000-0026BB765291", + "format": "uint8", + "value": 0, + "perms": [ + "pr", + "ev" + ], + "ev": true, + "minValue": 0, + "maxValue": 1, + "minStep": 1, + "valid-values": [ + 0, + 1 + ] + } + ] + }, + { + "iid": 5, + "type": "00000096-0000-1000-8000-0026BB765291", + "primary": false, + "hidden": false, + "characteristics": [ + { + "iid": 327682, + "type": "00000023-0000-1000-8000-0026BB765291", + "format": "string", + "value": "Battery Sensor", + "perms": [ + "pr" + ], + "ev": false + }, + { + "iid": 327683, + "type": "00000068-0000-1000-8000-0026BB765291", + "format": "uint8", + "value": 100, + "perms": [ + "pr", + "ev" + ], + "ev": true, + "unit": "percentage", + "minValue": 0, + "maxValue": 100, + "minStep": 1 + }, + { + "iid": 327685, + "type": "00000079-0000-1000-8000-0026BB765291", + "format": "uint8", + "value": 0, + "perms": [ + "pr", + "ev" + ], + "ev": true, + "minValue": 0, + "maxValue": 1, + "minStep": 1, + "valid-values": [ + 0, + 1 + ] + }, + { + "iid": 327684, + "type": "0000008F-0000-1000-8000-0026BB765291", + "format": "uint8", + "value": 2, + "perms": [ + "pr", + "ev" + ], + "ev": true, + "minValue": 2, + "maxValue": 2, + "minStep": 1, + "valid-values": [ + 2 + ] + } + ] + } + ] + } +] diff --git a/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py index 08c9e7b3976..fd9ef96752a 100644 --- a/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py +++ b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py @@ -45,7 +45,14 @@ async def test_aqara_gateway_setup(hass): ( "number.aqara_hub_1563_volume", "homekit-0000000123456789-aid:1-sid:65536-cid:65541", - "Aqara Hub-1563 - Volume", + "Aqara Hub-1563 Volume", + None, + EntityCategory.CONFIG, + ), + ( + "switch.aqara_hub_1563_pairing_mode", + "homekit-0000000123456789-aid:1-sid:65536-cid:65538", + "Aqara Hub-1563 Pairing Mode", None, EntityCategory.CONFIG, ), @@ -80,3 +87,66 @@ async def test_aqara_gateway_setup(hass): # All entities should be part of same device assert len(device_ids) == 1 + + +async def test_aqara_gateway_e1_setup(hass): + """Test that an Aqara E1 Gateway can be correctly setup in HA.""" + accessories = await setup_accessories_from_file(hass, "aqara_e1.json") + config_entry, pairing = await setup_test_accessories(hass, accessories) + + entity_registry = er.async_get(hass) + device_registry = dr.async_get(hass) + + sensors = [ + ( + "alarm_control_panel.aqara_hub_e1_00a0", + "homekit-00aa00000a0-16", + "Aqara-Hub-E1-00A0", + SUPPORT_ALARM_ARM_NIGHT | SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY, + None, + ), + ( + "number.aqara_hub_e1_00a0_volume", + "homekit-00aa00000a0-aid:1-sid:17-cid:1114116", + "Aqara-Hub-E1-00A0 Volume", + None, + EntityCategory.CONFIG, + ), + ( + "switch.aqara_hub_e1_00a0_pairing_mode", + "homekit-00aa00000a0-aid:1-sid:17-cid:1114117", + "Aqara-Hub-E1-00A0 Pairing Mode", + None, + EntityCategory.CONFIG, + ), + ] + + device_ids = set() + + for (entity_id, unique_id, friendly_name, supported_features, category) in sensors: + entry = entity_registry.async_get(entity_id) + assert entry.unique_id == unique_id + assert entry.entity_category == category + + helper = Helper( + hass, + entity_id, + pairing, + accessories[0], + config_entry, + ) + state = await helper.poll_and_get_state() + assert state.attributes["friendly_name"] == friendly_name + assert state.attributes.get("supported_features") == supported_features + + device = device_registry.async_get(entry.device_id) + assert device.manufacturer == "Aqara" + assert device.name == "Aqara-Hub-E1-00A0" + assert device.model == "HE1-G01" + assert device.sw_version == "3.3.0" + assert device.via_device_id is None + + device_ids.add(entry.device_id) + + # All entities should be part of same device + assert len(device_ids) == 1 diff --git a/tests/components/homekit_controller/specific_devices/test_eve_degree.py b/tests/components/homekit_controller/specific_devices/test_eve_degree.py index 043920fadec..312316e8870 100644 --- a/tests/components/homekit_controller/specific_devices/test_eve_degree.py +++ b/tests/components/homekit_controller/specific_devices/test_eve_degree.py @@ -41,7 +41,7 @@ async def test_eve_degree_setup(hass): ( "number.eve_degree_aa11_elevation", "homekit-AA00A0A00000-aid:1-sid:30-cid:33", - "Eve Degree AA11 - Elevation", + "Eve Degree AA11 Elevation", ), ] diff --git a/tests/components/homekit_controller/specific_devices/test_vocolinc_flowerbud.py b/tests/components/homekit_controller/specific_devices/test_vocolinc_flowerbud.py index fe570ff0b73..a58f306a241 100644 --- a/tests/components/homekit_controller/specific_devices/test_vocolinc_flowerbud.py +++ b/tests/components/homekit_controller/specific_devices/test_vocolinc_flowerbud.py @@ -31,8 +31,7 @@ async def test_vocolinc_flowerbud_setup(hass): ) state = await helper.poll_and_get_state() assert ( - state.attributes["friendly_name"] - == "VOCOlinc-Flowerbud-0d324b - Spray Quantity" + state.attributes["friendly_name"] == "VOCOlinc-Flowerbud-0d324b Spray Quantity" ) device = device_registry.async_get(entry.device_id) diff --git a/tests/components/homekit_controller/test_switch.py b/tests/components/homekit_controller/test_switch.py index c53d20891b1..5c737e63edc 100644 --- a/tests/components/homekit_controller/test_switch.py +++ b/tests/components/homekit_controller/test_switch.py @@ -38,6 +38,14 @@ def create_valve_service(accessory): remaining.value = 99 +def create_char_switch_service(accessory): + """Define swtch characteristics.""" + service = accessory.add_service(ServicesTypes.OUTLET) + + on_char = service.add_char(CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE) + on_char.value = False + + async def test_switch_change_outlet_state(hass, utcnow): """Test that we can turn a HomeKit outlet on and off again.""" helper = await setup_test_component(hass, create_switch_service) @@ -122,3 +130,51 @@ async def test_valve_read_state(hass, utcnow): helper.characteristics[("valve", "in-use")].value = InUseValues.NOT_IN_USE switch_1 = await helper.poll_and_get_state() assert switch_1.attributes["in_use"] is False + + +async def test_char_switch_change_state(hass, utcnow): + """Test that we can turn a characteristic on and off again.""" + helper = await setup_test_component( + hass, create_char_switch_service, suffix="pairing_mode" + ) + svc = helper.accessory.services.first(service_type=ServicesTypes.OUTLET) + pairing_mode = svc[CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE] + + await hass.services.async_call( + "switch", + "turn_on", + {"entity_id": "switch.testdevice_pairing_mode"}, + blocking=True, + ) + assert pairing_mode.value is True + + await hass.services.async_call( + "switch", + "turn_off", + {"entity_id": "switch.testdevice_pairing_mode"}, + blocking=True, + ) + assert pairing_mode.value is False + + +async def test_char_switch_read_state(hass, utcnow): + """Test that we can read the state of a HomeKit characteristic switch.""" + helper = await setup_test_component( + hass, create_char_switch_service, suffix="pairing_mode" + ) + svc = helper.accessory.services.first(service_type=ServicesTypes.OUTLET) + pairing_mode = svc[CharacteristicsTypes.Vendor.AQARA_PAIRING_MODE] + + # Initial state is that the switch is off + switch_1 = await helper.poll_and_get_state() + assert switch_1.state == "off" + + # Simulate that someone switched on the device in the real world not via HA + pairing_mode.set_value(True) + switch_1 = await helper.poll_and_get_state() + assert switch_1.state == "on" + + # Simulate that device switched off in the real world not via HA + pairing_mode.set_value(False) + switch_1 = await helper.poll_and_get_state() + assert switch_1.state == "off"