wasp: apps: Step counter application

Currently there's no fancy algorithms to estimate stride length. Just
pure simple step counting directly from the hardware's "intelligence
engine".

Signed-off-by: Daniel Thompson <daniel@redfelineninja.org.uk>
This commit is contained in:
Daniel Thompson 2020-06-09 21:29:00 +01:00
parent dea2ba8d65
commit ccaf12750b
10 changed files with 170 additions and 5 deletions

3
.gitmodules vendored
View File

@ -25,3 +25,6 @@
[submodule "reloader"]
path = reloader
url = https://github.com/daniel-thompson/wasp-reloader
[submodule "wasp/modules/bma42x-upy"]
path = wasp/modules/bma42x-upy
url = https://github.com/daniel-thompson/bma42x-upy

View File

@ -16,10 +16,6 @@ submodules :
bootloader:
$(MAKE) -C bootloader/ BOARD=$(BOARD)_nrf52832 all genhex
python3 -m nordicsemi dfu genpkg \
--bootloader bootloader/_build-$(BOARD)_nrf52832/$(BOARD)_nrf52832_bootloader-*-nosd.hex \
--softdevice bootloader/lib/softdevice/s132_nrf52_6.1.1/s132_nrf52_6.1.1_softdevice.hex \
bootloader.zip
python3 tools/hexmerge.py \
bootloader/_build-$(BOARD)_nrf52832/$(BOARD)_nrf52832_bootloader-*-nosd.hex \
bootloader/lib/softdevice/s132_nrf52_6.1.1/s132_nrf52_6.1.1_softdevice.hex \
@ -40,7 +36,8 @@ micropython: wasp/boards/pinetime/watch.py
$(MAKE) -C micropython/ports/nrf \
BOARD=$(BOARD) SD=s132 \
MICROPY_VFS_LFS2=1 \
FROZEN_MANIFEST=$(PWD)/wasp/boards/$(BOARD)/manifest.py
FROZEN_MANIFEST=$(PWD)/wasp/boards/$(BOARD)/manifest.py \
USER_C_MODULES=$(PWD)/wasp/modules
python3 -m nordicsemi dfu genpkg \
--dev-type 0x0052 \
--application micropython/ports/nrf/build-$(BOARD)-s132/firmware.hex \

BIN
res/feet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

82
wasp/apps/steps.py Normal file
View File

@ -0,0 +1,82 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
import wasp
import fonts
import icons
import time
import watch
# 2-bit RLE, generated from res/feet.png, 240 bytes
feet = (
b'\x02'
b'00'
b'\x13\xc1-\xc4+\xc6*\xc6*\xc6&\xc3\x01\xc6\t\xc2'
b'\x1b\xc3\x02\xc5\x08\xc4\x1a\xc4\x01\xc5\x08\xc5\x19\xc4\x02\xc3'
b'\x08\xc6\x17\xc1\x02\xc3\x02\xc3\x08\xc6\x16\xc3\x02\xc1\x0e\xc6'
b'\x01\xc3\x12\xc3\x11\xc6\x01\xc3\x13\xc2\x05\xc2\n\xc5\x02\xc3'
b'\x10\xc2\x01\xc2\x02\xc6\n\xc4\x01\xc4\x10\xc2\x04\xc7\x0b\xc1'
b'\x03\xc3\x11\xc3\x02\xc8\x10\xc2\x01\xc3\r\xc2\x02\xc9\x13\xc3'
b'\x0b\xc1\x05\xc9\x0c\xc2\x05\xc3\x0b\xc2\x03\xc9\x0c\xc5\x03\xc2'
b'\x0c\xc2\x02\xca\x0c\xc6\x05\xc2\t\xc2\x02\xca\x0c\xc7\x03\xc3'
b'\r\xca\x0c\xc8\x02\xc3\x0c\xca\r\xc9\x02\xc1\r\xca\r\xc9'
b'\x04\xc2\n\xca\x0e\xc9\x02\xc3\n\xca\x0e\xc9\x02\xc2\x0b\xca'
b'\x0e\xca\x0e\xca\x0e\xca\x0f\xc9\x0e\xca\x0f\xca\r\xca\x0f\xca'
b'\r\xca\x10\xcb\x0b\xca\x10\xcc\n\xca\x10\xcd\t\xca\x11\xcc'
b'\x08\xca\x12\xcc\x07\xcb\x13\xcb\x06\xcb\x14\xcb\x05\xcc\x15\xca'
b'\x04\xcc\x16\xc9\x05\xcc\x17\xc7\x05\xcd\x17\xc7\x05\xcc\x1a\xc4'
b"\x07\xcb%\xca&\xca'\xc8)\xc6+\xc4\x0e"
)
class StepCounterApp():
NAME = 'Steps'
ICON = icons.app
def __init__(self):
watch.accel.reset()
self._meter = wasp.widgets.BatteryMeter()
self._count = 0
def foreground(self):
"""Activate the application."""
self._last_clock = ( -1, -1, -1, -1, -1, -1 )
self._draw()
wasp.system.request_tick(1000)
def tick(self, ticks):
self._count += 686;
self._update()
def _draw(self):
"""Draw the display from scratch."""
draw = wasp.watch.drawable
draw.fill()
draw.blit(feet, 12, 132-24)
self._last_count = -1
self._update()
self._meter.draw()
def _update(self):
draw = wasp.watch.drawable
# Lazy update of the clock and battery meter
now = wasp.watch.rtc.get_localtime()
if now[4] != self._last_clock[4]:
t1 = '{:02}:{:02}'.format(now[3], now[4])
draw.set_font(fonts.sans24)
draw.string(t1, 48, 16, 240-96)
self._last_clock = now
self._meter.update()
if now[2] != self._last_clock[2]:
watch.accel.steps = 0
draw.fill(60, 132-18, 180, 36)
count = watch.accel.steps
t = str(count)
w = fonts.width(fonts.sans36, t)
draw.set_font(fonts.sans36)
draw.string(t, 228-w, 132-18)

