2019-04-03 15:40:03 +00:00
|
|
|
"""Support for TCP socket based sensors."""
|
2016-02-14 00:03:56 +00:00
|
|
|
import logging
|
2016-02-19 17:41:51 +00:00
|
|
|
import select
|
2019-12-09 13:41:48 +00:00
|
|
|
import socket
|
2021-05-15 18:29:11 +00:00
|
|
|
import ssl
|
2016-02-14 00:03:56 +00:00
|
|
|
|
2016-10-22 04:14:35 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2021-03-22 18:47:44 +00:00
|
|
|
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
|
2016-10-22 04:14:35 +00:00
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_HOST,
|
2019-12-09 13:41:48 +00:00
|
|
|
CONF_NAME,
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_PAYLOAD,
|
2019-12-09 13:41:48 +00:00
|
|
|
CONF_PORT,
|
2021-05-15 18:29:11 +00:00
|
|
|
CONF_SSL,
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_TIMEOUT,
|
|
|
|
CONF_UNIT_OF_MEASUREMENT,
|
|
|
|
CONF_VALUE_TEMPLATE,
|
2021-05-15 18:29:11 +00:00
|
|
|
CONF_VERIFY_SSL,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2016-02-17 18:12:36 +00:00
|
|
|
from homeassistant.exceptions import TemplateError
|
2016-10-22 04:14:35 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2016-02-14 00:03:56 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_BUFFER_SIZE = "buffer_size"
|
|
|
|
CONF_VALUE_ON = "value_on"
|
2016-02-18 16:57:32 +00:00
|
|
|
|
|
|
|
DEFAULT_BUFFER_SIZE = 1024
|
2019-07-31 19:25:30 +00:00
|
|
|
DEFAULT_NAME = "TCP Sensor"
|
2016-10-22 04:14:35 +00:00
|
|
|
DEFAULT_TIMEOUT = 10
|
2021-05-15 18:29:11 +00:00
|
|
|
DEFAULT_SSL = False
|
|
|
|
DEFAULT_VERIFY_SSL = True
|
2016-02-14 00:03:56 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_HOST): cv.string,
|
|
|
|
vol.Required(CONF_PORT): cv.port,
|
|
|
|
vol.Required(CONF_PAYLOAD): cv.string,
|
|
|
|
vol.Optional(CONF_BUFFER_SIZE, default=DEFAULT_BUFFER_SIZE): cv.positive_int,
|
|
|
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
|
|
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
|
|
|
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
|
|
|
vol.Optional(CONF_VALUE_ON): cv.string,
|
|
|
|
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
2021-05-15 18:29:11 +00:00
|
|
|
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
|
|
|
|
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
|
2019-07-31 19:25:30 +00:00
|
|
|
}
|
|
|
|
)
|
2016-02-14 00:03:56 +00:00
|
|
|
|
|
|
|
|
2018-08-24 14:37:30 +00:00
|
|
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
2016-10-22 04:14:35 +00:00
|
|
|
"""Set up the TCP Sensor."""
|
2018-08-24 14:37:30 +00:00
|
|
|
add_entities([TcpSensor(hass, config)])
|
2016-02-17 17:26:53 +00:00
|
|
|
|
|
|
|
|
2021-03-22 18:47:44 +00:00
|
|
|
class TcpSensor(SensorEntity):
|
2016-03-08 15:46:34 +00:00
|
|
|
"""Implementation of a TCP socket based sensor."""
|
|
|
|
|
2020-04-04 20:45:55 +00:00
|
|
|
required = ()
|
2016-02-17 17:26:53 +00:00
|
|
|
|
2016-02-17 18:12:36 +00:00
|
|
|
def __init__(self, hass, config):
|
2016-02-22 13:42:11 +00:00
|
|
|
"""Set all the config values if they exist and get initial state."""
|
2016-09-28 04:29:55 +00:00
|
|
|
value_template = config.get(CONF_VALUE_TEMPLATE)
|
|
|
|
|
|
|
|
if value_template is not None:
|
2017-01-11 16:26:29 +00:00
|
|
|
value_template.hass = hass
|
2016-09-28 04:29:55 +00:00
|
|
|
|
2016-02-17 18:12:36 +00:00
|
|
|
self._hass = hass
|
2016-02-17 17:26:53 +00:00
|
|
|
self._config = {
|
|
|
|
CONF_NAME: config.get(CONF_NAME),
|
2016-10-22 04:14:35 +00:00
|
|
|
CONF_HOST: config.get(CONF_HOST),
|
|
|
|
CONF_PORT: config.get(CONF_PORT),
|
|
|
|
CONF_TIMEOUT: config.get(CONF_TIMEOUT),
|
|
|
|
CONF_PAYLOAD: config.get(CONF_PAYLOAD),
|
|
|
|
CONF_UNIT_OF_MEASUREMENT: config.get(CONF_UNIT_OF_MEASUREMENT),
|
2016-09-28 04:29:55 +00:00
|
|
|
CONF_VALUE_TEMPLATE: value_template,
|
2016-02-17 17:26:53 +00:00
|
|
|
CONF_VALUE_ON: config.get(CONF_VALUE_ON),
|
2016-10-22 04:14:35 +00:00
|
|
|
CONF_BUFFER_SIZE: config.get(CONF_BUFFER_SIZE),
|
2016-02-17 17:26:53 +00:00
|
|
|
}
|
2021-05-15 18:29:11 +00:00
|
|
|
|
|
|
|
if config[CONF_SSL]:
|
|
|
|
self._ssl_context = ssl.create_default_context()
|
|
|
|
if not config[CONF_VERIFY_SSL]:
|
|
|
|
self._ssl_context.check_hostname = False
|
|
|
|
self._ssl_context.verify_mode = ssl.CERT_NONE
|
|
|
|
else:
|
|
|
|
self._ssl_context = None
|
|
|
|
|
2016-02-17 17:26:53 +00:00
|
|
|
self._state = None
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
2016-03-08 15:46:34 +00:00
|
|
|
"""Return the name of this sensor."""
|
2021-01-27 21:37:59 +00:00
|
|
|
return self._config[CONF_NAME]
|
2016-02-17 17:26:53 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def state(self):
|
2016-02-22 13:42:11 +00:00
|
|
|
"""Return the state of the device."""
|
2016-02-17 17:26:53 +00:00
|
|
|
return self._state
|
|
|
|
|
|
|
|
@property
|
|
|
|
def unit_of_measurement(self):
|
2016-03-08 15:46:34 +00:00
|
|
|
"""Return the unit of measurement of this entity."""
|
2016-10-22 04:14:35 +00:00
|
|
|
return self._config[CONF_UNIT_OF_MEASUREMENT]
|
2016-02-17 17:26:53 +00:00
|
|
|
|
|
|
|
def update(self):
|
2016-02-22 13:42:11 +00:00
|
|
|
"""Get the latest value for this sensor."""
|
2016-02-19 17:41:51 +00:00
|
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
2016-03-05 17:27:22 +00:00
|
|
|
sock.settimeout(self._config[CONF_TIMEOUT])
|
2016-02-19 17:41:51 +00:00
|
|
|
try:
|
2019-07-31 19:25:30 +00:00
|
|
|
sock.connect((self._config[CONF_HOST], self._config[CONF_PORT]))
|
2020-04-04 20:09:11 +00:00
|
|
|
except OSError as err:
|
2016-02-19 17:41:51 +00:00
|
|
|
_LOGGER.error(
|
|
|
|
"Unable to connect to %s on port %s: %s",
|
2019-07-31 19:25:30 +00:00
|
|
|
self._config[CONF_HOST],
|
|
|
|
self._config[CONF_PORT],
|
|
|
|
err,
|
|
|
|
)
|
2016-02-19 17:41:51 +00:00
|
|
|
return
|
|
|
|
|
2021-05-15 18:29:11 +00:00
|
|
|
if self._ssl_context is not None:
|
|
|
|
sock = self._ssl_context.wrap_socket(
|
|
|
|
sock, server_hostname=self._config[CONF_HOST]
|
|
|
|
)
|
|
|
|
|
2016-02-19 17:41:51 +00:00
|
|
|
try:
|
|
|
|
sock.send(self._config[CONF_PAYLOAD].encode())
|
2020-04-04 20:09:11 +00:00
|
|
|
except OSError as err:
|
2016-02-19 17:41:51 +00:00
|
|
|
_LOGGER.error(
|
|
|
|
"Unable to send payload %r to %s on port %s: %s",
|
2019-07-31 19:25:30 +00:00
|
|
|
self._config[CONF_PAYLOAD],
|
|
|
|
self._config[CONF_HOST],
|
|
|
|
self._config[CONF_PORT],
|
|
|
|
err,
|
|
|
|
)
|
2016-02-19 17:41:51 +00:00
|
|
|
return
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
readable, _, _ = select.select([sock], [], [], self._config[CONF_TIMEOUT])
|
2016-02-19 17:41:51 +00:00
|
|
|
if not readable:
|
|
|
|
_LOGGER.warning(
|
|
|
|
"Timeout (%s second(s)) waiting for a response after "
|
2020-07-05 21:04:19 +00:00
|
|
|
"sending %r to %s on port %s",
|
2019-07-31 19:25:30 +00:00
|
|
|
self._config[CONF_TIMEOUT],
|
|
|
|
self._config[CONF_PAYLOAD],
|
|
|
|
self._config[CONF_HOST],
|
|
|
|
self._config[CONF_PORT],
|
|
|
|
)
|
2016-02-19 17:41:51 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
value = sock.recv(self._config[CONF_BUFFER_SIZE]).decode()
|
2016-02-17 18:12:36 +00:00
|
|
|
|
|
|
|
if self._config[CONF_VALUE_TEMPLATE] is not None:
|
2016-02-17 17:26:53 +00:00
|
|
|
try:
|
2020-10-26 18:29:10 +00:00
|
|
|
self._state = self._config[CONF_VALUE_TEMPLATE].render(
|
|
|
|
parse_result=False, value=value
|
|
|
|
)
|
2016-02-17 18:12:36 +00:00
|
|
|
return
|
2018-10-25 20:15:20 +00:00
|
|
|
except TemplateError:
|
2016-02-17 17:26:53 +00:00
|
|
|
_LOGGER.error(
|
2016-02-17 18:12:36 +00:00
|
|
|
"Unable to render template of %r with value: %r",
|
2019-07-31 19:25:30 +00:00
|
|
|
self._config[CONF_VALUE_TEMPLATE],
|
|
|
|
value,
|
|
|
|
)
|
2016-02-17 17:26:53 +00:00
|
|
|
return
|
2016-02-17 18:12:36 +00:00
|
|
|
|
2016-02-17 17:26:53 +00:00
|
|
|
self._state = value
|