1
0
Fork 0

Compare commits

...

74 Commits

Author SHA1 Message Date
jebba 537f42f726 patchlet for ppc64le micropython 2021-06-15 17:36:59 -06:00
jebba 9bbd4e2ce1 rustc on bullseye, upgrade pip3 2021-06-15 17:20:09 -06:00
jebba 6df7ae418b local PATH 2021-06-15 17:09:16 -06:00
jebba 186221703d scriptlet to install python deps 2021-06-15 16:58:53 -06:00
jebba 70dcec6b19 A few notes on Pinetime OS 2021-06-15 16:56:47 -06:00
jebba ccdfbdf2cc softdevice os notes 2021-06-15 00:09:03 -06:00
jebba 76b6e35f97 spreadsheet of Pinetime OS/distros 2021-06-15 00:00:38 -06:00
jebba e656bf25e9 move docs/ etc to upstream dir 2021-06-14 23:46:37 -06:00
ml server 230eefb092 get latest submodules + urls 2021-06-14 17:52:22 -06:00
ml server e8beec6814 use jebbatime-bootloader 2021-06-14 17:42:52 -06:00
ml server fa16152edc rm bootloader mirror 2021-06-14 17:41:49 -06:00
ml server ac7f695f54 use https for micropython submodule 2021-06-14 16:52:38 -06:00
ml server a0b394cf5f rm micropython ssh url 2021-06-14 16:48:53 -06:00
ml server 9d23e8ba6b Use jebbatime micropython fork 2021-06-14 16:46:07 -06:00
ml server b31ae87ffe rm daniel thompson's fork of micropython submodule 2021-06-14 16:42:50 -06:00
jebba f142166178 non-free bits 2021-06-14 15:04:27 -06:00
ml server c253c9598b scriptlets to cat main.py over ble 2021-06-14 14:57:43 -06:00
ml server 3e2c687b27 rm some P8 2021-06-14 14:41:33 -06:00
ml server 13a2d54609 clean build script 2021-06-14 14:41:19 -06:00
ml server 4991a6adde cruft up make 2021-06-14 14:14:55 -06:00
ml server 874ed31c38 non-free 2021-06-14 14:04:56 -06:00
ml server 2dbfd10b31 not connected. rm sim bits 2021-06-14 13:52:05 -06:00
ml server 091ea48b50 rm some ble 2021-06-14 13:38:47 -06:00
ml server ada48a8715 rm heartrate driver, maybe break sim 2021-06-14 13:13:58 -06:00
jebba 4e24fe518a same license 2021-06-14 13:04:00 -06:00
jebba 21a0285901 rm'd 2021-06-14 12:59:38 -06:00
ml server 592c08f9cb Update download url for v1.1 2021-06-14 12:53:17 -06:00
ml server b6f85dda36 rm bma421 2021-06-14 12:38:34 -06:00
ml server d1472b11ea rm bma42x-upy.... 2021-06-14 12:37:07 -06:00
ml server 7f47e75b67 rm unused boards 2021-06-14 12:16:18 -06:00
jebba ad6883b450 to rm BMA421 2021-06-14 12:07:11 -06:00
ml server 673945ff2e rm gadgetbridge/steplogger 2021-06-14 11:53:23 -06:00
jebba fe4f99a7b5 do logos/graphics 2021-06-14 11:51:44 -06:00
jebba f667b7a5d1 todo 2021-06-14 11:46:28 -06:00
jebba b2a4b9fe4d upstream todo 2021-06-14 11:38:25 -06:00
jebba 3069859d05 Merge branch 'jebbatime' of spacecruft.org:jebbatime/jebbatime into jebbatime 2021-06-14 11:35:37 -06:00
jebba 77c7fc0be0 readme rm 2021-06-14 11:35:24 -06:00
ml server 728c9f3c5f rm ppg 2021-06-14 11:25:12 -06:00
jebbatime build server df6bcefdeb rm unused apps 2021-06-14 11:22:11 -06:00
ml server 9be8b06c68 jebbatime.png 2021-06-12 17:48:41 -06:00
ml server 2c8457f058 sim, fewer features 2021-06-12 17:43:22 -06:00
ml server 5d41bcda22 scriplet to upload main.py... 2021-06-12 17:39:22 -06:00
ml server cd48b23239 non-free bits 2021-06-12 17:13:05 -06:00
ml server 38fdaf5b1c set time after flash 2021-06-12 16:54:56 -06:00
ml server e86b207360 dev url 2021-06-12 16:37:17 -06:00
ml server fe930cee3b v1.0.1 url 2021-06-12 16:34:51 -06:00
ml server 9ad4aff2d0 scriplets, flash build/release watch/dev 2021-06-12 16:04:15 -06:00
ml server 29d4b48cba no gadgetbridge 2021-06-12 15:59:27 -06:00
jebbatime build server 3120967fb5 reboot into bootloader scriplets 2021-06-12 15:57:22 -06:00
ml server a3bacc2c00 run scriptlets 2021-06-12 15:54:52 -06:00
ml server 0c1d959b28 flash with build 2021-06-12 15:50:30 -06:00
ml server 7dc9995465 dev flash uses build image, not release 2021-06-12 15:31:29 -06:00
jebbatime build server a493df75a1 deps script, build dir... 2021-06-12 15:26:47 -06:00
ml server acbb2820a8 right console 2021-06-12 15:19:17 -06:00
ml server f359a42955 console scriptlets 2021-06-12 15:17:07 -06:00
jebbatime build server 41e0475c93 Set time scriptlets 2021-06-12 15:14:59 -06:00
jebba e89f23efe3 ntpdate 2021-06-12 15:03:33 -06:00
jebbatime build server 9b0e9c8308 reset scriptlets 2021-06-12 15:02:40 -06:00
jebbatime build server a312638fe9 brightness scriptlets 2021-06-12 15:01:10 -06:00
jebbatime build server 82297534dc battery scriptlets 2021-06-12 14:58:49 -06:00
jebbatime build server 05f00659f1 checktime scriptlets 2021-06-12 14:57:35 -06:00
jebbatime build server 32e1b8b5fd disconnect scriptlets 2021-06-12 14:55:26 -06:00
jebbatime build server d2001dc84b connect scriptlets 2021-06-12 14:53:40 -06:00
jebba 5e2a513b02 bluez 2021-06-12 14:51:55 -06:00
jebba e5ae058bca Merge branch 'jebbatime' of spacecruft.org:jebbatime/jebbatime into jebbatime 2021-06-12 14:51:31 -06:00
jebba 1cc4505f06 bluez 2021-06-12 14:51:08 -06:00
jebbatime build server e7e7dd4f9d build scriptlet 2021-06-12 14:49:08 -06:00
jebba 24147b141c 5min 2021-06-12 14:47:20 -06:00
ml server c6963b2ced git root 2021-06-12 14:42:19 -06:00
ml server 886f4d9b37 add flasher script 2021-06-12 14:38:58 -06:00
ml server 01762ca9dd Flash dev watch script 2021-06-12 14:30:25 -06:00
ml server 3e78d5baa4 config 2021-06-12 14:09:49 -06:00
jebba 2d2b7e005a use config 2021-06-12 14:08:19 -06:00
jebbatime build server 735df86e75 sample config 2021-06-12 14:07:10 -06:00
112 changed files with 671 additions and 4015 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ attic/
wasp/boards/*/watch.py
.idea
.vscode
.~lock.*.ods#

22
.gitmodules vendored
View File

@ -1,11 +1,3 @@
[submodule "bootloader"]
path = bootloader
# url = https://github.com/daniel-thompson/wasp-bootloader
url = https://mirrors.spacecruft.org/daniel-thompson/wasp-bootloader
[submodule "micropython"]
path = micropython
# url = https://github.com/daniel-thompson/micropython
url = https://mirrors.spacecruft.org/daniel-thompson/micropython
[submodule "tools/intelhex"]
path = tools/intelhex
# url = https://github.com/python-intelhex/intelhex
@ -34,7 +26,13 @@
path = reloader
# url = https://github.com/daniel-thompson/wasp-reloader
url = https://mirrors.spacecruft.org/daniel-thompson/wasp-reloader
[submodule "wasp/modules/bma42x-upy"]
path = wasp/modules/bma42x-upy
# url = https://github.com/daniel-thompson/bma42x-upy
url = https://mirrors.spacecruft.org/daniel-thompson/bma42x-upy
#[submodule "wasp/modules/bma42x-upy"]
# path = wasp/modules/bma42x-upy
## url = https://github.com/daniel-thompson/bma42x-upy
# url = https://mirrors.spacecruft.org/daniel-thompson/bma42x-upy
[submodule "micropython"]
path = micropython
url = https://spacecruft.org/jebbatime/micropython.git
[submodule "bootloader"]
path = bootloader
url = https://spacecruft.org/jebbatime/jebbatime-bootloader.git

View File

@ -31,10 +31,10 @@ submodules :
(cd bootloader; git submodule update --init)
(cd micropython/ports/nrf; $(MAKE) submodules)
(cd reloader; git submodule update --init)
(cd wasp/modules/bma42x-upy; git submodule update --init)
# XXX non-free
bootloader: build-$(BOARD_SAFE)
$(RM) bootloader/_build-$(BOARD)_nrf52832//$(BOARD)_nrf52832_bootloader-*-nosd.hex
$(RM) bootloader/_build-$(BOARD)_nrf52832/$(BOARD)_nrf52832_bootloader-*-nosd.hex
$(MAKE) -C bootloader/ BOARD=$(BOARD)_nrf52832 all genhex
$(PYTHON) tools/hexmerge.py \
bootloader/_build-$(BOARD)_nrf52832/$(BOARD)_nrf52832_bootloader-*-nosd.hex \
@ -43,7 +43,7 @@ bootloader: build-$(BOARD_SAFE)
$(PYTHON) tools/hex2c.py build-$(BOARD)/bootloader.hex > \
reloader/src/boards/$(BOARD)/bootloader.h
$(PYTHON) -m nordicsemi dfu genpkg \
--bootloader bootloader/_build-$(BOARD)_nrf52832//$(BOARD)_nrf52832_bootloader-*-nosd.hex \
--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 \
build-$(BOARD)/bootloader-daflasher.zip
@ -51,6 +51,7 @@ reloader: bootloader build-$(BOARD_SAFE)
$(MAKE) -C reloader/ BOARD=$(BOARD)
cp reloader/build-$(BOARD)/reloader*.zip build-$(BOARD)
# XXX non-free
softdevice:
micropython/ports/nrf/drivers/bluetooth/download_ble_stack.sh
@ -110,9 +111,7 @@ check:
.PHONY: bootloader reloader docs micropython
dist: DIST=../wasp-os-$(VERSION)
dist: k9
k9: p8
p8: pinetime
dist: pinetime
pinetime : mrproper
mrproper :
$(RM) -r \
@ -122,7 +121,7 @@ mrproper :
reloader/src/boards/*/bootloader.h \
micropython/mpy-cross/build \
micropython/ports/nrf/build-*
k9 p8 pinetime:
pinetime:
$(RM) wasp/boards/$@/watch.py
$(MAKE) BOARD=$@ all
dist: docs
@ -135,3 +134,4 @@ dist: docs
find $(DIST) -name __pycache__ | xargs $(RM) -r
tar -C .. -zcf $(DIST).tar.gz $(notdir $(DIST))
(cd ..; zip -9r $(DIST).zip $(notdir $(DIST)))

View File

