Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .beads/issues.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{"id":"specter-diy.new-6bg","title":"Fix C: Remove /qspi from sys.path in main.c","description":"Comment out /qspi and /qspi/lib from sys.path in stm32/main.c. Eliminates shadowing but users cant add modules to QSPI.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T18:52:46.875253+01:00","updated_at":"2025-12-19T19:27:55.312347+01:00","closed_at":"2025-12-19T19:27:55.312347+01:00","close_reason":"FAILED - CWD still /qspi, '' in sys.path still shadows"}
{"id":"specter-diy.new-9qr","title":"QSPI dirs shadow frozen modules after MicroPython upgrade","description":"New MicroPython uses .frozen as sys.path entry instead of absolute priority. CWD defaults to /qspi, so '' in sys.path finds /qspi/hosts before .frozen/hosts. Settings dirs shadow frozen modules. See .history/lvgl9-migration-lessons.md for details. Recommended fix: rename /qspi/hosts to /qspi/settings/hosts","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-19T18:47:45.871674+01:00","updated_at":"2025-12-19T19:30:24.589192+01:00","closed_at":"2025-12-19T19:30:24.589192+01:00","close_reason":"RESOLVED: Fix D implemented - .frozen first in sys.path. Tested all 4 fixes: A(fail), B(works+migration), C(fail), D(works)","dependencies":[{"issue_id":"specter-diy.new-9qr","depends_on_id":"specter-diy.new-gxv","type":"blocks","created_at":"2025-12-19T18:52:53.130203+01:00","created_by":"daemon"},{"issue_id":"specter-diy.new-9qr","depends_on_id":"specter-diy.new-bkl","type":"blocks","created_at":"2025-12-19T18:52:53.240199+01:00","created_by":"daemon"},{"issue_id":"specter-diy.new-9qr","depends_on_id":"specter-diy.new-6bg","type":"blocks","created_at":"2025-12-19T18:52:53.350989+01:00","created_by":"daemon"},{"issue_id":"specter-diy.new-9qr","depends_on_id":"specter-diy.new-kp3","type":"blocks","created_at":"2025-12-19T18:52:53.460851+01:00","created_by":"daemon"}]}
{"id":"specter-diy.new-bkl","title":"Fix B: Rename settings dirs to /qspi/settings/*","description":"Change Host.SETTINGS_DIR and app paths from /qspi/hosts to /qspi/settings/hosts etc. Cleanest long-term fix.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T18:52:46.610554+01:00","updated_at":"2025-12-19T19:23:08.013163+01:00","closed_at":"2025-12-19T19:23:08.013163+01:00","close_reason":"SUCCESS - works but requires migrating old /qspi dirs"}
{"id":"specter-diy.new-dz2","title":"VFS filesystems not mounting (/flash, /qspi)","description":"sdram.RAMDevice.ioctl() returns None instead of 0 for init/erase. VfsFat.mkfs fails with 'can't convert NoneType to int'. Bug in f469-disco/usermods/sdram module.","status":"open","priority":1,"issue_type":"bug","created_at":"2025-12-19T19:49:19.007935+01:00","updated_at":"2025-12-19T19:53:28.074456+01:00"}
{"id":"specter-diy.new-gxv","title":"Fix A: Change CWD in boot.py to /flash","description":"Change os.chdir('/flash') in boot.py before pyb.main() to avoid /qspi being current dir. Already attempted, needs re-test.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T18:52:46.357046+01:00","updated_at":"2025-12-19T18:54:29.409761+01:00","closed_at":"2025-12-19T18:54:29.409761+01:00","close_reason":"Failed: moving CWD to /flash still shadows because /flash/keystore exists. The '' in sys.path always shadows if CWD has matching dirs."}
{"id":"specter-diy.new-kp3","title":"Fix D: Move .frozen first in sys.path in runtime.c","description":"Insert .frozen at index 0 instead of append in py/runtime.c. Frozen modules always win regardless of CWD.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T18:52:47.141162+01:00","updated_at":"2025-12-19T19:15:17.619462+01:00","closed_at":"2025-12-19T19:15:17.619462+01:00","close_reason":"SUCCESS: Moving .frozen first in sys.path in runtime.c fixes the shadowing issue. Frozen modules now always take priority."}
{"id":"specter-diy.new-lp1","title":"QR scanner returns garbage data","description":"Scanner triggers/beeps/stops but data is binary garbage. PIN trigger mode at 9600 baud. May need different baud rate or scanner config.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-19T16:24:58.042665+01:00","updated_at":"2025-12-19T17:02:06.665561+01:00","closed_at":"2025-12-19T17:02:06.665561+01:00","close_reason":"Scanner works"}
{"id":"specter-diy.new-ok7","title":"Python code migration after MicroPython/LVGL 9.3.0 upgrade","description":"","status":"open","priority":1,"issue_type":"epic","created_at":"2025-12-19T16:24:40.788371+01:00","updated_at":"2025-12-19T16:24:40.788371+01:00","dependencies":[{"issue_id":"specter-diy.new-ok7","depends_on_id":"specter-diy.new-lp1","type":"blocks","created_at":"2025-12-19T16:25:03.594054+01:00","created_by":"daemon"}]}
3 changes: 2 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[submodule "f469-disco"]
path = f469-disco
url = https://github.com/diybitcoinhardware/f469-disco
url = https://github.com/diybitcoinhardware/f469-disco.git
branch = micropython-upgrade
[submodule "bootloader"]
path = bootloader
url = https://github.com/cryptoadvance/specter-bootloader
55 changes: 41 additions & 14 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,23 +1,50 @@
FROM python:3.9.15@sha256:b5f024fa682187ef9305a2b5d2c4bb583bef83356259669fc80273bb2222f5ed
ENV LANG=C.UTF-8
# syntax=docker/dockerfile:1.6
FROM python:3.9.23-bookworm@sha256:dc01447eea126f97459cbcb0e52a5863fcc84ff53462650ae5a28277c175f49d

