commit 05249b037c43094455a3dbd4cf95a21fff855da3
Author: Daniel Barlow <dan@telent.net>
Date: Sun, 11 Dec 2022 00:18:48 +0000
initial checkin, based quite closely on the ANT version
https://github.com/telent/python-ant/blob/biscuit/demos/ant.core/cadence.py
Diffstat:
| A | csc.py | | | 151 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | shell.nix | | | 49 | +++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 200 insertions(+), 0 deletions(-)
diff --git a/csc.py b/csc.py
@@ -0,0 +1,151 @@
+import BLE_GATT
+from gi.repository import GLib
+import pdb
+import struct
+
+import sys
+import time
+from datetime import datetime
+import os
+
+import pygame
+import pygame.freetype
+from pygame.locals import *
+
+class CadenceListener:
+ csc_feature = '00002a5c-0000-1000-8000-00805F9B34FB'
+
+ prevCtime = 0
+ prevWtime = 0
+ prevWrevs = 0
+ prevCrevs = 0
+ wheelSpeed = 0
+ crankSpeed = 0
+ crankSpeedMax = 120
+ startTime = None
+
+ def getWheelSpeed(self):
+ return self.wheelSpeed
+
+ def process(self, value):
+ flags = value[0]
+ offset = 1
+ if flags & 1:
+ wheelRevolutions,wheelTime = struct.unpack('<IH', bytes(value)[offset:offset+6])
+ offset = offset + 6
+ if flags & 2:
+ crankRevolutions,crankTime = struct.unpack('<HH', bytes(value)[offset:offset+4])
+ # print(wheelrevs, wheeltime, crankrevs, cranktime)
+ if(not self.startTime):
+ self.prevCtime = crankTime
+ self.prevWtime = wheelTime
+ self.prevCrevs = crankRevolutions
+ self.prevWrevs = wheelRevolutions
+ self.startTime = datetime.now()
+ return
+
+ # handle wraparound
+ if(crankTime < self.prevCtime):
+ self.prevCtime = self.prevCtime - 65536
+ if(wheelTime < self.prevWtime):
+ self.prevWtime = self.prevWtime - 65536
+ if(crankRevolutions < self.prevCrevs):
+ self.prevCrevs = self.prevCrevs - 65536
+ if(wheelRevolutions < self.prevWrevs):
+ self.prevWrevs = self.prevWrevs - 65536 * 65536
+
+ if(crankTime > self.prevCtime):
+ self.crankSpeed = 60 * 1024 * ((crankRevolutions - self.prevCrevs) /
+ float(crankTime - self.prevCtime))
+ print("crank", self.crankSpeed)
+ self.prevCtime = crankTime
+ self.prevCrevs = crankRevolutions
+
+ if(wheelTime > self.prevWtime):
+ revs = ((wheelRevolutions - self.prevWrevs) /
+ float(wheelTime - self.prevWtime))
+ self.wheelSpeed = revs * 2205 * 3600 / 1024.0
+ print("wheel",
+ (float(wheelTime - self.prevWtime)),
+ self.wheelSpeed)
+ self.prevWtime = wheelTime
+ self.prevWrevs = wheelRevolutions
+
+class Biscuit:
+ listener = CadenceListener()
+
+ def __init__(self):
+ pygame.init()
+ self.surface = pygame.display.set_mode((600,300), pygame.RESIZABLE)
+ self.width = self.surface.get_width();
+ self.height = self.surface.get_height();
+ self.font = None
+
+ def on_update(self):
+ scale = int(min(self.width/7, (self.height-20)/3))
+ if(self.font == None):
+ self.font = pygame.freetype.Font(os.environ['FONT'], scale)
+
+ self.surface.fill((10,20,10))
+ self.font.render_to(self.surface,
+ (scale/16,10),
+ "{: 5.1f} km/h".format(self.listener.wheelSpeed),
+ (0,255,0))
+ self.font.render_to(self.surface,
+ (scale/16,scale + 20),
+ "{: 5.1f} rpm".format(self.listener.crankSpeed),
+ (0,255,0))
+ if self.listener.startTime != None:
+ runTime = datetime.now() - self.listener.startTime
+ timelabel = str(runTime)[0:9]
+ else:
+ timelabel = datetime.now().strftime("%H:%M:%S")
+ self.font.render_to(self.surface,
+ (scale, scale * 2 + 30),
+ timelabel,
+ (0,255,0))
+ pix = self.width *((self.listener.crankSpeed-40)/ self.listener.crankSpeedMax)
+ pix = max(pix, 0)
+ pygame.draw.rect(self.surface, (180,0,0), pygame.Rect(0,self.height/2-60, pix, 50))
+ pygame.draw.rect(self.surface, (0,0,180), pygame.Rect(pix,self.height/2-60, 20, 50))
+ pygame.display.flip()
+
+
+
+bike_address = 'C2:35:5D:F2:F4:0F'
+bike = BLE_GATT.Central(bike_address)
+bike.connect()
+app = Biscuit()
+
+csc_measurement = '00002a5b-0000-1000-8000-00805F9B34FB'
+bike.on_value_change(csc_measurement, app.listener.process)
+
+running = True
+try:
+ # Time to go live
+ context = bike.mainloop.get_context()
+
+ print("Listening for events...")
+ while running:
+ time.sleep(0.1)
+ context.iteration(False)
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ running = False
+ if event.type == pygame.KEYUP and event.key == pygame.K_q:
+ running = False
+ if event.type == pygame.KEYUP and event.key == pygame.K_SPACE:
+ if app.listener.startTime == None:
+ app.listener.startTime = datetime.now()
+ else:
+ app.listener.startTime = None
+ if event.type == pygame.VIDEORESIZE:
+ app.width = event.w
+ app.height = event.h
+ app.font = None
+
+ app.on_update()
+
+finally:
+ pygame.quit()
+
diff --git a/shell.nix b/shell.nix
@@ -0,0 +1,49 @@
+with import <nixpkgs> {};
+let
+ inherit (pkgs.python3.pkgs) pydbus ruamel-yaml buildPythonPackage fetchPypi;
+ vext = buildPythonPackage rec {
+ propagatedBuildInputs = [ ruamel-yaml ];
+ pname = "vext";
+ version = "0.7.4";
+ src = fetchPypi {
+ inherit pname version;
+ hash = "sha256-0Yiw0hQXnNYjS0jDrHCPnLd5NznSp7t9HLA/O/Wc92c=";
+ };
+ };
+ vext_gi = buildPythonPackage rec {
+ propagatedBuildInputs = [ vext ];
+ pname = "vext.gi";
+ version = "0.7.4";
+ src = fetchPypi {
+ inherit pname version;
+ hash = "sha256-aKVWiMbN3JE90178aIITJHSXhW6FrykEUP70R53/PIE=";
+ };
+ };
+ ble_gatt = buildPythonPackage rec {
+ pname = "BLE_GATT";
+ version = "0.5.0";
+ propagatedBuildInputs = [
+ pydbus
+ # vext_gi
+ ];
+ prePatch = ''
+ sed -i -e '/import vext/d' BLE_GATT/__init__.py
+ sed -i -e "s/'vext.gi'//g" setup.py
+ '';
+ src = fetchPypi {
+ inherit pname version;
+ hash = "sha256-5qJ0FoopVqQH7JHBriD/D0VE24vM+UtoPJWTm1VmY0w=";
+ };
+ doCheck = false;
+ };
+ python = python3.withPackages(ps: with ps;
+ [
+ setuptools pydbus pyserial pyusb msgpack six future pygame
+ ble_gatt
+ ]);
+in stdenv.mkDerivation {
+ name = "wobble";
+# PYTHONPATH = "./src";
+ buildInputs = [ python dejavu_fonts ];
+ FONT = "${dejavu_fonts}/share/fonts/truetype/DejaVuSansMono-Bold.ttf";
+}