1
1
""" Orbivo S20. """
2
2
3
+ import binascii
3
4
import logging
4
5
import socket
5
6
10
11
11
12
# UDP best-effort.
12
13
RETRIES = 3
13
- TIMEOUT = 0.5
14
+ TIMEOUT = 1.0
15
+ DISCOVERY_TIMEOUT = 1.0
14
16
15
17
# Packet constants.
16
18
MAGIC = b'\x68 \x64 '
26
28
OFF = b'\x00 '
27
29
28
30
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
+
29
55
class S20Exception (Exception ):
30
56
""" S20 exception. """
31
57
pass
@@ -45,7 +71,9 @@ def __init__(self, host):
45
71
"""
46
72
self .host = host
47
73
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 )
49
77
self ._socket .bind (('' , PORT ))
50
78
(self ._mac , self ._mac_reversed ) = self ._discover_mac ()
51
79
@@ -75,16 +103,19 @@ def _discover_mac(self):
75
103
All configured devices reply. The response contains
76
104
the MAC address in both needed formats.
77
105
106
+ Discovery of multiple switches must be done synchronously.
107
+
78
108
:returns: Tuple of MAC address and reversed MAC address.
79
109
"""
80
110
mac = None
81
111
mac_reversed = None
82
112
cmd = MAGIC + DISCOVERY
83
113
resp = self ._udp_transact (cmd , self ._discovery_resp ,
84
- broadcast = True , timeout = 1.0 )
114
+ broadcast = True ,
115
+ timeout = DISCOVERY_TIMEOUT )
85
116
if resp :
86
117
(mac , mac_reversed ) = resp
87
- if not mac :
118
+ if mac is None :
88
119
raise S20Exception ("Couldn't discover {}" .format (self .host ))
89
120
return (mac , mac_reversed )
90
121
@@ -120,69 +151,44 @@ def _control(self, state):
120
151
"Device didn't acknowledge control request: {}" .format (
121
152
self .host ))
122
153
123
- def _discovery_resp (self , data , addr ):
154
+ def _discovery_resp (self , data ):
124
155
""" Handle a discovery response.
125
156
126
157
:param data: Payload.
127
158
:param addr: Address tuple.
128
- :returns: MAC address tuple .
159
+ :returns: MAC and reversed MAC .
129
160
"""
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 ())
132
164
return (data [7 :13 ], data [19 :25 ])
133
- return (None , None )
134
165
135
- def _subscribe_resp (self , data , addr ):
166
+ def _subscribe_resp (self , data ):
136
167
""" Handle a subscribe response.
137
168
138
169
:param data: Payload.
139
- :param addr: Address tuple.
140
170
:returns: State (ON/OFF)
141
171
"""
142
- if self . _is_subscribe_response (data , addr ):
172
+ if _is_subscribe_response (data ):
143
173
status = bytes ([data [23 ]])
144
174
_LOGGER .debug ("Successfully subscribed to %s, state: %s" ,
145
175
self .host , ord (status ))
146
176
return status
147
177
148
- def _control_resp (self , data , addr , state ):
178
+ def _control_resp (self , data , state ):
149
179
""" Handle a control response.
150
180
151
181
:param data: Payload.
152
- :param addr: Address tuple .
153
- :param state : Acknowledged state.
182
+ :param state: Requested state .
183
+ :returns : Acknowledged state.
154
184
"""
155
- if self . _is_control_response (data , addr ):
185
+ if _is_control_response (data ):
156
186
ack_state = bytes ([data [22 ]])
157
187
if state == ack_state :
158
188
_LOGGER .debug ("Received state ack from %s, state: %s" ,
159
189
self .host , ord (ack_state ))
160
190
return ack_state
161
191
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
-
186
192
def _udp_transact (self , payload , handler , * args ,
187
193
broadcast = False , timeout = TIMEOUT ):
188
194
""" Complete a UDP transaction.
@@ -208,7 +214,9 @@ def _udp_transact(self, payload, handler, *args,
208
214
while True :
209
215
try :
210
216
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 )
212
220
except socket .timeout :
213
221
break
214
222
if retval :
0 commit comments