Hello World, I'm cvag

  • Software Engineer
  • Industrial IoT
  • Systems Monitoring
  • Backend & Infrastructure
Name
Christopher Vaggelis
Email
crisvaggelis@gmail.com
GitHub
github.com/ChrisVagg
CV
View CV

Either increase your sacrifice or reduce desire.

I'm a software and R&D engineer who builds and owns systems end to end: hardware, IoT, networking, the software stack and the distributed services that tie them together. I helped build an industrial immersion-cooling product from the ground up. What I enjoy most is finding solutions to problems.

Career

2023 — Present

Software Engineer • R&D Engineer

@ Synapsecom Telecoms S.A.

Software engineer across Industrial IoT, Systems Monitoring and Backend & Infrastructure for Synapsecom and Coolblock, the company's immersion-cooling product.

  • Coolblock software — Build the software solutions for the company (Coolblock Panel, Coolblock Portal, Coolblock Telemetry).
  • Coolblock plant — Research and Development Engineer for the development, optimisation and automation of the company's industrial product (Immersion Cooling System R&D, Immersion Cooling IoT, Parallel Systems Cooling Automation).
  • European research — Lead for the company's work package in MODERATOR, a Horizon Europe project on immersion cooling and waste heat recovery for data centres, where Synapsecom contributes the immersion cooling system installed and operated in its own data centre, dissipating 40 kW of IT heat at a PUE of ~1.1 (Immersion Cooling System R&D).
  • Data centre infrastructure monitoring and control — Monitoring the DC infrastructure with custom software solution (Data Center Infrastructure Monitoring).
  • Network and IT monitoring — the data centre's routers, switches and IT systems, monitored with Observium, Zabbix, Akvorado, Uptime Kuma and Grafana.
  • Immersed IT equipment monitoring — out-of-band health, thermal and power telemetry for the multi-vendor servers running inside the immersion cooling system, using Redfish Standard (Redfish Server Telemetry).
  • Metering and automated per-tenant power billing report — Custom solution for power and energy consumption for colocation service tenants (Tenant Power & Energy Monitoring Platform).
  • Mentoring — Supervised a software engineering intern for three months, guiding them through building and testing a feature for Coolblock Portal.

2022 — 2023

Presidential Guard

@ Hellenic Army

  • One year of national service with the Presidential Guard.
  • Awarded the rank of Lance-Corporal, Company Duty — supervising the unit on duty.

2021

Software Engineering Intern

@ PD Neurotechnology

