66import socket
77import struct
88import time
9- from contextlib import contextmanager
109
1110from ssdp import SSDPRequest , SimpleServiceDiscoveryProtocol
1211
13- from .exceptions import YeelightError
1412from .devices import Bulb
13+ from .exceptions import YeelightError
1514
1615__all__ = ('search_bulbs' , 'YeelightProtocol' , 'bulbs' )
1716
1817logger = logging .getLogger ('yeelib' )
1918
20-
2119bulbs = {}
2220
2321MCAST_PORT = 1982
@@ -31,16 +29,34 @@ async def send_search_broadcast(transport, search_interval=30):
3129 ('ST' , 'wifi_bulb' ),
3230 ])
3331 while True :
34- request .sendto (transport , MCAST_ADDR )
32+ try :
33+ request .sendto (transport , MCAST_ADDR )
34+ except OSError :
35+ logger .exception ("Connection error" )
3536 await asyncio .sleep (search_interval )
3637
3738
3839class YeelightProtocol (SimpleServiceDiscoveryProtocol ):
3940 excluded_headers = ['DATE' , 'EXT' , 'SERVER' , 'CACHE-CONTROL' , 'LOCATION' ]
4041 location_patter = r'yeelight://(?P<ip>\d{1,3}(\.\d{1,3}){3}):(?P<port>\d+)'
4142
42- def __init__ (self , bulb_class = Bulb ):
43+ def __init__ (self , bulb_class = Bulb , loop = None ):
4344 self .bulb_class = bulb_class
45+ self .loop = loop or asyncio .get_event_loop ()
46+
47+ def connection_made (self , transport ):
48+ ucast_socket = transport .get_extra_info ('socket' )
49+ try :
50+ ucast_socket .bind (('' , MCAST_PORT ))
51+ fcntl .fcntl (ucast_socket , fcntl .F_SETFL , os .O_NONBLOCK )
52+ group = socket .inet_aton (
53+ SimpleServiceDiscoveryProtocol .MULTICAST_ADDRESS )
54+ mreq = struct .pack ("4sl" , group , socket .INADDR_ANY )
55+ ucast_socket .setsockopt (
56+ socket .IPPROTO_IP , socket .IP_ADD_MEMBERSHIP , mreq )
57+ except socket .error as e :
58+ ucast_socket .close ()
59+ self .connection_lost (exc = e )
4460
4561 @classmethod
4662 def header_to_kwargs (cls , headers ):
@@ -79,39 +95,21 @@ def register_bulb(self, **kwargs):
7995 else :
8096 bulbs [idx ].last_seen = time .time ()
8197
98+ def connection_lost (self , exc ):
99+ logger .exception ("connection error" )
82100
83- def open_unicast_socket ():
84- ucast_socket = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
85- ucast_socket .bind (('' , MCAST_PORT ))
86- fcntl .fcntl (ucast_socket , fcntl .F_SETFL , os .O_NONBLOCK )
87- group = socket .inet_aton (
88- SimpleServiceDiscoveryProtocol .MULTICAST_ADDRESS )
89- mreq = struct .pack ("4sl" , group , socket .INADDR_ANY )
90- ucast_socket .setsockopt (
91- socket .IPPROTO_IP , socket .IP_ADD_MEMBERSHIP , mreq )
92- return ucast_socket
101+ async def _restart ():
102+ await asyncio .sleep (10 )
103+ await search_bulbs (self .bulb_class , self .loop )
93104
105+ asyncio .Task (_restart ())
94106
95- @ contextmanager
96- def search_bulbs (bulb_class = Bulb , loop = None ):
107+
108+ async def search_bulbs (bulb_class = Bulb , loop = None ):
97109 if loop is None :
98110 loop = asyncio .get_event_loop ()
99- multicast_connection = loop .create_datagram_endpoint (
100- lambda : YeelightProtocol (bulb_class ),
101- family = socket .AF_INET )
102- mcast_transport , _ = loop .run_until_complete (multicast_connection )
103- loop .create_task (send_search_broadcast (mcast_transport ))
104-
105- ucast_socket = open_unicast_socket ()
106111 unicast_connection = loop .create_datagram_endpoint (
107- lambda : YeelightProtocol (bulb_class ),
108- sock = ucast_socket )
109- ucast_transport , _ = loop .run_until_complete (unicast_connection )
110- try :
111- yield bulbs
112- except BaseException :
113- pass
114- finally :
115- ucast_transport .close ()
116- mcast_transport .close ()
117- ucast_socket .close ()
112+ lambda : YeelightProtocol (bulb_class ), family = socket .AF_INET )
113+ ucast_transport , _ = await unicast_connection
114+ loop .create_task (send_search_broadcast (ucast_transport ))
115+ return bulbs
0 commit comments