Industrial IoT • PLC • Linux Edge • MQTT • TimescaleDB • Cloud

Industrial IoT Edge-to-Cloud Telemetry Lab

A working industrial IoT prototype built around a physical Velocio ACE PLC, Raspberry Pi Zero 2 W edge gateway, secure MQTT transport, Google Cloud infrastructure, a PostgreSQL / TimescaleDB historian, a FastAPI history service, and a live browser dashboard. The system acquires real PLC inputs, publishes structured telemetry over TLS, survives network outages, replays missed events from a durable SQLite store-and-forward queue, and preserves cloud-side event history across browser sessions and infrastructure reboots.

Physical PLCVelocio ACE11 + six digital switch inputs
Secure MQTTMQTT/TLS :8883 + broker ACLs
Store & ForwardSQLite-backed durable edge queue
Cloud HistorianPostgreSQL + TimescaleDB + HTTPS API
FortressFlash Labs PLC and embedded systems workbench
Physical ACE PLC / Raspberry Pi industrial telemetry prototype environment.
System Architecture

Real controller. Real edge gateway. Real cloud path.

The lab uses the same separation of responsibilities found in larger industrial telemetry systems: the controller owns machine I/O, the edge gateway handles acquisition and transport, the broker handles messaging, the historian persists operational history, and the browser combines durable history with the live stream.

Physical I/OSix toggle inputs
ACE11 PLCModbus / USB CDC
Pi Zero 2 WPython edge agent
Fortress NetworkWi-Fi → switch → firewall → 5G
Google Cloud VMMosquitto + nginx + services
Historian + APITimescaleDB + FastAPI
Browser HMIHTTPS history + WSS live
Control boundary:

The PLC remains the authoritative controller. The Pi edge gateway performs protocol translation, state normalization, buffering, secure transport, event identity, and recovery behavior. Cloud services persist and present telemetry rather than replacing local control logic.

Full Stack

From switch contact to live telemetry and durable history.

Every layer in the current build is active and independently testable.

Hardware + edge

Controller
Velocio ACE11 PLC
Physical signals
Six mapped digital inputs, C1–C6
PLC link
USB CDC serial, Modbus function-code reads
Gateway
Raspberry Pi Zero 2 W
Edge software
Python 3 + MinimalModbus + Eclipse Paho MQTT
Event identity
Persistent edge-generated event_id + sequence metadata
Local durability
SQLite persistent store-and-forward queue
Startup
systemd-managed edge agent with automatic restart

Cloud + application

Cloud host
Google Cloud Compute Engine, Ubuntu Linux
MQTT broker
Eclipse Mosquitto
Historian
PostgreSQL 17 + TimescaleDB
Ingestion
Python MQTT subscriber writing typed events into TimescaleDB
History API
FastAPI on localhost behind nginx HTTPS
Edge transport
MQTT over TLS on TCP 8883
Browser transport
WSS for live telemetry + HTTPS for historian queries
Visualization
Custom FortressFlash dashboard combining live and persisted history
Physical Network

The actual path to the cloud.

The edge gateway traverses a real Wi-Fi / switched / cellular uplink rather than sharing the browser's connection path. This makes it possible to test true edge network failures while the dashboard remains online.

ACE11 PLC ↓ USB / Modbus Raspberry Pi Zero 2 W ↓ Wi-Fi FORTRESS-AP01 ↓ Ethernet FORTRESS-SW01 ↓ Managed Switch FORTRESS-FW01 ↓ Firewall FORTRESS-FW01 ↓ T-Mobile 5G Internet ↓ MQTT/TLS :8883 Google Cloud VM ↓ Mosquitto Live path: nginx / WSS / TLS ↓ Browser dashboard Historian path: Mosquitto → Python ingestor → PostgreSQL / TimescaleDB → FastAPI on localhost → nginx HTTPS /api → Browser history + paged historian view Independent viewer path: PC / phone → Internet → Google Cloud → dashboard

Why the separate viewer path matters

The Pi can lose Wi-Fi while the browser remains connected to Google Cloud. This lets the dashboard visibly observe Last Will status transitions, stale telemetry age, recovery, and buffered-event replay.

Public exposure

