Skip to main content

29 posts tagged with "Modbus"

Modbus TCP and RTU protocol for industrial automation

View All Tags

Modbus RTU vs Modbus TCP: A Practical Comparison for Plant Engineers [2026]

· 12 min read

Modbus RTU vs TCP comparison

If you've spent time on the factory floor, you've touched Modbus. It's the lingua franca of industrial automation — older than most engineers who use it, yet still embedded in nearly every PLC, VFD, sensor, and temperature controller shipping today.

But "Modbus" isn't one protocol. It's two very different beasts that happen to share a register model. Understanding when to use Modbus RTU over RS-485 serial versus Modbus TCP over Ethernet isn't academic — it directly impacts your polling throughput, your wiring costs, your alarm response times, and whether your edge gateway can actually keep up with your machines.

This guide breaks down both protocols at the wire level, compares real-world performance, and gives you a decision framework for your next deployment.

The Frame: What Actually Goes On The Wire

Modbus RTU Frame Structure

Modbus RTU (Remote Terminal Unit) sends binary data over a serial connection — typically RS-485, sometimes RS-232 for point-to-point. The frame is compact:

[Slave Address: 1 byte] [Function Code: 1 byte] [Data: N bytes] [CRC-16: 2 bytes]

A typical "read holding registers" request looks like this on the wire:

01 03 00 00 00 0A C5 CD
│ │ │ │ └── CRC-16 (little-endian)
│ │ │ └──────── Quantity: 10 registers
│ │ └────────────── Starting address: 0x0000
│ └───────────────── Function code: 0x03 (Read Holding Registers)
└──────────────────── Slave address: 1

That's 8 bytes for the request. The response carries 20 bytes of register data plus 5 bytes of overhead — 25 bytes total. Clean. Efficient. Zero wasted bandwidth.

The silent interval problem: RTU uses timing to delimit frames. A gap of 3.5 character times (approximately 3.6ms at 9600 baud) signals the end of one frame. This means:

  • You cannot have pauses inside a frame
  • Multitasking operating systems (Linux, Windows) can introduce jitter that corrupts framing
  • At 9600 baud, one character takes ~1.04ms, so the inter-frame gap is ~3.6ms
  • At 19200 baud, the gap shrinks to ~1.8ms — tighter timing requirements

Modbus TCP Frame Structure

Modbus TCP wraps the same function codes in a TCP/IP packet with an MBAP (Modbus Application Protocol) header:

[Transaction ID: 2 bytes] [Protocol ID: 2 bytes] [Length: 2 bytes] [Unit ID: 1 byte] [Function Code: 1 byte] [Data: N bytes]

The same read request becomes:

00 01 00 00 00 06 01 03 00 00 00 0A
│ │ │ │ │ │ └── Quantity: 10 registers
│ │ │ │ │ └──────── Starting address: 0x0000
│ │ │ │ └─────────── Function code: 0x03
│ │ │ └────────────── Unit ID: 1
│ │ └──────────────────── Remaining bytes: 6
│ └────────────────────────── Protocol ID: 0x0000 (Modbus)
└──────────────────────────────── Transaction ID: 0x0001

Key difference: No CRC. TCP handles error detection at the transport layer. The Transaction ID is huge — it lets you pipeline multiple requests without waiting for responses, something RTU physically cannot do.

Serial Configuration: Getting the Basics Right

When you configure a Modbus RTU connection, you're setting up a serial port. The classic configuration that works with most PLCs:

ParameterTypical ValueNotes
Baud Rate9600Some devices support 19200, 38400, even 115200
Data Bits8Almost universally 8
ParityNoneSome devices default to Even — check documentation
Stop Bits1Use 2 when parity is None (per Modbus spec, though 1 works for most devices)
Byte Timeout4msTime between individual bytes within a frame
Response Timeout100msMaximum wait for slave response

The byte timeout and response timeout are where most deployment issues hide. Set the byte timeout too low on a noisy RS-485 bus and you'll get fragmented frames. Set the response timeout too high and your polling cycle slows to a crawl when a device goes offline.

Real-world rule: On a clean RS-485 bus with less than 100 meters of cable, 4ms byte timeout and 100ms response timeout works reliably. Add 20ms to the response timeout for every 100 meters of additional cable, and double both values if you're running near VFDs or welding equipment.

Modbus TCP: Port 502 and What Lives Behind It

Modbus TCP devices listen on port 502 by default. When you configure a gateway to talk to a PLC over TCP, you're specifying:

  • IP address of the PLC or protocol converter
  • TCP port (502 is standard)
  • Unit ID (equivalent to the slave address — matters when a single IP serves multiple logical devices)

The connection lifecycle matters more than most engineers realize:

  1. TCP handshake: ~1ms on a local network, but can spike to 50ms+ through managed switches with port security
  2. Keep-alive: Modbus TCP doesn't define keep-alive. Some PLCs will drop idle connections after 30-60 seconds
  3. Connection pooling: A well-designed gateway maintains persistent connections rather than reconnecting per poll cycle

The Unit ID trap: When you have a Modbus TCP-to-RTU bridge (common when retrofitting serial devices onto Ethernet), the Unit ID maps to the RTU slave address on the serial side. If you set Unit ID to 0 or 255, many bridges interpret this as "send to all devices" — which can cause chaos on a shared RS-485 bus.

Performance: Real Numbers, Not Spec Sheet Fantasy

Here's what actually matters — how fast can you poll data?

Modbus RTU at 9600 Baud

Reading 10 holding registers from a single device:

  • Request frame: 8 bytes → 8.3ms
  • Slave processing time: 2-10ms (PLC-dependent)
  • Response frame: 25 bytes → 26ms
  • Inter-frame gap: 3.6ms × 2 = 7.2ms
  • Total per device: ~45-55ms

With 10 devices on an RS-485 bus, one complete poll cycle takes 450-550ms. That's roughly 2 polls per second — acceptable for temperature monitoring, too slow for motion control.

Bumping to 19200 baud cuts transmission time in half, getting you to ~30ms per device or about 3.3 polls per second across 10 devices.

Modbus TCP on 100Mbps Ethernet

The same 10-register read over TCP:

  • Request frame: 12 bytes (+ TCP overhead) → under 1ms
  • Slave processing time: 2-10ms
  • Response frame: 29 bytes → under 1ms
  • TCP ACK overhead: ~0.5ms
  • Total per device: ~5-15ms

