diff --git a/homeassistant/components/homekit_controller/humidifier.py b/homeassistant/components/homekit_controller/humidifier.py index e4bed25d618fb..04e04d3e973f4 100644 --- a/homeassistant/components/homekit_controller/humidifier.py +++ b/homeassistant/components/homekit_controller/humidifier.py @@ -249,6 +249,69 @@ def unique_id(self) -> str: return f"homekit-{serial}-{self._iid}-{self.device_class}" +class VocolincFlowerbud(HomeKitEntity, HumidifierEntity): + """Representation of a HomeKit Controller Humidifier.""" + + def get_characteristic_types(self): + """Define the homekit characteristics the entity cares about.""" + return [ + CharacteristicsTypes.ACTIVE, + CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT, + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE, + CharacteristicsTypes.TARGET_HUMIDIFIER_DEHUMIDIFIER_STATE, + CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL, + ] + + @property + def device_class(self) -> str: + """Return the device class of the device.""" + return DEVICE_CLASS_HUMIDIFIER + + @property + def is_on(self): + """Return true if device is on.""" + return self.service.value(CharacteristicsTypes.ACTIVE) + + async def async_turn_on(self, **kwargs): + """Turn the specified valve on.""" + await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: True}) + + async def async_turn_off(self, **kwargs): + """Turn the specified valve off.""" + await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) + + @property + def target_humidity(self) -> Optional[int]: + """Return the humidity we try to reach.""" + if not self.is_on: + return 0 + + return ( + self.service.value( + CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL + ) + * 20 + ) + + async def async_set_humidity(self, humidity: int) -> None: + """Set new target humidity.""" + + if humidity < 20: + await self.async_put_characteristics({CharacteristicsTypes.ACTIVE: False}) + else: + if not self.is_on: + await self.async_put_characteristics( + {CharacteristicsTypes.ACTIVE: True} + ) + + await self.async_put_characteristics( + { + CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL: humidity + / 20 + } + ) + + async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Homekit humidifer.""" hkid = config_entry.data["AccessoryPairingID"] @@ -263,11 +326,16 @@ def async_add_service(service): entities = [] - if service.has(CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD): - entities.append(HomeKitHumidifier(conn, info)) + if service.has(CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL): + entities.append(VocolincFlowerbud(conn, info)) + else: + if service.has(CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD): + entities.append(HomeKitHumidifier(conn, info)) - if service.has(CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD): - entities.append(HomeKitDehumidifier(conn, info)) + if service.has( + CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD + ): + entities.append(HomeKitDehumidifier(conn, info)) async_add_entities(entities, True) diff --git a/tests/components/homekit_controller/test_humidifier.py b/tests/components/homekit_controller/test_humidifier.py index 0af795e2ce941..47d26feee6ea2 100644 --- a/tests/components/homekit_controller/test_humidifier.py +++ b/tests/components/homekit_controller/test_humidifier.py @@ -26,6 +26,11 @@ "relative-humidity.dehumidifier-threshold", ) +VOCOLINC_HUMIDIFIER_SPRAY_LEVEL = ( + "humidifier-dehumidifier", + f"Unknown Characteristic {CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL}", +) + def create_humidifier_service(accessory): """Define a humidifier characteristics as per page 219 of HAP spec.""" @@ -81,6 +86,33 @@ def create_dehumidifier_service(accessory): return service +def create_vocolinc_flowerbud_service(accessory): + """Define a diffuser accessory with VOCOLinc vendor specific characteristics.""" + service = accessory.add_service(ServicesTypes.HUMIDIFIER_DEHUMIDIFIER) + + service.add_char(CharacteristicsTypes.ACTIVE, value=False) + + cur_state = service.add_char(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT) + cur_state.value = 0 + + cur_state = service.add_char( + CharacteristicsTypes.CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE + ) + cur_state.value = -1 + + targ_state = service.add_char( + CharacteristicsTypes.TARGET_HUMIDIFIER_DEHUMIDIFIER_STATE + ) + targ_state.value = 0 + + targ_state = service.add_char( + CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL + ) + targ_state.value = 0 + + return service + + async def test_humidifier_active_state(hass, utcnow): """Test that we can turn a HomeKit humidifier on and off again.""" helper = await setup_test_component(hass, create_humidifier_service) @@ -331,3 +363,69 @@ async def test_dehumidifier_target_humidity_modes(hass, utcnow): state = await helper.poll_and_get_state() assert state.attributes["mode"] == "normal" assert state.attributes["humidity"] == 73 + + +async def test_vocolinc_flowerbud_read_state(hass, utcnow): + """Test that we can read the state of a HomeKit VOCOLinc Flowerbud accessory.""" + helper = await setup_test_component(hass, create_vocolinc_flowerbud_service) + + helper.characteristics[VOCOLINC_HUMIDIFIER_SPRAY_LEVEL].value = 5 + state = await helper.poll_and_get_state() + assert state.attributes["humidity"] == 0 + + helper.characteristics[ACTIVE].value = 1 + state = await helper.poll_and_get_state() + assert state.attributes["humidity"] == 100 + + helper.characteristics[VOCOLINC_HUMIDIFIER_SPRAY_LEVEL].value = 2 + state = await helper.poll_and_get_state() + assert state.attributes["humidity"] == 40 + + helper.characteristics[ACTIVE].value = 0 + state = await helper.poll_and_get_state() + assert state.attributes["humidity"] == 0 + + +async def test_vocolinc_flowerbud_set_state(hass, utcnow): + """Test that we can set the state of a HomeKit VOCOLinc Flowerbud accessory.""" + helper = await setup_test_component(hass, create_vocolinc_flowerbud_service) + + await hass.services.async_call( + DOMAIN, + "set_humidity", + {"entity_id": helper.entity_id, "humidity": 20}, + blocking=True, + ) + assert helper.characteristics[VOCOLINC_HUMIDIFIER_SPRAY_LEVEL].value == 1 + + await hass.services.async_call( + DOMAIN, + "set_humidity", + {"entity_id": helper.entity_id, "humidity": 60}, + blocking=True, + ) + assert helper.characteristics[VOCOLINC_HUMIDIFIER_SPRAY_LEVEL].value == 3 + + await hass.services.async_call( + DOMAIN, + "turn_off", + {"entity_id": helper.entity_id}, + blocking=True, + ) + assert helper.characteristics[ACTIVE].value == 0 + + await hass.services.async_call( + DOMAIN, + "turn_on", + {"entity_id": helper.entity_id}, + blocking=True, + ) + assert helper.characteristics[ACTIVE].value == 1 + + await hass.services.async_call( + DOMAIN, + "set_humidity", + {"entity_id": helper.entity_id, "humidity": 19}, + blocking=True, + ) + assert helper.characteristics[ACTIVE].value == 0