r/Embedded_Electronics 17h ago

Open-source HMI platform for embedded Linux panels: live Qt preview at the panel's real resolution, atomic install, automatic rollback

Every embedded Linux panel project I've worked on ends up in the same place. The UI and the hardware access grow into one binary, so the app can't run anywhere except on the board. Changing a label means rebuilding the image, or scp'ing files by hand and hoping. And when a deploy goes wrong at a customer site, the machine sits there with a black screen.

So I built the thing I kept wishing existed. MIT licensed: https://github.com/Hitheshkaranth/EmbeddedDisplayStudio

The idea

Ship the panel image once. After that, anyone — your app team, your customer's app team — drops a Qt/QML application onto it over SSH. No rebuild, no reflash, no hardware code in the app.

Three layers that don't know about each other:

Layer Owns Knows nothing about
hmi-hwd GPIO, ADC, UART, safe states pixels
hmi-gui QML, tag bindings, the customer bundle hardware
deployment atomic install, health check, rollback either of the above

The app binds to tags (ai.potdi.estopdo.relay1) that arrive as JSON over UDP on loopback. That's the whole contract. It's why the same bundle runs on your laptop, in the preview, and on the panel without a single #ifdef.

The deploy pipeline

This is the part I'd defend in a design review:

  • Nothing is converted. No build, no freezing, no cross-compilation. The panel carries a complete CPython and the Qt binding the manifest asks for, so your sources run there as-is.
  • The dependency check reads your code, not a list. Every packaged file is parsed, imports reduced to top-level names, stdlib and guarded imports removed. What's left, the panel is asked to actually import — because a wheel built for the wrong architecture sits on disk and still kills you at startup.
  • The swap can't leave the panel dark. current is promoted with rename(2), never rm then ln. If the new release doesn't signal readiness in 25 s, the symlink swaps back and the previous release restarts. The deploy fails; the machine still has a UI.
  • Validation runs twice from one implementation. Host CLI, target installer and desktop tool all call the same schema/manifest.py. They used to be three implementations that had drifted in both directions — a bundle that passes on a laptop and gets refused on the panel is worse than one that fails everywhere.

A successful deploy also becomes the boot default automatically. Nothing to enable by hand afterwards.

The desktop Studio

A Windows exe with its own Python and PySide6 baked in, so it previews a customer's app on a machine with no Python installed.

  • Live preview inside a panel bezel, at the panel's real geometry. Not a screenshot — a real QML engine with the same tag engine the device runs. On connect it reads the SOM's DRM connector and adopts the actual pixel size, so nobody lays out widgets against a screen that isn't plugged in.
  • Qt Widgets apps preview too. A runtime: python bundle owns its own window, so it's run unmodified in a child process, forced to target resolution, frames streamed into the bezel, window kept unmapped with WA_DontShowOnScreen.
  • Qt5 and Qt6 on the same panel. PySide2 bundles get a second interpreter at /opt/hmi-python-qt5 shipping its own Qt 5.15 — because the panel's Qt5 is a GLES build and every aarch64 PySide2 binary is compiled against desktop GL. Loading one against the other dies with undefined symbol: _ZTI18QOpenGLTimeMonitor. Ask me how I know.
  • Tag Lab drives the app with sine/square/ramp/noise per tag, so you can exercise panel behaviour before the I/O it binds to exists.
  • Panel Logs follows the journal from both services — which is where the fault that shows up an hour after a successful deploy actually lives.
  • A visual designer, if you'd rather drag widgets than write QML. Same window previews, generates and deploys what you drew, so the design and the bundle can't disagree.

Already have a Qt app?

Point the Studio at the folder. It finds the entry point, detects the Qt binding from the sources, and writes the manifest for you. Two files is a valid bundle:

{ "schema": 1, "name": "line-controller", "version": "1.4.0", "entry": "main.qml" }

Try it with no hardware

The daemon simulates its I/O, so the whole stack runs on a desktop:

python -m pip install PySide6
python daemon/hmi_hwd.py --config daemon/hwd.json --sim
python gui/hmi_loader/main.py --apps-dir apps/demo-app --windowed
python main.py

Honest limitations

  • 309 tests, but the installer and cross-validator suites need flock, so on Windows they skip — CI runs the full suite on Linux and fails if anything skips, because a skip is not a pass.
  • The designer is v1: .edsui → QML generation only. It doesn't import or round-trip arbitrary QML, and threshold metadata is stored in bindings but not yet turned into visual state expressions.
  • The packaged Studio is Windows-only right now. It runs from source anywhere.
  • The Qt5 runtime isn't installed by default; it's one script per panel.
  • Provisioning is the sharp edge. A base vendor image can ship python3-core alone — no json, no socket, no ctypes — which stops the installer before it starts. provision_panel.py --check surveys the board and names what's missing.

What I'd like feedback on

  • If you ship panels commercially: is "one image, many customer apps" how you'd actually want to work, or does your certification story make field-deployable app bundles a non-starter?
  • Is 25 s the right health-check window? It's generous for QML and tight for a heavy Widgets app with a slow first paint.
  • UDP-on-loopback for tags — anyone want to argue for a Unix domain socket instead? I went with UDP so anything can speak it, but I'm open.
2 Upvotes

4 comments sorted by

2

u/kantorcodes1 6h ago

on deploy_to_hmi.sh, if the new app passes the 25s readiness check but making it the boot default fails afterward, do you roll back the release too or leave it live and return nonzero?

1

u/Commercial_Designer5 4h ago

Exit codes

Code Meaning What an operator does
0 Success Nothing. The release is live and survives a reboot.
1 Validation or runtime error The deploy did not land. The previous release is back, or was never replaced.
2 Usage error Fix the command line.
3 Lock contention Another install holds /run/hmi/install.lock. Wait and retry.
4 Running, not the boot default The application is up and correct. Run systemctl enable hmi-gui.service on the panel before the next power cycle. Do not roll back — that would replace a working UI with an older one.

First of all, thank you for going through the repo, I really appreciate the effort you took to do so. The application leaves it live without rollback. Since post-deployment, when the GUI is ready to run at that point the release is installed, current, and proven to render. Rolling back would replace a working UI with an older one over a problem that only appears at the next power cycle. The panel keeps the good release; the exit code carries the warning.

Rollback failures already use 1, and a caller needs to tell "deployed, not persistent" from "not deployed" — those call for opposite responses. 4 is documented in both help texts and target/README.md §3.2, and callers that treat any non-zero as "roll back and page someone" must special-case it.

2

u/kantorcodes1 3h ago

that exit 4 distinction matters. a caller shouldn't treat “running, not persistent” like a failed deploy and undo a good release. i work on HOL Guard; its CLI extensions can keep list / status / check quiet while reviewing the actual change paths like deploy, rollback, and risky flags such as --insecure. would you be up for adding EmbeddedDisplayStudio support?

1

u/Commercial_Designer5 49m ago

I'll be updating this in the next release to accommodate it as a failure and initiate a rollback on trigger. I am plannig to integrate OpenDesign themed designer onto this project in comming builds where HOL Gaurd might play a crucial role in ensuring gaurd rails for hardware resource sharing addition onto generated design / code for agent execution. Once again, thank you for the feedback.