Industrial IoT Edge Platform
Operating a Multi-Site Data-Logging Fleet in Production
A production fleet of around 20 edge data-logging devices running across 10 industrial sites for different customers, on hardware from different brands. Machine and sensor data is acquired over Modbus, processed in containers at the edge, and streamed to cloud analytics — with one-click OTA updates and store-and-forward resilience.
Problem
Industrial sites generate valuable machine data, but cloud connectivity is unreliable and every customer site is different: different device brands, different machines, different sensors worth monitoring. Supporting a growing fleet remotely — keeping devices updated, diagnosing failures, and not losing data during outages — is where most of the real engineering effort lives.
Unreliable Connectivity
Sites drop offline; devices must buffer locally and lose nothing
Heterogeneous Fleet
Different customers, sites, and device brands behind one architecture
Remote Operations
Devices live at customer sites — updates and diagnosis happen from afar
Architecture
The system uses a three-tier edge computing architecture. PLCs communicate via industrial protocols (S7, Modbus, OPC UA) to local edge gateways. These gateways aggregate data, filter noise, and perform local analytics. Azure IoT Edge orchestrates containers with store-and-forward resilience for cloud connectivity interruptions.
PLC Layer
- •Siemens S7 protocol adapters
- •Allen-Bradley integration
- •Modbus TCP/RTU support
- •OPC UA for modern devices
Edge Layer
- •Azure IoT Edge runtime
- •Docker container orchestration
- •Local InfluxDB time-series storage
- •Store-and-forward message queue
Cloud Layer
- •Azure IoT Hub for device management
- •Time-series analytics pipeline
- •Grafana dashboards for visualization
- •Alerting and notification system
Protocol Adapters
- •Modular adapter pattern
- •Auto-discovery of new devices
- •Protocol translation to MQTT
- •Health monitoring per adapter
Technical Approach
Designed a three-tier architecture: machine layer for data acquisition over Modbus and field sensors, edge layer for containerized collection and local buffering, and cloud layer for long-term analytics. Fleet updates run through Azure Pipelines: each release produces a versioned container image, and an edge tool reads the release number deployed on every device and compares it against the latest build — so version drift is visible at a glance and updating a device is one click. Vendor SDKs are used where specific hardware or tooling requires them.
import asyncio
import json
from dataclasses import dataclass, asdict
from typing import Optional
from azure.iot.device import IoTHubModuleClient
@dataclass
class SensorReading:
device_id: str
timestamp: float
value: float
quality: str # 'good', 'uncertain', 'bad'
class EdgeGateway:
def __init__(self, connection_string: str):
self.client = IoTHubModuleClient.create_from_connection_string(
connection_string
)
self.local_buffer = []
self.cloud_connected = False
async def process_reading(self, reading: SensorReading):
# Always store locally first
self.local_buffer.append(asdict(reading))
# Try to send to cloud
if self.cloud_connected:
try:
message = json.dumps(asdict(reading))
await self.client.send_message(message)
except Exception:
self.cloud_connected = False
self._persist_to_disk(reading)
else:
# Store-and-forward pattern
self._persist_to_disk(reading)
def _persist_to_disk(self, reading: SensorReading):
# SQLite for local resilience
with self._get_db() as db:
db.execute(
"INSERT INTO pending VALUES (?, ?, ?, ?)",
(reading.device_id, reading.timestamp,
reading.value, reading.quality)
)
async def flush_buffer(self):
# Send buffered messages when connection restored
if not self.cloud_connected:
return
pending = self._get_pending_messages()
for msg in pending:
await self.client.send_message(json.dumps(msg))
self._mark_sent(msg['timestamp'])Key Technical Decisions
Azure IoT Edge Over Custom Solution
Chose Azure IoT Edge for its built-in container orchestration, security features, and managed device provisioning. This reduced operational complexity compared to building a custom edge runtime.
Store-and-Forward Pattern
Implemented SQLite-based local storage for message persistence during network outages. This ensures zero data loss even during extended connectivity interruptions common in manufacturing environments.
Modular Protocol Adapters
Designed each industrial protocol as a separate Docker container. This allows mixing different device types at a single site and enables independent updates without affecting other adapters.
Failure Isolation at Module Boundaries
Learned from a real incident: containers on a device must not trust each other to stay alive. Inter-module communication is wrapped so a neighbor's failure is handled and logged instead of cascading — on a remote device, that is the difference between a log line and a site visit.
Operating the Fleet
Building the platform is half the job — the other half is running ~20 devices at 10 sites you cannot walk up to. Fleet operations center on three things: knowing what release every device runs, updating with confidence, and surviving bad networks.
One-Click Updates, Driven by the Release Pipeline
1 · Build
Azure Pipelines turns every release into a versioned container image
2 · Compare
An edge tool reads each device's deployed release number and compares it against the latest build — drift is visible at a glance
3 · Deploy
Updating a device is one click; store-and-forward keeps data safe while modules restart
Incident: When One Container Took Down Another
A communication bug between two containers running on the same device meant that when one module failed, the exception propagated and crashed its neighbor — a single fault took the whole logger down at a remote site.
The fix was not exotic: rework the error handling at the module boundary so a failed call is caught, logged, and retried instead of crashing the caller. The device now degrades gracefully — one module can fail while the rest keeps logging. The lesson stuck: on hardware you cannot reach, failure isolation is a feature, not a refactor.
Hardware as the Interface
Edge work means treating hardware as an interface, not an abstraction: Modbus to talk to machines and gather data, field sensors chosen for what is actually worth monitoring at each site, and vendor SDKs where a specific device or tool requires them. Every brand in the fleet has its own quirks — the architecture absorbs them so the data pipeline does not have to.
Tech Stack
Results & Outcomes
Fleet of ~20 data-logging devices operated in production across 10 sites and multiple customers
Heterogeneous hardware — devices from different brands integrated behind one containerized collector architecture
One-click OTA updates: Azure Pipelines releases with deployed-vs-latest image comparison per device
Hardened failure isolation after a real incident where one container crash cascaded into another — a module failure now degrades gracefully instead of taking down the device
Machine and sensor data acquired over Modbus, with vendor SDKs where specific hardware required them
Store-and-forward buffering keeps data safe through the network outages that real sites produce
Live Demo
Explore the interactive fleet operations dashboard: device health, alerts, and a Fleet & Updates view showing deployed-vs-latest releases with one-click OTA updates. All data is simulated — the workflows mirror how the real fleet is run.
Open Dashboard Demo