Skip to content

Commit 167f17b

Browse files
committed
Add auto connect to 360 eye
1 parent ef5402d commit 167f17b

4 files changed

Lines changed: 97 additions & 57 deletions

File tree

libpurecool/dyson_360_eye.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@
1717
class Dyson360Eye(DysonDevice):
1818
"""Dyson 360 Eye device."""
1919

20+
def auto_connect(self, timeout=5, retry=15):
21+
"""Try to connect to device using mDNS.
22+
23+
:param timeout: Timeout
24+
:param retry: Max retry
25+
:return: True if connected, else False
26+
"""
27+
return self._auto_connect("_360eye_mqtt._tcp.local.", timeout, retry)
28+
29+
@staticmethod
30+
def _device_serial_from_name(name):
31+
"""Get device serial from mDNS name."""
32+
return (name.split(".")[0]).split("-", 1)[1]
33+
2034
def connect(self, device_ip, device_port=DEFAULT_PORT):
2135
"""Try to connect to device.
2236
@@ -27,6 +41,10 @@ def connect(self, device_ip, device_port=DEFAULT_PORT):
2741
self._network_device = NetworkDevice(self._name, device_ip,
2842
device_port)
2943

44+
return self._mqtt_connect()
45+
46+
def _mqtt_connect(self):
47+
"""Connect to the MQTT broker."""
3048
self._mqtt = mqtt.Client(userdata=self, protocol=3)
3149
self._mqtt.username_pw_set(self._serial, self._credentials)
3250
self._mqtt.on_message = self.on_message

libpurecool/dyson_device.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22

33
# pylint: disable=too-many-public-methods,too-many-instance-attributes
44

5-
from queue import Queue
5+
from queue import Queue, Empty
66
import logging
77
import json
88
import abc
99
import time
10+
import socket
1011

1112
from .utils import printable_fields
1213
from .utils import decrypt_password
14+
from .zeroconf import ServiceBrowser, Zeroconf
1315

1416
_LOGGER = logging.getLogger(__name__)
1517

@@ -64,6 +66,41 @@ def __repr__(self):
6466
class DysonDevice:
6567
"""Abstract Dyson device."""
6668

69+
class DysonDeviceListener:
70+
"""Message listener."""
71+
72+
def __init__(self, serial, add_device_function, serial_from_name):
73+
"""Create a new message listener.
74+
75+
:param serial: Device serial
76+
:param add_device_function: Callback function
77+
"""
78+
self._serial = serial
79+
self.add_device_function = add_device_function
80+
self.serial_from_name = serial_from_name
81+
82+
def remove_service(self, zeroconf, device_type, name):
83+
# pylint: disable=unused-argument,no-self-use
84+
"""Remove listener."""
85+
_LOGGER.info("Service %s removed", name)
86+
87+
def add_service(self, zeroconf, device_type, name):
88+
"""Add device.
89+
90+
:param zeroconf: MSDNS object
91+
:param device_type: Service type
92+
:param name: Device name
93+
"""
94+
device_serial = self.serial_from_name(name)
95+
if device_serial == self._serial:
96+
# Find searched device
97+
info = zeroconf.get_service_info(device_type, name)
98+
address = socket.inet_ntoa(info.address)
99+
network_device = NetworkDevice(device_serial, address,
100+
info.port)
101+
self.add_device_function(network_device)
102+
zeroconf.close()
103+
67104
@staticmethod
68105
def on_connect(client, userdata, flags, return_code):
69106
# pylint: disable=unused-argument
@@ -119,6 +156,35 @@ def connect(self, device_ip, device_port=DEFAULT_PORT):
119156
"""
120157
return
121158

159+
def _auto_connect(self, type_, timeout=5, retry=15):
160+
"""Try to connect to device using mDNS."""
161+
for i in range(retry):
162+
zeroconf = Zeroconf()
163+
listener = self.DysonDeviceListener(self._serial,
164+
self._add_network_device,
165+
self._device_serial_from_name)
166+
ServiceBrowser(zeroconf, type_, listener)
167+
try:
168+
self._network_device = self._search_device_queue.get(
169+
timeout=timeout)
170+
except Empty:
171+
# Unable to find device
172+
_LOGGER.warning("Unable to find device %s, try %s",
173+
self._serial, i)
174+
zeroconf.close()
175+
else:
176+
break
177+
if self._network_device is None:
178+
_LOGGER.error("Unable to connect to device %s", self._serial)
179+
return False
180+
return self._mqtt_connect()
181+
182+
@staticmethod
183+
@abc.abstractmethod
184+
def _device_serial_from_name(name):
185+
"""Get device serial from mDNS name."""
186+
return
187+
122188
@property
123189
@abc.abstractmethod
124190
def status_topic(self):

