Home Assistant Unofficial Reference 2024.12.1
binary_sensor.py
Go to the documentation of this file.
1 """Support for Hikvision event stream events represented as binary sensors."""
2 
3 from __future__ import annotations
4 
5 from datetime import timedelta
6 import logging
7 
8 from pyhik.hikvision import HikCamera
9 import voluptuous as vol
10 
12  PLATFORM_SCHEMA as BINARY_SENSOR_PLATFORM_SCHEMA,
13  BinarySensorDeviceClass,
14  BinarySensorEntity,
15 )
16 from homeassistant.const import (
17  ATTR_LAST_TRIP_TIME,
18  CONF_CUSTOMIZE,
19  CONF_DELAY,
20  CONF_HOST,
21  CONF_NAME,
22  CONF_PASSWORD,
23  CONF_PORT,
24  CONF_SSL,
25  CONF_USERNAME,
26  EVENT_HOMEASSISTANT_START,
27  EVENT_HOMEASSISTANT_STOP,
28 )
29 from homeassistant.core import HomeAssistant
31 from homeassistant.helpers.entity_platform import AddEntitiesCallback
32 from homeassistant.helpers.event import track_point_in_utc_time
33 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
34 from homeassistant.util.dt import utcnow
35 
36 _LOGGER = logging.getLogger(__name__)
37 
38 CONF_IGNORED = "ignored"
39 
40 DEFAULT_PORT = 80
41 DEFAULT_IGNORED = False
42 DEFAULT_DELAY = 0
43 
44 ATTR_DELAY = "delay"
45 
46 DEVICE_CLASS_MAP = {
47  "Motion": BinarySensorDeviceClass.MOTION,
48  "Line Crossing": BinarySensorDeviceClass.MOTION,
49  "Field Detection": BinarySensorDeviceClass.MOTION,
50  "Tamper Detection": BinarySensorDeviceClass.MOTION,
51  "Shelter Alarm": None,
52  "Disk Full": None,
53  "Disk Error": None,
54  "Net Interface Broken": BinarySensorDeviceClass.CONNECTIVITY,
55  "IP Conflict": BinarySensorDeviceClass.CONNECTIVITY,
56  "Illegal Access": None,
57  "Video Mismatch": None,
58  "Bad Video": None,
59  "PIR Alarm": BinarySensorDeviceClass.MOTION,
60  "Face Detection": BinarySensorDeviceClass.MOTION,
61  "Scene Change Detection": BinarySensorDeviceClass.MOTION,
62  "I/O": None,
63  "Unattended Baggage": BinarySensorDeviceClass.MOTION,
64  "Attended Baggage": BinarySensorDeviceClass.MOTION,
65  "Recording Failure": None,
66  "Exiting Region": BinarySensorDeviceClass.MOTION,
67  "Entering Region": BinarySensorDeviceClass.MOTION,
68 }
69 
70 CUSTOMIZE_SCHEMA = vol.Schema(
71  {
72  vol.Optional(CONF_IGNORED, default=DEFAULT_IGNORED): cv.boolean,
73  vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): cv.positive_int,
74  }
75 )
76 
77 PLATFORM_SCHEMA = BINARY_SENSOR_PLATFORM_SCHEMA.extend(
78  {
79  vol.Optional(CONF_NAME): cv.string,
80  vol.Required(CONF_HOST): cv.string,
81  vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
82  vol.Optional(CONF_SSL, default=False): cv.boolean,
83  vol.Required(CONF_USERNAME): cv.string,
84  vol.Required(CONF_PASSWORD): cv.string,
85  vol.Optional(CONF_CUSTOMIZE, default={}): vol.Schema(
86  {cv.string: CUSTOMIZE_SCHEMA}
87  ),
88  }
89 )
90 
91 
93  hass: HomeAssistant,
94  config: ConfigType,
95  add_entities: AddEntitiesCallback,
96  discovery_info: DiscoveryInfoType | None = None,
97 ) -> None:
98  """Set up the Hikvision binary sensor devices."""
99  name = config.get(CONF_NAME)
100  host = config[CONF_HOST]
101  port = config[CONF_PORT]
102  username = config[CONF_USERNAME]
103  password = config[CONF_PASSWORD]
104 
105  customize = config[CONF_CUSTOMIZE]
106 
107  protocol = "https" if config[CONF_SSL] else "http"
108 
109  url = f"{protocol}://{host}"
110 
111  data = HikvisionData(hass, url, port, name, username, password)
112 
113  if data.sensors is None:
114  _LOGGER.error("Hikvision event stream has no data, unable to set up")
115  return
116 
117  entities = []
118 
119  for sensor, channel_list in data.sensors.items():
120  for channel in channel_list:
121  # Build sensor name, then parse customize config.
122  if data.type == "NVR":
123  sensor_name = f"{sensor.replace(' ', '_')}_{channel[1]}"
124  else:
125  sensor_name = sensor.replace(" ", "_")
126 
127  custom = customize.get(sensor_name.lower(), {})
128  ignore = custom.get(CONF_IGNORED)
129  delay = custom.get(CONF_DELAY)
130 
131  _LOGGER.debug(
132  "Entity: %s - %s, Options - Ignore: %s, Delay: %s",
133  data.name,
134  sensor_name,
135  ignore,
136  delay,
137  )
138  if not ignore:
139  entities.append(
140  HikvisionBinarySensor(hass, sensor, channel[1], data, delay)
141  )
142 
143  add_entities(entities)
144 
145 
147  """Hikvision device event stream object."""
148 
149  def __init__(self, hass, url, port, name, username, password):
150  """Initialize the data object."""
151  self._url_url = url
152  self._port_port = port
153  self._name_name = name
154  self._username_username = username
155  self._password_password = password
156 
157  # Establish camera
158  self.camdatacamdata = HikCamera(self._url_url, self._port_port, self._username_username, self._password_password)
159 
160  if self._name_name is None:
161  self._name_name = self.camdatacamdata.get_name
162 
163  hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.stop_hikstop_hik)
164  hass.bus.listen_once(EVENT_HOMEASSISTANT_START, self.start_hikstart_hik)
165 
166  def stop_hik(self, event):
167  """Shutdown Hikvision subscriptions and subscription thread on exit."""
168  self.camdatacamdata.disconnect()
169 
170  def start_hik(self, event):
171  """Start Hikvision event stream thread."""
172  self.camdatacamdata.start_stream()
173 
174  @property
175  def sensors(self):
176  """Return list of available sensors and their states."""
177  return self.camdatacamdata.current_event_states
178 
179  @property
180  def cam_id(self):
181  """Return device id."""
182  return self.camdatacamdata.get_id
183 
184  @property
185  def name(self):
186  """Return device name."""
187  return self._name_name
188 
189  @property
190  def type(self):
191  """Return device type."""
192  return self.camdatacamdata.get_type
193 
194  def get_attributes(self, sensor, channel):
195  """Return attribute list for sensor/channel."""
196  return self.camdatacamdata.fetch_attributes(sensor, channel)
197 
198 
200  """Representation of a Hikvision binary sensor."""
201 
202  _attr_should_poll = False
203 
204  def __init__(self, hass, sensor, channel, cam, delay):
205  """Initialize the binary_sensor."""
206  self._hass_hass = hass
207  self._cam_cam = cam
208  self._sensor_sensor = sensor
209  self._channel_channel = channel
210 
211  if self._cam_cam.type == "NVR":
212  self._name_name = f"{self._cam.name} {sensor} {channel}"
213  else:
214  self._name_name = f"{self._cam.name} {sensor}"
215 
216  self._id_id = f"{self._cam.cam_id}.{sensor}.{channel}"
217 
218  if delay is None:
219  self._delay_delay = 0
220  else:
221  self._delay_delay = delay
222 
223  self._timer_timer = None
224 
225  # Register callback function with pyHik
226  self._cam_cam.camdata.add_update_callback(self._update_callback_update_callback, self._id_id)
227 
228  def _sensor_state(self):
229  """Extract sensor state."""
230  return self._cam_cam.get_attributes(self._sensor_sensor, self._channel_channel)[0]
231 
233  """Extract sensor last update time."""
234  return self._cam_cam.get_attributes(self._sensor_sensor, self._channel_channel)[3]
235 
236  @property
237  def name(self):
238  """Return the name of the Hikvision sensor."""
239  return self._name_name
240 
241  @property
242  def unique_id(self):
243  """Return a unique ID."""
244  return self._id_id
245 
246  @property
247  def is_on(self):
248  """Return true if sensor is on."""
249  return self._sensor_state_sensor_state()
250 
251  @property
252  def device_class(self):
253  """Return the class of this sensor, from DEVICE_CLASSES."""
254  try:
255  return DEVICE_CLASS_MAP[self._sensor_sensor]
256  except KeyError:
257  # Sensor must be unknown to us, add as generic
258  return None
259 
260  @property
262  """Return the state attributes."""
263  attr = {ATTR_LAST_TRIP_TIME: self._sensor_last_update_sensor_last_update()}
264 
265  if self._delay_delay != 0:
266  attr[ATTR_DELAY] = self._delay_delay
267 
268  return attr
269 
270  def _update_callback(self, msg):
271  """Update the sensor's state, if needed."""
272  _LOGGER.debug("Callback signal from: %s", msg)
273 
274  if self._delay_delay > 0 and not self.is_onis_on:
275  # Set timer to wait until updating the state
276  def _delay_update(now):
277  """Timer callback for sensor update."""
278  _LOGGER.debug(
279  "%s Called delayed (%ssec) update", self._name_name, self._delay_delay
280  )
281  self.schedule_update_ha_stateschedule_update_ha_state()
282  self._timer_timer = None
283 
284  if self._timer_timer is not None:
285  self._timer_timer()
286  self._timer_timer = None
287 
288  self._timer_timer = track_point_in_utc_time(
289  self._hass_hass, _delay_update, utcnow() + timedelta(seconds=self._delay_delay)
290  )
291 
292  elif self._delay_delay > 0 and self.is_onis_on:
293  # For delayed sensors kill any callbacks on true events and update
294  if self._timer_timer is not None:
295  self._timer_timer()
296  self._timer_timer = None
297 
298  self.schedule_update_ha_stateschedule_update_ha_state()
299 
300  else:
301  self.schedule_update_ha_stateschedule_update_ha_state()
def __init__(self, hass, url, port, name, username, password)
None schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1244
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)