No description
  • Elixir 90.4%
  • Nix 5.3%
  • JavaScript 2.2%
  • HTML 1.3%
  • CSS 0.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Pim Kunis 4e73be23e0 Modernize toolchain & deps, fix on-device serving, rename eisrom→esrom
Dependency & toolchain update:
- nix flake update (nixpkgs 2026-05 → 2026-07); pin both dev shells to
  Erlang/OTP 28 + Elixir 1.18 via beam.packages.erlang_28.
- esrom_ui: Phoenix 1.6→1.7, LiveView 0.17→1.x, phoenix_html 3→4. Migrate
  to the 1.7 web structure (Layouts / PageHTML / ErrorHTML+ErrorJSON,
  verified routes, ~H render, live routes); drop the old view/template tree.
- esrom_ui: circuits_gpio 1→2. The 2.x cdev backend holds the GPIO line
  exclusively, so open it once per morse run and reuse the handle instead
  of reopening on every dot/dash.
- esrom_firmware: bump the Nerves stack (nerves 1.14, nerves_system_rpi2
  2.0.3, nerves_pack, ring_logger, toolshed, …).

Fixes:
- flake: the firmware FHS shell uses `targetPkgs = p: with p; [...]`, and
  `with p;` silently shadowed the erlang_28 pin with nixpkgs' default
  OTP-27 beam set — breaking the Nerves host/target OTP match
  (nerves_system_rpi2 wants OTP 28). Bind the toolchain under
  collision-proof names and put `erl` on PATH.
- Serve the UI on the device: config/dev.exs now enables `server: true`
  and binds 0.0.0.0:80 in the non-host (firmware) branch. Previously the
  endpoint started with no listener, so the device pinged but served
  no page.

Rename eisrom → esrom across directories, modules (Esrom*), app atoms
(:esrom_ui / :esrom_firmware), and config keys. The project is "esrom";
"eisrom" was a typo.

Docs: rewrite README with Nix-oriented build/flash instructions and a
troubleshooting section (FHS `sudo`/no_new_privs limitation, host/target
OTP mismatch, unmounting auto-mounted SD partitions).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 16:41:39 +02:00
esrom_firmware Modernize toolchain & deps, fix on-device serving, rename eisrom→esrom 2026-07-19 16:41:39 +02:00
esrom_ui Modernize toolchain & deps, fix on-device serving, rename eisrom→esrom 2026-07-19 16:41:39 +02:00
.envrc Modernize toolchain & deps, fix on-device serving, rename eisrom→esrom 2026-07-19 16:41:39 +02:00
.gitignore Modernize toolchain & deps, fix on-device serving, rename eisrom→esrom 2026-07-19 16:41:39 +02:00
flake.lock Modernize toolchain & deps, fix on-device serving, rename eisrom→esrom 2026-07-19 16:41:39 +02:00
flake.nix Modernize toolchain & deps, fix on-device serving, rename eisrom→esrom 2026-07-19 16:41:39 +02:00
README.md Modernize toolchain & deps, fix on-device serving, rename eisrom→esrom 2026-07-19 16:41:39 +02:00

Esrom geocache

A Nerves-based geocache running on a Raspberry Pi 2. The device blinks a morse-code message through a GPIO-connected relay/lamp, driven from a Phoenix LiveView web interface that visitors open in their browser.

The repo is two Elixir apps:

  • esrom_ui — the Phoenix LiveView app (the web UI + morse logic). Runs on your laptop for development, and is embedded into the firmware for the device.
  • esrom_firmware — the Nerves project that bundles esrom_ui into a bootable Raspberry Pi image.

Prerequisites

  • Nix with flakes enabled. You do not need Elixir, Erlang, or the Nerves toolchain installed globally — the dev shells provide everything, pinned to the versions this project needs.
  • For flashing: an SD card and a card reader.

The two dev shells

Command What you get Use it for
nix develop Elixir + esbuild + inotify-tools Working on the esrom_ui web app
nix develop .#firmware An FHS sandbox with Elixir and the Nerves cross-compile toolchain Building the Raspberry Pi firmware