The algorithm that turns a patient recording into a measurable clinical result, for a Parkinson's disease monitoring platform.

  • Signal analysis — motion from a 9-axis IMU treated as time series, moved into the frequency domain with a Fourier transform, and classified into a score per symptom.
  • Implementation — the trained algorithm rewritten in Java and shipped as a class inside the company's Android app.
  • Validation — scores checked against real patient recordings, clinical observation and computer vision over the test videos, then delivered to the production solution (Signal Analysis for Parkinson's Disease Symptoms).

2016 — 2022

Diploma — Electrical and Computer Engineer

@ University of Patras School of Engineering

  • Five-year integrated Diploma in Electrical and Computer Engineering, a Master's-level qualification.
  • Diploma thesis: blockchain health records — a platform where patients grant or revoke a doctor's access to their vital signs on-chain, built end to end from sensor board to Solidity contract and Android clients. (thesis, Blockchain Health Records Platform).

Featured Projects

Production systems and personal engineering projects. An abstract architecture and summary of each project is shown below.

Personal

Redfish Firmware Rollout

Personal project · 2026

github.com/ChrisVagg/redfish-rollout-tool

Firmware rollout for server BMCs over the standard Redfish API: a fleet poller, a pre-flighted rollout in a canary and gated waves, and a lab of ten emulated OpenBMC GB200 NVL BMCs where the rollout really flashes firmware. It is built on the latest DMTF schemas with no vendor code, reads production ASUS, Dell, HPE, Supermicro and Gigabyte BMCs, and writes only to the lab. Beside them run the services a rollout needs: NetBox as the inventory's source of truth, a SeaweedFS image store that admits only packages whose signatures verify, an nginx HTTPS image cache in front of it that every update takes its image from, by push or pull, PostgreSQL holding a lock per host and every event of every run, and Prometheus, Loki and Grafana, which watch every BMC, keep its own logs and events and follow every rollout live. It is split as production would be: a control plane with the inventory, the store, the rollout state and the monitoring, and a site with its exporter, a Prometheus agent and the image cache, which only connects out.

  • +11

Personal Portfolio

Personal project · 2026

ProblemA portfolio needed to stay easy to edit as plain text, and deploy without hand-run steps.

A personal portfolio where every section is a Markdown file with front matter, and a single Node.js script turns the folder into a static page: projects, career, technologies and diagrams, with no framework and one dependency. The first version shipped as container images through GitHub Container Registry to a virtual machine behind Caddy; the current version is built and hosted by Cloudflare Pages.

  • +5

Coolblock — Immersion Cooling Platform

How it fits together

From bare metal to platform solution.

01020304
Immersion Cooling System R&D01

The engineering side of the industrial unit: Heat Dissipation & Energy efficiency analysis — solved for a Power Usage Efficiency under 1.1.

Immersion Cooling IoT02

IoT ecosystem & Network configuration of the unit's core components.

Coolblock Panel03

The on-site software solution for telemetry and operations in the industrial unit.

Coolblock Portal04

The fleet platform for the company's all production industrial units: one place to watch and operate every deployed unit.

Coolblock's Software Solution

Coolblock · Synapsecom Telecoms S.A. · 2023 — Present

ProblemNo single stack took a unit from thermal design to remote operation, on customer sites the company does not control.

The whole immersion cooling software solution, end to end — the parent of Coolblock Panel and Coolblock Portal. Every deployed unit runs its own Coolblock Panel, which controls the industrial system on site, and the Coolblock Portal watches and operates the whole fleet from one place.

Coolblock Portal

04

Coolblock · Synapsecom Telecoms S.A. · 2023 — Present

Operates the whole current-model fleet of 12 deployed systems from one place, with zero downtime since launch

ProblemUnits sit on customer networks with no inbound access and nobody watching. There was no one place where the team could monitor, alert on and operate the whole fleet, and each customer only their own systems.

The fleet platform for the company's production immersion cooling systems: one place to monitor and operate every deployed unit, and a customer portal where each customer reaches only their own systems. Each system in the field runs its own Coolblock Panel, and the Portal reaches all of them across a zero-trust overlay network, while role & relationship-based access control scopes every user to the units assigned to them.

  • +22

Coolblock Panel

03

Coolblock · Synapsecom Telecoms S.A. · 2023 — Present

Deployed on 12 systems, every current-model unit

ProblemEvery model and PLC needs local control on sites nobody can easily reach, and with no one watching, each unit must raise its own alerts.

The on-site software solution for a single industrial system, running on the embedded industrial PC. The same containerised stack ships to every system in the fleet and is provisioned per installation by model and serial number, so one build serves the whole product line while each appliance drives exactly one machine. It is the field half of Coolblock Portal.

  • +23

Coolblock Telemetry

Coolblock · Synapsecom Telecoms S.A. · 2026 — Present

Monitors 20 systems, legacy and current models alike

ProblemLegacy units have no embedded PC to run the Panel, so their readings never left site, and every unit needed one lossless cloud path that spares the PLCs.

The telemetry path from the controllers of every immersion cooling unit into a cloud time-series store: a gateway reads the controllers and publishes their readings over MQTT, a broker carries them, and a subscriber transforms the raw payloads and writes them to the database. It picks up where Immersion Cooling IoT leaves off — the controllers that layer configures are the ones read here.

  • +4

Immersion Cooling IoT

02

Coolblock · Synapsecom Telecoms S.A. · 2023 — Present

ProblemEvery unit ships with the same LAN addresses, yet engineers must reach any unit's devices remotely, without a site visit or a public port.

The IoT and network layer of an immersion cooling unit: the sensors calibrated and configured, the controllers that carry their readings, the fieldbus between controller and PLC, and the LAN everything sits on. It is the layer between the plant designed in Immersion Cooling System R&D and the software of Coolblock Panel — the path a physical reading takes before any application ever sees it.

Parallel Systems Cooling Automation

Coolblock · Synapsecom Telecoms S.A. · 2026 — Present

Cools 200 kW combined heat on one loop

ProblemTanks sharing one dry cooler would fight over it, and without a single controller the water side stays passive, with nothing holding its temperature.

Automation for one cooling loop feeding several industrial systems in parallel: the pumps that pull water from the buffer, the dry cooler fan that rejects the heat, the logic that decides how hard each has to run, and the path from the plant's own OPC UA address space out to the company's metrics and dashboards. It applies the thermal design of Immersion Cooling System R&D to a multi-system installation, wired and networked the way Immersion Cooling IoT lays out a single one.

  • +9

Immersion Cooling System R&D

01

Coolblock · Synapsecom Telecoms S.A. · 2023 — Present

All models run at a PUE under 1.2

ProblemAir cooling tops out at a PUE of about 1.5 and cannot keep up with cooling dense HPC clusters.

The process side of the immersion cooling line, where everything serves the PUE target: the heat and flow balance that fixes where the system has to run, the component selection that follows from it, the PLC routines that hold it there, and the instrumentation it is all measured on. It is what Coolblock Panel reads and writes on site.

Data Centre Infrastructure

Data Center Infrastructure Monitoring

Synapsecom Telecoms S.A. · 2023 — Present

Monitors 2 data centres

ProblemGenerators, UPS, cooling, fire detection and PLCs each speak a different protocol, so the DC engineer had no single page to monitor, alert on and operate them.

Real-time monitoring and control for critical data centre facility systems: every device is polled over its own protocol, normalised into one time-series store, and visualised and alerted on in one place, from Uniflair precision cooling to VESDA fire detection.

  • +4

Tenant Power & Energy Monitoring Platform

Synapsecom Telecoms S.A. · 2023 — Present

Serves 60 tenants across 100+ racks, billed automatically every month

ProblemTenants are billed for energy, but only some racks have a PDU and meters were read by hand, so bills could not be traced or automated.

An end-to-end platform for collecting, storing and billing electrical consumption per data centre tenant. Racks fitted with a power distribution unit are polled over SNMP; racks without one are metered directly by current transformers measuring each phase. A scheduled Python job reads both, so every rack is accounted for regardless of how it is instrumented.

Nautobot is the source of truth for tenants, racks and infrastructure ownership. The collection logic lives in GitLab and is synchronised with Nautobot, so the job is scheduled, its changes are reviewable and its reports are structured. Measurements are written to InfluxDB and presented through Grafana.

  • +6

Redfish Server Telemetry

Synapsecom Telecoms S.A. · 2023 — Present

Monitors 5 clusters running the company's VPS service

ProblemSubmerged servers run workloads that must not be touched, yet their temperatures, power and health still had to be measured. OS agents go silent when the OS is down and IPMI differs by vendor, so collection had to rely on the BMC alone, through one standard API.

Health, thermal and power telemetry for the cluster servers running inside the immersion cooling system, read out of band across Supermicro, ASUS, Gigabyte and Dell hardware. Each server's baseboard management controller is polled over the Redfish API, so readings come from the hardware itself and keep arriving whether the OS is up or down.

  • +2

Earlier Work

Signal Analysis for Parkinson's Disease Symptoms

PD Neurotechnology · 2021

Used with over 100 patients

ProblemParkinson's symptoms were judged only by eye during a clinic visit, with no objective measure from the patient's daily life.

Symptom scoring for Parkinson's disease, built on signal analysis. A custom wearable records the patient's movement during guided tests and daily life, an Android app receives the recording, and the algorithm inside it turns raw motion into a score per symptom that is pushed to the cloud platform.

  • +1

Blockchain Health Records Platform

University of Patras · Diploma thesis · 2021 — 2022

ProblemPatients could not choose which doctor reads their vital signs, or take that access back.

A health monitoring and management platform where patients own their vital signs and grant or revoke a doctor's access on-chain. Conceived during the COVID-19 pandemic, it pairs a custom sensor board with two Android clients and an Ethereum contract that holds the authorisation logic.

  • +5
FIRMWARE INTO THE LAB · WHAT EACH SCRIPT DOES AND FEEDS packages → the store · the catalog → control/catalog/ · bmc.mtd → lab/ Jenkins 2 newest builds signed .tar Download download-fw-package.sh into ingest/staging/ Sandbox verify-fw-package.sh offline · signatures Promote promote-store-fw-package.sh sha256 again · upload Store SeaweedFS source copy Cache nginx · HTTPS fetch on miss the catalog → control/catalog/lab/ bmc.mtd → lab/: the BMCs' first boot rollout.py fetches it NetBox · the inventory hosts · OOB IPs · tags · contexts REST · read-only, per service poller.py collect: crawl each BMC's tree health · firmware · inventory telemetry · capabilities · diff · oem Site folder · lab/ scenario.sh: make fault's scenarios policy.yaml: canary, waves, gates prometheus-agent.yml: the site's agent cache/: the cache's TLS certificate out: snapshots · runs · reports · spool pipeline.sh · rollout.py plan → canary → waves → report locks its hosts · records every step report.json · report.html Redfish REST · HTTPS GET · POST update · POST reset LAB HARDWARE · 10 QEMU SERVERS IN 3 RACKS, A BMC IN EACH · 127.1.0.1-10 Every container one per BMC · about 2 GiB of RAM QEMU server · gb200nvl-bmc only its BMC is emulated, no host CPU BMC · AST2600 chip OpenBMC GB200 NVL bmcweb · Redfish · EventService bmcN-flash volume · bmc.mtd 64 MiB flash · first boot: the older build rack r1 4 servers bmc1 127.1.0.1 bmc4 127.1.0.4 bmc7 127.1.0.7 bmc10 127.1.0.10 rack r2 3 servers bmc2 127.1.0.2 bmc5 127.1.0.5 bmc8 127.1.0.8 rack r3 3 servers bmc3 127.1.0.3 bmc6 127.1.0.6 bmc9 127.1.0.9 canary wave 1 wave 2 events → Vector :8088 → Loki · logs 5 min exporter: GET every 10 s exporter · agent the agent pushes every 10 s Prometheus remote write · Pushgateway Pushgateway · metrics pushed by rollout.py · PUT Loki · events · logs rollout · Vector · logs Grafana Lab · Fleet manager Lab · Host Lab · Pipeline Lab · Update details State DB · PostgreSQL host_locks: a lock per host · events: every step of every run SQL · grafana, reads only control plane: control/, one per fleet site: site/ and the site's folder, one per site, connects out only lab hardware: lab/

Pipeline

Plan
every BMC is read live and pre-flighted, and the hosts that pass are put into a canary and waves as the site's rollout policy says. The plan is saved, and the later stages run exactly that plan.
Canary per model
one host of each hardware model goes first, because a bad image is almost always model-specific; any canary failure stops the pipeline before wave 1.
Waves that grow
waves widen as the evidence builds (5%, 25%, then the rest in production) and spread across racks, with a cap per rack per wave, so no rack loses more nodes than it can spare.
Gates
the first waves halt on any failure, later ones once failures pass a threshold. A wave that passes soaks, then its hosts are read again before the next one starts.
One pipeline per host
before a stage touches anything it takes the lock of every host in it, all or none; a host another pipeline holds stops the stage, and a host left for a person keeps its lock until someone checks it and gives it back.
Policy as code
canary size, waves, rack spread, halt threshold, parallelism and soak live in a reviewed policy.yaml per site, because it decides how many hosts a bad image can reach.

Every host

Six steps
pre-flight on a fresh read, drain when the reset restarts the host, update, reset, post-check, undrain; the hosts of one wave run side by side.
Update
every image comes from the site's HTTPS image cache. Push: the rollout fetches it, checks its sha256 against the firmware catalog and posts it to the BMC's MultipartHttpPushUri. Pull: a BMC that offers SimpleUpdate is handed the cache URL and downloads it itself. Either way the update is followed as a Redfish Task, recording every change of state, progress and message.
Post-check and rollback
the target version must run, health must be no worse than before and no new job may have failed; otherwise the version from before is reinstalled, reset and checked again.
How the version is known
the post-check records whether the running version is only the firmware's own word, self-reported, or the BMC offers SPDM attestation for that part (ComponentIntegrity), so every report shows where the fleet relies on the firmware's word. Checking the signed measurements is designed, not built.
Left for a person
a host that can't be rolled back stays drained as needs_attention instead of being retried.

Pre-flight

Go, skip or block
every host gets a verdict with every reason: a downgrade, a component Redfish can't update, a target below LowestSupportedVersion, a missing image, a sha256 mismatch, an oversized image, no rollback path (an A/B bank doesn't count, as switching banks isn't built), a disabled update service, bad health or failed jobs.
Fail closed
only standard DMTF properties are read; what a BMC doesn't expose in standard form is reported as not verified rather than guessed.

Lab

Ten emulated BMCs
OpenBMC's GB200 NVL build boots in QEMU's AST2600 machine inside Docker, ten containers each with their own flash, so an update survives a restart and bmcweb serves the same Redfish a real BMC would. Each BMC has its own address, Redfish on port 443, as on a real network.
A real update
the lab boots the older of OpenBMC's two newest builds and the baseline approves the newer, so the first rollout flashes real firmware, not a mock.
Bad updates
faults are injected where real ones show: an update that silently changes nothing, a health regression after the flash, an image the BMC rejects, a checksum mismatch, a BMC that doesn't come back. A script runs each one and checks how every host ended.
A mixed rollout
the canary passes, then one wave holds a good host, one that is rolled back and one whose image is rejected: the strict gate halts it, and the last six BMCs are never touched.
One command each
from a fresh clone, make setup lab-up starts the control plane (NetBox, the state database, the image store and the monitoring) and the lab site (the image cache, the exporter and its Prometheus agent), then boots the ten BMCs; make dry-run and make update run the rollout, make test checks its decisions; make help lists the rest.

Services

Inventory
NetBox is the only inventory, for both sites: no file lists a host. Each is a server with its rack, project and model, its BMC a management-only interface whose address is the out-of-band IP; tags make a host a canary or writable, and config contexts say how its BMC is reached. The poller, the exporter and the rollout each read it with their own account, which may only view devices, and a read-only token. A device whose BMC account isn't named in its context, or that isn't reached over HTTPS, is skipped, so an edit in NetBox can't send one of the tools' secrets as a BMC login. A plan keeps each host's entry as NetBox gave it, and a later stage refuses a host whose entry changed since. The firmware catalog and the approved versions stay files, reviewed in git.
Image store
SeaweedFS, an object store standing in for production's S3 or Artifactory, keeps the only copy of every approved firmware package. A package gets in through three steps: a download over HTTPS that never opens it; a check in a sandbox container with no network, a read-only root and no privileges, which accepts only an OpenBMC package's regular files, verifies its manifest, key and image signatures against a key pinned in the repository, never the one the package carries, and checks the machine it was built for; then a promotion that checks each package's sha256 again before uploading it and writing the catalog. One package that fails stops them all. The lab pins OpenBMC's public development key, so there the gate shows the mechanism; production pins the vendor's.
Image cache
nginx serves the approved images over HTTPS, read-only, fetching each from the store once, under the paths the firmware catalog names: the catalog holds each image's path and sha256, the cache only the files. The rollout downloads each image it needs, checks it and pushes it; a copy that doesn't match is deleted and fetched again. The lab's BMCs take a push only, so pull is covered by pre-flight's checks and the tests.
Rollout state
PostgreSQL keeps a lock per host and every event of every run, whole, as JSON: append-only to the rollout's own account, and read by Grafana with an account that may only read.
Exporters
the poller also runs as one Prometheus exporter per site, the lab every 10 seconds and production every 30 seconds, read-only, with one Redfish session per BMC so the BMCs' own logs don't fill with logins: every reading with its unit and thresholds, the health of every component, every network link and its speed, each part's SPDM or TPM integrity as the BMC reports it, and each firmware component with whether and how Redfish can update it and whether it reports an A/B bank. A Prometheus agent beside it pushes every sample to the control plane, so nothing connects into a site.
BMC logs
every 5 minutes, over the same session, the exporter reads the new entries of each BMC's own logs, every standard LogService of its systems, managers and chassis and its JobService's log, and sends them to Loki. A BMC clock hours off (UTC labelled as another zone, on iDRAC and AMI) is corrected, a log cleared between two reads leaves a gap line, entries Loki couldn't take go again next pass, and a restart goes on from where the reads were, so nothing is stored twice.
BMC events
in the lab each BMC also pushes its events as they happen: make subscribe gives every host tagged writable one EventService subscription to the site's receiver, Vector, which checks the credentials the subscription carries and writes each event to Loki. The BMC retries a delivery that fails, and the 5-minute log reads catch what a lost one misses. Push rather than a server-sent event stream, on the evidence: all seven production models take subscriptions, only three advertise SSE, and the lab's SSE answers 404. Production waits: a subscription is a write.
Pushgateway
the rollout pushes its report as metrics after every recorded event: hosts by wave and state, versions, every step's duration, each gate.
Prometheus and Grafana
Prometheus takes each site's samples from its agent, scrapes the Pushgateway and keeps 30 days. Grafana, admin login only, reads Prometheus, Loki and the rollout state, in a Lab and a Prod folder, each with a Fleet manager (every BMC, its equipment, health and firmware, and a BMC a pipeline is resetting shown as updating, not as an outage), a Host dashboard (one BMC in full: its firmware and A/B bank, every component's health, its logs and pushed events, its network links, every reading against its thresholds, every firmware update it had) and a Pipeline dashboard that follows a rollout live, down to which pipeline holds which host. The Lab folder adds Update details: how a BMC update runs there, each fact checked against the DMTF schemas and on a lab BMC (the image applied OnReset, the Manager's reset alone, a Task and no Job, nothing staged to cancel), the rollback per resource, and per pipeline every host's evidence, stage by stage: each pre-flight check, the push, the Task, the reset and the rollback.

Reports

One model, two views
every pipeline leaves one report.json for services and a terminal and HTML report for people, drawn from the same data so they can't disagree.
Evidence where it happens
each step's intent is written before it and its result after, so a run stopped halfway still shows where every host was: the pre-flight checks, the handover method and URI, the task and its message IDs, the reset type, a rollback's reason.
Verdict first
the result and what needs a person lead; versions before and after, and every host by wave, follow.

Tests

Decisions on real data
33 unit tests run the rollout's decisions on a lab BMC's recorded Redfish data, one fact changed per case, in about a second; make update runs them first and stops on a failure.
Mutation testing
48 safety rules are broken one at a time the way a careless edit would (a downgrade let through, versions compared as text, a sha256 mismatch ignored, the rollback installing the target, a held lock let through); each break must fail a test, and all 48 do. Its first run found a real gap in a gate's test.
Fault injection
the bad updates run the real pipeline on the lab's BMCs, past the network boundary where the unit tests stop.

Production

Read-only today
the same poller, plan and pipeline run against production with a ReadOnly account; the rollout refuses every host not marked writable, and none is.
Production policy
the first wave strict, later ones halting over 2%, and 30 minutes of soak between waves; at one host per rack per wave, 1,000 hosts in 100 racks take 11 waves after the canary.
Vendor extensions
make oem lists each model's Oem extensions per BMC firmware version, from the snapshots, those about jobs, updates, banks and staging first. On the real fleet it found what standard Redfish hides around an update: Dell's update job queue, AMI's second BMC image and HPE's backup BIOS ROM. It qualifies a model; no rollout decision reads it, since an Oem path can change with the firmware the rollout installs.
Towards a fleet
designed, not built yet: the same six steps behind an approved image store (S3, MinIO or Artifactory) with an nginx cache per site, a workflow engine (Temporal or Argo) that resumes a stopped run from the rollout state, one state database for every site's agents, the inventory from NetBox as in the lab, the firmware catalog and approved versions reviewed in git, site agents with short-lived credentials from Vault that drain through Slurm, and Alertmanager paging on every halt.
Protocol
  • Redfish API
  • DMTF JSON Schemas
Rollout
  • Python
  • Bash
  • Make
Lab
  • OpenBMC
  • QEMU
  • Docker Compose
Services
  • NetBox
  • SeaweedFS
  • nginx
  • PostgreSQL
  • Prometheus
  • Loki
  • Vector
  • Pushgateway
  • Grafana
Reports
  • JSON
  • HTML

ProblemA portfolio needed to stay easy to edit as plain text, and deploy without hand-run steps.

Content

Markdown as the source
each project, role and technology group is one Markdown file; front matter carries the title, period, problem, impact and stack, and the body becomes the popup.
Static build
a Node.js script renders the Markdown with marked, inlines the architecture diagrams and brand icons as an SVG sprite, and writes plain HTML and CSS.

Version 1 — GHCR & VM

Images in GHCR
GitHub Actions builds two images, the site builder and Caddy, and pushes them to GitHub Container Registry tagged with the commit.
Push to the VM
the pipeline copies the compose file to the virtual machine over SSH and restarts it on the images just pushed, pinned to that commit; a failed pull leaves the running version in place.
Nothing built on the box
the VM only pulls and runs images, so a small server never compiles anything.
Caddy
serves the static files with zstd and gzip compression, security headers and cache rules that keep rebuilt pages fresh while fonts and images stay cached.
Automatic TLS
Caddy obtains and renews the certificate through a DNS-01 challenge against Cloudflare, built in with a custom Caddy image.

Version 2 — Cloudflare Pages

Cloudflare Pages
the site is now built and hosted by Cloudflare Pages straight from the repository, with no server to maintain.
Same rules, both paths
a headers file mirrors the Caddy rules, so security headers and caching behave the same as on the VM; the GHCR pipeline stays in the repository, ready to switch back.
Content
  • Markdown
Build
  • Node.js
  • JavaScript
  • HTML5
  • CSS
Version 1 — GHCR & VM
  • Docker
  • GitHub Actions
  • GHCR
  • Caddy
  • Nginx
  • Let's Encrypt
  • Bash
Version 2 — Cloudflare
  • Cloudflare Pages
COOLBLOCK PORTAL · FLEET PLATFORM PUBLIC SURFACE Browser Cloudflare zone · proxied · DNS-01 token ORIGIN TLS · LET'S ENCRYPT Traefik mTLS · OWN CA / /auth /api /monitor nginx + React static SPA · replicated Keycloak OIDC · realm from config FastAPI uvicorn · replicated Grafana auth.jwt · verifies via JWKS admin proxy dashboard POST iframe request the caller's Keycloak JWT, forwarded as a header resources PostgreSQL isolated network Redis unit offline → last values and last-seen, served from here QUEUE PER INTENT telemetry × 6 · single-threaded each PLC writes × 4 · serialised time-series writes multi-threaded resources sync runs alone · read-only InfluxDB data network · not published alerts + history Grafana rules · REST API OPENZITI OVERLAY Portal dials in-process · OpenZiti Python SDK PLC operations pushed · POST · the Panel's API telemetry pulled · GET · the Panel's API circuit breaker per unit · 20 s → 300 s COOLBLOCK PANEL · ONE PER UNIT · × N SITE NETWORK runs standalone when the overlay is down Operator unit's LAN or corporate network via NAT Traefik terminates TLS on the unit's network interface the operator interface is the only published route OpenZiti tunnel host networking · unit's identity hosts the panel — no inbound port / requests inward nginx + HTML/CSS/JS operator interface · replicated Express server behind the interface FastAPI uvicorn · replicated · unpublished MySQL bound to localhost config · users · rules · audit Redis broker · six queues by intent latest-value cache Celery Beat scheduler every 5 s cached · every poll history · on GET QUEUE PER INTENT PLC writes PLC reads chain time-series writes time-series reads alert evaluation alert notification InfluxDB TSDB notifications email · Slack Discord · webhook telemetry in control actions FIELD PLC · Profinet · Modbus TCP same local network — no boundary crossed
industrial · plant, field I/O & controlsoftware & services

ProblemNo single stack took a unit from thermal design to remote operation, on customer sites the company does not control.

How it fits together

R&D — plant & thermal design
The engineering side of the industrial unit: Heat Dissipation & Energy efficiency analysis — solved for a Power Usage Efficiency under 1.1.
Unit network
IoT ecosystem & Network configuration of the unit's core components.
On every unit
The on-site software solution for telemetry and operations in the industrial unit.
Fleet platform
The fleet platform for the company's all production industrial units: one place to watch and operate every deployed unit.
PUBLIC SURFACE Browser Cloudflare zone · proxied · DNS-01 token ORIGIN TLS · LET'S ENCRYPT Traefik PathPrefix · Headers · HeadersRegexp mTLS · OWN CA / /auth /api /monitor nginx + React static SPA · replicated Keycloak OIDC · realm from config FastAPI uvicorn · replicated Grafana auth.jwt · verifies via JWKS admin proxy dashboard POST iframe request the caller's Keycloak JWT, forwarded as a header resources PostgreSQL isolated network Redis notifications PagerDuty · Slack · Discord email · webhook QUEUE PER INTENT telemetry wide · single-threaded each PLC writes concurrency 1 · serialised ziti SDK ziti SDK time-series writes multi-threaded resources sync runs alone · read-only InfluxDB data network · not published alerts + history Grafana rules · REST API OPENZITI OVERLAY telemetry pulled GET · the Panel's API control actions pushed POST · the Panel's API Portal dials in-process OpenZiti Python SDK · monkeypatched sockets FIELD Coolblock Panel hosted service under the unit's own identity CI · GITLAB CD · PORTAINER GitLab CI push to main · masked vars push GitLab Registry private · versioned pull Portainer git stack · redeploy deploy Target VM Portainer agent

Operates the whole current-model fleet of 12 deployed systems from one place, with zero downtime since launch

ProblemUnits sit on customer networks with no inbound access and nobody watching. There was no one place where the team could monitor, alert on and operate the whole fleet, and each customer only their own systems.

Edge & TLS

Domain and public edge
Cloudflare holds the zone and proxies the public hostnames: it terminates TLS at its own edge and re-encrypts to the origin, so the origin address never appears in DNS. It also holds the API token Traefik uses to answer the DNS-01 challenge, so certificate issuance and renewal need no inbound HTTP path of their own.
Origin routing
Traefik is the reverse proxy at the origin: it terminates the connection on Let's Encrypt certificates and dispatches every request to an internal service using a router rule.
Internal TLS
public trust stops at Traefik. Behind Traefik the stack uses its own certificate authority at deploy and issues a leaf certificate per service, and the internal services are mutually authenticated on those certificates: Traefik re-encrypts to every backend rather than passing plaintext between containers, and a service proves its identity to its peer instead of being trusted for sharing a bridge network. Internal TLS approach helped with the scaling of the project using Docker Swarm and deploy the software in targets placed in different regions.

Application

Application tier
a replicated FastAPI backend service, behind a React and TypeScript interface styled with Tailwind CSS and served as static assets by nginx, also replicated.

Identity

Identity
Keycloak as the OIDC provider used for authentication of users and authorization of resources. Keycloak's db is a PostgreSQL instance.
Two-layer role model
realm roles carry the tier and client roles carry the fine-grained permissions a tier is allowed to exercise. A composite binds one to the other, so an endpoint asserts a permission and never a tier, and what a tier reaches is retuned in the realm rather than in code.
Relationship-based access
a role says a caller may have write permissions to a resource, not which one. The fleet is modelled in Keycloak as two levels of nested groups, so granting someone access is just a group membership, and their token arrives carrying the paths that membership puts them on. The backend resolves whichever resource the URL names up to its parent group and checks that against those paths, so holding the write permission is still not enough to reach a unit outside the caller's assigned resources. It is what lets a customer sign in to the same portal and reach only their own immersion systems, while the company's team reaches the whole fleet.
Both checks in one place
the role gate and the relationship gate compose as a single request dependency, and a caller has to clear both to reach the handler.
TOTP as second factor
the realm requires a time-based one-time password on top of the credential: a user enrolls an authenticator on first sign-in and every later login is completed with a rotating code, so a leaked password alone does not reach the fleet.
Identity brokering
Keycloak brokers GitHub and Google as external identity providers over OAuth 2.0 / OIDC, so a user signs in with the account their organisation already manages and Keycloak maps the returned profile onto a realm user and its roles. Deprovisioning stays with the provider, and the portal itself only ever sees its own realm tokens.
Keycloak using backend as middle proxy
the interface never calls Keycloak's admin API itself: backend proxies those calls under an admin service account, and the resources sync worker holds a separate read-only service account to pull realm resources back on its own schedule in order to populate the cache. Neither account's token is cached or written to a database — each is requested, used and dropped for max security.

Observability

Dashboards provisioned from the backend
a Grafana dashboard is rendered from a Jinja template per system model and POSTed to Grafana's API, so a new model arrives with its dashboard.
Dashboard iframes and auth.jwt
Frontend's monitoring page asks the backend for a panel-iframe and Traefik carries the caller's Keycloak token through to Grafana in the header its JWT auth reads. Grafana verifies the signature against the realm's JWKS endpoint rather than holding a secret of its own, takes the user from the token's sub claim, and maps a realm claim onto a Grafana read only role.
Alerts and alert history
Grafana alert rules run over the fleet's time series, so a threshold breach or a unit's indicator problem raises an alert. The portal reads history for both live and resolved alerts back over Grafana's REST API and renders them in its own interface, so an operator sees the fleet's alert state and its history without leaving the portal.
Notification endpoints
an alert does not wait to be looked at: Grafana contact points carry it off the dashboard to PagerDuty for the on-call rotation, to Slack and Discord incoming webhooks for the team channels, to email over SMTP for the operators who want a record, and to a generic webhook for whatever ticketing or NOC system a customer already runs. Notification policies route on the alert's own labels — unit, system model, severity — so a threshold warning lands in a channel while a unit going of notifies someone, and grouping, silences and repeat intervals stay in the policy rather than being rebuilt per endpoint.

Queue

Queue per intent
Celery over Redis working as message broker, with worker count and concurrency sized to the job: telemetry collection runs wide across single-threaded workers (monkeypatched by the OpenZiti SDK to open their TCP connections over the zero-trust overlay), PLC writes stay serialised so two setpoints can never race a controller, time-series writes into InfluxDB run multi-threaded, and the Keycloak resources sync runs alone.
Threads pool, one task per process
the OpenZiti SDK loads its identity context into the worker process and monkeypatches the socket module process-wide. The overlay workers therefore run Celery's threads pool rather than prefork, whose forked children would lose that context, at a concurrency of one so no two tasks share the patched sockets. Throughput comes from replicas.
CQRS
commands and queries are implemented independently. A setpoint is a command, carried by the serialised PLC-write workers to the unit over the overlay; every read the interface makes is answered from the read model the telemetry workers keep current — latest values in Redis, history in InfluxDB — so a query never reaches a PLC. The split also keeps the two loads off a single worker pool: each side is sized for its own job and fails on its own, so reads keep being served while the write workers are down, and setpoints still go out while telemetry collection is down.
Circuit breaker per unit
a failed poll opens a breaker for that unit, held in Redis so every telemetry replica monitors it. The unit is skipped for a window that doubles with each consecutive failure, from 20 seconds up to a 300-second ceiling (exponential backoff approach), and the first poll after the window is the trial: one success closes the breaker and resets the count. An operator can also reset it from the interface and poll the unit immediately instead of waiting out the window.
Scheduling
using celery beat scheduler.
Tank registry synced on change and reconciled
Keycloak is the source of truth for which units exist, and the telemetry workers poll from a copy of that list in Redis. Creating or deleting a unit triggers a sync straight away, and a scheduled sync checks the list from Keycloak every 15 minutes and whenever the worker starts as security fallback mechanism.

Storage

Time-series storage
InfluxDB holds the fleet's history, bound to localhost and reachable only across the internal network rather than published. Also InfluxDB is the only data-source for Grafana service.

Networking

Network segmentation
seven bridge networks partition the stack by concern — public surface, application tier, identity, broker, data plane, relational stores and the field overlay — so a container only sees the services it needs to communicate.
Zero-trust field reach
only the telemetry and PLC-write workers sit on the overlay network, and they join it through the OpenZiti Python SDK embedded in the worker process rather than a tunneller running beside them.

Zero Trust Network Access Configuration

The Panel hosts, the Portal dials
the Panel publishes its API as a hosted service on the Zero Trust Network Access overlay under the unit's own identity, and the Portal's workers are enrolled identities authorised to dial it. Neither side opens an inbound port, and no VPN reaches into the customer's network.
Telemetry pulled
the telemetry workers poll each unit's Panel API — a GET request across the overlay — on a schedule.
Actions pushed
control actions travel the other way — POST requests — over the same overlay to the Panel, which is what actually reaches control units of the system. The Portal never speaks a field protocol itself.
Where the boundary sits
the Panel owns the machine and keeps controlling it on its own, with its own interface, storage and alerting; the Portal owns the fleet view. A unit that loses the overlay keeps running.

CI/CD

Build
every push to protected branch runs a GitLab pipeline: it builds the custom images and pushes them, versioned, to the project's private container registry, so the host never builds anything and a deployment names a version release.
Two environments
the dev branch builds and deploys to dev; main and version tags build and deploy to prod. Images are tagged by environment and version in one registry, so prod can never pull a dev build, and each deploy job is its own GitLab environment. Every prod release also cuts a branch named after its version, so any deployed release has a branch to return to.
Deploy
the last job calls the Portainer API: it writes the stack's environment from the pipeline's variables and redeploys the Git-backed stack, so Portainer re-clones the repository and its agent on the target host pulls the new images and recreates only the services that changed. The compose file lives in the repository, never on the host.
Edge & TLS
  • Traefik
  • Nginx
  • Cloudflare
  • Let's Encrypt
Application
  • FastAPI
  • React
  • TypeScript
  • Tailwind CSS
  • REST APIs
  • Microservices
Identity
  • Keycloak
  • OAuth 2.0 / OIDC
  • SSO
  • Identity Brokering
  • RBAC
  • ReBAC
  • TOTP 2FA
  • JWT
  • OpenZiti
Queue
  • Celery
  • Redis
Databases
  • InfluxDB
  • PostgreSQL
Observability
  • Grafana
Alerting
  • PagerDuty
Delivery
  • Docker
  • Portainer
  • GitLab CI
Patterns
  • CQRS
  • Circuit Breaker
SITE NETWORK OPENZITI OVERLAY Operator unit's LAN or corporate network via NAT Coolblock Portal dials in as an enrolled identity Traefik terminates TLS on the unit's network interface three published routes — nothing else is reachable OpenZiti tunnel host networking · unit's identity hosts the panel — no inbound port / /api /backend requests inward nginx + HTML/CSS/JS operator interface · replicated Express server behind the interface FastAPI uvicorn · REST + Redfish · DSP2064 Redfish · from cache MySQL bound to localhost config · users · rules · audit Redis broker · six queues by intent latest-value cache Celery Beat scheduler every 5 s cached · every poll history · on GET QUEUE PER INTENT PLC writes PLC reads chain time-series writes time-series reads alert evaluation alert notification InfluxDB chained from every PLC read notifications email · Slack Discord · webhook telemetry in control actions FIELD PLC · Profinet · Modbus TCP same local network — no boundary crossed CI · GITLAB CD · BASH ON THE UNIT GitLab CI builds versioned images push GitLab Registry private · pinned tags docker login · that account pinned images Embedded PC Ubuntu · Docker · systemd installer + credentials the unit's own identity GitHub deploy repo · one per unit installer script · compose definition system model · component models · device serial ziti JWT · GitLab registry account (read-only)
industrial · plant, field I/O & controlsoftware & services

Deployed on 12 systems, every current-model unit

ProblemEvery model and PLC needs local control on sites nobody can easily reach, and with no one watching, each unit must raise its own alerts.

Platform

One build, provisioned per unit
the stack is written against a model definition rather than one machine. System model, serial number and PLC model come from the installation's own environment, so the same images drive every model in the product line and supporting a new one is configuration, not a fork.

Edge & access

Edge
Traefik terminates TLS on the unit's network interface and publishes three routes and no more: the operator interface, the /api its Express server exposes behind it, and /backend, the one route that carries every FastAPI endpoint, the Redfish service among them. Nothing else on the box is reachable from the site network.

Application

Operator interface
replicated frontend containers serving HTML and JavaScript styled with Tailwind CSS, with an Express server behind them whose /api carries the panel's own control plane: sessions and sign-in, user administration, alert rules, notification targets and the audit log. Every route sits behind a session and the administrative ones behind a role using middlewares, and MySQL holds that state bound to localhost, so it is never reachable off the box.
API
a replicated FastAPI backend on uvicorn, serving the interface, PLC clients, and the unit's Redfish service. Its data clients open once per process at startup and close on shutdown: a Redis connection pool for the latest-value cache and the results workers hand back, MySQL for the unit's identity (serial number, model, PLC address, firmware version) read at startup, and InfluxDB, whose local and cloud health the API reports alongside Redis and MySQL.

Access control

Attribute-based sign-in
how an operator signs in depends on where the request comes from, not only on who is making it. The Express server reads the client address that Traefik forwards. A request that arrives over loopback can only come from the unit's own embedded PC, the screen mounted on the machine, so the operator signs in there with a PIN. From anywhere else on the network basic authentication is required.

Queue

Queue per intent
Celery over Redis, split into six queues each with its own replicas and concurrency: PLC reads and time-series reads run wide, while PLC writes, time-series writes, alert evaluation and alert notification each run at a concurrency of one.
Serialised writes
every write queue runs single-threaded, so setpoints reaching the PLC and points reaching InfluxDB are serialised and cannot interleave, no matter how many replicas are up.
CQRS
writes and reads run on separate queues and separate workers. Setpoints reach the PLC only through the serialised write queue, while every read is answered from the read model the polling cycle maintains — the Redis latest-value cache for live values, InfluxDB through the time-series reads queue for history — so no request ever reads a PLC directly. The split also keeps the two loads off a single worker pool: each side is sized for its own job and fails on its own, so reads keep being served while the write workers are down, and setpoints still reach the PLC while polling is down.
Scheduled polling
Celery Beat drives the polling cycle: every 5 seconds it enqueues a task on the PLC reads queue, and a worker polls the PLC through its protocol client, so telemetry arrives on a fixed clock instead of on request.

Field I/O

Field I/O
two protocol clients drive the system's control hardware: Profinet to one PLC and Modbus TCP to the other, carrying telemetry in and setpoints out. The controllers sit on the same local network as the panel, so control traffic never crosses a network boundary.

Storage

Time-series storage
InfluxDB holds the metrics: every polling cycle writes the readings taken from the PLCs, and the same task caches each reading in Redis under exactly the key the metrics endpoint checks first. A live value is therefore answered from the cache and never reaches the database.
Reads go through the queue, not the API
the metrics endpoint never opens a database connection of its own. It fills in a pre-written Flux template scoped to the unit's serial number — a caller chooses a chart, a field and a range, never a query — posts it to the time-series reads queue and takes the result back through Redis, so query load lands on a worker pool sized for it rather than on the process serving requests.

Alerting & audit

Alerting
rules are evaluated on every cycle against readings queried straight from the time-series store, on the evaluator's own clock rather than on a request, and a separate notifier queue delivers whatever fires, so evaluation is never blocked behind delivery.
Outbox for notifications
the evaluator never sends anything itself. A triggered alert is written to Redis as a record carrying a notified flag, and the notifier works through those records and sets the flag only after the send succeeds. A failed delivery is picked up again on the next pass, so an alert is delivered at least once and never lost.
Audit logging
control actions and configuration changes are recorded against the identity that made them, so any setpoint written to the plant can be traced back to a person.

Networking

Zero-trust remote access
the OpenZiti tunneller runs as a Docker service in the unit's own Compose project, with host networking and its own overlay identity, and publishes the panel as a hosted service on an OpenZiti network. Instead of opening a port, the panel is reachable only to enrolled identities the overlay authorises, so a unit needs no inbound firewall rule, no public address and no VPN into the site network.
Flat network by design
one bridge network carries every container except the host-mode tunnel. A single-tenant appliance on a controlled site network trades the fleet platform's per-concern segmentation for something an engineer can reason about on site.

Redfish

A standards face on a proprietary panel
the unit publishes a DMTF Redfish service on the site network, so a data centre consumes an immersion tank through the same REST vocabulary its DCIM already speaks for servers, instead of a Coolblock-specific integration. It is what OCP ACS Immersion Requirements Rev 2.1 asks of an immersion system: a Redfish-compliant management interface over a defined minimum set of readings.
The standard's model
the resources sit where DMTF DSP2064 puts them. OCP publishes no immersion profile yet, so the Rev 2.1 minimum set is encoded as our own Redfish Interoperability Profile and validated in CI, so conformance is something an integrator checks, not something they take on trust.
Served from the cache
every Redfish GET is answered from the latest-value cache the 5-second polling cycle already fills, the same path the operator interface and the Portal read from.
Writes through the same path
the service operates the unit as well as reading it, with the verbs the Redfish specification assigns: PATCH on a Control changes its SetPoint or ControlMode, and POST to an action URI such as #CoolingUnit.SetMode runs an operation. Either request is translated into the same parameter-and-value payload the PLC-write workers already take and queued on the serialised write queue, so a setpoint sent by a DCIM reaches the PLC exactly like one from the operator interface or the Portal: one write path, three entry points.
Redfish status codes
an accepted write returns 202 Accepted with a Task Monitor URI backed by the queued task, which answers 202 while the write runs and 200 or 204 once it lands. A rejected write returns 400 with a Redfish error body: PropertyNotWritable, PropertyValueOutOfRange or ActionNotSupported, so the request is refused before anything reaches the PLC.
Schema published, not just implemented
the response models are the single source of truth: the same definitions generate the live JSON Schemas and the OData metadata document the Redfish Data Model Specification defines, and CI exports them to a static schema site, so an integrator validates against the unit's schema before touching a unit. The Redfish surface gets its own filtered API reference, separate from the panel's internal API.

Panel and Portal

Opposite ends of the same overlay
the Panel's tunnel hosts its service inward onto the overlay under the unit's identity, while the Portal's workers dial outward as enrolled identities. Same network, opposite roles, and neither side opens an inbound port.
Telemetry out
the Portal reaches the panel across the overlay and keeps its own fleet-wide history, so the same readings serve both the local interface and the fleet view without the unit ever being exposed.
Setpoints in
control actions arriving from the Portal land on the same API and go through the Panel's own write queues, so a remote setpoint is serialised, audited and traced to an identity exactly like a local one.
Standalone by design
the Panel owns the machine. It keeps controlling, storing, evaluating alerts and serving its operator interface whether or not the overlay is up; the Portal adds the fleet view on top, it is not a dependency.

CI/CD

Build
application code and its pipeline live in GitLab, which builds and publishes versioned images to a private registry, so what ships to a unit is a pinned image rather than a checkout. The code and the images live in GitLab; the deployment repository that installs them lives in GitHub, one per unit.
Two environments
every commit on dev or main is built once into an image tagged by its commit, and that same image is promoted rather than rebuilt. The dev branch promotes it to a dev tag that stays in GitLab for testing; main promotes it to its semantic version and latest, and only those prod releases are published to GitHub's registry, where the units pull from. A dev build therefore never reaches a unit in the field.
Log shipping
a Vector container collects the unit's container and host logs and forwards them off the box into a central Loki store, so a deployed unit's logs are queryable without anyone logging into it.
Deploy
a Bash installer script brings the stack up from a single Compose definition held in that unit's own GitHub deployment repository, and the whole stack is one Compose project owned by a systemd unit.
Remote upgrade
a deployed unit is upgraded in place over the overlay by pulling pinned image versions and recreating the stack, so any installation in the fleet reaches a known release without a site visit.
A credential per unit, not one for the fleet
every immersion system authenticates to the image registry as its own service account rather than sharing a single pull credential. A unit lives on a customer's site, in a room nobody here controls, so it is treated as something that can be lost: if one is compromised its access is revoked on its own and the rest of the fleet keeps pulling. It is the same principle as the unit's own overlay identity, applied to the supply of its code — per-unit identity, individually revocable, so decommissioning needs no more of a site visit than upgrading does.
Field I/O
  • Profinet
  • Modbus TCP
  • PLC
Application
  • FastAPI
  • Redfish
  • OpenAPI
  • Express
  • HTML5
  • CSS
  • Tailwind CSS
  • JavaScript
  • REST APIs
Identity
  • RBAC
  • ABAC
Queue
  • Celery
  • Redis
Databases
  • InfluxDB
  • MySQL
Observability
  • Vector
  • Loki
Edge & access
  • Nginx
  • Traefik
  • OpenZiti
Platform
  • Embedded PC
  • Ubuntu
  • Docker
  • systemd
Delivery
  • GitLab CI
  • GitHub
  • Bash
Patterns
  • CQRS
IMMERSION COOLING UNITS · CONTROLLERS WITH IOT INTERFACES Controller 1 Controller 2 Controller 3 Controller N management IoT interface IoT gateway proxy · publish DEVICE PATH controller · sensor · reading TOPIC <unit>/<sensor>/<reading> one MQTT connection per controller from the gateway TELEMETRY HOST · PORTAINER STACK · DOCKER COMPOSE RabbitMQ MQTT broker Telegraf stage 1 subscribe mqtt_consumer stage 2 transformer starlark stage 3 write influxdb_v2 CLOUD INSTANCE InfluxDB points tagged by unit and sensor data source Grafana unit telemetry · per sensor CI · GITLAB CD · PORTAINER GitLab CI push to main push GitLab Registry container images pull Portainer stack · redeploy deploy Telemetry host Portainer agent
industrial · plant, field I/O & controlsoftware & services

Monitors 20 systems, legacy and current models alike

ProblemLegacy units have no embedded PC to run the Panel, so their readings never left site, and every unit needed one lossless cloud path that spares the PLCs.

Collection

Controllers as the source
the controllers are read over their management interface rather than the fieldbus, so collecting telemetry never touches the control path.
Gateway as the proxy
an IoT gateway reads every controller and publishes on its behalf, so no controller connects to the broker or holds its credentials.

Messaging

Self-describing topics
each reading is published under a structured topic hierarchy, so the topic alone identifies the unit and sensor a message belongs to.
Event-driven
the gateway publishes each reading as an event without knowing who consumes it, and consumers subscribe to the topics they need, so a new consumer can be added without touching the gateway or the existing subscriber.
Broker
RabbitMQ with its MQTT plugin and authenticated access, with persistent state so restarting the container keeps sessions and queued messages.

Processing

Subscriber
Telegraf holds a persistent QoS 1 session, so readings are queued at the broker rather than lost while it is down.
Transformer
a Starlark processor turns raw payloads into tagged time-series points and keeps the measurement timestamp, so a delayed message still lands at the moment it was measured.

Storage

Output
batched writes to the cloud database, buffered locally while it is unreachable.

CI/CD

One Compose project
broker and subscriber run as a single Compose stack managed by Portainer.
Build
every push to the main branch runs a GitLab pipeline that validates the stack, builds the broker and subscriber images with their configuration baked in, and pushes them to a private registry, so the host never builds anything.
Deploy
the pipeline triggers a Portainer redeploy of the Git-backed stack, which pulls the new images and recreates only what changed.
Secrets
credentials live only in protected CI/CD variables and reach the stack at deploy time, so nothing secret is committed.
Collection
  • I/O Controllers
  • IoT Gateway
Messaging
  • MQTT
  • RabbitMQ
Processing
  • Telegraf
  • Starlark
Storage & dashboards
  • InfluxDB
  • Grafana
Delivery
  • Docker Compose
  • GitLab CI
  • Portainer
Patterns
  • Event-Driven Architecture
INTERNET Engineer, off the WAN no route to a private WAN address the tunnel is the only way in Internet reached outbound, never inbound the router dials out to the cloud VPN — full tunnel, no translation NATed again at the edge CORPORATE INDUSTRIAL WAN Corporate industrial WAN private routed network — the gateway's WAN address lives here an engineer already on it needs no tunnel · the LAN's only path out GATEWAY · NAT WAN-IP:port egress Gateway device WAN side: a corporate address · LAN side: the unit's private range destination NAT — a port on the WAN address lands on device:port source NAT — the LAN reaches the internet as that WAN address uplinks, in order: wifi · ethernet · cellular tracked by ping UNIT LAN in: device:port out I/O controller IoT: HTTP · MQTT fieldbus: to the master reads sensor metrics sensor firmware & config sensor diagnostics PLC the fieldbus master reads controller metrics writes to field devices runs the plant Pumps & field devices Profinet or Modbus card on the models that carry one Embedded PC runs the Coolblock Panel Modbus / Profinet client Profinet / Modbus Profinet / Modbus polls the PLC over the LAN WIRED I/O Sensors calibrated · scaled · sampled wired in, not on the LAN Analogue a continuous range, scaled to engineering units two kinds carry it Digital a discrete on / off state 0 or 1, nothing to scale 4–20 mA current loop · live zero 0–10 VDC voltage · run-length bound
industrial · plant, field I/O & controlsoftware & services

ProblemEvery unit ships with the same LAN addresses, yet engineers must reach any unit's devices remotely, without a site visit or a public port.

Instrumentation

Sensor calibration and setup
each sensor is commissioned against reference readings and set up on its controller with the right range, scaling and sampling, so what the controller reports is the measured condition rather than a raw count.
Controller and sensor communication
addressing, protocol parameters and process-data mapping configured per device, so every reading arrives on a known address in a known unit instead of being discovered later in software.

Fieldbus

I/O controller, two interfaces
the controller carries an IoT management interface speaking HTTP and MQTT for configuration and telemetry, and a separate fieldbus interface — Profinet or Modbus — for communication with the master PLC. The two are configured independently: one is how the unit is managed and read, the other is how it is controlled.
PLC as fieldbus master
the PLC drives Profinet and Modbus devices and reads and writes the signal level underneath them, in two classes: analogue, a continuous range arriving either as a 4-20 mA current loop or as a 0-10 VDC input, and digital, a discrete 0/1 state.
The panel is a fieldbus client too
the embedded PC does not just sit on the LAN, it speaks the protocols: its own Modbus TCP and Profinet clients poll the PLC for telemetry and push setpoints back, so control traffic stays inside the unit and never crosses the gateway.

Network

What sits on the LAN
the I/O controller, the PLC, the embedded PC running Coolblock Panel, and, on the models that carry a network card, the pumps and any other Profinet or Modbus field device. Sensors stay wired into the controller rather than networked.
Network configuration
every one of those has fixed addressing on the unit's own LAN. There is no dynamic pool at all: each device is pinned by a static mapping against its MAC, so an address exists because it was declared rather than because something asked for one.

Gateway

One gateway, two sides
the LAN sits behind a gateway whose WAN side is a static address on the company's industrial WAN, not a public one, and whose LAN side is the unit's own subnet and first hop. The internet sits upstream of that WAN rather than being it.
Several uplinks, one policy
wifi, ethernet and cellular in that failover order, each tracked by pinging public resolvers so a dead uplink is detected rather than assumed. Public resolvers and rebind protection are set on the WAN side.

Address translation

Outbound, source NAT
LAN-to-WAN forwarding is enabled for both private and public destinations, translated to the address of whichever uplink is active. That is how a controller or the panel reaches the internet at all, and the corporate edge translates it a second time on the way out.
Inbound, destination NAT in two shapes
port-translating forwards for the field devices, where an outside high port lands on Modbus TCP 502 or on a controller's HTTP 80; and same-port rules bound to a specific WAN address for the panel host's HTTPS, HTTP and SSH. The rule set is declared once per uplink, so a failover does not take the mappings with it.
Devices with no default gateway still answer
gateway-less routing is enabled, so a PLC or controller commissioned with an address and nothing else stays reachable from outside its own subnet.
Why translation rather than routing
every unit ships with the same LAN layout, so a device has the same address on every unit: the PLC on one unit and the PLC on the next share an IP. The gateway's WAN address is what identifies a unit, and NAT at that boundary lets identical LANs coexist on one WAN without renumbering a deployment.

Remote access

From the industrial WAN, no tunnel
an engineer already on the corporate WAN reaches the gateway's WAN address directly and a forwarding rule lands them on a specific controller, PLC or panel. The WAN routes as far as the gateway; the gateway translates the rest of the way in. What is reachable this way is exactly what has a rule.
From the internet, a full tunnel
a private WAN address is not routable from the internet, so the path in is a VPN that terminates on the gateway itself and lands on its LAN interface. Source NAT is off on the tunnel: the client keeps its own address, and every device on the unit is reachable on its own LAN address and native port rather than through a mapped one.
Nothing opened for the tunnel
the gateway dials out to the cloud service that brokers the VPN session, so remote access needs no inbound rule on the WAN and no listening port on a public address anywhere.
The two paths are deliberately unequal
the forwarding rules are a keyhole per service, reachable by anything on that plant WAN, so they stay limited to what must be reachable from it. The tunnel is the whole LAN, and it is gated on the VPN instead. Either way a deployed unit is serviceable for configuration and diagnostics without a site visit.

Hardware stack

Controllers
I/O controllers as the IoT and field-IO layer.
Sensors
temperature, pressure, flow and level, calibrated per unit.
PLCs
Profinet and Modbus TCP controllers as fieldbus masters.
Embedded PC
runs the custom software solution Coolblock Panel.
Gateway
an edge gateway router: the unit's router onto the corporate industrial WAN, its NAT boundary, its VPN endpoint, and its multi-uplink failover.
Instrumentation
  • I/O Controllers
  • Industrial Sensors
  • Sensor Calibration
Field signals
  • 4-20mA
  • 0-10 VDC
  • Digital I/O
Fieldbus
  • Profinet
  • Modbus TCP
  • PLC
IoT
  • MQTT
  • HTTP
  • REST APIs
Network
  • LAN
  • Industrial WAN
  • VLANs
  • NAT & Port Forwarding
  • VPN
  • Edge Gateway Router
SHARED COOLING LOOP · SYSTEMS IN PARALLEL Dry cooler fan · VFD 3 cooled water Buffer pulled from the buffer Circulation pumps duty · standby, alternated on time Pump 1 · AC motor VFD 1 Pump 2 · AC motor VFD 2 System inlet · IFM sensor temperature · volume entering every system the PID's process variable System 1 water-side passive System 2 water-side passive System N water-side passive return · warm water back to the dry cooler speed setpoints COMPANY CLOUD InfluxDB loop metrics Grafana dashboards · alerts metrics INFRA VM Python OPC UA client Docker service · corporate network write · read router · port forward CONTROL · UNMANAGED SWITCH LAN PLC OPC UA server manual a fixed speed per drive PID inlet temperature → setpoint drive, sensor or process fault → alarm Unmanaged switch the loop's own LAN · static addressing IFM controller volume · temperature at the inlet sensor
industrial · plant, field I/O & controlsoftware & services

Cools 200 kW combined heat on one loop

ProblemTanks sharing one dry cooler would fight over it, and without a single controller the water side stays passive, with nothing holding its temperature.

The shared loop

Shared component, shared control
the dry cooler serves every tank on the loop, so no system can be cooled on its own terms. One controller owns the loop and all of them are held by the same decision, instead of each unit chasing a setpoint the others also move.
Passive on the water side
each system runs its own algorithm for dissipating heat into its dielectric fluid. On the water side the tanks are passive: they take what the loop delivers. That is what makes one controller enough — there is nothing per-tank to coordinate here, only water to deliver at the right temperature.

Drives

Circulation as duty and standby
two AC motors pull from the buffer, each on its own VFD. One runs as duty and the other stands by, and the PLC alternates them on time so running hours and wear are shared rather than accumulated on one motor. The control output moves the duty pump's speed; the standby one is a spare that stays a spare.
Heat rejection on its own drive
the dry cooler's fan runs on a third VFD, so rejection is modulated by fan speed rather than staged on and off. The loop holds a temperature instead of cycling around one, and the fan draws power in proportion to the heat actually being rejected.

Instrumentation

One measurement that drives everything
a controller carries the sensor measuring volume and temperature of the water entering the systems. That inlet temperature is the process variable the whole loop is controlled on, and the flow reading is what confirms the duty pump is delivering what its drive was told to.

Control

PLC as the only master
the PLC reads the controller, runs the control logic and commands all three drives. Nothing else writes to the plant, so there is exactly one thing deciding what the loop is doing at any moment.
Two modes, one program
manual and PID live in the same program and the mode selects which one owns the drive outputs, so switching between them is a state change rather than a different build.
Faults raise an alarm
a drive fault, a sensor fault or a reading outside its band raises an alarm rather than being quietly controlled around. The loop is not tuned to hide a failure, because a shared cooler hiding one would hide it for every system on the loop.

Control modes

Manual
the operator sets each drive's speed directly: the duty pump's speed and the fan's speed as fixed commands.
PID
the temperature entering the systems is the process variable, measured at the controller, and the loop drives it to a setpoint by moving the drives. The systems share the dry cooler, so regulating the water going into all of them is what regulates each of them.
Why the inlet, not the tanks
inlet temperature is the one condition every system on the loop sees the same, and the only one a shared cooler can actually act on. What happens inside a tank is that tank's own dielectric-side control, working against whatever the loop hands it.

OPC UA

OPC UA server on the PLC
measured values, setpoints, mode and drive states are published as an OPC UA address space, so the plant has one typed interface to read and operate through rather than a raw register map per client.

Network

Flat, isolated network
an unmanaged switch carries the controller and the PLC on the loop's own LAN with static addressing. The unmanaged switch is connected to an edge gateway device.

Telemetry and access

A client
a Python OPC UA client reads the live values and writes setpoints and mode back over the same session. It is how the loop is operated and monitored from software, and it goes through the server's address space rather than the PLC's own protocol, so the control logic stays the only thing driving the plant.
Where it runs
as a Docker service on an infrastructure VM with connectivity to the corporate network, so operating the loop does not depend on anything sitting on site.
How it reaches the PLC
a port forward on the site router publishes the OPC UA server to the corporate network.
Metrics to the cloud
the client writes the values it reads into the company's cloud InfluxDB, so the loop's history lands in the same store.
Dashboards and alerting
Grafana used for real time monitoring and alerting.

Hardware stack

Drives
three VFDs: two on the AC motors circulating water from the buffer, one on the dry cooler's fan.
Controller and sensor
an IFM I/O controller with the instrumentation measuring volume and temperature at the point water enters the systems.
PLC
the loop's controller: the manual and PID routines, the drive commands, the alarms, and the OPC UA server the plant is read and operated through.
Switch
an unmanaged switch, the loop's own LAN between the IFM controller and the PLC.
Control
  • PLC
  • PID Control
  • VFD
  • OPC UA
Instrumentation
  • I/O Controllers
  • Industrial Sensors
Software
  • Python
  • Docker
Storage & dashboards
  • InfluxDB
  • Grafana
Network
  • LAN
  • Industrial Ethernet
  • NAT & Port Forwarding

All models run at a PUE under 1.2

ProblemAir cooling tops out at a PUE of about 1.5 and cannot keep up with cooling dense HPC clusters.

Thermal design

Heat balance
the IT load sets the heat the dielectric fluid has to carry, and the fluid's own properties set how much flow that takes for a given temperature rise. A dielectric fluid holds less heat per litre than water and is thicker, so the same load needs more flow and costs more pressure — most of the design follows from that.
Setpoints
fluid temperature is derived from the equipment's own limit downwards, and flow from the temperature rise allowed across the tank against what that flow costs in pumping power. Both come out as bands that hold across the load range, not single design points.
Optimisation
more flow evens out temperatures but costs power faster than it buys them, and colder fluid helps the equipment while making the fluid thicker and the heat harder to reject. The operating point is the one with the lowest total power that still keeps every component under its limit at worst-case ambient — that total, against the IT load, is the PUE, so the design target is what the optimisation is solved for rather than something checked afterwards.
Manifold flow distribution
I contributed to the analysis of the manifold, one of the main components of the system, which spreads the circulating fluid evenly across the unit. The heat balance assumes every part of the tank sees the design flow; an uneven split leaves some equipment in warmer, slower fluid than that, so the balance only holds where the manifold delivers it.

Component selection

Component selection
pumps, heat exchanger, compressors, condensers and fans sized against that operating point rather than a catalogue rating, and compared under real thermal load, since off-design behaviour and fluid compatibility are not on a datasheet.
Calculator
a calculator we built to make that comparison. It imports manufacturers' data for compressors, condensers, refrigerants and dielectric fluids — datasheet PDFs and polynomial performance workbooks, fixed- and variable-speed — into one source-traced library, takes refrigerant properties, and sweeps compressor and refrigerant combinations across flow, ambient and heat-rejection conditions, simulating thermostat cycles and pull-down, to find the combination that best fits the operating point. It is a first-pass estimator: it narrows the candidates before supplier verification, not instead of it.

Load testing

GPU load from LLM inference
open-weight models served through Ollama keep the GPUs of the IT equipment in the tank under sustained load. That raises the power the equipment draws, so the heat balance, the setpoints and the components are checked against real heat dissipation rather than a nominal rating.
Facility equipment Generators · UPS systems Precision cooling Fire detection PLCs Modbus TCP/IP PROFINET MQTT SNMP REST API Node-RED poll · protocol translation scheduling · error handling normalises to consistent names, units, tags and timestamps one flow per equipment class InfluxDB retained for incidents, capacity, trends Grafana one operator view · real-time and historical Alert rules equipment faults · UPS battery · cooling failure · threshold breach · comms loss authorised control actions CI · GITLAB CD · PORTAINER GitLab compose definition + flows push Portainer redeploys the stack on commit deploy Stack on VM Node-RED · InfluxDB · Grafana
industrial · plant, field I/O & controlsoftware & services

Monitors 2 data centres

ProblemGenerators, UPS, cooling, fire detection and PLCs each speak a different protocol, so the DC engineer had no single page to monitor, alert on and operate them.

Collection

Multi-protocol collection
Node-RED flows poll each class of equipment over Modbus TCP/IP, PROFINET, MQTT, SNMP and REST APIs, so hardware from different vendors and communication layers reaches one workflow. The flows carry the polling, event processing, protocol translation, scheduling and error handling.
What is measured
generator state, alarms, electrical values and runtime; UPS input and output metrics, battery condition, load, bypass and power quality; cooling temperature, humidity, compressor and fan status and set points; fire-detection early-warning, fault and health conditions; PLC signals, interlocks and environmental values.
Normalisation
protocol-specific payloads become structured time-series points with consistent naming, units, timestamps, tags and equipment metadata.

Operator control

Operator actions
authorised control actions are issued from the platform through the automation layer, shortening operator response time.

Storage & dashboards

Storage and dashboards
InfluxDB retains the telemetry for incident investigation, capacity planning and trend review; Grafana presents real-time and historical views.

Alerting

Alerting
Grafana alert rules cover equipment faults, abnormal temperatures, UPS battery issues, generator alarms, cooling failure, communication loss and threshold breaches.

CI/CD

Source of truth
the Compose definition and the Node-RED flows are held in GitLab, so a flow is reviewed and versioned rather than edited inside the running instance.
Deploy
Node-RED, InfluxDB and Grafana run together as a Portainer stack on a virtual machine, and every update to the repository triggers Portainer to redeploy it, so what runs in the data centre is what was last merged.
Why it matters here
a flow edited live in the runtime is a change nobody reviewed and nobody can restore. Releasing from source control makes a change to facility monitoring as auditable as a change to anything else.
Protocols
  • Modbus TCP/IP
  • PROFINET
  • MQTT
  • SNMP
Collection
  • Node-RED
  • REST APIs
Storage & dashboards
  • InfluxDB
  • Grafana
Delivery
  • Docker Compose
  • Portainer
  • GitLab CI
Racks with a PDU load and accumulated energy over SNMP Racks without one Current transformers on each phase meters read over Modbus TCP/IP Nautobot job Python collectors · SNMP + Modbus TCP/IP scheduled · every run and its result retained GitLab reviewed · versioned Nautobot source of truth tenants · racks · owners synced in as a job which rack is whose InfluxDB points tagged by rack and tenant Grafana current load · consumption · trends per tenant or rack per-phase readings expose an unbalanced circuit Monthly billing report accumulated energy per tenant for the period traceable to the run that produced it
industrial · plant, field I/O & controlsoftware & services

Serves 60 tenants across 100+ racks, billed automatically every month

ProblemTenants are billed for energy, but only some racks have a PDU and meters were read by hand, so bills could not be traced or automated.

Metering

Hybrid metering
PDU-equipped racks are read over SNMP and the remainder fall back to energy meters, covering whole-facility consumption without requiring uniform hardware.
Electrical measurements
Python collectors retrieve active, apparent and reactive power, power factor, per-phase current and accumulated energy over Modbus TCP/IP and SNMP.

Automation

Scheduled automation
collection runs as a Nautobot job on a schedule, replacing manual meter readings with a repeatable workflow.

Billing

Monthly billing report
accumulated readings and historical series are combined into per-tenant energy consumption for the billing period, mapped to the correct Nautobot tenant and rack so billing is auditable.

Dashboards

Dashboards
Grafana shows current load, phase balance, consumption and trends per tenant or rack over configurable periods.
Phase monitoring
per-phase readings expose unbalanced loads, overloaded circuits and changes in equipment behaviour.

Hardware stack

Current transformers
coils measuring current on each phase in racks with no PDU.
Energy meters
meters exposing power and accumulated energy over Modbus TCP/IP.
Power distribution units
rack PDUs reporting load and accumulated consumption over SNMP.

CI/CD

Jobs from source control
the collection and reporting logic lives in GitLab and is synchronised into Nautobot as a job, so the code that reads the meters is reviewed and versioned rather than pasted into the platform.
Scheduled execution
Nautobot runs the job on its schedule and retains the result, so a billing figure can be traced back to the run that produced it.
Containerised delivery
the supporting services ship as Docker images, so the environment the collectors run in is rebuilt rather than maintained by hand.
Source of truth
  • Nautobot
Collection
  • Python
  • Modbus TCP/IP
  • SNMP
  • REST APIs
Metering hardware
  • Current Transformers
  • Energy Meters
  • PDU
Storage & dashboards
  • InfluxDB
  • Grafana
Delivery
  • Docker
  • GitLab
Board sensors · I²C CPU · PECI PSU · PMBus Drives · DIMMs Baseboard management controller Redfish REST API · out of band Thermal Power Health Python collector · cron on a VM GitLab CI COMPANY CLOUD InfluxDB tagged by server, chassis and sensor Grafana read against the unit's own telemetry Alert rules · Grafana Alertmanager
industrial · plant, field I/O & controlsoftware & services

Monitors 5 clusters running the company's VPS service

ProblemSubmerged servers run workloads that must not be touched, yet their temperatures, power and health still had to be measured. OS agents go silent when the OS is down and IPMI differs by vendor, so collection had to rely on the BMC alone, through one standard API.

Collection

Out-of-band collection
a Python collector, run by cron on a VM, polls the Redfish REST API on each server's management controller on a schedule, so telemetry keeps arriving whether the operating system is up, busy or down.
Where the readings come from
the management controller reads the platform's own sensors directly: board and inlet temperatures and fan tachometers over I²C and SMBus, core temperatures over PECI, wattage and power supply state over PMBus, and drive and memory health from the devices themselves. Redfish is the REST face of that, so the API is enriched by the controller rather than by anything running in the operating system.
Multi-vendor fleet
the same collector polls Supermicro, ASUS, Gigabyte and Dell management controllers. Redfish gives them one protocol, but each vendor lays out and populates its tree differently, so nothing is hard-coded per model.
Metric autodiscovery
instead of a fixed list of URIs, the collector walks each controller's Redfish tree from the service root and discovers which metrics that vendor actually populates.

What is read

Thermal readings
inlet, outlet, CPU and board temperatures with their manufacturer thresholds, plus fan state, giving the immersion system's cooling performance measured at the load it is actually cooling.
Power readings
consumed watts, power supply state and redundancy, and the electrical draw of each server in the cluster.
Health state
chassis, processor, memory, storage and power subsystem status rolled up per server, so a degraded component is visible before it becomes an outage.

Storage & dashboards

Normalisation
the Redfish JSON resources are flattened into structured time-series points tagged by server, chassis and sensor, so hardware from different vendors lands in one consistent schema.
Storage and dashboards
readings are written to the company's cloud InfluxDB and presented in its cloud Grafana, where per-server temperature and power are read against the immersion system's own telemetry over the same period.

Alerting

Alerting
the rules are Grafana-managed and fire through its built-in Alertmanager, covering temperature thresholds, power supply faults, degraded health state and collection loss.

CI/CD

Build
the collector is built and published as a versioned Docker image from its GitLab repository.
Deploy
the VM's cron job runs that pinned image rather than a script on a host, so the collector polling a cluster is the one that was built, and rolling back means naming an earlier version.
Collection
  • Redfish
  • Python
  • REST APIs
Storage & dashboards
  • InfluxDB
  • Grafana
Delivery
  • Docker
  • GitLab
CAPTURE Custom PCB wearable 9-axis IMU — accelerometer, gyroscope, magnetometer guided patient tests and daily life Android app records the session, returns the result the shipped algorithm is a class inside it PIPELINE Calibration noise cut from the signal device-to-device check Time series motion held as recordings Fourier analysis time domain to frequency the features come from here Classifiers decision trees, trained on the captured recordings Symptom scores tremor · bradykinesia · dyskinesia dysmobility · finger tapping Cloud platform the result is pushed up VALIDATION Clinical check real patient recordings and clinical observation Video check OpenCV and ML over video, compared with the sensors Correlation sensor data × results × patient test outcomes
industrial · plant, field I/O & controlsoftware & services

Used with over 100 patients

ProblemParkinson's symptoms were judged only by eye during a clinic visit, with no objective measure from the patient's daily life.

Sensing

Wearable sensing
custom boards carrying a 9-axis IMU record body movement, hand motion, orientation and balance while the patient performs a guided test or goes about the day.
Calibration
recordings were analysed per device to cut sensor noise and bring the boards into agreement, so two devices reading the same movement produced the same signal before any symptom analysis ran.

Analysis

Frequency domain
each recording is a time series; a Fourier transform moves it into the frequency domain, where a symptom shows as energy in its own frequency band rather than as a shape in time.

Learning

Features and training
the spectral bands become the feature vector, and the labelled patient recordings train the classifiers — decision trees — to decide which symptom a recording shows and how strongly.
Symptoms scored
tremor, bradykinesia, dyskinesia, dysmobility and finger tapping, all read from the same recording.

On the device

On the device
the trained algorithm ships as a Java class inside the Android app, so scoring happens on the phone that holds the recording, with no round trip to a server.

Validation

Against the clinic
algorithm output was checked against real patient recordings alongside the clinician's own observation of the same test.
Correlation
correlation analysis across sensor data, algorithm score and clinical test outcome, to show the score tracked the symptom rather than the patient or the device.
Against video
computer vision and machine learning over images and video of the tests, used as an independent reading of the movement to compare with the IMU.

Software stack

Analysis
MATLAB and Python with NumPy and SciPy.
Vision and ML
OpenCV and scikit-learn.
Visualisation
Python and MATLAB tooling for reviewing signals and results.
Implementation
the algorithm rewritten in Java as a self-contained class for the Android app.

Hardware stack

IMU
accelerometer, gyroscope and magnetometer on a custom board.
Analysis
  • MATLAB
  • NumPy
  • SciPy
  • Fourier Analysis
Vision & learning
  • OpenCV
  • scikit-learn
Implementation
  • C
  • Python
  • Java

ProblemPatients could not choose which doctor reads their vital signs, or take that access back.

How it works

Capture
the patient takes a reading and the board sends heart rate, blood oxygen and skin temperature to the phone over Bluetooth.
Publish
the app builds a health report, uploads it to IPFS and writes the returned content identifier to the contract.
Authorise
the patient grants or revokes a doctor's address on-chain; nothing else can reach the report.
Retrieve
the doctor's app calls the contract and pulls the report from IPFS while authorised.

Software stack

Firmware
C on the Arduino, beat detection reporting instantaneous and average BPM.
Mobile clients
Java on Android, AndroidX and Material Design, one app for patients and one for doctors.
Smart contract
Solidity, AccessControl exposing storeData, giveAccess, revokeAccess and retrieveData.
On-device store
MongoDB Realm, readings tagged by state and searchable by date or value.
Cloud and auth
Firebase for authentication and non-sensitive data.
Chain access
web3j from Android, web3.js for direct calls.

Hardware stack

Board
a custom PCB of my own design and wiring.
Microcontroller
Arduino Uno.
Pulse oximeter
MAX30102 over I²C, heart rate and SpO₂ from red against infrared absorption.
Thermometer
MLX90614, non-contact infrared skin temperature, over I²C.
Radio
HC-05 module, Bluetooth serial to the phone.

Networks & testing

Local chain
Ganache, a personal Ethereum node to develop against.
Contract IDE
Remix, for writing and deploying AccessControl.
Testnet
Rinkeby, chosen for its Proof of Authority consensus and the fast confirmations that follow.
Alternatives
Ropsten, Kovan and Goerli, the other public testnets at the time.
Node access
Infura, one endpoint for both Ethereum and IPFS, with MetaMask as the wallet.
Blockchain
  • Ethereum
  • Solidity
  • Smart Contracts
  • Web3j
  • Web3.js
Storage
  • IPFS
  • MongoDB
  • Firebase
Client
  • Android
  • Java
Device
  • Arduino
  • C
  • Bluetooth

Technologies

Languages
Frontend
Architecture
Backend & APIs
Security & Identity
Caching, Messaging & Data Processing
Databases
Monitoring & Observability
Operations & Platforms
Infrastructure as Code
  • Ansible
Orchestrators
Industrial & IoT

AI in my workflow

How I put LLMs and agents to work, and where I keep the final say.

Infrastructure over MCP
Grafana and OpenZiti MCP servers build dashboards and query the Ziti controllers from an agent session.
Ticketing
the Jira MCP opens and updates tickets from the same session, and a service inventory keeps each Jira task one-to-one with what is actually deployed.
Automated QA
the Playwright MCP runs the UI end to end before a change ships.
Local models
open-weight models served through Ollama. Inference drives the GPUs of the IT equipment under test to a sustained, controlled load, raising its power draw to feed the heat-dissipation analysis in Coolblock R&D, and confidential data stays on local models, never reaching a hosted LLM.
Learning
Perplexity and ChatGPT as research partners, shortening the gap between learning something and building it.
Guardrails
Claude Code proposes, I decide: every diff is reviewed, tested against the real system, and nothing is committed or deployed automatically.