Plain MQTT on port 1883 is restricted to localhost on the VM. External edge traffic uses TLS on 8883. Browser MQTT is proxied through nginx as secure WebSockets. PostgreSQL and FastAPI also remain local-only and are reached externally only through the intended HTTPS history endpoint.

Reliability Engineering

Designed to survive a broken network path.

Connectivity loss is treated as a normal operating condition rather than a fatal application error. Local PLC polling is separated from disk and cloud delivery so the controller acquisition loop remains responsive during outages.

1. Acquire

The polling thread reads C1–C6 from the ACE PLC. A transient Modbus read failure gets one short retry before the read is reported as failed.

→
2. Persist

State-change events are handed to a background persistence worker and committed to SQLite before cloud delivery is considered complete.

→
3. Deliver + ACK

One SQLite row is published at a time with MQTT QoS 1. The row is deleted only after Paho receives the broker acknowledgement.

Auto reconnect

Network recovery

Paho retries automatically after Wi-Fi or WAN loss. On reconnect the gateway republishes current retained state and resumes pending delivery.

Durable queue

Store-and-forward

Events remain on local storage while the cloud path is unavailable. Buffered events are replayed oldest-first after reconnection.

Last Will

Offline detection

Mosquitto publishes the retained offline status when the edge connection expires, allowing the live dashboard to distinguish broker connectivity from device state.

Persistent identity

End-to-end event IDs

Every physical transition receives a persistent edge-generated event ID and sequence. That identity survives SQLite buffering, MQTT replay, ingestion, TimescaleDB storage, API retrieval, and browser display so duplicate deliveries can be safely suppressed.

Boot recovery

Automatic service restart

The Pi edge agent, Mosquitto broker, PostgreSQL / TimescaleDB, MQTT ingestor, and FastAPI service are managed for automatic startup. Full VM and edge-side restart paths were tested with telemetry resuming after boot.

Failure test performed:

The Pi Wi-Fi path was intentionally disconnected while the ACE PLC remained active. Switch transitions were generated both before and after MQTT detected the outage. After Wi-Fi restoration the edge agent reconnected automatically, restored current state, and replayed the six missing events from SQLite as BUFFERED history. Replayed events preserve their original timestamps and event IDs before being written to the cloud historian.

MQTT Contract

Current state and historical events are separate concepts.

Retained state provides the latest machine snapshot. Events preserve individual transitions. Status tracks edge availability. Every event also carries a persistent edge-generated event ID so the historian and browser can distinguish event identity from transport delivery attempts.

Topic Purpose QoS / retain
fortressflash/lab/ace11-plc/state Current C1–C6 input snapshot and sequence metadata. QoS 1 / retained
fortressflash/lab/ace11-plc/events Individual input transitions. Replayed rows include BUFFERED and replay timestamps. QoS 1 / not retained
fortressflash/lab/ace11-plc/status Online/offline state used by the dashboard and MQTT Last Will. QoS 1 / retained

Live event payload

{ "event_id": "ace11-plc-000000000182", "device_id": "ace11-plc", "timestamp": "2026-09-16T19:36:43Z", "seq": 182, "input": "C6", "previous": 0, "value": 1 }

Replayed buffered payload

{ "event_id": "ace11-plc-000000000183", "device_id": "ace11-plc", "timestamp": "2026-09-16T19:40:12Z", "seq": 183, "input": "C2", "previous": 0, "value": 1, "buffered": true, "replayed_at": "2026-09-16T19:42:01Z" }
Cloud Historian

Live transport and durable history are separate layers.

MQTT remains the real-time transport. PostgreSQL / TimescaleDB is the durable cloud-side system of record for individual PLC events. A local Python ingestion service subscribes to the event topic, normalizes typed values, and writes each event to the historian using the edge-generated event ID.

Historian write path

Mosquitto ↓ localhost MQTT Python historian ingestor ↓ typed insert PostgreSQL 17 + TimescaleDB ↓ persistent event history FastAPI history service

Browser read path