Both shells pin Erlang/OTP 28, which is the version the Nerves system (nerves_system_rpi2) expects. If the host and target OTP majors don't match, mix firmware refuses to build — so always build from nix develop .#firmware, not your own global Elixir.

Development (the web UI)

nix develop
cd esrom_ui
mix deps.get
mix phx.server

Open http://localhost:4000/seinlamp for the morse page (the bare / returns 204; see esrom_ui/lib/esrom_ui_web/router.ex for all routes).

Building the firmware

Enter the firmware shell. It drops you into an FHS bash sandbox — that layout is required because Nerves downloads pre-compiled toolchain binaries that expect a standard Linux filesystem (/lib/ld-linux.so, etc.), which NixOS doesn't have.

nix develop .#firmware

First time only, install the Nerves bootstrap Mix archive (into ~/.mix):

mix archive.install hex nerves_bootstrap

Then build:

cd esrom_firmware
mix deps.get
mix firmware

The default target is rpi2. On success you get:

esrom_firmware/_build/rpi2_dev/nerves/images/esrom_firmware.fw

To build for another board, set MIX_TARGET before entering the shell (and add the matching nerves_system_* dep in esrom_firmware/mix.exs):

MIX_TARGET=rpi3 nix develop .#firmware

Flashing an SD card

Do this from a normal terminal, not from inside nix develop .#firmware. That firmware shell is a bubblewrap sandbox with the kernel no_new_privs flag set, which blocks sudo — so mix burn / fwup fail there with sudo: The "no new privileges" flag is set. Build in the firmware shell, then flash from a regular shell.

  1. Insert the card and find its device node (e.g. /dev/mmcblk0 or /dev/sdX):

    lsblk
    
  2. Unmount any partitions your desktop auto-mounted (otherwise fwup errors with Error calling umount). No sudo needed — udisksctl uses polkit:

    for p in /dev/mmcblk0p*; do udisksctl unmount -b "$p" 2>/dev/null; done
    
  3. Flash. fwup comes from Nix; we resolve its full path so sudo (which resets PATH) can find it:

    FWUP=$(nix build --no-link --print-out-paths nixpkgs#fwup)/bin/fwup
    sudo "$FWUP" -a -t complete \
      -d /dev/mmcblk0 \
      -i esrom_firmware/_build/rpi2_dev/nerves/images/esrom_firmware.fw
    

    ⚠️ Double-check the -d device — this overwrites the whole disk.

Put the card in the Pi and power on. After boot, the web UI is served on port 80 (e.g. http://<device-ip>/seinlamp).

Updating a running device over the network

Once a device is on the network you can push new firmware over SSH — no need to pull the SD card. From nix develop .#firmware, after mix firmware:

cd esrom_firmware
mix upload <device-ip-or-hostname>

(Or generate a standalone script once with mix firmware.gen.script and run ./upload.sh <device>.) The device applies the update to its inactive A/B partition and reboots, so a bad image rolls back automatically.

SSH into the device

The firmware runs an SSH server (via nerves_pack) that gives you an IEx prompt using the keys in esrom_firmware/config/authorized_keys:

ssh <device-ip-or-hostname>

Useful once connected: VintageNet.info() to inspect network state.

Troubleshooting

  • Major version mismatch between host and target Erlang/OTP — you're building with the wrong Erlang. Build from nix develop .#firmware (pinned to OTP 28), not a globally installed Elixir.
  • sudo: The "no new privileges" flag is set — you ran mix burn/fwup inside the firmware FHS shell. Flash from a normal terminal instead (see above).
  • fwup: Error calling umount — a partition on the card is still mounted. Run the udisksctl unmount loop above, then re-flash.
  • Device pings but shows no web page — the endpoint only listens when built for a target (it binds 0.0.0.0:80 with server: true via the non-host branch of esrom_ui/config/dev.exs). Rebuild and reflash.