Historian
TimescaleDB (a time-series database) when the extension's there, native partitioning when it isn't. Per-tag deadband, so a sensor twitching in its last bit doesn't write a row every scan until the disk fills.
Gateway talks to your actual equipment over the protocols it already speaks, keeps every value with how much you should trust it, and alarms and automates on top of that. Agents can read the whole plant over MCP. When a sensor goes dark, you see a dark sensor, not a frozen number pretending everything's fine. And nothing moves a machine until someone with the authority says it can. You can believe the screen, and you can sleep.
The image is ghcr.io/hotloop-io/hotloop:4.18.11, the chart is hotloop from
https://hotloop.io/hotloop, and both pull with no login. The source repo is still private.
On 4.17.0 it's a helm upgrade with --reset-then-reuse-values, not
--reuse-values, and a couple of defaults moved, so
the upgrade notes are worth reading first. Still on 4.16.0 or 4.3.1? The notes
cover both.
HotLoop is one codebase shipped as four products, all from the same tag. IoT is the automation base, Edge adds the machine layer, and the Gateway is both of those plus what a whole site needs: fleet management, multi-site roll-ups and scheduled reports. The Edge Relay is the one with no screen that polls and forwards.
All four are out in 4.18.11, each with its own image and chart. The Gateway kept the image and chart names it shipped under, so nobody running it changes a thing. If you need one box running a whole site, this is the box. If the box sits next to one machine, Edge is smaller.
Restart a relay, ask it to rebirth (re-announce itself), or send it a new device file without anybody driving out to the box.
One Gateway asks the others the questions it would answer for itself. A site that doesn't answer shows as unreachable, never as zero alarms.
Historian numbers on a schedule, as CSV or HTML, stored and emailed. Only good readings count, and every row says how many it threw out.
The UI, the API, a rule, a script, an agent: same function, same checks, same order. Policy lives in one place because the day it lives in five, somebody forgets the fifth, and that somebody's write goes to a live press.
press-01/Setpoint, range 0 to 250 degC, sitting at 210. Everything ships locked, so your first
try is going to bounce. Unlock it one switch at a time and watch where it stops.
A simulation in your browser. The checks, their order and what they refuse are the Gateway's: the first from
its API, the rest straight out of internal/runtime/gate.go.
A logged-in user needs values.write, and a viewer does not have it. An agent needs the MCP token, or it gets a 401.
Writes ship off.
Agents get their own switch, safety.allowMcpWrites, checked only for an agent. It narrows the master switch. It can never open a door the master switch closed.
Every tag is read-only until somebody marks it writable, one at a time, on purpose. A browse import of 4,000 tags arms none of them.
Out of range is refused, never clamped. Somebody who typed 500 on a 0 to 250 setpoint made a mistake, and quietly sending 250 means they never find out.
A call through an entity adds the entity's own rules: a number's step, a text's pattern, an entity somebody disabled.
No running device, no write, and the refusal is audited. One that's up but mid-reconnect gets past this check.
In that order. A refusal is recorded as a refusal, and a write that hangs or crashes the process still left evidence that it was attempted.
An agent gets no side door. write_tag and call_service hit the same gate an operator
does, and neither one runs without a stated reason. The audit row records who and what, and the reason lands
in the system log right next to the write it explains. A write nobody can explain six months later is just a
timestamp with a guilty conscience.
Every reading carries good, uncertain or bad. A driver that can't read a point still reports, marked bad, because silence looks exactly like a value that hasn't changed. Alarms hold their state on a bad reading instead of evaluating it, so a dead instrument shows up as a dead instrument and not as a process upset.
Same sensor, same moment, two systems. Low alarm at 40 degC. Pull the wire and watch which one wakes somebody up for nothing.
LOW TEMPERATURE. Paging on-call.
Normal, held. The reading is bad, so the alarm isn't evaluated on it.
The left one just paged somebody at 3am for a process event that never happened. The right one told the truth: nobody knows the temperature right now, and the problem is the instrument, not the tank.
A simulation in your browser. The quality rule and the held alarm are the Gateway's.
27 tools and 4 resources, over streamable HTTP and stdio, up from 16 in 4.16.0. An agent reads the plant in entities, the infeed pump rather than register 40001, and it can only act through the gate. So you can ask it why line 3 is slow and let it read everything, knowing it can only touch what you armed, and every write it makes comes with a reason in the log.
It goes the other way too: a rule can call a tool on another MCP server.
Point one Gateway at the others and the Sites screen asks each of them the question it would answer itself. Each site keeps its own database, so a dead WAN link takes out a roll-up, never a plant.
A site that doesn't answer shows as unreachable, with the reason, and stays out of the totals. Counting a plant you can't reach as zero alarms is how a head office decides everything is fine on the worst night of the year.
Prefer this one when a device offers a choice. It browses, it is properly secured, and it subscribes.
The 1979 workhorse. PLCs, VFDs, power meters, RTUs, and the fallback of every gateway that has nothing better.
Plain MQTT, or Sparkplug B as a full primary host application.
Allen-Bradley ControlLogix, CompactLogix, and Micro820. Tags have names, so the controller can be asked what it holds.
Siemens S7-300, S7-400, S7-1200, and S7-1500, over ISO-on-TCP by rack and slot.
Read-only machine tool telemetry. There is no command channel in MTConnect, and this driver does not pretend otherwise.
The catch-all, for energy meters, environmental sensors, OEM controllers, and the REST side of a gateway that speaks something proprietary underneath.
The plant network as a device: switches, ports, PoE, WAN failover, and clients as read-only tags. Nothing writes to a console.
Switches, UPSs, PDUs, power meters, and any Linux box running snmpd, read-only. HotLoop never sends a SET.
Building controllers, meters, and thermostats on the IP network, read-only. HotLoop never writes a BACnet property.
Their decoding is tested hard, and tested decoding is still not a value that came off a real controller. If you're the first to point either one at real hardware, treat it as commissioning: check a value you already know before you trust a single screen.
SNMP was read from net-snmp's agent at every v3 security level, and BACnet/IP from bacpypes3, an independent BACnet stack, and frames recorded off it. Neither is any vendor's firmware. Vendors differ about their MIBs, their object lists, and how they answer a request that is too big, so treat the first read of a real switch, UPS, or controller as commissioning: compare the probe's values with the device's own display.
No sidecar, no second service to keep alive, no runtime to babysit. One static Go binary running as a non-root user with every capability dropped.
TimescaleDB (a time-series database) when the extension's there, native partitioning when it isn't. Per-tag deadband, so a sensor twitching in its last bit doesn't write a row every scan until the disk fills.
ISA-18.2 (the alarm-management standard), rtn-unack (cleared but not yet acknowledged) included. Acknowledged and cleared are separate, so the trip at 03:00 that fixed itself is still on the list when the morning shift walks in.
Eleven trigger types, entity state and the sun included, and conditions compiled on save. A broken rule gets caught in the editor, not at 3am halfway through a batch.
The Board is the screen that lives on a wall, with gauges that show the alarm limits where they fall. Build other dashboards for a role or a physical screen.
The infeed pump, not register 40001. Every tag gets an entity, and a motor, valve, or PID loop is one entity built from the tags that make it up.
One ISA-95 tree (the standard plant hierarchy), enterprise down to equipment, plus labels. Filter a whole area in one click, and the site, area, and line your devices already carry stay in step with it.
ntfy, Gotify, Discord, webhook, and email. Acknowledge from an ntfy push or the Discord bot and the Gateway records it. Discord has only met a stand-in.
Historian numbers on a schedule, as CSV or HTML, stored and emailed. Only good readings count, every row says how many it threw out, and an empty period says no data, never zero.
Read-only, secured, and Ignition 8.3.9 reads it end to end. The SCADA you already paid for can see every tag, and there is no write handler in there to worry about.
Its own product, image and chart: about 15 MB against the Gateway's 35 on arm64, no database, no UI. More on the Edge Relay.
An optional MQTT broker in the same process, so a single box can be its own Sparkplug hub. At scale, run a real broker.
A snapshot in pure Go over the connection it already has. Restore loads an older backup by column name and flat refuses one from a newer version, so a restore can't half-work.
The Board is the screen that lives on a wall: gauges with the alarm limits drawn where they fall, and a layout stored with the plant, so the panel PC and your laptop always show the same thing. It is one dashboard among the ones you can build, for a role or for a physical screen.
The infeed pump, not register 40001. Every tag gets an entity for free, and a motor, valve, or PID loop is one entity built from the tags that make it up. Rules, screens and agents read like the plant, not like a register map.
ntfy, Gotify, Discord, webhook, and email. Press Acknowledge on an ntfy push or in the Discord bot and the alarm is acknowledged in the Gateway, signed and single use. Discord has only met a stand-in, not the real thing.
Its own product with its own image and chart: about 15 MB against the Gateway's 35 on arm64, no database, no UI. The Gateway's fleet API tracks and commands every relay you register (no screen for it yet). More on the Edge Relay.
An optional MQTT broker in the same process, so a single box can be its own Sparkplug hub. Logins made in HotLoop carry per-topic publish and subscribe rules, and logins from HOTLOOP_BROKER_USERS can do anything. At scale, run a real broker.
Any Kubernetes, k3s included. The chart brings its own TimescaleDB, generates the database password, the MCP token and an admin account, and keeps all three across upgrades, so an upgrade never logs you out of your own plant.
helm repo add hotloop https://hotloop.io/hotloop
helm repo update
helm install hotloop hotloop/hotloop --version 4.18.11 \
--namespace hotloop --create-namespace
The user is admin. The chart generates the password, keeps it in the release Secret, and
the install notes print the command that reads it back:
kubectl -n hotloop get secret hotloop \
-o jsonpath='{.data.admin-password}' | base64 -d
kubectl -n hotloop port-forward svc/hotloop 8080:8080
Open http://localhost:8080, sign in, add a device, discover its tags. Then notice that you
can't write to anything. That's on purpose. Every tag starts read-only and gets armed one at a time, and
safety.allowWrites stays off until you decide this pod gets to move machinery. Nobody's
first day with HotLoop should end with a press doing something they didn't ask for.
The image is ghcr.io/hotloop-io/hotloop:4.18.11, amd64 and arm64, about 35 MB on arm64 and 37 MB on amd64, and it pulls
anonymously. The database is a setting, not an image: TimescaleDB by default, with plain PostgreSQL and
SQLite as database.type choices in the same image.
Each product picks its own. IoT takes SQLite or PostgreSQL. Edge and the Gateway take SQLite, PostgreSQL or TimescaleDB. The Edge Relay has no database at all, only a disk-backed forward queue.
Off a cluster, the Gateway has Podman Quadlet units under deploy/quadlet/gateway/ in the
hotloop repository, which is private for now, so the
Quadlet install guide is where to read how they
work. Those units were run rootless under systemd, including an update that can't start and gets rolled
back. The install docs cover the values worth
setting before a real plant sees it.
Use --reset-then-reuse-values, not plain --reuse-values, so the new defaults come along.
helm repo update hotloop
helm upgrade <release> hotloop/hotloop --version 4.18.11 --reset-then-reuse-values
The 4.17.0 and 4.18.10 charts render the same eight objects with the same selectors and the same volume
claims. Plain --reuse-values renders too this time, but it replays
4.17.0's values and drops every new default, so use the reset form or your values file.
Migrations 0016 to 0024 run on start. Migrations only add, so going back to 4.17.0 works,
but take a pg_dump first, like always. edgePublish.queueMaxBytes is 64 MiB
by default now, not unbounded. Past it the oldest readings are dropped and counted, because an
unbounded queue filled the volume in a long outage. Size it from your own reading rate, about 180 bytes a
reading, or set it to 0 to keep the old behavior. MQTT logins made in Settings carry topic rules
from a profile, and logins from HOTLOOP_BROKER_USERS are untouched. There's a new
fleet.update permission, admin only.
Use the reset form, and pg_dump first, because device passwords get encrypted on the first start.
helm repo update hotloop
helm upgrade <release> hotloop/hotloop --version 4.18.11 --reset-then-reuse-values Not --reuse-values. That replays 4.16.0's values, drops every default this chart
added, and the render dies on the first one it needs. Nothing changes on the cluster when it does, so just
run it again the right way.
Your device passwords get encrypted on the first start, with a key the chart makes in
<release>-secret-key. From then on that key is part of your data: a backup taken
after the upgrade only restores where the key is mounted, and going back to 4.16.0 hands every device the
sealed text as its password. So pg_dump before you upgrade, and keep that Secret somewhere
other than next to your backups, because a key sitting beside the thing it protects protects nothing.
Migrations 0009 to 0015 run on start, all additive.
The name changed, and Kubernetes refuses to change a workload selector in place, so the old workloads go first.
4.16.0 was the first published release after 4.3.1, so from 4.3.1 you're taking every version from 4.4.0
on in one jump, under a new name. The image was ghcr.io/embernet-ai/industrial-iot,
the chart was industrial-iot, and every IIOT_ variable is HOTLOOP_ now.
The chart name is baked into the workload selectors, and Kubernetes flat out refuses to change a selector
in place. Keep the release name and the namespace, and:
pg_dump the database. Belt and braces, and you'll be glad you did.<release>) and StatefulSet (<release>-postgresql). The database volume is its own claim and stays put.helm upgrade <release> hotloop/hotloop -f your-4.3.1-values.yaml, with any IIOT_ in extraEnv renamed and any image.repository override dropped. Not --reuse-values, which skips every default the chart added since.The new database pod mounts the old volume and the Gateway migrates it forward on start. We proved that by rendering both charts side by side, not by running it on a live cluster. That gap is exactly why step one exists.
Three more things will surprise a script. There's a login now. Writes default to off. And the API changed
in a handful of places, like empty lists coming back as [] and never null. The
license is HotLoop's own now, the Business Source License 1.1. The full list and the procedure are in
the upgrade docs.
4.18.11 moved the database to PostgreSQL 17 with an upgrade that runs itself, so read that one before you touch a running plant. 4.18.10 was the four-product split, MQTT and network discovery, historian targets and boxes that update themselves, and 4.17.0 was recipes, scripts, blueprints, helpers, the logbook and UniFi, read-only. Each has its own page with what it buys you and what it costs.
The Gateway is source-available under the HotLoop Business Source License 1.1, which is not an OSI open source license. There are no license keys and nothing unlocks at runtime. Home and lab use is free. Any business use is free through EmberNet, where signing up is free and running HotLoop is free. Support, SLAs and warranties come from our Official Systems Integrator, Fireball Industries, who'll also design, install, and support the rollout if you'd rather not.