- Elixir 90.4%
- Nix 5.3%
- JavaScript 2.2%
- HTML 1.3%
- CSS 0.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
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> |
||
| esrom_firmware | ||
| esrom_ui | ||
| .envrc | ||
| .gitignore | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
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 bundlesesrom_uiinto 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 kernelno_new_privsflag set, which blockssudo— somix burn/fwupfail there withsudo: The "no new privileges" flag is set. Build in the firmware shell, then flash from a regular shell.
-
Insert the card and find its device node (e.g.
/dev/mmcblk0or/dev/sdX):lsblk -
Unmount any partitions your desktop auto-mounted (otherwise
fwuperrors withError calling umount). Nosudoneeded —udisksctluses polkit:for p in /dev/mmcblk0p*; do udisksctl unmount -b "$p" 2>/dev/null; done -
Flash.
fwupcomes from Nix; we resolve its full path sosudo(which resetsPATH) 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
-ddevice — 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 fromnix develop .#firmware(pinned to OTP 28), not a globally installed Elixir.sudo: The "no new privileges" flag is set— you ranmix burn/fwupinside 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 theudisksctl unmountloop 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:80withserver: truevia the non-host branch ofesrom_ui/config/dev.exs). Rebuild and reflash.