Skip to content
This repository was archived by the owner on Oct 1, 2021. It is now read-only.

Commit c568fb6

Browse files
committed
Merge branch 'master' of https://github.com/balloob/netdisco
2 parents 580d41c + e8212c6 commit c568fb6

File tree

7 files changed

+116
-19
lines changed

7 files changed

+116
-19
lines changed

netdisco/discoverables/tellstick.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
""" Discovers Tellstick devices. """
2+
3+
from . import BaseDiscoverable
4+
5+
6+
class Discoverable(BaseDiscoverable):
7+
""" Adds support for discovering a Tellstick device. """
8+
9+
def __init__(self, netdis):
10+
self._netdis = netdis
11+
12+
def get_entries(self):
13+
return self._netdis.tellstick.entries

netdisco/discovery.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from .mdns import MDNS
1111
from .gdm import GDM
1212
from .lms import LMS
13+
from .tellstick import Tellstick
1314

1415
_LOGGER = logging.getLogger(__name__)
1516

@@ -22,6 +23,7 @@ class NetworkDiscovery(object):
2223
SSDP scans in the foreground.
2324
GDM scans in the foreground.
2425
LMS scans in the foreground.
26+
Tellstick scans in the foreground
2527
2628
start: is ready to scan
2729
scan: scan the network
@@ -37,6 +39,7 @@ def __init__(self, limit_discovery=None):
3739
self.ssdp = SSDP()
3840
self.gdm = GDM()
3941
self.lms = LMS()
42+
self.tellstick = Tellstick()
4043
self.discoverables = {}
4144

4245
self._load_device_support()
@@ -52,6 +55,7 @@ def scan(self):
5255
self.ssdp.scan()
5356
self.gdm.scan()
5457
self.lms.scan()
58+
self.tellstick.scan()
5559

5660
def stop(self):
5761
""" Turn discovery off. """
@@ -124,3 +128,6 @@ def print_raw_data(self):
124128
print("")
125129
print("LMS")
126130
pprint(self.lms.entries)
131+
print("")
132+
print("Tellstick")
133+
pprint(self.tellstick.entries)

netdisco/ssdp.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import requests
1313

14-
from .util import etree_to_dict
14+
from .util import etree_to_dict, interface_addresses
1515

1616
DISCOVER_TIMEOUT = 5
1717
SSDP_MX = 1
@@ -205,30 +205,33 @@ def scan(st=None, timeout=DISCOVER_TIMEOUT, max_entries=None):
205205
'ST: {}'.format(ssdp_st),
206206
'', '']).encode('ascii')
207207

208-
entries = []
209-
210-
calc_now = datetime.now
211-
start = calc_now()
208+
stop_wait = datetime.now() + timedelta(0, timeout)
212209

213-
try:
210+
sockets = []
211+
for addr in interface_addresses():
214212
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
215213

216-
sock.sendto(ssdp_request, ssdp_target)
214+
# Set the time-to-live for messages for local network
215+
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1)
216+
sock.bind((addr, 0))
217217

218-
sock.setblocking(0)
219-
220-
while True:
221-
time_diff = calc_now() - start
218+
sockets.append(sock)
222219

223-
# pylint: disable=maybe-no-member
224-
seconds_left = timeout - time_diff.seconds
220+
entries = []
221+
try:
222+
for sock in sockets:
223+
sock.sendto(ssdp_request, ssdp_target)
224+
sock.setblocking(0)
225225

226+
while True:
227+
time_diff = stop_wait - datetime.now()
228+
seconds_left = time_diff.total_seconds()
226229
if seconds_left <= 0:
227-
return entries
230+
break
228231

229-
ready = select.select([sock], [], [], seconds_left)[0]
232+
ready = select.select(sockets, [], [], seconds_left)[0]
230233

231-
if ready:
234+
for sock in ready:
232235
response = sock.recv(1024).decode("ascii")
233236

234237
entry = UPNPEntry.from_response(response)
@@ -237,14 +240,18 @@ def scan(st=None, timeout=DISCOVER_TIMEOUT, max_entries=None):
237240
entries.append(entry)
238241

239242
if max_entries and len(entries) == max_entries:
240-
return entries
243+
raise StopIteration
241244

242245
except socket.error:
243246
logging.getLogger(__name__).exception(
244247
"Socket error while discovering SSDP devices")
245248

249+
except StopIteration:
250+
pass
251+
246252
finally:
247-
sock.close()
253+
for sock in sockets:
254+
sock.close()
248255

249256
return entries
250257

netdisco/tellstick.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""
2+
Tellstick device discovery.
3+
"""
4+
import socket
5+
import threading
6+
from datetime import timedelta
7+
8+
9+
DISCOVERY_PORT = 30303
10+
DISCOVERY_ADDRESS = '<broadcast>'
11+
DISCOVERY_PAYLOAD = b"D"
12+
DISCOVERY_TIMEOUT = timedelta(seconds=5)
13+
14+
15+
class Tellstick(object):
16+
"""Base class to discover Tellstick devices."""
17+
18+
def __init__(self):
19+
self.entries = []
20+
self._lock = threading.RLock()
21+
22+
def scan(self):
23+
"""Scan the network."""
24+
with self._lock:
25+
self.update()
26+
27+
def all(self):
28+
"""Scan and return all found entries."""
29+
self.scan()
30+
return self.entries
31+
32+
def update(self):
33+
"""Scan network for Tellstick devices."""
34+
entries = []
35+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
36+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
37+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
38+
sock.settimeout(DISCOVERY_TIMEOUT.seconds)
39+
sock.sendto(DISCOVERY_PAYLOAD, (DISCOVERY_ADDRESS, DISCOVERY_PORT))
40+
while True:
41+
try:
42+
data, (address, port) = sock.recvfrom(1024)
43+
entry = data.decode("ascii").split(":")
44+
if len(entry) != 4: # expecting product, mac, activation code, version
45+
continue
46+
entry = (address,) + tuple(entry)
47+
entries.append(entry)
48+
except socket.timeout:
49+
break
50+
51+
self.entries = entries
52+
53+
if __name__ == "__main__":
54+
from pprint import pprint
55+
tellstick = Tellstick()
56+
pprint("Scanning for Tellstick devices..")
57+
tellstick.update()
58+
pprint(tellstick.entries)

netdisco/util.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"""
44
from collections import defaultdict
55

6+
import netifaces
7+
68

79
# Taken from http://stackoverflow.com/a/10077069
810
def etree_to_dict(t):
@@ -29,3 +31,12 @@ def etree_to_dict(t):
2931
else:
3032
d[tag_name] = text
3133
return d
34+
35+
36+
def interface_addresses(family=netifaces.AF_INET):
37+
"""Returns local addresses of any network associated with a local interface
38+
that has broadcast (and probably multicast) capability."""
39+
return [addr['addr']
40+
for i in netifaces.interfaces()
41+
for addr in netifaces.ifaddresses(i).get(family) or []
42+
if 'broadcast' in addr]

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
zeroconf>=0.17.4
22
requests>=2.0
3+
netifaces>=0.10.0

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
author='Paulus Schoutsen',
88
author_email='Paulus@PaulusSchoutsen.nl',
99
license='MIT',
10-
install_requires=['requests>=2.0', 'zeroconf>=0.17.4'],
10+
install_requires=['netifaces>=0.10.0', 'requests>=2.0', 'zeroconf>=0.17.4'],
1111
packages=find_packages(),
1212
zip_safe=False)

0 commit comments

Comments
 (0)