@@ -45,25 +45,25 @@ class SCALE_CLASS(IntEnum):
45
45
# split messages between several (mostly 2) payloads
46
46
MODERN = 2 # eg. Lunar, Lunar 2021, Pearl, Pearl 2021, Pearl S
47
47
# report weight in unit indicated by byte 2 of STATUS_A message
48
- RELAY = 3 # relaying scales without display eg. Umbra, Cosmo, ..
48
+ RELAY = 3 # relaying scales without display eg. Umbra, ..
49
49
# report weight always in g; byte 2 of STATUS_A message reports auto off timer
50
50
51
51
@unique
52
52
class UNIT (IntEnum ):
53
53
KG = 1
54
54
G = 2
55
- G_FIXED = 4
56
55
OZ = 5
57
56
58
57
@unique
59
58
class AUTO_OFF_TIMER (IntEnum ):
60
59
AUTO_SLEEP_OFF = 1
61
60
AUTO_SLEEP_1MIN = 2
62
61
AUTO_SLEEP_5MIN = 3
63
- AUTO_SLEEP_30MIN = 4
64
- AUTO_OFF_5MIN = 5
65
- AUTO_OFF_10MIN = 6
66
- AUTO_OFF_30MIN = 7
62
+ AUTO_SLEEP_15MIN = 4
63
+ AUTO_SLEEP_30MIN = 5
64
+ AUTO_OFF_5MIN = 6
65
+ AUTO_OFF_10MIN = 7
66
+ AUTO_OFF_30MIN = 8
67
67
68
68
@unique
69
69
class KEY_INFO (IntEnum ):
@@ -192,7 +192,7 @@ class AcaiaBLE(QObject, ClientBLE): # pyright: ignore [reportGeneralTypeIssues]
192
192
HEADER1 :Final [bytes ] = b'\xef '
193
193
HEADER2 :Final [bytes ] = b'\xdd '
194
194
195
- HEARTBEAT_FREQUENCY = 3 # every 3 sec send the heartbeat
195
+ HEARTBEAT_FREQUENCY = 5 # every 5 sec send the heartbeat
196
196
197
197
198
198
# NOTE: __slots__ are incompatible with multiple inheritance mixings in subclasses (as done below in class Acaia with QObject)
@@ -230,7 +230,7 @@ def __init__(self, connected_handler:Optional[Callable[[], None]] = None,
230
230
###
231
231
232
232
# configure heartbeat
233
- self .set_heartbeat (self .HEARTBEAT_FREQUENCY ) # send keep-alive heartbeat all 3- 5sec; seems not to be needed any longer after sending ID on newer firmware versions!?
233
+ self .set_heartbeat (self .HEARTBEAT_FREQUENCY ) # send keep-alive heartbeat all 5sec; only for LEGACY scales
234
234
235
235
# register Acaia Legacy UUIDs
236
236
for legacy_name in (ACAIA_LEGACY_LUNAR_NAME , ACAIA_LEGACY_PEARL_NAME ):
@@ -277,12 +277,15 @@ def on_connect(self) -> None:
277
277
if connected_service_UUID == ACAIA_LEGACY_SERVICE_UUID :
278
278
_log .debug ('connected to Acaia Legacy Scale' )
279
279
self .scale_class = SCALE_CLASS .LEGACY
280
+ self .set_heartbeat (self .HEARTBEAT_FREQUENCY ) # enable heartbeat
280
281
elif connected_service_UUID == ACAIA_UMBRA_SERVICE_UUID :
281
282
_log .debug ('connected to Acaia Relay Scale' )
282
283
self .scale_class = SCALE_CLASS .RELAY
284
+ self .set_heartbeat (0 ) # disable heartbeat
283
285
else : #connected_service_UUID == ACAIA_SERVICE_UUID:
284
286
_log .debug ('connected to Acaia Scale' )
285
287
self .scale_class = SCALE_CLASS .MODERN
288
+ self .set_heartbeat (0 ) # disable heartbeat
286
289
if self ._connected_handler is not None :
287
290
self ._connected_handler ()
288
291
@@ -298,22 +301,13 @@ def on_disconnect(self) -> None:
298
301
def parse_info (self , data :bytes ) -> None :
299
302
_log .debug ('INFO MSG' )
300
303
301
- # if len(data)>1:
302
- # print(data[1])
303
- # if len(data)>2:
304
- # print(data[2])
305
-
306
304
if len (data )> 5 :
307
305
self .firmware = (data [3 ],data [4 ],data [5 ])
308
306
_log .debug ('firmware: %s.%s.%s' , self .firmware [0 ], self .firmware [1 ], f'{ self .firmware [2 ]:>03} ' )
309
307
310
- # passwd_set
311
- # if len(data)>6:
312
- # print(data[6])
313
-
314
308
def decode_weight (self , payload :bytes ) -> Optional [float ]:
315
309
try :
316
- #big_endian = (payload[5] & 0x08) == 0x08
310
+ #big_endian = (payload[5] & 0x08) == 0x08 # bit 3 of byte 5 is set if weight is in big endian
317
311
big_endian = self .scale_class == SCALE_CLASS .RELAY
318
312
319
313
value :float = 0
@@ -449,7 +443,6 @@ def parse_key_event(self, payload:bytes) -> int:
449
443
if len (payload ) < EVENT_LEN .KEY :
450
444
return - 1
451
445
_log .debug ('KEY EVENT' )
452
- _log .debug ('PRINT payload: %s' , payload )
453
446
454
447
if payload [0 ] == KEY_INFO .TARE :
455
448
_log .debug ('tare key' )
@@ -505,21 +498,28 @@ def parse_scale_events(self, payload:bytes) -> None:
505
498
def parse_status (self , payload :bytes ) -> None :
506
499
_log .debug ('STATUS' )
507
500
508
- # battery level (7 bits of second byte) + TIMER_START (1bit)
509
- if payload and len (payload ) > 0 :
501
+ # byte 0: message len
502
+
503
+ # byte 1: battery level (7 bits of second byte) + TIMER_START (1bit)
504
+ if payload and len (payload ) > 1 :
510
505
self .battery = int (payload [1 ] & ~ (1 << 7 ))
511
506
self .battery_changed (self .battery )
512
507
_log .debug ('battery: %s%%' , self .battery )
513
- # unit (7 bits of third byte) + CD_START (1bit)
514
- if payload and len (payload ) > 1 :
508
+
509
+ # byte 2:
510
+ if payload and len (payload ) > 2 :
515
511
if self .scale_class == SCALE_CLASS .RELAY :
512
+ # relay scales: auto off setting for
516
513
auto_off = int (payload [2 ] & 0xFF )
517
514
if auto_off == 0 :
518
515
self .auto_off_timer = AUTO_OFF_TIMER .AUTO_SLEEP_OFF
519
516
_log .debug ('AUTO OFF TIMER: Auto Sleep Off' )
520
517
elif auto_off == 1 :
521
518
self .auto_off_timer = AUTO_OFF_TIMER .AUTO_SLEEP_5MIN
522
519
_log .debug ('AUTO OFF TIMER: AutoSleep 5 min' )
520
+ elif auto_off == 2 :
521
+ self .auto_off_timer = AUTO_OFF_TIMER .AUTO_SLEEP_15MIN
522
+ _log .debug ('AUTO OFF TIMER: AutoSleep 15 min' )
523
523
elif auto_off == 3 :
524
524
self .auto_off_timer = AUTO_OFF_TIMER .AUTO_SLEEP_30MIN
525
525
_log .debug ('AUTO OFF TIMER: AutoSleep 30 min' )
@@ -536,18 +536,74 @@ def parse_status(self, payload:bytes) -> None:
536
536
self .auto_off_timer = AUTO_OFF_TIMER .AUTO_SLEEP_1MIN
537
537
_log .debug ('AUTO OFF TIMER: AutoSleep 1 min' )
538
538
else :
539
+ # display scales: weight unit (7 bits of third byte) + CD_START (1bit)
539
540
self .unit = int (payload [2 ] & 0x7F )
540
- _log .debug ('unit: %s' , self .unit )
541
-
542
- # mode (7 bits of third byte) + tare (1bit)
543
- # sleep (4th byte), 0:off, 1:5sec, 2:10sec, 3:20sec, 4:30sec, 5:60sec
544
- # key disabled (5th byte), touch key setting 0: off , 1: on
545
- # sound (6th byte), beep setting 0 : off 1: on
546
- # resolution (7th byte), 0 : default, 1 : high
547
- # max weight (8th byte)
541
+ _log .debug ('unit: %s (%s)' , self .unit , ('g' if self .unit == UNIT .G else 'oz' ))
542
+
543
+ # byte 3:
544
+ if payload and len (payload ) > 3 :
545
+ if self .scale_class == SCALE_CLASS .RELAY :
546
+ # relay scales: beep setting (0:off, 1:on)
547
+ # display scales: beep setting (0:off, 1:on)
548
+ _log .debug ('sound: %s' , ('on' if payload [6 ] else 'off' ))
549
+ else :
550
+ # display scales: mode (7 bits of third byte) + tare (1bit)
551
+ mode = int (payload [3 ] & 0x7F )
552
+ _log .debug ('mode: %s' , mode )
553
+ _log .debug ('tare: %s' , (payload [3 ] & 0x80 ) == 0x80 )
554
+ # Lunar
555
+ # 2: NodE_1 Weighing Mode
556
+ # 4: NodE_2 Dual Display Mode
557
+ # 3: NodE_3 Timer Starts with Flow Mode (drop)
558
+ # 15: NodE_4 Auto-Tare Timer Starts with Flow Mode (drop/square)
559
+ # 16: NodE_5 Auto-Tare Auto-Start Timer Mode (triangle/square)
560
+ # 17: NodE_6 Auto-Tare Mode (square)
561
+
562
+ # byte 4:
563
+ if payload and len (payload ) > 4 :
564
+ if self .scale_class == SCALE_CLASS .RELAY :
565
+ # relay scales: weight unit setting (0:g, 1:oz)
566
+ self .unit = (UNIT .OZ if payload [4 ] == 1 else UNIT .G )
567
+ _log .debug ('unit: %s (%s)' , self .unit , ('g' if self .unit == UNIT .G else 'oz' ))
568
+ else :
569
+ sleep_modes = {0 :'off' , 1 :'5sec' , 2 :'10sec' , 3 :'20sec' , 4 :'30sec' , 5 :'60sec' }
570
+ _log .debug ('sleep: %s%s' , payload [4 ], (f' ({ sleep_modes [payload [4 ]]} )' if (payload [4 ] in sleep_modes ) else '' ))
571
+
572
+ # byte 5:
573
+ if payload and len (payload ) > 5 :
574
+ if self .scale_class == SCALE_CLASS .RELAY :
575
+ # relay scales: resolution setting
576
+ _log .debug ('resolution: %s' , ('0.01g' if payload [5 ] else '0.1g' )) # resolution/readability: 0.1g / 0.01g
577
+ else :
578
+ # display scales: key disabled (0: off , 1: on)
579
+ _log .debug ('keys disabled: %s' , ('on' if payload [5 ] else 'off' ))
580
+
581
+ # byte 6:
582
+ if payload and len (payload ) > 6 :
583
+ if self .scale_class == SCALE_CLASS .RELAY :
584
+ # relay scales: magic relay sensing (low/normal/high)
585
+ _log .debug ('magic relay sensing: %s' , payload [6 ])
586
+ # 0: low, 1: normal, 2: high
587
+ else :
588
+ # display scales: beep setting (0:off, 1:on)
589
+ _log .debug ('sound: %s' , ('on' if payload [6 ] else 'off' ))
590
+
591
+ # byte 7:
548
592
if payload and len (payload ) > 7 :
549
- self .max_weight = (payload [7 ] + 1 ) * 1000
550
- _log .debug ('max_weight: %s' , self .max_weight )
593
+ if self .scale_class == SCALE_CLASS .RELAY :
594
+ # relay scales: magic relay beep
595
+ _log .debug ('magic relay beep: %s' , ('on' if payload [7 ] else 'off' ))
596
+ else :
597
+ # display scales: resolution (0:default, 1:high)
598
+ _log .debug ('resolution: %s' , ('high' if payload [7 ] else 'default' ))
599
+
600
+ # byte 8/8/10:
601
+ if payload and len (payload ) > 10 and self .scale_class == SCALE_CLASS .RELAY :
602
+ # firmware version
603
+ firmware = (payload [8 ],payload [9 ],payload [10 ])
604
+ _log .debug ('firmware: %s.%s.%s' , firmware [0 ], firmware [1 ], firmware [2 ])
605
+
606
+ # bytes 11 & 12 reserved
551
607
552
608
553
609
def parse_data (self , msg_type :int , data :bytes ) -> None :
@@ -562,6 +618,7 @@ def parse_data(self, msg_type:int, data:bytes) -> None:
562
618
if self .id_sent and not self .fast_notifications_sent :
563
619
# we configure the scale to receive the initial
564
620
# weight notification as fast as possible
621
+ # Note: this event is needed to have the connected scale start to send weight messages even on relay scales which ignore the settings
565
622
self .fast_notifications ()
566
623
567
624
if not self .id_sent :
@@ -640,7 +697,7 @@ def send_ID(self) -> None:
640
697
# self.send_message(MSG.IDENTIFY,b'\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32\x33\x34')
641
698
self .id_sent = True
642
699
643
- # configure notifications
700
+ # NOTE: notifications configuration not supported by Umbra and newer scales!
644
701
645
702
def slow_notifications (self ) -> None :
646
703
_log .debug ('slow notifications' )
@@ -703,9 +760,7 @@ async def reader(self, stream:IteratorReader) -> None:
703
760
cmd = int .from_bytes (await stream .readexactly (1 ), 'big' )
704
761
if cmd in {CMD .SYSTEM_SA , CMD .INFO_A , CMD .STATUS_A , CMD .EVENT_SA }:
705
762
dl = await stream .readexactly (1 )
706
- data_len :int = min (20 , int .from_bytes (dl , 'big' ))
707
- # if cmd == CMD.STATUS_A: # all others are of variable length; STATUS_A with maxlen=16!?
708
- # data_len = min(data_len,16)
763
+ data_len :int = int .from_bytes (dl , 'big' )
709
764
data = await stream .readexactly (data_len - 1 )
710
765
crc = await stream .readexactly (2 )
711
766
data = dl + data
0 commit comments