Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for Qwikswitch devices."""
2 
3 from __future__ import annotations
4 
5 import logging
6 
7 from pyqwikswitch.async_ import QSUsb
8 from pyqwikswitch.qwikswitch import CMD_BUTTONS, QS_CMD, QS_ID, SENSORS, QSType
9 import voluptuous as vol
10 
11 from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA
12 from homeassistant.const import (
13  CONF_SENSORS,
14  CONF_SWITCHES,
15  CONF_URL,
16  EVENT_HOMEASSISTANT_START,
17  EVENT_HOMEASSISTANT_STOP,
18  Platform,
19 )
20 from homeassistant.core import HomeAssistant, callback
21 from homeassistant.helpers.aiohttp_client import async_get_clientsession
23 from homeassistant.helpers.discovery import load_platform
24 from homeassistant.helpers.dispatcher import async_dispatcher_send
25 from homeassistant.helpers.typing import ConfigType
26 
27 _LOGGER = logging.getLogger(__name__)
28 
29 DOMAIN = "qwikswitch"
30 
31 CONF_DIMMER_ADJUST = "dimmer_adjust"
32 CONF_BUTTON_EVENTS = "button_events"
33 CV_DIM_VALUE = vol.All(vol.Coerce(float), vol.Range(min=1, max=3))
34 
35 
36 CONFIG_SCHEMA = vol.Schema(
37  {
38  DOMAIN: vol.Schema(
39  {
40  vol.Required(CONF_URL, default="http://127.0.0.1:2020"): vol.Coerce(
41  str
42  ),
43  vol.Optional(CONF_DIMMER_ADJUST, default=1): CV_DIM_VALUE,
44  vol.Optional(CONF_BUTTON_EVENTS, default=[]): cv.ensure_list_csv,
45  vol.Optional(CONF_SENSORS, default=[]): vol.All(
46  cv.ensure_list,
47  [
48  vol.Schema(
49  {
50  vol.Required("id"): str,
51  vol.Optional("channel", default=1): int,
52  vol.Required("name"): str,
53  vol.Required("type"): str,
54  vol.Optional("class"): DEVICE_CLASSES_SCHEMA,
55  vol.Optional("invert"): bool,
56  }
57  )
58  ],
59  ),
60  vol.Optional(CONF_SWITCHES, default=[]): vol.All(cv.ensure_list, [str]),
61  }
62  )
63  },
64  extra=vol.ALLOW_EXTRA,
65 )
66 
67 
68 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
69  """Qwiskswitch component setup."""
70 
71  # Add cmd's to in /&listen packets will fire events
72  # By default only buttons of type [TOGGLE,SCENE EXE,LEVEL]
73  cmd_buttons = set(CMD_BUTTONS)
74  for btn in config[DOMAIN][CONF_BUTTON_EVENTS]:
75  cmd_buttons.add(btn)
76 
77  url = config[DOMAIN][CONF_URL]
78  dimmer_adjust = config[DOMAIN][CONF_DIMMER_ADJUST]
79  sensors = config[DOMAIN][CONF_SENSORS]
80  switches = config[DOMAIN][CONF_SWITCHES]
81 
82  def callback_value_changed(_qsd, qsid, _val):
83  """Update entity values based on device change."""
84  _LOGGER.debug("Dispatch %s (update from devices)", qsid)
85  async_dispatcher_send(hass, qsid, None)
86 
87  session = async_get_clientsession(hass)
88  qsusb = QSUsb(
89  url=url,
90  dim_adj=dimmer_adjust,
91  session=session,
92  callback_value_changed=callback_value_changed,
93  )
94 
95  # Discover all devices in QSUSB
96  if not await qsusb.update_from_devices():
97  return False
98 
99  hass.data[DOMAIN] = qsusb
100 
101  comps: dict[Platform, list] = {
102  Platform.SWITCH: [],
103  Platform.LIGHT: [],
104  Platform.SENSOR: [],
105  Platform.BINARY_SENSOR: [],
106  }
107 
108  sensor_ids = []
109  for sens in sensors:
110  try:
111  _, _type = SENSORS[sens["type"]]
112  sensor_ids.append(sens["id"])
113  if _type is bool:
114  comps[Platform.BINARY_SENSOR].append(sens)
115  continue
116  comps[Platform.SENSOR].append(sens)
117  for _key in ("invert", "class"):
118  if _key in sens:
119  _LOGGER.warning(
120  "%s should only be used for binary_sensors: %s", _key, sens
121  )
122  except KeyError:
123  _LOGGER.warning(
124  "Sensor validation failed for sensor id=%s type=%s",
125  sens["id"],
126  sens["type"],
127  )
128 
129  for qsid, dev in qsusb.devices.items():
130  if qsid in switches:
131  if dev.qstype != QSType.relay:
132  _LOGGER.warning("You specified a switch that is not a relay %s", qsid)
133  continue
134  comps[Platform.SWITCH].append(qsid)
135  elif dev.qstype in (QSType.relay, QSType.dimmer):
136  comps[Platform.LIGHT].append(qsid)
137  else:
138  _LOGGER.warning("Ignored unknown QSUSB device: %s", dev)
139  continue
140 
141  # Load platforms
142  for comp_name, comp_conf in comps.items():
143  if comp_conf:
144  load_platform(hass, comp_name, DOMAIN, {DOMAIN: comp_conf}, config)
145 
146  def callback_qs_listen(qspacket):
147  """Typically a button press or update signal."""
148  # If button pressed, fire a hass event
149  if QS_ID in qspacket:
150  if qspacket.get(QS_CMD, "") in cmd_buttons:
151  hass.bus.async_fire(f"qwikswitch.button.{qspacket[QS_ID]}", qspacket)
152  return
153 
154  if qspacket[QS_ID] in sensor_ids:
155  _LOGGER.debug("Dispatch %s ((%s))", qspacket[QS_ID], qspacket)
156  async_dispatcher_send(hass, qspacket[QS_ID], qspacket)
157 
158  # Update all ha_objects
159  hass.async_create_task(qsusb.update_from_devices())
160 
161  @callback
162  def async_start(_):
163  """Start listening."""
164  qsusb.listen(callback_qs_listen)
165 
166  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, async_start)
167 
168  @callback
169  def async_stop(_):
170  """Stop the listener."""
171  hass.data[DOMAIN].stop()
172 
173  hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, async_stop)
174 
175  return True
None async_stop(HomeAssistant hass)
Definition: discovery.py:694
None async_start(HomeAssistant hass, str discovery_topic, ConfigEntry config_entry)
Definition: discovery.py:356
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:68
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
None load_platform(core.HomeAssistant hass, Platform|str component, str platform, DiscoveryInfoType|None discovered, ConfigType hass_config)
Definition: discovery.py:137
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193