Compare commits
4 Commits
1fe1182ba3
...
f55f7a039d
Author | SHA1 | Date | |
---|---|---|---|
f55f7a039d | |||
cd243353a9 | |||
562497c2df | |||
c40aa38862 |
21
README.md
21
README.md
@ -7,3 +7,24 @@ Perhaps useful if you have a stationary bike of some kind and want to
|
|||||||
put the vital statistics on a big screen instead of on your handlebar.
|
put the vital statistics on a big screen instead of on your handlebar.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
First you need to identify bluetooth address of your bike sensor(s).
|
||||||
|
Spin the wheel to wake it up, then run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ bluetoothctl scan on
|
||||||
|
Discovery started
|
||||||
|
[CHG] Controller 24:FD:52:00:ED:F1 Discovering: yes
|
||||||
|
[NEW] Device C2:35:5D:F2:F4:0F Giant Combo
|
||||||
|
[CHG] Device 98:06:3A:15:B7:BA RSSI: -82
|
||||||
|
^C
|
||||||
|
$ bluetoothctl connect C2:35:5D:F2:F4:0F
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can start the app
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ python wobble.py C2:35:5D:F2:F4:0F
|
||||||
|
```
|
@ -1,11 +1,12 @@
|
|||||||
import BLE_GATT
|
import BLE_GATT
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
|
|
||||||
import pdb
|
import pdb
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
@ -13,8 +14,6 @@ import pygame.freetype
|
|||||||
from pygame.locals import *
|
from pygame.locals import *
|
||||||
|
|
||||||
class CadenceListener:
|
class CadenceListener:
|
||||||
csc_feature = '00002a5c-0000-1000-8000-00805F9B34FB'
|
|
||||||
|
|
||||||
prevCtime = 0
|
prevCtime = 0
|
||||||
prevWtime = 0
|
prevWtime = 0
|
||||||
prevWrevs = 0
|
prevWrevs = 0
|
||||||
@ -22,7 +21,8 @@ class CadenceListener:
|
|||||||
wheelSpeed = 0
|
wheelSpeed = 0
|
||||||
crankSpeed = 0
|
crankSpeed = 0
|
||||||
crankSpeedMax = 120
|
crankSpeedMax = 120
|
||||||
startTime = None
|
elapsedTime = None
|
||||||
|
firstMessage = True
|
||||||
|
|
||||||
def getWheelSpeed(self):
|
def getWheelSpeed(self):
|
||||||
return self.wheelSpeed
|
return self.wheelSpeed
|
||||||
@ -35,13 +35,13 @@ class CadenceListener:
|
|||||||
offset = offset + 6
|
offset = offset + 6
|
||||||
if flags & 2:
|
if flags & 2:
|
||||||
crankRevolutions,crankTime = struct.unpack('<HH', bytes(value)[offset:offset+4])
|
crankRevolutions,crankTime = struct.unpack('<HH', bytes(value)[offset:offset+4])
|
||||||
# print(wheelrevs, wheeltime, crankrevs, cranktime)
|
|
||||||
if(not self.startTime):
|
if(self.firstMessage):
|
||||||
self.prevCtime = crankTime
|
self.prevCtime = crankTime
|
||||||
self.prevWtime = wheelTime
|
self.prevWtime = wheelTime
|
||||||
self.prevCrevs = crankRevolutions
|
self.prevCrevs = crankRevolutions
|
||||||
self.prevWrevs = wheelRevolutions
|
self.prevWrevs = wheelRevolutions
|
||||||
self.startTime = datetime.now()
|
self.firstMessage = False
|
||||||
return
|
return
|
||||||
|
|
||||||
# handle wraparound
|
# handle wraparound
|
||||||
@ -62,16 +62,17 @@ class CadenceListener:
|
|||||||
self.prevCrevs = crankRevolutions
|
self.prevCrevs = crankRevolutions
|
||||||
|
|
||||||
if(wheelTime > self.prevWtime):
|
if(wheelTime > self.prevWtime):
|
||||||
revs = ((wheelRevolutions - self.prevWrevs) /
|
millis = float(wheelTime - self.prevWtime)
|
||||||
float(wheelTime - self.prevWtime))
|
self.rolling = True
|
||||||
|
revs = (wheelRevolutions - self.prevWrevs) / millis
|
||||||
|
|
||||||
self.wheelSpeed = revs * 2205 * 3600 / 1024.0
|
self.wheelSpeed = revs * 2205 * 3600 / 1024.0
|
||||||
print("wheel",
|
print("wheel", millis, self.wheelSpeed)
|
||||||
(float(wheelTime - self.prevWtime)),
|
|
||||||
self.wheelSpeed)
|
|
||||||
self.prevWtime = wheelTime
|
self.prevWtime = wheelTime
|
||||||
self.prevWrevs = wheelRevolutions
|
self.prevWrevs = wheelRevolutions
|
||||||
|
self.elapsedTime = (self.elapsedTime or 0) + (millis / 1024.0)
|
||||||
|
|
||||||
class Biscuit:
|
class Wobble:
|
||||||
listener = CadenceListener()
|
listener = CadenceListener()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -95,8 +96,8 @@ class Biscuit:
|
|||||||
(scale/16,scale + 20),
|
(scale/16,scale + 20),
|
||||||
"{: 5.1f} rpm".format(self.listener.crankSpeed),
|
"{: 5.1f} rpm".format(self.listener.crankSpeed),
|
||||||
(0,255,0))
|
(0,255,0))
|
||||||
if self.listener.startTime != None:
|
if self.listener.elapsedTime != None:
|
||||||
runTime = datetime.now() - self.listener.startTime
|
runTime = timedelta(seconds=self.listener.elapsedTime)
|
||||||
timelabel = str(runTime)[0:9]
|
timelabel = str(runTime)[0:9]
|
||||||
else:
|
else:
|
||||||
timelabel = datetime.now().strftime("%H:%M:%S")
|
timelabel = datetime.now().strftime("%H:%M:%S")
|
||||||
@ -111,41 +112,43 @@ class Biscuit:
|
|||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
|
|
||||||
|
|
||||||
|
bike = BLE_GATT.Central(sys.argv[1])
|
||||||
bike_address = 'C2:35:5D:F2:F4:0F'
|
|
||||||
bike = BLE_GATT.Central(bike_address)
|
|
||||||
bike.connect()
|
bike.connect()
|
||||||
app = Biscuit()
|
app = Wobble()
|
||||||
|
|
||||||
csc_measurement = '00002a5b-0000-1000-8000-00805F9B34FB'
|
csc_measurement = '00002a5b-0000-1000-8000-00805F9B34FB'
|
||||||
bike.on_value_change(csc_measurement, app.listener.process)
|
bike.on_value_change(csc_measurement, app.listener.process)
|
||||||
|
|
||||||
running = True
|
running = True
|
||||||
|
|
||||||
|
def handle_pygame_event(event):
|
||||||
|
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
|
||||||
|
|
||||||
|
def on_timer():
|
||||||
|
handle_pygame_event(pygame.event.poll())
|
||||||
|
app.on_update()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
GLib.timeout_add(100, on_timer)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Time to go live
|
# Time to go live
|
||||||
context = bike.mainloop.get_context()
|
|
||||||
|
|
||||||
print("Listening for events...")
|
print("Listening for events...")
|
||||||
while running:
|
bike.mainloop.run()
|
||||||
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:
|
finally:
|
||||||
pygame.quit()
|
pygame.quit()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user