But here's where TCP shines: pipelining. With the Transaction ID, you can fire 10 requests without waiting for responses. A well-optimized gateway can poll 10 devices in 15-25ms total — nearly 40-60 polls per second.

The Contiguous Register Advantage

Whether RTU or TCP, reading contiguous registers in a single request is dramatically faster than individual reads. Reading 50 contiguous registers costs roughly the same as reading 1 register — the overhead is in the framing, not the data payload.

If your PLC stores related data in registers 40001-40050, read them all in one Function Code 03 request. If the data is scattered across registers 40001, 40200, 40500, and 41000, you need four separate requests — four times the overhead.

Smart IIoT platforms like machineCDN optimize this automatically, grouping contiguous register reads into batch requests that minimize round-trips to the PLC.

Function Codes: The Ones That Actually Matter

The Modbus spec defines 20+ function codes, but in practice you'll use five:

CodeNameUse Case
0x01Read CoilsDigital outputs (on/off states)
0x02Read Discrete InputsDigital inputs (sensor contacts)
0x03Read Holding RegistersThe workhorse — analog values, setpoints, configuration
0x04Read Input RegistersRead-only process values (some PLCs put sensor data here)
0x06Write Single RegisterSending commands or setpoints to the PLC

The register type confusion: Modbus defines four data spaces — coils (1-bit R/W), discrete inputs (1-bit RO), holding registers (16-bit R/W), and input registers (16-bit RO). Different PLC manufacturers map data differently. A temperature reading might be in holding register 40001 on one brand and input register 30001 on another. Always check the PLC's register map.

Error Handling: Where Deployments Break

RTU Error Detection

RTU uses CRC-16 (polynomial 0xA001). If a single bit flips during transmission — common on electrically noisy factory floors — the CRC fails and the master discards the frame. The master then retries, burning another 45ms+.

Common RTU error scenarios:

  • No response (timeout): Device is offline, wrong slave address, or cable broken. The master waits for the full response timeout before moving on.
  • CRC mismatch: Electrical noise. Check cable shielding, termination resistors (120Ω at each end of the RS-485 bus), and distance from high-power equipment.
  • Exception response: The slave responds with function code + 0x80, indicating an error (illegal address, illegal data value, slave device failure). This is actually good — it means the device is alive and communicating.

TCP Error Handling

TCP's built-in retry and checksum mechanisms handle bit errors transparently. Your Modbus TCP errors are typically:

  • Connection refused: Device is down or port 502 is blocked
  • Connection timeout: Network issue, VLAN misconfiguration, or firewall
  • No response on established connection: PLC is overloaded or has crashed — the TCP connection stays open but the application layer is dead

The zombie connection problem: A PLC might crash while the TCP connection remains technically open (no FIN packet sent). Your gateway keeps sending requests into the void, timing out on each one. Implement application-level heartbeats — if you don't get a valid Modbus response within 3 consecutive poll cycles, tear down the connection and reconnect.

Wiring and Physical Layer Considerations

RS-485 for RTU

  • Max cable length: 1,200 meters (4,000 feet) at 9600 baud
  • Max devices per bus: 32 (standard drivers) or 256 (with high-impedance receivers)
  • Topology: Multi-drop bus (daisy-chain, NOT star)
  • Termination: 120Ω resistors at both ends of the bus
  • Cable: Shielded twisted pair (STP), 24 AWG minimum

The star topology trap: RS-485 is designed for daisy-chain (bus) topology. Running cables from a central hub to each device in a star pattern creates reflections that corrupt signals. If your plant layout forces star wiring, use an RS-485 hub/repeater at the center.

Ethernet for TCP

  • Max cable length: 100 meters per segment (Cat5e/Cat6)
  • Devices: Limited only by switch capacity and IP addressing
  • Topology: Star (standard Ethernet)
  • Switches: Use industrial-rated managed switches. Consumer switches will die in a factory environment within months.

When to Use Each Protocol

Choose Modbus RTU when:

  • Connecting to legacy devices that only have serial ports
  • Cable runs are long (200m+) and Ethernet infrastructure doesn't exist
  • You need simplicity — two wires, no switches, no IP configuration
  • Budget is tight and the device count is low (under 10 per bus)
  • Temperature controllers, VFDs, and simple sensors with RS-485 ports

Choose Modbus TCP when:

  • You need high poll rates (>5 Hz per device)
  • Connecting 10+ devices at one location
  • Ethernet infrastructure already exists
  • You want to pipeline requests for maximum throughput
  • Remote access or cloud connectivity is needed (TCP routes through firewalls more easily)
  • The PLC supports it (most modern PLCs do)

The hybrid reality: Most IIoT deployments end up with both. A Modbus TCP-capable PLC talks to the edge gateway over Ethernet while older serial devices connect through an RS-485 port on the same gateway. Platforms like machineCDN handle this natively — the edge gateway manages both protocol stacks and normalizes the data into a unified model before it leaves the plant floor.

Configuration Pitfalls That Will Waste Your Time

  1. Baud rate mismatch: Every device on an RTU bus must use the same baud rate. One device at 19200 on a 9600 bus will generate garbage that confuses everything.

  2. Duplicate slave addresses: Two devices with the same address on the same RS-485 bus will both try to respond simultaneously, corrupting each other's frames.

  3. Polling too fast: If your poll interval is shorter than the total round-trip time for all devices, requests will pile up and timeouts cascade. Calculate your minimum cycle time before setting the poll interval.

  4. Byte ordering (endianness): A 32-bit float spanning two 16-bit Modbus registers can be arranged as Big-Endian (AB CD), Little-Endian (CD AB), Big-Endian byte-swapped (BA DC), or Little-Endian byte-swapped (DC BA). The spec doesn't mandate an order. Each manufacturer chooses their own. Test with known values before assuming.

  5. Register addressing: Some documentation uses 0-based addressing (register 0 = first register), others use 1-based (register 1 = first register), and some use Modbus convention addressing (40001 = first holding register). Off-by-one errors here will give you data from the wrong register — and the values might look plausible enough to be dangerous.

Scaling Factors and Unit Conversion

PLCs store numbers as integers — typically 16-bit signed or unsigned values. A temperature of 72.5°F might be stored as:

  • 7250 with an implicit scale factor of ÷100
  • 725 with a scale factor of ÷10
  • 73 rounded to the nearest integer

