Industrial Edge Integration

Modbus + RS-485 on Pi4
from field bus to SQL and MQTT

This guide adds an industrial device lane to your platform at 10.10.10.250. You will design the physical RS-485 bus, poll Modbus devices with deterministic timing, store measurements in PostgreSQL, publish selected values to MQTT, and integrate this lane with your existing DNS, embedded, and Rust tracks.

Physical LayerRS-485 2-wire
ProtocolModbus RTU
HostPi4 at 10.10.10.250
StoragePostgreSQL
Message BusMQTT
Production PathRust service
Build Philosophy

Treat Modbus as a deterministic data acquisition system. Reliable Modbus projects are won in wiring, timing, and data modeling decisions before they are won in code.

Lesson 1

Understand the Modbus Runtime Model

Objective: internalize how Modbus RTU master-slave polling actually behaves on real RS-485 lines.

Learning Focus: You are learning to reason about deterministic polling loops, bus contention, and stale data risk so your implementation scales beyond one sensor and one script.

The key mental shift is to stop thinking of Modbus as a data API and start thinking of it as shared bus time. Every register request consumes finite line bandwidth, so architecture quality is measured by how predictably you schedule reads under normal and fault conditions. That scheduling discipline is what protects downstream SQL freshness and MQTT consumers from stale data masquerading as live state.

Teaching Lens

Modbus RTU is request-response. There is no push model in the field layer. Your Pi is the bus master that controls pacing and fairness across devices.

The practical engineering implication is that every register read consumes bus time. If you ask for too much too frequently, you create self-inflicted packet loss and timeout storms that look like random electrical noise.

bus-sequence
Poll cycle:
1) Master sends request to slave ID 3 for register block 40001-40008
2) Slave responds with bytes or timeout occurs
3) Master enforces inter-frame delay
4) Master moves to next slave ID
5) Loop repeats at fixed schedule

Checkpoint: you can explain why Modbus timing is a scheduling problem, not just a parsing problem.

Lesson 2

Select Required Hardware for Stable RS-485

Objective: choose hardware that survives real electrical environments and supports maintenance.

Learning Focus: You are learning how hardware choices affect noise immunity, diagnostics, and long-term serviceability of your field bus lane.

This lesson is where long-term reliability is won. The difference between a reliable deployment and a fragile one is usually not protocol code but physical design decisions: isolation, surge handling, cable geometry, and clear labeling so future maintenance does not silently break bus integrity.

Platform parts stack

Build your first production-capable stack with explicit roles for each component instead of treating RS-485 as a single adapter purchase.

recommended-bom
pi4 baseline
Host: Raspberry Pi4 with stable 5V power supply and UPS HAT or mini-UPS
    USB adapter: industrial USB-RS485 (FTDI class) with screw terminals
    Isolation: isolated RS-485 transceiver module if field ground quality is uncertain
    Cable: shielded twisted pair, labeled trunk route, short service loops
    Termination: 120 ohm resistors only at both physical trunk ends
    Bias: fail-safe bias network to prevent floating idle line state
    Protection: TVS on A/B and surge-aware grounding practice
    Enclosure: DIN rail or fixed enclosure with cable strain relief and labels
ComponentMinimum RequirementWhy It Matters
USB-RS485 adapterFT232 or CH340 based, screw terminal, surge-tolerantStable Linux serial device and predictable line behavior
Isolated transceiver (recommended)Galvanic isolation 1kV+Protects Pi from ground potential differences and transients
Termination120 ohm at each physical end of trunkReduces reflections on longer cable runs
Biasing resistorsFail-safe pull-up/pull-down networkKeeps idle bus in known state, avoids phantom framing
CableTwisted pair, shielded industrial cableImproves noise rejection and signal integrity
TVS protectionBidirectional transient suppression on A/BIncreases survivability in harsh power environments
Field Rule

A cheap adapter can work in a lab and fail in production. Prefer adapters with clear chipset identity and documented fail-safe behavior.

Checkpoint: your bill of materials includes termination, bias, and surge strategy, not just an adapter dongle.

Lesson 3

Wire RS-485 Topology Correctly

Objective: build a multi-drop trunk that avoids star reflections and random CRC errors.

Learning Focus: You are learning topology discipline and grounding strategy so communication quality comes from design, not luck.

