Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for reading data from a serial port."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 import json
7 import logging
8 
9 from serial import SerialException
10 import serial_asyncio_fast as serial_asyncio
11 import voluptuous as vol
12 
14  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
15  SensorEntity,
16 )
17 from homeassistant.const import CONF_NAME, CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_STOP
18 from homeassistant.core import HomeAssistant, callback
20 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
22 
23 _LOGGER = logging.getLogger(__name__)
24 
25 CONF_SERIAL_PORT = "serial_port"
26 CONF_BAUDRATE = "baudrate"
27 CONF_BYTESIZE = "bytesize"
28 CONF_PARITY = "parity"
29 CONF_STOPBITS = "stopbits"
30 CONF_XONXOFF = "xonxoff"
31 CONF_RTSCTS = "rtscts"
32 CONF_DSRDTR = "dsrdtr"
33 
34 DEFAULT_NAME = "Serial Sensor"
35 DEFAULT_BAUDRATE = 9600
36 DEFAULT_BYTESIZE = serial_asyncio.serial.EIGHTBITS
37 DEFAULT_PARITY = serial_asyncio.serial.PARITY_NONE
38 DEFAULT_STOPBITS = serial_asyncio.serial.STOPBITS_ONE
39 DEFAULT_XONXOFF = False
40 DEFAULT_RTSCTS = False
41 DEFAULT_DSRDTR = False
42 
43 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
44  {
45  vol.Required(CONF_SERIAL_PORT): cv.string,
46  vol.Optional(CONF_BAUDRATE, default=DEFAULT_BAUDRATE): cv.positive_int,
47  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
48  vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
49  vol.Optional(CONF_BYTESIZE, default=DEFAULT_BYTESIZE): vol.In(
50  [
51  serial_asyncio.serial.FIVEBITS,
52  serial_asyncio.serial.SIXBITS,
53  serial_asyncio.serial.SEVENBITS,
54  serial_asyncio.serial.EIGHTBITS,
55  ]
56  ),
57  vol.Optional(CONF_PARITY, default=DEFAULT_PARITY): vol.In(
58  [
59  serial_asyncio.serial.PARITY_NONE,
60  serial_asyncio.serial.PARITY_EVEN,
61  serial_asyncio.serial.PARITY_ODD,
62  serial_asyncio.serial.PARITY_MARK,
63  serial_asyncio.serial.PARITY_SPACE,
64  ]
65  ),
66  vol.Optional(CONF_STOPBITS, default=DEFAULT_STOPBITS): vol.In(
67  [
68  serial_asyncio.serial.STOPBITS_ONE,
69  serial_asyncio.serial.STOPBITS_ONE_POINT_FIVE,
70  serial_asyncio.serial.STOPBITS_TWO,
71  ]
72  ),
73  vol.Optional(CONF_XONXOFF, default=DEFAULT_XONXOFF): cv.boolean,
74  vol.Optional(CONF_RTSCTS, default=DEFAULT_RTSCTS): cv.boolean,
75  vol.Optional(CONF_DSRDTR, default=DEFAULT_DSRDTR): cv.boolean,
76  }
77 )
78 
79 
81  hass: HomeAssistant,
82  config: ConfigType,
83  async_add_entities: AddEntitiesCallback,
84  discovery_info: DiscoveryInfoType | None = None,
85 ) -> None:
86  """Set up the Serial sensor platform."""
87  name = config.get(CONF_NAME)
88  port = config.get(CONF_SERIAL_PORT)
89  baudrate = config.get(CONF_BAUDRATE)
90  bytesize = config.get(CONF_BYTESIZE)
91  parity = config.get(CONF_PARITY)
92  stopbits = config.get(CONF_STOPBITS)
93  xonxoff = config.get(CONF_XONXOFF)
94  rtscts = config.get(CONF_RTSCTS)
95  dsrdtr = config.get(CONF_DSRDTR)
96  value_template = config.get(CONF_VALUE_TEMPLATE)
97 
98  sensor = SerialSensor(
99  name,
100  port,
101  baudrate,
102  bytesize,
103  parity,
104  stopbits,
105  xonxoff,
106  rtscts,
107  dsrdtr,
108  value_template,
109  )
110 
111  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, sensor.stop_serial_read)
112  async_add_entities([sensor], True)
113 
114 
116  """Representation of a Serial sensor."""
117 
118  _attr_should_poll = False
119 
120  def __init__(
121  self,
122  name,
123  port,
124  baudrate,
125  bytesize,
126  parity,
127  stopbits,
128  xonxoff,
129  rtscts,
130  dsrdtr,
131  value_template,
132  ):
133  """Initialize the Serial sensor."""
134  self._name_name = name
135  self._state_state = None
136  self._port_port = port
137  self._baudrate_baudrate = baudrate
138  self._bytesize_bytesize = bytesize
139  self._parity_parity = parity
140  self._stopbits_stopbits = stopbits
141  self._xonxoff_xonxoff = xonxoff
142  self._rtscts_rtscts = rtscts
143  self._dsrdtr_dsrdtr = dsrdtr
144  self._serial_loop_task_serial_loop_task = None
145  self._template_template = value_template
146  self._attributes_attributes = None
147 
148  async def async_added_to_hass(self) -> None:
149  """Handle when an entity is about to be added to Home Assistant."""
150  self._serial_loop_task_serial_loop_task = self.hasshass.loop.create_task(
151  self.serial_readserial_read(
152  self._port_port,
153  self._baudrate_baudrate,
154  self._bytesize_bytesize,
155  self._parity_parity,
156  self._stopbits_stopbits,
157  self._xonxoff_xonxoff,
158  self._rtscts_rtscts,
159  self._dsrdtr_dsrdtr,
160  )
161  )
162 
163  async def serial_read(
164  self,
165  device,
166  baudrate,
167  bytesize,
168  parity,
169  stopbits,
170  xonxoff,
171  rtscts,
172  dsrdtr,
173  **kwargs,
174  ):
175  """Read the data from the port."""
176  logged_error = False
177  while True:
178  try:
179  reader, _ = await serial_asyncio.open_serial_connection(
180  url=device,
181  baudrate=baudrate,
182  bytesize=bytesize,
183  parity=parity,
184  stopbits=stopbits,
185  xonxoff=xonxoff,
186  rtscts=rtscts,
187  dsrdtr=dsrdtr,
188  **kwargs,
189  )
190 
191  except SerialException:
192  if not logged_error:
193  _LOGGER.exception(
194  "Unable to connect to the serial device %s. Will retry", device
195  )
196  logged_error = True
197  await self._handle_error_handle_error()
198  else:
199  _LOGGER.debug("Serial device %s connected", device)
200  while True:
201  try:
202  line = await reader.readline()
203  except SerialException:
204  _LOGGER.exception(
205  "Error while reading serial device %s", device
206  )
207  await self._handle_error_handle_error()
208  break
209  else:
210  line = line.decode("utf-8").strip()
211 
212  try:
213  data = json.loads(line)
214  except ValueError:
215  pass
216  else:
217  if isinstance(data, dict):
218  self._attributes_attributes = data
219 
220  if self._template_template is not None:
221  line = self._template_template.async_render_with_possible_json_value(
222  line
223  )
224 
225  _LOGGER.debug("Received: %s", line)
226  self._state_state = line
227  self.async_write_ha_stateasync_write_ha_state()
228 
229  async def _handle_error(self):
230  """Handle error for serial connection."""
231  self._state_state = None
232  self._attributes_attributes = None
233  self.async_write_ha_stateasync_write_ha_state()
234  await asyncio.sleep(5)
235 
236  @callback
237  def stop_serial_read(self, event):
238  """Close resources."""
239  if self._serial_loop_task_serial_loop_task:
240  self._serial_loop_task_serial_loop_task.cancel()
241 
242  @property
243  def name(self):
244  """Return the name of the sensor."""
245  return self._name_name
246 
247  @property
249  """Return the attributes of the entity (if any JSON present)."""
250  return self._attributes_attributes
251 
252  @property
253  def native_value(self):
254  """Return the state of the sensor."""
255  return self._state_state
def __init__(self, name, port, baudrate, bytesize, parity, stopbits, xonxoff, rtscts, dsrdtr, value_template)
Definition: sensor.py:132
def serial_read(self, device, baudrate, bytesize, parity, stopbits, xonxoff, rtscts, dsrdtr, **kwargs)
Definition: sensor.py:174
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:85