The register map documentation should specify the scale factor, but many don't. When you see register values like 7250, 1472, or 2840, you need to figure out the engineering units.

Temperature conversions are common in multi-vendor environments:

  • Fahrenheit to Celsius: (F - 32) × 5/9
  • Weight (lbs to kg): lbs ÷ 2.205
  • Pressure (PSI to kPa): PSI ÷ 0.145
  • Length (feet to meters): ft ÷ 3.281

A robust IIoT platform handles these conversions at the edge, storing normalized SI values in the cloud regardless of what the PLC natively reports.

Conclusion: The Protocol Doesn't Matter as Much as the Architecture

Modbus RTU and Modbus TCP are both viable for modern IIoT deployments. The protocol choice is a physical-layer decision — what ports does the equipment have, how far away is it, and how fast do you need data?

The real challenge is what happens after the data leaves the register: normalizing values from heterogeneous equipment, handling connectivity loss gracefully, batching telemetry for efficient cloud delivery, and turning raw register data into actionable insights.

Whether your machines speak RTU over serial or TCP over Ethernet, the goal is the same — get reliable, normalized data off the plant floor and into the hands of engineers who can act on it.


machineCDN connects to both Modbus RTU and Modbus TCP devices through its edge gateway, handling protocol translation, data normalization, and store-and-forward buffering automatically. Learn how it works →

Multi-Protocol PLC Auto-Detection: Building Intelligent Edge Gateway Discovery [2026]

· 14 min read

Multi-Protocol Auto-Detection Edge Gateway

You plug a new edge gateway into a plant floor network. It needs to figure out what PLCs are on the wire, what protocol each one speaks, and how to read their data — all without a configuration file.

This is the auto-detection problem, and getting it right is the difference between a 10-minute commissioning process and a 2-day integration project. In this guide, we'll walk through exactly how industrial edge gateways probe, detect, and configure communication with PLCs across EtherNet/IP and Modbus TCP, drawing from real-world patterns used in production IIoT deployments.

Multi-Protocol PLC Discovery: How to Automatically Identify Devices on Your Factory Network [2026]

· 12 min read
MachineCDN Team
Industrial IoT Experts

Commissioning a new IIoT gateway on a factory floor usually starts the same way: someone hands you an IP address, a spreadsheet of tag names, and the vague instruction "connect to the PLC." No documentation about which protocol the PLC speaks. No model number. Sometimes the IP address is wrong.

Manually probing devices is tedious and error-prone. Does this PLC speak EtherNet/IP or Modbus TCP? Is it a Micro800 or a CompactLogix? What registers hold the serial number? You can spend an entire day answering these questions for a single production cell.

Automated device discovery solves this by systematically probing known protocol endpoints, identifying the device type, extracting identification data (serial numbers, firmware versions), and determining the correct communication parameters — all without human intervention.

This guide covers the engineering details: protocol probe sequences, identification register maps, fallback logic, and the real-world edge cases that trip up naive implementations.

PLC Alarm Decoding in IIoT: Byte Masking, Bit Fields, and Building Reliable Alarm Pipelines [2026]

· 13 min read

PLC Alarm Decoding

Every machine on your plant floor generates alarms. Motor overtemp. Hopper empty. Pressure out of range. Conveyor jammed. These alarms exist as bits in PLC registers — compact, efficient, and completely opaque to anything outside the PLC unless you know how to decode them.

The challenge isn't reading the register. Any Modbus client can pull a 16-bit value from a holding register. The challenge is turning that 16-bit integer into meaningful alarm states — knowing that bit 3 means "high temperature warning" while bit 7 means "emergency stop active," and that some alarms span multiple registers using offset-and-byte-count encoding that doesn't map cleanly to simple bit flags.

This guide covers the real-world techniques for PLC alarm decoding in IIoT systems — the bit masking, the offset arithmetic, the edge detection, and the pipeline architecture that ensures no alarm gets lost between the PLC and your monitoring dashboard.

How PLCs Store Alarms

PLCs don't have alarm objects the way SCADA software does. They have registers — 16-bit integers that hold process data, configuration values, and yes, alarm states. The PLC programmer decides how alarms are encoded, and there are three common patterns.

Pattern 1: Single-Bit Alarms (One Bit Per Alarm)

The simplest and most common pattern. Each bit in a register represents one alarm:

Register 40100 (16-bit value: 0x0089 = 0000 0000 1000 1001)

Bit 0 (value 1): Motor Overload → ACTIVE ✓
Bit 1 (value 0): High Temperature → Clear
Bit 2 (value 0): Low Pressure → Clear
Bit 3 (value 1): Door Interlock Open → ACTIVE ✓
Bit 4 (value 0): Emergency Stop → Clear
Bit 5 (value 0): Communication Fault → Clear
Bit 6 (value 0): Vibration High → Clear
Bit 7 (value 1): Maintenance Due → ACTIVE ✓
Bits 8-15: (all 0) → Clear

To check if a specific alarm is active, you use bitwise AND with a mask:

is_active = (register_value >> bit_offset) & 1

For bit 3 (Door Interlock):

(0x0089 >> 3) & 1 = (0x0011) & 1 = 1 → ACTIVE

For bit 4 (Emergency Stop):

(0x0089 >> 4) & 1 = (0x0008) & 1 = 0 → Clear

This is clean and efficient. One register holds 16 alarms. Two registers hold 32. Most small PLCs can encode all their alarms in 2-4 registers.

Pattern 2: Multi-Bit Alarm Codes (Encoded Values)

Some PLCs use multiple bits to encode alarm severity or type. Instead of one bit per alarm, a group of bits represents an alarm code:

Register 40200 (value: 0x0034)

Bits 0-3: Feeder Status Code
0x0 = Normal
0x1 = Low material warning
0x2 = Empty hopper
0x3 = Jamming detected
0x4 = Motor fault

Bits 4-7: Dryer Status Code
0x0 = Normal
0x1 = Temperature deviation
0x2 = Dew point high
0x3 = Heater fault

To extract the feeder status:

feeder_code = register_value & 0x0F           // mask lower 4 bits
dryer_code = (register_value >> 4) & 0x0F // shift right 4, mask lower 4

For value 0x0034:

