Files
onefinity-firmware/.pi/BUILD.md
Henrik Muehe 54a15f9d12 Rewrite BUILD.md: clean up, add quick start, remove dead weight
- Quick start section at the top (3 commands)
- Removed inline Bullseye build recipe (moved to 'why not' appendix)
- Added build time estimates
- Cleaner table formatting
- gplan.so contents documented (cbang + camotics)
2026-04-30 16:39:57 +02:00

8.6 KiB

Onefinity CNC Firmware — Build, Flash & Backup

Quick Start

# 1. Build gplan.so (first time ~25min, then ~1sec)
.pi/build-gplan.sh

# 2. Build firmware package (frontend + AVR + Python, ~1min)
docker run --rm -v "$(pwd):/workspace" -w /workspace onefin-dev \
  bash -c 'make all && python3 ./setup.py sdist'

# 3. Flash to controller
curl -X PUT -F "firmware=@dist/bbctrl-1.6.7.tar.bz2" \
  -F "password=onefinity" http://10.1.10.55/api/firmware/update

Architecture Overview

The controller is a Raspberry Pi 2/3 (armv7l, Raspbian Stretch, Python 3.5) connected to an ATxmega192a3u AVR over serial. The Pi runs a Tornado web server that serves the UI, parses G-code, and plans motion. The AVR executes realtime step/direction pulses.

Browser ←WebSocket→ Pi (Tornado/Python) → GCode Planner → Serial → AVR → Stepper drivers

The firmware package (bbctrl-X.Y.Z.tar.bz2) contains:

Component Source Description
Python backend src/py/bbctrl/ Tornado web server, state machine, planner bridge
Web frontend build/http/ Pug + Stylus + Svelte → static HTML/JS/CSS
AVR firmware src/avr/bbctrl-avr-firmware.hex Realtime motion controller
gplan.so src/py/camotics/gplan.so CAMotics G-code planner (native ARM C++ extension)
Install scripts scripts/install.sh AVR flash, Python install, service restart

Prerequisites

  • Docker with QEMU binfmt support (default on Docker Desktop for Mac)
  • devcontainer image: docker build -t onefin-dev -f .devcontainer/Dockerfile .devcontainer/
  • SSH access: ssh bbmc@10.1.10.55 (password: onefinity)

Building

Step 1: gplan.so

gplan.so is the CAMotics G-code planner — a C++ Python extension that must be a 32-bit ARM binary linked against Python 3.5. It cannot be built in the devcontainer (wrong arch + wrong Python + wrong glibc).

Build from source (recommended):

.pi/build-gplan.sh

This uses a Raspbian Stretch Docker image (balenalib/raspberry-pi-debian:stretch) with the Pi's exact toolchain: GCC 6.3, Python 3.5, GLIBC 2.24. The image is built once (~25min under QEMU), then cached — subsequent runs take ~1sec.

The image pre-compiles two C++ dependencies:

  • cbang @ 18f1e96 — C++ utility library
  • camotics @ ec876c8 — G-code planner with S-curve motion planning

To force a full rebuild: docker rmi onefin-gplan && .pi/build-gplan.sh

Alternatives (if Docker build fails):

# From official release
curl -sL https://github.com/OneFinityCNC/onefinity-firmware/releases/download/v1.6.6/onefinity-1.6.6.tar.bz2 \
  | tar xjf - --include='*/gplan.so' -O > src/py/camotics/gplan.so

# From the running Pi
scp bbmc@10.1.10.55:/usr/local/lib/python3.5/dist-packages/bbctrl-*.egg/camotics/gplan.so src/py/camotics/

Verify — must show ELF 32-bit ... ARM ... libpython3.5m:

file src/py/camotics/gplan.so
readelf -d src/py/camotics/gplan.so | grep python

Step 2: Firmware package

docker run --rm -v "$(pwd):/workspace" -w /workspace onefin-dev \
  bash -c 'make all && python3 ./setup.py sdist'

This builds inside the devcontainer (arm64 Bullseye — fine for frontend/AVR/Python):

Component Tool Time
Node modules npm install ~30sec
Svelte components vite build ~5sec
Pug/Stylus → HTML pug-cli, stylus ~2sec
AVR firmware avr-g++ (ATxmega192a3u) ~10sec
Boot/Power/Jig MCUs avr-gcc ~5sec
Python sdist setup.py sdist ~2sec

Produces: dist/bbctrl-X.Y.Z.tar.bz2 (~3-4MB)

bbserial.ko (kernel module — usually skip)

Cross-compiles against the Pi's kernel headers (4.9.59-v7+). The Pi already has a working bbserial.ko installed. install.sh skips it gracefully if missing.

Flashing

Via web API (machine running)

curl -X PUT -F "firmware=@dist/bbctrl-1.6.7.tar.bz2" \
  -F "password=onefinity" http://10.1.10.55/api/firmware/update

Or: make update HOST=10.1.10.55

Via SSH (web UI down or crash-looping)