Most intermittent Modbus faults are wiring architecture faults. Good topology engineering means your protocol layer becomes boring and predictable, which is exactly what you want in production. This lesson teaches you to treat wiring diagrams as part of software correctness because electrical reflections directly become parser errors and timeout spikes.

Wiring model

Use one trunk with short drops. Avoid star wiring except at very short distances with tested signal quality.

wiring-reference
Pi4 USB-RS485 ---- Device A ---- Device B ---- Device C
      |                                         |
  120 ohm termination                      120 ohm termination

A-to-A, B-to-B, shared reference ground where vendor docs require it.
Shield: bond at one end according to site grounding policy.

Checkpoint: continuity and polarity are verified before any software polling starts.

Lesson 4

Tune Linux Serial Parameters

Objective: configure serial link settings that exactly match device manuals.

Learning Focus: You are learning to eliminate protocol mismatch faults by aligning baud, parity, stop bits, and timeout policy up front.

Serial configuration errors are deceptive because they often look like random communication noise. In reality they are deterministic mismatches. The practice you are building here is to lock a known-good profile, verify it with observable commands, and make that profile part of versioned infrastructure so service redeployments remain consistent.

Inspect and lock the serial device

Give your adapter a stable symlink so service configs survive reboot device reorder.

bash
udev + serial
ls -l /dev/serial/by-id/
udevadm info -a -n /dev/ttyUSB0

# Example runtime check
stty -F /dev/ttyUSB0 9600 cs8 -cstopb -parenb
stty -F /dev/ttyUSB0 -a
Timing Rule

Timeout values should be set from observed device response behavior, not copied from random examples. Slow devices need larger response windows.

Checkpoint: one known register read works with your chosen serial profile.

Lesson 5

Model Register Maps as Contracts

Objective: define typed register contracts that separate vendor quirks from your application logic.

Learning Focus: You are learning to design a data model that keeps parsing rules explicit and future migrations safe.

A register map is your API specification for field devices. If it is implicit, your pipeline will drift and silently corrupt semantics when firmware or tooling changes. By formalizing type, scale, and unit now, you make validation and migration testable, and you protect analytics from hidden interpretation errors.

Contract schema

Store mapping metadata in a structured file so your poller reads configuration rather than hardcoded magic numbers.

yaml
/opt/modbus/maps/power_meter.yaml
device: power_meter_a
slave_id: 3
byte_order: big
word_order: big
poll_interval_ms: 2000
registers:
  - name: voltage_l1
    address: 40001
    function: holding
    type: u16
    scale: 0.1
    unit: V
  - name: current_l1
    address: 40003
    function: holding
    type: u16
    scale: 0.01
    unit: A
  - name: active_power_total
    address: 40021
    function: holding
    type: i32
    scale: 1.0
    unit: W

Checkpoint: each field defines address, type, scale, unit, and update interval.

Lesson 6

Build a Python Modbus Poller

Objective: implement a resilient poll loop that handles retries, stale reads, and per-device pacing.

Learning Focus: You are learning control-loop hygiene so your first working script can evolve into an operations-grade collector.

What matters in this lesson is not just reading a register but reading it repeatedly under imperfect conditions without destabilizing the bus. You are building a loop that communicates quality state explicitly, so downstream systems can tell the difference between healthy values and degraded transport conditions.

Poller skeleton

This baseline loop demonstrates error budgeting, timestamping, and clean event shaping.

python
/opt/modbus/modbus_poller.py
import json
import time
from datetime import datetime, timezone
from pymodbus.client import ModbusSerialClient

SERIAL_PORT = "/dev/ttyUSB0"
BAUD = 9600
UNIT_ID = 3

client = ModbusSerialClient(
    method="rtu",
    port=SERIAL_PORT,
    baudrate=BAUD,
    bytesize=8,
    parity="N",
    stopbits=1,
    timeout=0.8,
)

if not client.connect():
    raise RuntimeError("Cannot open serial client")

while True:
    ts = datetime.now(timezone.utc).isoformat()
    rr = client.read_holding_registers(address=0, count=8, slave=UNIT_ID)
    if rr.isError():
      print(json.dumps({"ts": ts, "status": "error", "unit": UNIT_ID}))
      time.sleep(2)
      continue

    voltage_raw = rr.registers[0]
    current_raw = rr.registers[2]
    payload = {
      "ts": ts,
      "device": "power_meter_a",
      "voltage_l1": voltage_raw * 0.1,
      "current_l1": current_raw * 0.01,
      "status": "ok"
    }
    print(json.dumps(payload))
    time.sleep(2)