@ -1,4 +1,6 @@
# jebbatime
<img src="res/jebbatime.png"/>
jebbatime is an operating system and application suite for the PineTime watch.
# Build
@ -20,6 +22,9 @@ sudo apt install \
python3-cairo \
python3-ecdsa \
pkg-config \
bluez \
ntpdate \
sudo \
gcc-arm-none-eabi ccache \
fonts-lobstertwo
```
@ -27,7 +32,7 @@ sudo apt install \
Add to `~/.bashrc`:
```
PATH=/usr/lib/ccache:$PATH
PATH=~/.local/bin:/usr/lib/ccache:$PATH
```
### Source
@ -38,16 +43,28 @@ git clone git@spacecruft.org:jebbatime/jebbatime.git
# Or
git clone https://spacecruft.org/jebbatime/jebbatime.git
cd jebbatime
./scripts/jebbatime-install-deb-deps
./scripts/jebbatime-install-py-deps
```
### Python
Install Python dependencies with `pip`.
Manually, install Python dependencies with `pip`.
```
pip3 install --user -r wasp/requirements.txt
pip3 install --user freetype-py
```
## Bullseye
Debian Bullseye (11) on ppc64le wants `rustc` to build/install `pip`
package `cryptography`.
```
./scripts/jebbatime-install-deb-deps
sudo apt -y install rustc
./scripts/jebbatime-install-py-deps
```
### Submodules
Some submodules are mirrored.
@ -85,12 +102,55 @@ cd tools/micropython-font-to-py
mv lobstertwobold64.py ../../wasp/fonts/
```
# Setup
This assumes two devices, the sealed watch and the open developers watch.
```
mkdir ~/.config/
cp -p config/jebbatime.conf ~/.config/
# Adjust as needed.
vim ~/.config/jebbatime.conf
```
# Use
Basic scripts are in `scripts/`.
# Upstream
## Fork
jebbatime is a lesser fork of Daniel Thompson's Wasp-os.
* Same licenses.
* Renamed.
* Removed more than added.
* Removing bits not used on Pinetime.
* Removing bits not used by me.
* No step counter.
* No heart rate monitor.
* Etc.
* Not much useful added.
* Nice digital face font. Battery and BLE icon removed from main app.
## Source
Upstream source code:
* https://github.com/daniel-thompson/wasp-os
# Non-free bits
XXX Appears to include non-free "5-Clause Nordic License". `:|`
* https://wasp-os.readthedocs.io/en/latest/five-clause-nordic.html
* https://infocenter.nordicsemi.com/index.jsp?topic=%2Fstruct_nrf52%2Fstruct%2Fnrf52_softdevices.html
"A SoftDevice is a precompiled and linked binary software implementing a wireless protocol developed by Nordic Semiconductor."
* Non-free code gets pulled in via this build script:
`micropython/ports/nrf/drivers/bluetooth/download_ble_stack.sh`
* In `micropython/ports/nrf/drivers/bluetooth/`
```
4. This software, with or without modification, must only be used with a
Nordic Semiconductor ASA integrated circuit.
```

37
TODO.md 100644
View File

@ -0,0 +1,37 @@
# TODO
* Name.
* Remove BLE when running, but not from bootloader.
* Cleaner apps: Timer, Alarm, Stopwatch, Calc.
* Bigger buttons.
* Remove status bar.
* Battery app with numeric readout.
* Date app.
* Version app.
* Remove unused drivers.
* Remove unused bits in general.
* Mirrors of all submodules.
* Graphics, e.g. `logo.py`.
* Set default time and enable Alarm.
* Multiple alarms.
# Time
* Accurate within a couple seconds.
It keeps accruate time, just doesn't display it. `;)`
* BLE sync...
* NTP sync.
* GPS sync.
* WWVB sync.
* PTP sync.
# Random
Possible apps.
* Entropy generator.
* Auth device.
* Gate alert.
* Phone alert.
* FarmBot control.
* Blockchain ticker/alerts.
* Open Gate.
* Log heart/step data for Prometheus.

@ -1 +1 @@
Subproject commit 1d128f1311c084c23a662cb68263df9b187ebf94
Subproject commit dd7f0d2772f4c425468bf9940cb9904fc052b703

View File

@ -0,0 +1,19 @@
# Put here with correct MACs.
# ~/.config/jebbatime.conf
# Directory where jebbatime git repo is located
JEBBATIMEGITROOT=~/jebbatime
# Bluetooth MAC of main sealed watch.
JEBBATIMEMAC=00:11:22:33:44:00
# Bluetooth MAC of main sealed watch DFU. One larger than main MAC.
JEBBATIMEDFUMAC=00:11:22:33:44:01
# Bluetooth MAC of development unsealed watch.
JEBBATIMEDEVMAC=AA:BB:CC:DD:EE:40
# Bluetooth MAC of development unsealed watch DFU. One larger than main dev MAC.
JEBBATIMEDEVDFUMAC=AA:BB:CC:DD:EE:41
# NTP Server
JEBBATIMENTPSERVER=192.168.1.1

@ -1 +1 @@
Subproject commit 048bb2da83d0cbe73a829e461bd4f22b86c282bc
Subproject commit 79034e2b52e36248848fc76e2ee8c667926124cf

View File

@ -0,0 +1,13 @@
diff --git a/mpy-cross/Makefile b/mpy-cross/Makefile
index 2116cc670..2269bc1c3 100644
--- a/mpy-cross/Makefile
+++ b/mpy-cross/Makefile
@@ -43,6 +43,7 @@ else
LDFLAGS_ARCH = -Wl,-Map=$@.map,--cref -Wl,--gc-sections
endif
LDFLAGS = $(LDFLAGS_MOD) $(LDFLAGS_ARCH) -lm $(LDFLAGS_EXTRA)
+LDFLAGS += -no-pie
# source files
SRC_C = \

BIN
pinetime-os.ods 100644

Binary file not shown.

BIN
res/jebbatime.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -0,0 +1,11 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--battery \
--device $JEBBATIMEMAC

View File

@ -0,0 +1,12 @@
#!/bin/bash
# Reboot into bootloader
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--bootloader \
--device $JEBBATIMEMAC

View File

@ -0,0 +1,12 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--exec wasp/main.py \
--eval 'wasp.system.brightness=3' \
--device $JEBBATIMEMAC

View File

@ -0,0 +1,17 @@
#!/bin/bash
set -x
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
make mrproper
make clean BOARD=pinetime
git submodule sync
git submodule update --init
make submodules
make -j`nproc` all BOARD=pinetime

View File

@ -0,0 +1,10 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
make mrproper
make clean BOARD=pinetime

View File

@ -0,0 +1,12 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--exec wasp/main.py \
--eval "from shell import * ; cd('/flash') ; cat('main.py')" \
--device $JEBBATIMEMAC

View File

@ -0,0 +1,11 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--check-rtc \
--device $JEBBATIMEMAC

View File

@ -0,0 +1,10 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
echo "Connecting Bluetooth"
sudo bluetoothctl connect $JEBBATIMEMAC

View File

@ -0,0 +1,11 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--console \
--device $JEBBATIMEMAC

View File

@ -0,0 +1,11 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--battery \
--device $JEBBATIMEDEVMAC

View File

@ -0,0 +1,12 @@
#!/bin/bash
# Reboot into bootloader
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--bootloader \
--device $JEBBATIMEDEVMAC

View File

@ -0,0 +1,12 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--exec wasp/main.py \
--eval 'wasp.system.brightness=3' \
--device $JEBBATIMEDEVMAC

View File

@ -0,0 +1,12 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--exec wasp/main.py \
--eval "from shell import * ; cd('/flash') ; cat('main.py')" \
--device $JEBBATIMEDEVMAC

View File

@ -0,0 +1,11 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--check-rtc \
--device $JEBBATIMEDEVMAC

View File

@ -0,0 +1,10 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
echo "Connecting Bluetooth"
sudo bluetoothctl connect $JEBBATIMEDEVMAC

View File

@ -0,0 +1,11 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--console \
--device $JEBBATIMEDEVMAC

View File

@ -0,0 +1,10 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
echo "Disconnecting Bluetooth"
sudo bluetoothctl disconnect $JEBBATIMEDEVMAC

View File

@ -0,0 +1,32 @@
#!/bin/bash
# Need to be in root dir of jebbatime git archive.
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
# Reboot thang into bootloader
echo "Rebooting development PineTime watch into bootloader..."
./tools/wasptool \
--verbose \
--bootloader \
--device $JEBBATIMEDEVMAC
echo "Waiting..."
sleep 4
# Flash
echo "Flashing, will take ~5 minutes..."
# Use image in build dir
./tools/ota-dfu/dfu.py \
--legacy \
--verbose \
--address=$JEBBATIMEDEVDFUMAC \
--zip=build-pinetime/micropython.zip
sleep 1
echo "Setting time on watch"
./tools/wasptool \
--rtc \
--device $JEBBATIMEDEVMAC

View File

@ -0,0 +1,36 @@
#!/bin/bash
# Need to be in root dir of jebbatime git archive.
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
# Download latest release. XXX better link to latest.
rm -f /tmp/micropython-dev.zip
wget -O /tmp/micropython-dev.zip \
https://spacecruft.org/attachments/84fb3527-4ef1-47e4-8a55-ef55688ed05f
# Reboot thang into bootloader
echo "Rebooting development PineTime watch into bootloader..."
./tools/wasptool \
--verbose \
--bootloader \
--device $JEBBATIMEDEVMAC
echo "Waiting..."
sleep 4
# Flash
echo "Flashing, will take ~5 minutes..."
./tools/ota-dfu/dfu.py \
--legacy \
--verbose \
--address=$JEBBATIMEDEVDFUMAC \
--zip=/tmp/micropython-dev.zip
sleep 1
echo "Setting time on watch"
./tools/wasptool \
--rtc \
--device $JEBBATIMEDEVMAC

View File

@ -0,0 +1,11 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--reset \
--device $JEBBATIMEDEVMAC

View File

@ -0,0 +1,14 @@
#!/bin/bash
# If the watch isn't running for some reason.
# E.g. if it just displays "main.py".
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--exec wasp/main.py \
--eval 'wasp.system.run()' \
--device $JEBBATIMEDEVMAC

View File

@ -0,0 +1,35 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--battery \
--device $JEBBATIMEDEVMAC
echo "Connecting Bluetooth"
sudo bluetoothctl connect $JEBBATIMEDEVMAC
echo "Setting host `hostname` time via NTP"
sudo ntpdate $JEBBATIMENTPSERVER
echo "Checking time on watch"
./tools/wasptool \
--check-rtc \
--device $JEBBATIMEDEVMAC
echo "Setting time on watch"
./tools/wasptool \
--rtc \
--device $JEBBATIMEDEVMAC
echo "Checking time on watch"
./tools/wasptool \
--check-rtc \
--device $JEBBATIMEDEVMAC
echo "Disconnecting Bluetooth"
sudo bluetoothctl disconnect $JEBBATIMEDEVMAC

View File

@ -0,0 +1,12 @@
#!/bin/bash
# Upload wasp/main.py
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--upload wasp/main.py \
--device $JEBBATIMEDEVMAC

View File

@ -0,0 +1,10 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
echo "Disconnecting Bluetooth"
sudo bluetoothctl disconnect $JEBBATIMEMAC

View File

@ -0,0 +1,32 @@
#!/bin/bash
# Need to be in root dir of jebbatime git archive.
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
# Reboot thang into bootloader
echo "Rebooting development PineTime watch into bootloader..."
./tools/wasptool \
--verbose \
--bootloader \
--device $JEBBATIMEMAC
echo "Waiting..."
sleep 4
# Flash
echo "Flashing, will take ~5 minutes..."
# Use image in build dir
./tools/ota-dfu/dfu.py \
--legacy \
--verbose \
--address=$JEBBATIMEDFUMAC \
--zip=build-pinetime/micropython.zip
sleep 1
echo "Setting time on watch"
./tools/wasptool \
--rtc \
--device $JEBBATIMEMAC

View File

@ -0,0 +1,36 @@
#!/bin/bash
# Need to be in root dir of jebbatime git archive.
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
# Download latest release. XXX better link to latest.
rm -f /tmp/micropython.zip
wget -O /tmp/micropython.zip \
https://spacecruft.org/attachments/84fb3527-4ef1-47e4-8a55-ef55688ed05f
# Reboot thang into bootloader
echo "Rebooting development PineTime watch into bootloader..."
./tools/wasptool \
--verbose \
--bootloader \
--device $JEBBATIMEMAC
echo "Waiting..."
sleep 4
# Flash
echo "Flashing, will take ~5 minutes..."
./tools/ota-dfu/dfu.py \
--legacy \
--verbose \
--address=$JEBBATIMEDFUMAC \
--zip=/tmp/micropython.zip
sleep 1
echo "Setting time on watch"
./tools/wasptool \
--rtc \
--device $JEBBATIMEMAC

View File

@ -0,0 +1,20 @@
#!/bin/bash
# Debian Buster (10)
set -x
sudo apt install \
wget git build-essential libsdl2-2.0-0 python3-click python3-gi \
python3-numpy python3-pexpect python3-pil python3-pip python3-pydbus \
python3-serial unzip \
graphviz python3-recommonmark \
python3-sphinx \
python3-cairo \
python3-ecdsa \
pkg-config \
bluez \
ntpdate \
sudo \
gcc-arm-none-eabi ccache \
fonts-lobstertwo

View File

@ -0,0 +1,11 @@
#!/bin/bash
# Debian Buster (10)
set -x
pip3 install --upgrade pip
echo "Add ~/.local/bin to PATH"
~/.local/bin/pip3 install --user -r wasp/requirements.txt
~/.local/bin/pip3 install --user freetype-py

View File

@ -0,0 +1,11 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--reset \
--device $JEBBATIMEMAC

View File

@ -0,0 +1,14 @@
#!/bin/bash
# If the watch isn't running for some reason.
# E.g. if it just displays "main.py".
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--exec wasp/main.py \
--eval 'wasp.system.run()' \
--device $JEBBATIMEMAC

View File

@ -0,0 +1,35 @@
#!/bin/bash
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--battery \
--device $JEBBATIMEMAC
echo "Connecting Bluetooth"
sudo bluetoothctl connect $JEBBATIMEMAC
echo "Setting host `hostname` time via NTP"
sudo ntpdate $JEBBATIMENTPSERVER
echo "Checking time on watch"
./tools/wasptool \
--check-rtc \
--device $JEBBATIMEMAC
echo "Setting time on watch"
./tools/wasptool \
--rtc \
--device $JEBBATIMEMAC
echo "Checking time on watch"
./tools/wasptool \
--check-rtc \
--device $JEBBATIMEMAC
echo "Disconnecting Bluetooth"
sudo bluetoothctl disconnect $JEBBATIMEMAC

View File

@ -0,0 +1,12 @@
#!/bin/bash
# Upload wasp/main.py
# Get config
. ~/.config/jebbatime.conf
cd $JEBBATIMEGITROOT
./tools/wasptool \
--upload wasp/main.py \
--device $JEBBATIMEMAC

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,101 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
"""Analogue clock
~~~~~~~~~~~~~~~~~
Shows the time as a traditional watch face together with a battery meter.
.. figure:: res/ChronoApp.png
:width: 179
Screenshot of the analogue clock application
"""
import wasp
class ChronoApp():
"""Simple analogue clock application.
"""
NAME = 'Chrono'
def foreground(self):
"""Activate the application.
Configure the status bar, redraw the display and request a periodic
tick callback every second.
"""
wasp.system.bar.clock = False
self._draw(True)
wasp.system.request_tick(1000)
def sleep(self):
"""Prepare to enter the low power mode.
:returns: True, which tells the system manager not to automatically
switch to the default application before sleeping.
"""
return True
def wake(self):
"""Return from low power mode.
Time will have changes whilst we have been asleep so we must
udpate the display (but there is no need for a full redraw because
the display RAM is preserved during a sleep.
"""
self._draw()
def tick(self, ticks):
"""Periodic callback to update the display."""
self._draw()
def _draw(self, redraw=False):
"""Draw or lazily update the display.
The updates are as lazy by default and avoid spending time redrawing
if the time on display has not changed. However if redraw is set to
True then a full redraw is be performed.
"""
draw = wasp.watch.drawable
hi = wasp.system.theme('bright')
c1 = draw.darken(wasp.system.theme('spot1'), wasp.system.theme('contrast'))
if redraw:
now = wasp.watch.rtc.get_localtime()
# Clear the display and draw that static parts of the watch face
draw.fill()
# Redraw the status bar
wasp.system.bar.draw()
# Draw the dividers
draw.set_color(wasp.system.theme('mid'))
for theta in range(12):
draw.polar(120, 120, theta * 360 // 12, 110, 118, 3)
self._hh = 0
self._mm = 0
else:
now = wasp.system.bar.update()
if not now or self._mm == now[4]:
# Skip the update
return
# Undraw old time
hh = (30 * (self._hh % 12)) + (self._mm / 2)
mm = 6 * self._mm
draw.polar(120, 120, hh, 5, 75, 7, 0)
draw.polar(120, 120, mm, 5, 106, 5, 0)
# Record the minute that is currently being displayed
self._hh = now[3]
self._mm = now[4]
# Draw the new time
hh = (30 * (self._hh % 12)) + (self._mm / 2)
mm = 6 * self._mm
draw.polar(120, 120, hh, 5, 75, 7, hi)
draw.polar(120, 120, hh, 5, 60, 3, draw.darken(c1, 2))
draw.polar(120, 120, mm, 5, 106, 5, hi)

View File

@ -1,270 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
"""Logo demo for PineTime
~~~~~~~~~~~~~~~~~~~~~~~~~
This demo is simply an alternating sweep of the Pine64 and
MicroPython logos. It cycles through a variety of colours
and swaps between the logos every 5 images (so if you change
anything make sure len(colors) is not a multiple of 5).
"""
import wasp
import icons
# 2-bit RLE, generated from res/demo_icon.png, 292 bytes
demo_icon = (
b'\x02'
b'`@'
b'.\xc1?\x1f\xc3?\x1d\xc5?\x1b\xc7?\x19\xc9?\x17'
b'\xcb?\x16\xcc?\x10\xc1\x06\xc8\x06\xc1?\n\xc4\x06\xc4'
b'\x06\xc3?\n\xc6\x0c\xc6?\x08\xc9\x08\xc8?\x08\xc7\x0c'
b'\xc7?\x06\xc6\x06\xc4\x06\xc5?\x06\xc4\x05\xc9\x06\xc3?'
b'\x06\xc1\x06\xce\x05\xc2?\n\xd2?\x0c\xd7?\x08\xdc?'
b'\x05\xdc\x05\xc18\xc3\x05\xd7\x06\xc38\xc5\x06\xd2\x05\xc6'
b'8\xc7\x06\xce\x05\xc88\xca\x05\xc9\x06\xca8\xcc\x06\xc4'
b'\x06\xcc8\xce\x0b\xcf8\xd0\x08\xd08\xce\x0b\xcf8\xcc'
b'\x06\xc4\x06\xcc8\xc9\x06\xc9\x06\xca8\xc7\x06\xcd\x06\xc8'
b'8\xc5\x06\xd2\x06\xc58\xc3\x05\xd7\x06\xc3>\xdb\x06\xc1'
b'<\xe08\xc2\x06\xdf\x07\xc12\xc3\x06\xdb\x06\xc42\xc6'
b'\x06\xd6\x06\xc54\xc7\x06\xd1\x06\xc84\xca\x05\xcd\x06\xc9'
b'6\xcb\x06\xc8\x05\xcc6\xcd\x06\xc3\x06\xcd7\xd0\n\xcf'
b'8\xd0\x08\xd08\xce\x05\xc1\x06\xcd:\xca\x06\xc5\x06\xcb'
b':\xc8\x06\xca\x05\xc8<\xc5\x05\xcf\x06\xc5<\xc3\x05\xd3'
b'\x06\xc2?\x04\xd8?\x07\xdc?\x05\xdb?\x08\xd7?\r'
b'\xd2?\x11\xce?\x15\xc9?\x1a\xc5?\x1d\xc3?\x1e\xc3'
b'?\x1e\xc3?\x1e\xc3?\x1e\xc3?\x1e\xc3?\x1e\xc3?'
b'Q'
)
colors = (
0xffff,
0xf800, # red
0xffff,
0xffe0, # yellow
0xffff,
0x07e0, # green
0xffff,
0x07ff, # cyan
0xffff,
0x001f, # blue
0xffff,
0xf81f, # magenta
)
class Hack:
"""wasptool uses class (starting in column 0) as a clue for chunk at a
time transmission. Hence we use fake classes to demark places it is safe
to split an evaluation.
"""
pass
# 1-bit RLE, generated from res/pine64.png, 961 bytes
pine64 = (
240, 240,
b'x\x01\xee\x03\xec\x05\xea\x07\xe8\t\xe6\x0b\xe4\r\xe2\x0f'
b'\xe0\x11\xde\x13\xdc\x15\xda\x17\xd8\x19\xd6\x1b\xd4\x1d\xd2\x1f'
b"\xd1 \xcf!\xce#\xcc%\xca'\xc8)\xc6+\xc4-"
b"\xc3-\xc6'\xcb#\xd0\x1d\xb6\x02\x1d\x19\x1b\x03\x99\x05"
b'\x1e\x13\x1c\x05\x99\x08\x1d\x0f\x1c\x08\x98\n\x1e\t\x1d\n'
b'\x97\r\x1e\x05\x1c\r\x97\x10:\x10\x96\x126\x12\x95\x15'
b'1\x15\x95\x17-\x18\x93\x1b(\x1a\x93\x1d$\x1c\x93\x1e'
b'!\x1f\x91\x1d%\x1d\x91\x1b*\x1a\x91\x19.\x19\x8f\x17'
b'\x19\x01\x19\x17\x8f\x15\x19\x05\x19\x15\x8f\x13\x19\t\x1a\x13'
b'\x8d\x12\x19\r\x1a\x11\x8d\x10\x18\x12\x1a\x10\x8c\r\x19\x17'
b'\x19\x0e\x8b\x0c\x19\x1b\x19\x0c\x8b\n\x19\x1f\x1a\n\x89\t'
b'\x18$\x1a\x08\x89\x07\x18)\x19\x06\x89\x04\x19-\x19\x05'
b'\x87\x03\x191\x19\x03\x87\x01\x186\x19\x01\x9e;\xb3?'
b'\xafC\xabG\xa6L\xa2Q\x9dU\x98Z\x94^\x90c'
b'\x8bg\x87k\x85kn\x01\x18f\x1a\x01V\x03\x19a'
b'\x1a\x03V\x05\x19]\x1a\x05V\x07\x19Y\x19\x08V\n'
b'\x18T\x1a\nV\x0c\x19O\x1a\x0cV\x0e\x19K\x19\x0f'
b'V\x11\x18G\x19\x11V\x13\x18B\x1a\x13V\x16\x18='
b'\x19\x16V\x18\x189\x19\x18V\x1a\x185\x19\x1aV\x1c'
b"\x181\x19\x1cV\x1f\x18+\x19\x1fV!\x18'\x19!"
b'V#\x18#\x19#V%\x18\x1f\x18&V(\x17\x1a'
b'\x19(V*\x18\x15\x19*V,\x18\x11\x18-V/'
b'\x17\r\x18/V1\x17\x08\x191V4\x17\x03\x184'
b'V6.6V8*8V:&:V= ='
b'V<"<V:&:V7+8V505'
b'V3\x19\x01\x1a3V0\x1a\x05\x1b0V.\x19\x0b'
b"\x1a.V,\x19\x0f\x1a,V)\x1a\x13\x1b)V'"
b"\x1a\x17\x1b'V%\x19\x1d\x1a%V#\x19!\x1a#"
b'V \x1a%\x1b V\x1e\x1a)\x1b\x1eV\x1c\x1a.'
b'\x1a\x1cV\x19\x1b2\x1b\x19V\x17\x1a7\x1b\x17V\x14'
b'\x1b<\x1a\x15V\x12\x1b@\x1b\x12V\x10\x1aE\x1b\x10'
b'V\x0e\x1aI\x1b\x0eV\x0b\x1bM\x1c\x0bV\t\x1bQ'
b'\x1c\tV\x07\x1aW\x1b\x07V\x04\x1b[\x1b\x05V\x02'
b'\x1b_\x1c\x02qc\x8ai\x85m\x81q}uyz'
b'vyZ\x03\x1cu\x1d\x03=\x04\x1cq\x1d\x04>\x06'
b'\x1cm\x1d\x06?\x08\x1ch\x1c\x08@\n\x1cd\x1c\n'
b'A\x0c\x1b_\x1c\x0cB\x0e\x1b[\x1c\x0eB\x11\x1bV'
b'\x1b\x11C\x12\x1bR\x1b\x12D\x14\x1bM\x1c\x14E\x16'
b'\x1aI\x1b\x16F\x18\x1aE\x1b\x18G\x1a\x1a?\x1b\x1a'
b'H\x1c\x1a;\x1b\x1cI\x1e\x197\x1a\x1eJ \x193'
b'\x1a J"\x1a-\x1b!L$\x19)\x1a$L&'
b"\x19%\x1a&M(\x18!\x1a'N*\x19\x1c\x19*"
b'O+\x19\x18\x19+P.\x18\x13\x19.Q/\x18\x0f'
b'\x19/R2\x18\n\x182S3\x18\x05\x193T5'
b'\x18\x01\x195T8,8U9(9V<"<'
b'W< <X:$:Y7(7Z4-5'
b'Z222[/\x1a\x01\x1b/\\-\x19\x06\x1b-'
b"])\x1a\x0b\x1b)^'\x1a\x0f\x1b'_$\x1a\x13"
b'\x1b$`!\x1a\x19\x1b!a\x1e\x1a\x1d\x1b\x1eb\x1c'
b'\x1a!\x1b\x1cc\x19\x1a%\x1b\x19d\x16\x1a*\x1c\x16'
b'd\x14\x1a/\x1b\x14e\x11\x1a3\x1b\x11f\x0f\x1a7'
b'\x1b\x0fg\x0b\x1a<\x1c\x0bh\t\x1a@\x1c\ti\x06'
b'\x1aE\x1b\x06j\x03\x1bI\x1c\x03\x86M\xa0S\x9bW'
b'\x97[\x93_\x8ee\x89i\x85m\x85i\x89e\x8d`'
b'\x93[\x97W\x9bS\x9fN\xa5I\xa9E\xadA\xb1<'
b'\xb68\xbb3\xbf/\xc3+\xc7&\xcd!\xd1\x1d\xd5\x19'
b'\xda\x13\xdf\x0f\xe2\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c'
b'\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c'
b'\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c'
b'\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c'
b's'
)
class Hack:
pass
# 1-bit RLE, generated from res/micropython.png, 1491 bytes
micropython = (
240, 240,
b'\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00'
b'\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\x1fc'
b'\tc!c\tc!c\tc!c\tc!c'
b'\tc!c\tc!c\tc!c\tc!c'
b'\tc!c\tc!c\tc!c\tc!c'
b'\tc!c\tc!c\tc!c\tc!c'
b'\tc!c\tc!c\tc!c\tc!c'
b'\tc!c\tc!c\tc!c\tc!c'
b'\tc!c\tc!c\tc!c\tc!c'
b'\tc!c\tc!c\tc!c\tc!c'
b'\tc!c\tc!c\tc!c\tc!c'
b'\tc!c\tc!c\tc!c\tc!c'
b'\tc!c\tc!c\tc!c\tc!c'
b'\tc!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\t-\t-'
b'\t-!-\t-\t-\t-!-\tc\t-'
b'!-\tc\t-!-\tc\t-!-\tc'
b'\t-!-\tc\t-!-\tc\t-!-'
b'\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-'
b'\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-'
b'\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-'
b'\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-'
b'\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-'
b'\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-'
b'\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-'
b'\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-'
b'\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-'
b'\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-'
b'\tc\t\x12\x0c\x0f!-\tc\t-!-\tc'
b'\t-!-\tc\t-!-\tc\t-!-'
b'\tc\t-!-\tc\t-!-\tc\t-'
b'!-\tc\t-!-\tc\t-!-\tc'
b'\t-!-\tc\t-!-\tc\t-!-'
b'\tc\t-!-\tc\t-!-\tc\t-'
b'!-\tc\t-!-\tc\t-!-\tc'
b'\t-\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00'
b'\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00'
b'\xff\x00\x11'
)
class DemoApp():
"""Application for live demos.
Start this to give the watch something "interesting" to do when talking
over demos!
"""
NAME = 'Demo'
ICON = demo_icon
def __init__(self):
self._logo = pine64
self._color = 0
self._i = 0
def foreground(self):
"""Draw the first frame and establish the tick."""
self._draw()
wasp.system.request_tick(2000)
def tick(self, ticks):
"""Handle the tick."""
self._draw()
wasp.system.keep_awake()
def _draw(self):
"""Draw the next frame."""
draw = wasp.watch.drawable
if self._i < 5:
self._i += 1
else:
self._i = 0
if self._logo == pine64:
self._logo = micropython
else:
self._logo = pine64
draw.fill()
draw.rleblit(self._logo, fg=colors[self._color])
self._color += 1
if self._color >= len(colors):
self._color = 0