scp dist/bbctrl-1.6.7.tar.bz2 bbmc@10.1.10.55:/tmp/
ssh bbmc@10.1.10.55 'echo onefinity | sudo -S bash -c "
  systemctl stop bbctrl
  mkdir -p /var/lib/bbctrl/firmware
  cp /tmp/bbctrl-1.6.7.tar.bz2 /var/lib/bbctrl/firmware/update.tar.bz2
  /usr/local/bin/update-bbctrl
"'

What happens during flash

  1. update-bbctrl stops bbctrl, extracts tarball to /tmp/update/
  2. install.sh runs:
    • Flashes AVR via scripts/avr109-flash.py (serial bootloader protocol)
    • setup.py install --force — installs Python package + frontend + gplan.so
    • Restarts bbctrl systemd service
    • May reboot if boot config or kernel module changed

Recovery from bad flash

SSH still works even when bbctrl is crash-looping:

  1. Check the error: sudo python3 /usr/local/bin/bbctrl 2>&1 | head -20
  2. Common cause: wrong gplan.so architecture → replace with correct one (see above)
  3. Nuclear option: restore SD card from backup

Running Locally (demo mode)

Full stack in Docker with AVR emulator — no Pi needed:

# Build bbemu (AVR emulator, native in devcontainer)
docker run --rm -v "$(pwd):/workspace" -w /workspace/src/avr/emu onefin-dev make

# Run demo (needs arm64 gplan.so for the container, not armhf)
docker run --rm -d --name onefin-demo \
  -v "$(pwd):/workspace" -w /workspace -p 8765:80 \
  onefin-dev bash -c '
    pip3 install -q tornado sockjs-tornado pyserial watchdog
    cp src/avr/emu/bbemu /usr/local/bin/
    pip3 install -q -e .
    exec bbctrl --demo --port 80 --addr 0.0.0.0 --disable-camera
  '

Open http://localhost:8765 — full UI with emulated controller.

Note: demo mode needs a container-arch gplan.so (arm64 + Python 3.9), not the Pi one. The devcontainer build from the Makefile's gplan target produces this, or it can be built following the procedure in scripts/gplan-build.sh.

SD Card Backup & Restore

# Backup (~50 min, streams raw dd from Pi, compresses locally)
./backup/onefinity-backup.sh backup

# Verify
./backup/onefinity-backup.sh verify backup/onefinity-20260430.img.gz

# Restore to local SD card
./backup/onefinity-backup.sh restore backup/onefinity-20260430.img.gz /dev/diskN

# Restore back to Pi over SSH
./backup/onefinity-backup.sh restore backup/onefinity-20260430.img.gz

Environment: ONEFINITY_HOST (default 10.1.10.55), ONEFINITY_USER (bbmc), ONEFINITY_PASS (onefinity).

Python 3.5 Compatibility

The Pi runs Python 3.5.3. Avoid features added in later versions:

Avoid Use instead
f"hello {name}" "hello %s" % name or "hello {}".format(name)
subprocess.run(capture_output=True) stdout=subprocess.PIPE, stderr=subprocess.PIPE
subprocess.run(text=True) .stdout.decode('utf-8')
dataclasses plain classes with __init__
:= walrus operator separate assignment
asyncio.run() loop.run_until_complete()
dict[str, int] Dict[str, int] from typing

Pi Details

Host 10.1.10.55
SSH bbmc / onefinity
OS Raspbian Stretch (Debian 9)
Kernel 4.9.59-v7+
Python 3.5.3
GCC 6.3.0
GLIBC 2.24 (max symbol: GLIBC_2.24)
GLIBCXX 3.4.22
Arch armv7l (32-bit ARM, EABI5)
SD card 30GB (~2.8GB used)
Service systemctl {start,stop,restart,status} bbctrl
Log /var/log/bbctrl.log or journalctl -u bbctrl
Config /var/lib/bbctrl/config.json
Uploads /var/lib/bbctrl/upload/
Web root /usr/local/lib/python3.5/dist-packages/bbctrl-*.egg/bbctrl/http/
AVR serial /dev/ttyAMA0 at 230400 baud

Why Not Build gplan.so on Bullseye?

Documented for reference — we tried two approaches that don't work:

1. devcontainer (arm64 Bullseye): Wrong ELF class (64-bit vs 32-bit) and wrong Python (3.9 vs 3.5). Cross-compiling with CXX=arm-linux-gnueabihf-g++ fails because SCons ignores CC/CXX environment variables.

2. Bullseye armhf container: Correct architecture, but GCC 10 / glibc 2.31 produce objects requiring GLIBC_2.29+ and GLIBCXX_3.4.26+ symbols. The Pi's Stretch only has GLIBC_2.24 / GLIBCXX_3.4.22. Even -static-libstdc++ -static-libgcc doesn't help — glibc symbols leak through the object files. Relinking against Python 3.5m works but the GLIBC mismatch remains.

3. Plain debian:stretch armhf: The archived repos have broken package metadata — apt-get install build-essential fails with unresolvable version conflicts.

Solution: balenalib/raspberry-pi-debian:stretch with legacy.raspbian.org repos. See .pi/Dockerfile.gplan.