diff --git a/homeassistant/components/axis.py b/homeassistant/components/axis/__init__.py similarity index 79% rename from homeassistant/components/axis.py rename to homeassistant/components/axis/__init__.py index 63fce8a74ee..26fe41724f9 100644 --- a/homeassistant/components/axis.py +++ b/homeassistant/components/axis/__init__.py @@ -10,24 +10,21 @@ import voluptuous as vol from homeassistant.components.discovery import SERVICE_AXIS 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, EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery from homeassistant.helpers.dispatcher import dispatcher_send -from homeassistant.helpers.entity import Entity from homeassistant.util.json import load_json, save_json -REQUIREMENTS = ['axis==14'] +REQUIREMENTS = ['axis==16'] _LOGGER = logging.getLogger(__name__) DOMAIN = 'axis' CONFIG_FILE = 'axis.conf' -AXIS_DEVICES = {} - EVENT_TYPES = ['motion', 'vmd3', 'pir', 'sound', 'daynight', 'tampering', 'input'] @@ -99,8 +96,6 @@ def request_configuration(hass, config, name, host, serialnumber): return False 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[serialnumber] = dict(device_config) 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): """Set up for Axis devices.""" + hass.data[DOMAIN] = {} + def _shutdown(call): """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) device.stop() @@ -160,7 +157,7 @@ def setup(hass, config): name = discovery_info['hostname'] 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)) if serialnumber in config_file: # Device config previously saved to file @@ -178,7 +175,7 @@ def setup(hass, config): request_configuration(hass, config, name, host, serialnumber) else: # Device already registered, but on a different IP - device = AXIS_DEVICES[serialnumber] + device = hass.data[DOMAIN][serialnumber] device.config.host = host dispatcher_send(hass, DOMAIN + '_' + device.name + '_new_ip', host) @@ -195,7 +192,7 @@ def setup(hass, config): def vapix_service(call): """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]: response = device.vapix.do_request( call.data[SERVICE_CGI], @@ -214,7 +211,7 @@ def setup(hass, config): def setup_device(hass, config, device_config): """Set up an Axis device.""" - from axis import AxisDevice + import axis def signal_callback(action, event): """Call to configure events when initialized on event stream.""" @@ -229,18 +226,32 @@ def setup_device(hass, config, device_config): discovery.load_platform( hass, component, DOMAIN, event_config, config) - event_types = list(filter(lambda x: x in device_config[CONF_INCLUDE], - EVENT_TYPES)) - device_config['events'] = event_types - device_config['signal'] = signal_callback - device = AxisDevice(hass.loop, **device_config) - device.name = device_config[CONF_NAME] + event_types = [ + event + for event in device_config[CONF_INCLUDE] + if event in EVENT_TYPES + ] - if device.serial_number is None: - # If there is no serial number a connection could not be made - _LOGGER.error("Couldn't connect to %s", device_config[CONF_HOST]) + device = axis.AxisDevice( + loop=hass.loop, host=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 + except axis.RequestError: + return False + + device.name = device_config[CONF_NAME] + for component in device_config[CONF_INCLUDE]: if component == 'camera': camera_config = { @@ -253,51 +264,6 @@ def setup_device(hass, config, device_config): discovery.load_platform( hass, component, DOMAIN, camera_config, config) - AXIS_DEVICES[device.serial_number] = device if event_types: hass.add_job(device.start) 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 diff --git a/homeassistant/components/axis/services.yaml b/homeassistant/components/axis/services.yaml new file mode 100644 index 00000000000..03db5ce7af8 --- /dev/null +++ b/homeassistant/components/axis/services.yaml @@ -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' \ No newline at end of file diff --git a/homeassistant/components/binary_sensor/axis.py b/homeassistant/components/binary_sensor/axis.py index b66a766ca4a..671bbc730d0 100644 --- a/homeassistant/components/binary_sensor/axis.py +++ b/homeassistant/components/binary_sensor/axis.py @@ -7,10 +7,11 @@ https://home-assistant.io/components/binary_sensor.axis/ from datetime import timedelta import logging -from homeassistant.components.axis import AxisDeviceEvent from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.const import CONF_TRIGGER_TIME -from homeassistant.helpers.event import track_point_in_utc_time +from homeassistant.const import ( + 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 DEPENDENCIES = ['axis'] @@ -20,48 +21,71 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): """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.""" - def __init__(self, hass, event_config): + def __init__(self, event_config): """Initialize the Axis binary sensor.""" - self.hass = hass - self._state = False - self._delay = event_config[CONF_TRIGGER_TIME] - self._timer = None - AxisDeviceEvent.__init__(self, event_config) + self.axis_event = event_config[CONF_EVENT] + self.device_name = event_config[CONF_NAME] + self.location = event_config[ATTR_LOCATION] + self.delay = event_config[CONF_TRIGGER_TIME] + 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 def is_on(self): """Return true if event is active.""" - return self._state + return self.axis_event.is_tripped - def update(self): - """Get the latest data and update the state.""" - self._state = self.axis_event.is_tripped + @property + def name(self): + """Return the name of the event.""" + return '{}_{}_{}'.format( + self.device_name, self.axis_event.event_type, self.axis_event.id) - def _update_callback(self): - """Update the sensor's state, if needed.""" - self.update() + @property + def device_class(self): + """Return the class of the event.""" + return self.axis_event.event_class - if self._timer is not None: - self._timer() - self._timer = None + @property + def should_poll(self): + """No polling needed.""" + return False - if self._delay > 0 and not self.is_on: - # Set timer to wait until updating the state - def _delay_update(now): - """Timer callback for sensor update.""" - _LOGGER.debug("%s called delayed (%s sec) update", - self._name, self._delay) - self.schedule_update_ha_state() - self._timer = None + @property + def device_state_attributes(self): + """Return the state attributes of the event.""" + attr = {} - self._timer = track_point_in_utc_time( - self.hass, _delay_update, - utcnow() + timedelta(seconds=self._delay)) - else: - self.schedule_update_ha_state() + attr[ATTR_LOCATION] = self.location + + return attr diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml index e8512d67fc4..8988021a5b6 100644 --- a/homeassistant/components/services.yaml +++ b/homeassistant/components/services.yaml @@ -260,23 +260,6 @@ eight_sleep: description: Duration to heat at the target level in seconds. 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_authenticate: description: Start AirPlay device authentication. diff --git a/requirements_all.txt b/requirements_all.txt index f8ff629a9ba..5f2f8b2ea33 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -162,7 +162,7 @@ async-upnp-client==0.12.7 # avion==0.7 # homeassistant.components.axis -axis==14 +axis==16 # homeassistant.components.tts.baidu baidu-aip==1.6.6