# Onefinity CNC Firmware — Build, Flash & Backup ## Architecture Overview The Onefinity controller is a **Raspberry Pi 2/3** (armv7l, Raspbian Stretch, Python 3.5) connected to an **ATxmega192a3u** AVR microcontroller over serial. The Pi runs a Tornado web server (`bbctrl`) that serves the UI and plans G-code 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: - **Python backend** (`src/py/bbctrl/`) — Tornado web server, planner, state machine - **Web frontend** (`build/http/`) — Pug/Stylus/Svelte compiled to 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 .so) - **Install scripts** (`scripts/install.sh`, etc.) ## Prerequisites - Docker (for the devcontainer build environment) - The devcontainer image: `docker build -t onefin-dev -f .devcontainer/Dockerfile .devcontainer/` - SSH access to the Pi: `ssh bbmc@10.1.10.55` (password: `onefinity`) ## Building ### Full build (frontend + AVR + package) ```bash docker run --rm -v "$(pwd):/workspace" -w /workspace onefin-dev \ bash -c 'make all && python3 ./setup.py sdist' ``` Produces: `dist/bbctrl-1.6.7.tar.bz2` (~3MB) ### What `make all` builds | Component | Command | Output | |---|---|---| | Web frontend | `npm install`, pug/stylus/svelte compile | `build/http/` | | AVR firmware | `make -C src/avr` (avr-g++) | `src/avr/bbctrl-avr-firmware.hex` | | Bootloader | `make -C src/boot` | `src/boot/bbctrl-avr-boot.hex` | | Power MCU | `make -C src/pwr` | `src/pwr/bbctrl-pwr-firmware.hex` | | Jig firmware | `make -C src/jig` | `src/jig/bbctrl-jig-firmware.hex` | ### gplan.so — the critical gotcha `gplan.so` is the CAMotics G-code planner compiled as a Python C extension. It **must be a 32-bit ARM binary** linked against **Python 3.5** to run on the Pi. **Do NOT build gplan.so in the devcontainer.** The devcontainer runs arm64/Debian Bullseye with Python 3.9. The resulting `.so` will be the wrong architecture and wrong Python ABI. Cross-compiling with `CXX=arm-linux-gnueabihf-g++` also fails because SCons ignores CC/CXX env vars. The correct file must be: ``` ELF 32-bit LSB shared object, ARM, EABI5 (linked against libpython3.5m.so.1.0) ``` If you see `ELF 64-bit LSB shared object, ARM aarch64` or `libpython3.9` — wrong. **Option A: Build from source (recommended)** Uses Docker's armv7 QEMU emulation on Apple Silicon. Requires a one-time copy of Python 3.5 headers from the Pi (~1.7MB): ```bash # One-time: grab Python 3.5 headers + lib from Pi ssh bbmc@10.1.10.55 'tar czf - /usr/include/python3.5m \ /usr/lib/arm-linux-gnueabihf/libpython3.5m.so*' > /tmp/pi-python35.tar.gz ``` Then build (takes ~30min under QEMU): ```bash docker run --rm --platform linux/arm/v7 \ -v "$(pwd):/workspace" -w /workspace \ -v /tmp/pi-python35.tar.gz:/tmp/pi-python35.tar.gz \ debian:bullseye bash -c ' set -e tar xzf /tmp/pi-python35.tar.gz -C / apt-get update -qq && apt-get install -y -qq build-essential scons git \ ca-certificates python3-dev libssl-dev libexpat1-dev libbz2-dev \ liblz4-dev zlib1g-dev perl file ln -sf /usr/lib/arm-linux-gnueabihf/libpython3.5m.so.1.0 \ /usr/lib/arm-linux-gnueabihf/libpython3.5m.so # Clone cbang + camotics at pinned commits mkdir -p /tmp/cbang && cd /tmp/cbang && git init -q git remote add origin https://github.com/CauldronDevelopmentLLC/cbang git fetch --depth 1 -q origin 18f1e963107ef26abe750c023355a5c40dd07853 git reset --hard FETCH_HEAD -q mkdir -p /tmp/camotics && cd /tmp/camotics && git init -q git remote add origin https://github.com/CauldronDevelopmentLLC/camotics git fetch --depth 1 -q origin ec876c80d20fc19837133087cef0c447df5a939d git reset --hard FETCH_HEAD -q # Build cbang cd /tmp/cbang && scons -j2 disable_local="re2 libevent" export CBANG_HOME="/tmp/cbang" # Patch camotics (clamp div-by-zero in planner) P="/tmp/camotics/src/gcode/plan" mkdir -p /tmp/camotics/build && touch /tmp/camotics/build/version.txt for F in LineCommand.cpp LinePlanner.cpp; do for V in maxVel maxJerk maxAccel; do perl -i -0pe "s/(fabs\\((config\\.$V\\[axis\\]) \\/ unit\\[axis\\]\\));/std::min(\\2, \\1);/gm" $P/$F done done # Compile with Python 3.9 (SCons needs python3-dev to find headers) cd /tmp/camotics && scons -j2 gplan.so with_gui=0 with_tpl=0 # Relink against Python 3.5m (the Pi target) g++ -o /workspace/src/py/camotics/gplan.so \ -Wl,--as-needed -Wl,-s -Wl,-x -Wl,--gc-sections -pthread -shared \ build/gplan.os -L/tmp/cbang/lib \ build/libCAMoticsPy.a build/libCAMotics.a build/libDXF.a \ build/libSTL.a build/libGCode.a \ -lstdc++ -lutil -lm -ldl -lz -lcbang -lcbang-boost \ -lssl -lcrypto -llz4 -lexpat -lbz2 -lcrypt -lpthread \ -lpython3.5m build/dxflib/libdxflib.a ' ``` Why the relink step: SCons compiles `.o` files that are Python-version-agnostic (they only use `#include ` which is ABI-compatible between 3.5-3.9 for the subset camotics uses). The only version-specific part is the final `-lpython3.X` link. So we let SCons build with 3.9 (since it ignores overrides) then relink the same objects against 3.5m. **Option B: From official release** ```bash curl -L https://github.com/OneFinityCNC/onefinity-firmware/releases/download/v1.6.6/onefinity-1.6.6.tar.bz2 \ | tar xjf - --include='*/gplan.so' --strip-components=3 -C src/py/camotics/ ``` **Option C: From a working Pi** ```bash scp bbmc@10.1.10.55:/usr/local/lib/python3.5/dist-packages/bbctrl-*.egg/camotics/gplan.so src/py/camotics/ ``` ### bbserial.ko — kernel module The `bbserial.ko` kernel module requires cross-compiling against the Pi's exact kernel headers (4.9.59-v7+). The `make pkg` target tries to build it but it's rarely needed — the Pi already has a matching `.ko` installed. The `install.sh` script skips it gracefully if the file is missing (`cp: cannot stat 'src/bbserial/bbserial.ko': No such file or directory`). ### AVR emulator (for local demo mode) ```bash docker run --rm -v "$(pwd):/workspace" -w /workspace/src/avr/emu onefin-dev make ``` Produces `src/avr/emu/bbemu` — a native binary that emulates the AVR for demo mode. ## Flashing ### Via web API (recommended, machine must be running) ```bash curl -X PUT \ -H "Content-Type: multipart/form-data" \ -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 PASSWORD=onefinity` ### Via SSH (if web UI is down / crash-looping) ```bash 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 if flash breaks the Pi If bbctrl is crash-looping after a bad flash: 1. SSH still works: `ssh bbmc@10.1.10.55` 2. Check the error: `sudo python3 /usr/local/bin/bbctrl 2>&1 | head -20` 3. Common fix: replace gplan.so with correct ARM binary (see above) 4. Nuclear option: restore from SD card backup (see below) ## Running locally (demo mode) Run the full stack in Docker with the AVR emulator: ```bash # Build everything first (make all + bbemu + gplan.so for arm64 devcontainer) # Then: 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 ' ``` Note: demo mode needs its own gplan.so matching the container's arch (arm64 + Python 3.9). Build it with the gplan build procedure in the Makefile, or use the one already in `src/py/camotics/` if it matches. Open http://localhost:8765 — full UI with emulated AVR. ## SD Card Backup & Restore ```bash # Backup (streams raw dd from Pi, compresses locally with gzip, ~50 min) ./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 ``` See `backup/onefinity-backup.sh` for details. Environment variables: `ONEFINITY_HOST` (default 10.1.10.55), `ONEFINITY_USER` (bbmc), `ONEFINITY_PASS` (onefinity). ## Pi Details | | | |---|---| | Host | `10.1.10.55` | | SSH user | `bbmc` | | sudo password | `onefinity` | | OS | Raspbian Stretch (Debian 9) | | Kernel | 4.9.59-v7+ | | Python | 3.5.3 | | Arch | armv7l (32-bit ARM) | | SD card | 30GB | | 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 |