33
44_MODES = ["auto" , "cool" , "dry" , "fan" , "heat" ]
55
6+ _SWING_CHAR_TO_NAME = {
7+ "a" : "auto" ,
8+ "h" : "horizontal" ,
9+ "3" : "30" ,
10+ "4" : "45" ,
11+ "6" : "60" ,
12+ "v" : "vertical" ,
13+ "x" : "stop" ,
14+ }
15+
16+ _SWING_NAME_TO_CHAR = {value : key for key , value in _SWING_CHAR_TO_NAME .items ()}
17+
18+ SWING_MODES = list (_SWING_CHAR_TO_NAME .values ())
19+
620
721class CoolMasterNet ():
822 """A connection to a coolmasternet bridge."""
9- def __init__ (self , host , port = 10102 , read_timeout = 1 ):
23+ def __init__ (self , host , port = 10102 , read_timeout = 1 , swing_support = False ):
1024 """Initialize this CoolMasterNet instance to connect to a particular
1125 host at a particular port."""
1226 self ._host = host
1327 self ._port = port
1428 self ._read_timeout = read_timeout
29+ self ._swing_support = swing_support
30+ self ._concurrent_reads = asyncio .Semaphore (3 )
1531
1632 async def _make_request (self , request ):
1733 """Send a request to the CoolMasterNet and returns the response."""
18- reader , writer = await asyncio .open_connection (self ._host , self ._port )
34+ async with self ._concurrent_reads :
35+
36+ reader , writer = await asyncio .open_connection (self ._host , self ._port )
1937
20- try :
21- prompt = await asyncio .wait_for (reader .readuntil (b">" ), self ._read_timeout )
22- if prompt != b">" :
23- raise Exception ("CoolMasterNet prompt not found" )
38+ try :
39+ prompt = await asyncio .wait_for (reader .readuntil (b">" ), self ._read_timeout )
40+ if prompt != b">" :
41+ raise Exception ("CoolMasterNet prompt not found" )
2442
25- writer .write ((request + "\n " ).encode ("ascii" ))
26- response = await asyncio .wait_for (reader .readuntil (b"\n >" ), self ._read_timeout )
43+ writer .write ((request + "\n " ).encode ("ascii" ))
44+ response = await asyncio .wait_for (reader .readuntil (b"\n >" ), self ._read_timeout )
2745
28- data = response .decode ("ascii" )
29- if data .endswith ("\n >" ):
30- data = data [:- 1 ]
46+ data = response .decode ("ascii" )
47+ if data .endswith ("\n >" ):
48+ data = data [:- 1 ]
3149
32- if data .endswith ("OK\r \n " ):
33- data = data [:- 4 ]
50+ if data .endswith ("OK\r \n " ):
51+ data = data [:- 4 ]
3452
35- return data
36- finally :
37- writer .close ()
38- await writer .wait_closed ()
53+ return data
54+ finally :
55+ writer .close ()
56+ await writer .wait_closed ()
3957
4058 async def info (self ):
4159 """Get the general info the this CoolMasterNet."""
@@ -48,20 +66,30 @@ async def status(self):
4866 """Return a list of CoolMasterNetUnit objects with current status."""
4967 status_lines = (await self ._make_request ("ls2" )).strip ().split ("\r \n " )
5068 return {
51- line [0 :6 ]:CoolMasterNetUnit (self , line [0 :6 ], line )
52- for line in status_lines
69+ key : unit
70+ for unit , key in await asyncio .gather (
71+ * (CoolMasterNetUnit .create (self , line [0 :6 ], line ) for line in status_lines ))
5372 }
5473
5574
5675class CoolMasterNetUnit ():
5776 """An immutable snapshot of a unit."""
58- def __init__ (self , bridge , unit_id , raw ):
77+ def __init__ (self , bridge , unit_id , raw , swing_raw ):
5978 """Initialize a unit snapshot."""
6079 self ._raw = raw
80+ self ._swing_raw = swing_raw
6181 self ._unit_id = unit_id
6282 self ._bridge = bridge
6383 self ._parse ()
6484
85+ @classmethod
86+ async def create (cls , bridge , unit_id , raw = None ):
87+ if raw is None :
88+ raw = (await bridge ._make_request (f"ls2 { unit_id } " )).strip ()
89+ swing_raw = ((await bridge ._make_request (f"query { unit_id } s" )).strip ()
90+ if bridge ._swing_support else "" )
91+ return CoolMasterNetUnit (bridge , unit_id , raw , swing_raw ), unit_id
92+
6593 def _parse (self ):
6694 fields = re .split (r"\s+" , self ._raw .strip ())
6795 if len (fields ) != 9 :
@@ -73,14 +101,14 @@ def _parse(self):
73101 self ._temperature = float (fields [3 ][:- 1 ])
74102 self ._fan_speed = fields [4 ].lower ()
75103 self ._mode = fields [5 ].lower ()
104+ self ._swing = _SWING_CHAR_TO_NAME .get (self ._swing_raw )
76105
77106 async def _make_unit_request (self , request ):
78107 return await self ._bridge ._make_request (request .replace ("UID" , self ._unit_id ))
79108
80109 async def refresh (self ):
81110 """Refresh the data from CoolMasterNet and return it as a new instance."""
82- status_line = (await self ._make_unit_request ("ls2 UID" )).strip ()
83- return CoolMasterNetUnit (self ._bridge , status_line [0 :6 ], status_line )
111+ return (await CoolMasterNetUnit .create (self ._bridge , self ._unit_id ))[0 ]
84112
85113 @property
86114 def unit_id (self ):
@@ -112,6 +140,11 @@ def mode(self):
112140 """The current mode (e.g. heat, cool)."""
113141 return self ._mode
114142
143+ @property
144+ def swing (self ):
145+ """The current swing mode (e.g. horizontal)."""
146+ return self ._swing
147+
115148 @property
116149 def temperature_unit (self ):
117150 return self ._temperature_unit
@@ -137,6 +170,21 @@ async def set_thermostat(self, value):
137170 await self ._make_unit_request (f"temp UID { value } " )
138171 return await self .refresh ()
139172
173+ async def set_swing (self , value ):
174+ """Set the swing mode."""
175+ if not value in SWING_MODES :
176+ raise ValueError (
177+ f"Unrecognized swing mode { value } . Valid values: { ', ' .join (SWING_MODES )} "
178+ )
179+
180+ return_value = await self ._make_unit_request (f"swing UID { _SWING_NAME_TO_CHAR [value ]} " )
181+ if return_value .startswith ("Unsupported Feature" ):
182+ raise ValueError (
183+ f"Unit { self ._unit_id } doesn't support swing mode { value } ."
184+ )
185+
186+ return await self .refresh ()
187+
140188 async def turn_on (self ):
141189 """Turn a unit on."""
142190 await self ._make_unit_request ("on UID" )
0 commit comments