Add faucet, shower, sprinkler, valve to HomeKit (#17145)
This commit is contained in:
parent
2e62afabdc
commit
37a47b5a59
7 changed files with 168 additions and 12 deletions
|
@ -24,7 +24,8 @@ from .const import (
|
|||
BRIDGE_NAME, CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FEATURE_LIST,
|
||||
CONF_FILTER, DEFAULT_AUTO_START, DEFAULT_PORT, DEVICE_CLASS_CO,
|
||||
DEVICE_CLASS_CO2, DEVICE_CLASS_PM25, DOMAIN, HOMEKIT_FILE,
|
||||
SERVICE_HOMEKIT_START, TYPE_OUTLET, TYPE_SWITCH)
|
||||
SERVICE_HOMEKIT_START, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER,
|
||||
TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE)
|
||||
from .util import (
|
||||
show_setup_message, validate_entity_config, validate_media_player_features)
|
||||
|
||||
|
@ -41,8 +42,13 @@ STATUS_RUNNING = 1
|
|||
STATUS_STOPPED = 2
|
||||
STATUS_WAIT = 3
|
||||
|
||||
SWITCH_TYPES = {TYPE_OUTLET: 'Outlet',
|
||||
TYPE_SWITCH: 'Switch'}
|
||||
SWITCH_TYPES = {
|
||||
TYPE_FAUCET: 'Valve',
|
||||
TYPE_OUTLET: 'Outlet',
|
||||
TYPE_SHOWER: 'Valve',
|
||||
TYPE_SPRINKLER: 'Valve',
|
||||
TYPE_SWITCH: 'Switch',
|
||||
TYPE_VALVE: 'Valve'}
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.All({
|
||||
|
|
|
@ -32,8 +32,12 @@ BRIDGE_SERIAL_NUMBER = 'homekit.bridge'
|
|||
MANUFACTURER = 'Home Assistant'
|
||||
|
||||
# #### Switch Types ####
|
||||
TYPE_FAUCET = 'faucet'
|
||||
TYPE_OUTLET = 'outlet'
|
||||
TYPE_SHOWER = 'shower'
|
||||
TYPE_SPRINKLER = 'sprinkler'
|
||||
TYPE_SWITCH = 'switch'
|
||||
TYPE_VALVE = 'valve'
|
||||
|
||||
# #### Services ####
|
||||
SERV_ACCESSORY_INFO = 'AccessoryInformation'
|
||||
|
@ -57,6 +61,7 @@ SERV_SMOKE_SENSOR = 'SmokeSensor'
|
|||
SERV_SWITCH = 'Switch'
|
||||
SERV_TEMPERATURE_SENSOR = 'TemperatureSensor'
|
||||
SERV_THERMOSTAT = 'Thermostat'
|
||||
SERV_VALVE = 'Valve'
|
||||
SERV_WINDOW_COVERING = 'WindowCovering'
|
||||
|
||||
# #### Characteristics ####
|
||||
|
@ -85,6 +90,7 @@ CHAR_CURRENT_TEMPERATURE = 'CurrentTemperature'
|
|||
CHAR_FIRMWARE_REVISION = 'FirmwareRevision'
|
||||
CHAR_HEATING_THRESHOLD_TEMPERATURE = 'HeatingThresholdTemperature'
|
||||
CHAR_HUE = 'Hue'
|
||||
CHAR_IN_USE = 'InUse'
|
||||
CHAR_LEAK_DETECTED = 'LeakDetected'
|
||||
CHAR_LOCK_CURRENT_STATE = 'LockCurrentState'
|
||||
CHAR_LOCK_TARGET_STATE = 'LockTargetState'
|
||||
|
@ -109,6 +115,7 @@ CHAR_TARGET_POSITION = 'TargetPosition'
|
|||
CHAR_TARGET_SECURITY_STATE = 'SecuritySystemTargetState'
|
||||
CHAR_TARGET_TEMPERATURE = 'TargetTemperature'
|
||||
CHAR_TEMP_DISPLAY_UNITS = 'TemperatureDisplayUnits'
|
||||
CHAR_VALVE_TYPE = 'ValveType'
|
||||
|
||||
# #### Properties ####
|
||||
PROP_MAX_VALUE = 'maxValue'
|
||||
|
|
|
@ -5,15 +5,29 @@ from pyhap.const import CATEGORY_OUTLET, CATEGORY_SWITCH
|
|||
|
||||
from homeassistant.components.switch import DOMAIN
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON)
|
||||
ATTR_ENTITY_ID, CONF_TYPE, SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON)
|
||||
from homeassistant.core import split_entity_id
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import HomeAccessory
|
||||
from .const import CHAR_ON, CHAR_OUTLET_IN_USE, SERV_OUTLET, SERV_SWITCH
|
||||
from .const import (
|
||||
CHAR_ACTIVE, CHAR_IN_USE, CHAR_ON, CHAR_OUTLET_IN_USE, CHAR_VALVE_TYPE,
|
||||
SERV_OUTLET, SERV_SWITCH, SERV_VALVE, TYPE_FAUCET, TYPE_SHOWER,
|
||||
TYPE_SPRINKLER, TYPE_VALVE)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CATEGORY_SPRINKLER = 28
|
||||
CATEGORY_FAUCET = 29
|
||||
CATEGORY_SHOWER_HEAD = 30
|
||||
|
||||
VALVE_TYPE = {
|
||||
TYPE_FAUCET: (CATEGORY_FAUCET, 3),
|
||||
TYPE_SHOWER: (CATEGORY_SHOWER_HEAD, 2),
|
||||
TYPE_SPRINKLER: (CATEGORY_SPRINKLER, 1),
|
||||
TYPE_VALVE: (CATEGORY_FAUCET, 0),
|
||||
}
|
||||
|
||||
|
||||
@TYPES.register('Outlet')
|
||||
class Outlet(HomeAccessory):
|
||||
|
@ -80,3 +94,43 @@ class Switch(HomeAccessory):
|
|||
self.entity_id, current_state)
|
||||
self.char_on.set_value(current_state)
|
||||
self.flag_target_state = False
|
||||
|
||||
|
||||
@TYPES.register('Valve')
|
||||
class Valve(HomeAccessory):
|
||||
"""Generate a Valve accessory."""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Initialize a Valve accessory object."""
|
||||
super().__init__(*args)
|
||||
self.flag_target_state = False
|
||||
valve_type = self.config[CONF_TYPE]
|
||||
self.category = VALVE_TYPE[valve_type][0]
|
||||
|
||||
serv_valve = self.add_preload_service(SERV_VALVE)
|
||||
self.char_active = serv_valve.configure_char(
|
||||
CHAR_ACTIVE, value=False, setter_callback=self.set_state)
|
||||
self.char_in_use = serv_valve.configure_char(
|
||||
CHAR_IN_USE, value=False)
|
||||
self.char_valve_type = serv_valve.configure_char(
|
||||
CHAR_VALVE_TYPE, value=VALVE_TYPE[valve_type][1])
|
||||
|
||||
def set_state(self, value):
|
||||
"""Move value state to value if call came from HomeKit."""
|
||||
_LOGGER.debug('%s: Set switch state to %s',
|
||||
self.entity_id, value)
|
||||
self.flag_target_state = True
|
||||
self.char_in_use.set_value(value)
|
||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
|
||||
self.hass.services.call(DOMAIN, service, params)
|
||||
|
||||
def update_state(self, new_state):
|
||||
"""Update switch state after state changed."""
|
||||
current_state = (new_state.state == STATE_ON)
|
||||
if not self.flag_target_state:
|
||||
_LOGGER.debug('%s: Set current state to %s',
|
||||
self.entity_id, current_state)
|
||||
self.char_active.set_value(current_state)
|
||||
self.char_in_use.set_value(current_state)
|
||||
self.flag_target_state = False
|
||||
|
|
|
@ -11,8 +11,8 @@ import homeassistant.helpers.config_validation as cv
|
|||
import homeassistant.util.temperature as temp_util
|
||||
from .const import (
|
||||
CONF_FEATURE, CONF_FEATURE_LIST, HOMEKIT_NOTIFY_ID, FEATURE_ON_OFF,
|
||||
FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, TYPE_OUTLET,
|
||||
TYPE_SWITCH)
|
||||
FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, TYPE_FAUCET,
|
||||
TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -38,7 +38,9 @@ MEDIA_PLAYER_SCHEMA = vol.Schema({
|
|||
|
||||
SWITCH_TYPE_SCHEMA = BASIC_INFO_SCHEMA.extend({
|
||||
vol.Optional(CONF_TYPE, default=TYPE_SWITCH): vol.All(
|
||||
cv.string, vol.In((TYPE_OUTLET, TYPE_SWITCH))),
|
||||
cv.string, vol.In((
|
||||
TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER,
|
||||
TYPE_SWITCH, TYPE_VALVE))),
|
||||
})
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ import homeassistant.components.climate as climate
|
|||
import homeassistant.components.media_player as media_player
|
||||
from homeassistant.components.homekit import get_accessory, TYPES
|
||||
from homeassistant.components.homekit.const import (
|
||||
CONF_FEATURE_LIST, FEATURE_ON_OFF, TYPE_OUTLET, TYPE_SWITCH)
|
||||
CONF_FEATURE_LIST, FEATURE_ON_OFF, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER,
|
||||
TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE)
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE, ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES,
|
||||
ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, CONF_TYPE, TEMP_CELSIUS,
|
||||
|
@ -140,6 +141,10 @@ def test_type_sensors(type_name, entity_id, state, attrs):
|
|||
('Switch', 'script.test', 'on', {}, {}),
|
||||
('Switch', 'switch.test', 'on', {}, {}),
|
||||
('Switch', 'switch.test', 'on', {}, {CONF_TYPE: TYPE_SWITCH}),
|
||||
('Valve', 'switch.test', 'on', {}, {CONF_TYPE: TYPE_FAUCET}),
|
||||
('Valve', 'switch.test', 'on', {}, {CONF_TYPE: TYPE_VALVE}),
|
||||
('Valve', 'switch.test', 'on', {}, {CONF_TYPE: TYPE_SHOWER}),
|
||||
('Valve', 'switch.test', 'on', {}, {CONF_TYPE: TYPE_SPRINKLER}),
|
||||
])
|
||||
def test_type_switches(type_name, entity_id, state, attrs, config):
|
||||
"""Test if switch types are associated correctly."""
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
"""Test different accessory types: Switches."""
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.homekit.type_switches import Outlet, Switch
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
|
||||
from homeassistant.components.homekit.const import (
|
||||
TYPE_FAUCET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_VALVE)
|
||||
from homeassistant.components.homekit.type_switches import (
|
||||
Outlet, Switch, Valve)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_TYPE, STATE_OFF, STATE_ON
|
||||
from homeassistant.core import split_entity_id
|
||||
|
||||
from tests.common import async_mock_service
|
||||
|
@ -90,3 +93,70 @@ async def test_switch_set_state(hass, hk_driver, entity_id):
|
|||
await hass.async_block_till_done()
|
||||
assert call_turn_off
|
||||
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
|
||||
|
||||
async def test_valve_set_state(hass, hk_driver):
|
||||
"""Test if Valve accessory and HA are updated accordingly."""
|
||||
entity_id = 'switch.valve_test'
|
||||
|
||||
hass.states.async_set(entity_id, None)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
acc = Valve(hass, hk_driver, 'Valve', entity_id, 2,
|
||||
{CONF_TYPE: TYPE_FAUCET})
|
||||
await hass.async_add_job(acc.run)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.category == 29 # Faucet
|
||||
assert acc.char_valve_type.value == 3 # Water faucet
|
||||
|
||||
acc = Valve(hass, hk_driver, 'Valve', entity_id, 2,
|
||||
{CONF_TYPE: TYPE_SHOWER})
|
||||
await hass.async_add_job(acc.run)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.category == 30 # Shower
|
||||
assert acc.char_valve_type.value == 2 # Shower head
|
||||
|
||||
acc = Valve(hass, hk_driver, 'Valve', entity_id, 2,
|
||||
{CONF_TYPE: TYPE_SPRINKLER})
|
||||
await hass.async_add_job(acc.run)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.category == 28 # Sprinkler
|
||||
assert acc.char_valve_type.value == 1 # Irrigation
|
||||
|
||||
acc = Valve(hass, hk_driver, 'Valve', entity_id, 2,
|
||||
{CONF_TYPE: TYPE_VALVE})
|
||||
await hass.async_add_job(acc.run)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert acc.aid == 2
|
||||
assert acc.category == 29 # Faucet
|
||||
|
||||
assert acc.char_active.value is False
|
||||
assert acc.char_in_use.value is False
|
||||
assert acc.char_valve_type.value == 0 # Generic Valve
|
||||
|
||||
hass.states.async_set(entity_id, STATE_ON)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_active.value is True
|
||||
assert acc.char_in_use.value is True
|
||||
|
||||
hass.states.async_set(entity_id, STATE_OFF)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_active.value is False
|
||||
assert acc.char_in_use.value is False
|
||||
|
||||
# Set from HomeKit
|
||||
call_turn_on = async_mock_service(hass, 'switch', 'turn_on')
|
||||
call_turn_off = async_mock_service(hass, 'switch', 'turn_off')
|
||||
|
||||
await hass.async_add_job(acc.char_active.client_update_value, True)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_in_use.value is True
|
||||
assert call_turn_on
|
||||
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
|
||||
await hass.async_add_job(acc.char_active.client_update_value, False)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_in_use.value is False
|
||||
assert call_turn_off
|
||||
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
|
|
|
@ -4,7 +4,8 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.components.homekit.const import (
|
||||
CONF_FEATURE, CONF_FEATURE_LIST, HOMEKIT_NOTIFY_ID, FEATURE_ON_OFF,
|
||||
FEATURE_PLAY_PAUSE, TYPE_OUTLET)
|
||||
FEATURE_PLAY_PAUSE, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER,
|
||||
TYPE_SWITCH, TYPE_VALVE)
|
||||
from homeassistant.components.homekit.util import (
|
||||
convert_to_float, density_to_air_quality, dismiss_setup_message,
|
||||
show_setup_message, temperature_to_homekit, temperature_to_states,
|
||||
|
@ -58,8 +59,19 @@ def test_validate_entity_config():
|
|||
assert vec({'media_player.demo': config}) == \
|
||||
{'media_player.demo': {CONF_FEATURE_LIST:
|
||||
{FEATURE_ON_OFF: {}, FEATURE_PLAY_PAUSE: {}}}}
|
||||
|
||||
assert vec({'switch.demo': {CONF_TYPE: TYPE_FAUCET}}) == \
|
||||
{'switch.demo': {CONF_TYPE: TYPE_FAUCET}}
|
||||
assert vec({'switch.demo': {CONF_TYPE: TYPE_OUTLET}}) == \
|
||||
{'switch.demo': {CONF_TYPE: TYPE_OUTLET}}
|
||||
assert vec({'switch.demo': {CONF_TYPE: TYPE_SHOWER}}) == \
|
||||
{'switch.demo': {CONF_TYPE: TYPE_SHOWER}}
|
||||
assert vec({'switch.demo': {CONF_TYPE: TYPE_SPRINKLER}}) == \
|
||||
{'switch.demo': {CONF_TYPE: TYPE_SPRINKLER}}
|
||||
assert vec({'switch.demo': {CONF_TYPE: TYPE_SWITCH}}) == \
|
||||
{'switch.demo': {CONF_TYPE: TYPE_SWITCH}}
|
||||
assert vec({'switch.demo': {CONF_TYPE: TYPE_VALVE}}) == \
|
||||
{'switch.demo': {CONF_TYPE: TYPE_VALVE}}
|
||||
|
||||
|
||||
def test_validate_media_player_features():
|
||||
|
|
Loading…
Add table
Reference in a new issue