feeder_code = 0x0034 & 0x0F = 0x04 → Motor fault
dryer_code = (0x0034 >> 4) & 0x0F = 0x03 → Heater fault

This pattern is more compact but harder to decode — you need to know both the bit offset AND the mask width (how many bits represent this alarm).

Pattern 3: Offset-Array Alarms

For machines with many alarm types — blenders with multiple hoppers, granulators with different zones, chillers with multiple pump circuits — the PLC programmer often uses an array structure where a single tag (register) holds multiple alarm values at different offsets:

Tag ID 5, Register 40300: Alarm Word
Read as an array of values: [value0, value1, value2, value3, ...]

Offset 0: Master alarm (1 = any alarm active)
Offset 1: Hopper 1 high temp
Offset 2: Hopper 1 low level
Offset 3: Hopper 2 high temp
Offset 4: Hopper 2 low level
...

In this pattern, the PLC transmits the register value as a JSON-encoded array (common with modern IIoT gateways). To check a specific alarm:

values = [0, 1, 0, 0, 1, 0, 0, 0]
is_hopper1_high_temp = values[1] // → 1 (ACTIVE)
is_hopper2_low_level = values[4] // → 1 (ACTIVE)

When offset is 0 and the byte count is also 0, you're looking at a simple scalar — the entire first value is the alarm state. When offset is non-zero, you index into the array. When the byte count is non-zero, you're doing bit masking on the scalar value:

if (bytes == 0 && offset == 0):
active = values[0] // Simple: first value is the state
elif (bytes == 0 && offset != 0):
active = values[offset] != 0 // Array: index by offset
elif (bytes != 0):
active = (values[0] >> offset) & bytes // Bit masking: shift and mask

This three-way decode logic is the core of real-world alarm processing. Miss any branch and you'll have phantom alarms or blind spots.

Building the Alarm Decode Pipeline

A reliable alarm pipeline has four stages: poll, decode, deduplicate, and notify.

Stage 1: Polling Alarm Registers

Alarm registers must be polled at a higher frequency than general telemetry. Process temperatures can be sampled every 5-10 seconds, but alarms need sub-second detection for safety-critical states.

The practical approach:

  • Alarm registers: Poll every 1-2 seconds
  • Process data registers: Poll every 5-10 seconds
  • Configuration registers: Poll once at startup or on-demand

Group alarm-related tag IDs together so they're read in a single Modbus transaction. If your PLC stores alarm data across tags 5, 6, and 7, read all three in one poll cycle rather than three separate requests.

Stage 2: Decode Each Tag

For each alarm tag received, look up the alarm type definitions — a configuration that maps tag_id + offset + byte_count to an alarm name and decode method.

Example alarm type configuration:

Alarm NameMachine TypeTag IDOffsetBytesUnit
Motor OverloadGranulator500-
High TemperatureGranulator510°F
Vibration WarningGranulator504-
Jam DetectionGranulator620-

The decode logic for each row:

Motor Overload (tag 5, offset 0, bytes 0): active = values[0] — direct scalar

High Temperature (tag 5, offset 1, bytes 0): active = values[1] != 0 — array index

Vibration Warning (tag 5, offset 0, bytes 4): active = (values[0] >> 0) & 4 — bit mask at position 0 with mask width 4. This checks if the third bit (value 4 in decimal) is set in the raw alarm word.

Jam Detection (tag 6, offset 2, bytes 0): active = values[2] != 0 — array index on a different tag

Stage 3: Edge Detection and Deduplication

Raw alarm states are level-based — "the alarm IS active right now." But alarm notifications need to be edge-triggered — "the alarm JUST became active."

Without edge detection, every poll cycle generates a notification for every active alarm. A motor overload alarm that stays active for 30 minutes would generate 1,800 notifications at 1-second polling. Your operators will mute alerts within hours.

The edge detection approach:

previous_state = get_cached_state(device_id, alarm_type_id)
current_state = decode_alarm(tag_values, offset, bytes)

if current_state AND NOT previous_state:
trigger_alarm_activation(alarm)
elif NOT current_state AND previous_state:
trigger_alarm_clear(alarm)

cache_state(device_id, alarm_type_id, current_state)

Critical: The cached state must survive gateway restarts. Store it in persistent storage (file or embedded database), not just in memory. Otherwise, every reboot triggers a fresh wave of alarm notifications for all currently-active alarms.

Stage 4: Notification and Routing

Not all alarms are equal. A "maintenance due" flag shouldn't page the on-call engineer at 2 AM. A "motor overload on running machine" absolutely should.

Alarm routing rules:

SeverityResponseNotification
Critical (E-stop, fire, safety)Immediate shutdownSMS + phone call + dashboard
High (equipment damage risk)Operator attention neededPush notification + dashboard
Medium (process deviation)Investigate within shiftDashboard + email digest
Low (maintenance, informational)Schedule during downtimeDashboard only

The machine's running state matters for alarm priority. An active alarm on a stopped machine is informational. The same alarm on a running machine is critical. This context-aware prioritization requires correlating alarm data with the machine's operational state — the running tag, idle state, and whether the machine is in a planned downtime window.

Machine-Specific Alarm Patterns

Different machine types encode alarms differently. Here are patterns common across industrial equipment:

Blenders and Feeders

Blenders with multiple hoppers generate per-hopper alarms. A 6-hopper batch blender might have:

  • Tags 1-6: Per-hopper weight/level values
  • Tag 7: Alarm word with per-hopper fault bits
  • Tag 8: Master alarm rollup

The number of active hoppers varies by recipe. A machine configured for 4 ingredients only uses hoppers 1-4. Alarms on hoppers 5-6 should be suppressed — they're not connected, and their registers contain stale data.

Discovery pattern: Read the "number of hoppers" or "ingredients configured" register first. Only decode alarms for hoppers 1 through N.

Temperature Control Units (TCUs)

TCUs have a unique alarm pattern: the alert tag is a single scalar where a non-zero value indicates any active alert. This is the simplest pattern — no bit masking, no offset arrays:

alert_tag_value = read_tag(tag_id=23)
if alert_tag_value[0] != 0:
alarm_active = True

This works because TCUs typically have their own built-in alarm logic. The IIoT gateway doesn't need to decode individual fault codes — the TCU has already determined that something is wrong. The gateway just needs to surface that to the operator.

Granulators and Heavy Equipment

