rushabhcodes/fidget-counter

A tscircuit package created by rushabhcodes.

Version
1.0.31
License
unset
Stars
0

firmware/tools/client.py

"""Pilot BLE reader/reset utility. Install bleak==0.22.3 on the computer."""
import argparse, asyncio, json, struct
from bleak import BleakClient, BleakScanner

SERVICE = "76b8a000-5aa4-4c10-b5df-0da684303f21"
STATE = "76b8a001-5aa4-4c10-b5df-0da684303f21"
CONTROL = "76b8a002-5aa4-4c10-b5df-0da684303f21"
def decode(payload):
    if len(payload) != 20: raise ValueError("Expected20-byte state")
    version,flags,angle,a,b,total,sequence = struct.unpack("<BBHIIII",payload)
    if version != 1: raise ValueError("Unsupported protocol version")
    return {"total":total,"forward":a,"reverse":b,"angle_degrees":angle/16,
            "sensor_fault":bool(flags&1),"storage_fault":bool(flags&2),
            "unsaved":bool(flags&4),"sample_gap":bool(flags&8),
            "saturated":bool(flags&16),"sequence":sequence}
async def run(args):
    device = args.address or await BleakScanner.find_device_by_filter(
        lambda d,a: SERVICE in [u.lower() for u in (a.service_uuids or [])],timeout=20)
    if not device: raise RuntimeError("No Fidget Counter found")
    async with BleakClient(device) as client:
        if args.reset or args.save:
            await client.write_gatt_char(CONTROL,b"ZERO" if args.reset else b"SAVE",response=True)
            for _ in range(30):
                state = decode(await client.read_gatt_char(STATE))
                if not state["unsaved"] and (not args.reset or state["total"] == 0):
                    if state["storage_fault"]: raise RuntimeError("Count storage fault")
                    print(json.dumps(state)); break
                await asyncio.sleep(.1)
            else: raise RuntimeError("Reset/save not confirmed; inspect device status")
        else:
            print(json.dumps(decode(await client.read_gatt_char(STATE))))
            await client.start_notify(STATE,lambda _,data:print(json.dumps(decode(data))))
            await asyncio.sleep(args.seconds)
            await client.stop_notify(STATE)
def main():
    p = argparse.ArgumentParser(description=__doc__)
    p.add_argument("--address",help="OS-specific BLE address/identifier; select explicitly if multiple units")
    group = p.add_mutually_exclusive_group()
    group.add_argument("--reset",action="store_true",help="Clear all saved/live counts")
    group.add_argument("--save",action="store_true",help="Checkpoint counts before removing the cell")
    p.add_argument("--seconds",type=float,default=60)
    asyncio.run(run(p.parse_args()))
if __name__ == "__main__": main()