Homekit Update, Support for TempSensor (°F) (#12676)
* Changed version of "HAP-python" to "v1.1.7" * Updated acc file to simplify init calls * Code refactored and '°F' temp Sensors added * Changed call to 'HomeAccessory' and 'HomeBridge' * Extended function of 'add_preload_service' to add additional characteristics * Added function to override characteristic property values * TemperatureSensor * Added unit * Added calc_temperature * Updated tests
This commit is contained in:
parent
347ba1a2d8
commit
27b1d448a3
8 changed files with 120 additions and 74 deletions
|
@ -11,7 +11,8 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.const import (
|
||||
ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, CONF_PORT,
|
||||
TEMP_CELSIUS, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||
TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.util import get_local_ip
|
||||
from homeassistant.util.decorator import Registry
|
||||
|
||||
|
@ -21,7 +22,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
_RE_VALID_PINCODE = re.compile(r"^(\d{3}-\d{2}-\d{3})$")
|
||||
|
||||
DOMAIN = 'homekit'
|
||||
REQUIREMENTS = ['HAP-python==1.1.5']
|
||||
REQUIREMENTS = ['HAP-python==1.1.7']
|
||||
|
||||
BRIDGE_NAME = 'Home Assistant'
|
||||
CONF_PIN_CODE = 'pincode'
|
||||
|
@ -74,7 +75,8 @@ def import_types():
|
|||
def get_accessory(hass, state):
|
||||
"""Take state and return an accessory object if supported."""
|
||||
if state.domain == 'sensor':
|
||||
if state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS:
|
||||
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
if unit == TEMP_CELSIUS or unit == TEMP_FAHRENHEIT:
|
||||
_LOGGER.debug("Add \"%s\" as \"%s\"",
|
||||
state.entity_id, 'TemperatureSensor')
|
||||
return TYPES['TemperatureSensor'](hass, state.entity_id,
|
||||
|
@ -103,8 +105,7 @@ class HomeKit():
|
|||
def setup_bridge(self, pin):
|
||||
"""Setup the bridge component to track all accessories."""
|
||||
from .accessories import HomeBridge
|
||||
self.bridge = HomeBridge(BRIDGE_NAME, pincode=pin)
|
||||
self.bridge.set_accessory_info('homekit.bridge')
|
||||
self.bridge = HomeBridge(BRIDGE_NAME, 'homekit.bridge', pin)
|
||||
|
||||
def start_driver(self, event):
|
||||
"""Start the accessory driver."""
|
||||
|
|
|
@ -1,55 +1,62 @@
|
|||
"""Extend the basic Accessory and Bridge functions."""
|
||||
import logging
|
||||
|
||||
from pyhap.accessory import Accessory, Bridge, Category
|
||||
|
||||
from .const import (
|
||||
SERVICES_ACCESSORY_INFO, MANUFACTURER,
|
||||
SERV_ACCESSORY_INFO, MANUFACTURER,
|
||||
CHAR_MODEL, CHAR_MANUFACTURER, CHAR_SERIAL_NUMBER)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def set_accessory_info(acc, model, manufacturer=MANUFACTURER,
|
||||
serial_number='0000'):
|
||||
"""Set the default accessory information."""
|
||||
service = acc.get_service(SERV_ACCESSORY_INFO)
|
||||
service.get_characteristic(CHAR_MODEL).set_value(model)
|
||||
service.get_characteristic(CHAR_MANUFACTURER).set_value(manufacturer)
|
||||
service.get_characteristic(CHAR_SERIAL_NUMBER).set_value(serial_number)
|
||||
|
||||
|
||||
def add_preload_service(acc, service, chars=None, opt_chars=None):
|
||||
"""Define and return a service to be available for the accessory."""
|
||||
from pyhap.loader import get_serv_loader, get_char_loader
|
||||
service = get_serv_loader().get(service)
|
||||
if chars:
|
||||
chars = chars if isinstance(chars, list) else [chars]
|
||||
for char_name in chars:
|
||||
char = get_char_loader().get(char_name)
|
||||
service.add_characteristic(char)
|
||||
if opt_chars:
|
||||
opt_chars = opt_chars if isinstance(opt_chars, list) else [opt_chars]
|
||||
for opt_char_name in opt_chars:
|
||||
opt_char = get_char_loader().get(opt_char_name)
|
||||
service.add_opt_characteristic(opt_char)
|
||||
acc.add_service(service)
|
||||
return service
|
||||
|
||||
|
||||
def override_properties(char, new_properties):
|
||||
"""Override characteristic property values."""
|
||||
char.properties.update(new_properties)
|
||||
|
||||
|
||||
class HomeAccessory(Accessory):
|
||||
"""Class to extend the Accessory class."""
|
||||
|
||||
ALL_CATEGORIES = Category
|
||||
|
||||
def __init__(self, display_name):
|
||||
def __init__(self, display_name, model, category='OTHER'):
|
||||
"""Initialize a Accessory object."""
|
||||
super().__init__(display_name)
|
||||
|
||||
def set_category(self, category):
|
||||
"""Set the category of the accessory."""
|
||||
self.category = category
|
||||
|
||||
def add_preload_service(self, service):
|
||||
"""Define the services to be available for the accessory."""
|
||||
from pyhap.loader import get_serv_loader
|
||||
self.add_service(get_serv_loader().get(service))
|
||||
|
||||
def set_accessory_info(self, model, manufacturer=MANUFACTURER,
|
||||
serial_number='0000'):
|
||||
"""Set the default accessory information."""
|
||||
service_info = self.get_service(SERVICES_ACCESSORY_INFO)
|
||||
service_info.get_characteristic(CHAR_MODEL) \
|
||||
.set_value(model)
|
||||
service_info.get_characteristic(CHAR_MANUFACTURER) \
|
||||
.set_value(manufacturer)
|
||||
service_info.get_characteristic(CHAR_SERIAL_NUMBER) \
|
||||
.set_value(serial_number)
|
||||
set_accessory_info(self, model)
|
||||
self.category = getattr(Category, category, Category.OTHER)
|
||||
|
||||
|
||||
class HomeBridge(Bridge):
|
||||
"""Class to extend the Bridge class."""
|
||||
|
||||
def __init__(self, display_name, pincode):
|
||||
def __init__(self, display_name, model, pincode):
|
||||
"""Initialize a Bridge object."""
|
||||
super().__init__(display_name, pincode=pincode)
|
||||
|
||||
def set_accessory_info(self, model, manufacturer=MANUFACTURER,
|
||||
serial_number='0000'):
|
||||
"""Set the default accessory information."""
|
||||
service_info = self.get_service(SERVICES_ACCESSORY_INFO)
|
||||
service_info.get_characteristic(CHAR_MODEL) \
|
||||
.set_value(model)
|
||||
service_info.get_characteristic(CHAR_MANUFACTURER) \
|
||||
.set_value(manufacturer)
|
||||
service_info.get_characteristic(CHAR_SERIAL_NUMBER) \
|
||||
.set_value(serial_number)
|
||||
set_accessory_info(self, model)
|
||||
|
|
|
@ -2,17 +2,20 @@
|
|||
MANUFACTURER = 'HomeAssistant'
|
||||
|
||||
# Service: AccessoryInfomation
|
||||
SERVICES_ACCESSORY_INFO = 'AccessoryInformation'
|
||||
SERV_ACCESSORY_INFO = 'AccessoryInformation'
|
||||
CHAR_MODEL = 'Model'
|
||||
CHAR_MANUFACTURER = 'Manufacturer'
|
||||
CHAR_SERIAL_NUMBER = 'SerialNumber'
|
||||
|
||||
# Service: TemperatureSensor
|
||||
SERVICES_TEMPERATURE_SENSOR = 'TemperatureSensor'
|
||||
SERV_TEMPERATURE_SENSOR = 'TemperatureSensor'
|
||||
CHAR_CURRENT_TEMPERATURE = 'CurrentTemperature'
|
||||
|
||||
# Service: WindowCovering
|
||||
SERVICES_WINDOW_COVERING = 'WindowCovering'
|
||||
SERV_WINDOW_COVERING = 'WindowCovering'
|
||||
CHAR_CURRENT_POSITION = 'CurrentPosition'
|
||||
CHAR_TARGET_POSITION = 'TargetPosition'
|
||||
CHAR_POSITION_STATE = 'PositionState'
|
||||
|
||||
# Properties
|
||||
PROP_CELSIUS = {'minValue': -273, 'maxValue': 999}
|
||||
|
|
|
@ -5,9 +5,9 @@ from homeassistant.components.cover import ATTR_CURRENT_POSITION
|
|||
from homeassistant.helpers.event import async_track_state_change
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import HomeAccessory
|
||||
from .accessories import HomeAccessory, add_preload_service
|
||||
from .const import (
|
||||
SERVICES_WINDOW_COVERING, CHAR_CURRENT_POSITION,
|
||||
SERV_WINDOW_COVERING, CHAR_CURRENT_POSITION,
|
||||
CHAR_TARGET_POSITION, CHAR_POSITION_STATE)
|
||||
|
||||
|
||||
|
@ -23,10 +23,7 @@ class Window(HomeAccessory):
|
|||
|
||||
def __init__(self, hass, entity_id, display_name):
|
||||
"""Initialize a Window accessory object."""
|
||||
super().__init__(display_name)
|
||||
self.set_category(self.ALL_CATEGORIES.WINDOW)
|
||||
self.set_accessory_info(entity_id)
|
||||
self.add_preload_service(SERVICES_WINDOW_COVERING)
|
||||
super().__init__(display_name, entity_id, 'WINDOW')
|
||||
|
||||
self._hass = hass
|
||||
self._entity_id = entity_id
|
||||
|
@ -34,12 +31,12 @@ class Window(HomeAccessory):
|
|||
self.current_position = None
|
||||
self.homekit_target = None
|
||||
|
||||
self.service_cover = self.get_service(SERVICES_WINDOW_COVERING)
|
||||
self.char_current_position = self.service_cover. \
|
||||
self.serv_cover = add_preload_service(self, SERV_WINDOW_COVERING)
|
||||
self.char_current_position = self.serv_cover. \
|
||||
get_characteristic(CHAR_CURRENT_POSITION)
|
||||
self.char_target_position = self.service_cover. \
|
||||
self.char_target_position = self.serv_cover. \
|
||||
get_characteristic(CHAR_TARGET_POSITION)
|
||||
self.char_position_state = self.service_cover. \
|
||||
self.char_position_state = self.serv_cover. \
|
||||
get_characteristic(CHAR_POSITION_STATE)
|
||||
|
||||
self.char_target_position.setter_callback = self.move_cover
|
||||
|
@ -53,7 +50,7 @@ class Window(HomeAccessory):
|
|||
self._hass, self._entity_id, self.update_cover_position)
|
||||
|
||||
def move_cover(self, value):
|
||||
"""Move cover to value if call came from homekit."""
|
||||
"""Move cover to value if call came from HomeKit."""
|
||||
if value != self.current_position:
|
||||
_LOGGER.debug("%s: Set position to %d", self._entity_id, value)
|
||||
self.homekit_target = value
|
||||
|
|
|
@ -1,38 +1,55 @@
|
|||
"""Class to hold all sensor accessories."""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import STATE_UNKNOWN
|
||||
from homeassistant.const import (
|
||||
STATE_UNKNOWN, ATTR_UNIT_OF_MEASUREMENT, TEMP_FAHRENHEIT, TEMP_CELSIUS)
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import HomeAccessory
|
||||
from .accessories import (
|
||||
HomeAccessory, add_preload_service, override_properties)
|
||||
from .const import (
|
||||
SERVICES_TEMPERATURE_SENSOR, CHAR_CURRENT_TEMPERATURE)
|
||||
SERV_TEMPERATURE_SENSOR, CHAR_CURRENT_TEMPERATURE, PROP_CELSIUS)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def calc_temperature(state, unit=TEMP_CELSIUS):
|
||||
"""Calculate temperature from state and unit.
|
||||
|
||||
Always return temperature as Celsius value.
|
||||
Conversion is handled on the device.
|
||||
"""
|
||||
if state == STATE_UNKNOWN:
|
||||
return None
|
||||
|
||||
if unit == TEMP_FAHRENHEIT:
|
||||
value = round((float(state) - 32) / 1.8, 2)
|
||||
else:
|
||||
value = float(state)
|
||||
return value
|
||||
|
||||
|
||||
@TYPES.register('TemperatureSensor')
|
||||
class TemperatureSensor(HomeAccessory):
|
||||
"""Generate a TemperatureSensor accessory for a temperature sensor.
|
||||
|
||||
Sensor entity must return either temperature in °C or STATE_UNKNOWN.
|
||||
Sensor entity must return temperature in °C, °F or STATE_UNKNOWN.
|
||||
"""
|
||||
|
||||
def __init__(self, hass, entity_id, display_name):
|
||||
"""Initialize a TemperatureSensor accessory object."""
|
||||
super().__init__(display_name)
|
||||
self.set_category(self.ALL_CATEGORIES.SENSOR)
|
||||
self.set_accessory_info(entity_id)
|
||||
self.add_preload_service(SERVICES_TEMPERATURE_SENSOR)
|
||||
super().__init__(display_name, entity_id, 'SENSOR')
|
||||
|
||||
self._hass = hass
|
||||
self._entity_id = entity_id
|
||||
|
||||
self.service_temp = self.get_service(SERVICES_TEMPERATURE_SENSOR)
|
||||
self.char_temp = self.service_temp. \
|
||||
self.serv_temp = add_preload_service(self, SERV_TEMPERATURE_SENSOR)
|
||||
self.char_temp = self.serv_temp. \
|
||||
get_characteristic(CHAR_CURRENT_TEMPERATURE)
|
||||
override_properties(self.char_temp, PROP_CELSIUS)
|
||||
self.unit = None
|
||||
|
||||
def run(self):
|
||||
"""Method called be object after driver is started."""
|
||||
|
@ -48,6 +65,9 @@ class TemperatureSensor(HomeAccessory):
|
|||
if new_state is None:
|
||||
return
|
||||
|
||||
temperature = new_state.state
|
||||
if temperature != STATE_UNKNOWN:
|
||||
self.char_temp.set_value(float(temperature))
|
||||
unit = new_state.attributes[ATTR_UNIT_OF_MEASUREMENT]
|
||||
temperature = calc_temperature(new_state.state, unit)
|
||||
if temperature is not None:
|
||||
self.char_temp.set_value(temperature)
|
||||
_LOGGER.debug("%s: Current temperature set to %d°C",
|
||||
self._entity_id, temperature)
|
||||
|
|
|
@ -24,7 +24,7 @@ attrs==17.4.0
|
|||
DoorBirdPy==0.1.2
|
||||
|
||||
# homeassistant.components.homekit
|
||||
HAP-python==1.1.5
|
||||
HAP-python==1.1.7
|
||||
|
||||
# homeassistant.components.isy994
|
||||
PyISY==1.1.0
|
||||
|
|
|
@ -19,7 +19,7 @@ asynctest>=0.11.1
|
|||
|
||||
|
||||
# homeassistant.components.homekit
|
||||
HAP-python==1.1.5
|
||||
HAP-python==1.1.7
|
||||
|
||||
# homeassistant.components.notify.html5
|
||||
PyJWT==1.5.3
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
"""Test different accessory types: Sensors."""
|
||||
import unittest
|
||||
|
||||
from homeassistant.components.homekit.sensors import TemperatureSensor
|
||||
from homeassistant.components.homekit.sensors import (
|
||||
TemperatureSensor, calc_temperature)
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, STATE_UNKNOWN)
|
||||
ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_UNKNOWN)
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
def test_calc_temperature():
|
||||
"""Test if temperature in Celsius is calculated correctly."""
|
||||
assert calc_temperature(STATE_UNKNOWN) is None
|
||||
|
||||
assert calc_temperature('20') == 20
|
||||
assert calc_temperature('20.12', TEMP_CELSIUS) == 20.12
|
||||
|
||||
assert calc_temperature('75.2', TEMP_FAHRENHEIT) == 24
|
||||
assert calc_temperature('-20.6', TEMP_FAHRENHEIT) == -29.22
|
||||
|
||||
|
||||
class TestHomekitSensors(unittest.TestCase):
|
||||
"""Test class for all accessory types regarding sensors."""
|
||||
|
||||
|
@ -16,7 +28,7 @@ class TestHomekitSensors(unittest.TestCase):
|
|||
self.hass = get_test_home_assistant()
|
||||
|
||||
def tearDown(self):
|
||||
"""Stop down everthing that was started."""
|
||||
"""Stop down everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_temperature_celsius(self):
|
||||
|
@ -32,6 +44,12 @@ class TestHomekitSensors(unittest.TestCase):
|
|||
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
|
||||
self.hass.block_till_done()
|
||||
|
||||
self.hass.states.set(temperature_sensor, '20')
|
||||
self.hass.states.set(temperature_sensor, '20',
|
||||
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_temp.value, 20)
|
||||
|
||||
self.hass.states.set(temperature_sensor, '75.2',
|
||||
{ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT})
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_temp.value, 24)
|
||||
|
|
Loading…
Add table
Reference in a new issue