Dashboard load ↓ HTTPS GET /api/events nginx ↓ localhost :8000 FastAPI ↓ SQL query TimescaleDB ↓ JSON history Browser timeline + paged table
Original event timeHistory is ordered by when the PLC transition occurred, not only by cloud receipt time.
Received timeThe historian separately records when the cloud ingestor accepted the event.
Buffered provenanceReplayed events retain BUFFERED state and replay timestamp after network recovery.
End-to-end dedupe keyThe same event_id exists at the Pi, MQTT payload, ingestor, TimescaleDB row, API response, and browser.
Security Model

Encrypted transport with constrained broker access.

The public-facing MQTT path is encrypted and authenticated. Browser access is intentionally separated from edge publishing permissions.

TLS edge transportPi Zero connects to mqtt.fortressflash.com over MQTT/TLS on 8883 with certificate verification.
Broker authenticationMosquitto requires authenticated clients rather than anonymous access.
Topic ACLsThe ACE edge identity is scoped to its telemetry hierarchy; the browser dashboard uses read-only access.
Local-only plaintextPort 1883 remains bound to localhost for VM-side services rather than public Internet use.
Database isolationPostgreSQL remains local to the VM; the browser never connects directly to the database.
nginx TLS terminationBrowser MQTT uses secure WebSockets and historian access uses HTTPS through the same public TLS endpoint.
Secrets outside sourceService credentials are loaded from locked-down environment files rather than embedded in Python application code.
Certificate lifecycleLet's Encrypt certificates provide public CA validation for the broker hostname and browser endpoint.
Dashboard

A live view of the physical controller.

The browser UI combines two independent data paths: MQTT over WSS/TLS for immediate live state and event updates, plus an HTTPS historian API for durable history. Replayed store-and-forward events remain visibly tagged BUFFERED after refresh because that provenance is stored in TimescaleDB.

What the operator sees

C1–C6 indicatorsCurrent physical input state from the latest retained message.
Edge online stateIndependent of the browser's own cloud connection.
Online since / uptimeDerived from the retained edge status timestamp.
Telemetry ageTracks the age of actual PLC telemetry rather than browser session age.
Historian-backed event streamRecent persisted transitions are loaded from TimescaleDB on every page load.
Live MQTT mergeNew WSS events are appended immediately without waiting for a history re-query.
Event-ID dedupeHistorian and live paths share the same event ID so duplicate rows are suppressed in the browser.
Buffered replay labelsRecovered outage events retain original time and display replay time separately.
Paged historian tableThe dashboard can open a modal table of persisted events with paging, sequence numbers, BUFFERED state, and event IDs.

Current behavior

A physical switch change is detected by the Pi, assigned a persistent event ID, persisted locally, delivered with MQTT QoS 1, acknowledged by the broker, written into TimescaleDB, and reflected in the browser. If the edge link fails, acquisition continues locally and pending events drain after recovery into both the live stream and the historian.

Open Dashboard →

Next Engineering Layers

What comes after the reliable telemetry path.

The controller-to-cloud path, durable historian, HTTPS history API, and hybrid live/history dashboard are operational. Future work focuses on packaging, infrastructure automation, reconciliation, bidirectional HMI control, and additional industrial interfaces.

Containers

Dockerize server-side services. Package broker, ingestion, and later data services for repeatable deployment.

Infrastructure

Terraform / IaC. Capture the Google Cloud VM, firewall policy, networking, and supporting resources as version-controlled infrastructure.

Reconciliation

Edge-to-cloud continuity checks. Use persistent event IDs / sequences to compare edge progress against historian state and explicitly repair missing ranges when needed.

HMI control

Bidirectional commands. Map ACE outputs and send authenticated supervisory commands from the browser while retaining PLC-side control and interlocks.

Expansion

Additional protocols and links. Candidate additions include OpenPLC, OPC-UA, CAN/BMS telemetry, LoRa-constrained links, and simulated industrial processes.

Project summary

This lab now demonstrates a complete physical edge-to-cloud telemetry path: industrial controller acquisition, Linux edge processing, durable store-and-forward, persistent event identity, automatic network recovery, MQTT QoS 1 delivery, TLS, authentication and ACLs, cloud-hosted messaging, PostgreSQL / TimescaleDB history, a FastAPI HTTPS history service, systemd-managed service recovery, secure browser WebSockets, event deduplication, and combined live + historian visualization.