Checkpoint: your loop produces predictable JSON samples with explicit UTC timestamps and error states.

Lesson 7

Persist Modbus Telemetry in PostgreSQL

Objective: store field telemetry in relational form while preserving raw context for forensic debugging.

Learning Focus: You are learning storage design that supports both analytics queries and incident reconstruction.

This storage layer should answer two different classes of questions: operational questions about current health and forensic questions about what exactly happened during failures. That is why typed columns and raw payload context belong together. One supports fast query ergonomics, the other preserves evidence during incident analysis.

Schema design

Use typed columns for core values and JSONB for raw frames and parser metadata.

sql
modbus tables
CREATE TABLE IF NOT EXISTS modbus_samples (
  id BIGSERIAL PRIMARY KEY,
  ts_utc TIMESTAMPTZ NOT NULL,
  device_name TEXT NOT NULL,
  slave_id INT NOT NULL,
  metric_name TEXT NOT NULL,
  metric_value DOUBLE PRECISION,
  quality TEXT NOT NULL,
  raw_payload JSONB,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX IF NOT EXISTS idx_modbus_samples_ts ON modbus_samples(ts_utc DESC);
CREATE INDEX IF NOT EXISTS idx_modbus_samples_dev_metric ON modbus_samples(device_name, metric_name, ts_utc DESC);

Checkpoint: you can query last-known-good value and last error event for each device.

Lesson 8

Bridge Modbus Metrics to MQTT Topics

Objective: publish selected Modbus values into your MQTT fabric without overloading broker or clients.

Learning Focus: You are learning how to convert deterministic poll data into event streams that preserve quality and freshness semantics.

The engineering goal here is semantic preservation. When you bridge to MQTT, you must carry timestamp, unit, and quality state so subscribers can make safe decisions. If those semantics are lost, a stale value can look valid and trigger bad automation outcomes.

Topic contract

Use one status topic and one metrics namespace. Carry quality fields so subscribers can reject stale values.

topic-contract
mqtt/pi4/modbus/device/power_meter_a/status
mqtt/pi4/modbus/device/power_meter_a/metric/voltage_l1
mqtt/pi4/modbus/device/power_meter_a/metric/current_l1
mqtt/pi4/modbus/device/power_meter_a/metric/active_power_total

Payload baseline:
{
  "ts": "2026-05-12T20:08:00Z",
  "value": 231.6,
  "unit": "V",
  "quality": "ok",
  "source": "modbus_rtu"
}
Weave Point

This ties directly into your existing MQTT lessons and SQL ingestors. Modbus becomes one more producer lane, not a separate universe.

Checkpoint: MQTT consumers can distinguish good values from stale or errored samples.

Lesson 9

Implement a Rust Modbus Service Path

Objective: migrate the poller core to Rust for stronger type safety and long-running service stability.

Learning Focus: You are learning migration strategy: keep contracts fixed while replacing runtime internals with safer implementation language.

This migration is a reliability exercise, not a schema rewrite. The contract remains stable while the runtime gains stronger type guarantees, cleaner error propagation, and better memory safety characteristics for long-lived services. Keeping that boundary explicit prevents accidental breaking changes in SQL and MQTT consumers.

Cargo shape

Start with a service-oriented crate layout so retries, logging, and transport can evolve independently.

rust
Cargo.toml highlights
[dependencies]
anyhow = "1"
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }
tracing = "0.1"
tracing-subscriber = "0.3"
# choose a maintained modbus crate compatible with your serial stack

The Rust service should preserve the exact device map contract from Lesson 5. This makes Python-to-Rust migration low risk because downstream SQL and MQTT code remains unchanged.

Checkpoint: your Rust service emits payloads that are contract-compatible with the Python version.

Lesson 10

Execute Modbus Writes with Safety Gates

Objective: control writable registers without creating dangerous or unauthorized state changes.

Learning Focus: You are learning command governance, including bounds checks, role boundaries, and two-step confirmation for high-risk writes.

Write paths are where automation systems become safety systems. This lesson establishes that write operations are controlled transactions with preconditions, authorization, and post-write verification. That discipline protects both equipment and operators when something behaves unexpectedly.

Control policy baseline

