Axis - prepare for config entry (#17566)

Make component more in line with other more modern components in preparation for config entry support.
pull/17938/head
Robert Svensson 2018-10-29 06:52:30 +01:00 committed by GitHub
parent 3802fec568
commit 4579717317
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 106 additions and 118 deletions

View File

@ -10,24 +10,21 @@ import voluptuous as vol
from homeassistant.components.discovery import SERVICE_AXIS from homeassistant.components.discovery import SERVICE_AXIS
from homeassistant.const import ( from homeassistant.const import (
ATTR_LOCATION, ATTR_TRIPPED, CONF_EVENT, CONF_HOST, CONF_INCLUDE, ATTR_LOCATION, CONF_EVENT, CONF_HOST, CONF_INCLUDE,
CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TRIGGER_TIME, CONF_USERNAME, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TRIGGER_TIME, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP) EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.entity import Entity
from homeassistant.util.json import load_json, save_json from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['axis==14'] REQUIREMENTS = ['axis==16']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = 'axis' DOMAIN = 'axis'
CONFIG_FILE = 'axis.conf' CONFIG_FILE = 'axis.conf'
AXIS_DEVICES = {}
EVENT_TYPES = ['motion', 'vmd3', 'pir', 'sound', EVENT_TYPES = ['motion', 'vmd3', 'pir', 'sound',
'daynight', 'tampering', 'input'] 'daynight', 'tampering', 'input']
@ -99,8 +96,6 @@ def request_configuration(hass, config, name, host, serialnumber):
return False return False
if setup_device(hass, config, device_config): if setup_device(hass, config, device_config):
del device_config['events']
del device_config['signal']
config_file = load_json(hass.config.path(CONFIG_FILE)) config_file = load_json(hass.config.path(CONFIG_FILE))
config_file[serialnumber] = dict(device_config) config_file[serialnumber] = dict(device_config)
save_json(hass.config.path(CONFIG_FILE), config_file) save_json(hass.config.path(CONFIG_FILE), config_file)
@ -146,9 +141,11 @@ def request_configuration(hass, config, name, host, serialnumber):
def setup(hass, config): def setup(hass, config):
"""Set up for Axis devices.""" """Set up for Axis devices."""
hass.data[DOMAIN] = {}
def _shutdown(call): def _shutdown(call):
"""Stop the event stream on shutdown.""" """Stop the event stream on shutdown."""
for serialnumber, device in AXIS_DEVICES.items(): for serialnumber, device in hass.data[DOMAIN].items():
_LOGGER.info("Stopping event stream for %s.", serialnumber) _LOGGER.info("Stopping event stream for %s.", serialnumber)
device.stop() device.stop()
@ -160,7 +157,7 @@ def setup(hass, config):
name = discovery_info['hostname'] name = discovery_info['hostname']
serialnumber = discovery_info['properties']['macaddress'] serialnumber = discovery_info['properties']['macaddress']
if serialnumber not in AXIS_DEVICES: if serialnumber not in hass.data[DOMAIN]:
config_file = load_json(hass.config.path(CONFIG_FILE)) config_file = load_json(hass.config.path(CONFIG_FILE))
if serialnumber in config_file: if serialnumber in config_file:
# Device config previously saved to file # Device config previously saved to file
@ -178,7 +175,7 @@ def setup(hass, config):
request_configuration(hass, config, name, host, serialnumber) request_configuration(hass, config, name, host, serialnumber)
else: else:
# Device already registered, but on a different IP # Device already registered, but on a different IP
device = AXIS_DEVICES[serialnumber] device = hass.data[DOMAIN][serialnumber]
device.config.host = host device.config.host = host
dispatcher_send(hass, DOMAIN + '_' + device.name + '_new_ip', host) dispatcher_send(hass, DOMAIN + '_' + device.name + '_new_ip', host)
@ -195,7 +192,7 @@ def setup(hass, config):
def vapix_service(call): def vapix_service(call):
"""Service to send a message.""" """Service to send a message."""
for _, device in AXIS_DEVICES.items(): for device in hass.data[DOMAIN].values():
if device.name == call.data[CONF_NAME]: if device.name == call.data[CONF_NAME]:
response = device.vapix.do_request( response = device.vapix.do_request(
call.data[SERVICE_CGI], call.data[SERVICE_CGI],
@ -214,7 +211,7 @@ def setup(hass, config):
def setup_device(hass, config, device_config): def setup_device(hass, config, device_config):
"""Set up an Axis device.""" """Set up an Axis device."""
from axis import AxisDevice import axis
def signal_callback(action, event): def signal_callback(action, event):
"""Call to configure events when initialized on event stream.""" """Call to configure events when initialized on event stream."""
@ -229,18 +226,32 @@ def setup_device(hass, config, device_config):
discovery.load_platform( discovery.load_platform(
hass, component, DOMAIN, event_config, config) hass, component, DOMAIN, event_config, config)
event_types = list(filter(lambda x: x in device_config[CONF_INCLUDE], event_types = [
EVENT_TYPES)) event
device_config['events'] = event_types for event in device_config[CONF_INCLUDE]
device_config['signal'] = signal_callback if event in EVENT_TYPES
device = AxisDevice(hass.loop, **device_config) ]
device.name = device_config[CONF_NAME]
if device.serial_number is None: device = axis.AxisDevice(
# If there is no serial number a connection could not be made loop=hass.loop, host=device_config[CONF_HOST],
_LOGGER.error("Couldn't connect to %s", device_config[CONF_HOST]) username=device_config[CONF_USERNAME],
password=device_config[CONF_PASSWORD],
port=device_config[CONF_PORT], web_proto='http',
event_types=event_types, signal=signal_callback)
try:
hass.data[DOMAIN][device.vapix.serial_number] = device
except axis.Unauthorized:
_LOGGER.error("Credentials for %s are faulty",
device_config[CONF_HOST])
return False return False
except axis.RequestError:
return False
device.name = device_config[CONF_NAME]
for component in device_config[CONF_INCLUDE]: for component in device_config[CONF_INCLUDE]:
if component == 'camera': if component == 'camera':
camera_config = { camera_config = {
@ -253,51 +264,6 @@ def setup_device(hass, config, device_config):
discovery.load_platform( discovery.load_platform(
hass, component, DOMAIN, camera_config, config) hass, component, DOMAIN, camera_config, config)
AXIS_DEVICES[device.serial_number] = device
if event_types: if event_types:
hass.add_job(device.start) hass.add_job(device.start)
return True return True
class AxisDeviceEvent(Entity):
"""Representation of a Axis device event."""
def __init__(self, event_config):
"""Initialize the event."""
self.axis_event = event_config[CONF_EVENT]
self._name = '{}_{}_{}'.format(
event_config[CONF_NAME], self.axis_event.event_type,
self.axis_event.id)
self.location = event_config[ATTR_LOCATION]
self.axis_event.callback = self._update_callback
def _update_callback(self):
"""Update the sensor's state, if needed."""
self.schedule_update_ha_state(True)
@property
def name(self):
"""Return the name of the event."""
return self._name
@property
def device_class(self):
"""Return the class of the event."""
return self.axis_event.event_class
@property
def should_poll(self):
"""Return the polling state. No polling needed."""
return False
@property
def device_state_attributes(self):
"""Return the state attributes of the event."""
attr = {}
tripped = self.axis_event.is_tripped
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
attr[ATTR_LOCATION] = self.location
return attr

View File

@ -0,0 +1,15 @@
vapix_call:
description: Configure device using Vapix parameter management.
fields:
name:
description: Name of device to Configure. [Required]
example: M1065-W
cgi:
description: Which cgi to call on device. [Optional] Default is 'param.cgi'
example: 'applications/control.cgi'
action:
description: What type of call. [Optional] Default is 'update'
example: 'start'
param:
description: What parameter to operate on. [Required]
example: 'package=VideoMotionDetection'

View File

@ -7,10 +7,11 @@ https://home-assistant.io/components/binary_sensor.axis/
from datetime import timedelta from datetime import timedelta
import logging import logging
from homeassistant.components.axis import AxisDeviceEvent
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import CONF_TRIGGER_TIME from homeassistant.const import (
from homeassistant.helpers.event import track_point_in_utc_time ATTR_LOCATION, CONF_EVENT, CONF_NAME, CONF_TRIGGER_TIME)
from homeassistant.core import callback
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
DEPENDENCIES = ['axis'] DEPENDENCIES = ['axis']
@ -20,48 +21,71 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Axis binary devices.""" """Set up the Axis binary devices."""
add_entities([AxisBinarySensor(hass, discovery_info)], True) add_entities([AxisBinarySensor(discovery_info)], True)
class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice): class AxisBinarySensor(BinarySensorDevice):
"""Representation of a binary Axis event.""" """Representation of a binary Axis event."""
def __init__(self, hass, event_config): def __init__(self, event_config):
"""Initialize the Axis binary sensor.""" """Initialize the Axis binary sensor."""
self.hass = hass self.axis_event = event_config[CONF_EVENT]
self._state = False self.device_name = event_config[CONF_NAME]
self._delay = event_config[CONF_TRIGGER_TIME] self.location = event_config[ATTR_LOCATION]
self._timer = None self.delay = event_config[CONF_TRIGGER_TIME]
AxisDeviceEvent.__init__(self, event_config) self.remove_timer = None
async def async_added_to_hass(self):
"""Subscribe sensors events."""
self.axis_event.callback = self._update_callback
def _update_callback(self):
"""Update the sensor's state, if needed."""
if self.remove_timer is not None:
self.remove_timer()
self.remove_timer = None
if self.delay == 0 or self.is_on:
self.schedule_update_ha_state()
else: # Run timer to delay updating the state
@callback
def _delay_update(now):
"""Timer callback for sensor update."""
_LOGGER.debug("%s called delayed (%s sec) update",
self.name, self.delay)
self.async_schedule_update_ha_state()
self.remove_timer = None
self.remove_timer = async_track_point_in_utc_time(
self.hass, _delay_update,
utcnow() + timedelta(seconds=self.delay))
@property @property
def is_on(self): def is_on(self):
"""Return true if event is active.""" """Return true if event is active."""
return self._state return self.axis_event.is_tripped
def update(self): @property
"""Get the latest data and update the state.""" def name(self):
self._state = self.axis_event.is_tripped """Return the name of the event."""
return '{}_{}_{}'.format(
self.device_name, self.axis_event.event_type, self.axis_event.id)
def _update_callback(self): @property
"""Update the sensor's state, if needed.""" def device_class(self):
self.update() """Return the class of the event."""
return self.axis_event.event_class
if self._timer is not None: @property
self._timer() def should_poll(self):
self._timer = None """No polling needed."""
return False
if self._delay > 0 and not self.is_on: @property
# Set timer to wait until updating the state def device_state_attributes(self):
def _delay_update(now): """Return the state attributes of the event."""
"""Timer callback for sensor update.""" attr = {}
_LOGGER.debug("%s called delayed (%s sec) update",
self._name, self._delay)
self.schedule_update_ha_state()
self._timer = None
self._timer = track_point_in_utc_time( attr[ATTR_LOCATION] = self.location
self.hass, _delay_update,
utcnow() + timedelta(seconds=self._delay)) return attr
else:
self.schedule_update_ha_state()

View File

@ -260,23 +260,6 @@ eight_sleep:
description: Duration to heat at the target level in seconds. description: Duration to heat at the target level in seconds.
example: 3600 example: 3600
axis:
vapix_call:
description: Configure device using Vapix parameter management.
fields:
name:
description: Name of device to Configure. [Required]
example: M1065-W
cgi:
description: Which cgi to call on device. [Optional] Default is 'param.cgi'
example: 'applications/control.cgi'
action:
description: What type of call. [Optional] Default is 'update'
example: 'start'
param:
description: What parameter to operate on. [Required]
example: 'package=VideoMotionDetection'
apple_tv: apple_tv:
apple_tv_authenticate: apple_tv_authenticate:
description: Start AirPlay device authentication. description: Start AirPlay device authentication.

View File

@ -162,7 +162,7 @@ async-upnp-client==0.12.7
# avion==0.7 # avion==0.7
# homeassistant.components.axis # homeassistant.components.axis
axis==14 axis==16
# homeassistant.components.tts.baidu # homeassistant.components.tts.baidu
baidu-aip==1.6.6 baidu-aip==1.6.6