View File

@ -1,122 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
"""Digital dual clock
~~~~~~~~~~~~~~~~~~~~~~~~
Shows a time (as HH and MM vertically) together with a battery meter.
.. figure:: res/DualApp.png
:width: 179
"""
import wasp
import icons
import fonts.clock_dual as digits
DIGITS = (
digits.clock_dual_0, digits.clock_dual_1, digits.clock_dual_2, digits.clock_dual_3,
digits.clock_dual_4, digits.clock_dual_5, digits.clock_dual_6, digits.clock_dual_7,
digits.clock_dual_8, digits.clock_dual_9
)
class DualClockApp():
"""Simple digital clock application."""
NAME = 'Dual'
# 2-bit RLE, generated from clock_dual_icon.png, 298 bytes
ICON = (
b'\x02'
b'`@'
b'?\xff\xc1@\xacF\r\xc6?\x06H\x0b\xca?\x04H'
b'\n\xcc?\x03C\x02C\n\xc4\x05\xc4?\x07C\t\xc4'
b'\x07\xc3?\x07C\t\xc3\t\xc2?\x07C\t\xc3\t\xc3'
b'?\x06C\t\xc3\t\xc3?\x06C\t\xc3\t\xc3?\x06'
b'C\t\xc4\x07\xc4?\x06C\n\xc4\x05\xc5?\x06C\n'
b'\xca\x01\xc3?\x06C\x0b\xc9\x01\xc3?\x06C\r\xc5\x03'
b'\xc3?\x06C\x15\xc3?\x06C\x14\xc4?\x06C\x14\xc3'
b'?\x07C\x14\xc3?\x07C\x13\xc4?\x07C\x0b\xc1\x05'
b'\xc5?\x03M\x06\xca?\x04M\x06\xc9?\x05M\x07\xc6'
b'?\xff\xedE\x0b\xc8?\x07I\x07\xcc?\x04K\x06\xce'
b'?\x02D\x03D\x06\xc2\x08\xc4?\x01D\x05D\x10\xc4'
b'?\x00C\x07C\x11\xc3>D\x07D\x10\xc3>C\t'
b'C\x10\xc3>C\tC\x10\xc3>C\tC\x0f\xc4>'
b'C\tC\x0e\xc4?\x00C\tC\r\xc5?\x00C\t'
b'C\x0c\xc5?\x01C\tC\x0b\xc5?\x02C\tC\n'
b'\xc5?\x03C\tC\t\xc5?\x04D\x07D\x08\xc4?'
b'\x07C\x07C\x08\xc4?\x08C\x06D\x07\xc4?\nD'
b'\x03D\x07\xc3?\x0cK\x06\xcf?\x02I\x07\xcf?\x04'
b'E\t\xcf?\xff\xff\xe4'
)
_min = None
def foreground(self):
"""Activate the application.
Configure the status bar, redraw the display and request a periodic
tick callback every second.
"""
wasp.system.bar.clock = False
self._draw(True)
wasp.system.request_tick(1000)
def sleep(self):
"""Prepare to enter the low power mode.
:returns: True, which tells the system manager not to automatically
switch to the default application before sleeping.
"""
return True
def wake(self):
"""Return from low power mode.
Time will have changes whilst we have been asleep so we must
udpate the display (but there is no need for a full redraw because
the display RAM is preserved during a sleep.
"""
self._draw()
def tick(self, ticks):
"""Periodic callback to update the display."""
self._draw()
def _draw(self, redraw=False):
"""Draw or lazily update the display.
The updates are as lazy by default and avoid spending time redrawing
if the time on display has not changed. However if redraw is set to
True then a full redraw is be performed.
"""
draw = wasp.watch.drawable
hi = wasp.system.theme('bright')
lo = wasp.system.theme('mid')
if redraw:
now = wasp.watch.rtc.get_localtime()
# Clear the display and draw that static parts of the watch face
draw.fill()
#draw.blit(digits.clock_colon, 2*48, 80, fg=mid)
# Redraw the status bar
wasp.system.bar.draw()
else:
# The update is doubly lazy... we update the status bar and if
# the status bus update reports a change in the time of day
# then we compare the minute on display to make sure we
# only update the main clock once per minute.
now = wasp.system.bar.update()
if not now or self._min == now[4]:
# Skip the update
return
# Draw the changeable parts of the watch face
draw.blit(DIGITS[now[4] % 10], 40 + 1*90, 140, fg=hi)
draw.blit(DIGITS[now[4] // 10], 40 + 0*90, 140, fg=hi)
draw.blit(DIGITS[now[3] % 10], 40 + 1*90, 40, fg=lo)
draw.blit(DIGITS[now[3] // 10], 40 + 0*90, 40, fg=lo)
#draw.roundRect(25, 135, 180, 100, 5, lo)
# Record the minute that is currently being displayed
self._min = now[4]

View File

@ -1,119 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Johannes Wache
"""Fibonacci clock
~~~~~~~~~~~~~~~~~~
The Fibonacci sequence is a sequence of numbers created by the Italian
mathematician Fibonacci in the 13th century. This is a sequence starting with
1 and 1, where each subsequent number is the sum of the previous two. For the
clock I used the first 5 terms: 1, 1, 2, 3 and 5.
.. figure:: res/FiboApp.png
:width: 179
Screenshot of the fibonacci clock application
The screen of the clock is made up of five squares whose side lengths match
the first five Fibonacci numbers: 1, 1, 2, 3 and 5. The hours are displayed
using red and the minutes using green. When a square is used to display both
the hours and minutes it turns blue. White squares are ignored.
To tell time on the Fibonacci clock you need to do some calculations. To read
the hour, simply add up the corresponding values of the red and blue squares.
To read the minutes, do the same with the green and blue squares. The minutes
are displayed in 5 minute increments (0 to 12) so you have to multiply your
result by 5 to get the actual number.
"""
import wasp
import icons
COLORS = [0xffff,0xf800,0x07e0,0x001f] # White, red, green and blue
FIELDS = b'\x05\x03\x02\x01\x01'
MONTH = 'JanFebMarAprMayJunJulAugSepOctNovDec'
# 2-bit RLE, generated from res/fibo_icon.png, 246 bytes
icon = (
b'\x02'
b'`@'
b'?\xff\xffk\xd3\x01\xc9\x01@\x1er\x10\xd3\x01\xc9\x01'
b'r\x10\xd3\x01\xc9\x01r\x10\xd3\x01\xc9\x01r\x10\xd3\x01'
b'\xc9\x01r\x10\xd3\x01\xc9\x01r\x10\xd3\x01\xc9\x01r\x10'
b'\xd3\x01\xc9\x01r\x10\xd3\x01\xc9\x01r\x10\xd3\x0br\x10'
b'\xd3\x01I\x01r\x10\xd3\x01I\x01r\x10\xd3\x01I\x01'
b'r\x10\xd3\x01I\x01r\x10\xd3\x01I\x01r\x10\xd3\x01'
b'I\x01r\x10\xd3\x01I\x01r\x10\xd3\x01I\x01r\x10'
b'\xd3\x01I\x01r.r\x10\xdd\x01r\x10\xdd\x01r\x10'
b'\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r\x10'
b'\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r\x10'
b'\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r\x10'
b'\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r\x10'
b'\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r\x10'
b'\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r\x10'
b'\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r\x10\xdd\x01r?'
b'\xff\xffk'
)
class FibonacciClockApp():
"""Displays the time as a Fibonacci Clock.
"""
NAME = 'Fibo'
ICON = icon
def foreground(self):
"""Activate the application."""
wasp.system.bar.clock = False
self._draw(True)
wasp.system.request_tick(1000)
def sleep(self):
return True
def wake(self):
self._draw()
def tick(self, ticks):
self._draw()
def _draw(self, redraw=False):
"""Draw or lazily update the display."""
draw = wasp.watch.drawable
if redraw:
now = wasp.watch.rtc.get_localtime()
draw.fill()
wasp.system.bar.draw()
else:
now = wasp.system.bar.update()
if not now or self._min == now[4]:
return
#calculate colors of fields:
field_colors = bytearray(5)
hr = now[3]
mn = now[4] // 5 # Clock can only display every 5 minutes
if (hr >= 12):
hr -= 12
for i in range(5):
if ((hr - FIELDS[i]) >= 0):
hr -= FIELDS[i]
field_colors[i] += 1
if ((mn - FIELDS[i]) >= 0):
mn -= FIELDS[i]
field_colors[i] += 2
draw.fill(x=71,y=60,w=23,h=23,bg=COLORS[field_colors[4]]) # 1 field
draw.fill(x=71,y=85,w=23,h=23,bg=COLORS[field_colors[3]]) # 1 field
draw.fill(x=21,y=60,w=48,h=48,bg=COLORS[field_colors[2]]) # 2 field
draw.fill(x=21,y=110,w=73,h=73,bg=COLORS[field_colors[1]]) # 3 field
draw.fill(x=96,y=60,w=123,h=123,bg=COLORS[field_colors[0]]) # 5 field
month = now[1] - 1
month = MONTH[month*3:(month+1)*3]
draw.string('{} {} {}'.format(now[2], month, now[0]),
0, 202, width=240)
# Record the minute that is currently being displayed
self._min = now[4]

View File

@ -1,258 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
"""Conway's Game of Life
~~~~~~~~~~~~~~~~~~~~~~~~
The Game of Life is a "no player game" played on a two dimensional grid
where the rules interact to make interesting patterns.
.. figure:: res/LifeApp.png
:width: 179
Screenshot of the Game of Life application
The game is based on four simple rules:
1. Death by isolation: a cell dies if has fewer than two live neighbours.
2. Death by overcrowding: a cell dies if it has more than three live
neighbours.
3. Survival: a living cell continues to survive if it has two or three
neighbours.
4. Reproduction: a dead cell comes alive if it has exactly three
neighbours.
On 11 April 2020 John H. Conway who, among many, many other
achievements, devised the rule set for his Game of Life, died of
complications from a COVID-19 infection.
The Game of Life is the first "toy" program I ever recall seeing on a
computer (running in a mid 1980s Apple Macintosh). It sparked something
even if "toy" is perhaps an underwhelming description of the Game of Life.
Either way it occupies a special place in my childhood. For that, this
application is dedicated to Professor Conway.
"""
import array
import machine
import micropython
import wasp
@micropython.viper
def xorshift12(v: int) -> int:
"""12-bit xorshift pseudo random number generator.
With only 12-bits of state this PRNG is another toy! It appears
here because it allows us to visit every possible 12-bit value
(except zero) whilst taking an interesting route. This allows us to
make the redraw (which is too slow to fully conceal) visually
engaging.
"""
v ^= v << 1
v ^= (v >> 3) & 0x1ff
v ^= (v << 7)
return v & 0xfff
@micropython.viper
def get_color(v: int) -> int:
"""Convert a 12-bit number into a reasonably bright RGB565 pixel"""
rgb = v ^ (v << 4)
while 0 == (rgb & 0xc710):
rgb += 0x2104
return rgb
@micropython.viper
def get_cell(board, stride: int, x: int, y: int) -> bool:
b = ptr32(board)
xw = x >> 5
xb = x & 0x1f
yw = y * (stride >> 5)
return bool(b[yw + xw] & (1 << xb))
@micropython.viper
def set_cell(board, stride: int, x: int, y: int, v: bool):
b = ptr32(board)
xw = x >> 5
xb = x & 0x1f
yw = y * (stride >> 5)
m = 1 << xb
c = b[yw + xw]
# viper doesn't implement bitwise not so we are having
# to clear bits using xor...
if v:
b[yw + xw] = c | m
elif c & m:
b[yw + xw] = c ^ m
@micropython.viper
def game_of_life(b, xmax: int, ymax: int, nb):
"""Run a single generation of Conway's Game of Life
In the code below we have simplified the rules described at the top
of this file to "a cell is alive it has three live neighbours or if
it was previously alive and has two neighbours, otherwise it is
dead.".
"""
board = ptr32(b)
next_board = ptr32(nb)
for y in range(1, ymax-1):
tm = int(get_cell(board, xmax, 0, y-1))
tr = int(get_cell(board, xmax, 1, y-1))
cm = int(get_cell(board, xmax, 0, y))
cr = int(get_cell(board, xmax, 1, y))
bm = int(get_cell(board, xmax, 0, y+1))
br = int(get_cell(board, xmax, 1, y+1))
for x in range(1, xmax-1):
tl = tm
tm = tr
tr = int(get_cell(board, xmax, x+1, y-1))
cl = cm
cm = cr
cr = int(get_cell(board, xmax, x+1, y))
bl = bm
bm = br
br = int(get_cell(board, xmax, x+1, y+1))
c = tl + tm + tr + cl + cr + bl + bm + br
set_cell(next_board, xmax, x, y, c == 3 or (cm and c == 2))
# 2-bit RLE, generated from res/gameoflife.png, 404 bytes
# The icon is a carefully selected generation of an "acorn", I wanted
# to avoid using a glider, they are overused to the point of cliche!
icon = (
b'\x02'
b'`@'
b'?\xff\xff\xee@\xf8B\x02B\x02B?\x16L?\x15'
b'L?\x16B\x02B\x02B?\x1bB?\x1eD?\x1d'
b'D?\x1eB?\x17\x80\xee\x82\x02\x82\x06\x82\x02\x82?'
b'\x0e\x88\x04\x88?\r\x88\x04\x88?\x0e\x82\x02\x82\x06B'
b'\x02\x82?\x03\xc0\x89\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02'
b'\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc25\xec4\xec5'
b'\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02'
b'\xc2\x02\xc2\x02\xc2*B\x02B\x12\xc2\x06B\x06\xc2\x12'
b'B\x02B\x1dH\x10\xc4\x04D\x04\xc4\x10H\x1cH\x10'
b'\xc4\x04D\x04\xc4\x10H\x1dB\x02B\x12\xc2\x06B\x06'
b'\xc2\x12B\x02B\x1eB\x16\xc2\x0e\xc2\x16B\x1dD\x14'
b'\xc4\x0c\xc4\x14D\x1cD\x14\xc4\x0c\xc4\x14D\x1dB\x16'
b'\xc2\x0e\xc2\x16B\x1eB>B\x1dD<D\x1cD<'
b'D\x1dB>B"B\x02B\x06B\x02B\x02B\x0e'
b'B\x02B\x02B\x06B\x02B%H\x04L\x0cL\x04'
b'H$H\x04L\x0cL\x04H%B\x02B\x06B\x02'
b'B\x02B\x0eB\x02B\x02B\x06B\x02B2B\n'
b'B\x06B\nB=D\x08D\x04D\x08D<D\x08'
b'D\x04D\x08D=B\nB\x06B\nB>B\x02'
b'B\x06\x82\x06\x82\x06B\x02B=H\x04\x84\x04\x84\x04'
b'H<H\x04\x84\x04\x84\x04H=B\x02B\x06\x82\x06'
b'\x82\x06B\x02B>\x82\x02\x82\x16\x82\x02\x82=\x88\x14'
b'\x88<\x88\x14\x88=\x82\x02\x82\x16\x82\x02\x82>\x82\x02'
b'\x82\x02\x82\x0e\x82\x02\x82\x02\x82=\x8c\x0c\x8c<\x8c\x0c'
b'\x8c=\x82\x02\x82\x02\x82\x0e\x82\x02\x82\x02\x82?\xff\xff'
b'\xe2'
)
class GameOfLifeApp():
"""Application implementing Conway's Game of Life.
"""
NAME = 'Life'
ICON = icon
def __init__(self):
"""Initialize the application."""
self._board = array.array('I', [0] * (64*64//32))
self._next_board = array.array('I', self._board)
self._color = 1
self.touch(None)
def foreground(self):
"""Activate the application."""
self._draw()
wasp.system.request_event(wasp.EventMask.TOUCH)
wasp.system.request_tick(625)
def tick(self, ticks):
"""Notify the application that its periodic tick is due."""
wasp.system.keep_awake()
#t = machine.Timer(id=1, period=8000000)
#t.start()
game_of_life(self._board, 64, 64, self._next_board)
#t1 = t.time()
self._update()
#t2 = t.time()
#t.stop()
#del t
#wasp.watch.drawable.string('{:4.2f}s {:4.2f}s'.format(t1 / 1000000,
# t2 / 1000000), 6, 210)
def touch(self, event):
"""Notify the application of a touchscreen touch event."""
board = self._next_board
for i in range(len(board)):
board[i] = 0
board[62] = 32 << 16
board[64] = 8 << 16
board[66] = 103 << 16
if None != event:
self._update()
def _draw(self):
"""Draw the display from scratch."""
wasp.watch.drawable.fill()
board = self._board
for i in range(len(board)):
board[i] = 0
self._update()
def _update(self):
"""Update the dynamic parts of the application display."""
b = self._board
nb = self._next_board
self._board = nb
self._next_board = b
display = wasp.watch.display
lb = display.linebuffer
alive = lb[0:2*16]
self._color = xorshift12(self._color)
rgbhi = get_color(self._color)
rgblo = rgbhi & 0xff
rgbhi >>= 8
for i in range(0, len(alive), 2):
alive[i] = rgbhi
alive[i+1] = rgblo
for i in (0, 3, 12, 15):
alive[i*2] = 0
alive[i*2+1] = 0
dead = lb[2*16:4*16]
for i in range(len(dead)):
dead[i] = 0
def draw_cell(cell, display, px):
x = ((cell & 0x3f) - 2) * 4
y = ((cell >> 6) -2) * 4
if x < 0 or x >= 240 or y < 0 or y >= 240:
return
display.set_window(x, y, 4, 4)
display.write_data(px)
draw_cell(1, display, alive if b[1//32] & (1 << (1 & 0x1f)) else dead)
v = xorshift12(1)
while 1 != v:
me = b[v//32] & (1 << (v & 0x1f))
nx = nb[v//32] & (1 << (v & 0x1f))
if me != nx:
draw_cell(v, display, alive if nx else dead)
v = xorshift12(v)
draw_cell(0, display, alive if b[0//32] & (1 << (0 & 0x1f)) else dead)

View File

@ -1,65 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
"""Haiku viewer
~~~~~~~~~~~~~~~
These three lines poems are fun to write and fit nicely on a tiny screen.
.. figure:: res/HaikuApp.png
:width: 179
If there is a file called haiku.txt in the flash filesystem then this app
allows it to be displayed three lines at a time using the pager.
This application also (optionally) loads an icon from the filesystem allowing
to be customized to match whether theme your verses are based around.
"""
import wasp
import icons
import io
import sys
from apps.pager import PagerApp
class HaikuApp(PagerApp):
NAME = 'Haiku'
def __init__(self):
# Throw an exception if there is no poetry for us to read...
open('haiku.txt').close()
try:
with open('haiku.rle', 'rb') as f:
self.ICON = f.read()
except:
# Leave the default app icon if none is present
pass
super().__init__('')
self._counter = -4
def foreground(self):
lines = []
self._counter += 4
with open('haiku.txt') as f:
for i in range(self._counter):
_ = f.readline()
lines = [ '', ]
for i in range(3):
lines.append(f.readline())
if len(lines[2]) == 0:
self._counter = 0
f.seek(0)
lines = [ '', ]
for i in range(3):
lines.append(f.readline())
self._msg = '\n'.join(lines)
super().foreground()

View File

@ -1,127 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
"""Heart rate monitor
~~~~~~~~~~~~~~~~~~~~~
A graphing heart rate monitor using a PPG sensor.
.. figure:: res/HeartApp.png
:width: 179
This program also implements some (entirely optional) debug features to
store the raw heart data to the filesystem so that the samples can be used
to further refine the heart rate detection algorithm.
To enable the logging feature select the heart rate application using the
watch UI and then run the following command via wasptool:
.. code-block:: sh
./tools/wasptool --eval 'wasp.system.app.debug = True'
Once debug has been enabled then the watch will automatically log heart
rate data whenever the heart rate application is running (and only
when it is running). Setting the debug flag to False will disable the
logging when the heart rate monitor next exits.
Finally to download the logs for analysis try:
.. code-block:: sh
./tools/wasptool --pull hrs.data
"""
import wasp
import machine
import ppg
class HeartApp():
"""Heart rate monitor application."""
NAME = 'Heart'
def __init__(self):
self._debug = False
self._hrdata = None
def foreground(self):
"""Activate the application."""
wasp.watch.hrs.enable()
# There is no delay after the enable because the redraw should
# take long enough it is not needed
draw = wasp.watch.drawable
draw.fill()
draw.set_color(wasp.system.theme('bright'))
draw.string('PPG graph', 0, 6, width=240)
wasp.system.request_tick(1000 // 8)
self._hrdata = ppg.PPG(wasp.watch.hrs.read_hrs())
if self._debug:
self._hrdata.enable_debug()
self._x = 0
def background(self):
wasp.watch.hrs.disable()
self._hrdata = None
def _subtick(self, ticks):
"""Notify the application that its periodic tick is due."""
draw = wasp.watch.drawable
spl = self._hrdata.preprocess(wasp.watch.hrs.read_hrs())
if len(self._hrdata.data) >= 240:
draw.set_color(wasp.system.theme('bright'))
draw.string('{} bpm'.format(self._hrdata.get_heart_rate()),
0, 6, width=240)
# Graph is orange by default...
color = wasp.system.theme('spot1')
# If the maths goes wrong lets show it in the chart!
if spl > 100 or spl < -100:
color = 0xffff
if spl > 104 or spl < -104:
spl = 0
spl += 104
x = self._x
draw.fill(0, x, 32, 1, 208-spl)
draw.fill(color, x, 239-spl, 1, spl)
x += 2
if x >= 240:
x = 0
self._x = x
def tick(self, ticks):
"""This is an outrageous hack but, at present, the RTC can only
wake us up every 125ms so we implement sub-ticks using a regular
timer to ensure we can read the sensor at 24Hz.
"""
t = machine.Timer(id=1, period=8000000)
t.start()
self._subtick(1)
wasp.system.keep_awake()
while t.time() < 41666:
pass
self._subtick(1)
while t.time() < 83332:
pass
self._subtick(1)
t.stop()
del t
@property
def debug(self):
return self._debug
@debug.setter
def debug(self, value):
self._debug = value
if value and self._hrdata:
self._hrdata.enable_debug()

View File

@ -1,19 +0,0 @@
# SPDX-License-Identifier: MY-LICENSE
# Copyright (C) YEAR(S), AUTHOR
import wasp
class HelloApp():
"""A hello world application for wasp-os."""
NAME = "Hello"
def __init__(self, msg="Hello, world!"):
self.msg = msg
def foreground(self):
self._draw()
def _draw(self):
draw = wasp.watch.drawable
draw.fill()
draw.string(self.msg, 0, 108, width=240)

View File

@ -1,211 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
# Copyright (C) 2020 Carlos Gil
"""Music Player for GadgetBridge
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. figure:: res/MusicApp.png
:width: 179
Screenshot of the Music Player application
Music Player Controller:
* Touch: play/pause
* Swipe UPDOWN: Volume down/up
* Swipe LEFTRIGHT: next/previous
"""
import wasp
import icons
import time
from micropython import const
# 2-bit RLE, generated from res/music_icon.png, 358 bytes
icon = (
b'\x02'
b'`@'
b'\x1e\xa4<\xa4<\xa4;\xa6?X\xec2\xf0/\xf2-'
b'\xf4,\xc3@[n\xc3,\xc3n\xc3,\xc3n\xc3,'
b'\xc3n\xc3,\xc3b\xc7E\xc3,\xc3]\xccE\xc3,'
b'\xc3Z\xcfE\xc3,\xc3T\xd5E\xc3,\xc3Q\xd8E'
b'\xc3,\xc3Q\xd8E\xc3,\xc3Q\xd8E\xc3,\xc3Q'
b'\xd0E\xc3E\xc3,\xc3Q\xccI\xc3E\xc3,\xc3Q'
b'\xc9L\xc3E\xc3,\xc3Q\xc5P\xc3E\xc3,\xc3Q'
b'\xc3R\xc3E\xc3,\xc3Q\xc3R\xc3E\xc3,\xc3Q'
b'\xc3R\xc3E\xc3+\xc4Q\xc3R\xc3E\xc3*\xc5Q'
b'\xc3R\xc3E\xc3*\xc5Q\xc3R\xc3E\xc3*\xc5Q'
b'\xc3R\xc3E\xc3*\xc5Q\xc3R\xc3E\xc3*\xc5Q'
b'\xc3R\xc3E\xc3*\xc5Q\xc3R\xc3E\xc3*\xc5Q'
b'\xc3L\xc4B\xc3E\xc3+\xc4Q\xc3I\xccE\xc3,'
b'\xc3Q\xc3H\xcdE\xc3,\xc3Q\xc3G\xceE\xc3,'
b'\xc3K\xc3C\xc3F\xcfE\xc3,\xc3H\xccF\xcfE'
b'\xc3,\xc3G\xcdF\xcfE\xc3,\xc3F\xceF\xcfE'
b'\xc3,\xc3E\xcfF\xcfE\xc3,\xc3E\xcfG\xceE'
b'\xc3,\xc3D\xd0H\xccF\xc3,\xc3D\xd0I\xcaG'
b'\xc3,\xc3E\xcfK\xc5J\xc3,\xc3E\xce[\xc3,'
b'\xc3F\xcd[\xc3,\xc3G\xca]\xc3,\xc3I\xc7^'
b'\xc3,\xc3n\xc3,\xc3n\xc3,\xc3n\xc3,\xc3n'
b'\xc3,\xf4-\xf2/\xf02\xec?X\xa6;\xa4<\xa4'
b'<\xa4\x1e'
)
DISPLAY_WIDTH = const(240)
ICON_SIZE = const(72)
CENTER_AT = const((DISPLAY_WIDTH - ICON_SIZE) // 2)
class MusicPlayerApp(object):
""" Music Player Controller application."""
NAME = 'Music'
ICON = icon
def __init__(self):
self._pauseplay = wasp.widgets.GfxButton(CENTER_AT, CENTER_AT, icons.play)
self._back = wasp.widgets.GfxButton(0, 120-12, icons.back)
self._fwd = wasp.widgets.GfxButton(240-48, 120-12, icons.fwd)
self._play_state = False
self._musicstate = 'pause'
self._artist = ''
self._track = ''
self._state_changed = True
self._track_changed = True
self._artist_changed = True
def _send_cmd(self, cmd):
print('\r')
for i in range(1):
for i in range(0, len(cmd), 20):
print(cmd[i: i + 20], end='')
time.sleep(0.2)
print(' ')
print(' ')
def _fill_space(self, key):
if key == 'top':
wasp.watch.drawable.fill(
x=0, y=0, w=DISPLAY_WIDTH, h=CENTER_AT)
elif key == 'down':
wasp.watch.drawable.fill(x=0, y=CENTER_AT + ICON_SIZE,
w=DISPLAY_WIDTH,
h=DISPLAY_WIDTH - (CENTER_AT + ICON_SIZE))
def foreground(self):
"""Activate the application."""
state = wasp.system.musicstate.get('state')
artist = wasp.system.musicinfo.get('artist')
track = wasp.system.musicinfo.get('track')
if state:
self._musicstate = state
if self._musicstate == 'play':
self._play_state = True
elif self._musicstate == 'pause':
self._play_state = False
if artist:
self._artist = artist
if track:
self._track = track
wasp.watch.drawable.fill()
self.draw()
wasp.system.request_tick(1000)
wasp.system.request_event(wasp.EventMask.SWIPE_UPDOWN |
wasp.EventMask.TOUCH)
def background(self):
"""De-activate the application (without losing state)."""
self._state_changed = True
self._track_changed = True
self._artist_changed = True
def tick(self, ticks):
wasp.system.keep_awake()
music_state_now = wasp.system.musicstate.get('state')
music_artist_now = wasp.system.musicinfo.get('artist')
music_track_now = wasp.system.musicinfo.get('track')
if music_state_now:
if music_state_now != self._musicstate:
self._musicstate = music_state_now
self._state_changed = True
else:
self._state_changed = False
wasp.system.musicstate = {}
if music_track_now:
if music_track_now != self._track:
self._track = music_track_now
self._track_changed = True
else:
self._track_changed = False
if music_artist_now:
if music_artist_now != self._artist:
self._artist = music_artist_now
self._artist_changed = True
else:
self._artist_changed = False
wasp.system.musicinfo = {}
self._update()
def swipe(self, event):
"""
Notify the application of a touchscreen swipe event.
"""
if event[0] == wasp.EventType.UP:
self._send_cmd('{"t":"music", "n":"volumeup"} ')
elif event[0] == wasp.EventType.DOWN:
self._send_cmd('{"t":"music", "n":"volumedown"} ')
def touch(self, event):
if self._pauseplay.touch(event):
self._play_state = not self._play_state
if self._play_state:
self._musicstate = 'play'
self._pauseplay.gfx = icons.pause
self._pauseplay.draw()
self._send_cmd('{"t":"music", "n":"play"} ')
else:
self._musicstate = 'pause'
self._pauseplay.gfx = icons.play
self._pauseplay.draw()
self._send_cmd('{"t":"music", "n":"pause"} ')
elif self._back.touch(event):
self._send_cmd('{"t":"music", "n":"previous"} ')
elif self._fwd.touch(event):
self._send_cmd('{"t":"music", "n":"next"} ')
def draw(self):
"""Redraw the display from scratch."""
self._draw()
def _draw(self):
"""Redraw the updated zones."""
if self._state_changed:
self._pauseplay.draw()
if self._track_changed:
self._draw_label(self._track, 24 + 144)
if self._artist_changed:
self._draw_label(self._artist, 12)
self._back.draw()
self._fwd.draw()
def _draw_label(self, label, pos):
"""Redraw label info"""
if label:
draw = wasp.watch.drawable
chunks = draw.wrap(label, 240)
self._fill_space(pos)
for i in range(len(chunks)-1):
sub = label[chunks[i]:chunks[i+1]].rstrip()
draw.string(sub, 0, pos + 24 * i, 240)
def _update(self):
if self._musicstate == 'play':
self._play_state = True
self._pauseplay.gfx = icons.pause
elif self._musicstate == 'pause':
self._play_state = False
self._pauseplay.gfx = icons.play
self._draw()
def update(self):
pass

View File

@ -1,217 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Miguel Rochefort
"""Play 2048
~~~~~~~~~~~~
A popular sliding block puzzle game in which tiles are combined to make the
number 2048.
.. figure:: res/2048App.png
:width: 179
Screenshot of the 2048 game application
"""
import wasp
import icons
import widgets
import random
import fonts
from micropython import const
SCREEN_SIZE = const(240)
GRID_PADDING = const(8)
GRID_SIZE = const(4)
CELL_SIZE = const(50)
GRID_BACKGROUND = const(0x942F)
CELL_BACKGROUND = [0x9CB1, 0xEF3B, 0xEF19, 0xF58F, 0xF4AC, 0xF3EB, 0xF2E7, 0xEE6E, 0xEE6C, 0xEE4A, 0xEE27, 0xEE05]
CELL_FOREGROUND = [0x9CB1, 0x736C, 0x736C, 0xFFBE, 0xFFBE, 0xFFBE, 0xFFBE, 0xFFBE, 0xFFBE, 0xFFBE, 0xFFBE, 0xFFBE]
CELL_LABEL = ['','2','4','8','16','32','64','128','256','512','1K','2K'] # TODO: Display 1024 and 2048 (text-wrapping)
# 2-bit RLE, generated from res/2048_icon.png, 578 bytes
icon = (
b'\x02'
b'`@'
b'\x10\xbf\x01 \xbf\x01 \xbf\x01 \x83@\x81M\x82M'
b'\x82M\x82\x80\xc8\x8d\xc0\xdb\xc3 \xc3M\xc2M\xc2M'
b'\xc2\x8d\xc3 \xc3M\xc2M\xc2M\xc2\x8d\xc3 \xc3M'
b'\xc2M\xc2M\xc2\x8d\xc3 \xc3M\xc2M\xc2M\xc2\x8d'
b'\xc3 \xc3M\xc2M\xc2M\xc2\x8d\xc3 \xc3M\xc2M'
b'\xc2M\xc2\x8d\xc3 \xc3M\xc2M\xc2M\xc2\x8d\xc3 '
b'\xc3M\xc2M\xc2M\xc2\x8d\xc3 \xc3M\xc2M\xc2M'
b'\xc2\x8d\xc3 \xc3M\xc2M\xc2M\xc2\x8d\xc3 \xc3M'
b'\xc2M\xc2M\xc2\x8d\xc3 \xc3M\xc2M\xc2M\xc2\x8d'
b'\xc3 \xff\x01 \xff\x01 \xc3M\xc2M\xc2M\xc2@'
b'\xfaM\xc3 \xc3\x80\x81\x8d\xc2\x8d\xc2\x8d\xc2M\xc3 '
b'\xc3\x8d\xc2\x8d\xc2\x8d\xc2M\xc3 \xc3\x8d\xc2\x8d\xc2\x8d'
b'\xc2M\xc3 \xc3\x8d\xc2\x8d\xc2\x8d\xc2M\xc3 \xc3\x8d'
b'\xc2\x8d\xc2\x8d\xc2M\xc3 \xc3\x8d\xc2\x8d\xc2\x8d\xc2M'
b'\xc3 \xc3\x8d\xc2\x8d\xc2\x8d\xc2M\xc3 \xc3\x8d\xc2\x8d'
b'\xc2\x8d\xc2M\xc3 \xc3\x8d\xc2\x8d\xc2\x8d\xc2M\xc3 '
b'\xc3\x8d\xc2\x8d\xc2\x8d\xc2M\xc3 \xc3\x8d\xc2\x8d\xc2\x8d'
b'\xc2M\xc3 \xc3\x8d\xc2\x8d\xc2\x8d\xc2M\xc3 \xff\x01'
b' \xff\x01 \xc3\xc0\xfb\xcd@\xdbB\x8dB\x8dB\xcd'
b'C C\xcdB\x8dB\x8dB\xcdC C\xcdB\x8d'
b'B\x8dB\xcdC C\xcdB\x8dB\x8dB\xcdC '
b'C\xcdB\x8dB\x8dB\xcdC C\xcdB\x8dB\x8d'
b'B\xcdC C\xcdB\x8dB\x8dB\xcdC C\xcd'
b'B\x8dB\x8dB\xcdC C\xcdB\x8dB\x8dB\xcd'
b'C C\xcdB\x8dB\x8dB\xcdC C\xcdB\x8d'
b'B\x8dB\xcdC C\xcdB\x8dB\x8dB\xcdC '
b'C\xcdB\x8dB\x8dB\xcdC \x7f\x01 \x7f\x01 '
b'C\x8dB\x8dB\x80\xf6\x8dB\x8dC C\xc0\x81\xcd'
b'B\xcdB\x8dB\x8dC C\xcdB\xcdB\x8dB\x8d'
b'C C\xcdB\xcdB\x8dB\x8dC C\xcdB\xcd'
b'B\x8dB\x8dC C\xcdB\xcdB\x8dB\x8dC '
b'C\xcdB\xcdB\x8dB\x8dC C\xcdB\xcdB\x8d'
b'B\x8dC C\xcdB\xcdB\x8dB\x8dC C\xcd'
b'B\xcdB\x8dB\x8dC C\xcdB\xcdB\x8dB\x8d'
b'C C\xcdB\xcdB\x8dB\x8dC C\xcdB\xcd'
b'B\x8dB\x8dC \x7f\x01 \x7f\x01 \x7f\x01\x10'
)
class Play2048App():
"""Let's play the 2048 game."""
NAME = '2048'
ICON = icon
def __init__(self):
"""Initialize the application."""
self._board = None
self._state = 0
self._confirmation_view = None
def foreground(self):
"""Activate the application."""
wasp.system.request_event(wasp.EventMask.TOUCH |
wasp.EventMask.SWIPE_UPDOWN |
wasp.EventMask.SWIPE_LEFTRIGHT)
self._state = 0
if not self._board:
self._start_game()
self._draw()
def touch(self,event):
"""Notify the application of a touchscreen touch event."""
if self._state == 0:
if not self._confirmation_view:
self._confirmation_view = widgets.ConfirmationView()
self._confirmation_view.draw('Restart game?')
self._state = 1
elif self._state == 1:
if self._confirmation_view.touch(event):
if self._confirmation_view.value:
self._start_game()
self._draw()
self._state = 0
def swipe(self, event):
"""Notify the application of a touchscreen swipe event."""
moved = False
if self._state == 0:
if event[0] == wasp.EventType.UP:
moved = self._shift(1,False)
elif event[0] == wasp.EventType.DOWN:
moved = self._shift(-1,False)
elif event[0] == wasp.EventType.LEFT:
moved = self._shift(1,True)
elif event[0] == wasp.EventType.RIGHT:
moved = self._shift(-1,True)
if moved:
self._add_tile()
def _draw(self):
"""Draw the display from scratch."""
board = self._board
draw = wasp.watch.drawable
draw.fill(GRID_BACKGROUND)
draw.set_font(fonts.sans24)
for y in range(GRID_SIZE):
for x in range(GRID_SIZE):
self._update(draw, board[y][x], y, x)
def _update(self, draw, cell, row, col):
"""Update the specified cell of the application display."""
x = GRID_PADDING + (col * (CELL_SIZE + GRID_PADDING))
y = GRID_PADDING + (row * (CELL_SIZE + GRID_PADDING))
draw.set_color(CELL_FOREGROUND[cell], CELL_BACKGROUND[cell])
draw.fill(CELL_BACKGROUND[cell], x, y, CELL_SIZE, CELL_SIZE)
draw.string(CELL_LABEL[cell], x, y + 16, CELL_SIZE)
def _start_game(self):
"""Start a new game."""
self._board = self._create_board()
self._add_tile()
self._add_tile()
def _create_board(self):
"""Create an empty 4x4 board."""
board = []
for _ in range(GRID_SIZE):
board.append([0] * GRID_SIZE)
return board
def _add_tile(self):
"""Add a new tile to a random empty location on the board."""
board = self._board
randint = random.randint
y = randint(0, GRID_SIZE-1)
x = randint(0, GRID_SIZE-1)
while board[y][x] != 0:
y = randint(0, GRID_SIZE-1)
x = randint(0, GRID_SIZE-1)
board[y][x] = 1
self._update(wasp.watch.drawable,1,y,x)
def _shift(self, direction, orientation):
"""Shift and merge the tiles vertically."""
draw = wasp.watch.drawable
update = self._update
board = self._board
moved = False
def read(y, x):
if not orientation:
y,x = x,y
return board[y][x]
def write(y, x, v):
if not orientation:
y,x = x,y
board[y][x] = v
update(draw, v, y, x)
if direction > 0:
s = 0 + 1
e = GRID_SIZE
else:
s = GRID_SIZE - 1 - 1
e = 0 - 1
for y in range(GRID_SIZE):
p = s - direction
for x in range(s,e,direction):
a = read(y,x)
b = read(y,p)
if a != 0:
if a == b:
write(y, p, a + 1)
write(y, x, 0)
moved = True
p += direction
else:
if b != 0:
p += direction
if x != p:
write(y, p, a)
write(y, x, 0)
moved = True
return moved

View File

@ -1,142 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020-21 Daniel Thompson
"""Settings application
~~~~~~~~~~~~~~~~~~~~~~~
Allows a very small set of user preferences (including the date and
time) to be set on the device itself.
.. figure:: res/SettingsApp.png
:width: 179
.. note::
The settings tool is not expected to comprehensively present every
user configurable preference. Some are better presented via a
companion app and some particular exotic ones are perhaps best
managed with a user-provided ``main.py``.
"""
import wasp
import fonts
import icons
class SettingsApp():
"""Settings application."""
NAME = 'Settings'
ICON = icons.settings
def __init__(self):
self._slider = wasp.widgets.Slider(3, 10, 90)
self._nfy_slider = wasp.widgets.Slider(3, 10, 90)
self._scroll_indicator = wasp.widgets.ScrollIndicator()
self._HH = wasp.widgets.Spinner(50, 60, 0, 23, 2)
self._MM = wasp.widgets.Spinner(130, 60, 0, 59, 2)
self._dd = wasp.widgets.Spinner(20, 60, 1, 31, 1)
self._mm = wasp.widgets.Spinner(90, 60, 1, 12, 1)
self._yy = wasp.widgets.Spinner(160, 60, 20, 60, 2)
self._settings = ['Brightness', 'Notification Level', 'Time', 'Date']
self._sett_index = 0
self._current_setting = self._settings[0]
def foreground(self):
self._slider.value = wasp.system.brightness - 1
self._draw()
wasp.system.request_event(wasp.EventMask.TOUCH)
wasp.system.request_event(wasp.EventMask.SWIPE_UPDOWN)
def touch(self, event):
if self._current_setting == 'Brightness':
self._slider.touch(event)
wasp.system.brightness = self._slider.value + 1
elif self._current_setting == 'Notification Level':
self._nfy_slider.touch(event)
wasp.system.notify_level = self._nfy_slider.value + 1
elif self._current_setting == 'Time':
if self._HH.touch(event) or self._MM.touch(event):
now = list(wasp.watch.rtc.get_localtime())
now[3] = self._HH.value
now[4] = self._MM.value
wasp.watch.rtc.set_localtime(now)
elif self._current_setting == 'Date':
if self._yy.touch(event) or self._mm.touch(event) \
or self._dd.touch(event):
now = list(wasp.watch.rtc.get_localtime())
now[0] = self._yy.value + 2000
now[1] = self._mm.value
now[2] = self._dd.value
wasp.watch.rtc.set_localtime(now)
self._update()
def swipe(self, event):
"""Handle NEXT events by augmenting the default processing by resetting
the count if we are not currently timing something.
No other swipe event is possible for this application.
"""
if event[0] == wasp.EventType.UP:
self._sett_index += 1
self._draw()
elif event[0] == wasp.EventType.DOWN:
self._sett_index -= 1
self._draw()
def _draw(self):
"""Redraw the display from scratch."""
draw = wasp.watch.drawable
mute = wasp.watch.display.mute
self._current_setting = self._settings[self._sett_index % len(self._settings)]
mute(True)
draw.fill()
draw.set_color(wasp.system.theme('bright'))
draw.set_font(fonts.sans24)
draw.string(self._current_setting, 0, 6, width=240)
if self._current_setting == 'Brightness':
self._slider.value = wasp.system.brightness - 1
elif self._current_setting == 'Notification Level':
self._nfy_slider.value = wasp.system.notify_level - 1
elif self._current_setting == 'Time':
now = wasp.watch.rtc.get_localtime()
self._HH.value = now[3]
self._MM.value = now[4]
draw.set_font(fonts.sans28)
draw.string(':', 110, 120-14, width=20)
self._HH.draw()
self._MM.draw()
elif self._current_setting == 'Date':
now = wasp.watch.rtc.get_localtime()
self._yy.value = now[0] - 2000
self._mm.value = now[1]
self._dd.value = now[2]
self._yy.draw()
self._mm.draw()
self._dd.draw()
draw.set_font(fonts.sans24)
draw.string('DD MM YY',0,180, width=240)
self._scroll_indicator.draw()
self._update()
mute(False)
def _update(self):
draw = wasp.watch.drawable
draw.set_color(wasp.system.theme('bright'))
if self._current_setting == 'Brightness':
if wasp.system.brightness == 3:
say = "High"
elif wasp.system.brightness == 2:
say = "Mid"
else:
say = "Low"
self._slider.update()
draw.string(say, 0, 150, width=240)
elif self._current_setting == 'Notification Level':
if wasp.system.notify_level == 3:
say = "High"
elif wasp.system.notify_level == 2:
say = "Mid"
else:
say = "Silent"
self._nfy_slider.update()
draw.string(say, 0, 150, width=240)

View File

@ -1,175 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Johannes Wache
"""Snake Game
~~~~~~~~~~~~~
This is a classic arcade game called snake.
.. figure:: res/SnakeApp.png
:width: 179
Screenshot of the snake game
You have to direct the white snake to the food block (blue dot) by swiping in the desired direction. You must not hit the border or the snake's body itself.
Every time the snake eats the food, its length increases by 1. (In the current version there is an error that the length of the snake is not increased by 1 when the snake gets the food for the first time. This has to be fixed).
Once the game is over, you can try again by tapping on the screen and then swipe in the direction you want to move. If you want to leave the game, simply wipe in any direction once the game is over.
And now: Have fun playing! :)
"""
# 2-bit RLE, generated from res/snake_icon.png, 280 bytes
snake_icon = (
b'\x02'
b'`@'
b'4@\x81M?\x14M?\x14M?\x14M?\x14M'
b'?\x14M?\x14M?\x14M?\x14M?U\x80\x0b'
b'\x8f?\x12\x8f\x10\xcd\x02\xcd%\x8f\x10\xcd\x02\xcd%\x8f'
b'\x10\xcd\x02\xcd%\x8f\x10\xcd\x02\xcd%\x8f\x10\xcd\x02\xcd'
b'%\x8f\x10\xcd\x02\xcd%\x8f\x10\xcd\x02\xcd%\x8f\x10\xcd'
b'\x02\xcd%\x8f\x10\xcd\x02\xcd%\x8f\x10\xcd\x02\xcd%\x8f'
b'\x10\xcd\x02\xcd%\x8f\x10\xcd\x02\xcd%\x8f\x10\xcd\x02\xcd'
b'%\x8f?s\xcd \xcd&\xcd \xcd&\xcd \xcd&'
b'\xcd \xcd&\xcd \xcd&\xcd \xcd&\xcd \xcd&'
b'\xcd \xcd&\xcd \xcd&\xcd \xcd&\xcd \xcd&'
b'\xcd \xcd&\xcd \xcd?\xa7\xcd\x02\xcd\x02\xcd\x02\xcd'
b'&\xcd\x02\xcd\x02\xcd\x02\xcd&\xcd\x02\xcd\x02\xcd\x02\xcd'
b'&\xcd\x02\xcd\x02\xcd\x02\xcd&\xcd\x02\xcd\x02\xcd\x02\xcd'
b'&\xcd\x02\xcd\x02\xcd\x02\xcd&\xcd\x02\xcd\x02\xcd\x02\xcd'
b'&\xcd\x02\xcd\x02\xcd\x02\xcd&\xcd\x02\xcd\x02\xcd\x02\xcd'
b'&\xcd\x02\xcd\x02\xcd\x02\xcd&\xcd\x02\xcd\x02\xcd\x02\xcd'
b'&\xcd\x02\xcd\x02\xcd\x02\xcd&\xcd\x02\xcd\x02\xcd\x02\xcd'
b'?\xff\xff\xff\x94'
)
import wasp, time
from random import randint
class SnakeGameApp():
NAME = 'Snake'
ICON = snake_icon
def __init__(self):
self.running = True
self.snake = Snake()
self.food_location()
self.highscore = 1
def foreground(self):
"""Activate the application."""
wasp.watch.drawable.fill()
if self.running:
self.update()
else:
self.snake.show()
wasp.watch.drawable.fill(x=self.food[0],y=self.food[1],w=15,h=15,bg=0x00ff)
wasp.system.request_event(wasp.EventMask.TOUCH |
wasp.EventMask.SWIPE_UPDOWN |
wasp.EventMask.SWIPE_LEFTRIGHT)
wasp.system.request_tick(250)
def touch(self,event):
if not self.running:
self.running = True
wasp.watch.drawable.fill()
def swipe(self, event):
if self.running:
"""Notify the application of a touchscreen swipe event."""
if event[0] == wasp.EventType.UP:
self.snake.set_dir(0,-15)
elif event[0] == wasp.EventType.DOWN:
self.snake.set_dir(0,15)
elif event[0] == wasp.EventType.LEFT:
self.snake.set_dir(-15,0)
elif event[0] == wasp.EventType.RIGHT:
self.snake.set_dir(15,0)
else:
return True
def tick(self, ticks):
"""Notify the application that its periodic tick is due."""
self.update()
def food_location(self):
x = randint(0,15) * 15
y = randint(0,15) * 15
self.food = [x,y]
def update(self):
draw = wasp.watch.drawable
"""Draw the display from scratch."""
if (self.snake.eat(self.food)):
self.food_location()
self.snake.update()
if (self.snake.end_game()):
if len(self.snake.body) > self.highscore:
self.highscore = len(self.snake.body)
self.running = False
wasp.watch.vibrator.pulse()
self.snake = Snake()
draw.fill()
draw.set_color(0xf000)
draw.string('GAME', 0, 60, width=240)
draw.string('OVER', 0, 98, width=240)
draw.string('Highscore: '+str(self.highscore-1),0,180,width=240)
draw.reset()
return True
if self.running:
self.snake.show()
draw.fill(x=self.food[0],y=self.food[1],w=15,h=15,bg=0x00ff)
return True
# Based on https://www.youtube.com/watch?v=OMoVcohRgZA
class Snake():
def __init__(self):
self.body = [[120,120]]
self.xdir = 0
self.ydir = 0
self.justate = False
self.oldtail = [0,0]
def set_dir(self,x,y):
self.xdir = x
self.ydir = y
def update(self):
self.oldtail = self.body[0].copy()
head = self.body[-1].copy()
if not self.justate:
self.body = self.body[1:]
self.justate = False
head[0] += self.xdir
head[1] += self.ydir
self.body.append(head)
def eat(self,pos):
x = self.body[-1][0]
y = self.body[-1][1]
if (x == pos[0] and y == pos[1]):
self.justate = True
# Color food white so it appears as a body part:
wasp.watch.drawable.fill(x=(self.body[-1][0]),y=(self.body[-1][1]),w=15,h=15,bg=0x0000)
wasp.watch.drawable.fill(x=self.body[-1][0]+1,y=self.body[-1][1]+1,w=13,h=13,bg=0xffff)
return True
return False
def end_game(self):
x = self.body[-1][0]
y = self.body[-1][1]
if (x >= 240 or x < 0) or (y >= 240 or y < 0):
return True
for i in range(len(self.body)-1):
part = self.body[i]
if (part[0] == x and part[1] == y):
return True
return False
def show(self):
draw = wasp.watch.drawable
draw.fill(x=self.oldtail[0],y=self.oldtail[1],w=15,h=15,bg=0x0000)
draw.fill(x=self.body[-1][0]+1,y=self.body[-1][1]+1,w=13,h=13,bg=0xffff)

View File

@ -1,114 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
"""Software
~~~~~~~~~~~
A tool to enable/disable applications.
.. figure:: res/SoftwareApp.png
:width: 179
Most applications are disabled by default at boot in order to conserve
RAM (which is in short supply and very useful to anyone wanting to
write an application). This tools allows us to boot and conserve RAM
whilst still allowing users to activate so many awesome applications!
"""
import wasp
import icons
class SoftwareApp():
"""Enable and disable applications."""
NAME = 'Software'
ICON = icons.software
def foreground(self):
"""Activate the application."""
def factory(label):
nonlocal y
cb = wasp.widgets.Checkbox(0, y, label)
y += 40
if y > 160:
y = 0
return cb
y = 0
db = []
db.append(('alarm', factory('Alarm')))
db.append(('calc', factory('Calculator')))
db.append(('chrono', factory('Chrono')))
db.append(('dual_clock', factory('Dual Clock')))
db.append(('fibonacci_clock', factory('Fibonacci Clock')))
db.append(('gameoflife', factory('Game Of Life')))
db.append(('musicplayer', factory('Music Player')))
db.append(('play2048', factory('Play 2048')))
db.append(('snake', factory('Snake Game')))
db.append(('flashlight', factory('Torch')))
db.append(('testapp', factory('Test')))
db.append(('timer', factory('Timer')))
db.append(('weather', factory('Weather')))
db.append(('word_clock', factory('Word Clock')))
# Get the initial state for the checkboxes
for _, checkbox in db:
label = checkbox.label.replace(' ', '')
for app in wasp.system.launcher_ring:
if type(app).__name__.startswith(label):
checkbox.state = True
break
self.si = wasp.widgets.ScrollIndicator()
self.page = 0
self.db = db
self._draw()
wasp.system.request_event(wasp.EventMask.TOUCH |
wasp.EventMask.SWIPE_UPDOWN)
def background(self):
del self.si
del self.page
del self.db
def get_page(self):
i = self.page * 5
return self.db[i:i+5]
def swipe(self, event):
"""Notify the application of a touchscreen swipe event."""
page = self.page
pages = (len(self.db)-1) // 5
if event[0] == wasp.EventType.DOWN:
page = page - 1 if page > 0 else pages
if event[0] == wasp.EventType.UP:
page = page + 1 if page < pages else 0
self.page = page
mute = wasp.watch.display.mute
mute(True)
self._draw()
mute(False)
def touch(self, event):
"""Notify the application of a touchscreen touch event."""
for module, checkbox in self.get_page():
if checkbox.touch(event):
label = checkbox.label.replace(' ', '')
if checkbox.state:
wasp.system.register('apps.{}.{}App'.format(module, label))
else:
for app in wasp.system.launcher_ring:
if type(app).__name__.startswith(label):
wasp.system.launcher_ring.remove(app)
break
break
def _draw(self):
"""Draw the display from scratch."""
wasp.watch.drawable.fill()
self.si.draw()
for _, checkbox in self.get_page():
checkbox.draw()

View File

@ -1,177 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
"""Step counter
~~~~~~~~~~~~~~~
Provide a daily step count.
.. figure:: res/StepsApp.png
:width: 179
The step counts automatically reset at midnight.
"""
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():
"""Step counter application."""
NAME = 'Steps'
ICON = icons.app
def __init__(self):
watch.accel.reset()
self._scroll = wasp.widgets.ScrollIndicator()
self._wake = 0
def foreground(self):
"""Cancel the alarm and draw the application.
Cancelling the alarm has two effects. Firstly it ensures the
step count won't change whilst we are watching it and, secondly
it ensures that if the time of day has been set to a value in
the past that we reconfigure the alarm.
This does in the side effect that if the application of open at
midnight then the reset doesn't happen for that day.
"""
wasp.system.cancel_alarm(self._wake, self._reset)
wasp.system.bar.clock = True
self._page = -1
self._draw()
wasp.system.request_event(wasp.EventMask.SWIPE_UPDOWN)
wasp.system.request_tick(1000)
def background(self):
"""Set an alarm to trigger at midnight and reset the counter."""
now = watch.rtc.get_localtime()
yyyy = now[0]
mm = now[1]
dd = now[2]
then = (yyyy, mm, dd+1, 0, 0, 0, 0, 0, 0)
self._wake = time.mktime(then)
wasp.system.set_alarm(self._wake, self._reset)
def _reset(self):
""""Reset the step counter and re-arm the alarm."""
watch.accel.steps = 0
self._wake += 24 * 60 * 60
wasp.system.set_alarm(self._wake, self._reset)
def swipe(self, event):
if event[0] == wasp.EventType.DOWN:
if self._page == -1:
return
self._page -= 1
else:
self._page += 1
mute = wasp.watch.display.mute
mute(True)
self._draw()
mute(False)
def tick(self, ticks):
if self._page == -1:
self._update()
def _draw(self):
"""Draw the display from scratch."""
draw = wasp.watch.drawable
draw.fill()
if self._page == -1:
self._update()
wasp.system.bar.draw()
else:
self._update_graph()
def _update(self):
draw = wasp.watch.drawable
# Draw the icon
draw.blit(feet, 12, 132-24)
# Update the status bar
now = wasp.system.bar.update()
# Update the scroll indicator
scroll = self._scroll
scroll.up = False
scroll.draw()
# Update the step count
count = watch.accel.steps
t = str(count)
w = fonts.width(fonts.sans36, t)
draw.set_font(fonts.sans36)
draw.set_color(draw.lighten(wasp.system.theme('spot1'), wasp.system.theme('contrast')))
draw.string(t, 228-w, 132-18)
def _update_graph(self):
draw = watch.drawable
draw.set_font(fonts.sans24)
draw.set_color(0xffff)
# Draw the date
now = int(watch.rtc.time())
then = now - ((24*60*60) * self._page)
walltime = time.localtime(then)
draw.string('{:02d}-{:02d}'.format(walltime[2], walltime[1]), 0, 0)
# Get the iterable step date for the currently selected date
data = wasp.system.steps.data(then)
# Bail if there is no data
if not data:
draw.string('No data', 239-160, 0, 160, right=True)
return
color = wasp.system.theme('spot2')
# Draw the frame
draw.fill(0x3969, 0, 39, 240, 1)
draw.fill(0x3969, 0, 239, 240, 1)
for i in (0, 60, 90, 120, 150, 180, 239):
draw.fill(0x3969, i, 39, 1, 201)
total = 0
for x, d in enumerate(data):
if d == 0 or x < 2:
# TODO: the x < 2 conceals BUGZ
continue
total += d
d = d // 4
if d > 200:
draw.fill(0xffff, x, 239-200, 1, 200)
else:
draw.fill(color, x, 239-d, 1, d)
draw.string(str(total), 239-160, 0, 160, right=True)

View File

@ -1,98 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
"""The complete set of wasp-os application entry points are documented
below as part of a template application. Note that the template does
not rely on any specific parent class. This is because applications in
wasp-os can rely on *duck typing* making a class hierarchy pointless.
"""
import wasp
import icons
class TemplateApp():
"""Template application.
The template application includes every application entry point. It
is used as a reference guide and can also be used as a template for
creating new applications.
.. data:: NAME = 'Template'
Applications must provide a short ``NAME`` that is used by the
launcher to describe the application. Names that are longer than
8 characters are likely to be abridged by the launcher in order
to fit on the screen.
.. data:: ICON = RLE2DATA
Applications can optionally provide an icon for display by the
launcher. Applications that expect to be installed on the quick
ring will not be listed by the launcher and need not provide any
icon. When no icon is provided the system will use a default
icon.
The icon is an opportunity to differentiate your application from others
so supplying an icon is strongly recommended. The icon, when provided,
must not be larger than 96x64.
"""
NAME = 'Template'
ICON = icons.app
def __init__(self):
"""Initialize the application."""
pass
def foreground(self):
"""Activate the application."""
self._draw()
wasp.system.request_event(wasp.EventMask.TOUCH |
wasp.EventMask.SWIPE_UPDOWN |
wasp.EventMask.BUTTON)
wasp.system.request_tick(1000)
def background(self):
"""De-activate the application."""
pass
def sleep(self):
"""Notify the application the device is about to sleep."""
return False
def wake(self):
"""Notify the application the device is waking up."""
pass
def press(self, button, state):
"""Notify the application of a button-press event."""
draw = wasp.watch.drawable
draw.string('Button', 0, 108, width=240)
def swipe(self, event):
"""Notify the application of a touchscreen swipe event."""
draw = wasp.watch.drawable
if event[0] == wasp.EventType.UP:
draw.string('Swipe up', 0, 108, width=240)
else:
draw.string('Swipe down', 0, 108, width=240)
def tick(self, ticks):
"""Notify the application that its periodic tick is due."""
pass
def touch(self, event):
"""Notify the application of a touchscreen touch event."""
draw = wasp.watch.drawable
wasp.watch.drawable.string('({}, {})'.format(
event[1], event[2]), 0, 108, width=240)
def _draw(self):
"""Draw the display from scratch."""
draw = wasp.watch.drawable
draw.fill()
draw.string(self.NAME, 0, 6, width=240)
self._update()
def _update(self):
"""Update the dynamic parts of the application display."""
pass

View File

@ -1,267 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
"""Self Tests
~~~~~~~~~~~~~
A collection of tests used to develop features or provide useful metrics such
as performance indicators or memory usage.
.. figure:: res/SelfTestApp.png
:width: 179
"""
import wasp
import gc
import fonts
import icons
import machine
from apps.pager import PagerApp
class TestApp():
"""Self test application."""
NAME = 'Self Test'
ICON = icons.app
def __init__(self):
self.tests = ('Alarm', 'Button', 'Checkbox', 'Crash', 'Colours', 'Fill', 'Fill-H', 'Fill-V', 'Free Mem', 'Line', 'Notifications', 'RLE', 'String', 'Touch', 'Wrap')
self.test = self.tests[0]
self.scroll = wasp.widgets.ScrollIndicator()
self._checkbox = wasp.widgets.Checkbox(4, 104, 'Check me')
self._sliders = (
wasp.widgets.Slider(32, 10, 90, 0xf800),
wasp.widgets.Slider(64, 10, 140, 0x27e4),
wasp.widgets.Slider(32, 10, 190, 0x211f),
)
self._spinner = wasp.widgets.Spinner(90, 60, 0, 99)
def foreground(self):
"""Activate the application."""
self.on_screen = ( -1, -1, -1, -1, -1, -1 )
self._draw()
wasp.system.request_event(wasp.EventMask.TOUCH |
wasp.EventMask.SWIPE_UPDOWN |
wasp.EventMask.BUTTON)
def press(self, button, state):
draw = wasp.watch.drawable
if self.test == 'Alarm':
self._test_alarm()
elif self.test == 'Button':
draw.string('{}: {}'.format(button, state), 0, 108, width=240)
elif self.test == 'Crash':
self.crash()
elif self.test == 'String':
self._benchmark_string()
elif self.test == 'Touch':
draw.string('Button', 0, 108, width=240)
def swipe(self, event):
tests = self.tests
i = tests.index(self.test)
if event[0] == wasp.EventType.UP:
i += 1
if i >= len(tests):
i = 0
else:
i -= 1
if i < 0:
i = len(tests) - 1
self.test = tests[i]
self._draw()
def touch(self, event):
if self.test == 'Checkbox':
self._checkbox.touch(event)
elif self.test == 'Colours':
if event[2] > 90:
s = self._sliders[(event[2] - 90) // 50]
s.touch(event)
s.update()
self.scroll.draw()
self._update_colours()
elif self.test.startswith('Fill'):
self._benchmark_fill()
elif self.test == 'Notifications':
if self._spinner.touch(event):
notifications = wasp.system.notifications
if len(notifications) > self._spinner.value:
wasp.system.unnotify(
next(iter(notifications.keys())))
else:
wasp.system.notify(wasp.watch.rtc.get_uptime_ms(),
{
"src":"Hangouts",
"title":"A Name",
"body":"message contents"
})
elif self.test == 'RLE':
self._benchmark_rle()
elif self.test == 'String':
self._benchmark_string()
elif self.test == 'Touch':
wasp.watch.drawable.string('({}, {})'.format(
event[1], event[2]), 0, 108, width=240)
elif self.test == 'Wrap':
self._benchmark_wrap()
elif self.test == 'Line':
self._benchmark_line()
def _alarm(self):
wasp.system.wake()
wasp.system.switch(PagerApp('Alarm triggered'))
def _test_alarm(self):
def nop():
pass
now = wasp.watch.rtc.time()
wasp.system.set_alarm(now + 30, self._alarm)
wasp.system.set_alarm(now + 30, nop)
if not wasp.system.cancel_alarm(now + 30, nop):
bug()
wasp.watch.drawable.string("Done.", 12, 24+80)
def _benchmark_rle(self):
draw = wasp.watch.drawable
draw.fill(0, 0, 30, 240, 240-30)
self.scroll.draw()
t = machine.Timer(id=1, period=8000000)
t.start()
for i in range(0, 128, 16):
draw.blit(icons.software, i+16, i+32)
elapsed = t.time()
t.stop()
del t
draw.string('{}s'.format(elapsed / 1000000), 12, 24+192)
def _benchmark_fill(self):
draw = wasp.watch.drawable
draw.fill(0, 0, 30, 240, 240-30)
self.scroll.draw()
t = machine.Timer(id=1, period=8000000)
if self.test == 'Fill':
t.start()
draw.fill(0xffff, 60, 60, 120, 120)
elapsed = t.time()
elif self.test == 'Fill-H':
t.start()
for i in range(60, 180, 2):
draw.fill(0xffff, 60, i, 120, 1)
elapsed = t.time()
elif self.test == 'Fill-V':
t.start()
for i in range(60, 180, 2):
draw.fill(0xffff, i, 60, 1, 120)
elapsed = t.time()
t.stop()
del t
draw.string('{}s'.format(elapsed / 1000000), 12, 24+192)
def _benchmark_string(self):
draw = wasp.watch.drawable
draw.fill(0, 0, 30, 240, 240-30)
draw.set_color(0xffff, 0x4208)
self.scroll.draw()
t = machine.Timer(id=1, period=8000000)
t.start()
draw.string("The quick brown", 12, 24+24)
draw.string("fox jumped over", 12, 24+48)
draw.string("the lazy dog.", 12, 24+72)
draw.string("0123456789", 12, 24+120, width=228)
draw.string('!"ÂŁ$%^&*()', 12, 24+144, width=228)
elapsed = t.time()
t.stop()
del t
draw.string('{}s'.format(elapsed / 1000000), 12, 24+192)
def _benchmark_line(self):
draw = wasp.watch.drawable
# instead of calculating by trig functions, use LUT
points = (0, 50), (19, 46), (35, 35), (46, 19),
draw.fill(0, 70, 70, 100, 100)
self.scroll.draw()
t = machine.Timer(id=1, period=8000000)
t.start()
for x, y in points:
draw.line(120, 120, 120+x, 120+y, 4, 0xfb00) # red
draw.line(120, 120, 120+y, 120-x, 3, 0x07c0) # green
draw.line(120, 120, 120-x, 120-y, 5, 0x6b3f) # blue
draw.line(120, 120, 120-y, 120+x, 2, 0xffe0) # yellow
elapsed = t.time()
t.stop()
del t
draw.string('{}s'.format(elapsed / 1000000), 12, 24+192)
def _benchmark_wrap(self):
draw = wasp.watch.drawable
draw.fill(0, 0, 30, 240, 240-30)
self.scroll.draw()
t = machine.Timer(id=1, period=8000000)
t.start()
draw = wasp.watch.drawable
s = 'This\nis a very long string that will need to be wrappedinmultipledifferentways!'
chunks = draw.wrap(s, 240)
for i in range(len(chunks)-1):
sub = s[chunks[i]:chunks[i+1]].rstrip()
draw.string(sub, 0, 48+24*i)
elapsed = t.time()
t.stop()
del t
draw.string('{}s'.format(elapsed / 1000000), 12, 24+192)
def _draw(self):
"""Redraw the display from scratch."""
wasp.watch.display.mute(True)
draw = wasp.watch.drawable
draw.fill()
draw.set_font(fonts.sans24)
draw.string('{} test'.format(self.test),
0, 6, width=240)
if self.test == 'Alarm':
draw.string("Press button to", 12, 24+24)
draw.string("set alarm.", 12, 24+48)
elif self.test == 'Checkbox':
self._checkbox.draw()
elif self.test == 'Crash':
draw.string("Press button to", 12, 24+24)
draw.string("throw exception.", 12, 24+48)
elif self.test == 'Colours':
for s in self._sliders:
s.draw()
self._update_colours()
elif self.test == 'Free Mem':
if wasp.watch.free:
draw.string("Boot: {}".format(wasp.watch.free), 12, 3*24)
draw.string("Init: {}".format(wasp.free), 12, 4*24)
draw.string("Now: {}".format(gc.mem_free()), 12, 5*24)
gc.collect()
draw.string("GC: {}".format(gc.mem_free()), 12, 6*24)
else:
draw.string("Not supported", 12, 4*24)
elif self.test == 'Notifications':
self._spinner.value = len(wasp.system.notifications)
self._spinner.draw()
elif self.test == 'RLE':
draw.blit(self.ICON, 120-48, 120-32)
self.scroll.draw()
wasp.watch.display.mute(False)
def _update_colours(self):
draw = wasp.watch.drawable
r = self._sliders[0].value
g = self._sliders[1].value
b = self._sliders[2].value
rgb = (r << 11) + (g << 5) + b
draw.string('RGB565 #{:04x}'.format(rgb), 0, 6, width=240)
draw.fill(rgb, 60, 35, 120, 50)

View File

@ -1,164 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
# Copyright (C) 2020 Carlos Gil
"""Weather for GadgetBridge and wasp-os companion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. figure:: res/WeatherApp.png
:width: 179
Screenshot of the Weather application
"""
import wasp
import icons
import time
import fonts.sans36
# 2-bit RLE, generated from res/weather_icon.png, 309 bytes
icon = (
b'\x02'
b'`@'
b'\x1e\xa4<\xa4<\xa4;\xa5?Y\xec2\xf0/\xf2-'
b'\xf4,\xc3@;n\xc3,\xc3n\xc3,\xc3n\xc3,'
b'\xc3n\xc3,\xc3n\xc3,\xc3n\xc3,\xc3n\xc3,'
b'\xc3n\xc3,\xc3n\xc3,\xc3O\x80\xd3\x87X\xc3,'
b'\xc3M\x8bV\xc3,\xc3K\x8fT\xc3,\xc3J\x91S'
b'\xc3,\xc3I\x93R\xc3,\xc3H\x95Q\xc3,\xc3H'
b'\x95Q\xc3,\xc3G\x97P\xc3,\xc3G\x97P\xc3+'
b'\xc4F\x99O\xc3*\xc5F\x99O\xc3*\xc5F\x99O'
b'\xc3*\xc5F\x99O\xc3*\xc5F\x99O\xc3*\xc5F'
b'\x99\xc4K\xc3*\xc5F\x8e\xc5\x84\xc1\x81\xc5J\xc3*'
b'\xc5G\x8c\xc8\x81\xcaH\xc3+\xc4G\x83\xc5\x83\xd6F'
b'\xc3,\xc3H\x81\xe0E\xc3,\xc3G\xe2E\xc3,\xc3'
b'G\xe3D\xc3,\xc3F\xe4D\xc3,\xc3F\xe4D\xc3'
b',\xc3F\xe4D\xc3,\xc3F\xe4D\xc3,\xc3F\xe3'
b'E\xc3,\xc3G\xe2E\xc3,\xc3G\xe1F\xc3,\xc3'
b'H\xc9A\xccC\xc5H\xc3,\xc3J\xc5E\xc9Q\xc3'
b',\xc3V\xc5S\xc3,\xc3n\xc3,\xc3n\xc3,\xc3'
b'n\xc3,\xc3n\xc3,\xc3n\xc3,\xc3n\xc3,\xf4'
b'-\xf2/\xf02\xec?X\xc0\xdb\xe6;\xe4<\xe4<'
b'\xe4\x1e'
)
class WeatherApp(object):
""" Weather application."""
NAME = 'Weather'
ICON = icon
def __init__(self):
self._temp = ''
self._hum = ''
self._txt = ''
self._wind = ''
self._loc = ''
self._temp_changed = True
self._hum_changed = True
self._txt_changed = True
self._wind_changed = True
self._loc_changed = True
def foreground(self):
"""Activate the application."""
temp = wasp.system.weatherinfo.get('temp')
hum = wasp.system.weatherinfo.get('hum')
txt = wasp.system.weatherinfo.get('txt')
wind = wasp.system.weatherinfo.get('wind')
loc = wasp.system.weatherinfo.get('loc')
if temp:
self._temp = temp
if hum:
self._hum = hum
if txt:
self._txt = txt
if wind:
self._wind = wind
if loc:
self._loc = loc
wasp.watch.drawable.fill()
self.draw()
wasp.system.request_tick(1000)
def background(self):
"""De-activate the application (without losing state)."""
self._temp_changed = True
self._hum_changed = True
self._txt_changed = True
self._wind_changed = True
self._loc_changed = True
def tick(self, ticks):
wasp.system.keep_awake()
temp_now = wasp.system.weatherinfo.get('temp')
hum_now = wasp.system.weatherinfo.get('hum')
txt_now = wasp.system.weatherinfo.get('txt')
wind_now = wasp.system.weatherinfo.get('wind')
loc_now = wasp.system.weatherinfo.get('loc')
if temp_now:
if temp_now != self._temp:
self._temp = temp_now
self._temp_changed = True
else:
self._temp_changed = False
if hum_now:
if hum_now != self._hum:
self._hum = hum_now
self._hum_changed = True
else:
self._hum_changed = False
if txt_now:
if txt_now != self._txt:
self._txt = txt_now
self._txt_changed = True
else:
self._txt_changed = False
if wind_now:
if wind_now != self._wind:
self._wind = wind_now
self._wind_changed = True
else:
self._wind_changed = False
if loc_now:
if loc_now != self._loc:
self._loc = loc_now
self._loc_changed = True
else:
self._loc_changed = False
wasp.system.weatherinfo = {}
self._update()
def draw(self):
"""Redraw the display from scratch."""
self._draw()
def _draw(self):
"""Redraw the updated zones."""
if self._temp_changed:
self._draw_label(self._temp, 54, 36)
if self._hum_changed:
self._draw_label("Humidity: "+self._hum, 160)
if self._txt_changed:
self._draw_label(self._txt, 12)
if self._wind_changed:
self._draw_label("Wind: "+self._wind, 120)
if self._loc_changed:
self._draw_label(self._loc, 200)
def _draw_label(self, label, pos, size = 24):
"""Redraw label info"""
if label:
draw = wasp.watch.drawable
draw.reset()
if size == 36:
draw.set_font(fonts.sans36)
draw.string(label, 0, pos, 240)
def _update(self):
self._draw()
def update(self):
pass

View File

@ -1,195 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
# Copyright (C) 2021 Brendan Sleight
"""Word clock
~~~~~~~~~~~~~~~~
Shows a time as words together with a battery meter and the date.
.. figure:: res/WordClkApp.png
:width: 179
"""
import wasp
import icons
MONTH = 'JanFebMarAprMayJunJulAugSepOctNovDec'
class WordClockApp():
"""Simple digital clock application."""
NAME = 'WordClk'
ICON = icons.app
def foreground(self):
"""Activate the application.
Configure the status bar, redraw the display and request a periodic
tick callback every second.
"""
wasp.system.bar.clock = False
self._draw(True)
wasp.system.request_tick(1000)
def sleep(self):
"""Prepare to enter the low power mode.
:returns: True, which tells the system manager not to automatically
switch to the default application before sleeping.
"""
return True
def wake(self):
"""Return from low power mode.
Time will have changes whilst we have been asleep so we must
udpate the display (but there is no need for a full redraw because
the display RAM is preserved during a sleep.
"""
self._draw()
def tick(self, ticks):
"""Periodic callback to update the display."""
self._draw()
def _draw(self, redraw=False):
"""Draw or lazily update the display.
The updates are as lazy by default and avoid spending time redrawing
if the time on display has not changed. However if redraw is set to
True then a full redraw is be performed.
"""
draw = wasp.watch.drawable
hi = wasp.system.theme('bright')
if redraw:
now = wasp.watch.rtc.get_localtime()
# Clear the display and draw that static parts of the watch face
draw.fill()
# Redraw the status bar
wasp.system.bar.draw()
else:
# The update is doubly lazy... we update the status bar and if
# the status bus update reports a change in the time of day
# then we compare the minute on display to make sure we
# only update the main clock once per minute.
now = wasp.system.bar.update()
if not now or self._min == now[4]:
# Skip the update
return
draw.set_color(hi)
# Format the month as text
month = now[1] - 1
month = MONTH[month*3:(month+1)*3]
# Record the minute that is currently being displayed
self._hour = now[3]
self._min = now[4]
# Testing
# self._hour = 23
# self._min = 59
# Convert to words
part_day = ""
hour = ""
part_hour = ""
minute_words= ""
part_day = ""
hours_a = ["midnight", "one", "two",
"three", "four", "five",
"six", "seven", "eight",
"nine", "ten", "eleven",
"twelve",
"one", "two",
"three", "four", "five",
"six", "seven", "eight",
"nine", "ten", "eleven"]
if (self._min > 32):
hour = hours_a[(self._hour + 1) % 24]
else:
hour = hours_a[self._hour % 24]
if (hour != "midnight" and hour != "twelve"):
if (self._hour >= 22):
part_day = " at night"
elif (self._hour >= 18):
part_day = " in the evening"
elif (self._hour >= 12):
part_day = " in the afternoon"
elif (self._hour >= 6):
part_day = " in the morning"
elif (self._hour >= 3):
part_day = " in the early hours"
elif (self._hour >= 0):
part_day = " at night"
if (self._min > 57):
part_hour = ""
elif (self._min > 52):
part_hour = "five to "
elif (self._min > 47):
part_hour = "ten to "
elif (self._min > 42):
part_hour = "quarter to "
elif (self._min > 37):
part_hour = "twenty to "
elif (self._min > 32):
part_hour = "twenty-five to "
elif (self._min > 27):
part_hour = "half past "
elif (self._min > 22):
part_hour = "twenty-five past "
elif (self._min > 17):
part_hour = "twenty past "
elif (self._min > 12):
part_hour = "quarter past "
elif (self._min > 7):
part_hour = "ten past "
elif (self._min > 2):
part_hour = "five past "
else:
part_hour = ""
minute_words_int = (self._min % 5)
if (minute_words_int == 4):
minute_words = "almost"
if (minute_words_int == 3):
minute_words = "coming up to"
if (minute_words_int == 2):
minute_words = "after"
if (minute_words_int == 1):
minute_words = "just gone"
self._words = ""
if (minute_words !=""):
self._words = minute_words + "\n"
if (part_hour !=""):
self._words = self._words + part_hour + "\n"
self._words = self._words + hour + "\n"
if (part_day !=""):
self._words = self._words + part_day
# No capitilise in Micropython
# ASCII convert
self._words = chr(ord(self._words[0])-32) + self._words[1:]
# Some phases may be 5 lines long, some may be 1
draw.fill(0, 0, 48)
chunks = draw.wrap(self._words, 240)
lines_of_text = len(chunks)-1
offset_y=int(((5-lines_of_text/2)*26))
for i in range(len(chunks)-1):
sub = self._words[chunks[i]:chunks[i+1]].rstrip()
draw.string(sub, 0, offset_y+26*i, 240)
draw.string('{} {} {}'.format(now[2], month, now[0]),
0, 214, width=240)

View File

@ -1,13 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
freeze('../..',
(
'drivers/battery.py',
'drivers/signal.py',
'drivers/vibrator.py',
),
opt=3
)
freeze('.', 'watch.py', opt=3)

View File

@ -1,8 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
from machine import RTCounter as RTC
# Start measuring time (and feeding the watchdog)
rtc = RTC(1, mode=RTC.PERIODIC)
rtc.start()

View File

@ -1,34 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
import os, sys
sys.path.append(os.path.dirname(os.getcwd()))
import manifest_240x240
freeze('.', 'watch.py', opt=3)
freeze('../..', manifest_240x240.manifest +
(
'boot.py',
'draw565.py',
'drivers/bma421.py',
'drivers/battery.py',
'drivers/hrs3300.py',
'drivers/nrf_rtc.py',
'drivers/signal.py',
'drivers/st7789.py',
'drivers/touch.py',
'drivers/vibrator.py',
'gadgetbridge.py',
'ppg.py',
'shell.py',
'wasp.py',
),
opt=3
)
freeze('../../drivers/flash',
(
'bdevice.py',
'flash/flash_spi.py'
), opt=3
)

View File

@ -1,141 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
def nop():
pass
schedule = nop
def _callback(obj):
schedule()
# Start measuring time (and feeding the watchdog) before *anything* else
from machine import RTCounter
from drivers.nrf_rtc import RTC
rtc = RTC(RTCounter(1, mode=RTCounter.PERIODIC, period=1, callback=_callback))
rtc.counter.start()
import gc
import os
import time
import draw565
from machine import I2C
from machine import Pin
#from machine import Signal
from machine import SPI
from drivers.battery import Battery
from drivers.bma421 import BMA421
from drivers.touch import TouchButton
from drivers.hrs3300 import HRS3300
from drivers.signal import Signal
from drivers.st7789 import ST7789_SPI
from drivers.vibrator import Vibrator
from flash.flash_spi import FLASH
from ubluepy import uart_connected as connected
class Backlight(object):
lo = Pin("BL_LO", Pin.OUT, value=0)
mid = Pin("BL_MID", Pin.OUT, value=1)
hi = Pin("BL_HI", Pin.OUT, value=1)
def __init__(self, level=1):
self.set(level)
def set(self, level):
"""K9 backlight is very weak so we avoid lo in its own because it is
pointless.
"""
hi = 1
mid = 1
lo = 0
if level >= 3:
hi = 0
mid = 0
elif level == 2:
hi = 0
elif level == 1:
mid = 0
else:
lo = 1
self.hi(hi)
self.mid(mid)
self.lo(lo)
# Setup the display (and manage the backlight)
backlight = Backlight(0)
spi = SPI(0)
spi.init(polarity=1, phase=1, baudrate=8000000)
display = ST7789_SPI(240, 240, spi,
cs=Pin("DISP_CS", Pin.OUT),
dc=Pin("DISP_DC", Pin.OUT),
res=Pin("DISP_RST", Pin.OUT))
drawable = draw565.Draw565(display)
def boot_msg(s):
drawable.string(s, 0, 108, width=240)
if safe_mode:
time.sleep_ms(500)
safe_mode = False
boot_msg("Init button")
button = Pin('BUTTON', Pin.IN)
safe_mode = button.value()
if safe_mode:
backlight.set(2)
time.sleep(1)
try:
# Setup the last few bits and pieces
boot_msg("Init battery")
battery = Battery(
Pin('BATTERY', Pin.IN),
Signal(Pin('CHARGING', Pin.IN), invert=True),
Signal(Pin('USB_PWR', Pin.IN), invert=True))
boot_msg("Init I2C")
i2c = I2C(1, scl='I2C_SCL', sda='I2C_SDA')
boot_msg("Init BMA421")
accel = BMA421(i2c)
boot_msg("Init HRS3300")
hrs = HRS3300(i2c)
boot_msg("Init touch")
touch = TouchButton(Pin('TP_INT', Pin.IN),
Pin('TP_RST', Pin.OUT, value=0), _callback)
boot_msg("Init motor")
vibrator = Vibrator(Pin('MOTOR', Pin.OUT, value=0), active_low=True)
# Mount the filesystem
boot_msg("Init SPINOR")
flash = FLASH(spi, (Pin('NOR_CS', Pin.OUT, value=1),))
try:
boot_msg("Mount FS")
os.mount(flash, '/flash')
except AttributeError:
# Format the filesystem (and provide a default version of main.py)
boot_msg("Format FS")
os.VfsLfs2.mkfs(flash)
boot_msg("Retry mount FS")
os.mount(flash,'/flash')
boot_msg("Write main.py")
with open('/flash/main.py', 'w') as f:
f.write('''\
#include('main.py')
''')
# Only change directory if the button is not pressed (this will
# allow us access to fix any problems with main.py)!
if not safe_mode:
boot_msg("Enter /flash")
os.chdir('/flash')
boot_msg("main.py")
else:
boot_msg("Safe mode")
except:
drawable.string("FAILED", 0, 136, width=240)
backlight.set(2)
gc.collect()
free = gc.mem_free()

View File

@ -1,14 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
freeze('../..',
(
'drivers/battery.py',
'drivers/signal.py',
'drivers/st7789.py',
'drivers/vibrator.py',
),
opt=3
)
freeze('.', 'watch.py', opt=3)

View File

@ -1,8 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
from machine import RTCounter as RTC
# Start measuring time (and feeding the watchdog)
rtc = RTC(1, mode=RTC.PERIODIC)
rtc.start()

View File

@ -1,34 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
import os, sys
sys.path.append(os.path.dirname(os.getcwd()))
import manifest_240x240
freeze('.', 'watch.py', opt=3)
freeze('../..', manifest_240x240.manifest +
(
'boot.py',
'draw565.py',
'drivers/bma421.py',
'drivers/battery.py',
'drivers/cst816s.py',
'drivers/hrs3300.py',
'drivers/nrf_rtc.py',
'drivers/signal.py',
'drivers/st7789.py',
'drivers/vibrator.py',
'gadgetbridge.py',
'ppg.py',
'shell.py',
'wasp.py',
),
opt=3
)
freeze('../../drivers/flash',
(
'bdevice.py',
'flash/flash_spi.py'
), opt=3
)

View File

@ -1,129 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
def nop():
pass
schedule = nop
def _callback(obj):
schedule()
# Start measuring time (and feeding the watchdog) before *anything* else
from machine import RTCounter
from drivers.nrf_rtc import RTC
rtc = RTC(RTCounter(1, mode=RTCounter.PERIODIC, period=1, callback=_callback))
rtc.counter.start()
import gc
import os
import time
import draw565
from machine import I2C
from machine import Pin
#from machine import Signal
from machine import SPI
from drivers.battery import Battery
from drivers.bma421 import BMA421
from drivers.cst816s import CST816S
from drivers.hrs3300 import HRS3300
from drivers.signal import Signal
from drivers.st7789 import ST7789_SPI
from drivers.vibrator import Vibrator
from flash.flash_spi import FLASH
from ubluepy import uart_connected as connected
class Backlight(object):
lo = Pin("BL_LO", Pin.OUT, value=0)
mid = Pin("BL_MID", Pin.OUT, value=1)
hi = Pin("BL_HI", Pin.OUT, value=1)
def __init__(self, level=1):
self.set(level)
def set(self, level):
hi = 1
mid = 1
lo = 1
if level >= 3:
hi = 0
elif level == 2:
mid = 0
elif level == 1:
lo = 0
self.hi(hi)
self.mid(mid)
self.lo(lo)
# Setup the display (and manage the backlight)
backlight = Backlight(0)
spi = SPI(0)
spi.init(polarity=1, phase=1, baudrate=8000000)
display = ST7789_SPI(240, 240, spi,
cs=Pin("DISP_CS", Pin.OUT),
dc=Pin("DISP_DC", Pin.OUT),
res=Pin("DISP_RST", Pin.OUT))
drawable = draw565.Draw565(display)
def boot_msg(s):
drawable.string(s, 0, 108, width=240)
if safe_mode:
time.sleep_ms(500)
safe_mode = False
boot_msg("Init button")
button = Pin('BUTTON', Pin.IN)
safe_mode = button.value()
if safe_mode:
backlight.set(1)
time.sleep(1)
try:
# Setup the last few bits and pieces
boot_msg("Init hardware")
usb_pwr = Signal(Pin('USB_PWR', Pin.IN), invert=True)
battery = Battery(Pin('BATTERY', Pin.IN), usb_pwr, usb_pwr)
i2c = I2C(1, scl='I2C_SCL', sda='I2C_SDA')
accel = BMA421(i2c)
hrs = HRS3300(i2c)
touch = CST816S(i2c,
Pin('TP_INT', Pin.IN), Pin('TP_RST', Pin.OUT, value=0),
_callback)
vibrator = Vibrator(Pin('MOTOR', Pin.OUT, value=0), active_low=True)
# Mount the filesystem
boot_msg("Init SPINOR")
flash = FLASH(spi, (Pin('NOR_CS', Pin.OUT, value=1),))
try:
boot_msg("Mount FS")
os.mount(flash, '/flash')
except AttributeError:
# Format the filesystem (and provide a default version of main.py)
boot_msg("Format FS")
os.VfsLfs2.mkfs(flash)
boot_msg("Retry mount FS")
os.mount(flash,'/flash')
boot_msg("Write main.py")
with open('/flash/main.py', 'w') as f:
f.write('''\
#include('main.py')
''')
# Only change directory if the button is not pressed (this will
# allow us access to fix any problems with main.py)!
if not safe_mode:
boot_msg("Enter /flash")
os.chdir('/flash')
boot_msg("main.py")
else:
boot_msg("Safe mode")
except:
drawable.string("FAILED", 0, 136, width=240)
backlight.set(1)
gc.collect()
free = gc.mem_free()

View File

@ -11,15 +11,12 @@ freeze('../..', manifest_240x240.manifest +
(
'boot.py',
'draw565.py',
'drivers/bma421.py',
'drivers/battery.py',
'drivers/cst816s.py',
'drivers/hrs3300.py',
'drivers/nrf_rtc.py',
'drivers/signal.py',
'drivers/st7789.py',
'drivers/vibrator.py',
'ppg.py',
'shell.py',
'wasp.py',
),

View File

@ -21,20 +21,15 @@ import draw565
from machine import I2C
from machine import Pin
#from machine import Signal
from machine import SPI
from drivers.battery import Battery
from drivers.bma421 import BMA421
from drivers.cst816s import CST816S
from drivers.hrs3300 import HRS3300
from drivers.signal import Signal
from drivers.st7789 import ST7789_SPI
from drivers.vibrator import Vibrator
from flash.flash_spi import FLASH
from ubluepy import uart_connected as connected
class Backlight(object):
lo = Pin("BL_LO", Pin.OUT, value=0)
mid = Pin("BL_MID", Pin.OUT, value=1)
@ -90,8 +85,6 @@ try:
Signal(Pin('CHARGING', Pin.IN), invert=True),
Signal(Pin('USB_PWR', Pin.IN), invert=True))
i2c = I2C(1, scl='I2C_SCL', sda='I2C_SDA')
accel = BMA421(i2c)
hrs = HRS3300(i2c)
touch = CST816S(i2c,
Pin('TP_INT', Pin.IN), Pin('TP_RST', Pin.OUT, value=0),
_callback)

View File

@ -3,28 +3,9 @@
import wasp
# Test app is used a lot on the simulator. Let's make sure it is
# registered by default.
wasp.system.register('apps.testapp.TestApp')
# Ensure there's something interesting to look at ;-)
wasp.system.set_music_info({
'track': 'Tasteless Brass Duck',
'artist': 'Dreams of Bamboo',
})
wasp.system.set_weather_info({
'temp': '22',
'hum': '100%',
'txt': 'Cloudy',
'wind': '25km/h',
'loc': 'Toronto'
})
# Increase the display blanking time to avoid spamming the console
# with backlight activations.
wasp.system.blank_after = 300
wasp.system.blank_after = 3600
# Instantiate the analogue clock application and replace the default
# (digital) clock with this alternative.
@ -36,7 +17,6 @@ wasp.system.blank_after = 300
# Adopt a basic all-orange theme
#wasp.system.set_theme(
# b'\xff\x00' # ble
# b'\xff\x00' # scroll-indicator
# b'\xff\x00' # battery
# b'\xff\x00' # status-clock

View File

@ -25,29 +25,6 @@ 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):
pass
@ -133,47 +110,6 @@ class RTC(object):
def get_uptime_ms(self):
return int(self.uptime * 1000)
class HRS():
DATA = (
9084,9084,9025,9025,9009,9009,9009,9015,9015,9024,9024,9024,9073,9073,9074,9074,
9074,9100,9100,9097,9097,9097,9045,9045,9023,9023,9023,9035,9035,9039,9039,9039,
9049,9049,9052,9052,9052,9066,9066,9070,9070,9070,9078,9078,9081,9081,9081,9092,
9092,9093,9093,9093,9094,9094,9108,9108,9108,9124,9124,9122,9122,9122,9100,9100,
9110,9110,9110,9112,9112,9118,9118,9118,9127,9127,9136,9136,9136,9147,9147,9154,
9154,9154,9156,9156,9153,9153,9153,9152,9152,9156,9156,9156,9161,9161,9161,9177,
9177,9186,9186,9196,9196,9196,9201,9201,9201,9189,9189,9176,9176,9176,9176,9176,
9175,9175,9175,9175,9175,9180,9180,9180,9189,9189,9202,9202,9202,9207,9207,9181,
9181,9181,9167,9167,9169,9169,9169,9163,9163,9164,9164,9164,9165,9165,9172,9172,
9172,9180,9180,9192,9192,9192,9178,9178,9161,9161,9161,9163,9163,9173,9173,9173,
9170,9170,9179,9179,9183,9183,9183,9196,9196,9207,9207,9207,9208,9208,9186,9186,
9186,9182,9182,9193,9193,9193,9197,9197,9188,9204,9204,9212,9212,9212,9223,9223,
9228,9228,9228,9235,9235,9215,9215,9215,9217,9217,9225,9225,9225,9230,9230,9237,
9237,9237,9246,9246,9260,9260,9260,9270,9270,9269,9269,9269,9256,9256,9256,9256,
9256,9263,9263,9274,9274,9274,9288,9288,9292,9292,9292,9307,9307,9310
)
def __init__(self):
self._i = 0
self._step = 1
def enable(self):
pass
def disable(self):
pass
def read_hrs(self):
d = self.DATA[self._i]
self._i += self._step
if self._i >= len(self.DATA):
self._i -= 1
self._step = -1
elif self._i < 0:
self._i += 1
self._step = 1
return d
backlight = Backlight()
spi = SPI(0)
spi.init(polarity=1, phase=1, baudrate=8000000)
@ -183,16 +119,11 @@ 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)
hrs = HRS()
rtc = RTC()
touch = CST816S(I2C(0), Pin('TP_INT', Pin.IN, quiet=True), Pin('TP_RST', Pin.OUT, quiet=True))
vibrator = Vibrator(Pin('MOTOR', Pin.OUT, value=0), active_low=True)
def connected():
return not (int(rtc.uptime / 30) & 1)
# Free memory cannot be measured on the simulator
free = 0

View File

@ -1,13 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
battery = 'Default battery icon'
bomb = 'Small bomb icon'
app = 'Default application icon'
clock = 'Default digital clock icon'
software = 'Default software application icon'
headset = 'Default music player icon'
settings = 'Default settings icon'
torch = 'Default torch or flashlight icon'
up_arrow = 'Small (16x9) up arrow'
down_arrow = 'Small (16x9) down arrow'

Some files were not shown because too many files have changed in this diff Show More