Granulators and similar heavy-rotating-equipment tend to use the full three-pattern decode. They have:

  • Simple scalar alarms (is the machine faulted? yes/no)
  • Array-offset alarms (which specific fault zone is affected?)
  • Bit-masked alarm words (which combination of faults is present?)

All three might exist simultaneously on the same machine, across different tags. Your decode logic must handle them all.

Common Pitfalls in Alarm Pipeline Design

1. Polling the Same Tag Multiple Times

If multiple alarm types reference the same tag_id, don't read the tag separately for each alarm. Read the tag once per poll cycle, then run all alarm type decoders against the cached value. This is especially important over Modbus RTU where every extra register read costs 40-50ms.

Group alarm types by their unique tag_ids:

unique_tags = distinct(tag_id for alarm_type in alarm_types)
for tag_id in unique_tags:
values = read_register(device, tag_id)
cache_values(device, tag_id, values)

for alarm_type in alarm_types:
values = get_cached_values(device, alarm_type.tag_id)
active = decode(values, alarm_type.offset, alarm_type.bytes)

2. Ignoring the Difference Between Alarm and Active Alarm

Many systems maintain two concepts:

  • Alarm: A historical record of what happened and when
  • Active Alarm: The current state, right now

Active alarms are tracked in real-time and cleared when the condition resolves. Historical alarms are never deleted — they form the audit trail.

A common mistake is treating the active alarm table as the alarm history. Active alarms should be a thin, frequently-updated state table. Historical alarms should be an append-only log with timestamps for activation, acknowledgment, and clearance.

3. Not Handling Stale Data

When a gateway loses communication with a PLC, the last-read register values persist in cache. If the alarm pipeline continues using these stale values, it won't detect new alarms or clear resolved ones.

Implement a staleness check:

  • Track the timestamp of the last successful read per device
  • If data is older than 2× the poll interval, mark all alarms for that device as "UNKNOWN" (not active, not clear — unknown)
  • Display UNKNOWN state visually distinct from both ACTIVE and CLEAR on the dashboard

4. Timestamp Confusion

PLC registers don't carry timestamps. The timestamp is assigned by whatever reads the register — the edge gateway, the cloud API, or the SCADA system.

For alarm accuracy:

  • Timestamp at the edge gateway, not in the cloud. Network latency can add seconds (or minutes during connectivity loss) between the actual alarm event and cloud receipt.
  • Use the gateway's NTP-synchronized clock. PLCs don't have accurate clocks — some don't have clocks at all.
  • Store timestamps in UTC. Convert to local time only at the display layer, using the machine's configured timezone.

5. Unit Conversion on Alarm Thresholds

If a PLC stores temperature in Fahrenheit and your alarm threshold logic operates in Celsius (or vice versa), every comparison is wrong. This happens more than you'd think in multi-vendor environments where some equipment uses imperial units and others use metric.

Normalize at the edge. Convert all values to SI units (Celsius, kilograms, meters, kPa) before applying alarm logic. This means your alarm thresholds are always in consistent units regardless of the source equipment.

Common conversions that trip people up:

  • Weight/throughput: Imperial (lbs/hr) vs. metric (kg/hr). 1 lb = 0.4536 kg.
  • Flow: GPM vs. LPM. 1 GPM = 3.785 LPM.
  • Length: ft/min vs. m/min. 1 ft = 0.3048 m.
  • Pressure delta: PSI to kPa — ÷0.145.
  • Temperature delta: A 10°F delta ≠ a 10°C delta. Delta conversion: ΔC = ΔF × 5/9.

Architecture: From PLC Register to Dashboard Alert

The end-to-end alarm pipeline in a well-designed IIoT system:

PLC Register (bit field)

Edge Gateway (poll + decode + edge detect)

Local Buffer (persist if cloud is unreachable)

Cloud Ingestion (batch upload with timestamps)

Alarm Service (route + prioritize + notify)

Dashboard / SMS / Email

The critical path: PLC → Gateway → Operator. Everything else (cloud storage, analytics, history) is important but secondary. If the cloud goes down, the gateway must still detect alarms, log them locally, and trigger local notifications (buzzer, light tower, SMS via cellular).

machineCDN implements this architecture with its edge gateway handling the decode and buffering layers, ensuring alarm data is never lost even during connectivity gaps. The gateway maintains PLC communication state, handles the three-pattern alarm decode natively, and batches alarm events for efficient cloud delivery.

Testing Your Alarm Pipeline

Before deploying to production, test every alarm path:

  1. Force each alarm in the PLC (using the PLC programming software) and verify it appears on the dashboard within your target latency
  2. Clear each alarm and verify the dashboard reflects the clear state
  3. Disconnect the PLC (pull the Ethernet cable or RS-485 connector) and verify alarms transition to UNKNOWN, not CLEAR
  4. Reconnect the PLC while alarms are active and verify they immediately show as ACTIVE without requiring a transition through CLEAR first
  5. Restart the gateway while alarms are active and verify no duplicate alarm notifications are generated
  6. Simulate cloud outage and verify alarms are buffered locally and delivered in order when connectivity returns

If any of these tests fail, your alarm pipeline has a gap. Fix it before your operators learn to ignore alerts.

Conclusion

PLC alarm decoding is unglamorous work — bit masking, offset arithmetic, edge detection. It's not the part of IIoT that makes it into the keynote slides. But it's the part that determines whether your monitoring system catches a motor overload at 2 AM or lets it burn out a $50,000 gearbox.

The three-pattern decode (scalar, array-offset, bit-mask) covers the vast majority of industrial equipment. Get this right at the edge gateway layer, add proper edge detection and staleness handling, and your alarm pipeline will be as reliable as the hardwired annunciators it's replacing.


machineCDN's edge gateway decodes alarm registers from any PLC — Modbus RTU or TCP — with configurable alarm type mappings, automatic edge detection, and store-and-forward buffering. No alarms lost, no false positives from stale data. See how it works →

Protocol Bridging: Translating Modbus to MQTT at the Industrial Edge [2026]

· 15 min read

Protocol Bridging Architecture

Every plant floor speaks Modbus. Every cloud platform speaks MQTT. The 20 inches of Ethernet cable between them is where industrial IoT projects succeed or fail.

