colmi_r02_client.steps
1from dataclasses import dataclass 2 3from colmi_r02_client.packet import make_packet 4 5CMD_GET_STEP_SOMEDAY = 67 # 0x43 6 7 8def read_steps_packet(day_offset: int = 0) -> bytearray: 9 """ 10 Read the steps for a given day offset from "today" relative to the ring's internal clock. 11 12 There's also 4 more bytes I don't fully understand but seem constant 13 - 0x0f # constant 14 - 0x00 # idk 15 - 0x5f # less than 95 and greater than byte 16 - 0x01 # constant 17 """ 18 sub_data = bytearray(b"\x00\x0f\x00\x5f\x01") 19 sub_data[0] = day_offset 20 21 return make_packet(CMD_GET_STEP_SOMEDAY, sub_data) 22 23 24@dataclass 25class SportDetail: 26 year: int 27 month: int 28 day: int 29 time_index: int 30 """I'm not sure about this one yet""" 31 calories: int 32 steps: int 33 distance: int 34 """Distance in meters""" 35 36 37class NoData: 38 """Returned when there's no heart rate data""" 39 40 41class SportDetailParser: 42 r""" 43 Parse SportDetailPacket, of which there will be several 44 45 example data: 46 bytearray(b'C\xf0\x05\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x009') 47 bytearray(b'C#\x08\x13\x10\x00\x05\xc8\x000\x00\x1b\x00\x00\x00\xa9') 48 bytearray(b'C#\x08\x13\x14\x01\x05\xb6\x18\xaa\x04i\x03\x00\x00\x83') 49 bytearray(b'C#\x08\x13\x18\x02\x058\x04\xe1\x00\x95\x00\x00\x00R') 50 bytearray(b'C#\x08\x13\x1c\x03\x05\x05\x02l\x00H\x00\x00\x00`') 51 bytearray(b'C#\x08\x13L\x04\x05\xef\x01c\x00D\x00\x00\x00m') 52 """ 53 54 def __init__(self): 55 self.reset() 56 57 def reset(self) -> None: 58 self.new_calorie_protocol = False 59 self.index = 0 60 self.details: list[SportDetail] = [] 61 62 def parse(self, packet: bytearray) -> list[SportDetail] | None | NoData: 63 assert len(packet) == 16 64 assert packet[0] == CMD_GET_STEP_SOMEDAY 65 66 if self.index == 0 and packet[1] == 255: 67 self.reset() 68 return NoData() 69 70 if self.index == 0 and packet[1] == 240: 71 if packet[3] == 1: 72 self.new_calorie_protocol = True 73 self.index += 1 74 return None 75 76 year = bcd_to_decimal(packet[1]) + 2000 77 month = bcd_to_decimal(packet[2]) 78 day = bcd_to_decimal(packet[3]) 79 time_index = packet[4] 80 calories = packet[7] | (packet[8] << 8) 81 if self.new_calorie_protocol: 82 calories *= 10 83 steps = packet[9] | (packet[10] << 8) 84 distance = packet[11] | (packet[12] << 8) 85 86 details = SportDetail( 87 year=year, 88 month=month, 89 day=day, 90 time_index=time_index, 91 calories=calories, 92 steps=steps, 93 distance=distance, 94 ) 95 self.details.append(details) 96 97 if packet[5] == packet[6] - 1: 98 x = self.details 99 self.reset() 100 return x 101 else: 102 self.index += 1 103 return None 104 105 106def bcd_to_decimal(b: int) -> int: 107 return (((b >> 4) & 15) * 10) + (b & 15)
CMD_GET_STEP_SOMEDAY =
67
def
read_steps_packet(day_offset: int = 0) -> bytearray:
9def read_steps_packet(day_offset: int = 0) -> bytearray: 10 """ 11 Read the steps for a given day offset from "today" relative to the ring's internal clock. 12 13 There's also 4 more bytes I don't fully understand but seem constant 14 - 0x0f # constant 15 - 0x00 # idk 16 - 0x5f # less than 95 and greater than byte 17 - 0x01 # constant 18 """ 19 sub_data = bytearray(b"\x00\x0f\x00\x5f\x01") 20 sub_data[0] = day_offset 21 22 return make_packet(CMD_GET_STEP_SOMEDAY, sub_data)
Read the steps for a given day offset from "today" relative to the ring's internal clock.
There's also 4 more bytes I don't fully understand but seem constant
- 0x0f # constant
- 0x00 # idk
- 0x5f # less than 95 and greater than byte
- 0x01 # constant
@dataclass
class
SportDetail:
25@dataclass 26class SportDetail: 27 year: int 28 month: int 29 day: int 30 time_index: int 31 """I'm not sure about this one yet""" 32 calories: int 33 steps: int 34 distance: int 35 """Distance in meters"""
class
NoData:
Returned when there's no heart rate data
class
SportDetailParser:
42class SportDetailParser: 43 r""" 44 Parse SportDetailPacket, of which there will be several 45 46 example data: 47 bytearray(b'C\xf0\x05\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x009') 48 bytearray(b'C#\x08\x13\x10\x00\x05\xc8\x000\x00\x1b\x00\x00\x00\xa9') 49 bytearray(b'C#\x08\x13\x14\x01\x05\xb6\x18\xaa\x04i\x03\x00\x00\x83') 50 bytearray(b'C#\x08\x13\x18\x02\x058\x04\xe1\x00\x95\x00\x00\x00R') 51 bytearray(b'C#\x08\x13\x1c\x03\x05\x05\x02l\x00H\x00\x00\x00`') 52 bytearray(b'C#\x08\x13L\x04\x05\xef\x01c\x00D\x00\x00\x00m') 53 """ 54 55 def __init__(self): 56 self.reset() 57 58 def reset(self) -> None: 59 self.new_calorie_protocol = False 60 self.index = 0 61 self.details: list[SportDetail] = [] 62 63 def parse(self, packet: bytearray) -> list[SportDetail] | None | NoData: 64 assert len(packet) == 16 65 assert packet[0] == CMD_GET_STEP_SOMEDAY 66 67 if self.index == 0 and packet[1] == 255: 68 self.reset() 69 return NoData() 70 71 if self.index == 0 and packet[1] == 240: 72 if packet[3] == 1: 73 self.new_calorie_protocol = True 74 self.index += 1 75 return None 76 77 year = bcd_to_decimal(packet[1]) + 2000 78 month = bcd_to_decimal(packet[2]) 79 day = bcd_to_decimal(packet[3]) 80 time_index = packet[4] 81 calories = packet[7] | (packet[8] << 8) 82 if self.new_calorie_protocol: 83 calories *= 10 84 steps = packet[9] | (packet[10] << 8) 85 distance = packet[11] | (packet[12] << 8) 86 87 details = SportDetail( 88 year=year, 89 month=month, 90 day=day, 91 time_index=time_index, 92 calories=calories, 93 steps=steps, 94 distance=distance, 95 ) 96 self.details.append(details) 97 98 if packet[5] == packet[6] - 1: 99 x = self.details 100 self.reset() 101 return x 102 else: 103 self.index += 1 104 return None
Parse SportDetailPacket, of which there will be several
example data: bytearray(b'C\xf0\x05\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x009') bytearray(b'C#\x08\x13\x10\x00\x05\xc8\x000\x00\x1b\x00\x00\x00\xa9') bytearray(b'C#\x08\x13\x14\x01\x05\xb6\x18\xaa\x04i\x03\x00\x00\x83') bytearray(b'C#\x08\x13\x18\x02\x058\x04\xe1\x00\x95\x00\x00\x00R') bytearray(b'C#\x08\x13\x1c\x03\x05\x05\x02l\x00H\x00\x00\x00`') bytearray(b'C#\x08\x13L\x04\x05\xef\x01c\x00D\x00\x00\x00m')
63 def parse(self, packet: bytearray) -> list[SportDetail] | None | NoData: 64 assert len(packet) == 16 65 assert packet[0] == CMD_GET_STEP_SOMEDAY 66 67 if self.index == 0 and packet[1] == 255: 68 self.reset() 69 return NoData() 70 71 if self.index == 0 and packet[1] == 240: 72 if packet[3] == 1: 73 self.new_calorie_protocol = True 74 self.index += 1 75 return None 76 77 year = bcd_to_decimal(packet[1]) + 2000 78 month = bcd_to_decimal(packet[2]) 79 day = bcd_to_decimal(packet[3]) 80 time_index = packet[4] 81 calories = packet[7] | (packet[8] << 8) 82 if self.new_calorie_protocol: 83 calories *= 10 84 steps = packet[9] | (packet[10] << 8) 85 distance = packet[11] | (packet[12] << 8) 86 87 details = SportDetail( 88 year=year, 89 month=month, 90 day=day, 91 time_index=time_index, 92 calories=calories, 93 steps=steps, 94 distance=distance, 95 ) 96 self.details.append(details) 97 98 if packet[5] == packet[6] - 1: 99 x = self.details 100 self.reset() 101 return x 102 else: 103 self.index += 1 104 return None
def
bcd_to_decimal(b: int) -> int: