""" homeassistant.components.sensor.tcp ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides a sensor which gets its values from a TCP socket. """ import logging import socket import re from select import select from homeassistant.const import CONF_NAME, CONF_HOST from homeassistant.helpers.entity import Entity from homeassistant.components.tcp import ( DOMAIN, CONF_PORT, CONF_TIMEOUT, CONF_PAYLOAD, CONF_UNIT, CONF_VALUE_REGEX, CONF_VALUE_ON, CONF_BUFFER_SIZE, DEFAULT_TIMEOUT, DEFAULT_BUFFER_SIZE ) DEPENDENCIES = [DOMAIN] _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): """ Create the Sensor. """ if not Sensor.validate_config(config): return False add_entities((Sensor(config),)) class Sensor(Entity): """ Sensor Entity which gets its value from a TCP socket. """ required = tuple() def __init__(self, config): """ Set all the config values if they exist and get initial state. """ self._config = { CONF_NAME: config.get(CONF_NAME), CONF_HOST: config[CONF_HOST], CONF_PORT: config[CONF_PORT], CONF_TIMEOUT: config.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), CONF_PAYLOAD: config[CONF_PAYLOAD], CONF_UNIT: config.get(CONF_UNIT), CONF_VALUE_REGEX: config.get(CONF_VALUE_REGEX), CONF_VALUE_ON: config.get(CONF_VALUE_ON), CONF_BUFFER_SIZE: config.get( CONF_BUFFER_SIZE, DEFAULT_BUFFER_SIZE), } self._state = None self.update() @classmethod def validate_config(cls, config): """ Ensure the config has all of the necessary values. """ always_required = (CONF_HOST, CONF_PORT, CONF_PAYLOAD) for key in always_required + tuple(cls.required): if key not in config: _LOGGER.error( "You must provide %r to create any TCP entity.", key) return False return True @property def name(self): name = self._config[CONF_NAME] if name is not None: return name return super(Sensor, self).name @property def state(self): return self._state @property def unit_of_measurement(self): return self._config[CONF_UNIT] def update(self): """ Get the latest value for this sensor. """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.connect((self._config[CONF_HOST], self._config[CONF_PORT])) except socket.error as err: _LOGGER.error( "Unable to connect to %s on port %s: %s", self._config[CONF_HOST], self._config[CONF_PORT], err) return try: sock.send(self._config[CONF_PAYLOAD].encode()) except socket.error as err: _LOGGER.error( "Unable to send payload %r to %s on port %s: %s", self._config[CONF_PAYLOAD], self._config[CONF_HOST], self._config[CONF_PORT], err) return readable, _, _ = select([sock], [], [], self._config[CONF_TIMEOUT]) if not readable: _LOGGER.warning( "Timeout (%s second(s)) waiting for a response after sending " "%r to %s on port %s.", self._config[CONF_TIMEOUT], self._config[CONF_PAYLOAD], self._config[CONF_HOST], self._config[CONF_PORT]) return value = sock.recv(self._config[CONF_BUFFER_SIZE]).decode() if self._config[CONF_VALUE_REGEX] is not None: match = re.match(self._config[CONF_VALUE_REGEX], value) if match is None: _LOGGER.warning( "Unable to match value using value_regex of %r: %r", self._config[CONF_VALUE_REGEX], value) return try: self._state = match.groups()[0] except IndexError: _LOGGER.error( "You must include a capture group in the regex for %r: %r", self.name, self._config[CONF_VALUE_REGEX]) return return self._state = value