Protocol bridging — the act of reading data from one industrial protocol and publishing it via another — sounds trivial on paper. Poll a register, format a JSON payload, publish to a topic. Three lines of pseudocode. But the engineers who've actually deployed these bridges at scale know the truth: the hard problems aren't in the translation. They're in the timing, the buffering, the failure modes, and the dozens of edge cases that only surface when a PLC reboots at 2 AM while your MQTT broker is mid-failover.

This guide covers the real engineering of Modbus-to-MQTT bridges — from register-level data mapping to store-and-forward architectures that survive weeks of disconnection.

Why Bridging Is Harder Than It Looks

Modbus and MQTT are fundamentally different communication paradigms. Understanding these differences is critical to building a bridge that doesn't collapse under production conditions.

Modbus is synchronous and polled. The master (your gateway) initiates every transaction. It sends a request frame, waits for a response, processes the data, and moves on. There's no concept of subscriptions, push notifications, or asynchronous updates. If you want a value, you ask for it. Every. Single. Time.

MQTT is asynchronous and event-driven. Publishers send messages whenever they have data. Subscribers receive messages whenever they arrive. The broker decouples producers from consumers. There's no concept of polling — data flows when it's ready.

Bridging these two paradigms means your gateway must act as a Modbus master on one side (issuing timed read requests) and an MQTT client on the other (publishing messages asynchronously). The gateway is the only component that speaks both languages, and it bears the full burden of timing, error handling, and data integrity.

The Timing Mismatch

Modbus RTU on RS-485 at 9600 baud takes roughly 20ms per single-register transaction (request frame + inter-frame delay + response frame + turnaround time). Reading 100 registers individually would take 2 seconds — an eternity if you need sub-second update rates.

Modbus TCP eliminates the serial timing constraints but introduces TCP socket management, connection timeouts, and the possibility of the PLC's TCP stack running out of connections (most PLCs support only 4–8 simultaneous TCP connections).

MQTT, meanwhile, can handle thousands of messages per second. The bottleneck is never the MQTT side — it's always the Modbus side. Your bridge architecture must respect the slower protocol's constraints while maximizing throughput.

Register Mapping: The Foundation

The first engineering decision is how to map Modbus registers to MQTT topics and payloads. There are three common approaches, each with trade-offs.

Approach 1: One Register, One Message

Topic: plant/line3/plc1/holding/40001
Payload: {"value": 1847, "ts": 1709312400, "type": "uint16"}

Pros: Simple, granular, easy to subscribe to individual data points. Cons: Catastrophic at scale. 200 registers means 200 MQTT publishes per poll cycle. At a 1-second poll rate, that's 200 messages/second — sustainable for the broker, but wasteful in bandwidth and processing overhead on constrained gateways.

Approach 2: Batched JSON Messages

Topic: plant/line3/plc1/batch
Payload: {
"ts": 1709312400,
"device_type": 1010,
"tags": [
{"id": 1, "value": 1847, "type": "uint16"},
{"id": 2, "value": 23.45, "type": "float"},
{"id": 3, "value": true, "type": "bool"}
]
}

Pros: Drastically fewer MQTT messages. One publish carries an entire poll cycle's worth of data. Cons: JSON encoding adds CPU overhead on embedded gateways. Payload size can grow large if you have hundreds of tags.

Approach 3: Binary-Encoded Batches

Instead of JSON, encode tag values in a compact binary format: a header with timestamp and device metadata, followed by packed tag records (tag ID + status + type + value). A single 16-bit register value takes 2 bytes in binary vs. ~30 bytes in JSON.

Pros: Minimum bandwidth. Critical for cellular-connected gateways where data costs money per megabyte. Cons: Requires matching decoders on the cloud side. Harder to debug.

The right approach depends on your constraints. For Ethernet-connected gateways with ample bandwidth, batched JSON is the sweet spot. For cellular or satellite links, binary encoding can reduce data costs by 10–15x.

Contiguous Register Coalescing

The single most impactful optimization in any Modbus-to-MQTT bridge is contiguous register coalescing: instead of reading registers one at a time, group adjacent registers into a single Modbus read request.

Consider a tag list where you need registers at addresses 40100, 40101, 40102, 40103, and 40110. A naive implementation makes 5 read requests. A smart bridge recognizes that 40100–40103 are contiguous and reads them in one Read Holding Registers (function code 03) call with a quantity of 4. That's 2 transactions instead of 5.

The coalescing logic must respect several constraints:

  1. Same function code. You can't coalesce a coil read (FC 01) with a holding register read (FC 03). The bridge must group tags by their Modbus register type — coils (0xxxxx), discrete inputs (1xxxxx), input registers (3xxxxx), and holding registers (4xxxxx) — and coalesce within each group.

  2. Maximum register count per transaction. The Modbus specification limits a single read to 125 registers (for 16-bit registers) or 2000 coils. In practice, keeping blocks under 50 registers reduces the risk of timeout errors on slower PLCs.

  3. Addressing gaps. If registers 40100 and 40150 both need reading, coalescing them into a single 51-register read wastes 49 registers worth of response data. Set a maximum gap threshold (e.g., 10 registers) — if the gap exceeds it, split into separate transactions.

  4. Same polling interval. Tags polled every second shouldn't be grouped with tags polled every 60 seconds. Coalescing must respect per-tag timing configuration.

// Pseudocode: Coalescing algorithm
sort tags by address ascending
group_head = first_tag
group_count = 1

for each subsequent tag:
if tag.function_code == group_head.function_code
AND tag.address == group_head.address + group_registers
AND group_registers < MAX_BLOCK_SIZE
AND tag.interval == group_head.interval:
// extend current group
group_registers += tag.elem_count
group_count += 1
else:
// read current group, start new one
read_modbus_block(group_head, group_count, group_registers)
group_head = tag
group_count = 1

In production deployments, contiguous coalescing routinely reduces Modbus transaction counts by 5–10x, which directly translates to faster poll cycles and fresher data.

Data Type Handling: Where the Devils Live

Modbus registers are 16-bit words. Everything else — 32-bit integers, IEEE 754 floats, booleans packed into bit fields — is a convention imposed by the PLC programmer. Your bridge must handle all of these correctly.

32-Bit Values Across Two Registers

A 32-bit float or integer spans two consecutive 16-bit Modbus registers. The critical question: which register contains the high word?

