Case study 01
The complete software stack for a shipping bioprinter
Frontend to firmware boundary across four heterogeneous devices, with an offline update path — owned end to end by one engineer.
Context
Biomotion Technologies builds a bio-3D printer — a machine that deposits living material to the precision a lab protocol demands, rather than the precision a plastic bracket demands. I am the company's only software engineer.
That means the operator-facing application, the backend that drives the machine, the computer vision and slicing services, the way four separate boards talk to each other, and the process by which any of that reaches a printer sitting in a lab are all mine. I also act as the technical counterpart to the founders, which in practice means translating product and biology requirements into architecture decisions and telling them honestly what each one costs.
The scope is the appeal of the job. It is also the constraint behind every decision below: there is no platform team to absorb a bad call, and no second opinion unless I go and find one.
Constraints
The hard problems here are not algorithmic. They are situational.
The printers have no internet. Lab machines sit on isolated networks, or on no network at all. An update cannot assume connectivity, a package registry, or an engineer at the keyboard. It has to travel on removable media and apply itself correctly when someone who has never read a release note plugs it in.
Four heterogeneous devices, one product. A Duet3D motion controller, a Raspberry Pi, an NVIDIA Jetson, and the client. Different compute, different operating assumptions, different failure modes, and four different ideas of what "restart" means. Most of the real engineering is in the seams between them, not inside any one of them.
A failed run is expensive in a way a failed plastic print is not. The material is biological and its viable window is finite. Thirty minutes into a print, "just start it again" is not available as a recovery strategy.
One engineer. Every abstraction has to pay for itself immediately. Anything clever that I would have to re-derive in six months is a liability, not an asset. A lot of this job is refusing to build things.
Architecture and decisions
Let the motion controller do motion. The Duet3D board exists to run motion loops with hard timing guarantees, and its firmware is better at that than anything I would write on top of a general-purpose Linux userspace. So the boundary is drawn there: the backend composes and streams G-code and reads state back, and never tries to close a control loop itself. Everything above the controller is then allowed to be soft real-time, which turns the rest of the stack back into ordinary software with ordinary failure handling.
One process owns machine state. The C# backend is the only component that knows what the machine is currently doing. The React frontend never talks to hardware, and neither do the Python services — they receive work and return results. With four devices in play, the failure mode I most wanted to design out was two components disagreeing about whether the printer is busy.
Containers on the Pi. Not for scaling — there is exactly one of each service per machine. For reproducibility and rollback. On a device I cannot SSH into once it ships, "the image that runs here is byte-identical to the one I tested" is worth considerably more than the runtime overhead it costs.
Computer vision and slicing as separate services. They have different resource profiles: the CV work wants the Jetson's GPU, slicing is CPU-bound and bursty. Splitting them also decouples their release cadence from the backend's, which matters when the slicing behaviour is still being tuned against real material and the rest of the stack is not.
Offline updates as a single versioned artifact. One file, one version, one apply step, and it either lands completely or not at all. The requirement that drove the design is that a printer must never come up in a half-updated state, because nobody will be there to notice that it did.
Outcome
The stack runs in production on shipping hardware. The release process went from not existing to a repeatable, versioned pipeline that produces a single artifact a non-engineer can apply to a machine in the field, and the CI/CD that builds it was likewise built from zero.
What I would do differently: I built the device integrations before the integration tests, and paid for it every time one of the four devices changed behaviour under me. On a system where the hardware and the software are being developed at the same time, the test harness that fakes the devices is not overhead you add once things settle down — it is the thing that lets you find out which side actually broke.