2015-11-04 03:53:59 +00:00
|
|
|
"""
|
|
|
|
homeassistant.components.mysensors
|
2016-01-27 08:23:44 +00:00
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2015-11-04 03:53:59 +00:00
|
|
|
MySensors component that connects to a MySensors gateway via pymysensors
|
|
|
|
API.
|
|
|
|
|
|
|
|
For more details about this platform, please refer to the documentation at
|
2016-01-27 08:23:44 +00:00
|
|
|
https://home-assistant.io/components/sensor.mysensors/
|
2015-11-04 03:53:59 +00:00
|
|
|
"""
|
|
|
|
import logging
|
|
|
|
|
2015-12-23 22:20:39 +00:00
|
|
|
from homeassistant.helpers import validate_config
|
|
|
|
import homeassistant.bootstrap as bootstrap
|
2015-11-04 03:53:59 +00:00
|
|
|
|
|
|
|
from homeassistant.const import (
|
2015-12-09 03:43:18 +00:00
|
|
|
EVENT_HOMEASSISTANT_START,
|
2015-11-04 03:53:59 +00:00
|
|
|
EVENT_HOMEASSISTANT_STOP,
|
2015-12-31 04:48:23 +00:00
|
|
|
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED,
|
|
|
|
TEMP_CELCIUS,)
|
2015-11-04 03:53:59 +00:00
|
|
|
|
2015-12-23 22:20:39 +00:00
|
|
|
CONF_GATEWAYS = 'gateways'
|
2015-11-04 03:53:59 +00:00
|
|
|
CONF_PORT = 'port'
|
|
|
|
CONF_DEBUG = 'debug'
|
|
|
|
CONF_PERSISTENCE = 'persistence'
|
|
|
|
CONF_PERSISTENCE_FILE = 'persistence_file'
|
|
|
|
CONF_VERSION = 'version'
|
2015-12-23 22:20:39 +00:00
|
|
|
DEFAULT_VERSION = '1.4'
|
2015-11-04 03:53:59 +00:00
|
|
|
|
|
|
|
DOMAIN = 'mysensors'
|
|
|
|
DEPENDENCIES = []
|
2015-12-05 23:29:03 +00:00
|
|
|
REQUIREMENTS = [
|
2015-12-18 02:37:49 +00:00
|
|
|
'https://github.com/theolind/pymysensors/archive/'
|
2016-01-10 03:10:38 +00:00
|
|
|
'005bff4c5ca7a56acd30e816bc3bcdb5cb2d46fd.zip#pymysensors==0.4']
|
2015-11-04 03:53:59 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
ATTR_NODE_ID = 'node_id'
|
|
|
|
ATTR_CHILD_ID = 'child_id'
|
2016-01-10 03:10:38 +00:00
|
|
|
ATTR_PORT = 'port'
|
2015-11-04 03:53:59 +00:00
|
|
|
|
2015-12-05 23:29:03 +00:00
|
|
|
GATEWAYS = None
|
2015-12-31 04:48:23 +00:00
|
|
|
|
|
|
|
DISCOVER_SENSORS = "mysensors.sensors"
|
|
|
|
DISCOVER_SWITCHES = "mysensors.switches"
|
|
|
|
|
|
|
|
# Maps discovered services to their platforms
|
|
|
|
DISCOVERY_COMPONENTS = [
|
|
|
|
('sensor', DISCOVER_SENSORS),
|
|
|
|
('switch', DISCOVER_SWITCHES),
|
|
|
|
]
|
2015-11-04 03:53:59 +00:00
|
|
|
|
|
|
|
|
2015-12-23 22:20:39 +00:00
|
|
|
def setup(hass, config):
|
|
|
|
"""Setup the MySensors component."""
|
2015-11-04 03:53:59 +00:00
|
|
|
if not validate_config(config,
|
2015-12-23 22:20:39 +00:00
|
|
|
{DOMAIN: [CONF_GATEWAYS]},
|
2015-11-04 03:53:59 +00:00
|
|
|
_LOGGER):
|
|
|
|
return False
|
|
|
|
|
2016-01-10 03:10:38 +00:00
|
|
|
import mysensors.mysensors as mysensors
|
2015-12-23 22:20:39 +00:00
|
|
|
|
2015-12-31 04:48:23 +00:00
|
|
|
version = str(config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION))
|
|
|
|
is_metric = (hass.config.temperature_unit == TEMP_CELCIUS)
|
2015-12-05 23:29:03 +00:00
|
|
|
|
2015-12-31 04:48:23 +00:00
|
|
|
def setup_gateway(port, persistence, persistence_file, version):
|
2015-12-08 00:03:07 +00:00
|
|
|
"""Return gateway after setup of the gateway."""
|
2016-01-10 03:10:38 +00:00
|
|
|
gateway = mysensors.SerialGateway(port, event_callback=None,
|
|
|
|
persistence=persistence,
|
|
|
|
persistence_file=persistence_file,
|
|
|
|
protocol_version=version)
|
2015-12-31 04:48:23 +00:00
|
|
|
gateway.metric = is_metric
|
2015-12-05 23:29:03 +00:00
|
|
|
gateway.debug = config[DOMAIN].get(CONF_DEBUG, False)
|
2016-01-10 03:10:38 +00:00
|
|
|
gateway = GatewayWrapper(gateway, version)
|
|
|
|
# pylint: disable=attribute-defined-outside-init
|
|
|
|
gateway.event_callback = gateway.callback_factory()
|
2015-12-09 03:43:18 +00:00
|
|
|
|
2015-12-31 04:48:23 +00:00
|
|
|
def gw_start(event):
|
|
|
|
"""Callback to trigger start of gateway and any persistence."""
|
|
|
|
gateway.start()
|
|
|
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
|
|
|
|
lambda event: gateway.stop())
|
|
|
|
if persistence:
|
|
|
|
for node_id in gateway.sensors:
|
|
|
|
gateway.event_callback('persistence', node_id)
|
2015-12-05 23:29:03 +00:00
|
|
|
|
2015-12-31 04:48:23 +00:00
|
|
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, gw_start)
|
2015-11-04 03:53:59 +00:00
|
|
|
|
2015-12-23 22:20:39 +00:00
|
|
|
return gateway
|
2015-12-05 23:29:03 +00:00
|
|
|
|
|
|
|
# Setup all ports from config
|
|
|
|
global GATEWAYS
|
|
|
|
GATEWAYS = {}
|
2015-12-23 22:20:39 +00:00
|
|
|
conf_gateways = config[DOMAIN][CONF_GATEWAYS]
|
|
|
|
if isinstance(conf_gateways, dict):
|
|
|
|
conf_gateways = [conf_gateways]
|
|
|
|
persistence = config[DOMAIN].get(CONF_PERSISTENCE, True)
|
2015-12-31 04:48:23 +00:00
|
|
|
|
2015-12-23 22:20:39 +00:00
|
|
|
for index, gway in enumerate(conf_gateways):
|
|
|
|
port = gway[CONF_PORT]
|
|
|
|
persistence_file = gway.get(
|
|
|
|
CONF_PERSISTENCE_FILE,
|
|
|
|
hass.config.path('mysensors{}.pickle'.format(index + 1)))
|
|
|
|
GATEWAYS[port] = setup_gateway(
|
2015-12-31 04:48:23 +00:00
|
|
|
port, persistence, persistence_file, version)
|
|
|
|
|
|
|
|
for (component, discovery_service) in DISCOVERY_COMPONENTS:
|
|
|
|
# Ensure component is loaded
|
|
|
|
if not bootstrap.setup_component(hass, component, config):
|
|
|
|
return False
|
|
|
|
# Fire discovery event
|
|
|
|
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
|
|
|
|
ATTR_SERVICE: discovery_service,
|
|
|
|
ATTR_DISCOVERED: {}})
|
2015-11-04 03:53:59 +00:00
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2015-12-31 04:48:23 +00:00
|
|
|
def pf_callback_factory(
|
|
|
|
s_types, v_types, devices, add_devices, entity_class):
|
|
|
|
"""Return a new callback for the platform."""
|
|
|
|
def mysensors_callback(gateway, node_id):
|
|
|
|
"""Callback for mysensors platform."""
|
|
|
|
if gateway.sensors[node_id].sketch_name is None:
|
|
|
|
_LOGGER.info('No sketch_name: node %s', node_id)
|
2015-11-04 03:53:59 +00:00
|
|
|
return
|
2015-12-31 04:48:23 +00:00
|
|
|
|
|
|
|
for child in gateway.sensors[node_id].children.values():
|
2015-12-23 22:20:39 +00:00
|
|
|
for value_type in child.values.keys():
|
2016-01-26 02:35:34 +00:00
|
|
|
key = node_id, child.id, value_type
|
2015-12-31 04:48:23 +00:00
|
|
|
if child.type not in s_types or value_type not in v_types:
|
|
|
|
continue
|
2016-01-26 02:35:34 +00:00
|
|
|
if key in devices:
|
|
|
|
devices[key].update_ha_state(True)
|
|
|
|
continue
|
|
|
|
name = '{} {}.{}'.format(
|
|
|
|
gateway.sensors[node_id].sketch_name, node_id, child.id)
|
|
|
|
devices[key] = entity_class(
|
|
|
|
gateway, node_id, child.id, name, value_type)
|
|
|
|
|
|
|
|
_LOGGER.info('Adding new devices: %s', devices[key])
|
|
|
|
add_devices([devices[key]])
|
|
|
|
if key in devices:
|
|
|
|
devices[key].update_ha_state(True)
|
2015-12-31 04:48:23 +00:00
|
|
|
return mysensors_callback
|
|
|
|
|
|
|
|
|
2016-01-10 03:10:38 +00:00
|
|
|
class GatewayWrapper(object):
|
2015-12-31 04:48:23 +00:00
|
|
|
"""Gateway wrapper class, by subclassing serial gateway."""
|
|
|
|
|
2016-01-10 03:10:38 +00:00
|
|
|
def __init__(self, gateway, version):
|
2015-12-31 04:48:23 +00:00
|
|
|
"""Setup class attributes on instantiation.
|
|
|
|
|
|
|
|
Args:
|
2016-01-10 03:10:38 +00:00
|
|
|
gateway (mysensors.SerialGateway): Gateway to wrap.
|
|
|
|
version (str): Version of mysensors API.
|
2015-12-31 04:48:23 +00:00
|
|
|
|
|
|
|
Attributes:
|
2016-01-10 03:10:38 +00:00
|
|
|
_wrapped_gateway (mysensors.SerialGateway): Wrapped gateway.
|
2015-12-31 04:48:23 +00:00
|
|
|
version (str): Version of mysensors API.
|
|
|
|
platform_callbacks (list): Callback functions, one per platform.
|
|
|
|
const (module): Mysensors API constants.
|
2016-01-10 03:10:38 +00:00
|
|
|
__initialised (bool): True if GatewayWrapper is initialised.
|
2015-12-31 04:48:23 +00:00
|
|
|
"""
|
2016-01-10 03:10:38 +00:00
|
|
|
self._wrapped_gateway = gateway
|
2015-12-31 04:48:23 +00:00
|
|
|
self.version = version
|
|
|
|
self.platform_callbacks = []
|
|
|
|
self.const = self.get_const()
|
2016-01-10 03:10:38 +00:00
|
|
|
self.__initialised = True
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
"""See if this object has attribute name."""
|
|
|
|
# Do not use hasattr, it goes into infinite recurrsion
|
|
|
|
if name in self.__dict__:
|
|
|
|
# this object has it
|
|
|
|
return getattr(self, name)
|
|
|
|
# proxy to the wrapped object
|
|
|
|
return getattr(self._wrapped_gateway, name)
|
|
|
|
|
|
|
|
def __setattr__(self, name, value):
|
|
|
|
"""See if this object has attribute name then set to value."""
|
|
|
|
if '_GatewayWrapper__initialised' not in self.__dict__:
|
|
|
|
return object.__setattr__(self, name, value)
|
|
|
|
elif name in self.__dict__:
|
|
|
|
object.__setattr__(self, name, value)
|
|
|
|
else:
|
|
|
|
object.__setattr__(self._wrapped_gateway, name, value)
|
2015-12-31 04:48:23 +00:00
|
|
|
|
|
|
|
def get_const(self):
|
|
|
|
"""Get mysensors API constants."""
|
|
|
|
if self.version == '1.5':
|
|
|
|
import mysensors.const_15 as const
|
|
|
|
else:
|
|
|
|
import mysensors.const_14 as const
|
|
|
|
return const
|
|
|
|
|
|
|
|
def callback_factory(self):
|
|
|
|
"""Return a new callback function."""
|
|
|
|
def node_update(update_type, node_id):
|
|
|
|
"""Callback for node updates from the MySensors gateway."""
|
|
|
|
_LOGGER.info('update %s: node %s', update_type, node_id)
|
|
|
|
for callback in self.platform_callbacks:
|
|
|
|
callback(self, node_id)
|
|
|
|
|
|
|
|
return node_update
|