There's no standard. Some PLCs use big-endian word order (high word first, often called "ABCD" byte order). Others use little-endian word order (low word first, "CDAB"). Some use mid-endian orders ("BADC" or "DCBA"). You must know your PLC's convention, or your 23.45°C temperature reading becomes 1.7e+38 garbage.

For IEEE 754 floats specifically, the conversion from two 16-bit registers to a float is:

// Big-endian word order (ABCD)
float_value = ieee754_decode(register[n] << 16 | register[n+1])

// Little-endian word order (CDAB)
float_value = ieee754_decode(register[n+1] << 16 | register[n])

Production bridges must support configurable byte/word ordering on a per-tag basis, because it's common to have PLCs from different manufacturers on the same network.

Boolean Extraction From Status Words

PLCs frequently pack multiple boolean states into a single 16-bit register — machine running, alarm active, door open, etc. Extracting individual bits requires configurable shift-and-mask operations:

bit_value = (register_value >> shift_count) & mask

Where shift_count identifies the bit position (0–15) and mask is typically 0x01 for a single bit. The bridge's tag configuration should support this as a first-class feature, not a post-processing hack.

Type Safety Across the Bridge

When values cross from Modbus to MQTT, type information must be preserved. A uint16 register value of 65535 means something very different from a signed int16 value of -1 — even though the raw bits are identical. Your MQTT payload must carry the type alongside the value, whether in JSON field names or binary format headers.

Connection Resilience: The Store-and-Forward Pattern

The Modbus side of a protocol bridge is local — wired directly to PLCs over Ethernet or RS-485. It rarely fails. The MQTT side connects to a remote broker over a WAN link that will fail. Cellular drops out. VPN tunnels collapse. Cloud brokers restart for maintenance.

A production bridge must implement store-and-forward: continue reading from Modbus during MQTT outages, buffer the data locally, and drain the buffer when connectivity returns.

Page-Based Ring Buffers

The most robust buffering approach for embedded gateways uses a page-based ring buffer in pre-allocated memory:

  1. Format a fixed memory region into equal-sized pages at startup.
  2. Write incoming Modbus data to the current "work page." When a page fills, move it to the "used" queue.
  3. Send pages from the "used" queue to MQTT, one message at a time. Wait for the MQTT publish acknowledgment (at QoS 1) before advancing the read pointer.
  4. Recycle fully-delivered pages back to the "free" list.

If the MQTT connection drops:

  • Stop sending, but keep writing to new pages.
  • If all pages fill up (true buffer overflow), start overwriting the oldest used page. You lose the oldest data, but never the newest.

This design has several properties that matter for industrial deployments:

  • No dynamic memory allocation. The entire buffer is pre-allocated. No malloc, no fragmentation, no out-of-memory crashes at 3 AM.
  • Bounded memory usage. You know exactly how much RAM the buffer consumes. Critical on gateways with 64–256 MB.
  • Delivery guarantees. Each page tracks its own read pointer. If the gateway crashes mid-delivery, the page is re-sent on restart (at-least-once semantics).

How Long Can You Buffer?

Quick math: A gateway reading 100 tags every 5 seconds generates roughly 2 KB of batched JSON per poll cycle. That's 24 KB/minute, 1.4 MB/hour, 34 MB/day. A 256 MB buffer holds 7+ days of data. In binary format, that extends to 50+ days.

For most industrial applications, 24–48 hours of buffering is sufficient to survive maintenance windows, network outages, and firmware upgrades.

MQTT Connection Management

The MQTT side of the bridge deserves careful engineering. Industrial connections aren't like web applications — they run for months without restart, traverse multiple NATs and firewalls, and must recover automatically from every failure mode.

Async Connection With Threaded Reconnect

Never block the Modbus polling loop waiting for an MQTT connection. The correct architecture uses a separate thread for MQTT connection management:

  1. The main thread polls Modbus on a tight timer and writes data to the buffer.
  2. A connection thread handles MQTT connect/reconnect attempts asynchronously.
  3. The buffer drains automatically when the MQTT connection becomes available.

This separation ensures that a 30-second MQTT connection timeout doesn't stall your 1-second Modbus poll cycle. Data keeps flowing into the buffer regardless of MQTT state.

Reconnect Strategy

Use a fixed reconnect delay (5 seconds works well for most deployments) rather than exponential backoff. Industrial MQTT connections are long-lived — the overhead of a 5-second retry is negligible compared to the cost of missing data during a 60-second exponential backoff.

However, protect against connection storms: if the broker is down for an extended period, ensure reconnect attempts don't overwhelm the gateway's CPU or the broker's TCP listener.

TLS Certificate Management

Production MQTT bridges almost always use TLS (port 8883 rather than 1883). The bridge must handle:

  • Certificate expiration. Monitor the TLS certificate file's modification timestamp. If the cert file changes on disk, tear down the current MQTT connection and reinitialize with the new certificate. Don't wait for the existing connection to fail — proactively reconnect.
  • SAS token rotation. When using Azure IoT Hub or similar services with time-limited tokens, parse the token's expiration timestamp and reconnect before it expires.
  • CA certificate bundles. Embedded gateways often ship with minimal CA stores. Ensure your IoT hub's root CA is explicitly included in the gateway's certificate chain.

Change-of-Value vs. Periodic Reporting

Not all tags need the same reporting strategy. A bridge should support both:

Periodic reporting publishes every tag value at a fixed interval, regardless of whether the value changed. Simple, predictable, but wasteful for slowly-changing values like ambient temperature or firmware version.

Change-of-value (COV) reporting compares each newly read value against the previous value and only publishes when a change is detected. This dramatically reduces MQTT traffic for boolean states (machine on/off), setpoints, and alarm registers that change infrequently.

The implementation stores the last-read value for each tag and performs a comparison before deciding whether to publish:

if tag.compare_enabled:
if new_value != tag.last_value:
publish(tag, new_value)
tag.last_value = new_value
else:
publish(tag, new_value) # always publish

A hybrid approach works best: use COV for digital signals and alarm words, periodic for analog measurements like temperature and pressure. Some tags (critical alarms, safety interlocks) should always be published immediately — bypassing both the normal comparison logic and the batching system — to minimize latency.

Calculated and Dependent Tags

Real-world PLCs don't always expose data in the format you need. A bridge should support calculated tags — values derived from raw register data through mathematical or bitwise operations.