policy
Rule 1: all write requests validated against min/max engineering limits
Rule 2: writable registers whitelisted by device profile
Rule 3: operator identity recorded with correlation ID
Rule 4: read-back verification required after each write
Rule 5: timeout or mismatch marks command as failed and raises alert
Safety Rule

Never expose direct write endpoints to untrusted networks. Route control actions through authenticated APIs with audit trails.

Checkpoint: each write action leaves an auditable trail with command intent, result, and read-back state.

Lesson 11

Scale to Multi-Drop and Multi-Segment Networks

Objective: grow from single-device tests to many devices while preserving timing guarantees.

Learning Focus: You are learning poll-budget engineering so total cycle time remains predictable as register counts increase.

Scaling is about budget management. As you add devices, registers, and derived metrics, your cycle time grows nonlinearly if you do not redesign schedules. The method here is to treat latency and freshness as measured budgets, then partition workloads across pollers or segments before service quality degrades.

Capacity planning model

Estimate cycle duration before you add devices, then tune block sizes and intervals to stay within your freshness targets.

formula
Total cycle time ~= sum(device_request_time + response_time + inter_frame_delay)

If freshness target is 2 seconds and estimated cycle is 7 seconds,
split device list across two poller services or reduce per-cycle register volume.

Checkpoint: measured cycle duration aligns with your planned telemetry freshness objective.

Lesson 12

Runbook: Diagnostics and Failure Recovery

Objective: diagnose common Modbus failures quickly with repeatable triage steps.

Learning Focus: You are learning operational literacy so packet errors, CRC faults, and silence conditions become measurable events, not mysteries.

The purpose of this runbook is to reduce mean time to recovery. Instead of guessing, you execute a layered diagnostic sequence from physical layer to data interpretation. That order matters because higher-layer symptoms often originate from wiring or serial profile mistakes.

Triage matrix

SymptomLikely CauseFirst Action
Frequent timeoutswrong baud/parity or broken A/B polarityverify serial profile and line polarity physically
CRC errors burstnoise, no termination, long stubscheck termination and stub length
Only some slaves respondduplicate slave IDs or power issueisolate segments and validate IDs one by one
Values jump unrealisticallywrong scaling or word orderrecheck register map and endian assumptions

Checkpoint: on-call procedures include physical, serial, protocol, and data-layer checks in that order.

Lesson 13

Secure the OT-to-IT Boundary

Objective: segment Modbus field devices from general LAN traffic while preserving telemetry flows.

Learning Focus: You are learning boundary design that contains risk if an edge host is compromised.

Security here is primarily an architecture problem. The safest pattern is to keep raw field access local, expose only curated data upstream, and require authenticated policy gates for control writes. This reduces blast radius if a workstation or application node is compromised.

Zone model on your platform

Keep USB-RS485 physically attached to the Pi collector and avoid routing raw Modbus over broad network segments unless required.

zone-layout
OT field bus (RS-485) -> Pi4 collector
Pi4 collector -> local PostgreSQL
Pi4 collector -> local MQTT broker
Clients consume via NGINX API or MQTT ACL topics
No direct write path from user VLAN to field bus without policy gateway

Checkpoint: write capability is constrained to authenticated service paths, not arbitrary desktop clients.

Lesson 14

Weave Modbus into the Whole Platform

Objective: integrate Modbus lane with DNS naming, SQL analytics, MQTT eventing, and Rust service deployment as one coherent system.

Learning Focus: You are learning platform orchestration so each subsystem has a clear role and failure containment boundary.

This final integration lesson is about system coherence. A mature platform is not a pile of tools but a set of bounded services that exchange clear contracts. Modbus contributes field truth, MQTT distributes state changes, SQL preserves history, and DNS plus service management keep everything discoverable and operable.

System Weave

Use your home server lessons to keep DNS and host identity stable, your embedded lessons for local control safety, your MQTT lessons for fan-out telemetry, and your Rust lessons for production-grade runtime paths.

At this point, the architecture is unified: RS-485 gathers plant truth, SQL stores history, MQTT distributes near-real-time state, and service boundaries enforce safety and operability.

integration-checklist
1) Device map versioned in repo
2) Poller service managed by systemd
3) SQL retention and indexes validated
4) MQTT topics ACL-protected
5) Alert rules for stale or failed quality
6) Write commands audited and read-back verified
7) Recovery drill performed for adapter failure
Outcome

You now have an industrial edge lane that is teachable, testable, and production-oriented rather than script-fragile.