1
0
mirror of https://github.com/home-assistant/core.git synced 2025-06-23 14:37:07 +00:00
Guillaume DELVIT 8ec5e53cf4
Add full options to serial sensor platform ()
Improve Serial sensor platform with full options for configuring the serial port.
bytesize, parity, stopbits, xonxoff, rtscts, dsrdtr.

The default values are unchanged so it is 100% compatible with previous config.
2020-05-01 10:29:22 +02:00

252 lines
7.6 KiB
Python

"""Support for reading data from a serial port."""
import asyncio
import json
import logging
from serial import SerialException
import serial_asyncio
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
CONF_SERIAL_PORT = "serial_port"
CONF_BAUDRATE = "baudrate"
CONF_BYTESIZE = "bytesize"
CONF_PARITY = "parity"
CONF_STOPBITS = "stopbits"
CONF_XONXOFF = "xonxoff"
CONF_RTSCTS = "rtscts"
CONF_DSRDTR = "dsrdtr"
DEFAULT_NAME = "Serial Sensor"
DEFAULT_BAUDRATE = 9600
DEFAULT_BYTESIZE = serial_asyncio.serial.EIGHTBITS
DEFAULT_PARITY = serial_asyncio.serial.PARITY_NONE
DEFAULT_STOPBITS = serial_asyncio.serial.STOPBITS_ONE
DEFAULT_XONXOFF = False
DEFAULT_RTSCTS = False
DEFAULT_DSRDTR = False
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_SERIAL_PORT): cv.string,
vol.Optional(CONF_BAUDRATE, default=DEFAULT_BAUDRATE): cv.positive_int,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_BYTESIZE, default=DEFAULT_BYTESIZE): vol.In(
[
serial_asyncio.serial.FIVEBITS,
serial_asyncio.serial.SIXBITS,
serial_asyncio.serial.SEVENBITS,
serial_asyncio.serial.EIGHTBITS,
]
),
vol.Optional(CONF_PARITY, default=DEFAULT_PARITY): vol.In(
[
serial_asyncio.serial.PARITY_NONE,
serial_asyncio.serial.PARITY_EVEN,
serial_asyncio.serial.PARITY_ODD,
serial_asyncio.serial.PARITY_MARK,
serial_asyncio.serial.PARITY_SPACE,
]
),
vol.Optional(CONF_STOPBITS, default=DEFAULT_STOPBITS): vol.In(
[
serial_asyncio.serial.STOPBITS_ONE,
serial_asyncio.serial.STOPBITS_ONE_POINT_FIVE,
serial_asyncio.serial.STOPBITS_TWO,
]
),
vol.Optional(CONF_XONXOFF, default=DEFAULT_XONXOFF): cv.boolean,
vol.Optional(CONF_RTSCTS, default=DEFAULT_RTSCTS): cv.boolean,
vol.Optional(CONF_DSRDTR, default=DEFAULT_DSRDTR): cv.boolean,
}
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Serial sensor platform."""
name = config.get(CONF_NAME)
port = config.get(CONF_SERIAL_PORT)
baudrate = config.get(CONF_BAUDRATE)
bytesize = config.get(CONF_BYTESIZE)
parity = config.get(CONF_PARITY)
stopbits = config.get(CONF_STOPBITS)
xonxoff = config.get(CONF_XONXOFF)
rtscts = config.get(CONF_RTSCTS)
dsrdtr = config.get(CONF_DSRDTR)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
sensor = SerialSensor(
name,
port,
baudrate,
bytesize,
parity,
stopbits,
xonxoff,
rtscts,
dsrdtr,
value_template,
)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, sensor.stop_serial_read)
async_add_entities([sensor], True)
class SerialSensor(Entity):
"""Representation of a Serial sensor."""
def __init__(
self,
name,
port,
baudrate,
bytesize,
parity,
stopbits,
xonxoff,
rtscts,
dsrdtr,
value_template,
):
"""Initialize the Serial sensor."""
self._name = name
self._state = None
self._port = port
self._baudrate = baudrate
self._bytesize = bytesize
self._parity = parity
self._stopbits = stopbits
self._xonxoff = xonxoff
self._rtscts = rtscts
self._dsrdtr = dsrdtr
self._serial_loop_task = None
self._template = value_template
self._attributes = None
async def async_added_to_hass(self):
"""Handle when an entity is about to be added to Home Assistant."""
self._serial_loop_task = self.hass.loop.create_task(
self.serial_read(
self._port,
self._baudrate,
self._bytesize,
self._parity,
self._stopbits,
self._xonxoff,
self._rtscts,
self._dsrdtr,
)
)
async def serial_read(
self,
device,
baudrate,
bytesize,
parity,
stopbits,
xonxoff,
rtscts,
dsrdtr,
**kwargs,
):
"""Read the data from the port."""
logged_error = False
while True:
try:
reader, _ = await serial_asyncio.open_serial_connection(
url=device,
baudrate=baudrate,
bytesize=bytesize,
parity=parity,
stopbits=stopbits,
xonxoff=xonxoff,
rtscts=rtscts,
dsrdtr=dsrdtr,
**kwargs,
)
except SerialException as exc:
if not logged_error:
_LOGGER.exception(
"Unable to connect to the serial device %s: %s. Will retry",
device,
exc,
)
logged_error = True
await self._handle_error()
else:
_LOGGER.info("Serial device %s connected", device)
while True:
try:
line = await reader.readline()
except SerialException as exc:
_LOGGER.exception(
"Error while reading serial device %s: %s", device, exc
)
await self._handle_error()
break
else:
line = line.decode("utf-8").strip()
try:
data = json.loads(line)
except ValueError:
pass
else:
if isinstance(data, dict):
self._attributes = data
if self._template is not None:
line = self._template.async_render_with_possible_json_value(
line
)
_LOGGER.debug("Received: %s", line)
self._state = line
self.async_write_ha_state()
async def _handle_error(self):
"""Handle error for serial connection."""
self._state = None
self._attributes = None
self.async_write_ha_state()
await asyncio.sleep(5)
@callback
def stop_serial_read(self, event):
"""Close resources."""
if self._serial_loop_task:
self._serial_loop_task.cancel()
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def device_state_attributes(self):
"""Return the attributes of the entity (if any JSON present)."""
return self._attributes
@property
def state(self):
"""Return the state of the sensor."""
return self._state