feat: add general real time measurements

Add support for all "real time" measurements that are supported in the
QRing protocol. This includes:

- heart rate
- SPO2
- HRV?
- Blood sugar??
- ECG???

and others

closes #9

Co-authored-by: Wesley Ellis <tahnok@gmail.com>
This commit is contained in:
rdmm123 2024-10-23 22:16:49 -04:00 committed by Wesley Ellis
parent 45e71b0723
commit 8020f861a1
13 changed files with 1857 additions and 877 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ captures/*.bin
*.sqlite
.hypothesis
dist/
__pycache__

View File

@ -82,7 +82,7 @@ Found device(s)
Once you have your address you can use it to do things like get real time heart rate
```sh
colmi_r02_client --address=70:CB:0D:D0:34:1C get-real-time-heart-rate
colmi_r02_client --address=70:CB:0D:D0:34:1C get-real-time heart-rate
```
```

View File

@ -14,7 +14,7 @@ import asyncclick as click
from bleak import BleakScanner
from colmi_r02_client.client import Client
from colmi_r02_client import steps, pretty_print, db, date_utils, hr
from colmi_r02_client import steps, pretty_print, db, date_utils, hr, real_time
logging.basicConfig(level=logging.WARNING, format="%(name)s: %(message)s")
@ -144,19 +144,17 @@ async def set_heart_rate_log_settings(client: Client, enable: bool, interval: in
@cli_client.command()
@click.pass_obj
async def get_real_time_heart_rate(client: Client) -> None:
"""Get real time heart rate.
TODO: add number of readings
"""
@click.argument("reading", nargs=1, type=click.Choice(list(real_time.REAL_TIME_MAPPING.keys())))
async def get_real_time(client: Client, reading: str) -> None:
"""Get any real time measurement (like heart rate or SPO2)"""
async with client:
click.echo("Starting reading, please wait.")
result = await client.get_realtime_heart_rate()
reading_type = real_time.REAL_TIME_MAPPING[reading]
result = await client.get_realtime_reading(reading_type)
if result:
click.echo(result)
else:
click.echo("Error, no HR detected. Is the ring being worn?")
click.echo(f"Error, no {reading.replace('-', ' ')} detected. Is the ring being worn?")
@cli_client.command()

View File

@ -10,18 +10,7 @@ from typing import Any
from bleak import BleakClient
from bleak.backends.characteristic import BleakGATTCharacteristic
from colmi_r02_client import (
battery,
date_utils,
real_time_hr,
steps,
set_time,
blink_twice,
hr,
hr_settings,
packet,
reboot,
)
from colmi_r02_client import battery, date_utils, steps, set_time, blink_twice, hr, hr_settings, packet, reboot, real_time
UART_SERVICE_UUID = "6E40FFF0-B5A3-F393-E0A9-E50E24DCCA9E"
UART_RX_CHAR_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
@ -52,8 +41,8 @@ class FullData:
COMMAND_HANDLERS: dict[int, Callable[[bytearray], Any]] = {
battery.CMD_BATTERY: battery.parse_battery,
real_time_hr.CMD_START_HEART_RATE: real_time_hr.parse_heart_rate,
real_time_hr.CMD_STOP_HEART_RATE: empty_parse,
real_time.CMD_START_REAL_TIME: real_time.parse_real_time_reading,
real_time.CMD_STOP_REAL_TIME: empty_parse,
steps.CMD_GET_STEP_SOMEDAY: steps.SportDetailParser().parse,
hr.CMD_READ_HEART_RATE: hr.HeartRateLogParser().parse,
set_time.CMD_SET_TIME: empty_parse,
@ -141,10 +130,10 @@ class Client:
assert isinstance(result, battery.BatteryInfo)
return result
async def get_realtime_heart_rate(self) -> list[int] | None:
return await self._poll_real_time_reading(real_time_hr.START_HEART_RATE_PACKET)
async def _poll_real_time_reading(self, reading_type: real_time.RealTimeReading) -> list[int] | None:
start_packet = real_time.get_start_packet(reading_type)
stop_packet = real_time.get_stop_packet(reading_type)
async def _poll_real_time_reading(self, start_packet: bytearray) -> list[int] | None:
await self.send_packet(start_packet)
valid_readings: list[int] = []
@ -152,28 +141,27 @@ class Client:
tries = 0
while len(valid_readings) < 6 and tries < 20:
try:
data: real_time_hr.Reading | real_time_hr.ReadingError = await asyncio.wait_for(
self.queues[real_time_hr.CMD_START_HEART_RATE].get(),
data: real_time.Reading | real_time.ReadingError = await asyncio.wait_for(
self.queues[real_time.CMD_START_REAL_TIME].get(),
timeout=2,
)
if isinstance(data, real_time_hr.ReadingError):
if isinstance(data, real_time.ReadingError):
error = True
break
if data.value != 0:
valid_readings.append(data.value)
except TimeoutError:
tries += 1
await self.send_packet(real_time_hr.CONTINUE_HEART_RATE_PACKET)
# TODO remove this since it breaks Realtec based rings
await self.send_packet(real_time.CONTINUE_HEART_RATE_PACKET)
await self.send_packet(
real_time_hr.STOP_HEART_RATE_PACKET,
)
await self.send_packet(stop_packet)
if error:
return None
return valid_readings
async def get_realtime_spo2(self) -> list[int] | None:
return await self._poll_real_time_reading(real_time_hr.START_SPO2_PACKET)
async def get_realtime_reading(self, reading_type: real_time.RealTimeReading) -> list[int] | None:
return await self._poll_real_time_reading(reading_type)
async def set_time(self, ts: datetime) -> None:
await self.send_packet(set_time.set_time_packet(ts))

View File

@ -0,0 +1,92 @@
"""
Stream real time data from the ring.
Currently heart rate and SPO2 seem reasonable.
HRV, ECG, blood pressure and blood sugar seem unlikely to be something you
can correct
"""
from dataclasses import dataclass
from enum import IntEnum
from colmi_r02_client.packet import make_packet
class Action(IntEnum):
START = 1
PAUSE = 2
CONTINUE = 3
STOP = 4
class RealTimeReading(IntEnum):
"""
Taken from https://colmi.puxtril.com/commands/#data-request
"""
HEART_RATE = 1
BLOOD_PRESSURE = 2
SPO2 = 3
FATIGUE = 4
HEALTH_CHECK = 5
# leaving this out as it's redundant
# REAL_TIME_HEART_RATE = 6
ECG = 7
PRESSURE = 8
BLOOD_SUGAR = 9
HRV = 10
REAL_TIME_MAPPING: dict[str, RealTimeReading] = {
"heart-rate": RealTimeReading.HEART_RATE,
"blood-pressure": RealTimeReading.BLOOD_PRESSURE,
"spo2": RealTimeReading.SPO2,
"fatigue": RealTimeReading.FATIGUE,
"health-check": RealTimeReading.HEALTH_CHECK,
"ecg": RealTimeReading.ECG,
"pressure": RealTimeReading.PRESSURE,
"blood-sugar": RealTimeReading.BLOOD_SUGAR,
"hrv": RealTimeReading.HRV,
}
CMD_START_REAL_TIME = 105
CMD_STOP_REAL_TIME = 106
CMD_REAL_TIME_HEART_RATE = 30
CONTINUE_HEART_RATE_PACKET = make_packet(CMD_REAL_TIME_HEART_RATE, bytearray(b"3"))
@dataclass
class Reading:
kind: RealTimeReading
value: int
@dataclass
class ReadingError:
kind: RealTimeReading
code: int
def get_start_packet(reading_type: RealTimeReading) -> bytearray:
return make_packet(CMD_START_REAL_TIME, bytearray([reading_type, Action.START]))
def get_continue_packet(reading_type: RealTimeReading) -> bytearray:
return make_packet(CMD_START_REAL_TIME, bytearray([reading_type, Action.CONTINUE]))
def get_stop_packet(reading_type: RealTimeReading) -> bytearray:
return make_packet(CMD_STOP_REAL_TIME, bytearray([reading_type, 0, 0]))
def parse_real_time_reading(packet: bytearray) -> Reading | ReadingError:
assert packet[0] == CMD_START_REAL_TIME
kind = RealTimeReading(packet[1])
error_code = packet[2]
if error_code != 0:
return ReadingError(kind=kind, code=error_code)
return Reading(kind=kind, value=packet[3])

View File

@ -1,54 +0,0 @@
"""
This covers commands for starting and stopping the real time
heart rate and blood oxygen (SPO2) measurements, and parsing the results
"""
from dataclasses import dataclass
from colmi_r02_client.packet import make_packet
CMD_REAL_TIME_HEART_RATE = 30 # 0x1E
CMD_START_HEART_RATE = 105 # 0x69
CMD_STOP_HEART_RATE = 106 # 0x6A
START_HEART_RATE_PACKET = make_packet(
CMD_START_HEART_RATE,
bytearray(b"\x01\x00"),
) # why is this backwards?
CONTINUE_HEART_RATE_PACKET = make_packet(CMD_REAL_TIME_HEART_RATE, bytearray(b"3"))
STOP_HEART_RATE_PACKET = make_packet(CMD_STOP_HEART_RATE, bytearray(b"\x01\x00\x00"))
START_SPO2_PACKET = make_packet(CMD_START_HEART_RATE, bytearray(b"\x03\x25"))
STOP_SPO2_PACKET = make_packet(CMD_STOP_HEART_RATE, bytearray(b"\x03\x00\x00"))
@dataclass
class Reading:
kind: int
"""
either heart rate or spo2
TODO make this an enum and figure out which is which
"""
value: int
@dataclass
class ReadingError:
code: int
kind: int
def parse_heart_rate(packet: bytearray) -> Reading | ReadingError:
"""Parses the heart rate and spo2 packets"""
assert packet[0] == CMD_START_HEART_RATE
kind = packet[1]
error_code = packet[2]
if error_code != 0:
return ReadingError(kind=kind, code=error_code)
return Reading(kind=packet[1], value=packet[3])

View File

@ -46,7 +46,7 @@
<li><a href="colmi_r02_client/hr_settings.html">hr_settings</a></li>
<li><a href="colmi_r02_client/packet.html">packet</a></li>
<li><a href="colmi_r02_client/pretty_print.html">pretty_print</a></li>
<li><a href="colmi_r02_client/real_time_hr.html">real_time_hr</a></li>
<li><a href="colmi_r02_client/real_time.html">real_time</a></li>
<li><a href="colmi_r02_client/reboot.html">reboot</a></li>
<li><a href="colmi_r02_client/set_time.html">set_time</a></li>
<li><a href="colmi_r02_client/steps.html">steps</a></li>
@ -159,7 +159,7 @@ colmi_r02_client </h1>
<p>Once you have your address you can use it to do things like get real time heart rate</p>
<div class="pdoc-code codehilite">
<pre><span></span><code>colmi_r02_client<span class="w"> </span>--address<span class="o">=</span><span class="m">70</span>:CB:0D:D0:34:1C<span class="w"> </span>get-real-time-heart-rate
<pre><span></span><code>colmi_r02_client<span class="w"> </span>--address<span class="o">=</span><span class="m">70</span>:CB:0D:D0:34:1C<span class="w"> </span>get-real-time<span class="w"> </span>heart-rate
</code></pre>
</div>

View File

@ -75,7 +75,7 @@
</span><span id="L-14"><a href="#L-14"><span class="linenos"> 14</span></a><span class="kn">from</span> <span class="nn">bleak</span> <span class="kn">import</span> <span class="n">BleakScanner</span>
</span><span id="L-15"><a href="#L-15"><span class="linenos"> 15</span></a>
</span><span id="L-16"><a href="#L-16"><span class="linenos"> 16</span></a><span class="kn">from</span> <span class="nn">colmi_r02_client.client</span> <span class="kn">import</span> <span class="n">Client</span>
</span><span id="L-17"><a href="#L-17"><span class="linenos"> 17</span></a><span class="kn">from</span> <span class="nn">colmi_r02_client</span> <span class="kn">import</span> <span class="n">steps</span><span class="p">,</span> <span class="n">pretty_print</span><span class="p">,</span> <span class="n">db</span><span class="p">,</span> <span class="n">date_utils</span><span class="p">,</span> <span class="n">hr</span>
</span><span id="L-17"><a href="#L-17"><span class="linenos"> 17</span></a><span class="kn">from</span> <span class="nn">colmi_r02_client</span> <span class="kn">import</span> <span class="n">steps</span><span class="p">,</span> <span class="n">pretty_print</span><span class="p">,</span> <span class="n">db</span><span class="p">,</span> <span class="n">date_utils</span><span class="p">,</span> <span class="n">hr</span><span class="p">,</span> <span class="n">real_time</span>
</span><span id="L-18"><a href="#L-18"><span class="linenos"> 18</span></a>
</span><span id="L-19"><a href="#L-19"><span class="linenos"> 19</span></a><span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">WARNING</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">%(name)s</span><span class="s2">: </span><span class="si">%(message)s</span><span class="s2">&quot;</span><span class="p">)</span>
</span><span id="L-20"><a href="#L-20"><span class="linenos"> 20</span></a>
@ -205,182 +205,184 @@
</span><span id="L-144"><a href="#L-144"><span class="linenos">144</span></a>
</span><span id="L-145"><a href="#L-145"><span class="linenos">145</span></a><span class="nd">@cli_client</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
</span><span id="L-146"><a href="#L-146"><span class="linenos">146</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">pass_obj</span>
</span><span id="L-147"><a href="#L-147"><span class="linenos">147</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">get_real_time_heart_rate</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">Client</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-148"><a href="#L-148"><span class="linenos">148</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;Get real time heart rate.</span>
</span><span id="L-149"><a href="#L-149"><span class="linenos">149</span></a>
</span><span id="L-150"><a href="#L-150"><span class="linenos">150</span></a><span class="sd"> TODO: add number of readings</span>
</span><span id="L-151"><a href="#L-151"><span class="linenos">151</span></a><span class="sd"> &quot;&quot;&quot;</span>
</span><span id="L-152"><a href="#L-152"><span class="linenos">152</span></a>
</span><span id="L-153"><a href="#L-153"><span class="linenos">153</span></a> <span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="p">:</span>
</span><span id="L-154"><a href="#L-154"><span class="linenos">154</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;Starting reading, please wait.&quot;</span><span class="p">)</span>
</span><span id="L-155"><a href="#L-155"><span class="linenos">155</span></a> <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">get_realtime_heart_rate</span><span class="p">()</span>
</span><span id="L-156"><a href="#L-156"><span class="linenos">156</span></a> <span class="k">if</span> <span class="n">result</span><span class="p">:</span>
</span><span id="L-157"><a href="#L-157"><span class="linenos">157</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span><span id="L-158"><a href="#L-158"><span class="linenos">158</span></a> <span class="k">else</span><span class="p">:</span>
</span><span id="L-159"><a href="#L-159"><span class="linenos">159</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;Error, no HR detected. Is the ring being worn?&quot;</span><span class="p">)</span>
</span><span id="L-160"><a href="#L-160"><span class="linenos">160</span></a>
</span><span id="L-161"><a href="#L-161"><span class="linenos">161</span></a>
</span><span id="L-162"><a href="#L-162"><span class="linenos">162</span></a><span class="nd">@cli_client</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
</span><span id="L-163"><a href="#L-163"><span class="linenos">163</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">pass_obj</span>
</span><span id="L-164"><a href="#L-164"><span class="linenos">164</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span>
</span><span id="L-165"><a href="#L-165"><span class="linenos">165</span></a> <span class="s2">&quot;--when&quot;</span><span class="p">,</span>
</span><span id="L-166"><a href="#L-166"><span class="linenos">166</span></a> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">DateTime</span><span class="p">(),</span>
</span><span id="L-167"><a href="#L-167"><span class="linenos">167</span></a> <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span><span id="L-168"><a href="#L-168"><span class="linenos">168</span></a> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;The date you want steps for&quot;</span><span class="p">,</span>
</span><span id="L-169"><a href="#L-169"><span class="linenos">169</span></a><span class="p">)</span>
</span><span id="L-170"><a href="#L-170"><span class="linenos">170</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span><span class="s2">&quot;--as-csv&quot;</span><span class="p">,</span> <span class="n">is_flag</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Print as CSV&quot;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span><span id="L-171"><a href="#L-171"><span class="linenos">171</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">get_steps</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">Client</span><span class="p">,</span> <span class="n">when</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">as_csv</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-172"><a href="#L-172"><span class="linenos">172</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;Get step data&quot;&quot;&quot;</span>
</span><span id="L-173"><a href="#L-173"><span class="linenos">173</span></a>
</span><span id="L-174"><a href="#L-174"><span class="linenos">174</span></a> <span class="k">if</span> <span class="n">when</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-175"><a href="#L-175"><span class="linenos">175</span></a> <span class="n">when</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">(</span><span class="n">tz</span><span class="o">=</span><span class="n">timezone</span><span class="o">.</span><span class="n">utc</span><span class="p">)</span>
</span><span id="L-176"><a href="#L-176"><span class="linenos">176</span></a> <span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="p">:</span>
</span><span id="L-177"><a href="#L-177"><span class="linenos">177</span></a> <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">get_steps</span><span class="p">(</span><span class="n">when</span><span class="p">)</span>
</span><span id="L-178"><a href="#L-178"><span class="linenos">178</span></a> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">steps</span><span class="o">.</span><span class="n">NoData</span><span class="p">):</span>
</span><span id="L-179"><a href="#L-179"><span class="linenos">179</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;No results for day&quot;</span><span class="p">)</span>
</span><span id="L-180"><a href="#L-180"><span class="linenos">180</span></a> <span class="k">return</span>
</span><span id="L-181"><a href="#L-181"><span class="linenos">181</span></a>
</span><span id="L-182"><a href="#L-182"><span class="linenos">182</span></a> <span class="k">if</span> <span class="ow">not</span> <span class="n">as_csv</span><span class="p">:</span>
</span><span id="L-183"><a href="#L-183"><span class="linenos">183</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="n">pretty_print</span><span class="o">.</span><span class="n">print_dataclasses</span><span class="p">(</span><span class="n">result</span><span class="p">))</span>
</span><span id="L-184"><a href="#L-184"><span class="linenos">184</span></a> <span class="k">else</span><span class="p">:</span>
</span><span id="L-185"><a href="#L-185"><span class="linenos">185</span></a> <span class="n">out</span> <span class="o">=</span> <span class="n">StringIO</span><span class="p">()</span>
</span><span id="L-186"><a href="#L-186"><span class="linenos">186</span></a> <span class="n">writer</span> <span class="o">=</span> <span class="n">csv</span><span class="o">.</span><span class="n">DictWriter</span><span class="p">(</span><span class="n">out</span><span class="p">,</span> <span class="n">fieldnames</span><span class="o">=</span><span class="p">[</span><span class="n">f</span><span class="o">.</span><span class="n">name</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">dataclasses</span><span class="o">.</span><span class="n">fields</span><span class="p">(</span><span class="n">steps</span><span class="o">.</span><span class="n">SportDetail</span><span class="p">)])</span>
</span><span id="L-187"><a href="#L-187"><span class="linenos">187</span></a> <span class="n">writer</span><span class="o">.</span><span class="n">writeheader</span><span class="p">()</span>
</span><span id="L-188"><a href="#L-188"><span class="linenos">188</span></a> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">result</span><span class="p">:</span>
</span><span id="L-189"><a href="#L-189"><span class="linenos">189</span></a> <span class="n">writer</span><span class="o">.</span><span class="n">writerow</span><span class="p">(</span><span class="n">dataclasses</span><span class="o">.</span><span class="n">asdict</span><span class="p">(</span><span class="n">r</span><span class="p">))</span>
</span><span id="L-190"><a href="#L-190"><span class="linenos">190</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="n">out</span><span class="o">.</span><span class="n">getvalue</span><span class="p">())</span>
</span><span id="L-191"><a href="#L-191"><span class="linenos">191</span></a>
</span><span id="L-192"><a href="#L-192"><span class="linenos">192</span></a>
</span><span id="L-193"><a href="#L-193"><span class="linenos">193</span></a><span class="nd">@cli_client</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
</span><span id="L-194"><a href="#L-194"><span class="linenos">194</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">pass_obj</span>
</span><span id="L-195"><a href="#L-195"><span class="linenos">195</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">reboot</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">Client</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-196"><a href="#L-196"><span class="linenos">196</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;Reboot the ring&quot;&quot;&quot;</span>
</span><span id="L-197"><a href="#L-197"><span class="linenos">197</span></a>
</span><span id="L-198"><a href="#L-198"><span class="linenos">198</span></a> <span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="p">:</span>
</span><span id="L-199"><a href="#L-199"><span class="linenos">199</span></a> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">reboot</span><span class="p">()</span>
</span><span id="L-200"><a href="#L-200"><span class="linenos">200</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;Ring rebooted&quot;</span><span class="p">)</span>
</span><span id="L-201"><a href="#L-201"><span class="linenos">201</span></a>
</span><span id="L-202"><a href="#L-202"><span class="linenos">202</span></a>
</span><span id="L-203"><a href="#L-203"><span class="linenos">203</span></a><span class="nd">@cli_client</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
</span><span id="L-204"><a href="#L-204"><span class="linenos">204</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">pass_obj</span>
</span><span id="L-205"><a href="#L-205"><span class="linenos">205</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span>
</span><span id="L-206"><a href="#L-206"><span class="linenos">206</span></a> <span class="s2">&quot;--command&quot;</span><span class="p">,</span>
</span><span id="L-207"><a href="#L-207"><span class="linenos">207</span></a> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">IntRange</span><span class="p">(</span><span class="nb">min</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="mi">255</span><span class="p">),</span>
</span><span id="L-208"><a href="#L-208"><span class="linenos">208</span></a> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Raw command&quot;</span><span class="p">,</span>
</span><span id="L-209"><a href="#L-209"><span class="linenos">209</span></a><span class="p">)</span>
</span><span id="L-210"><a href="#L-210"><span class="linenos">210</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span>
</span><span id="L-211"><a href="#L-211"><span class="linenos">211</span></a> <span class="s2">&quot;--subdata&quot;</span><span class="p">,</span>
</span><span id="L-212"><a href="#L-212"><span class="linenos">212</span></a> <span class="nb">type</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span>
</span><span id="L-213"><a href="#L-213"><span class="linenos">213</span></a> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Hex encoded subdata array, will be parsed into a bytearray&quot;</span><span class="p">,</span>
</span><span id="L-214"><a href="#L-214"><span class="linenos">214</span></a><span class="p">)</span>
</span><span id="L-215"><a href="#L-215"><span class="linenos">215</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span><span class="s2">&quot;--replies&quot;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">IntRange</span><span class="p">(</span><span class="nb">min</span><span class="o">=</span><span class="mi">0</span><span class="p">),</span> <span class="n">default</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;How many reply packets to wait for&quot;</span><span class="p">)</span>
</span><span id="L-216"><a href="#L-216"><span class="linenos">216</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">raw</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">Client</span><span class="p">,</span> <span class="n">command</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">subdata</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span> <span class="n">replies</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-217"><a href="#L-217"><span class="linenos">217</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;Send the ring a raw command&quot;&quot;&quot;</span>
</span><span id="L-147"><a href="#L-147"><span class="linenos">147</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">argument</span><span class="p">(</span><span class="s2">&quot;reading&quot;</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">Choice</span><span class="p">(</span><span class="nb">list</span><span class="p">(</span><span class="n">real_time</span><span class="o">.</span><span class="n">REAL_TIME_MAPPING</span><span class="o">.</span><span class="n">keys</span><span class="p">())))</span>
</span><span id="L-148"><a href="#L-148"><span class="linenos">148</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">get_real_time</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">Client</span><span class="p">,</span> <span class="n">reading</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-149"><a href="#L-149"><span class="linenos">149</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;Get any real time measurement (like heart rate or SPO2)&quot;&quot;&quot;</span>
</span><span id="L-150"><a href="#L-150"><span class="linenos">150</span></a> <span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="p">:</span>
</span><span id="L-151"><a href="#L-151"><span class="linenos">151</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;Starting reading, please wait.&quot;</span><span class="p">)</span>
</span><span id="L-152"><a href="#L-152"><span class="linenos">152</span></a> <span class="n">reading_type</span> <span class="o">=</span> <span class="n">real_time</span><span class="o">.</span><span class="n">REAL_TIME_MAPPING</span><span class="p">[</span><span class="n">reading</span><span class="p">]</span>
</span><span id="L-153"><a href="#L-153"><span class="linenos">153</span></a> <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">get_realtime_reading</span><span class="p">(</span><span class="n">reading_type</span><span class="p">)</span>
</span><span id="L-154"><a href="#L-154"><span class="linenos">154</span></a> <span class="k">if</span> <span class="n">result</span><span class="p">:</span>
</span><span id="L-155"><a href="#L-155"><span class="linenos">155</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span><span id="L-156"><a href="#L-156"><span class="linenos">156</span></a> <span class="k">else</span><span class="p">:</span>
</span><span id="L-157"><a href="#L-157"><span class="linenos">157</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Error, no </span><span class="si">{</span><span class="n">reading</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39; &#39;</span><span class="p">)</span><span class="si">}</span><span class="s2"> detected. Is the ring being worn?&quot;</span><span class="p">)</span>
</span><span id="L-158"><a href="#L-158"><span class="linenos">158</span></a>
</span><span id="L-159"><a href="#L-159"><span class="linenos">159</span></a>
</span><span id="L-160"><a href="#L-160"><span class="linenos">160</span></a><span class="nd">@cli_client</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
</span><span id="L-161"><a href="#L-161"><span class="linenos">161</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">pass_obj</span>
</span><span id="L-162"><a href="#L-162"><span class="linenos">162</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span>
</span><span id="L-163"><a href="#L-163"><span class="linenos">163</span></a> <span class="s2">&quot;--when&quot;</span><span class="p">,</span>
</span><span id="L-164"><a href="#L-164"><span class="linenos">164</span></a> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">DateTime</span><span class="p">(),</span>
</span><span id="L-165"><a href="#L-165"><span class="linenos">165</span></a> <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span><span id="L-166"><a href="#L-166"><span class="linenos">166</span></a> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;The date you want steps for&quot;</span><span class="p">,</span>
</span><span id="L-167"><a href="#L-167"><span class="linenos">167</span></a><span class="p">)</span>
</span><span id="L-168"><a href="#L-168"><span class="linenos">168</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span><span class="s2">&quot;--as-csv&quot;</span><span class="p">,</span> <span class="n">is_flag</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Print as CSV&quot;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span><span id="L-169"><a href="#L-169"><span class="linenos">169</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">get_steps</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">Client</span><span class="p">,</span> <span class="n">when</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">as_csv</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-170"><a href="#L-170"><span class="linenos">170</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;Get step data&quot;&quot;&quot;</span>
</span><span id="L-171"><a href="#L-171"><span class="linenos">171</span></a>
</span><span id="L-172"><a href="#L-172"><span class="linenos">172</span></a> <span class="k">if</span> <span class="n">when</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-173"><a href="#L-173"><span class="linenos">173</span></a> <span class="n">when</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">(</span><span class="n">tz</span><span class="o">=</span><span class="n">timezone</span><span class="o">.</span><span class="n">utc</span><span class="p">)</span>
</span><span id="L-174"><a href="#L-174"><span class="linenos">174</span></a> <span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="p">:</span>
</span><span id="L-175"><a href="#L-175"><span class="linenos">175</span></a> <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">get_steps</span><span class="p">(</span><span class="n">when</span><span class="p">)</span>
</span><span id="L-176"><a href="#L-176"><span class="linenos">176</span></a> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">steps</span><span class="o">.</span><span class="n">NoData</span><span class="p">):</span>
</span><span id="L-177"><a href="#L-177"><span class="linenos">177</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;No results for day&quot;</span><span class="p">)</span>
</span><span id="L-178"><a href="#L-178"><span class="linenos">178</span></a> <span class="k">return</span>
</span><span id="L-179"><a href="#L-179"><span class="linenos">179</span></a>
</span><span id="L-180"><a href="#L-180"><span class="linenos">180</span></a> <span class="k">if</span> <span class="ow">not</span> <span class="n">as_csv</span><span class="p">:</span>
</span><span id="L-181"><a href="#L-181"><span class="linenos">181</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="n">pretty_print</span><span class="o">.</span><span class="n">print_dataclasses</span><span class="p">(</span><span class="n">result</span><span class="p">))</span>
</span><span id="L-182"><a href="#L-182"><span class="linenos">182</span></a> <span class="k">else</span><span class="p">:</span>
</span><span id="L-183"><a href="#L-183"><span class="linenos">183</span></a> <span class="n">out</span> <span class="o">=</span> <span class="n">StringIO</span><span class="p">()</span>
</span><span id="L-184"><a href="#L-184"><span class="linenos">184</span></a> <span class="n">writer</span> <span class="o">=</span> <span class="n">csv</span><span class="o">.</span><span class="n">DictWriter</span><span class="p">(</span><span class="n">out</span><span class="p">,</span> <span class="n">fieldnames</span><span class="o">=</span><span class="p">[</span><span class="n">f</span><span class="o">.</span><span class="n">name</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">dataclasses</span><span class="o">.</span><span class="n">fields</span><span class="p">(</span><span class="n">steps</span><span class="o">.</span><span class="n">SportDetail</span><span class="p">)])</span>
</span><span id="L-185"><a href="#L-185"><span class="linenos">185</span></a> <span class="n">writer</span><span class="o">.</span><span class="n">writeheader</span><span class="p">()</span>
</span><span id="L-186"><a href="#L-186"><span class="linenos">186</span></a> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">result</span><span class="p">:</span>
</span><span id="L-187"><a href="#L-187"><span class="linenos">187</span></a> <span class="n">writer</span><span class="o">.</span><span class="n">writerow</span><span class="p">(</span><span class="n">dataclasses</span><span class="o">.</span><span class="n">asdict</span><span class="p">(</span><span class="n">r</span><span class="p">))</span>
</span><span id="L-188"><a href="#L-188"><span class="linenos">188</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="n">out</span><span class="o">.</span><span class="n">getvalue</span><span class="p">())</span>
</span><span id="L-189"><a href="#L-189"><span class="linenos">189</span></a>
</span><span id="L-190"><a href="#L-190"><span class="linenos">190</span></a>
</span><span id="L-191"><a href="#L-191"><span class="linenos">191</span></a><span class="nd">@cli_client</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
</span><span id="L-192"><a href="#L-192"><span class="linenos">192</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">pass_obj</span>
</span><span id="L-193"><a href="#L-193"><span class="linenos">193</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">reboot</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">Client</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-194"><a href="#L-194"><span class="linenos">194</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;Reboot the ring&quot;&quot;&quot;</span>
</span><span id="L-195"><a href="#L-195"><span class="linenos">195</span></a>
</span><span id="L-196"><a href="#L-196"><span class="linenos">196</span></a> <span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="p">:</span>
</span><span id="L-197"><a href="#L-197"><span class="linenos">197</span></a> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">reboot</span><span class="p">()</span>
</span><span id="L-198"><a href="#L-198"><span class="linenos">198</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;Ring rebooted&quot;</span><span class="p">)</span>
</span><span id="L-199"><a href="#L-199"><span class="linenos">199</span></a>
</span><span id="L-200"><a href="#L-200"><span class="linenos">200</span></a>
</span><span id="L-201"><a href="#L-201"><span class="linenos">201</span></a><span class="nd">@cli_client</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
</span><span id="L-202"><a href="#L-202"><span class="linenos">202</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">pass_obj</span>
</span><span id="L-203"><a href="#L-203"><span class="linenos">203</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span>
</span><span id="L-204"><a href="#L-204"><span class="linenos">204</span></a> <span class="s2">&quot;--command&quot;</span><span class="p">,</span>
</span><span id="L-205"><a href="#L-205"><span class="linenos">205</span></a> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">IntRange</span><span class="p">(</span><span class="nb">min</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="nb">max</span><span class="o">=</span><span class="mi">255</span><span class="p">),</span>
</span><span id="L-206"><a href="#L-206"><span class="linenos">206</span></a> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Raw command&quot;</span><span class="p">,</span>
</span><span id="L-207"><a href="#L-207"><span class="linenos">207</span></a><span class="p">)</span>
</span><span id="L-208"><a href="#L-208"><span class="linenos">208</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span>
</span><span id="L-209"><a href="#L-209"><span class="linenos">209</span></a> <span class="s2">&quot;--subdata&quot;</span><span class="p">,</span>
</span><span id="L-210"><a href="#L-210"><span class="linenos">210</span></a> <span class="nb">type</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span>
</span><span id="L-211"><a href="#L-211"><span class="linenos">211</span></a> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Hex encoded subdata array, will be parsed into a bytearray&quot;</span><span class="p">,</span>
</span><span id="L-212"><a href="#L-212"><span class="linenos">212</span></a><span class="p">)</span>
</span><span id="L-213"><a href="#L-213"><span class="linenos">213</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span><span class="s2">&quot;--replies&quot;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">IntRange</span><span class="p">(</span><span class="nb">min</span><span class="o">=</span><span class="mi">0</span><span class="p">),</span> <span class="n">default</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;How many reply packets to wait for&quot;</span><span class="p">)</span>
</span><span id="L-214"><a href="#L-214"><span class="linenos">214</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">raw</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">Client</span><span class="p">,</span> <span class="n">command</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">subdata</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span> <span class="n">replies</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-215"><a href="#L-215"><span class="linenos">215</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;Send the ring a raw command&quot;&quot;&quot;</span>
</span><span id="L-216"><a href="#L-216"><span class="linenos">216</span></a>
</span><span id="L-217"><a href="#L-217"><span class="linenos">217</span></a> <span class="n">p_subdata</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">subdata</span><span class="p">)</span> <span class="k">if</span> <span class="n">subdata</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="nb">bytearray</span><span class="p">()</span>
</span><span id="L-218"><a href="#L-218"><span class="linenos">218</span></a>
</span><span id="L-219"><a href="#L-219"><span class="linenos">219</span></a> <span class="n">p_subdata</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">subdata</span><span class="p">)</span> <span class="k">if</span> <span class="n">subdata</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="nb">bytearray</span><span class="p">()</span>
</span><span id="L-220"><a href="#L-220"><span class="linenos">220</span></a>
</span><span id="L-221"><a href="#L-221"><span class="linenos">221</span></a> <span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="p">:</span>
</span><span id="L-222"><a href="#L-222"><span class="linenos">222</span></a> <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">raw</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="n">p_subdata</span><span class="p">,</span> <span class="n">replies</span><span class="p">)</span>
</span><span id="L-223"><a href="#L-223"><span class="linenos">223</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span><span id="L-224"><a href="#L-224"><span class="linenos">224</span></a>
</span><span id="L-225"><a href="#L-225"><span class="linenos">225</span></a>
</span><span id="L-226"><a href="#L-226"><span class="linenos">226</span></a><span class="nd">@cli_client</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
</span><span id="L-227"><a href="#L-227"><span class="linenos">227</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">pass_obj</span>
</span><span id="L-228"><a href="#L-228"><span class="linenos">228</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span>
</span><span id="L-229"><a href="#L-229"><span class="linenos">229</span></a> <span class="s2">&quot;--db&quot;</span><span class="p">,</span>
</span><span id="L-230"><a href="#L-230"><span class="linenos">230</span></a> <span class="s2">&quot;db_path&quot;</span><span class="p">,</span>
</span><span id="L-231"><a href="#L-231"><span class="linenos">231</span></a> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">writable</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">path_type</span><span class="o">=</span><span class="n">Path</span><span class="p">),</span>
</span><span id="L-232"><a href="#L-232"><span class="linenos">232</span></a> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Path to a directory or file to use as the database. If dir, then filename will be ring_data.sqlite&quot;</span><span class="p">,</span>
</span><span id="L-233"><a href="#L-233"><span class="linenos">233</span></a><span class="p">)</span>
</span><span id="L-234"><a href="#L-234"><span class="linenos">234</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span>
</span><span id="L-235"><a href="#L-235"><span class="linenos">235</span></a> <span class="s2">&quot;--start&quot;</span><span class="p">,</span>
</span><span id="L-236"><a href="#L-236"><span class="linenos">236</span></a> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">DateTime</span><span class="p">(),</span>
</span><span id="L-237"><a href="#L-237"><span class="linenos">237</span></a> <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span><span id="L-238"><a href="#L-238"><span class="linenos">238</span></a> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;The date you want to start grabbing data from&quot;</span><span class="p">,</span>
</span><span id="L-239"><a href="#L-239"><span class="linenos">239</span></a><span class="p">)</span>
</span><span id="L-240"><a href="#L-240"><span class="linenos">240</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span>
</span><span id="L-241"><a href="#L-241"><span class="linenos">241</span></a> <span class="s2">&quot;--end&quot;</span><span class="p">,</span>
</span><span id="L-242"><a href="#L-242"><span class="linenos">242</span></a> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">DateTime</span><span class="p">(),</span>
</span><span id="L-243"><a href="#L-243"><span class="linenos">243</span></a> <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span><span id="L-244"><a href="#L-244"><span class="linenos">244</span></a> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;The date you want to start grabbing data to&quot;</span><span class="p">,</span>
</span><span id="L-245"><a href="#L-245"><span class="linenos">245</span></a><span class="p">)</span>
</span><span id="L-246"><a href="#L-246"><span class="linenos">246</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">sync</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">Client</span><span class="p">,</span> <span class="n">db_path</span><span class="p">:</span> <span class="n">Path</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span> <span class="n">start</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span> <span class="n">end</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">|</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-247"><a href="#L-247"><span class="linenos">247</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
</span><span id="L-248"><a href="#L-248"><span class="linenos">248</span></a><span class="sd"> Sync all data from the ring to a sqlite database</span>
</span><span id="L-249"><a href="#L-249"><span class="linenos">249</span></a>
</span><span id="L-250"><a href="#L-250"><span class="linenos">250</span></a><span class="sd"> Currently grabs:</span>
</span><span id="L-251"><a href="#L-251"><span class="linenos">251</span></a><span class="sd"> - heart rates</span>
</span><span id="L-252"><a href="#L-252"><span class="linenos">252</span></a><span class="sd"> &quot;&quot;&quot;</span>
</span><span id="L-253"><a href="#L-253"><span class="linenos">253</span></a>
</span><span id="L-254"><a href="#L-254"><span class="linenos">254</span></a> <span class="k">if</span> <span class="n">db_path</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-255"><a href="#L-255"><span class="linenos">255</span></a> <span class="n">db_path</span> <span class="o">=</span> <span class="n">Path</span><span class="o">.</span><span class="n">cwd</span><span class="p">()</span>
</span><span id="L-256"><a href="#L-256"><span class="linenos">256</span></a> <span class="k">if</span> <span class="n">db_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span><span id="L-257"><a href="#L-257"><span class="linenos">257</span></a> <span class="n">db_path</span> <span class="o">/=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&quot;ring_data.sqlite&quot;</span><span class="p">)</span>
</span><span id="L-258"><a href="#L-258"><span class="linenos">258</span></a>
</span><span id="L-259"><a href="#L-259"><span class="linenos">259</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Writing to </span><span class="si">{</span><span class="n">db_path</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
</span><span id="L-260"><a href="#L-260"><span class="linenos">260</span></a> <span class="k">with</span> <span class="n">db</span><span class="o">.</span><span class="n">get_db_session</span><span class="p">(</span><span class="n">db_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
</span><span id="L-219"><a href="#L-219"><span class="linenos">219</span></a> <span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="p">:</span>
</span><span id="L-220"><a href="#L-220"><span class="linenos">220</span></a> <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">raw</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="n">p_subdata</span><span class="p">,</span> <span class="n">replies</span><span class="p">)</span>
</span><span id="L-221"><a href="#L-221"><span class="linenos">221</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span><span id="L-222"><a href="#L-222"><span class="linenos">222</span></a>
</span><span id="L-223"><a href="#L-223"><span class="linenos">223</span></a>
</span><span id="L-224"><a href="#L-224"><span class="linenos">224</span></a><span class="nd">@cli_client</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
</span><span id="L-225"><a href="#L-225"><span class="linenos">225</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">pass_obj</span>
</span><span id="L-226"><a href="#L-226"><span class="linenos">226</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span>
</span><span id="L-227"><a href="#L-227"><span class="linenos">227</span></a> <span class="s2">&quot;--db&quot;</span><span class="p">,</span>
</span><span id="L-228"><a href="#L-228"><span class="linenos">228</span></a> <span class="s2">&quot;db_path&quot;</span><span class="p">,</span>
</span><span id="L-229"><a href="#L-229"><span class="linenos">229</span></a> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">writable</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">path_type</span><span class="o">=</span><span class="n">Path</span><span class="p">),</span>
</span><span id="L-230"><a href="#L-230"><span class="linenos">230</span></a> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Path to a directory or file to use as the database. If dir, then filename will be ring_data.sqlite&quot;</span><span class="p">,</span>
</span><span id="L-231"><a href="#L-231"><span class="linenos">231</span></a><span class="p">)</span>
</span><span id="L-232"><a href="#L-232"><span class="linenos">232</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span>
</span><span id="L-233"><a href="#L-233"><span class="linenos">233</span></a> <span class="s2">&quot;--start&quot;</span><span class="p">,</span>
</span><span id="L-234"><a href="#L-234"><span class="linenos">234</span></a> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">DateTime</span><span class="p">(),</span>
</span><span id="L-235"><a href="#L-235"><span class="linenos">235</span></a> <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span><span id="L-236"><a href="#L-236"><span class="linenos">236</span></a> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;The date you want to start grabbing data from&quot;</span><span class="p">,</span>
</span><span id="L-237"><a href="#L-237"><span class="linenos">237</span></a><span class="p">)</span>
</span><span id="L-238"><a href="#L-238"><span class="linenos">238</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span>
</span><span id="L-239"><a href="#L-239"><span class="linenos">239</span></a> <span class="s2">&quot;--end&quot;</span><span class="p">,</span>
</span><span id="L-240"><a href="#L-240"><span class="linenos">240</span></a> <span class="nb">type</span><span class="o">=</span><span class="n">click</span><span class="o">.</span><span class="n">DateTime</span><span class="p">(),</span>
</span><span id="L-241"><a href="#L-241"><span class="linenos">241</span></a> <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span><span id="L-242"><a href="#L-242"><span class="linenos">242</span></a> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;The date you want to start grabbing data to&quot;</span><span class="p">,</span>
</span><span id="L-243"><a href="#L-243"><span class="linenos">243</span></a><span class="p">)</span>
</span><span id="L-244"><a href="#L-244"><span class="linenos">244</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">sync</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">Client</span><span class="p">,</span> <span class="n">db_path</span><span class="p">:</span> <span class="n">Path</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span> <span class="n">start</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span> <span class="n">end</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">|</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-245"><a href="#L-245"><span class="linenos">245</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;</span>
</span><span id="L-246"><a href="#L-246"><span class="linenos">246</span></a><span class="sd"> Sync all data from the ring to a sqlite database</span>
</span><span id="L-247"><a href="#L-247"><span class="linenos">247</span></a>
</span><span id="L-248"><a href="#L-248"><span class="linenos">248</span></a><span class="sd"> Currently grabs:</span>
</span><span id="L-249"><a href="#L-249"><span class="linenos">249</span></a><span class="sd"> - heart rates</span>
</span><span id="L-250"><a href="#L-250"><span class="linenos">250</span></a><span class="sd"> &quot;&quot;&quot;</span>
</span><span id="L-251"><a href="#L-251"><span class="linenos">251</span></a>
</span><span id="L-252"><a href="#L-252"><span class="linenos">252</span></a> <span class="k">if</span> <span class="n">db_path</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-253"><a href="#L-253"><span class="linenos">253</span></a> <span class="n">db_path</span> <span class="o">=</span> <span class="n">Path</span><span class="o">.</span><span class="n">cwd</span><span class="p">()</span>
</span><span id="L-254"><a href="#L-254"><span class="linenos">254</span></a> <span class="k">if</span> <span class="n">db_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span><span id="L-255"><a href="#L-255"><span class="linenos">255</span></a> <span class="n">db_path</span> <span class="o">/=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&quot;ring_data.sqlite&quot;</span><span class="p">)</span>
</span><span id="L-256"><a href="#L-256"><span class="linenos">256</span></a>
</span><span id="L-257"><a href="#L-257"><span class="linenos">257</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Writing to </span><span class="si">{</span><span class="n">db_path</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
</span><span id="L-258"><a href="#L-258"><span class="linenos">258</span></a> <span class="k">with</span> <span class="n">db</span><span class="o">.</span><span class="n">get_db_session</span><span class="p">(</span><span class="n">db_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
</span><span id="L-259"><a href="#L-259"><span class="linenos">259</span></a> <span class="k">if</span> <span class="n">start</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-260"><a href="#L-260"><span class="linenos">260</span></a> <span class="n">start</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">get_last_sync</span><span class="p">(</span><span class="n">session</span><span class="p">)</span>
</span><span id="L-261"><a href="#L-261"><span class="linenos">261</span></a> <span class="k">if</span> <span class="n">start</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-262"><a href="#L-262"><span class="linenos">262</span></a> <span class="n">start</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">get_last_sync</span><span class="p">(</span><span class="n">session</span><span class="p">)</span>
</span><span id="L-263"><a href="#L-263"><span class="linenos">263</span></a> <span class="k">if</span> <span class="n">start</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-264"><a href="#L-264"><span class="linenos">264</span></a> <span class="n">start</span> <span class="o">=</span> <span class="n">date_utils</span><span class="o">.</span><span class="n">now</span><span class="p">()</span> <span class="o">-</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">days</span><span class="o">=</span><span class="mi">7</span><span class="p">)</span>
</span><span id="L-265"><a href="#L-265"><span class="linenos">265</span></a> <span class="k">if</span> <span class="n">end</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-266"><a href="#L-266"><span class="linenos">266</span></a> <span class="n">end</span> <span class="o">=</span> <span class="n">date_utils</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
</span><span id="L-262"><a href="#L-262"><span class="linenos">262</span></a> <span class="n">start</span> <span class="o">=</span> <span class="n">date_utils</span><span class="o">.</span><span class="n">now</span><span class="p">()</span> <span class="o">-</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">days</span><span class="o">=</span><span class="mi">7</span><span class="p">)</span>
</span><span id="L-263"><a href="#L-263"><span class="linenos">263</span></a> <span class="k">if</span> <span class="n">end</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-264"><a href="#L-264"><span class="linenos">264</span></a> <span class="n">end</span> <span class="o">=</span> <span class="n">date_utils</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
</span><span id="L-265"><a href="#L-265"><span class="linenos">265</span></a>
</span><span id="L-266"><a href="#L-266"><span class="linenos">266</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Syncing from </span><span class="si">{</span><span class="n">start</span><span class="si">}</span><span class="s2"> to </span><span class="si">{</span><span class="n">end</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
</span><span id="L-267"><a href="#L-267"><span class="linenos">267</span></a>
</span><span id="L-268"><a href="#L-268"><span class="linenos">268</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Syncing from </span><span class="si">{</span><span class="n">start</span><span class="si">}</span><span class="s2"> to </span><span class="si">{</span><span class="n">end</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
</span><span id="L-269"><a href="#L-269"><span class="linenos">269</span></a>
</span><span id="L-270"><a href="#L-270"><span class="linenos">270</span></a> <span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="p">:</span>
</span><span id="L-271"><a href="#L-271"><span class="linenos">271</span></a> <span class="n">fd</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">get_full_data</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">)</span>
</span><span id="L-272"><a href="#L-272"><span class="linenos">272</span></a> <span class="n">db</span><span class="o">.</span><span class="n">sync</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">fd</span><span class="p">)</span>
</span><span id="L-273"><a href="#L-273"><span class="linenos">273</span></a>
</span><span id="L-274"><a href="#L-274"><span class="linenos">274</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;Done&quot;</span><span class="p">)</span>
</span><span id="L-275"><a href="#L-275"><span class="linenos">275</span></a>
</span><span id="L-268"><a href="#L-268"><span class="linenos">268</span></a> <span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="p">:</span>
</span><span id="L-269"><a href="#L-269"><span class="linenos">269</span></a> <span class="n">fd</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">get_full_data</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">)</span>
</span><span id="L-270"><a href="#L-270"><span class="linenos">270</span></a> <span class="n">db</span><span class="o">.</span><span class="n">sync</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">fd</span><span class="p">)</span>
</span><span id="L-271"><a href="#L-271"><span class="linenos">271</span></a> <span class="n">when</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">(</span><span class="n">tz</span><span class="o">=</span><span class="n">timezone</span><span class="o">.</span><span class="n">utc</span><span class="p">)</span>
</span><span id="L-272"><a href="#L-272"><span class="linenos">272</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;Ignore unexpect packet&quot;</span><span class="p">)</span>
</span><span id="L-273"><a href="#L-273"><span class="linenos">273</span></a> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">set_time</span><span class="p">(</span><span class="n">when</span><span class="p">)</span>
</span><span id="L-274"><a href="#L-274"><span class="linenos">274</span></a>
</span><span id="L-275"><a href="#L-275"><span class="linenos">275</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;Done&quot;</span><span class="p">)</span>
</span><span id="L-276"><a href="#L-276"><span class="linenos">276</span></a>
</span><span id="L-277"><a href="#L-277"><span class="linenos">277</span></a><span class="n">DEVICE_NAME_PREFIXES</span> <span class="o">=</span> <span class="p">[</span>
</span><span id="L-278"><a href="#L-278"><span class="linenos">278</span></a> <span class="s2">&quot;R01&quot;</span><span class="p">,</span>
</span><span id="L-279"><a href="#L-279"><span class="linenos">279</span></a> <span class="s2">&quot;R02&quot;</span><span class="p">,</span>
</span><span id="L-280"><a href="#L-280"><span class="linenos">280</span></a> <span class="s2">&quot;R03&quot;</span><span class="p">,</span>
</span><span id="L-281"><a href="#L-281"><span class="linenos">281</span></a> <span class="s2">&quot;R04&quot;</span><span class="p">,</span>
</span><span id="L-282"><a href="#L-282"><span class="linenos">282</span></a> <span class="s2">&quot;R05&quot;</span><span class="p">,</span>
</span><span id="L-283"><a href="#L-283"><span class="linenos">283</span></a> <span class="s2">&quot;R06&quot;</span><span class="p">,</span>
</span><span id="L-284"><a href="#L-284"><span class="linenos">284</span></a> <span class="s2">&quot;R07&quot;</span><span class="p">,</span>
</span><span id="L-285"><a href="#L-285"><span class="linenos">285</span></a> <span class="s2">&quot;R10&quot;</span><span class="p">,</span> <span class="c1"># maybe compatible?</span>
</span><span id="L-286"><a href="#L-286"><span class="linenos">286</span></a> <span class="s2">&quot;VK-5098&quot;</span><span class="p">,</span>
</span><span id="L-287"><a href="#L-287"><span class="linenos">287</span></a> <span class="s2">&quot;MERLIN&quot;</span><span class="p">,</span>
</span><span id="L-288"><a href="#L-288"><span class="linenos">288</span></a> <span class="s2">&quot;Hello Ring&quot;</span><span class="p">,</span>
</span><span id="L-289"><a href="#L-289"><span class="linenos">289</span></a> <span class="s2">&quot;RING1&quot;</span><span class="p">,</span>
</span><span id="L-290"><a href="#L-290"><span class="linenos">290</span></a> <span class="s2">&quot;boAtring&quot;</span><span class="p">,</span>
</span><span id="L-291"><a href="#L-291"><span class="linenos">291</span></a> <span class="s2">&quot;TR-R02&quot;</span><span class="p">,</span>
</span><span id="L-292"><a href="#L-292"><span class="linenos">292</span></a> <span class="s2">&quot;SE&quot;</span><span class="p">,</span>
</span><span id="L-293"><a href="#L-293"><span class="linenos">293</span></a> <span class="s2">&quot;EVOLVEO&quot;</span><span class="p">,</span>
</span><span id="L-294"><a href="#L-294"><span class="linenos">294</span></a> <span class="s2">&quot;GL-SR2&quot;</span><span class="p">,</span>
</span><span id="L-295"><a href="#L-295"><span class="linenos">295</span></a> <span class="s2">&quot;Blaupunkt&quot;</span><span class="p">,</span>
</span><span id="L-296"><a href="#L-296"><span class="linenos">296</span></a> <span class="s2">&quot;KSIX RING&quot;</span><span class="p">,</span>
</span><span id="L-297"><a href="#L-297"><span class="linenos">297</span></a><span class="p">]</span>
</span><span id="L-298"><a href="#L-298"><span class="linenos">298</span></a>
</span><span id="L-299"><a href="#L-299"><span class="linenos">299</span></a>
</span><span id="L-300"><a href="#L-300"><span class="linenos">300</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">group</span><span class="p">()</span>
</span><span id="L-301"><a href="#L-301"><span class="linenos">301</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">util</span><span class="p">():</span>
</span><span id="L-302"><a href="#L-302"><span class="linenos">302</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;Generic utilities for the R02 that don&#39;t need an address.&quot;&quot;&quot;</span>
</span><span id="L-303"><a href="#L-303"><span class="linenos">303</span></a>
</span><span id="L-304"><a href="#L-304"><span class="linenos">304</span></a>
</span><span id="L-305"><a href="#L-305"><span class="linenos">305</span></a><span class="nd">@util</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
</span><span id="L-306"><a href="#L-306"><span class="linenos">306</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span><span class="s2">&quot;--all&quot;</span><span class="p">,</span> <span class="n">is_flag</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Print all devices, no name filtering&quot;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span><span id="L-307"><a href="#L-307"><span class="linenos">307</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">scan</span><span class="p">(</span><span class="nb">all</span><span class="p">:</span> <span class="nb">bool</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-308"><a href="#L-308"><span class="linenos">308</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;Scan for possible devices based on known prefixes and print the bluetooth address.&quot;&quot;&quot;</span>
</span><span id="L-309"><a href="#L-309"><span class="linenos">309</span></a>
</span><span id="L-310"><a href="#L-310"><span class="linenos">310</span></a> <span class="c1"># TODO maybe bluetooth specific stuff like this should be in another package?</span>
</span><span id="L-311"><a href="#L-311"><span class="linenos">311</span></a> <span class="n">devices</span> <span class="o">=</span> <span class="k">await</span> <span class="n">BleakScanner</span><span class="o">.</span><span class="n">discover</span><span class="p">()</span>
</span><span id="L-312"><a href="#L-312"><span class="linenos">312</span></a>
</span><span id="L-313"><a href="#L-313"><span class="linenos">313</span></a> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">devices</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
</span><span id="L-314"><a href="#L-314"><span class="linenos">314</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;Found device(s)&quot;</span><span class="p">)</span>
</span><span id="L-315"><a href="#L-315"><span class="linenos">315</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="s1">&#39;Name&#39;</span><span class="si">:</span><span class="s2">&gt;20</span><span class="si">}</span><span class="s2"> | Address&quot;</span><span class="p">)</span>
</span><span id="L-316"><a href="#L-316"><span class="linenos">316</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;-&quot;</span> <span class="o">*</span> <span class="mi">44</span><span class="p">)</span>
</span><span id="L-317"><a href="#L-317"><span class="linenos">317</span></a> <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">devices</span><span class="p">:</span>
</span><span id="L-318"><a href="#L-318"><span class="linenos">318</span></a> <span class="n">name</span> <span class="o">=</span> <span class="n">d</span><span class="o">.</span><span class="n">name</span>
</span><span id="L-319"><a href="#L-319"><span class="linenos">319</span></a> <span class="k">if</span> <span class="n">name</span> <span class="ow">and</span> <span class="p">(</span><span class="nb">all</span> <span class="ow">or</span> <span class="nb">any</span><span class="p">(</span><span class="n">name</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">DEVICE_NAME_PREFIXES</span> <span class="k">if</span> <span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="n">p</span><span class="p">))):</span>
</span><span id="L-320"><a href="#L-320"><span class="linenos">320</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">name</span><span class="si">:</span><span class="s2">&gt;20</span><span class="si">}</span><span class="s2"> | </span><span class="si">{</span><span class="n">d</span><span class="o">.</span><span class="n">address</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
</span><span id="L-321"><a href="#L-321"><span class="linenos">321</span></a> <span class="k">else</span><span class="p">:</span>
</span><span id="L-322"><a href="#L-322"><span class="linenos">322</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;No devices found. Try moving the ring closer to computer&quot;</span><span class="p">)</span>
</span><span id="L-277"><a href="#L-277"><span class="linenos">277</span></a>
</span><span id="L-278"><a href="#L-278"><span class="linenos">278</span></a><span class="n">DEVICE_NAME_PREFIXES</span> <span class="o">=</span> <span class="p">[</span>
</span><span id="L-279"><a href="#L-279"><span class="linenos">279</span></a> <span class="s2">&quot;R01&quot;</span><span class="p">,</span>
</span><span id="L-280"><a href="#L-280"><span class="linenos">280</span></a> <span class="s2">&quot;R02&quot;</span><span class="p">,</span>
</span><span id="L-281"><a href="#L-281"><span class="linenos">281</span></a> <span class="s2">&quot;R03&quot;</span><span class="p">,</span>
</span><span id="L-282"><a href="#L-282"><span class="linenos">282</span></a> <span class="s2">&quot;R04&quot;</span><span class="p">,</span>
</span><span id="L-283"><a href="#L-283"><span class="linenos">283</span></a> <span class="s2">&quot;R05&quot;</span><span class="p">,</span>
</span><span id="L-284"><a href="#L-284"><span class="linenos">284</span></a> <span class="s2">&quot;R06&quot;</span><span class="p">,</span>
</span><span id="L-285"><a href="#L-285"><span class="linenos">285</span></a> <span class="s2">&quot;R07&quot;</span><span class="p">,</span>
</span><span id="L-286"><a href="#L-286"><span class="linenos">286</span></a> <span class="s2">&quot;R10&quot;</span><span class="p">,</span>
</span><span id="L-287"><a href="#L-287"><span class="linenos">287</span></a> <span class="s2">&quot;COLMI&quot;</span><span class="p">,</span>
</span><span id="L-288"><a href="#L-288"><span class="linenos">288</span></a> <span class="s2">&quot;VK-5098&quot;</span><span class="p">,</span>
</span><span id="L-289"><a href="#L-289"><span class="linenos">289</span></a> <span class="s2">&quot;MERLIN&quot;</span><span class="p">,</span>
</span><span id="L-290"><a href="#L-290"><span class="linenos">290</span></a> <span class="s2">&quot;Hello Ring&quot;</span><span class="p">,</span>
</span><span id="L-291"><a href="#L-291"><span class="linenos">291</span></a> <span class="s2">&quot;RING1&quot;</span><span class="p">,</span>
</span><span id="L-292"><a href="#L-292"><span class="linenos">292</span></a> <span class="s2">&quot;boAtring&quot;</span><span class="p">,</span>
</span><span id="L-293"><a href="#L-293"><span class="linenos">293</span></a> <span class="s2">&quot;TR-R02&quot;</span><span class="p">,</span>
</span><span id="L-294"><a href="#L-294"><span class="linenos">294</span></a> <span class="s2">&quot;SE&quot;</span><span class="p">,</span>
</span><span id="L-295"><a href="#L-295"><span class="linenos">295</span></a> <span class="s2">&quot;EVOLVEO&quot;</span><span class="p">,</span>
</span><span id="L-296"><a href="#L-296"><span class="linenos">296</span></a> <span class="s2">&quot;GL-SR2&quot;</span><span class="p">,</span>
</span><span id="L-297"><a href="#L-297"><span class="linenos">297</span></a> <span class="s2">&quot;Blaupunkt&quot;</span><span class="p">,</span>
</span><span id="L-298"><a href="#L-298"><span class="linenos">298</span></a> <span class="s2">&quot;KSIX RING&quot;</span><span class="p">,</span>
</span><span id="L-299"><a href="#L-299"><span class="linenos">299</span></a><span class="p">]</span>
</span><span id="L-300"><a href="#L-300"><span class="linenos">300</span></a>
</span><span id="L-301"><a href="#L-301"><span class="linenos">301</span></a>
</span><span id="L-302"><a href="#L-302"><span class="linenos">302</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">group</span><span class="p">()</span>
</span><span id="L-303"><a href="#L-303"><span class="linenos">303</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">util</span><span class="p">():</span>
</span><span id="L-304"><a href="#L-304"><span class="linenos">304</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;Generic utilities for the R02 that don&#39;t need an address.&quot;&quot;&quot;</span>
</span><span id="L-305"><a href="#L-305"><span class="linenos">305</span></a>
</span><span id="L-306"><a href="#L-306"><span class="linenos">306</span></a>
</span><span id="L-307"><a href="#L-307"><span class="linenos">307</span></a><span class="nd">@util</span><span class="o">.</span><span class="n">command</span><span class="p">()</span>
</span><span id="L-308"><a href="#L-308"><span class="linenos">308</span></a><span class="nd">@click</span><span class="o">.</span><span class="n">option</span><span class="p">(</span><span class="s2">&quot;--all&quot;</span><span class="p">,</span> <span class="n">is_flag</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&quot;Print all devices, no name filtering&quot;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span><span id="L-309"><a href="#L-309"><span class="linenos">309</span></a><span class="k">async</span> <span class="k">def</span> <span class="nf">scan</span><span class="p">(</span><span class="nb">all</span><span class="p">:</span> <span class="nb">bool</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span><span id="L-310"><a href="#L-310"><span class="linenos">310</span></a><span class="w"> </span><span class="sd">&quot;&quot;&quot;Scan for possible devices based on known prefixes and print the bluetooth address.&quot;&quot;&quot;</span>
</span><span id="L-311"><a href="#L-311"><span class="linenos">311</span></a>
</span><span id="L-312"><a href="#L-312"><span class="linenos">312</span></a> <span class="c1"># TODO maybe bluetooth specific stuff like this should be in another package?</span>
</span><span id="L-313"><a href="#L-313"><span class="linenos">313</span></a> <span class="n">devices</span> <span class="o">=</span> <span class="k">await</span> <span class="n">BleakScanner</span><span class="o">.</span><span class="n">discover</span><span class="p">()</span>
</span><span id="L-314"><a href="#L-314"><span class="linenos">314</span></a>
</span><span id="L-315"><a href="#L-315"><span class="linenos">315</span></a> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">devices</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
</span><span id="L-316"><a href="#L-316"><span class="linenos">316</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;Found device(s)&quot;</span><span class="p">)</span>
</span><span id="L-317"><a href="#L-317"><span class="linenos">317</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="s1">&#39;Name&#39;</span><span class="si">:</span><span class="s2">&gt;20</span><span class="si">}</span><span class="s2"> | Address&quot;</span><span class="p">)</span>
</span><span id="L-318"><a href="#L-318"><span class="linenos">318</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;-&quot;</span> <span class="o">*</span> <span class="mi">44</span><span class="p">)</span>
</span><span id="L-319"><a href="#L-319"><span class="linenos">319</span></a> <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">devices</span><span class="p">:</span>
</span><span id="L-320"><a href="#L-320"><span class="linenos">320</span></a> <span class="n">name</span> <span class="o">=</span> <span class="n">d</span><span class="o">.</span><span class="n">name</span>
</span><span id="L-321"><a href="#L-321"><span class="linenos">321</span></a> <span class="k">if</span> <span class="n">name</span> <span class="ow">and</span> <span class="p">(</span><span class="nb">all</span> <span class="ow">or</span> <span class="nb">any</span><span class="p">(</span><span class="n">name</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">DEVICE_NAME_PREFIXES</span> <span class="k">if</span> <span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="n">p</span><span class="p">))):</span>
</span><span id="L-322"><a href="#L-322"><span class="linenos">322</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">name</span><span class="si">:</span><span class="s2">&gt;20</span><span class="si">}</span><span class="s2"> | </span><span class="si">{</span><span class="n">d</span><span class="o">.</span><span class="n">address</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
</span><span id="L-323"><a href="#L-323"><span class="linenos">323</span></a> <span class="k">else</span><span class="p">:</span>
</span><span id="L-324"><a href="#L-324"><span class="linenos">324</span></a> <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="s2">&quot;No devices found. Try moving the ring closer to computer&quot;</span><span class="p">)</span>
</span></pre></div>
@ -401,7 +403,7 @@
<div class="attr variable">
<span class="name">DEVICE_NAME_PREFIXES</span> =
<input id="DEVICE_NAME_PREFIXES-view-value" class="view-value-toggle-state" type="checkbox" aria-hidden="true" tabindex="-1">
<label class="view-value-button pdoc-button" for="DEVICE_NAME_PREFIXES-view-value"></label><span class="default_value">[&#39;R01&#39;, &#39;R02&#39;, &#39;R03&#39;, &#39;R04&#39;, &#39;R05&#39;, &#39;R06&#39;, &#39;R07&#39;, &#39;R10&#39;, &#39;VK-5098&#39;, &#39;MERLIN&#39;, &#39;Hello Ring&#39;, &#39;RING1&#39;, &#39;boAtring&#39;, &#39;TR-R02&#39;, &#39;SE&#39;, &#39;EVOLVEO&#39;, &#39;GL-SR2&#39;, &#39;Blaupunkt&#39;, &#39;KSIX RING&#39;]</span>
<label class="view-value-button pdoc-button" for="DEVICE_NAME_PREFIXES-view-value"></label><span class="default_value">[&#39;R01&#39;, &#39;R02&#39;, &#39;R03&#39;, &#39;R04&#39;, &#39;R05&#39;, &#39;R06&#39;, &#39;R07&#39;, &#39;R10&#39;, &#39;COLMI&#39;, &#39;VK-5098&#39;, &#39;MERLIN&#39;, &#39;Hello Ring&#39;, &#39;RING1&#39;, &#39;boAtring&#39;, &#39;TR-R02&#39;, &#39;SE&#39;, &#39;EVOLVEO&#39;, &#39;GL-SR2&#39;, &#39;Blaupunkt&#39;, &#39;KSIX RING&#39;]</span>
</div>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

44
tests/test_real_time.py Normal file
View File

@ -0,0 +1,44 @@
import pytest
from colmi_r02_client import real_time
@pytest.mark.parametrize("reading_type", real_time.RealTimeReading)
def test_real_time_generation(reading_type: real_time.RealTimeReading):
result = real_time.get_start_packet(reading_type)
assert result[0] == real_time.CMD_START_REAL_TIME
assert result[1] == reading_type
assert result[2] == real_time.Action.START
assert result[-1] == real_time.CMD_START_REAL_TIME + real_time.Action.START + reading_type
result = real_time.get_continue_packet(reading_type)
assert result[0] == real_time.CMD_START_REAL_TIME
assert result[1] == reading_type
assert result[2] == real_time.Action.CONTINUE
assert result[-1] == real_time.CMD_START_REAL_TIME + real_time.Action.CONTINUE + reading_type
result = real_time.get_stop_packet(reading_type)
assert result[0] == real_time.CMD_STOP_REAL_TIME
assert result[1] == reading_type
assert result[2] == result[3] == 0
assert result[-1] == real_time.CMD_STOP_REAL_TIME + reading_type
def test_parse_real_time_reading_success():
input = bytearray(b"i\x01\x00N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb8")
expected = real_time.Reading(real_time.RealTimeReading.HEART_RATE, 78)
result = real_time.parse_real_time_reading(input)
assert result == expected
def test_parse_real_time_reading_fail():
input = bytearray(b"i\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00k")
expected = real_time.ReadingError(real_time.RealTimeReading.HEART_RATE, 1)
result = real_time.parse_real_time_reading(input)
assert result == expected

View File

@ -1,19 +0,0 @@
from colmi_r02_client.real_time_hr import parse_heart_rate, Reading, ReadingError
def test_parse_heart_rate_hr_success():
packet = bytearray(b"i\x01\x00N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb8")
expected = Reading(1, 78)
result = parse_heart_rate(packet)
assert result == expected
def test_parse_heart_rate_hr_fail():
packet = bytearray(b"i\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00k")
expected = ReadingError(1, 1)
result = parse_heart_rate(packet)
assert result == expected