A custom Yocto Linux distribution for the Raspberry Pi 5
eOS is a from-scratch embedded Linux platform built on Yocto/OpenEmbedded for the Raspberry Pi 5 (16GB). It pairs A/B RAUC OTA updates with an MQTT service bus, SQLite persistence, a Qt6/QML local UI, a Rust sensor-fusion framework, and a fully on-device voice subsystem.
YoctoBitBakeOpenEmbeddedkasRAUC A/B OTAsystemdD-BusMosquitto MQTTSQLiteQt6/QMLRustRaspberry Pi 5
Authored and extended Yocto recipes (.bb/.bbappend) across the meta-eos layer, with BitBake PR bumps, AUTOREV pinning, and IPK packaging for incremental on-device deployment
Work across the Linux subsystem stack: systemd unit design, D-Bus interfaces (org.eos.Config1, org.eos.RoomCommands1, org.eos.RoomAggregates1), a Mosquitto MQTT broker hardened with TLS and ACLs, and SQLite schema migrations with multi-writer concurrency tuning
Wrote a generic Rust RoomAggregator framework for per-room sensor fusion across thermal, mmWave radar, air-quality, ambient-light, and PIR motion inputs, with D-Bus calibration and SQLite-backed persistence
Owned the build and deploy flow end to end: extended kas orchestration plus an in-house eos-build CLI, cross-compilation, WIC image builds, bmaptool SD flashing, and RAUC A/B verification
Elipse · no public repo
FIG. 02
In the field
eOS Room Controller
The 24 V board eOS runs a room from — designed, fabbed, then re-spun
The per-room node of the eOS fleet: dimmable 24 V DC channels for lights and a fan, addressable RGB, wired Ethernet back to the Pi hub over MQTT-TLS, and an I²S microphone bridge. Designed and laid out in KiCad 9. v1 was fabricated and served three rooms; v2 replaced it and runs the fleet today.
KiCad 9ESP32-S3PCA9685TPS26631 eFuseW550024 V DCMOSFET PWMI²SSMD / PCBA
Bus
24 V DC — no mains
Outputs
8-ch 24 V PWM · 2× RGB
Uplink
Ethernet → MQTT-TLS
Audio
XVF3800 mic bridge (I²S)
Build
KiCad 9 → full SMD PCBA
State
v2 live in 3 rooms
Key figures
Board area
−57%173×168 → 125×100 mm
Components
₹3,160 / boardpopulated cost, ex-GST
Plug-in modules
4 → 1one socketed ESP32-S3-ETH
Protection stages
0 → 5fuse · eFuse · TVS · fuse · eFuse
Dimmer channels
8, on zero MCU pinsPCA9685 — 16-ch part, 8 wired
Decisions closed
40 of 40D1–D40, no open verdicts
Engineering detail
Designed the board in KiCad 9 — schematic through layout to the fabrication package; v1 was fabbed and put into service (PCB1–PCB3), and v2 re-spun the whole design at 125 × 100 mm — 57% less board area.
Rebuilt the power path around what actually failed: v1's MP1584 burned twice and its AMS1117 drifted to ~4.4 V and killed a W5500 rated 3.63 V absolute maximum. v2 answers with five protection stages — 6.3 A SMD fuse → TPS26631 60 V eFuse (reverse polarity, 6 A OCP, 33 V OVP, 18 V UVLO, inrush control) → SMCJ24A TVS → tap fuse → TPS25947 eFuse guarding the whole 5 V rail.
Ran the PCA9685 at 5 V so it drives the FET gates directly, which deleted all four UCC27524 gate drivers; eight TO-220 FETs became four dual SMD packages, and the axial flyback diodes collapsed to a single dual Schottky serving the two fan channels. The board went from 81 hand-soldered through-hole parts to about 34 placement classes on a stencil-and-reflow board — though the populated part count rose to 119, because the growth is the protection network v1 did not have.
Merged the MCU and Ethernet into one socketed Waveshare ESP32-S3-ETH module — four plug-in modules down to one, five SPI GPIOs freed, and a dead PHY becomes a 30-second swap in a ceiling instead of board surgery.
Recorded 40 dated design decisions (D1–D40) with rationale and closed every one, including two standing rules the fleet still works to: cut only true redundancy, never performance (D36), and read a part's datasheet before designing it in (D40) — written after a missed common-ground destroyed a radar and two ESP32s.
Chased a total 24 V rail collapse on the bench down to two points on one net disagreeing — the input eFuse's dV/dT pin reading 0.5 V against 0 V on its soft-start capacitor pad. An open there loses soft-start, so inrush trips the eFuse into a retry loop and the rail never establishes.
Elipse · no public repo
FIG. 03
Deployed
Industrial Anti-Collision System
UWB crane anti-collision safety system, deployed at Tata Steel BlueScope
A real-time industrial Anti-Collision System for crane operations, built during an embedded internship at Radiogeet and deployed at Tata Steel BlueScope. It uses UWB proximity detection and zone-based safety logic to trigger industrial outputs that prevent hazardous crane movements.
esp-idf ota partition scheme (two app slots)Engineering detail
Architected ESP32-S3 dual-core firmware: core 1 runs time-critical UWB distance measurement while core 2 handles zone calculation, system logic, and an embedded web UI
Linked nodes over ESP-NOW for low-latency peer-to-peer communication
Drove an 8-channel industrial relay system via MODBUS RTU over RS485 to Masibus DO cards
Extended the platform with STM32 work: AHT10 and ADS1115 interfacing, timers and ADC/DAC, 2/4-wire RS485, LoRa links, and Masibus DI/DO/AI/AO cards
48 V, 16 dimmed channels at 24 A — four layers, native Ethernet
A 48 V lighting and fan controller for the eOS fleet: sixteen independently dimmed channels driven from one I²C PWM generator through quad drivers into eight dual MOSFETs, with native Ethernet through an on-board three-port switch and two addressable-RGBW paths. Four copper layers on a published impedance-controlled stackup, routed to zero unconnected and verified against a DC review on the final copper.
kicad 3d render — 201 × 120 mm, black maskall four copper layers — front, two inner planes, back
KiCad48 V DCESP32LAN9354RMIIPCA96854-layer stackup100 Ω differential12-bit PWM dimmingAddressable RGBW
Bus
48 V DC
Channels
16 × 12-bit PWM dimming
Rating
1.5 A/ch · 6 A/group · 24 A
Uplink
LAN9354 · native RMII
Stackup
4-layer, impedance-controlled
PWM
2,929.6875 Hz from 48 MHz
Key figures
Board rating
24 A / 1,152 W1.5 A × 16 channels at 48 V
Channel budget
1.5 A × 166 A per four-channel group
Schematic
180 nets · 912 nodesERC clean, zero waivers
Routing closed
0 unconnected3,070 segments · 763 vias, four layers
DC drop screen
19.9 mVworst driver distribution, 50 mV bound
Ground return
1.00 W / 44 mVat 24.16 A on the final copper
Engineering detail
Rated the board as a hierarchy rather than a single number: 1.5 A per channel, 6 A per four-channel group and 24 A across all sixteen — 1,152 W — with the channel derating chosen so that four fully loaded channels come to exactly the 6 A group rating, under an 8 A group fuse.
Made the ESP32 the RMII clock master: GPIO17's EMAC_CLK_OUT_180 through 33 Ω into the Ethernet switch's reference-clock input, strapped to accept it. That deleted a gated clock buffer and four support parts, freed GPIO0 as a clean BOOT strap, and closed a buffered-clock timing question by removing the buffer that had raised it.
Moved the board to four copper layers on a published impedance stackup — 1.55 mm, 35 µm on every layer, inner planes split into separate logic and power grounds — with 100 Ω differential pairs at 0.12 / 0.14 mm on the outer layers over layer 2.
Drove all sixteen outputs from one I²C PWM generator through four quad drivers into eight dual MOSFETs at 2,929.6875 Hz, and held the generator's output-enable disabled by a hardware pull-up so no firmware path can bring the board up with channels already live.
Closed the first board's routing at zero unconnected and zero shorts after roughly twelve strategies, the decisive one being that connections reported as closed were not touching: endpoints were being reached on the wrong copper layer, so a front pad was 'met' from the back.
Regenerated the schematic to 180 nets and 912 nodes with ERC clean and zero waivers, then closed the four-layer routing at zero unconnected and produced the fabrication package — gerbers, drill, assembly and the impedance-controlled stackup call-out.
Elipse · no public repo
FIG. 05
Fabricated
eOS Switchboard
A SELV wall keypad that switches nothing — tactile keys, addressable indicators, CAN
The wall-mounted control surface for eOS. It switches nothing itself: every load it appears to control is driven by the room controller back at the distribution board, so this panel only senses intent and renders state. SELV throughout, with no mains copper on any layer, on a 145 × 70 mm board that fits a standard 8M concealed box shared with an AC socket.
1.4% per mmv0.3 design — 69 mm travel, 24 indicators
Segment error
0.19 mm14 segments; 24 modelled at 1.62 mm
Key switches
12 under 6 keystwo per key at ±9 mm, 160 gf each
LED budget
1.098 A full white30-pixel chain, ~8% under the ~1.2 A ceiling
Switch life
1,000,000 opsAlps SKQG, 5.2 × 5.2 × 1.5 mm SMD
Board
145 × 70 mm2-layer, fits a standard 8M concealed box
Engineering detail
Reversed my own earlier decision with measurements rather than taste: v0.2 gave every key its own 26 mm capacitive strip, which meant one millimetre of thumb moved the level 3.8%. v0.3 replaced them with a single full-height 69 mm slider — 2.7× the travel at 1.4% per millimetre, and 24 indicators instead of 7. The slider becoming modal is the price, and it was accepted in writing rather than discovered later.
Disqualified the ESP32-S3's own touch peripheral on the vendor's own words rather than on channel count: Espressif state it is not recommended for mass-production products and has not passed the Conducted Susceptibility test — which, on a wall switch sharing a steel box with mains, ends the argument. The design put the slider on an external CY8CMBR3116 instead, used deliberately as a 16-channel front end with centroids computed on the S3.
Settled the segment count by arithmetic, not intuition: position comes from a three-point centroid, so a finger spanning three or more segments falls outside that window. 24 segments measured 1.62 mm of error against 0.19 mm for 14 — and 14 is exactly one chip, being 16 channels less the two the part fixes as guard and shield.
Put two tactile switches under every key at ±9 mm instead of one in the middle, because a rigid printed key has to feel the same at its corners as at its centre. At 160 gf per switch that is 160 gf off-centre and 320 gf pressed dead-on, which lands inside the force band derived for the metal snap domes it replaced.
Gave the logic and the LEDs independent bucks rather than one buck and an LDO: slider sensitivity tracks logic-rail noise, so 30 addressable pixels stepping current must not land on that rail. The 3.3 V buck's input is Schottky-ORed between the 24 V tap and USB VBUS, so the board comes up and flashes on USB alone with the 5 V LED rail dead.
Wrote down what could not be known. The tactile switch's snap ratio is uncomputable because Alps publishes operating force, travel and life for the part but not release force; an earlier revision of the spec quoted 85.7%, that figure belonged to a different, retired switch, and the spec now records the withdrawal and forbids citing a number at all.
Elipse · no public repo
FIG. 06
Active dev Apache-2.0
pcbrouter
A KiCad autorouter in Rust — exact integer geometry, judged by KiCad's own DRC
A KiCad-native autorouter and routing verifier written in Rust, grown out of the toolkit that finished the Zone Controller. It reads and writes real KiCad boards, proves its model against pcbnew before it routes anything, checks its own output with KiCad's DRC, and is benchmarked on a corpus of published hardware.
field-identical to pcbnew1,212 footprints · 4,514 pads
Pad geometry
within 3.1 µmvs KiCad's own polygons, 4,337 pads
Olimex, 2-layer
71 s vs 14 min 15 sagainst the Python reference
Peak memory
58 MB vs 605 MBsame board, same task
New DRC
no clearance, short or holeon every board KiCad judged
Reruns
byte-identicaldeterministic at 0.25 mm tiles
Engineering detail
Built the whole geometry stack on exact integers: board coordinates parse from decimal millimetres straight to integer nanometres, and clearance predicates run on i128 and 256-bit rationals — so no clearance decision anywhere rests on a floating-point comparison.
Made KiCad the arbiter rather than the router's own opinion. The loader's dump matches pcbnew field for field across 1,212 footprints, 4,514 pads, 15,107 segments, 2,350 vias and 108 zones; pad outlines land within 3.1 µm of KiCad's own polygons; and the obstacle index is cross-checked against an independent shapely model on every segment of every board.
Ported the Python reference router to Rust: the two-layer Olimex ESP32-PoE routes in 71 seconds against 14 minutes 15 seconds, in 58 MB against 605 MB — and where the reference leaves 45 dangling tracks and 6 dangling vias behind, the port left none. Completion caught up once negotiated congestion landed.
Added PathFinder-style negotiated congestion over a tile graph with exact capacities, with the resulting corridors steering the exact detailed router. It took the same board from 111–118 unconnected down to 96, and produced byte-identical boards across two runs.
Found what the model was blind to twice, and re-measured both Olimex boards against the corrected model rather than keeping the flattering numbers. First copper the router was not treating as obstacles: graphic shapes such as net-tie bars, and text on copper layers. Then connectivity — it had been reading a poured net from the zone fills saved in the file, which KiCad discards and recomputes, so on the four-layer board the ground pour never entered the task list at all. Rebuilding the pour model to KiCad's own fill rules took that board from 63 unconnected to 29, with no pour split by routing for the first time.
Benchmarks on a commit-pinned corpus of published designs — HackRF One, Bus Pirate 5, and Olimex ESP32-PoE in both two- and four-layer form — each board loaded, stripped of every track and via, re-routed from nothing and scored the same way, with its upstream licence recorded.
personal project
FIG. 07
Boots on real hardware Active dev
Ember
A personal OS whose init and shell I wrote from scratch in Rust
An existing Linux kernel and a reproducible Yocto userland, with systemd removed entirely and replaced by two programs written from scratch: spark, a Rust init and service manager running as PID 1, and hearth, a shell that ships as root's login shell. It boots a real laptop from UEFI, updates over signed A/B images, and rolls itself back unaided.
5 blockseach SAFETY-argued; the libraries forbid it
Kernel → interactive shell
8.32 s0.06 s of it inside spark; target default at 13.27 s
Hardware smoke
59 checks passon the Victus, signed bundle v0.15
spark binary
1,065 KBstatic musl, unstripped
Update bundle
147.0 MiBverity-signed RAUC, A/B slots
Engineering detail
Wrote spark, a single-threaded Rust PID 1 that is a complete service manager in one epoll loop: a declared TOML service fleet with a dependency graph, readiness-driven parallel startup, window-bounded doubling restart backoff with permanent parking, boot targets with a bootloader rescue lever, and every service contained in its own cgroup v2.
Designed it so PID 1 cannot die — a PID 1 that exits panics the kernel — with no unwrap on the live path, `panic = "abort"`, and a panic hook that logs to `/dev/kmsg` and hangs alive rather than terminating. It also refuses to act as init unless it really is PID 1.
Put every syscall behind a single trait, so the reap, mount and supervision logic lives in a library that stays `#![forbid(unsafe_code)]` and is unit-tested against a fake kernel: spark never has to be PID 1 to be tested. The FFI is a thin binary holding two justified, SAFETY-argued unsafe blocks.
Drains SIGCHLD correctly rather than naively — one signal can mean several dead children because the signal coalesces, so it calls `waitpid(-1, WNOHANG)` until the kernel says stop, with a pid-to-service map that makes every death attributable.
Wrote hearth, the shell: lexer, parser, expansion, evaluator, operators, job control and a line-editor layer, shipped as root's login shell on the running system and held by 122 unit tests, 34 CLI tests and 40 pty assertions.
Built the distro as a Yocto layer with spark registered as a first-class init manager. The x86-64 image carries signed A/B RAUC slots, read-only roots, autonomous rollback and persistent home and SSH identity, and boots an HP Victus from a USB stick through UEFI and GRUB-EFI.
personal project
FIG. 08
Part of eOS
On-Device Voice Subsystem
A fully on-device, Rust voice pipeline: wake word → STT → TTS
The voice layer of eOS, built end to end in Rust to run entirely on-device. It chains a transfer-learning wake-word detector, multi-mic best-source fusion, Whisper speech-to-text, and Piper text-to-speech, with async-Rust barge-in for natural interruption.
Trained a transfer-learning wake-word detector using Google's speech_embedding backbone with a PyTorch head, exported to ONNX and run on the pure-Rust tract runtime
Implemented multi-mic best-source fusion across ESP32 satellite microphones to pick the cleanest audio
Integrated Whisper for speech-to-text and Piper for text-to-speech, all running locally on the device
Built async-Rust barge-in so the system can be interrupted mid-response
I'm an Electronics & Communication Engineering graduate (RGUKT Srikakulam, 2026) who works across the entire embedded stack: control boards drawn in KiCad and sent to fabrication, bare-metal and RTOS firmware on ESP32 and STM32, a custom Yocto Linux distribution built from the recipe up, and ML and voice models running directly on-device. The interesting problems usually live at the seams between those layers — that's where I spend my time, and it's why I like owning a system end to end, from the device tree to the deploy flow. Right now that means contributing to eOS at Elipse; before that, an anti-collision safety system I built firmware for as an intern went into production at Tata Steel BlueScope — the projects above tell both stories in full.
03
// 03 — revision history
Where I've done the work
May 2026 – Presentactive
Embedded Systems Engineer · Elipse
Hyderabad, India
Contributing to eOS at Elipse — a custom Yocto-based Linux distribution for the Raspberry Pi 5 — owning the build-and-deploy pipeline, the Rust sensor-fusion framework, the on-device voice subsystem, and the control boards the fleet runs on.
Design the fleet's control hardware in KiCad, schematic through layout to the fabrication package — a 24 V per-room controller (re-spun at 57% less board area and running the fleet today), a 48 V, 16-channel floor controller on a four-layer impedance-controlled stackup, and a SELV wall keypad — plus the bench bring-up and rework that follows each one.
Own the build-and-deploy flow end to end: Yocto recipes across the meta-eos layer with BitBake PR bumps, AUTOREV pinning, and IPK packaging, through the in-house eos-build CLI, WIC images, bmaptool flashing, and RAUC A/B verification.
Built the on-device voice subsystem in Rust: transfer-learned wake word (PyTorch → ONNX → tract), multi-mic best-source fusion across ESP32 satellites, Whisper STT, Piper TTS, and async barge-in.
Authored ESP32 satellite firmware (ESP-IDF v5.2): BLE provisioning with on-chip EC P-256 keygen and X.509 CSR exchange with the hub CA, full NVS lifecycle across OTA, and SNTP-synced audio streaming.
Developed an industrial Anti-Collision System for crane operations — deployed at Tata Steel BlueScope — using real-time UWB proximity detection and zone-based safety logic to prevent hazardous crane movements.
Shipped the Anti-Collision System to production at Tata Steel BlueScope: real-time UWB proximity sensing with zone-based safety logic driving industrial relays that halt unsafe crane motion.
Wrote ESP32-S3 dual-core firmware — one core for time-critical UWB ranging, the other for zone logic and the embedded web UI — linked over ESP-NOW and driving 8-channel Masibus relays via MODBUS RTU over RS485.
Interfaced STM32 with AHT10 and ADS1115 — timers, internal ADC/DAC, 2/4-wire RS485, LoRa long-range links, and Masibus DI/DO/AI/AO cards (STM32CubeIDE).