Skip to content
Closed
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0993e1c
add HomeKit humidifier/dehumidifier
adrum Oct 24, 2020
905d651
add homekit diffuser
adrum Oct 24, 2020
c7c8065
added more test coverage
adrum Oct 24, 2020
ab06362
return correct spray level when off and activate if not on
adrum Oct 24, 2020
cf18a0f
simplified char logic
adrum Oct 25, 2020
1759d59
use mode constants
adrum Oct 25, 2020
32907fb
Renamed HomeKit Contorller
adrum Oct 25, 2020
b4d8f08
improved threshold logic
adrum Oct 25, 2020
d1af0f7
Merge branch 'add-homekit-humidifer-dehumidifier' of ssh://github.com…
adrum Oct 25, 2020
f20e37e
split up homekit humidifier into 2 entities
adrum Oct 31, 2020
51def50
Merge branch 'add-homekit-humidifer-dehumidifier' into add-homekit-hu…
adrum Oct 31, 2020
0ae6c8a
use new get char logic
adrum Oct 31, 2020
fa2e5bb
fixed tests
adrum Oct 31, 2020
8c3ee09
Merge branch 'add-homekit-humidifer-dehumidifier' into add-homekit-hu…
adrum Oct 31, 2020
14ede68
diffuser tests
adrum Oct 31, 2020
a655bb8
added specific device test
adrum Oct 31, 2020
bdae947
fixed mode and switch logic
adrum Nov 9, 2020
cbe21aa
added set mode tests
adrum Nov 9, 2020
8a94e30
Merge branch 'add-homekit-humidifer-dehumidifier' into add-homekit-hu…
adrum Nov 9, 2020
fc68adb
bump aiohomekit
adrum Nov 9, 2020
ae9e441
removed redundant methods present in base class
adrum Nov 14, 2020
da60c21
Merge branch 'add-homekit-humidifer-dehumidifier' into add-homekit-hu…
adrum Nov 14, 2020
fc80a29
Merge branch 'dev' into add-homekit-humidifier-diffuser
adrum Nov 14, 2020
f1086bc
Merge branch 'dev' into add-homekit-humidifier-diffuser
adrum Nov 14, 2020
97db089
Merge branch 'dev' into add-homekit-humidifier-diffuser
adrum Nov 14, 2020
ac3d981
remove redundant properties
adrum Nov 14, 2020
ad1649e
removed unneeded test
adrum Nov 14, 2020
3139581
reanme flowerbud specific class
adrum Nov 14, 2020
16da871
one more renamed
adrum Nov 14, 2020
a2bf653
Merge branch 'dev' into add-homekit-humidifier-diffuser
adrum Nov 20, 2020
47de760
use new check for characteristics on services
adrum Nov 22, 2020
f9d2f38
removed redundant properties
adrum Nov 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 119 additions & 19 deletions homeassistant/components/homekit_controller/humidifier.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Support for HomeKit Controller humidifier."""
from typing import List, Optional
from typing import Any, Dict, List, Optional

from aiohomekit.model.characteristics import CharacteristicsTypes

from homeassistant.components.humidifier import HumidifierEntity
from homeassistant.components.humidifier.const import (
ATTR_HUMIDITY,
DEVICE_CLASS_DEHUMIDIFIER,
DEVICE_CLASS_HUMIDIFIER,
MODE_AUTO,
Expand Down Expand Up @@ -248,6 +249,92 @@ def unique_id(self) -> str:
return f"homekit-{serial}-{self._iid}-{self.device_class}"


class HomeKitDiffuser(HomeKitEntity, HumidifierEntity):
Comment thread
adrum marked this conversation as resolved.
Outdated
"""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,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like these are all used, can we trim them out?

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 state_attributes(self) -> Dict[str, Any]:
"""Return the optional state attributes."""
data = {
ATTR_HUMIDITY: self.service.value(
CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL
)
* 20,
}

if not self.is_on:
data[ATTR_HUMIDITY] = 0

return data

@property
def available_modes(self) -> Optional[List[str]]:
"""Return a list of available modes.

Requires SUPPORT_MODES.
"""
return []

@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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think aiohomekit clamps this to the minStep automatically so this never sends floats right?

}
)


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Homekit humidifer."""
hkid = config_entry.data["AccessoryPairingID"]
Expand All @@ -266,14 +353,19 @@ def get_service(acc, iid):
return None

def get_char(serv, iid):
try:
type_name = CharacteristicsTypes[iid]
type_uuid = CharacteristicsTypes.get_uuid(type_name)
for char in serv.get("characteristics"):
if char.get("type") == type_uuid:
return char
except KeyError:
return None

if len(iid) == 36:
type_uuid = iid
else:
try:
type_name = CharacteristicsTypes[iid]
type_uuid = CharacteristicsTypes.get_uuid(type_name)
except KeyError:
return None

for char in serv.get("characteristics"):
if char.get("type") == type_uuid:
return char
return None

@callback
Expand All @@ -286,18 +378,26 @@ def async_add_service(aid, service):
serv = get_service(acc, service["iid"])

if (
get_char(serv, CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD)
is not None
):
async_add_entities([HomeKitHumidifier(conn, info)], True)

if (
get_char(
serv, CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD
)
get_char(serv, CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL)
is not None
):
async_add_entities([HomeKitDehumidifier(conn, info)], True)
async_add_entities([HomeKitDiffuser(conn, info)], True)
else:
if (
get_char(
serv, CharacteristicsTypes.RELATIVE_HUMIDITY_HUMIDIFIER_THRESHOLD
)
is not None
):
async_add_entities([HomeKitHumidifier(conn, info)], True)

if (
get_char(
serv, CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD
)
is not None
):
async_add_entities([HomeKitDehumidifier(conn, info)], True)

return True

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""
Test against characteristics captured from a VOCOLinc Flowerbud.

https://github.com/home-assistant/core/issues/26180
"""
from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes

from homeassistant.components.humidifier import DOMAIN

from tests.components.homekit_controller.common import setup_test_component

ACTIVE = ("humidifier-dehumidifier", "active")
CURRENT_HUMIDIFIER_DEHUMIDIFIER_STATE = (
"humidifier-dehumidifier",
"humidifier-dehumidifier.state.current",
)
TARGET_HUMIDIFIER_DEHUMIDIFIER_STATE = (
"humidifier-dehumidifier",
"humidifier-dehumidifier.state.target",
)
RELATIVE_HUMIDITY_CURRENT = ("humidifier-dehumidifier", "relative-humidity.current")

VOCOLINC_HUMIDIFIER_SPRAY_LEVEL = (
"humidifier-dehumidifier",
f"Unknown Characteristic {CharacteristicsTypes.Vendor.VOCOLINC_HUMIDIFIER_SPRAY_LEVEL}",
)


def create_diffuser_service(accessory):
Comment thread
adrum marked this conversation as resolved.
Outdated
"""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_diffuser_read_state(hass, utcnow):
"""Test that we can read the state of a HomeKit diffuser accessory."""
helper = await setup_test_component(hass, create_diffuser_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_diffuser_set_state(hass, utcnow):
"""Test that we can set the state of a HomeKit diffuser accessory."""
helper = await setup_test_component(hass, create_diffuser_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
Loading