core/homeassistant/components/mysensors.py

219 lines
8.1 KiB
Python

"""
Connect to a MySensors gateway via pymysensors API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors/
"""
import logging
import homeassistant.bootstrap as bootstrap
from homeassistant.const import (
ATTR_DISCOVERED, ATTR_SERVICE, EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED, TEMP_CELCIUS)
from homeassistant.helpers import validate_config
CONF_GATEWAYS = 'gateways'
CONF_PORT = 'port'
CONF_DEBUG = 'debug'
CONF_PERSISTENCE = 'persistence'
CONF_PERSISTENCE_FILE = 'persistence_file'
CONF_VERSION = 'version'
CONF_BAUD_RATE = 'baud_rate'
CONF_OPTIMISTIC = 'optimistic'
DEFAULT_VERSION = '1.4'
DEFAULT_BAUD_RATE = 115200
DOMAIN = 'mysensors'
DEPENDENCIES = []
REQUIREMENTS = [
'https://github.com/theolind/pymysensors/archive/'
'f0c928532167fb24823efa793ec21ca646fd37a6.zip#pymysensors==0.5']
_LOGGER = logging.getLogger(__name__)
ATTR_NODE_ID = 'node_id'
ATTR_CHILD_ID = 'child_id'
ATTR_PORT = 'port'
GATEWAYS = None
DISCOVER_SENSORS = 'mysensors.sensors'
DISCOVER_SWITCHES = 'mysensors.switches'
DISCOVER_LIGHTS = 'mysensors.lights'
DISCOVER_BINARY_SENSORS = 'mysensors.binary_sensor'
# Maps discovered services to their platforms
DISCOVERY_COMPONENTS = [
('sensor', DISCOVER_SENSORS),
('switch', DISCOVER_SWITCHES),
('light', DISCOVER_LIGHTS),
('binary_sensor', DISCOVER_BINARY_SENSORS),
]
def setup(hass, config):
"""Setup the MySensors component."""
if not validate_config(config,
{DOMAIN: [CONF_GATEWAYS]},
_LOGGER):
return False
if not all(CONF_PORT in gateway
for gateway in config[DOMAIN][CONF_GATEWAYS]):
_LOGGER.error('Missing required configuration items '
'in %s: %s', DOMAIN, CONF_PORT)
return False
import mysensors.mysensors as mysensors
version = str(config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION))
is_metric = (hass.config.temperature_unit == TEMP_CELCIUS)
def setup_gateway(port, persistence, persistence_file, version, baud_rate):
"""Return gateway after setup of the gateway."""
gateway = mysensors.SerialGateway(port, event_callback=None,
persistence=persistence,
persistence_file=persistence_file,
protocol_version=version,
baud=baud_rate)
gateway.metric = is_metric
gateway.debug = config[DOMAIN].get(CONF_DEBUG, False)
optimistic = config[DOMAIN].get(CONF_OPTIMISTIC, False)
gateway = GatewayWrapper(gateway, version, optimistic)
# pylint: disable=attribute-defined-outside-init
gateway.event_callback = gateway.callback_factory()
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)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, gw_start)
return gateway
# Setup all ports from config
global GATEWAYS
GATEWAYS = {}
conf_gateways = config[DOMAIN][CONF_GATEWAYS]
if isinstance(conf_gateways, dict):
conf_gateways = [conf_gateways]
persistence = config[DOMAIN].get(CONF_PERSISTENCE, True)
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)))
baud_rate = gway.get(CONF_BAUD_RATE, DEFAULT_BAUD_RATE)
GATEWAYS[port] = setup_gateway(
port, persistence, persistence_file, version, baud_rate)
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: {}})
return True
def pf_callback_factory(map_sv_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)
return
for child in gateway.sensors[node_id].children.values():
for value_type in child.values.keys():
key = node_id, child.id, value_type
if child.type not in map_sv_types or \
value_type not in map_sv_types[child.type]:
continue
if key in devices:
devices[key].update_ha_state(True)
continue
name = '{} {}.{}'.format(
gateway.sensors[node_id].sketch_name, node_id, child.id)
if isinstance(entity_class, dict):
device_class = entity_class[child.type]
else:
device_class = entity_class
devices[key] = device_class(
gateway, node_id, child.id, name, value_type, child.type)
_LOGGER.info('Adding new devices: %s', devices[key])
add_devices([devices[key]])
if key in devices:
devices[key].update_ha_state(True)
return mysensors_callback
class GatewayWrapper(object):
"""Gateway wrapper class."""
def __init__(self, gateway, version, optimistic):
"""Setup class attributes on instantiation.
Args:
gateway (mysensors.SerialGateway): Gateway to wrap.
version (str): Version of mysensors API.
optimistic (bool): Send values to actuators without feedback state.
Attributes:
_wrapped_gateway (mysensors.SerialGateway): Wrapped gateway.
version (str): Version of mysensors API.
platform_callbacks (list): Callback functions, one per platform.
const (module): Mysensors API constants.
optimistic (bool): Send values to actuators without feedback state.
__initialised (bool): True if GatewayWrapper is initialised.
"""
self._wrapped_gateway = gateway
self.version = version
self.platform_callbacks = []
self.const = self.get_const()
self.optimistic = optimistic
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)
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.debug('update %s: node %s', update_type, node_id)
for callback in self.platform_callbacks:
callback(self, node_id)
return node_update