Home Assistant Unofficial Reference 2024.12.1
binary_sensor.py
Go to the documentation of this file.
1 """Support for exposing Concord232 elements as sensors."""
2 
3 from __future__ import annotations
4 
5 import datetime
6 import logging
7 
8 from concord232 import client as concord232_client
9 import requests
10 import voluptuous as vol
11 
13  DEVICE_CLASSES_SCHEMA as BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
14  PLATFORM_SCHEMA as BINARY_SENSOR_PLATFORM_SCHEMA,
15  BinarySensorDeviceClass,
16  BinarySensorEntity,
17 )
18 from homeassistant.const import CONF_HOST, CONF_PORT
19 from homeassistant.core import HomeAssistant
21 from homeassistant.helpers.entity_platform import AddEntitiesCallback
22 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
23 import homeassistant.util.dt as dt_util
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_NAME = "Alarm"
32 DEFAULT_PORT = "5007"
33 DEFAULT_SSL = False
34 
35 SCAN_INTERVAL = datetime.timedelta(seconds=10)
36 
37 ZONE_TYPES_SCHEMA = vol.Schema({cv.positive_int: BINARY_SENSOR_DEVICE_CLASSES_SCHEMA})
38 
39 PLATFORM_SCHEMA = BINARY_SENSOR_PLATFORM_SCHEMA.extend(
40  {
41  vol.Optional(CONF_EXCLUDE_ZONES, default=[]): vol.All(
42  cv.ensure_list, [cv.positive_int]
43  ),
44  vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
45  vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
46  vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA,
47  }
48 )
49 
50 
52  hass: HomeAssistant,
53  config: ConfigType,
54  add_entities: AddEntitiesCallback,
55  discovery_info: DiscoveryInfoType | None = None,
56 ) -> None:
57  """Set up the Concord232 binary sensor platform."""
58 
59  host = config[CONF_HOST]
60  port = config[CONF_PORT]
61  exclude = config[CONF_EXCLUDE_ZONES]
62  zone_types = config[CONF_ZONE_TYPES]
63  sensors = []
64 
65  try:
66  _LOGGER.debug("Initializing client")
67  client = concord232_client.Client(f"http://{host}:{port}")
68  client.zones = client.list_zones()
69  client.last_zone_update = dt_util.utcnow()
70 
71  except requests.exceptions.ConnectionError as ex:
72  _LOGGER.error("Unable to connect to Concord232: %s", str(ex))
73  return
74 
75  # The order of zones returned by client.list_zones() can vary.
76  # When the zones are not named, this can result in the same entity
77  # name mapping to different sensors in an unpredictable way. Sort
78  # the zones by zone number to prevent this.
79 
80  client.zones.sort(key=lambda zone: zone["number"])
81 
82  for zone in client.zones:
83  _LOGGER.debug("Loading Zone found: %s", zone["name"])
84  if zone["number"] not in exclude:
85  sensors.append(
87  hass,
88  client,
89  zone,
90  zone_types.get(zone["number"], get_opening_type(zone)),
91  )
92  )
93 
94  add_entities(sensors, True)
95 
96 
97 def get_opening_type(zone):
98  """Return the result of the type guessing from name."""
99  if "MOTION" in zone["name"]:
100  return BinarySensorDeviceClass.MOTION
101  if "KEY" in zone["name"]:
102  return BinarySensorDeviceClass.SAFETY
103  if "SMOKE" in zone["name"]:
104  return BinarySensorDeviceClass.SMOKE
105  if "WATER" in zone["name"]:
106  return "water"
107  return BinarySensorDeviceClass.OPENING
108 
109 
111  """Representation of a Concord232 zone as a sensor."""
112 
113  def __init__(self, hass, client, zone, zone_type):
114  """Initialize the Concord232 binary sensor."""
115  self._hass_hass = hass
116  self._client_client = client
117  self._zone_zone = zone
118  self._number_number = zone["number"]
119  self._zone_type_zone_type = zone_type
120 
121  @property
122  def device_class(self):
123  """Return the class of this sensor, from DEVICE_CLASSES."""
124  return self._zone_type_zone_type
125 
126  @property
127  def name(self):
128  """Return the name of the binary sensor."""
129  return self._zone_zone["name"]
130 
131  @property
132  def is_on(self):
133  """Return true if the binary sensor is on."""
134  # True means "faulted" or "open" or "abnormal state"
135  return bool(self._zone_zone["state"] != "Normal")
136 
137  def update(self) -> None:
138  """Get updated stats from API."""
139  last_update = dt_util.utcnow() - self._client_client.last_zone_update
140  _LOGGER.debug("Zone: %s ", self._zone_zone)
141  if last_update > datetime.timedelta(seconds=1):
142  self._client_client.zones = self._client_client.list_zones()
143  self._client_client.last_zone_update = dt_util.utcnow()
144  _LOGGER.debug("Updated from zone: %s", self._zone_zone["name"])
145 
146  if hasattr(self._client_client, "zones"):
147  self._zone_zone = next(
148  (x for x in self._client_client.zones if x["number"] == self._number_number), None
149  )
None add_entities(AsusWrtRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)