Independent consultant. I build the operating system layer for embedded hardware — custom Linux images, Zephyr firmware, board bring-up, and the industrial protocols that run on top.
A Raspberry Pi 5 booting a Buildroot image built from a one-line description, then answering Modbus, OPC UA, MQTT, HTTP, CAN, DNP3, BACnet/IP and IEC 61850 off a single shared tag table:
The last part is the one integrators care about. A general-purpose distro with the same services installed opens dozens of ports nobody asked for. This image opens the nine it needs, because nothing else was ever put in it.
Buildroot images that boot in seconds and contain only what you chose. Reproducible, auditable, and small enough that two copies fit on one card.
New silicon, a vendor BSP that nearly works, a device tree that does not match the hardware. Serial console, a logic analyser, and the patience to find the one line that matters.
Modbus, OPC UA, DNP3, BACnet/IP, IEC 61850, CAN and MQTT — gateways, simulators and device-side servers that an integrator's existing tools can talk to.
A/B partitions with automatic rollback, signed images, and a device that can tell you what it is running. The difference between a product and a truck roll.
RTOS work where Linux is too large or the timing too tight, including mixed designs where a Cortex-A runs Linux and a Cortex-M runs the control loop.
A machine-readable bill of materials, every license in the image, the source archives the GPL obliges you to hand over, and an inventory of what is actually on the device — not what the build intended.
Build systems, CI that produces the same image twice, and tooling that catches a broken configuration before it reaches a board.
I keep a bench of real boards and bring each one up properly rather than trusting a vendor image:
Across those: arm64 and armv7, eMMC and SD and NVMe, device tree overlays, silicon stepping differences, PCIe, CAN and RS485, camera and display pipelines.
Most embedded problems are not hard, they are invisible. A symbol nobody set, a device tree for the wrong silicon revision, a driver built as a module that nothing loads. The board boots, something is missing, and nothing says what.
So I build the instrumentation first: a serial console on the firmware UART, a way to ask a running board what it actually brought up, and checks that compare what a build claimed against what the hardware needs. Then the debugging is reading rather than guessing.
Everything I deliver comes with the reasoning written down — why a symbol is set, which failure taught it, and what to check if it breaks again.
Most teams shipping embedded Linux cannot produce a complete list of what is on their device. The image was built from a vendor BSP that was forked from another BSP, something was copied in during manufacturing, and the honest answer to "what version of OpenSSL is on that board" is that nobody knows.
That used to be an embarrassment. Since 11 September 2026 it is a reporting obligation: the EU Cyber Resilience Act now requires manufacturers to report actively exploited vulnerabilities in their products, and from 11 December 2027 the full obligations apply — including identifying and documenting the components in a product, with a software bill of materials in a commonly used, machine-readable format. You cannot report on a vulnerability in a component you cannot prove you ship.
Three artefacts come out of the work, and they answer different questions:
Every package in the image with its version, its license, the text of that
license, and — for the licenses that demand it — the source archive you are
obliged to hand to anyone who asks. Buildroot will generate this
(make legal-info) for an image that was built properly; the work
is in getting there, because a package with an unclear license or a vendor
blob with no license at all is exactly what the manifest exposes, and it is
much cheaper to find that before a product ships than during a customer's
legal review.
The same inventory in CycloneDX or SPDX, which is what a vulnerability
scanner, a procurement portal or a market surveillance authority will
actually accept. Generated from the build
(make show-info | utils/generate-cyclonedx) rather than written
by hand, so it is correct by construction and regenerates with every image
instead of going stale the week after it is signed off.
The first two describe what the build claimed. This one describes the board in front of you, and the two are not always the same: every binary on the root filesystem and every shared library it needs resolved against what is actually installed, every kernel module present and which of them loaded, every device node that bound to a driver and every one that did not.
That last point is where the interesting failures live. A driver built as a module that nothing ever loads looks identical to a working system in the manifest, and identical to broken hardware on the bench. I build the tooling that tells the two apart, and publish it.
On the legal side I can produce the evidence and tell you what the obligations look like in practice. I am not a lawyer, and for the question of whether a particular license obligation has been discharged you want one.
The same shape every time, whatever the board. The point of the last three directories is that someone who is not me can rebuild the image, prove what is in it, and fix it after I am gone:
delivery/
├── image/
│ ├── product-1.4.0.img the image that ships
│ ├── product-1.4.0.img.sha256 what it must hash to
│ └── update-1.4.0.raucb signed A/B bundle
├── compliance/
│ ├── manifest.csv package, version, license
│ ├── licenses/ full text of every one
│ ├── sources/ the archives GPL owes
│ └── sbom.cdx.json CycloneDX, machine-readable
├── triage/
│ ├── libraries.txt each binary, each .so
│ ├── modules.txt built, present, loaded
│ └── devices.txt node to driver, or not
├── config/
│ ├── defconfig rebuilds it from source
│ ├── kernel.config every symbol, and why
│ └── device-tree/ base .dts and overlays
└── docs/
├── BRINGUP.md what broke, what fixed it
├── REBUILD.md checkout to identical image
└── RUNBOOK.md flash, recover, roll back
No part of that is a deliverable I add at the end to pad an invoice. Each one is a by-product of building the image correctly in the first place — which is the whole argument for doing it this way.
I write about this work and publish the tooling at github.com/vinodhalaharvi, including a tool that checks an embedded Linux configuration against the board it is meant to run on, before the image is built.
Available for short engagements and longer projects. Happy to start with a conversation about whether I am the right person for the problem.