5
5
Parts were taken from the cometblue module by im-0
6
6
Port to bleak/asyncio is based on pySwitchbot
7
7
"""
8
+ from __future__ import annotations
9
+
10
+ import asyncio
8
11
import logging
9
12
import struct
10
- import time
11
- from contextlib import contextmanager
12
13
13
- import asyncio
14
- from bleak import BleakError , BleakScanner
14
+ from bleak import BleakError
15
+ from bleak import BleakClient
15
16
from bleak .backends .device import BLEDevice
16
- from bleak .backends .service import BleakGATTCharacteristic , BleakGATTServiceCollection
17
- from bleak_retry_connector import (
18
- BleakClientWithServiceCache ,
19
- BleakNotFoundError ,
20
- ble_device_has_changed ,
21
- establish_connection ,
22
- )
17
+ from bleak_retry_connector import establish_connection
23
18
24
19
_LOGGER = logging .getLogger (__name__ )
25
20
26
-
27
21
PASSWORD_CHAR = "47e9ee30-47e9-11e4-8939-164230d1df67"
28
22
TEMPERATURE_CHAR = "47e9ee2b-47e9-11e4-8939-164230d1df67"
29
23
BATTERY_CHAR = "47e9ee2c-47e9-11e4-8939-164230d1df67"
30
24
STATUS_CHAR = "47e9ee2a-47e9-11e4-8939-164230d1df67"
31
25
DATETIME_CHAR = "47e9ee01-47e9-11e4-8939-164230d1df67"
32
- SOFTWARE_REV = "00002a28-0000-1000-8000-00805f9b34fb" # software_revision (0.0.6-sygonix1)
33
- MODEL_CHAR = "00002a24-0000-1000-8000-00805f9b34fb" # model_number (Comet Blue)
26
+ SOFTWARE_REV = "00002a28-0000-1000-8000-00805f9b34fb" # software_revision (0.0.6-sygonix1)
27
+ MODEL_CHAR = "00002a24-0000-1000-8000-00805f9b34fb" # model_number (Comet Blue)
34
28
MANUFACTURER_CHAR = "00002a29-0000-1000-8000-00805f9b34fb" # manufacturer_name (EUROtronic GmbH)
35
- #FIRMWARE_CHAR = "00002a26-0000-1000-8000-00805f9b34fb"
29
+ # FIRMWARE_CHAR = "00002a26-0000-1000-8000-00805f9b34fb"
36
30
37
- FIRMWARE_CHAR = "47e9ee2d-47e9-11e4-8939-164230d1df67" # firmware_revision2 (COBL0126)
31
+ FIRMWARE_CHAR = "47e9ee2d-47e9-11e4-8939-164230d1df67" # firmware_revision2 (COBL0126)
38
32
_PIN_STRUCT_PACKING = '<I'
39
33
_DATETIME_STRUCT_PACKING = '<BBBBB'
40
34
_DAY_STRUCT_PACKING = '<BBBBBBBB'
@@ -55,7 +49,7 @@ def _encode_datetime(dt):
55
49
56
50
class CometBlueStates :
57
51
"""CometBlue Thermostat States"""
58
- TEMPERATURE_OFF = 7.5 # special temperature, valve fully closed
52
+ TEMPERATURE_OFF = 7.5 # special temperature, valve fully closed
59
53
_TEMPERATURES_STRUCT_PACKING = '<bbbbbbb'
60
54
_STATUS_STRUCT_PACKING = '<BBB'
61
55
@@ -82,7 +76,12 @@ def __init__(self):
82
76
self ._status = dict ()
83
77
self ._battery_level = None
84
78
self ._current_temp = None
85
- self .clear_temperatures ()
79
+ self .target_temperature = None
80
+ self .target_temp_l = None
81
+ self .target_temp_h = None
82
+ self .offset_temperature = None
83
+ self .window_open_detection = None
84
+ self .window_open_minutes = None
86
85
87
86
def clear_temperatures (self ):
88
87
self .target_temperature = None
@@ -170,7 +169,6 @@ def encode_status(value):
170
169
@status_code .setter
171
170
def status_code (self , val ):
172
171
def decode_status (value ):
173
- state_bytes = struct .unpack (CometBlueStates ._STATUS_STRUCT_PACKING , value )
174
172
state_dword = struct .unpack ('<I' , value + b'\x00 ' )[0 ]
175
173
176
174
report = {}
@@ -245,18 +243,17 @@ def temperatures(self, value):
245
243
@property
246
244
def all_temperatures_none (self ):
247
245
"""True if any of the temperature properties is not None"""
248
- values = set ((self .target_temperature , self .target_temp_l , self .target_temp_h , self .offset_temperature , self .window_open_detection , self .window_open_minutes ))
246
+ values = {self .target_temperature , self .target_temp_l , self .target_temp_h , self .offset_temperature , self .window_open_detection ,
247
+ self .window_open_minutes }
249
248
values .remove (None )
250
249
return len (values ) == 0
251
250
252
251
253
-
254
252
class CometBlue :
255
253
"""CometBlue Thermostat """
256
254
257
- def __init__ (self , address , pin ):
255
+ def __init__ (self , pin ):
258
256
super (CometBlue , self ).__init__ ()
259
- self ._address = address
260
257
self ._device : BLEDevice | None = None
261
258
self ._pin = pin
262
259
self .available = False
@@ -265,20 +262,18 @@ def __init__(self, address, pin):
265
262
self ._target = CometBlueStates ()
266
263
self ._connect_lock = asyncio .Lock ()
267
264
self ._operation_lock = asyncio .Lock ()
268
- self ._client : BleakClientWithServiceCache | None = None
269
- self ._cached_services : BleakGATTServiceCollection | None = None
270
- self ._read_char : BleakGATTCharacteristic | None = None
271
- self ._write_char : BleakGATTCharacteristic | None = None
265
+ self ._client : BleakClient | None = None
272
266
self ._disconnect_timer : asyncio .TimerHandle | None = None
273
267
self ._expected_disconnect = False
274
- self .loop = asyncio .get_event_loop ()
268
+ self ._loop = asyncio .get_event_loop ()
275
269
# btle.Debugging = True
276
- async def _ensure_connected (self ):
270
+
271
+ async def _ensure_connected (self , device : BLEDevice ):
277
272
"""Ensure connection to device is established."""
278
273
if self ._connect_lock .locked ():
279
274
_LOGGER .debug (
280
275
"%s: Connection already in progress, waiting for it to complete;" ,
281
- self . _address ,
276
+ device . address ,
282
277
)
283
278
if self ._client and self ._client .is_connected :
284
279
self ._reset_disconnect_timer ()
@@ -288,54 +283,44 @@ async def _ensure_connected(self):
288
283
if self ._client and self ._client .is_connected :
289
284
self ._reset_disconnect_timer ()
290
285
return
291
- _LOGGER .debug ("%s: Connecting; " , self ._address )
292
- if self ._device is None :
293
- self ._device = await BleakScanner .find_device_by_address (self ._address , 60.0 )
294
- if self ._device is None :
295
- self ._device = await BleakScanner .find_device_by_address (self ._address , 60.0 )
296
- if self ._device is None :
297
- raise Exception ("could not discover device" )
298
-
299
- client = await establish_connection (
300
- BleakClientWithServiceCache ,
286
+ _LOGGER .debug ("%s: Connecting; " , device .address )
287
+ self ._device = device
288
+ self ._client = await establish_connection (
289
+ BleakClient ,
301
290
self ._device ,
302
- self ._address ,
303
- self ._disconnected ,
304
- cached_services = self ._cached_services ,
291
+ self ._device .address ,
292
+ self ._disconnected
305
293
)
306
- self ._cached_services = client .services
307
- _LOGGER .debug ("%s: Connected" , self ._address )
308
- services = client .services
309
- self ._client = client
294
+ _LOGGER .debug ("%s: Connected" , self ._device .address )
310
295
# authenticate with PIN and initialize static values
311
296
self ._reset_disconnect_timer ()
312
297
313
298
data = struct .pack (_PIN_STRUCT_PACKING , self ._pin )
314
299
try :
315
300
await self ._client .write_gatt_char (PASSWORD_CHAR , data , response = True )
316
301
except BleakError :
317
- _LOGGER .error ("provided pin was not accepted by device %s" % client .address )
302
+ _LOGGER .error ("provided pin was not accepted by device %s" % self . _client .address )
318
303
319
- _LOGGER .debug ("Connected and authenticated with device %s" , self ._address )
304
+ _LOGGER .debug ("Connected and authenticated with device %s" , self ._device . address )
320
305
321
-
322
- def _disconnected (self , client : BleakClientWithServiceCache ) -> None :
306
+ def _disconnected (self , _ : BleakClient ) -> None :
323
307
"""Disconnected callback."""
324
308
if self ._expected_disconnect :
325
309
_LOGGER .debug (
326
- "%s: Disconnected from device;" , self ._address
310
+ "%s: Disconnected from device;" , self ._device . address
327
311
)
328
312
return
329
313
_LOGGER .warning (
330
314
"%s: Device unexpectedly disconnected" ,
331
- self ._address
315
+ self ._device . address
332
316
)
317
+
333
318
def _reset_disconnect_timer (self ):
334
319
"""Reset disconnect timer."""
335
320
if self ._disconnect_timer :
336
321
self ._disconnect_timer .cancel ()
337
322
self ._expected_disconnect = False
338
- self ._disconnect_timer = self .loop .call_later (
323
+ self ._disconnect_timer = self ._loop .call_later (
339
324
DISCONNECT_DELAY , self ._disconnect
340
325
)
341
326
@@ -355,7 +340,6 @@ async def _execute_disconnect(self):
355
340
if client and client .is_connected :
356
341
await client .disconnect ()
357
342
358
-
359
343
def should_update (self ):
360
344
"""
361
345
Signal necessity to call update() on next cycle because values need
@@ -480,13 +464,13 @@ def window_open(self):
480
464
"""Return True if device detected opened window"""
481
465
return self ._current .window_open
482
466
483
- async def update (self ):
467
+ async def update (self , device : BLEDevice ):
484
468
"""Communicate with device, first try to write new values, then read from device"""
485
469
current = self ._current
486
470
target = self ._target
487
471
488
- #with self.btle_connection() as conn:
489
- await self ._ensure_connected ()
472
+ # with self.btle_connection() as conn:
473
+ await self ._ensure_connected (device )
490
474
491
475
conn = self ._client
492
476
device_infos = [
@@ -496,30 +480,29 @@ async def update(self):
496
480
current .software_rev
497
481
]
498
482
if None in device_infos :
499
- _LOGGER .debug ("Fetching hardware information for device %s" , self ._address )
483
+ _LOGGER .debug ("Fetching hardware information for device %s" , self ._device . address )
500
484
current .model = (await conn .read_gatt_char (MODEL_CHAR )).decode ()
501
485
current .firmware_rev = (await conn .read_gatt_char (FIRMWARE_CHAR )).decode ()
502
486
current .manufacturer = (await conn .read_gatt_char (MANUFACTURER_CHAR )).decode ()
503
- current .software_rev = (await conn .read_gatt_char (SOFTWARE_REV )).decode ()
487
+ current .software_rev = (await conn .read_gatt_char (SOFTWARE_REV )).decode ()
504
488
_LOGGER .debug ("Sucessfully fetched hardware information" )
505
489
506
490
if not target .all_temperatures_none :
507
491
await conn .write_gatt_char (TEMPERATURE_CHAR ,
508
- target .temperatures ,
509
- response = True )
492
+ target .temperatures ,
493
+ response = True )
510
494
target .clear_temperatures ()
511
- _LOGGER .debug ("Successfully updated Temperatures for device %s" , self ._address )
495
+ _LOGGER .debug ("Successfully updated Temperatures for device %s" , self ._device . address )
512
496
513
497
if target .status_code is not None :
514
498
await conn .write_gatt_char (STATUS_CHAR ,
515
- target .status_code ,
516
- response = True )
499
+ target .status_code ,
500
+ response = True )
517
501
target .status_code = None
518
- _LOGGER .debug ("Successfully updated status for device %s" , self ._address )
502
+ _LOGGER .debug ("Successfully updated status for device %s" , self ._device . address )
519
503
520
504
current .temperatures = await conn .read_gatt_char (TEMPERATURE_CHAR )
521
505
current .status_code = await conn .read_gatt_char (STATUS_CHAR )
522
506
current .battery_level = await conn .read_gatt_char (BATTERY_CHAR )
523
- _LOGGER .debug ("Successfully fetched new readings for device %s" , self ._address )
507
+ _LOGGER .debug ("Successfully fetched new readings for device %s" , self ._device . address )
524
508
self .available = True
525
-
0 commit comments