View File

@ -9,10 +9,12 @@ freeze('../..',
'apps/launcher.py',
'apps/pager.py',
'apps/settings.py',
'apps/steps.py',
'apps/stopwatch.py',
'apps/testapp.py',
'boot.py',
'draw565.py',
'drivers/bma421.py',
'drivers/battery.py',
'drivers/cst816s.py',
'drivers/nrf_rtc.py',

View File

@ -18,6 +18,7 @@ from machine import Pin
from machine import SPI
from drivers.battery import Battery
from drivers.bma421 import BMA421
from drivers.cst816s import CST816S
from drivers.signal import Signal
from drivers.st7789 import ST7789_SPI
@ -65,6 +66,7 @@ battery = Battery(
Signal(Pin('USB_PWR', Pin.IN), invert=True))
button = Pin('BUTTON', Pin.IN)
i2c = I2C(1, scl='I2C_SCL', sda='I2C_SDA')
accel = BMA421(i2c)
touch = CST816S(i2c)
vibrator = Vibrator(Pin('MOTOR', Pin.OUT, value=0), active_low=True)

View File

@ -22,6 +22,28 @@ from drivers.cst816s import CST816S
from drivers.st7789 import ST7789_SPI
from drivers.vibrator import Vibrator
class Accelerometer:
"""Simulated accelerometer.
Accelerometers such as BMA421 are complex and most of the driver
is written in C. For that reason we simulate the accelerometer
rather than emulate (by comparison we emulate the ST7789).
"""
def reset(self):
self._steps = 3
@property
def steps(self):
"""Report the number of steps counted."""
if self._steps < 10000:
self._steps = int(self._steps * 1.34)
else:
self._steps += 1
return self._steps
@steps.setter
def steps(self, value):
self.reset()
class Backlight(object):
def __init__(self, level=1):
@ -105,6 +127,7 @@ display = ST7789_SPI(240, 240, spi,
res=Pin("DISP_RST", Pin.OUT, quiet=True))
drawable = draw565.Draw565(display)
accel = Accelerometer()
battery = Battery()
button = Pin('BUTTON', Pin.IN, quiet=True)
rtc = RTC()

53
wasp/drivers/bma421.py Normal file
View File

@ -0,0 +1,53 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
"""Bosch BMA421 accelerometer driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
import bma42x
import time
class BMA421:
"""BMA421 driver
.. automethod:: __init__
"""
def __init__(self, i2c):
"""Configure the driver.
:param machine.I2C i2c: I2C bus used to access the sensor.
"""
self._dev = bma42x.BMA42X(i2c)
def reset(self):
"""Reset and reinitialize the sensor."""
dev = self._dev
# Init, reset, wait for reset, enable I2C watchdog
dev.init()
dev.set_command_register(0xb6)
time.sleep(0.20)
dev.set_reg(bma42x.NV_CONFIG_ADDR, 6);
# Configure the sensor for basic step counting
dev.write_config_file()
dev.set_accel_enable(True)
dev.set_accel_config(odr=bma42x.OUTPUT_DATA_RATE_100HZ,
range=bma42x.ACCEL_RANGE_2G,
bandwidth=bma42x.ACCEL_NORMAL_AVG4,
perf_mode=bma42x.CIC_AVG_MODE)
dev.feature_enable(bma42x.STEP_CNTR, True)
@property
def steps(self):
"""Report the number of steps counted."""
return self._dev.step_counter_output()
@steps.setter
def steps(self, value):
if value != 0:
raise ValueError()
# TODO: There is a more efficient way to reset the step counter
# but I haven't looked it up yet!
self.reset()

@ -0,0 +1 @@
Subproject commit 55c16a0d0407b1f15efe0d2118a4cca13102fe7e

View File

@ -25,6 +25,7 @@ from apps.flashlight import FlashlightApp
from apps.launcher import LauncherApp
from apps.pager import PagerApp, CrashApp
from apps.settings import SettingsApp
from apps.steps import StepCounterApp
from apps.stopwatch import StopwatchApp
from apps.testapp import TestApp
@ -108,6 +109,7 @@ class Manager():
# TODO: Eventually these should move to main.py
self.register(ClockApp(), True)
self.register(StopwatchApp(), True)
self.register(StepCounterApp(), True)
self.register(FlashlightApp(), False)
self.register(SettingsApp(), False)
self.register(TestApp(), False)