Common patterns include:

  • Bit extraction from status words. A 16-bit register contains 16 individual boolean states. The bridge extracts each bit as a separate tag using shift-and-mask operations.
  • Scaling and offset. Raw register value 4000 represents 400.0°F when divided by 10. The bridge applies a linear transformation (value × k1 / k2) to produce engineering units.
  • Dependent tag chains. When a parent tag's value changes, the bridge automatically reads and publishes a set of dependent tags. Example: when the "recipe number" register changes, immediately read all recipe parameter registers.

These calculations must happen at the edge, inside the bridge, before data is published to MQTT. Pushing raw register values to the cloud and calculating there wastes bandwidth and adds latency.

A bridge should publish its own health status alongside machine data. The most critical metric is link state — whether the gateway can actually communicate with the PLC.

When a Modbus read fails with a connection error (timeout, connection reset, connection refused, or broken pipe), the bridge should:

  1. Set the link state to "down" and publish immediately (not batched).
  2. Close the existing Modbus connection and attempt reconnection.
  3. Continue publishing link-down status at intervals so the cloud system knows the gateway is alive but the PLC is unreachable.
  4. When reconnection succeeds, set link state to "up" and force-read all tags to re-establish baseline values.

This link state telemetry is invaluable for distinguishing between "the machine is off" and "the network cable is unplugged" — two very different problems that look identical without gateway-level diagnostics.

How machineCDN Handles Protocol Bridging

machineCDN's edge gateway was built from the ground up for exactly this problem. The gateway daemon handles Modbus RTU (serial), Modbus TCP, and EtherNet/IP on the device side, and publishes all data over MQTT with TLS to the cloud.

Key architectural decisions in the machineCDN gateway:

  • Pre-allocated page buffer with configurable page sizes for zero-allocation runtime operation.
  • Automatic contiguous register coalescing that respects function code boundaries, tag intervals, and register limits.
  • Per-tag COV comparison with an option to bypass batching for latency-critical values.
  • Calculated tag chains for bit extraction and dependent tag reads.
  • Hourly full refresh — every 60 minutes, the gateway resets all COV baselines and publishes every tag value, ensuring the cloud always has a complete snapshot even if individual change events were missed.
  • Async MQTT reconnection with certificate hot-reloading and SAS token expiration monitoring.

The result is a bridge that reliably moves data from plant-floor PLCs to cloud dashboards with sub-second latency during normal operation and zero data loss during outages lasting hours or days.

Deployment Checklist

Before deploying a Modbus-to-MQTT bridge in production:

  • Map every register — document address, data type, byte order, scaling factor, and engineering units
  • Set appropriate poll intervals — 1s for process-critical, 5–60s for environmental, 300s+ for configuration data
  • Size the buffer — calculate daily data volume and ensure the buffer can hold 24+ hours
  • Test byte ordering — verify float and 32-bit integer decoding against known PLC values before trusting the data
  • Configure COV vs periodic — boolean and alarm tags = COV, analog = periodic
  • Enable TLS — never run MQTT unencrypted on production networks
  • Monitor link state — alert on PLC disconnections, not just missing data
  • Test failover — unplug the WAN cable for 4 hours and verify data drains correctly when it reconnects

Protocol bridging isn't glamorous work. It's plumbing. But it's the plumbing that determines whether your IIoT deployment delivers reliable data or expensive noise. Get the bridge right, and everything downstream — analytics, dashboards, predictive maintenance — just works.

Data Normalization in IIoT: Handling Register Formats, Byte Ordering, and Scaling Factors [2026]

· 11 min read
MachineCDN Team
Industrial IoT Experts

Every IIoT engineer eventually faces the same rude awakening: you've got a perfectly good Modbus connection to a PLC, registers are responding, data is flowing — and every single value is wrong.

Not "connection refused" wrong. Not "timeout" wrong. The insidious kind of wrong where a temperature reading of 23.5°C shows up as 17,219, or a pressure value oscillates between astronomical numbers and zero for no apparent reason.

Welcome to the data normalization problem — the unsexy, unglamorous, absolutely critical layer between raw industrial registers and usable engineering data. Get it wrong, and your entire IIoT platform is built on garbage.

Data Normalization in IIoT: Handling PLC Register Formats, Byte Ordering, and Scaling Factors [2026 Guide]

· 13 min read
MachineCDN Team
Industrial IoT Experts

If you've ever stared at a raw Modbus register dump and tried to figure out why your temperature reading shows 16,838 instead of 72.5°F, this article is for you. Data normalization is the unglamorous but absolutely critical layer between industrial equipment and useful analytics — and getting it wrong means your dashboards lie, your alarms misfire, and your predictive maintenance models train on garbage.

After years of building data pipelines from PLCs across plastics, HVAC, and conveying systems, here's what we've learned about the hard parts nobody warns you about.

Modbus TCP vs RTU: A Practical Guide for Plant Engineers [2026]

· 14 min read
MachineCDN Team
Industrial IoT Experts

Modbus TCP vs RTU

Modbus has been the lingua franca of industrial automation for over four decades. Despite the rise of OPC-UA, MQTT, and EtherNet/IP, Modbus remains the most widely deployed protocol on factory floors worldwide. If you're connecting PLCs, chillers, temperature controllers, or blenders to any kind of monitoring or cloud platform, you will encounter Modbus — guaranteed.

But Modbus comes in two flavors that behave very differently at the wire level: Modbus RTU (serial) and Modbus TCP (Ethernet). Choosing the wrong one — or misconfiguring either — is the single most common source of data collection failures in IIoT deployments.

This guide covers the real differences that matter when you're wiring up a plant, not textbook definitions.

Protocol Bridging in IIoT: Translating Between Modbus, EtherNet/IP, and MQTT at the Edge [2026]

· 14 min read

Every manufacturing plant is a polyglot. Modbus RTU on the serial bus. Modbus TCP on the local network. EtherNet/IP talking to Allen-Bradley PLCs. And now someone wants all of that data in the cloud via MQTT.

Protocol bridging at the edge is the unglamorous but critical work that makes IIoT actually function. Get it right, and you have a seamless data pipeline from a 20-year-old Modbus RTU device to a modern cloud analytics platform. Get it wrong, and you have data gaps, crashed connections, and a plant floor that's lost trust in your "smart factory" initiative.

This guide covers the architecture, pitfalls, and hard-won lessons from building protocol bridges that run in production — not just in proof-of-concepts.