Home Assistant Unofficial Reference 2024.12.1
binary_sensor.py
Go to the documentation of this file.
1 """Support for exposing NX584 elements as sensors."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import threading
7 import time
8 
9 from nx584 import client as nx584_client
10 import requests
11 import voluptuous as vol
12 
14  DEVICE_CLASSES_SCHEMA as BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
15  PLATFORM_SCHEMA as BINARY_SENSOR_PLATFORM_SCHEMA,
16  BinarySensorDeviceClass,
17  BinarySensorEntity,
18 )
19 from homeassistant.const import CONF_HOST, CONF_PORT
20 from homeassistant.core import HomeAssistant
22 from homeassistant.helpers.entity_platform import AddEntitiesCallback
23 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
24 
25 _LOGGER = logging.getLogger(__name__)
26 
27 CONF_EXCLUDE_ZONES = "exclude_zones"
28 CONF_ZONE_TYPES = "zone_types"
29 
30 DEFAULT_HOST = "localhost"
31 DEFAULT_PORT = "5007"
32 DEFAULT_SSL = False
33 
34 ZONE_TYPES_SCHEMA = vol.Schema({cv.positive_int: BINARY_SENSOR_DEVICE_CLASSES_SCHEMA})
35 
36 PLATFORM_SCHEMA = BINARY_SENSOR_PLATFORM_SCHEMA.extend(
37  {
38  vol.Optional(CONF_EXCLUDE_ZONES, default=[]): vol.All(
39  cv.ensure_list, [cv.positive_int]
40  ),
41  vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
42  vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
43  vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA,
44  }
45 )
46 
47 
49  hass: HomeAssistant,
50  config: ConfigType,
51  add_entities: AddEntitiesCallback,
52  discovery_info: DiscoveryInfoType | None = None,
53 ) -> None:
54  """Set up the NX584 binary sensor platform."""
55 
56  host = config[CONF_HOST]
57  port = config[CONF_PORT]
58  exclude = config[CONF_EXCLUDE_ZONES]
59  zone_types = config[CONF_ZONE_TYPES]
60 
61  try:
62  client = nx584_client.Client(f"http://{host}:{port}")
63  zones = client.list_zones()
64  except requests.exceptions.ConnectionError as ex:
65  _LOGGER.error("Unable to connect to NX584: %s", str(ex))
66  return
67 
68  version = [int(v) for v in client.get_version().split(".")]
69  if version < [1, 1]:
70  _LOGGER.error("NX584 is too old to use for sensors (>=0.2 required)")
71  return
72 
73  zone_sensors = {
74  zone["number"]: NX584ZoneSensor(
75  zone, zone_types.get(zone["number"], BinarySensorDeviceClass.OPENING)
76  )
77  for zone in zones
78  if zone["number"] not in exclude
79  }
80  if zone_sensors:
81  add_entities(zone_sensors.values())
82  watcher = NX584Watcher(client, zone_sensors)
83  watcher.start()
84  else:
85  _LOGGER.warning("No zones found on NX584")
86 
87 
89  """Representation of a NX584 zone as a sensor."""
90 
91  _attr_should_poll = False
92 
93  def __init__(self, zone, zone_type):
94  """Initialize the nx594 binary sensor."""
95  self._zone_zone = zone
96  self._zone_type_zone_type = zone_type
97 
98  @property
99  def device_class(self):
100  """Return the class of this sensor, from DEVICE_CLASSES."""
101  return self._zone_type_zone_type
102 
103  @property
104  def name(self):
105  """Return the name of the binary sensor."""
106  return self._zone_zone["name"]
107 
108  @property
109  def is_on(self):
110  """Return true if the binary sensor is on."""
111  # True means "faulted" or "open" or "abnormal state"
112  return self._zone_zone["state"]
113 
114  @property
116  """Return the state attributes."""
117  return {
118  "zone_number": self._zone_zone["number"],
119  "bypassed": self._zone_zone.get("bypassed", False),
120  }
121 
122 
123 class NX584Watcher(threading.Thread):
124  """Event listener thread to process NX584 events."""
125 
126  def __init__(self, client, zone_sensors):
127  """Initialize NX584 watcher thread."""
128  super().__init__()
129  self.daemondaemon = True
130  self._client_client = client
131  self._zone_sensors_zone_sensors = zone_sensors
132 
133  def _process_zone_event(self, event):
134  zone = event["zone"]
135  if not (zone_sensor := self._zone_sensors_zone_sensors.get(zone)):
136  return
137  zone_sensor._zone["state"] = event["zone_state"] # noqa: SLF001
138  zone_sensor.schedule_update_ha_state()
139 
140  def _process_events(self, events):
141  for event in events:
142  if event.get("type") == "zone_status":
143  self._process_zone_event_process_zone_event(event)
144 
145  def _run(self):
146  """Throw away any existing events so we don't replay history."""
147  self._client_client.get_events()
148  while True:
149  if events := self._client_client.get_events():
150  self._process_events_process_events(events)
151 
152  def run(self):
153  """Run the watcher."""
154  while True:
155  try:
156  self._run_run()
157  except requests.exceptions.ConnectionError:
158  _LOGGER.error("Failed to reach NX584 server")
159  time.sleep(10)
None add_entities(AsusWrtRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)