3
3
import binascii
4
4
import logging
5
5
import socket
6
+ import threading
6
7
import time
7
8
8
9
_LOGGER = logging .getLogger (__name__ )
15
16
TIMEOUT = 1.0
16
17
DISCOVERY_TIMEOUT = 1.0
17
18
19
+ # Timeout after which to renew device subscriptions
20
+ SUBSCRIPTION_TIMEOUT = 60
21
+
18
22
# Packet constants.
19
23
MAGIC = b'\x68 \x64 '
20
24
DISCOVERY = b'\x00 \x06 \x71 \x61 '
28
32
ON = b'\x01 '
29
33
OFF = b'\x00 '
30
34
31
- # Timeout after which to renew device subscriptions
32
- SUBSCRIPTION_TIMEOUT = 60
35
+ # Socket
36
+ _SOCKET = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
37
+
38
+ # Buffer
39
+ _BUFFER = {}
40
+
41
+
42
+ def _listen ():
43
+ """ Listen on socket. """
44
+ while True :
45
+ data , addr = _SOCKET .recvfrom (1024 )
46
+ _BUFFER [addr [0 ]] = data
47
+
48
+
49
+ def _setup ():
50
+ """ Set up module.
51
+
52
+ Open a UDP socket, and listen in a thread.
53
+ """
54
+ for opt in [socket .SO_BROADCAST , socket .SO_REUSEADDR , socket .SO_REUSEPORT ]:
55
+ _SOCKET .setsockopt (socket .SOL_SOCKET , opt , 1 )
56
+ _SOCKET .bind (('' , PORT ))
57
+ udp = threading .Thread (target = _listen , daemon = True )
58
+ udp .start ()
59
+
60
+
61
+ def discover (timeout = DISCOVERY_TIMEOUT ):
62
+ """ Discover devices on the local network.
63
+
64
+ :param timeout: Optional timeout in seconds.
65
+ :returns: Set of discovered host addresses.
66
+ """
67
+ hosts = set ()
68
+ payload = MAGIC + DISCOVERY
69
+ for _ in range (RETRIES ):
70
+ _SOCKET .sendto (bytearray (payload ), ('255.255.255.255' , PORT ))
71
+ start = time .time ()
72
+ while time .time () < start + timeout :
73
+ for host , data in _BUFFER .copy ().items ():
74
+ if _is_discovery_response (data ):
75
+ if host not in hosts :
76
+ _LOGGER .debug ("Discovered device at %s" , host )
77
+ hosts .add (host )
78
+ return hosts
33
79
34
80
35
81
def _is_discovery_response (data ):
@@ -74,13 +120,7 @@ def __init__(self, host):
74
120
:param host: IP or hostname of device.
75
121
"""
76
122
self .host = host
77
- self ._socket = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
78
- for opt in [socket .SO_BROADCAST , socket .SO_REUSEADDR ,
79
- socket .SO_REUSEPORT ]:
80
- self ._socket .setsockopt (socket .SOL_SOCKET , opt , 1 )
81
- self ._socket .bind (('' , PORT ))
82
123
(self ._mac , self ._mac_reversed ) = self ._discover_mac ()
83
-
84
124
self ._subscribe ()
85
125
86
126
@property
@@ -144,6 +184,10 @@ def _subscribe(self):
144
184
"No status could be found for {}" .format (self .host ))
145
185
146
186
def _subscription_is_recent (self ):
187
+ """ Check if subscription occurred recently.
188
+
189
+ :returns: Yes (True) or no (False)
190
+ """
147
191
return self .last_subscribed > time .time () - SUBSCRIPTION_TIMEOUT
148
192
149
193
def _control (self , state ):
@@ -219,24 +263,22 @@ def _udp_transact(self, payload, handler, *args,
219
263
:param broadcast: Send a broadcast instead.
220
264
:param timeout: Timeout in seconds.
221
265
"""
266
+ if self .host in _BUFFER :
267
+ del _BUFFER [self .host ]
222
268
host = self .host
223
269
if broadcast :
224
270
host = '255.255.255.255'
225
271
retval = None
226
- self ._socket .settimeout (timeout )
227
272
for _ in range (RETRIES ):
228
- self ._socket .sendto (bytearray (payload ), (host , PORT ))
229
- while True :
230
- try :
231
- data , addr = self ._socket .recvfrom (1024 )
232
- # From the right device?
233
- if addr [0 ] == self .host :
234
- retval = handler (data , * args )
235
- # Return as soon as a response is received
236
- if retval :
237
- return retval
238
- except socket .timeout :
239
- break
273
+ _SOCKET .sendto (bytearray (payload ), (host , PORT ))
274
+ start = time .time ()
275
+ while time .time () < start + timeout :
276
+ data = _BUFFER .get (self .host , None )
277
+ if data :
278
+ retval = handler (data , * args )
279
+ # Return as soon as a response is received
280
+ if retval :
281
+ return retval
240
282
241
283
def _turn_on (self ):
242
284
""" Turn on the device. """
@@ -245,3 +287,6 @@ def _turn_on(self):
245
287
def _turn_off (self ):
246
288
""" Turn off the device. """
247
289
self ._control (OFF )
290
+
291
+
292
+ _setup ()
0 commit comments