Make It Yours: User Layers and the Edit-Build-Flash Loop

Part 3 of The Thingino Developer’s Journey / <- Part 2

Sooner or later every firmware developer’s tree becomes an archaeological dig: a debug flag here, a hardcoded MQTT broker there, an experimental package enabled three weeks ago and forgotten. Then you go to send a pull request and spend an evening separating “the fix” from “my life choices.”

Thingino solves this structurally. Everything personal lives in a layered user tree that is gitignored, scoped, and merged into the build automatically. Your checkout stays clean; your customizations persist.

The three layers

user/common/                 # applies to every build you make
user/<camera>/               # applies to one camera model
user/<camera>/<ip>/          # applies to one specific device

Narrower scope wins. Build with CAMERA=<camera> IP=192.168.88.31 make and all three layers apply, device-scope last. Build without IP= and the device layer is skipped. Want a factory-pure build with no user input at all?

PRISTINE=1 CAMERA=<camera> make

That single variable is your “is it my config or is it a real bug?” switch, and you will come to love it.

What goes in a layer

Each layer directory can contain any of these:

File / dir What it does
local.fragment extra Buildroot config symbols appended to .config (e.g. BR2_PACKAGE_STRACE=y)
local.mk user-scoped package source overrides (see Part 5)
thingino.json merged into /etc/thingino.json — core settings, MQTT, motors, web UI
prudynt.json streamer settings — resolution, bitrate, OSD
overlay/ files copied straight into the root filesystem
opt/ files that appear at /opt/ on the camera
local.uenv.txt extra U-Boot environment entries

A concrete example. Suppose all your cameras should talk to your MQTT broker, and one particular camera on your bench needs strace and verbose debugging:

user/common/thingino.json                 ->  {"mqtt_sub": {"enabled": true, "host": "192.168.1.10"}}
user/wyze_cam_v3_t31x_gc2053_rtl8189ftv/192.168.88.31/local.fragment
                                          ->  BR2_PACKAGE_STRACE=y

Every build gets your broker. Only the bench camera gets the debug tooling. Nothing touches git.

Config fragments participate in dependency tracking: edit a local.fragment and the next make fast notices and regenerates the config. No stale-config paranoia required.

The loop: edit, build, flash

With user layers in place, daily development is a tight cycle:

CAMERA=<camera> make fast                    # incremental build
CAMERA=<camera> IP=192.168.88.31 make ota    # flash over the network

make ota pushes the full image to a camera already running Thingino and flashes it. For most changes the camera is back on its feet in a couple of minutes. Copying single files by hand is even faster during experimentation — one dropbear-flavored gotcha: use scp -O (legacy protocol), or scp will just sit there judging you.

Also handy: back up a camera’s overlay partition (its writable settings) before you do something bold:

CAMERA=<camera> IP=192.168.88.31 make backup-overlay

When a change should be shared

The user tree is for your stuff. When you discover a change that belongs in the product — a package everyone should have, a fixed default — it graduates:

CAMERA=<camera> make menuconfig     # make the change in the config UI
CAMERA=<camera> make saveconfig     # write it back to the camera defconfig

Then the diff in configs/cameras/<camera>/ becomes your pull request. make edit-defconfig opens the defconfig directly if you know exactly what you’re changing. The full reference for the user tree lives in docs/local-build-settings.md.

What you learned

  • user/common/ -> user/<camera>/ -> user/<camera>/<ip>/, narrow wins.
  • Fragments, JSON add-ons, overlays, and opt/ cover nearly every customization without touching git.
  • PRISTINE=1 answers “is it me or the firmware?”
  • make fast + IP=... make ota is the loop; scp -O is the handshake.

Next up: Part 4 — Adopting a Stray: you found a camera that isn’t supported yet. Time to fix that, and earn your place in the configs/cameras/ credits.