Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for Ness D8X/D16X devices."""
2 
3 from collections import namedtuple
4 import datetime
5 import logging
6 
7 from nessclient import ArmingMode, ArmingState, Client
8 import voluptuous as vol
9 
11  DEVICE_CLASSES_SCHEMA as BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
12  BinarySensorDeviceClass,
13 )
14 from homeassistant.const import (
15  ATTR_CODE,
16  ATTR_STATE,
17  CONF_HOST,
18  CONF_SCAN_INTERVAL,
19  EVENT_HOMEASSISTANT_STOP,
20  Platform,
21 )
22 from homeassistant.core import HomeAssistant, ServiceCall
23 from homeassistant.helpers import config_validation as cv
24 from homeassistant.helpers.discovery import async_load_platform
25 from homeassistant.helpers.dispatcher import async_dispatcher_send
26 from homeassistant.helpers.start import async_at_started
27 from homeassistant.helpers.typing import ConfigType
28 
29 _LOGGER = logging.getLogger(__name__)
30 
31 DOMAIN = "ness_alarm"
32 DATA_NESS = "ness_alarm"
33 
34 CONF_DEVICE_PORT = "port"
35 CONF_INFER_ARMING_STATE = "infer_arming_state"
36 CONF_ZONES = "zones"
37 CONF_ZONE_NAME = "name"
38 CONF_ZONE_TYPE = "type"
39 CONF_ZONE_ID = "id"
40 ATTR_OUTPUT_ID = "output_id"
41 DEFAULT_SCAN_INTERVAL = datetime.timedelta(minutes=1)
42 DEFAULT_INFER_ARMING_STATE = False
43 
44 SIGNAL_ZONE_CHANGED = "ness_alarm.zone_changed"
45 SIGNAL_ARMING_STATE_CHANGED = "ness_alarm.arming_state_changed"
46 
47 ZoneChangedData = namedtuple("ZoneChangedData", ["zone_id", "state"]) # noqa: PYI024
48 
49 DEFAULT_ZONE_TYPE = BinarySensorDeviceClass.MOTION
50 ZONE_SCHEMA = vol.Schema(
51  {
52  vol.Required(CONF_ZONE_NAME): cv.string,
53  vol.Required(CONF_ZONE_ID): cv.positive_int,
54  vol.Optional(
55  CONF_ZONE_TYPE, default=DEFAULT_ZONE_TYPE
56  ): BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
57  }
58 )
59 
60 CONFIG_SCHEMA = vol.Schema(
61  {
62  DOMAIN: vol.Schema(
63  {
64  vol.Required(CONF_HOST): cv.string,
65  vol.Required(CONF_DEVICE_PORT): cv.port,
66  vol.Optional(
67  CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
68  ): cv.positive_time_period,
69  vol.Optional(CONF_ZONES, default=[]): vol.All(
70  cv.ensure_list, [ZONE_SCHEMA]
71  ),
72  vol.Optional(
73  CONF_INFER_ARMING_STATE, default=DEFAULT_INFER_ARMING_STATE
74  ): cv.boolean,
75  }
76  )
77  },
78  extra=vol.ALLOW_EXTRA,
79 )
80 
81 SERVICE_PANIC = "panic"
82 SERVICE_AUX = "aux"
83 
84 SERVICE_SCHEMA_PANIC = vol.Schema({vol.Required(ATTR_CODE): cv.string})
85 SERVICE_SCHEMA_AUX = vol.Schema(
86  {
87  vol.Required(ATTR_OUTPUT_ID): cv.positive_int,
88  vol.Optional(ATTR_STATE, default=True): cv.boolean,
89  }
90 )
91 
92 
93 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
94  """Set up the Ness Alarm platform."""
95 
96  conf = config[DOMAIN]
97 
98  zones = conf[CONF_ZONES]
99  host = conf[CONF_HOST]
100  port = conf[CONF_DEVICE_PORT]
101  scan_interval = conf[CONF_SCAN_INTERVAL]
102  infer_arming_state = conf[CONF_INFER_ARMING_STATE]
103 
104  client = Client(
105  host=host,
106  port=port,
107  update_interval=scan_interval.total_seconds(),
108  infer_arming_state=infer_arming_state,
109  )
110  hass.data[DATA_NESS] = client
111 
112  async def _close(event):
113  await client.close()
114 
115  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _close)
116 
117  async def _started(event):
118  # Force update for current arming status and current zone states (once Home Assistant has finished loading required sensors and panel)
119  _LOGGER.debug("invoking client keepalive() & update()")
120  hass.loop.create_task(client.keepalive())
121  hass.loop.create_task(client.update())
122 
123  async_at_started(hass, _started)
124 
125  hass.async_create_task(
127  hass, Platform.BINARY_SENSOR, DOMAIN, {CONF_ZONES: zones}, config
128  )
129  )
130  hass.async_create_task(
131  async_load_platform(hass, Platform.ALARM_CONTROL_PANEL, DOMAIN, {}, config)
132  )
133 
134  def on_zone_change(zone_id: int, state: bool):
135  """Receives and propagates zone state updates."""
137  hass, SIGNAL_ZONE_CHANGED, ZoneChangedData(zone_id=zone_id, state=state)
138  )
139 
140  def on_state_change(arming_state: ArmingState, arming_mode: ArmingMode | None):
141  """Receives and propagates arming state updates."""
143  hass, SIGNAL_ARMING_STATE_CHANGED, arming_state, arming_mode
144  )
145 
146  client.on_zone_change(on_zone_change)
147  client.on_state_change(on_state_change)
148 
149  async def handle_panic(call: ServiceCall) -> None:
150  await client.panic(call.data[ATTR_CODE])
151 
152  async def handle_aux(call: ServiceCall) -> None:
153  await client.aux(call.data[ATTR_OUTPUT_ID], call.data[ATTR_STATE])
154 
155  hass.services.async_register(
156  DOMAIN, SERVICE_PANIC, handle_panic, schema=SERVICE_SCHEMA_PANIC
157  )
158  hass.services.async_register(
159  DOMAIN, SERVICE_AUX, handle_aux, schema=SERVICE_SCHEMA_AUX
160  )
161 
162  return True
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:93
None async_load_platform(core.HomeAssistant hass, Platform|str component, str platform, DiscoveryInfoType|None discovered, ConfigType hass_config)
Definition: discovery.py:152
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193
CALLBACK_TYPE async_at_started(HomeAssistant hass, Callable[[HomeAssistant], Coroutine[Any, Any, None]|None] at_start_cb)
Definition: start.py:80