libpurecool/dyson_pure_cool_link.py

Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import json
66
import logging
77
import time
8-
import socket
98
from threading import Thread
109
from queue import Queue, Empty
1110

@@ -20,48 +19,13 @@
2019
support_heating_v2
2120
from .dyson_pure_state import DysonPureHotCoolState, DysonPureCoolState, \
2221
DysonEnvironmentalSensorState
23-
from .zeroconf import ServiceBrowser, Zeroconf
2422

2523
_LOGGER = logging.getLogger(__name__)
2624

2725

2826
class DysonPureCoolLink(DysonDevice):
2927
"""Dyson device (fan)."""
3028

31-
class DysonDeviceListener:
32-
"""Message listener."""
33-
34-
def __init__(self, serial, add_device_function):
35-
"""Create a new message listener.
36-
37-
:param serial: Device serial
38-
:param add_device_function: Callback function
39-
"""
40-
self._serial = serial
41-
self.add_device_function = add_device_function
42-
43-
def remove_service(self, zeroconf, device_type, name):
44-
# pylint: disable=unused-argument,no-self-use
45-
"""Remove listener."""
46-
_LOGGER.info("Service %s removed", name)
47-
48-
def add_service(self, zeroconf, device_type, name):
49-
"""Add device.
50-
51-
:param zeroconf: MSDNS object
52-
:param device_type: Service type
53-
:param name: Device name
54-
"""
55-
device_serial = (name.split(".")[0]).split("_")[1]
56-
if device_serial == self._serial:
57-
# Find searched device
58-
info = zeroconf.get_service_info(device_type, name)
59-
address = socket.inet_ntoa(info.address)
60-
network_device = NetworkDevice(device_serial, address,
61-
info.port)
62-
self.add_device_function(network_device)
63-
zeroconf.close()
64-
6529
def __init__(self, json_body):
6630
"""Create a new Pure Cool Link device.
6731
@@ -119,25 +83,12 @@ def auto_connect(self, timeout=5, retry=15):
11983
:param retry: Max retry
12084
:return: True if connected, else False
12185
"""
122-
for i in range(retry):
123-
zeroconf = Zeroconf()
124-
listener = self.DysonDeviceListener(self._serial,
125-
self._add_network_device)
126-
ServiceBrowser(zeroconf, "_dyson_mqtt._tcp.local.", listener)
127-
try:
128-
self._network_device = self._search_device_queue.get(
129-
timeout=timeout)
130-
except Empty:
131-
# Unable to find device
132-
_LOGGER.warning("Unable to find device %s, try %s",
133-
self._serial, i)
134-
zeroconf.close()
135-
else:
136-
break
137-
if self._network_device is None:
138-
_LOGGER.error("Unable to connect to device %s", self._serial)
139-
return False
140-
return self._mqtt_connect()
86+
return self._auto_connect("_dyson_mqtt._tcp.local.", timeout, retry)
87+
88+
@staticmethod
89+
def _device_serial_from_name(name):
90+
"""Get device serial from mDNS name."""
91+
return (name.split(".")[0]).split("_")[1]
14192

14293
def connect(self, device_ip, device_port=DEFAULT_PORT):
14394
"""Connect to the device using ip address.

tests/test_libpurecoollink.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ def on_add_device(network_device):
103103
pass
104104

105105

106+
def device_serial_from_name(name):
107+
return (name.split(".")[0]).split("_")[1]
108+
109+
106110
class TestLibPureCoolLink(unittest.TestCase):
107111
def setUp(self):
108112
pass
@@ -237,7 +241,8 @@ def test_status_topic(self):
237241
@mock.patch('socket.inet_ntoa', )
238242
def test_device_dyson_listener(self, mocked_ntoa):
239243
listener = DysonPureCoolLink.DysonDeviceListener('serial-1',
240-
on_add_device)
244+
on_add_device,
245+
device_serial_from_name)
241246
zeroconf = Mock()
242247
listener.remove_service(zeroconf, "ptype", "serial-1")
243248
info = Mock()

0 commit comments

Comments
 (0)