Home Assistant Unofficial Reference 2024.12.1
switch.py
Go to the documentation of this file.
1 """Support for SNMP enabled switch."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 import pysnmp.hlapi.asyncio as hlapi
9 from pysnmp.hlapi.asyncio import (
10  CommunityData,
11  ObjectIdentity,
12  ObjectType,
13  UdpTransportTarget,
14  UsmUserData,
15  getCmd,
16  setCmd,
17 )
18 from pysnmp.proto.rfc1902 import (
19  Counter32,
20  Counter64,
21  Gauge32,
22  Integer,
23  Integer32,
24  IpAddress,
25  Null,
26  ObjectIdentifier,
27  OctetString,
28  Opaque,
29  TimeTicks,
30  Unsigned32,
31 )
32 import voluptuous as vol
33 
35  PLATFORM_SCHEMA as SWITCH_PLATFORM_SCHEMA,
36  SwitchEntity,
37 )
38 from homeassistant.const import (
39  CONF_HOST,
40  CONF_NAME,
41  CONF_PAYLOAD_OFF,
42  CONF_PAYLOAD_ON,
43  CONF_PORT,
44  CONF_USERNAME,
45 )
46 from homeassistant.core import HomeAssistant
48 from homeassistant.helpers.entity_platform import AddEntitiesCallback
49 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
50 
51 from .const import (
52  CONF_AUTH_KEY,
53  CONF_AUTH_PROTOCOL,
54  CONF_BASEOID,
55  CONF_COMMUNITY,
56  CONF_PRIV_KEY,
57  CONF_PRIV_PROTOCOL,
58  CONF_VARTYPE,
59  CONF_VERSION,
60  DEFAULT_AUTH_PROTOCOL,
61  DEFAULT_HOST,
62  DEFAULT_NAME,
63  DEFAULT_PORT,
64  DEFAULT_PRIV_PROTOCOL,
65  DEFAULT_VARTYPE,
66  DEFAULT_VERSION,
67  MAP_AUTH_PROTOCOLS,
68  MAP_PRIV_PROTOCOLS,
69  SNMP_VERSIONS,
70 )
71 from .util import (
72  CommandArgsType,
73  RequestArgsType,
74  async_create_command_cmd_args,
75  async_create_request_cmd_args,
76 )
77 
78 _LOGGER = logging.getLogger(__name__)
79 
80 CONF_COMMAND_OID = "command_oid"
81 CONF_COMMAND_PAYLOAD_OFF = "command_payload_off"
82 CONF_COMMAND_PAYLOAD_ON = "command_payload_on"
83 
84 DEFAULT_COMMUNITY = "private"
85 DEFAULT_PAYLOAD_OFF = 0
86 DEFAULT_PAYLOAD_ON = 1
87 
88 MAP_SNMP_VARTYPES = {
89  "Counter32": Counter32,
90  "Counter64": Counter64,
91  "Gauge32": Gauge32,
92  "Integer32": Integer32,
93  "Integer": Integer,
94  "IpAddress": IpAddress,
95  "Null": Null,
96  # some work todo to support tuple ObjectIdentifier, this just supports str
97  "ObjectIdentifier": ObjectIdentifier,
98  "OctetString": OctetString,
99  "Opaque": Opaque,
100  "TimeTicks": TimeTicks,
101  "Unsigned32": Unsigned32,
102 }
103 
104 PLATFORM_SCHEMA = SWITCH_PLATFORM_SCHEMA.extend(
105  {
106  vol.Required(CONF_BASEOID): cv.string,
107  vol.Optional(CONF_COMMAND_OID): cv.string,
108  vol.Optional(CONF_COMMAND_PAYLOAD_ON): cv.string,
109  vol.Optional(CONF_COMMAND_PAYLOAD_OFF): cv.string,
110  vol.Optional(CONF_COMMUNITY, default=DEFAULT_COMMUNITY): cv.string,
111  vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
112  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
113  vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
114  vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
115  vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
116  vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): vol.In(SNMP_VERSIONS),
117  vol.Optional(CONF_USERNAME): cv.string,
118  vol.Optional(CONF_AUTH_KEY): cv.string,
119  vol.Optional(CONF_AUTH_PROTOCOL, default=DEFAULT_AUTH_PROTOCOL): vol.In(
120  MAP_AUTH_PROTOCOLS
121  ),
122  vol.Optional(CONF_PRIV_KEY): cv.string,
123  vol.Optional(CONF_PRIV_PROTOCOL, default=DEFAULT_PRIV_PROTOCOL): vol.In(
124  MAP_PRIV_PROTOCOLS
125  ),
126  vol.Optional(CONF_VARTYPE, default=DEFAULT_VARTYPE): cv.string,
127  }
128 )
129 
130 
132  hass: HomeAssistant,
133  config: ConfigType,
134  async_add_entities: AddEntitiesCallback,
135  discovery_info: DiscoveryInfoType | None = None,
136 ) -> None:
137  """Set up the SNMP switch."""
138  name: str = config[CONF_NAME]
139  host: str = config[CONF_HOST]
140  port: int = config[CONF_PORT]
141  community = config.get(CONF_COMMUNITY)
142  baseoid: str = config[CONF_BASEOID]
143  command_oid: str | None = config.get(CONF_COMMAND_OID)
144  command_payload_on: str | None = config.get(CONF_COMMAND_PAYLOAD_ON)
145  command_payload_off: str | None = config.get(CONF_COMMAND_PAYLOAD_OFF)
146  version: str = config[CONF_VERSION]
147  username = config.get(CONF_USERNAME)
148  authkey = config.get(CONF_AUTH_KEY)
149  authproto: str = config[CONF_AUTH_PROTOCOL]
150  privkey = config.get(CONF_PRIV_KEY)
151  privproto: str = config[CONF_PRIV_PROTOCOL]
152  payload_on: str = config[CONF_PAYLOAD_ON]
153  payload_off: str = config[CONF_PAYLOAD_OFF]
154  vartype: str = config[CONF_VARTYPE]
155 
156  if version == "3":
157  if not authkey:
158  authproto = "none"
159  if not privkey:
160  privproto = "none"
161 
162  auth_data = UsmUserData(
163  username,
164  authKey=authkey or None,
165  privKey=privkey or None,
166  authProtocol=getattr(hlapi, MAP_AUTH_PROTOCOLS[authproto]),
167  privProtocol=getattr(hlapi, MAP_PRIV_PROTOCOLS[privproto]),
168  )
169  else:
170  auth_data = CommunityData(community, mpModel=SNMP_VERSIONS[version])
171 
172  transport = UdpTransportTarget((host, port))
173  request_args = await async_create_request_cmd_args(
174  hass, auth_data, transport, baseoid
175  )
176  command_args = await async_create_command_cmd_args(hass, auth_data, transport)
177 
179  [
180  SnmpSwitch(
181  name,
182  host,
183  port,
184  baseoid,
185  command_oid,
186  payload_on,
187  payload_off,
188  command_payload_on,
189  command_payload_off,
190  vartype,
191  request_args,
192  command_args,
193  )
194  ],
195  True,
196  )
197 
198 
200  """Representation of a SNMP switch."""
201 
202  def __init__(
203  self,
204  name: str,
205  host: str,
206  port: int,
207  baseoid: str,
208  commandoid: str | None,
209  payload_on: str,
210  payload_off: str,
211  command_payload_on: str | None,
212  command_payload_off: str | None,
213  vartype: str,
214  request_args: RequestArgsType,
215  command_args: CommandArgsType,
216  ) -> None:
217  """Initialize the switch."""
218 
219  self._attr_name_attr_name = name
220  self._baseoid_baseoid = baseoid
221  self._vartype_vartype = vartype
222 
223  # Set the command OID to the base OID if command OID is unset
224  self._commandoid_commandoid = commandoid or baseoid
225  self._command_payload_on_command_payload_on = command_payload_on or payload_on
226  self._command_payload_off_command_payload_off = command_payload_off or payload_off
227 
228  self._state_state: bool | None = None
229  self._payload_on_payload_on = payload_on
230  self._payload_off_payload_off = payload_off
231  self._target_target = UdpTransportTarget((host, port))
232  self._request_args_request_args = request_args
233  self._command_args_command_args = command_args
234 
235  async def async_turn_on(self, **kwargs: Any) -> None:
236  """Turn on the switch."""
237  # If vartype set, use it - https://www.pysnmp.com/pysnmp/docs/api-reference.html#pysnmp.smi.rfc1902.ObjectType
238  await self._execute_command_execute_command(self._command_payload_on_command_payload_on)
239 
240  async def async_turn_off(self, **kwargs: Any) -> None:
241  """Turn off the switch."""
242  await self._execute_command_execute_command(self._command_payload_off_command_payload_off)
243 
244  async def _execute_command(self, command: str) -> None:
245  # User did not set vartype and command is not a digit
246  if self._vartype_vartype == "none" and not self._command_payload_on_command_payload_on.isdigit():
247  await self._set_set(command)
248  # User set vartype Null, command must be an empty string
249  elif self._vartype_vartype == "Null":
250  await self._set_set("")
251  # user did not set vartype but command is digit: defaulting to Integer
252  # or user did set vartype
253  else:
254  await self._set_set(MAP_SNMP_VARTYPES.get(self._vartype_vartype, Integer)(command))
255 
256  async def async_update(self) -> None:
257  """Update the state."""
258  get_result = await getCmd(*self._request_args_request_args)
259  errindication, errstatus, errindex, restable = get_result
260 
261  if errindication:
262  _LOGGER.error("SNMP error: %s", errindication)
263  elif errstatus:
264  _LOGGER.error(
265  "SNMP error: %s at %s",
266  errstatus.prettyPrint(),
267  errindex and restable[-1][int(errindex) - 1] or "?",
268  )
269  else:
270  for resrow in restable:
271  if resrow[-1] == self._payload_on_payload_on or resrow[-1] == Integer(
272  self._payload_on_payload_on
273  ):
274  self._state_state = True
275  elif resrow[-1] == self._payload_off_payload_off or resrow[-1] == Integer(
276  self._payload_off_payload_off
277  ):
278  self._state_state = False
279  else:
280  _LOGGER.warning(
281  "Invalid payload '%s' received for entity %s, state is unknown",
282  resrow[-1],
283  self.entity_identity_id,
284  )
285  self._state_state = None
286 
287  @property
288  def is_on(self) -> bool | None:
289  """Return true if switch is on; False if off. None if unknown."""
290  return self._state_state
291 
292  async def _set(self, value: Any) -> None:
293  """Set the state of the switch."""
294  await setCmd(
295  *self._command_args_command_args, ObjectType(ObjectIdentity(self._commandoid_commandoid), value)
296  )
None async_turn_on(self, **Any kwargs)
Definition: switch.py:235
None async_turn_off(self, **Any kwargs)
Definition: switch.py:240
None __init__(self, str name, str host, int port, str baseoid, str|None commandoid, str payload_on, str payload_off, str|None command_payload_on, str|None command_payload_off, str vartype, RequestArgsType request_args, CommandArgsType command_args)
Definition: switch.py:216
None _execute_command(self, str command)
Definition: switch.py:244
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: switch.py:136
RequestArgsType async_create_request_cmd_args(HomeAssistant hass, UsmUserData|CommunityData auth_data, UdpTransportTarget|Udp6TransportTarget target, str object_id)
Definition: util.py:63
CommandArgsType async_create_command_cmd_args(HomeAssistant hass, UsmUserData|CommunityData auth_data, UdpTransportTarget|Udp6TransportTarget target)
Definition: util.py:49