ENV LANG=C.UTF-8
ARG DEBIAN_FRONTEND=noninteractive
ARG TOOLCHAIN_VER=14.3.rel1
ARG TARGETARCH
ARG TARGET_DIR="/opt/arm-toolchain"

# Minimal deps for download/verify/extract
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl xz-utils grep coreutils \
&& rm -rf /var/lib/apt/lists/*

# ARM Embedded Toolchain
# Integrity is checked using the MD5 checksum provided by ARM at https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
RUN curl -sSfL -o arm-toolchain.tar.bz2 "https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2?revision=05382cca-1721-44e1-ae19-1e7c3dc96118&rev=05382cca172144e1ae191e7c3dc96118&hash=3ACFE672E449EBA7A21773EE284A88BC7DFA5044" && \
echo 2b9eeccc33470f9d3cda26983b9d2dc6 arm-toolchain.tar.bz2 > /tmp/arm-toolchain.md5 && \
md5sum --check /tmp/arm-toolchain.md5 && rm /tmp/arm-toolchain.md5 && \
tar xf arm-toolchain.tar.bz2 -C /opt && \
rm arm-toolchain.tar.bz2
# Default to Arm's blob storage mirror
ARG TOOLCHAIN_MIRROR=armkeil.blob.core.windows.net/developer/Files/downloads/gnu

# Adding GCC to PATH and defining rustup/cargo home directories
ENV PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update/bin:$PATH
# Pin Arm GNU Toolchain per-arch with SHA256 sums
ENV TOOLCHAIN_SHA256_AMD64=8f6903f8ceb084d9227b9ef991490413014d991874a1e34074443c2a72b14dbd
ENV TOOLCHAIN_SHA256_ARM64=2d465847eb1d05f876270494f51034de9ace9abe87a4222d079f3360240184d3

# Installing python requirements
# Arm GNU Toolchain (arm-none-eabi), host-aware, SHA-256 verified, cached download
RUN --mount=type=cache,target=/root/.cache \
set -eux; \
case "$TARGETARCH" in \
amd64) host="x86_64"; TOOLCHAIN_SHA256="$TOOLCHAIN_SHA256_AMD64" ;; \
arm64) host="aarch64"; TOOLCHAIN_SHA256="$TOOLCHAIN_SHA256_ARM64" ;; \
*) echo "Unsupported arch: $TARGETARCH" && exit 1 ;; \
esac; \
file="arm-gnu-toolchain-${TOOLCHAIN_VER}-${host}-arm-none-eabi.tar.xz"; \
url="https://${TOOLCHAIN_MIRROR}/${TOOLCHAIN_VER}/binrel/${file}"; \
dest="/root/.cache/$file"; \
if [ ! -f "$dest" ]; then \
echo "Downloading $url"; \
curl -fSL --retry 5 --retry-all-errors -C - -o "$dest" "$url"; \
else \
echo "Using cached $dest"; \
fi; \
echo "${TOOLCHAIN_SHA256} $dest" | sha256sum -c -; \
tar -xJf "$dest" -C /opt; \
ln -s /opt/arm-gnu-toolchain-${TOOLCHAIN_VER}-${host}-arm-none-eabi ${TARGET_DIR}

ENV PATH="${TARGET_DIR}/bin:${PATH}"

# Python deps
COPY bootloader/tools/requirements.txt .
RUN pip3 install -r requirements.txt
RUN python -m pip install --no-cache-dir -r requirements.txt

WORKDIR /app

CMD ["/usr/bin/env", "bash", "./build_firmware.sh"]
26 changes: 16 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ mpy-cross: $(TARGET_DIR) $(MPY_DIR)/mpy-cross/Makefile
make -C $(MPY_DIR)/mpy-cross \
DEBUG=$(DEBUG) \
CFLAGS_EXTRA="$(MPY_CFLAGS)" && \
cp $(MPY_DIR)/mpy-cross/mpy-cross $(TARGET_DIR)
cp $(MPY_DIR)/mpy-cross/build/mpy-cross $(TARGET_DIR)

# embed git metadata for firmware builds
.PHONY: git-info
Expand All @@ -39,13 +39,18 @@ git-info:
disco: $(TARGET_DIR) mpy-cross $(MPY_DIR)/ports/stm32 git-info
@echo Building firmware
make -C $(MPY_DIR)/ports/stm32 \
BOARD=$(BOARD) \
FLAVOR=$(FLAVOR) \
USE_DBOOT=$(USE_DBOOT) \
USER_C_MODULES=$(USER_C_MODULES) \
FROZEN_MANIFEST=$(FROZEN_MANIFEST_DISCO) \
DEBUG=$(DEBUG) \
CFLAGS_EXTRA="$(MPY_CFLAGS)" && \
BOARD=$(BOARD) \
FLAVOR=$(FLAVOR) \
USE_DBOOT=$(USE_DBOOT) \
USER_C_MODULES=$(USER_C_MODULES) \
FROZEN_MANIFEST=$(FROZEN_MANIFEST_DISCO) \
CFLAGS_EXTRA="-DMP_CONFIGFILE=\"<mpconfigport_specter.h>\" $(MPY_CFLAGS)"
DEBUG=$(DEBUG) && \
arm-none-eabi-objcopy -O binary \
$(MPY_DIR)/ports/stm32/build-STM32F469DISC/firmware.elf \
$(TARGET_DIR)/specter-diy.bin && \
cp $(MPY_DIR)/ports/stm32/build-STM32F469DISC/firmware.hex \
$(TARGET_DIR)/specter-diy.hex
arm-none-eabi-objcopy -O binary \
$(MPY_DIR)/ports/stm32/build-STM32F469DISC/firmware.elf \
$(TARGET_DIR)/specter-diy.bin && \
Expand Down Expand Up @@ -76,8 +81,8 @@ unix: $(TARGET_DIR) mpy-cross $(MPY_DIR)/ports/unix git-info
make -C $(MPY_DIR)/ports/unix \
USER_C_MODULES=$(USER_C_MODULES) \
FROZEN_MANIFEST=$(FROZEN_MANIFEST_UNIX) \
CFLAGS_EXTRA="$(MPY_CFLAGS)" && \
cp $(MPY_DIR)/ports/unix/micropython $(TARGET_DIR)/micropython_unix
CFLAGS_EXTRA="-DMP_CONFIGFILE=\"<mpconfigport_specter.h>\" $(MPY_CFLAGS)" && \
cp $(MPY_DIR)/ports/unix/build-standard/micropython $(TARGET_DIR)/micropython_unix

simulate: unix
$(TARGET_DIR)/micropython_unix simulate.py
Expand All @@ -90,6 +95,7 @@ all: mpy-cross disco unix
clean:
rm -rf $(TARGET_DIR)
make -C $(MPY_DIR)/mpy-cross clean
rm -rf $(MPY_DIR)/mpy-cross/build
make -C $(MPY_DIR)/ports/unix \
USER_C_MODULES=$(USER_C_MODULES) \
FROZEN_MANIFEST=$(FROZEN_MANIFEST_UNIX) clean
Expand Down
6 changes: 5 additions & 1 deletion boot/debug/hardwaretest.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ async def main(self):
await self.qr.enable()
s = await self.qr.get_data()
if s:
data = s.read().decode()
raw = s.read()
try:
data = raw.decode()
except:
data = repr(raw)
await self.gui.alert("Here's what we scanned:", data)
else:
conn = get_connection()
Expand Down
58 changes: 50 additions & 8 deletions docs/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,45 +61,87 @@ You'll need to have [Nix](https://nixos.org/) installed as well.

To compile the firmware for the board you will need `arm-none-eabi-gcc` compiler.

**Debian/Ubuntu**:
#### Debian/Ubuntu

```sh
sudo apt-get install build-essential gcc-arm-none-eabi binutils-arm-none-eabi gdb-multiarch openocd
```

**Archlinux**:
#### Archlinux

```sh
sudo pacman -S arm-none-eabi-gcc arm-none-eabi-binutils openocd base-devel python-case
```
You might need change default gcc flag settings with `CFLAGS_EXTRA="-w"`. Export it or set the variable before of `make`
to avoid warnings being raised as errors.

**MacOS**:
#### MacOS

```sh
brew tap ArmMbed/homebrew-formulae
brew install arm-none-eabi-gcc
```

On **Windows**: Install linux subsystem and follow Linux instructions.
#### Windows

Install the Linux subsystem and follow Linux instructions. The recommended distribution as of this writing is **Ubuntu 22.04 LTS**. It can be installed using the following command in the **administrator's PowerShell**:

```sh
# Install Ubuntu 22.04 as WSL Linux environment
wsl --install -d Ubuntu-22.04
# Start new instance of Ubuntu 22.04 (if multiple distros are present in system)
wsl -d Ubuntu-22.04
```

To install all the dependencies on Windows, make sure the **universe** repository is enabled and the package list is up to date. If not, here is a quick fix:

```sh
# Uncomment all lines in /etc/apt/sources.list that begin with # deb
sudo sed -i 's/^# deb/deb/' /etc/apt/sources.list
# Enable universe repository
sudo add-apt-repository universe
# Update package list
sudo apt update
```

### Prerequisities (Manually): Simulator

You may need to install SDL2 library to simulate the screen of the device.

**Linux**:
#### Linux

```sh
sudo apt install libsdl2-dev
```

**MacOS**:
#### MacOS

```sh
brew install sdl2
```

**Windows**:
To make the SDL2 library available to your C/C++ toolchain, ensure that Homebrew’s include and library paths are added to the compiler and linker flags. If they are not already set, you can add the following lines to your shell profile (commonly `~/.zprofile` or `~/.bash_profile`) or configure them in another way before making the Unix build:

```sh
export LDFLAGS="-L/opt/homebrew/lib $LDFLAGS"
export CPPFLAGS="-idirafter /opt/homebrew/include $CPPFLAGS"
export CFLAGS="-idirafter /opt/homebrew/include $CFLAGS"
```

#### Windows

- `sudo apt install libsdl2-dev` on Linux side.
- install and launch [Xming](https://sourceforge.net/projects/xming/) on Windows side
- set `export DISPLAY=:0` on linux part

[VcXsrv](https://github.com/marchaesen/vcxsrv) is another viable option for running X11 applications on Windows. Here is an example environment that should be configured on the WSL side:

```sh
export LIBGL_ALWAYS_INDIRECT=0
export LIBGL_ALWAYS_SOFTWARE=true
export DISPLAY=127.0.0.1:0
export XDG_RUNTIME_DIR=/mnt/wslg/runtime-dir
```

## Build

All build commands might need a prefix like `nix develop -c` or need to be run from `nix develop` shell. If you use direnv, you don't need to do anything, apart from an initial `direnv allow`.
Expand Down
15 changes: 15 additions & 0 deletions docs/reproducible-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,18 @@ For Apple M1 add a platform flag to the docker commands:
docker build -t diy . --platform linux/x86_64
docker run --platform linux/amd64 -ti -v `pwd`:/app -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) diy
```

# Simplified build without signing

If you just need a HEX file without bootloader and signatures you can use this command:

```sh
docker run -ti -v `pwd`:/app diy bash -c "make clean && make disco"
```

This will generate the binaries of the main firmware, which can be flashed into the Discovery board via ST-LINK or ROM bootloader.

```txt
bin/specter-diy.bin
bin/specter-diy.hex
```
2 changes: 1 addition & 1 deletion f469-disco
Submodule f469-disco updated 50 files
+5 −3 .gitmodules
+2 −1 Makefile
+67 −30 examples/gui/address_navigator.py
+51 −37 examples/gui/counter.py
+9 −18 libs/common/lvqr.py
+1 −1 micropython
+0 −8 usermods/qrcode/micropython.mk
+0 −103 usermods/qrcode/qrcode.c
+0 −1,022 usermods/qrcode/qrcodegen/qrcodegen.c
+0 −311 usermods/qrcode/qrcodegen/qrcodegen.h
+55 −38 usermods/scard/connection.c
+31 −23 usermods/scard/ports/stm32/scard_io.c
+1 −1 usermods/scard/ports/stm32/scard_io.h
+4 −2 usermods/scard/protocols.c
+25 −24 usermods/scard/reader.c
+5 −1 usermods/scard/scard.c
+6 −6 usermods/scard/scard.h
+4 −1 usermods/scard/t1_protocol/t1_protocol.c
+22 −17 usermods/sdram/sdram.c
+1 −1 usermods/secp256k1
+8 −5 usermods/udisplay_f469/display.c
+7 −4 usermods/udisplay_f469/display_unix.c
+7 −1 usermods/udisplay_f469/display_unix_sdl.c
+26 −23 usermods/udisplay_f469/display_unixport/display.py
+2 −2 usermods/udisplay_f469/fonts/font_roboto_mono_12.c
+2 −2 usermods/udisplay_f469/fonts/font_roboto_mono_16.c
+2 −2 usermods/udisplay_f469/fonts/font_roboto_mono_22.c
+2 −2 usermods/udisplay_f469/fonts/font_roboto_mono_28.c
+11 −11 usermods/udisplay_f469/fonts/square.c
+88 −0 usermods/udisplay_f469/generate-bindings.sh
+924 −374 usermods/udisplay_f469/lv_conf.h
+47,059 −0 usermods/udisplay_f469/lv_mpy.c
+0 −31,289 usermods/udisplay_f469/lv_mpy_example.c
+0 −211 usermods/udisplay_f469/lv_sdl_hal/SDL/SDL_monitor.c
+0 −53 usermods/udisplay_f469/lv_sdl_hal/SDL/SDL_monitor.h
+0 −108 usermods/udisplay_f469/lv_sdl_hal/SDL/SDL_mouse.c
+0 −73 usermods/udisplay_f469/lv_sdl_hal/SDL/SDL_mouse.h
+0 −235 usermods/udisplay_f469/lv_sdl_hal/SDL/lv_drv_conf.h
+458 −71 usermods/udisplay_f469/lv_sdl_hal/SDL/modSDL.c
+27 −68 usermods/udisplay_f469/lv_stm_hal/lv_stm_hal.c
+1 −1 usermods/udisplay_f469/lvgl
+6 −10 usermods/udisplay_f469/micropython.mk
+0 −220 usermods/udisplay_f469/pixelart/px_img.c
+0 −37 usermods/udisplay_f469/pixelart/px_img.h
+1 −0 usermods/udisplay_f469/udisplay.qstr
+240 −0 usermods/udisplay_f469/udisplay_demo.py
+8 −5 usermods/uebmit/uembit.c
+12 −12 usermods/uhashlib/crypto/sha2.c
+50 −42 usermods/uhashlib/hashlib.c
+17 −12 usermods/uhashlib/uhmac.c
8 changes: 4 additions & 4 deletions src/gui/async_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,18 @@ def hide_loader(self):
async def load_screen(self, scr):
while self.background is not None:
await asyncio.sleep_ms(10)
old_scr = lv.scr_act()
lv.scr_load(scr)
old_scr = lv.screen_active()
lv.screen_load(scr)
self.scr = scr
old_scr.del_async()
old_scr.delete_async()

async def open_popup(self, scr):
# wait for another popup to finish
while self.background is not None:
await asyncio.sleep_ms(10)
self.background = self.scr
self.scr = scr
lv.scr_load(scr)
lv.screen_load(scr)

async def close_popup(self):
scr = self.background
Expand Down
Loading
Loading