# Onefinity CNC Firmware — Build, Flash & Backup ## Quick Start ```bash # 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): ```bash .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](https://github.com/CauldronDevelopmentLLC/cbang) @ `18f1e96` — C++ utility library - [camotics](https://github.com/CauldronDevelopmentLLC/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): ```bash # 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`: ```bash file src/py/camotics/gplan.so readelf -d src/py/camotics/gplan.so | grep python ``` ### Step 2: Firmware package ```bash 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) ```bash 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) ```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 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: ```bash # 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 ```bash # 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`.