Skip to content

Commit 09f8c97

Browse files
support multiple switches
1 parent c27e938 commit 09f8c97

File tree

1 file changed

+49
-41
lines changed

1 file changed

+49
-41
lines changed

orvibo/s20.py

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
""" Orbivo S20. """
22

3+
import binascii
34
import logging
45
import socket
56

@@ -10,7 +11,8 @@
1011

1112
# UDP best-effort.
1213
RETRIES = 3
13-
TIMEOUT = 0.5
14+
TIMEOUT = 1.0
15+
DISCOVERY_TIMEOUT = 1.0
1416

1517
# Packet constants.
1618
MAGIC = b'\x68\x64'
@@ -26,6 +28,30 @@
2628
OFF = b'\x00'
2729

2830

31+
def _is_discovery_response(data):
32+
""" Is this a discovery response?
33+
34+
:param data: Payload.
35+
"""
36+
return data[0:6] == (MAGIC + DISCOVERY_RESP)
37+
38+
39+
def _is_subscribe_response(data):
40+
""" Is this a subscribe response?
41+
42+
:param data: Payload.
43+
"""
44+
return data[0:6] == (MAGIC + SUBSCRIBE_RESP)
45+
46+
47+
def _is_control_response(data):
48+
""" Is this a control response?
49+
50+
:param data: Payload.
51+
"""
52+
return data[0:6] == (MAGIC + CONTROL_RESP)
53+
54+
2955
class S20Exception(Exception):
3056
""" S20 exception. """
3157
pass
@@ -45,7 +71,9 @@ def __init__(self, host):
4571
"""
4672
self.host = host
4773
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
48-
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
74+
for opt in [socket.SO_BROADCAST, socket.SO_REUSEADDR,
75+
socket.SO_REUSEPORT]:
76+
self._socket.setsockopt(socket.SOL_SOCKET, opt, 1)
4977
self._socket.bind(('', PORT))
5078
(self._mac, self._mac_reversed) = self._discover_mac()
5179

@@ -75,16 +103,19 @@ def _discover_mac(self):
75103
All configured devices reply. The response contains
76104
the MAC address in both needed formats.
77105
106+
Discovery of multiple switches must be done synchronously.
107+
78108
:returns: Tuple of MAC address and reversed MAC address.
79109
"""
80110
mac = None
81111
mac_reversed = None
82112
cmd = MAGIC + DISCOVERY
83113
resp = self._udp_transact(cmd, self._discovery_resp,
84-
broadcast=True, timeout=1.0)
114+
broadcast=True,
115+
timeout=DISCOVERY_TIMEOUT)
85116
if resp:
86117
(mac, mac_reversed) = resp
87-
if not mac:
118+
if mac is None:
88119
raise S20Exception("Couldn't discover {}".format(self.host))
89120
return (mac, mac_reversed)
90121

@@ -120,69 +151,44 @@ def _control(self, state):
120151
"Device didn't acknowledge control request: {}".format(
121152
self.host))
122153

123-
def _discovery_resp(self, data, addr):
154+
def _discovery_resp(self, data):
124155
""" Handle a discovery response.
125156
126157
:param data: Payload.
127158
:param addr: Address tuple.
128-
:returns: MAC address tuple.
159+
:returns: MAC and reversed MAC.
129160
"""
130-
if self._is_discovery_response(data, addr):
131-
_LOGGER.debug("Discovered MAC of %s", self.host)
161+
if _is_discovery_response(data):
162+
_LOGGER.debug("Discovered MAC of %s: %s", self.host,
163+
binascii.hexlify(data[7:13]).decode())
132164
return (data[7:13], data[19:25])
133-
return (None, None)
134165

135-
def _subscribe_resp(self, data, addr):
166+
def _subscribe_resp(self, data):
136167
""" Handle a subscribe response.
137168
138169
:param data: Payload.
139-
:param addr: Address tuple.
140170
:returns: State (ON/OFF)
141171
"""
142-
if self._is_subscribe_response(data, addr):
172+
if _is_subscribe_response(data):
143173
status = bytes([data[23]])
144174
_LOGGER.debug("Successfully subscribed to %s, state: %s",
145175
self.host, ord(status))
146176
return status
147177

148-
def _control_resp(self, data, addr, state):
178+
def _control_resp(self, data, state):
149179
""" Handle a control response.
150180
151181
:param data: Payload.
152-
:param addr: Address tuple.
153-
:param state: Acknowledged state.
182+
:param state: Requested state.
183+
:returns: Acknowledged state.
154184
"""
155-
if self._is_control_response(data, addr):
185+
if _is_control_response(data):
156186
ack_state = bytes([data[22]])
157187
if state == ack_state:
158188
_LOGGER.debug("Received state ack from %s, state: %s",
159189
self.host, ord(ack_state))
160190
return ack_state
161191

162-
def _is_discovery_response(self, data, addr):
163-
""" Is this a discovery response?
164-
165-
:param data: Payload.
166-
:param addr: Address tuple.
167-
"""
168-
return data[0:6] == (MAGIC + DISCOVERY_RESP) and addr[0] == self.host
169-
170-
def _is_subscribe_response(self, data, addr):
171-
""" Is this a subscribe response?
172-
173-
:param data: Payload.
174-
:param addr: Address tuple.
175-
"""
176-
return data[0:6] == (MAGIC + SUBSCRIBE_RESP) and addr[0] == self.host
177-
178-
def _is_control_response(self, data, addr):
179-
""" Is this a control response?
180-
181-
:param data: Payload.
182-
:param addr: Address tuple.
183-
"""
184-
return data[0:6] == (MAGIC + CONTROL_RESP) and addr[0] == self.host
185-
186192
def _udp_transact(self, payload, handler, *args,
187193
broadcast=False, timeout=TIMEOUT):
188194
""" Complete a UDP transaction.
@@ -208,7 +214,9 @@ def _udp_transact(self, payload, handler, *args,
208214
while True:
209215
try:
210216
data, addr = self._socket.recvfrom(1024)
211-
retval = handler(data, addr, *args)
217+
# From the right device?
218+
if addr[0] == self.host:
219+
retval = handler(data, *args)
212220
except socket.timeout:
213221
break
214222
if retval:

0 commit comments

Comments
 (0)