Wireless tags platform (#13495)
* Initial version of wirelesstags platform support. * Pinned wirelesstagpy, generated requirements_all. * Fixed temperature units in imperial units systems, make binary events more tags specific. * Lowercased tag name during entity_id generation. * Fixed: tag_id template for tag_binary_events, support of light sensor for homebridge. * Minor style cleanup. * Removed state, define_name, icon. Reworked async arm/disarm update. Removed static attrs. Introduced available property. Custom events contains components name now. Cleaned dedundant items from schema definition. * Removed comment and beep duration from attributes. Minor cleanup of documentation comment. * Ignoring Wemo switches linked in iOS app. * Reworked passing data from platform to components using signals.pull/14795/merge
parent
90a51160c4
commit
1a7e8c88a3
|
@ -311,6 +311,9 @@ omit =
|
|||
homeassistant/components/wink/*
|
||||
homeassistant/components/*/wink.py
|
||||
|
||||
homeassistant/components/wirelesstag.py
|
||||
homeassistant/components/*/wirelesstag.py
|
||||
|
||||
homeassistant/components/xiaomi_aqara.py
|
||||
homeassistant/components/*/xiaomi_aqara.py
|
||||
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
"""
|
||||
Binary sensor support for Wireless Sensor Tags.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.wirelesstag/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.components.wirelesstag import (
|
||||
DOMAIN as WIRELESSTAG_DOMAIN,
|
||||
WIRELESSTAG_TYPE_13BIT, WIRELESSTAG_TYPE_WATER,
|
||||
WIRELESSTAG_TYPE_ALSPRO,
|
||||
WIRELESSTAG_TYPE_WEMO_DEVICE,
|
||||
SIGNAL_BINARY_EVENT_UPDATE,
|
||||
WirelessTagBaseSensor)
|
||||
from homeassistant.const import (
|
||||
CONF_MONITORED_CONDITIONS, STATE_ON, STATE_OFF)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
DEPENDENCIES = ['wirelesstag']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# On means in range, Off means out of range
|
||||
SENSOR_PRESENCE = 'presence'
|
||||
|
||||
# On means motion detected, Off means cear
|
||||
SENSOR_MOTION = 'motion'
|
||||
|
||||
# On means open, Off means closed
|
||||
SENSOR_DOOR = 'door'
|
||||
|
||||
# On means temperature become too cold, Off means normal
|
||||
SENSOR_COLD = 'cold'
|
||||
|
||||
# On means hot, Off means normal
|
||||
SENSOR_HEAT = 'heat'
|
||||
|
||||
# On means too dry (humidity), Off means normal
|
||||
SENSOR_DRY = 'dry'
|
||||
|
||||
# On means too wet (humidity), Off means normal
|
||||
SENSOR_WET = 'wet'
|
||||
|
||||
# On means light detected, Off means no light
|
||||
SENSOR_LIGHT = 'light'
|
||||
|
||||
# On means moisture detected (wet), Off means no moisture (dry)
|
||||
SENSOR_MOISTURE = 'moisture'
|
||||
|
||||
# On means tag battery is low, Off means normal
|
||||
SENSOR_BATTERY = 'low_battery'
|
||||
|
||||
# Sensor types: Name, device_class, push notification type representing 'on',
|
||||
# attr to check
|
||||
SENSOR_TYPES = {
|
||||
SENSOR_PRESENCE: ['Presence', 'presence', 'is_in_range', {
|
||||
"on": "oor",
|
||||
"off": "back_in_range"
|
||||
}, 2],
|
||||
SENSOR_MOTION: ['Motion', 'motion', 'is_moved', {
|
||||
"on": "motion_detected",
|
||||
}, 5],
|
||||
SENSOR_DOOR: ['Door', 'door', 'is_door_open', {
|
||||
"on": "door_opened",
|
||||
"off": "door_closed"
|
||||
}, 5],
|
||||
SENSOR_COLD: ['Cold', 'cold', 'is_cold', {
|
||||
"on": "temp_toolow",
|
||||
"off": "temp_normal"
|
||||
}, 4],
|
||||
SENSOR_HEAT: ['Heat', 'heat', 'is_heat', {
|
||||
"on": "temp_toohigh",
|
||||
"off": "temp_normal"
|
||||
}, 4],
|
||||
SENSOR_DRY: ['Too dry', 'dry', 'is_too_dry', {
|
||||
"on": "too_dry",
|
||||
"off": "cap_normal"
|
||||
}, 2],
|
||||
SENSOR_WET: ['Too wet', 'wet', 'is_too_humid', {
|
||||
"on": "too_humid",
|
||||
"off": "cap_normal"
|
||||
}, 2],
|
||||
SENSOR_LIGHT: ['Light', 'light', 'is_light_on', {
|
||||
"on": "too_bright",
|
||||
"off": "light_normal"
|
||||
}, 1],
|
||||
SENSOR_MOISTURE: ['Leak', 'moisture', 'is_leaking', {
|
||||
"on": "water_detected",
|
||||
"off": "water_dried",
|
||||
}, 1],
|
||||
SENSOR_BATTERY: ['Low Battery', 'battery', 'is_battery_low', {
|
||||
"on": "low_battery"
|
||||
}, 3]
|
||||
}
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
|
||||
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the platform for a WirelessTags."""
|
||||
platform = hass.data.get(WIRELESSTAG_DOMAIN)
|
||||
|
||||
sensors = []
|
||||
tags = platform.tags
|
||||
for tag in tags.values():
|
||||
allowed_sensor_types = WirelessTagBinarySensor.allowed_sensors(tag)
|
||||
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
|
||||
if sensor_type in allowed_sensor_types:
|
||||
sensors.append(WirelessTagBinarySensor(platform, tag,
|
||||
sensor_type))
|
||||
|
||||
add_devices(sensors, True)
|
||||
hass.add_job(platform.install_push_notifications, sensors)
|
||||
|
||||
|
||||
class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorDevice):
|
||||
"""A binary sensor implementation for WirelessTags."""
|
||||
|
||||
@classmethod
|
||||
def allowed_sensors(cls, tag):
|
||||
"""Return list of allowed sensor types for specific tag type."""
|
||||
sensors_map = {
|
||||
# 13-bit tag - allows everything but not light and moisture
|
||||
WIRELESSTAG_TYPE_13BIT: [
|
||||
SENSOR_PRESENCE, SENSOR_BATTERY,
|
||||
SENSOR_MOTION, SENSOR_DOOR,
|
||||
SENSOR_COLD, SENSOR_HEAT,
|
||||
SENSOR_DRY, SENSOR_WET],
|
||||
|
||||
# Moister/water sensor - temperature and moisture only
|
||||
WIRELESSTAG_TYPE_WATER: [
|
||||
SENSOR_PRESENCE, SENSOR_BATTERY,
|
||||
SENSOR_COLD, SENSOR_HEAT,
|
||||
SENSOR_MOISTURE],
|
||||
|
||||
# ALS Pro: allows everything, but not moisture
|
||||
WIRELESSTAG_TYPE_ALSPRO: [
|
||||
SENSOR_PRESENCE, SENSOR_BATTERY,
|
||||
SENSOR_MOTION, SENSOR_DOOR,
|
||||
SENSOR_COLD, SENSOR_HEAT,
|
||||
SENSOR_DRY, SENSOR_WET,
|
||||
SENSOR_LIGHT],
|
||||
|
||||
# Wemo are power switches.
|
||||
WIRELESSTAG_TYPE_WEMO_DEVICE: [SENSOR_PRESENCE]
|
||||
}
|
||||
|
||||
# allow everything if tag type is unknown
|
||||
# (i just dont have full catalog of them :))
|
||||
tag_type = tag.tag_type
|
||||
fullset = SENSOR_TYPES.keys()
|
||||
return sensors_map[tag_type] if tag_type in sensors_map else fullset
|
||||
|
||||
def __init__(self, api, tag, sensor_type):
|
||||
"""Initialize a binary sensor for a Wireless Sensor Tags."""
|
||||
super().__init__(api, tag)
|
||||
self._sensor_type = sensor_type
|
||||
self._name = '{0} {1}'.format(self._tag.name,
|
||||
SENSOR_TYPES[self._sensor_type][0])
|
||||
self._device_class = SENSOR_TYPES[self._sensor_type][1]
|
||||
self._tag_attr = SENSOR_TYPES[self._sensor_type][2]
|
||||
self.binary_spec = SENSOR_TYPES[self._sensor_type][3]
|
||||
self.tag_id_index_template = SENSOR_TYPES[self._sensor_type][4]
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
tag_id = self.tag_id
|
||||
event_type = self.device_class
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
SIGNAL_BINARY_EVENT_UPDATE.format(tag_id, event_type),
|
||||
self._on_binary_event_callback)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if the binary sensor is on."""
|
||||
return self._state == STATE_ON
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the binary sensor."""
|
||||
return self._device_class
|
||||
|
||||
@property
|
||||
def principal_value(self):
|
||||
"""Return value of tag.
|
||||
|
||||
Subclasses need override based on type of sensor.
|
||||
"""
|
||||
return (
|
||||
STATE_ON if getattr(self._tag, self._tag_attr, False)
|
||||
else STATE_OFF)
|
||||
|
||||
def updated_state_value(self):
|
||||
"""Use raw princial value."""
|
||||
return self.principal_value
|
||||
|
||||
@callback
|
||||
def _on_binary_event_callback(self, event):
|
||||
"""Update state from arrive push notification."""
|
||||
# state should be 'on' or 'off'
|
||||
self._state = event.data.get('state')
|
||||
self.async_schedule_update_ha_state()
|
|
@ -0,0 +1,176 @@
|
|||
"""
|
||||
Sensor support for Wirelss Sensor Tags platform.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.wirelesstag/
|
||||
"""
|
||||
|
||||
import logging
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.const import (
|
||||
CONF_MONITORED_CONDITIONS)
|
||||
from homeassistant.components.wirelesstag import (
|
||||
DOMAIN as WIRELESSTAG_DOMAIN,
|
||||
WIRELESSTAG_TYPE_13BIT, WIRELESSTAG_TYPE_WATER,
|
||||
WIRELESSTAG_TYPE_ALSPRO,
|
||||
WIRELESSTAG_TYPE_WEMO_DEVICE,
|
||||
SIGNAL_TAG_UPDATE,
|
||||
WirelessTagBaseSensor)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import TEMP_CELSIUS
|
||||
|
||||
DEPENDENCIES = ['wirelesstag']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SENSOR_TEMPERATURE = 'temperature'
|
||||
SENSOR_HUMIDITY = 'humidity'
|
||||
SENSOR_MOISTURE = 'moisture'
|
||||
SENSOR_LIGHT = 'light'
|
||||
|
||||
SENSOR_TYPES = {
|
||||
SENSOR_TEMPERATURE: {
|
||||
'unit': TEMP_CELSIUS,
|
||||
'attr': 'temperature'
|
||||
},
|
||||
SENSOR_HUMIDITY: {
|
||||
'unit': '%',
|
||||
'attr': 'humidity'
|
||||
},
|
||||
SENSOR_MOISTURE: {
|
||||
'unit': '%',
|
||||
'attr': 'moisture'
|
||||
},
|
||||
SENSOR_LIGHT: {
|
||||
'unit': 'lux',
|
||||
'attr': 'light'
|
||||
}
|
||||
}
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
|
||||
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the sensor platform."""
|
||||
platform = hass.data.get(WIRELESSTAG_DOMAIN)
|
||||
sensors = []
|
||||
tags = platform.tags
|
||||
for tag in tags.values():
|
||||
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
|
||||
if sensor_type in WirelessTagSensor.allowed_sensors(tag):
|
||||
sensors.append(WirelessTagSensor(
|
||||
platform, tag, sensor_type, hass.config))
|
||||
|
||||
add_devices(sensors, True)
|
||||
|
||||
|
||||
class WirelessTagSensor(WirelessTagBaseSensor):
|
||||
"""Representation of a Sensor."""
|
||||
|
||||
@classmethod
|
||||
def allowed_sensors(cls, tag):
|
||||
"""Return array of allowed sensor types for tag."""
|
||||
all_sensors = SENSOR_TYPES.keys()
|
||||
sensors_per_tag_type = {
|
||||
WIRELESSTAG_TYPE_13BIT: [
|
||||
SENSOR_TEMPERATURE,
|
||||
SENSOR_HUMIDITY],
|
||||
WIRELESSTAG_TYPE_WATER: [
|
||||
SENSOR_TEMPERATURE,
|
||||
SENSOR_MOISTURE],
|
||||
WIRELESSTAG_TYPE_ALSPRO: [
|
||||
SENSOR_TEMPERATURE,
|
||||
SENSOR_HUMIDITY,
|
||||
SENSOR_LIGHT],
|
||||
WIRELESSTAG_TYPE_WEMO_DEVICE: []
|
||||
}
|
||||
|
||||
tag_type = tag.tag_type
|
||||
return (
|
||||
sensors_per_tag_type[tag_type] if tag_type in sensors_per_tag_type
|
||||
else all_sensors)
|
||||
|
||||
def __init__(self, api, tag, sensor_type, config):
|
||||
"""Constructor with platform(api), tag and hass sensor type."""
|
||||
super().__init__(api, tag)
|
||||
|
||||
self._sensor_type = sensor_type
|
||||
self._tag_attr = SENSOR_TYPES[self._sensor_type]['attr']
|
||||
self._unit_of_measurement = SENSOR_TYPES[self._sensor_type]['unit']
|
||||
self._name = self._tag.name
|
||||
|
||||
# I want to see entity_id as:
|
||||
# sensor.wirelesstag_bedroom_temperature
|
||||
# and not as sensor.bedroom for temperature and
|
||||
# sensor.bedroom_2 for humidity
|
||||
self._entity_id = '{}.{}_{}_{}'.format('sensor', WIRELESSTAG_DOMAIN,
|
||||
self.underscored_name,
|
||||
self._sensor_type)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
SIGNAL_TAG_UPDATE.format(self.tag_id),
|
||||
self._update_tag_info_callback)
|
||||
|
||||
@property
|
||||
def entity_id(self):
|
||||
"""Overriden version."""
|
||||
return self._entity_id
|
||||
|
||||
@property
|
||||
def underscored_name(self):
|
||||
"""Provide name savvy to be used in entity_id name of self."""
|
||||
return self.name.lower().replace(" ", "_")
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the sensor."""
|
||||
return self._sensor_type
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def principal_value(self):
|
||||
"""Return sensor current value."""
|
||||
return getattr(self._tag, self._tag_attr, False)
|
||||
|
||||
@callback
|
||||
def _update_tag_info_callback(self, event):
|
||||
"""Handle push notification sent by tag manager."""
|
||||
if event.data.get('id') != self.tag_id:
|
||||
return
|
||||
|
||||
_LOGGER.info("Entity to update state: %s event data: %s",
|
||||
self, event.data)
|
||||
new_value = self.principal_value
|
||||
try:
|
||||
if self._sensor_type == SENSOR_TEMPERATURE:
|
||||
new_value = event.data.get('temp')
|
||||
elif (self._sensor_type == SENSOR_HUMIDITY or
|
||||
self._sensor_type == SENSOR_MOISTURE):
|
||||
new_value = event.data.get('cap')
|
||||
elif self._sensor_type == SENSOR_LIGHT:
|
||||
new_value = event.data.get('lux')
|
||||
except Exception as error: # pylint: disable=W0703
|
||||
_LOGGER.info("Unable to update value of entity: \
|
||||
%s error: %s event: %s", self, error, event)
|
||||
|
||||
self._state = self.decorate_value(new_value)
|
||||
self.async_schedule_update_ha_state()
|
|
@ -0,0 +1,118 @@
|
|||
"""
|
||||
Switch implementation for Wireless Sensor Tags (wirelesstag.net) platform.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/switch.wirelesstag/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
|
||||
from homeassistant.components.wirelesstag import (
|
||||
DOMAIN as WIRELESSTAG_DOMAIN,
|
||||
WIRELESSTAG_TYPE_13BIT, WIRELESSTAG_TYPE_WATER,
|
||||
WIRELESSTAG_TYPE_ALSPRO,
|
||||
WIRELESSTAG_TYPE_WEMO_DEVICE,
|
||||
WirelessTagBaseSensor)
|
||||
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
|
||||
from homeassistant.const import (
|
||||
CONF_MONITORED_CONDITIONS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
DEPENDENCIES = ['wirelesstag']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ARM_TEMPERATURE = 'temperature'
|
||||
ARM_HUMIDITY = 'humidity'
|
||||
ARM_MOTION = 'motion'
|
||||
ARM_LIGHT = 'light'
|
||||
ARM_MOISTURE = 'moisture'
|
||||
|
||||
# Switch types: Name, tag sensor type
|
||||
SWITCH_TYPES = {
|
||||
ARM_TEMPERATURE: ['Arm Temperature', 'temperature'],
|
||||
ARM_HUMIDITY: ['Arm Humidity', 'humidity'],
|
||||
ARM_MOTION: ['Arm Motion', 'motion'],
|
||||
ARM_LIGHT: ['Arm Light', 'light'],
|
||||
ARM_MOISTURE: ['Arm Moisture', 'moisture']
|
||||
}
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
|
||||
vol.All(cv.ensure_list, [vol.In(SWITCH_TYPES)]),
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up switches for a Wireless Sensor Tags."""
|
||||
platform = hass.data.get(WIRELESSTAG_DOMAIN)
|
||||
|
||||
switches = []
|
||||
tags = platform.load_tags()
|
||||
for switch_type in config.get(CONF_MONITORED_CONDITIONS):
|
||||
for _, tag in tags.items():
|
||||
if switch_type in WirelessTagSwitch.allowed_switches(tag):
|
||||
switches.append(WirelessTagSwitch(platform, tag, switch_type))
|
||||
|
||||
add_devices(switches, True)
|
||||
|
||||
|
||||
class WirelessTagSwitch(WirelessTagBaseSensor, SwitchDevice):
|
||||
"""A switch implementation for Wireless Sensor Tags."""
|
||||
|
||||
@classmethod
|
||||
def allowed_switches(cls, tag):
|
||||
"""Return allowed switch types for wireless tag."""
|
||||
all_sensors = SWITCH_TYPES.keys()
|
||||
sensors_per_tag_spec = {
|
||||
WIRELESSTAG_TYPE_13BIT: [
|
||||
ARM_TEMPERATURE, ARM_HUMIDITY, ARM_MOTION],
|
||||
WIRELESSTAG_TYPE_WATER: [
|
||||
ARM_TEMPERATURE, ARM_MOISTURE],
|
||||
WIRELESSTAG_TYPE_ALSPRO: [
|
||||
ARM_TEMPERATURE, ARM_HUMIDITY, ARM_MOTION, ARM_LIGHT],
|
||||
WIRELESSTAG_TYPE_WEMO_DEVICE: []
|
||||
}
|
||||
|
||||
tag_type = tag.tag_type
|
||||
|
||||
result = (
|
||||
sensors_per_tag_spec[tag_type]
|
||||
if tag_type in sensors_per_tag_spec else all_sensors)
|
||||
_LOGGER.info("Allowed switches: %s tag_type: %s",
|
||||
str(result), tag_type)
|
||||
|
||||
return result
|
||||
|
||||
def __init__(self, api, tag, switch_type):
|
||||
"""Initialize a switch for Wireless Sensor Tag."""
|
||||
super().__init__(api, tag)
|
||||
self._switch_type = switch_type
|
||||
self.sensor_type = SWITCH_TYPES[self._switch_type][1]
|
||||
self._name = '{} {}'.format(self._tag.name,
|
||||
SWITCH_TYPES[self._switch_type][0])
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn on the switch."""
|
||||
self._api.arm(self)
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn on the switch."""
|
||||
self._api.disarm(self)
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if entity is on."""
|
||||
return self._state
|
||||
|
||||
def updated_state_value(self):
|
||||
"""Provide formatted value."""
|
||||
return self.principal_value
|
||||
|
||||
@property
|
||||
def principal_value(self):
|
||||
"""Provide actual value of switch."""
|
||||
attr_name = 'is_{}_sensor_armed'.format(self.sensor_type)
|
||||
return getattr(self._tag, attr_name, False)
|
|
@ -0,0 +1,256 @@
|
|||
"""
|
||||
Wireless Sensor Tags platform support.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/wirelesstag/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from requests.exceptions import HTTPError, ConnectTimeout
|
||||
import voluptuous as vol
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL, ATTR_VOLTAGE, CONF_USERNAME, CONF_PASSWORD)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
dispatcher_send)
|
||||
|
||||
REQUIREMENTS = ['wirelesstagpy==0.3.0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# straight of signal in dBm
|
||||
ATTR_TAG_SIGNAL_STRAIGHT = 'signal_straight'
|
||||
# indicates if tag is out of range or not
|
||||
ATTR_TAG_OUT_OF_RANGE = 'out_of_range'
|
||||
# number in percents from max power of tag receiver
|
||||
ATTR_TAG_POWER_CONSUMPTION = 'power_consumption'
|
||||
|
||||
|
||||
NOTIFICATION_ID = 'wirelesstag_notification'
|
||||
NOTIFICATION_TITLE = "Wireless Sensor Tag Setup"
|
||||
|
||||
DOMAIN = 'wirelesstag'
|
||||
DEFAULT_ENTITY_NAMESPACE = 'wirelesstag'
|
||||
|
||||
WIRELESSTAG_TYPE_13BIT = 13
|
||||
WIRELESSTAG_TYPE_ALSPRO = 26
|
||||
WIRELESSTAG_TYPE_WATER = 32
|
||||
WIRELESSTAG_TYPE_WEMO_DEVICE = 82
|
||||
|
||||
SIGNAL_TAG_UPDATE = 'wirelesstag.tag_info_updated_{}'
|
||||
SIGNAL_BINARY_EVENT_UPDATE = 'wirelesstag.binary_event_updated_{}_{}'
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
class WirelessTagPlatform:
|
||||
"""Principal object to manage all registered in HA tags."""
|
||||
|
||||
def __init__(self, hass, api):
|
||||
"""Designated initializer for wirelesstags platform."""
|
||||
self.hass = hass
|
||||
self.api = api
|
||||
self.tags = {}
|
||||
|
||||
def load_tags(self):
|
||||
"""Load tags from remote server."""
|
||||
self.tags = self.api.load_tags()
|
||||
return self.tags
|
||||
|
||||
def arm(self, switch):
|
||||
"""Arm entity sensor monitoring."""
|
||||
func_name = 'arm_{}'.format(switch.sensor_type)
|
||||
arm_func = getattr(self.api, func_name)
|
||||
if arm_func is not None:
|
||||
arm_func(switch.tag_id)
|
||||
|
||||
def disarm(self, switch):
|
||||
"""Disarm entity sensor monitoring."""
|
||||
func_name = 'disarm_{}'.format(switch.sensor_type)
|
||||
disarm_func = getattr(self.api, func_name)
|
||||
if disarm_func is not None:
|
||||
disarm_func(switch.tag_id)
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def make_push_notitication(self, name, url, content):
|
||||
"""Factory for notification config."""
|
||||
from wirelesstagpy import NotificationConfig
|
||||
return NotificationConfig(name, {
|
||||
'url': url, 'verb': 'POST',
|
||||
'content': content, 'disabled': False, 'nat': True})
|
||||
|
||||
def install_push_notifications(self, binary_sensors):
|
||||
"""Setup local push notification from tag manager."""
|
||||
_LOGGER.info("Registering local push notifications.")
|
||||
configs = []
|
||||
|
||||
binary_url = self.binary_event_callback_url
|
||||
for event in binary_sensors:
|
||||
for state, name in event.binary_spec.items():
|
||||
content = ('{"type": "' + event.device_class +
|
||||
'", "id":{' + str(event.tag_id_index_template) +
|
||||
'}, "state": \"' + state + '\"}')
|
||||
config = self.make_push_notitication(name, binary_url, content)
|
||||
configs.append(config)
|
||||
|
||||
content = ("{\"name\":\"{0}\",\"id\":{1},\"temp\":{2}," +
|
||||
"\"cap\":{3},\"lux\":{4}}")
|
||||
update_url = self.update_callback_url
|
||||
update_config = self.make_push_notitication(
|
||||
'update', update_url, content)
|
||||
configs.append(update_config)
|
||||
|
||||
result = self.api.install_push_notification(0, configs, True)
|
||||
if not result:
|
||||
self.hass.components.persistent_notification.create(
|
||||
"Error: failed to install local push notifications<br />",
|
||||
title="Wireless Sensor Tag Setup Local Push Notifications",
|
||||
notification_id="wirelesstag_failed_push_notification")
|
||||
else:
|
||||
_LOGGER.info("Installed push notifications for all tags.")
|
||||
|
||||
@property
|
||||
def update_callback_url(self):
|
||||
"""Return url for local push notifications(update event)."""
|
||||
return '{}/api/events/wirelesstag_update_tags'.format(
|
||||
self.hass.config.api.base_url)
|
||||
|
||||
@property
|
||||
def binary_event_callback_url(self):
|
||||
"""Return url for local push notifications(binary event)."""
|
||||
return '{}/api/events/wirelesstag_binary_event'.format(
|
||||
self.hass.config.api.base_url)
|
||||
|
||||
def handle_update_tags_event(self, event):
|
||||
"""Main entry to handle push event from wireless tag manager."""
|
||||
_LOGGER.info("push notification for update arrived: %s", event)
|
||||
dispatcher_send(
|
||||
self.hass,
|
||||
SIGNAL_TAG_UPDATE.format(event.data.get('id')),
|
||||
event)
|
||||
|
||||
def handle_binary_event(self, event):
|
||||
"""Handle push notifications for binary (on/off) events."""
|
||||
_LOGGER.info("Push notification for binary event arrived: %s", event)
|
||||
try:
|
||||
tag_id = event.data.get('id')
|
||||
event_type = event.data.get('type')
|
||||
dispatcher_send(
|
||||
self.hass,
|
||||
SIGNAL_BINARY_EVENT_UPDATE.format(tag_id, event_type),
|
||||
event)
|
||||
except Exception as ex: # pylint: disable=W0703
|
||||
_LOGGER.error("Unable to handle binary event:\
|
||||
%s error: %s", str(event), str(ex))
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the Wireless Sensor Tag component."""
|
||||
conf = config[DOMAIN]
|
||||
username = conf.get(CONF_USERNAME)
|
||||
password = conf.get(CONF_PASSWORD)
|
||||
|
||||
try:
|
||||
from wirelesstagpy import (WirelessTags, WirelessTagsException)
|
||||
wirelesstags = WirelessTags(username=username, password=password)
|
||||
|
||||
platform = WirelessTagPlatform(hass, wirelesstags)
|
||||
platform.load_tags()
|
||||
hass.data[DOMAIN] = platform
|
||||
except (ConnectTimeout, HTTPError, WirelessTagsException) as ex:
|
||||
_LOGGER.error("Unable to connect to wirelesstag.net service: %s",
|
||||
str(ex))
|
||||
hass.components.persistent_notification.create(
|
||||
"Error: {}<br />"
|
||||
"Please restart hass after fixing this."
|
||||
"".format(ex),
|
||||
title=NOTIFICATION_TITLE,
|
||||
notification_id=NOTIFICATION_ID)
|
||||
return False
|
||||
|
||||
# listen to custom events
|
||||
hass.bus.listen('wirelesstag_update_tags',
|
||||
hass.data[DOMAIN].handle_update_tags_event)
|
||||
hass.bus.listen('wirelesstag_binary_event',
|
||||
hass.data[DOMAIN].handle_binary_event)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class WirelessTagBaseSensor(Entity):
|
||||
"""Base class for HA implementation for Wireless Sensor Tag."""
|
||||
|
||||
def __init__(self, api, tag):
|
||||
"""Initialize a base sensor for Wireless Sensor Tag platform."""
|
||||
self._api = api
|
||||
self._tag = tag
|
||||
self._uuid = self._tag.uuid
|
||||
self.tag_id = self._tag.tag_id
|
||||
self._name = self._tag.name
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling state."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def principal_value(self):
|
||||
"""Return base value.
|
||||
|
||||
Subclasses need override based on type of sensor.
|
||||
"""
|
||||
return 0
|
||||
|
||||
def updated_state_value(self):
|
||||
"""Default implementation formats princial value."""
|
||||
return self.decorate_value(self.principal_value)
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def decorate_value(self, value):
|
||||
"""Decorate input value to be well presented for end user."""
|
||||
return '{:.1f}'.format(value)
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return self._tag.is_alive
|
||||
|
||||
def update(self):
|
||||
"""Update state."""
|
||||
if not self.should_poll:
|
||||
return
|
||||
|
||||
updated_tags = self._api.load_tags()
|
||||
updated_tag = updated_tags[self._uuid]
|
||||
if updated_tag is None:
|
||||
_LOGGER.error('Unable to update tag: "%s"', self.name)
|
||||
return
|
||||
|
||||
self._tag = updated_tag
|
||||
self._state = self.updated_state_value()
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
ATTR_BATTERY_LEVEL: self._tag.battery_remaining,
|
||||
ATTR_VOLTAGE: '{:.2f}V'.format(self._tag.battery_volts),
|
||||
ATTR_TAG_SIGNAL_STRAIGHT: '{}dBm'.format(
|
||||
self._tag.signal_straight),
|
||||
ATTR_TAG_OUT_OF_RANGE: not self._tag.is_in_range,
|
||||
ATTR_TAG_POWER_CONSUMPTION: '{:.2f}%'.format(
|
||||
self._tag.power_consumption)
|
||||
}
|
|
@ -1378,6 +1378,9 @@ websocket-client==0.37.0
|
|||
# homeassistant.components.media_player.webostv
|
||||
websockets==3.2
|
||||
|
||||
# homeassistant.components.wirelesstag
|
||||
wirelesstagpy==0.3.0
|
||||
|
||||
# homeassistant.components.zigbee
|
||||
xbee-helper==0.0.7
|
||||
|
||||
|
|
Loading…
Reference in New Issue