Skip to main content

Hardware Sensors vs Protocol-Native IIoT: Why IoTFlows SenseAi and MachineCDN Take Opposite Approaches

· 9 min read
MachineCDN Team
Industrial IoT Experts

The industrial IoT market is splitting into two camps, and the choice between them has long-term implications for your maintenance strategy, your budget, and your data quality. On one side: platforms like IoTFlows that deploy proprietary sensors to collect machine data from the outside. On the other: platforms like MachineCDN that connect directly to the controllers already running your equipment.

This isn't just a technology debate. It's a fundamental question about where manufacturing data should come from — and who owns the measurement infrastructure.

IoTFlows AI Job Scheduling vs MachineCDN Predictive Maintenance: Which AI Approach Delivers More Value?

· 9 min read
MachineCDN Team
Industrial IoT Experts

Both IoTFlows and MachineCDN use AI to improve manufacturing outcomes, but they apply artificial intelligence to fundamentally different problems. IoTFlows focuses its AI capabilities on job scheduling optimization — using machine learning to sequence production runs for maximum throughput. MachineCDN applies AI to predictive maintenance and anomaly detection — using real-time PLC data to predict equipment failures before they happen.

Understanding which AI approach delivers more value depends entirely on where your plant loses the most money today.

IoTFlows vs MachineCDN for Downtime Root Cause Analysis: Which Platform Finds Problems Faster?

· 8 min read
MachineCDN Team
Industrial IoT Experts

When a $40,000-per-hour stamping press goes down, the last thing your maintenance team needs is ambiguity. They need to know exactly what failed, exactly when, and exactly why — not a vibration score that says "something might be wrong."

That's where the fundamental difference between IoTFlows and MachineCDN becomes crystal clear. Both platforms promise downtime root cause analysis, but they approach the problem from opposite directions — and the approach determines how fast your team gets answers.

MachineCDN vs Samsara for Factory Equipment Monitoring: IoT Fleet Platform vs Purpose-Built IIoT

· 8 min read
MachineCDN Team
Industrial IoT Experts

Samsara is one of the most recognizable names in IoT — a publicly traded company (NYSE: IOT) valued at over $20 billion that processes trillions of data points from millions of connected devices. When manufacturers evaluate connected platforms, Samsara's name inevitably comes up.

But here's the nuance that matters: Samsara is a fleet and operations IoT platform that also does manufacturing. MachineCDN is a manufacturing IIoT platform that does nothing else. That distinction shapes every feature, every price point, and every deployment decision.

This comparison goes deeper than our initial MachineCDN vs Samsara analysis — specifically focusing on the factory equipment monitoring use case that matters most to manufacturing engineers.

Top 8 MachineMetrics Alternatives for Manufacturing in 2026

· 10 min read
MachineCDN Team
Industrial IoT Experts

MachineMetrics has built a solid reputation in CNC machine monitoring. If you're running a job shop full of Haas, Mazak, and DMG MORI mills, their platform delivers real-time visibility into spindle utilization, cycle times, and part counts.

But MachineMetrics has limitations that become obvious as your monitoring needs expand. Their strength — deep CNC integration via MTConnect and FANUC FOCAS — is also their constraint. If you need to monitor injection molding machines, packaging lines, compressors, furnaces, or any non-CNC equipment, you'll quickly hit the edges of what MachineMetrics can do.

Here are 8 alternatives worth evaluating in 2026, ranked by how well they solve the problems MachineMetrics doesn't.

MachineMetrics Pricing in 2026: What Does MachineMetrics Actually Cost?

· 8 min read
MachineCDN Team
Industrial IoT Experts

If you've been evaluating CNC monitoring platforms, MachineMetrics has probably come up. They've carved out a niche in discrete manufacturing — particularly in CNC machine shops — with a platform that promises real-time production visibility and predictive analytics. But when it comes to actually pricing the thing, you'll find that MachineMetrics makes it surprisingly difficult to get a straight answer.

Materials and Inventory Management for Manufacturing: How IIoT Closes the Visibility Gap

· 9 min read
MachineCDN Team
Industrial IoT Experts

Ask any production manager what stops their lines, and "we ran out of material" will be in the top five answers. It's not a technology failure or a mechanical breakdown — it's a visibility problem. The hopper ran empty because nobody checked it. The raw material wasn't staged because the warehouse didn't know the production schedule changed. The spare part wasn't available because nobody tracked consumption rates.

Materials and inventory management in manufacturing has been a blind spot for most IIoT platforms, which focus exclusively on machine health and OEE. But material availability is directly connected to equipment uptime, product quality, and production throughput. A machine that's running perfectly still produces zero output if its material hopper is empty.

Contiguous Modbus Register Reads: How to Optimize PLC Polling for Maximum Throughput [2026]

· 12 min read

If you're polling a PLC with Modbus and reading one register at a time, you're wasting 80% of your bus time on protocol overhead. Every Modbus transaction carries a fixed cost — framing bytes, CRC calculations, response timeouts, and turnaround delays — regardless of whether you're reading 1 register or 120. The math is brutal: reading 60 holding registers individually means 60 request/response cycles. Coalescing them into a single read means one cycle that returns all 60 values.

This article breaks down the mechanics of contiguous register optimization, shows you exactly how to implement it, and explains why it's the single highest-impact change you can make to your IIoT data collection architecture.

Modbus register optimization

The Hidden Cost of Naive Polling

Let's do the math on a typical Modbus RTU link at 9600 baud.

A single Modbus RTU read request (function code 03) for one holding register looks like this:

FieldBytes
Slave Address1
Function Code1
Starting Address2
Quantity of Registers2
CRC2
Request Total8

The response for a single register:

FieldBytes
Slave Address1
Function Code1
Byte Count1
Register Value2
CRC2
Response Total7

That's 15 bytes on the wire for 2 bytes of actual data — 13.3% payload efficiency.

Now add the silent interval between frames. Modbus RTU requires a minimum 3.5-character gap (roughly 4ms at 9600 baud) between transactions. Plus a typical slave response time of 5–50ms. For a conservative 20ms response delay:

  • Wire time per transaction: ~15.6ms (15 bytes × 1.04ms/byte at 9600 baud)
  • Turnaround + gap: ~24ms
  • Total per register: ~39.6ms
  • 60 registers individually: ~2,376ms (2.4 seconds!)

Now, reading those same 60 registers in one contiguous block:

  • Request: 8 bytes (unchanged)
  • Response: 1 + 1 + 1 + 120 + 2 = 125 bytes
  • Wire time: ~138.5ms
  • One turnaround: ~24ms
  • Total: ~162.5ms

That's 14.6x faster for the same data. On a serial bus where you might have multiple slave devices and hundreds of tags, this is the difference between a 1-second polling cycle and a 15-second one.

Understanding Modbus Address Spaces

Before you can coalesce reads, you need to understand that Modbus defines four distinct address spaces, each requiring a different function code:

Address RangeRegister TypeFunction CodeRead Operation
0–65,535Coils (discrete outputs)FC 01Read Coils
100,001–165,536Discrete InputsFC 02Read Discrete Inputs
300,001–365,536Input Registers (16-bit, read-only)FC 04Read Input Registers
400,001–465,536Holding Registers (16-bit, read/write)FC 03Read Holding Registers

Critical rule: you cannot coalesce reads across function codes. A request using FC 03 (holding registers) cannot include addresses that belong to FC 04 (input registers). They're physically different memory areas in the PLC. Any optimization algorithm must first partition tags by their function code, then optimize within each partition.

The address encoding convention (where 400001 maps to holding register 0) is a common source of bugs. When you see address 404002 in a tag configuration, the leading 4 indicates holding registers (FC 03), and the actual Modbus address sent on the wire is 4002. Your coalescing logic needs to strip the prefix for wire protocol but keep it for function code selection.

The Coalescing Algorithm

The core idea is simple: sort tags by address within each function code group, then merge adjacent tags into single read operations. Here's the logic:

Step 1: Sort Tags by Address

Your tag list must be ordered by Modbus address. If tags arrive in arbitrary order (as they typically do from configuration files), sort them first. This is a one-time cost at startup.

Tag: "Delivery Temp"    addr: 404002  type: float  ecount: 2
Tag: "Mold Temp" addr: 404004 type: float ecount: 2
Tag: "Return Temp" addr: 404006 type: float ecount: 2
Tag: "Flow Value" addr: 404008 type: float ecount: 2
Tag: "System Standby" addr: 404010 type: float ecount: 2

All five tags use FC 03 (holding registers). Their addresses are contiguous: 4002, 4004, 4006, 4008, 4010. Each occupies 2 registers (32-bit float = 2 × 16-bit registers).

Step 2: Walk the Sorted List and Build Read Groups

Starting from the first tag, accumulate subsequent tags into the same group as long as:

  1. Same function code — The address prefix maps to the same Modbus command
  2. Contiguous addresses — The next tag's address equals the current head address plus accumulated register count
  3. Same polling interval — Tags with different intervals should be in separate groups (a 1-second tag shouldn't force a 60-second tag to be read every second)
  4. Register count limit — Modbus protocol limits a single read to 125 registers (for FC 03/04) or 2000 coils (for FC 01/02). In practice, keeping it under 50–120 registers per read improves reliability on noisy links

When any condition fails, finalize the current group, issue the read, and start a new group with the current tag as head.

Step 3: Dispatch the Coalesced Read

For our five temperature tags:

Single coalesced read:
Function Code: 03
Starting Address: 4002
Quantity: 10 registers (5 tags × 2 registers each)

One transaction returns all 10 registers. The response buffer contains the raw bytes in order — you then walk through the buffer, extracting values according to each tag's type and element count.

Step 4: Unpack Values from the Response Buffer

This is where data types matter. The response is a flat array of 16-bit words. For each tag in the group, you consume the correct number of words:

  • uint16/int16: 1 word, direct assignment
  • uint32/int32: 2 words, combine as (word[1] << 16) | word[0] (check your PLC's byte order!)
  • float32: 2 words, requires IEEE 754 reconstruction — modbus_get_float() in libmodbus or manual byte swapping
  • bool/int8/uint8: 1 word, mask with & 0xFF
Response buffer: [w0, w1, w2, w3, w4, w5, w6, w7, w8, w9]
|-------| |-------| |-------| |-------| |-------|
Tag 0 Tag 1 Tag 2 Tag 3 Tag 4
float float float float float

Handling Gaps in the Address Space

Real-world tag configurations rarely have perfectly contiguous addresses. You'll encounter gaps:

Tag A: addr 404000, ecount 2
Tag B: addr 404004, ecount 2 ← gap of 2 registers at 404002
Tag C: addr 404006, ecount 2

You have two choices:

  1. Read through the gap — Issue one read from 4000 to 4007 (8 registers), and simply ignore the 2 garbage registers at offset 2–3. This is usually optimal if the gap is small (< 10 registers). The cost of reading extra registers is almost zero.

  2. Split into separate reads — If the gap is large (say, 50+ registers), two smaller reads are more efficient than one bloated read full of data you'll discard.

A good heuristic: if the gap is less than the per-transaction overhead expressed in registers, read through it. At 9600 baud, a transaction costs ~24ms of overhead, equivalent to reading about 12 extra registers. So gaps under 12 registers should be read through.

Handling Mixed Polling Intervals

Not all tags need the same update rate. Temperature setpoints might only need reading every 60 seconds, while pump status flags need 1-second updates. Your coalescing algorithm must handle this.

The approach: partition by interval before coalescing by address. Tags with interval: 1 form one pool, tags with interval: 60 form another. Within each pool, apply address-based coalescing normally.

During each polling cycle, check whether enough time has elapsed since a tag's last read. If a tag isn't due for reading, skip it — but this means breaking the contiguous chain:

Cycle at T=30s:
Tag A (interval: 1s) → READ addr: 404000
Tag B (interval: 60s) → SKIP addr: 404002 ← breaks contiguity
Tag C (interval: 1s) → READ addr: 404004

Tags A and C can't be coalesced because Tag B sits between them and isn't being read. The algorithm must detect the break and issue two separate reads.

Optimization: If Tag B is cheap to read (1–2 registers), consider reading it anyway and discarding the result. The overhead of an extra 2 registers in a contiguous block is far less than the overhead of a separate transaction.

Modbus RTU vs TCP: Different Optimization Priorities

Modbus RTU (Serial)

  • Bottleneck: Baud rate and turnaround time
  • Priority: Minimize transaction count at all costs
  • Flush between reads: Always flush the serial buffer before starting a new poll cycle to clear any stale or corrupted data
  • Retry logic: Implement 2–3 retries per read with short delays — serial links are noisy, but a retry is still cheaper than dropping data
  • Response timeout: Configure carefully. Too short (< 50ms) causes false timeouts; too long (> 500ms) kills throughput. 100–200ms is typical for most PLCs
  • Byte/character timeout: Set to ~5ms at 9600 baud. This detects mid-frame breaks

Modbus TCP

  • Bottleneck: Connection management, not bandwidth
  • Priority: Keep connection alive and reuse it
  • Connection recovery: Detect ETIMEDOUT, ECONNRESET, ECONNREFUSED, EPIPE, and EBADF — these all mean the connection is dead and needs reconnecting
  • No inter-frame gaps: TCP handles framing, so back-to-back transactions are fine
  • Default port: 502, but some PLCs use non-standard ports — make this configurable

Real-World Configuration Example

Here's a practical tag configuration for a temperature control unit using Modbus RTU, with 32 holding registers read as IEEE 754 floats:

{
"protocol": "modbus-rtu",
"batch_timeout": 60,
"link": {
"port": "/dev/rs232",
"base_addr": 1,
"baud": 9600,
"parity": "N",
"data_bits": 8,
"stop_bits": 2,
"byte_timeout_ms": 5,
"response_timeout_ms": 200
},
"tags": [
{"name": "Delivery Temp", "addr": 404002, "type": "float", "ecount": 2, "interval": 60},
{"name": "Mold Temp", "addr": 404004, "type": "float", "ecount": 2, "interval": 60},
{"name": "Return Temp", "addr": 404006, "type": "float", "ecount": 2, "interval": 60},
{"name": "Flow Value", "addr": 404008, "type": "float", "ecount": 2, "interval": 60},
{"name": "Heater Output %", "addr": 404054, "type": "float", "ecount": 2, "interval": 60},
{"name": "Cooling Output %", "addr": 404056, "type": "float", "ecount": 2, "interval": 60},
{"name": "Pump Status", "addr": 404058, "type": "float", "ecount": 2, "interval": 1, "immediate": true},
{"name": "Heater Status", "addr": 404060, "type": "float", "ecount": 2, "interval": 1, "immediate": true},
{"name": "Vent Status", "addr": 404062, "type": "float", "ecount": 2, "interval": 1, "immediate": true}
]
}

The coalescing algorithm would produce:

  • Group 1 (interval: 60s): Read 404002–404009 → 4 tags, 8 registers, one read
  • Group 2 (interval: 60s): Read 404054–404057 → 2 tags, 4 registers, one read (non-contiguous with Group 1, separate read)
  • Group 3 (interval: 1s): Read 404058–404063 → 3 tags, 6 registers, one read

Three transactions instead of nine. At 9600 baud, that saves ~240ms per polling cycle — which adds up to minutes of saved bus time per hour.

Common Pitfalls

1. Ignoring Element Count

A float tag occupies 2 registers, not 1. If your coalescing logic treats every tag as 1 register, your contiguity check will be wrong and you'll read corrupt data. Always use addr + elem_count when calculating the next expected address.

2. Byte Order Confusion

Different PLCs use different byte orders for 32-bit values. Some use big-endian word order (ABCD), others use little-endian (DCBA), and some use mid-endian (BADC or CDAB). If your float values come back as NaN or astronomically wrong numbers, byte order is almost certainly the issue. Test with a known value (like a temperature setpoint you can verify on the HMI) and adjust.

3. Not Handling Read Failures Gracefully

When a coalesced read fails, the entire group fails. Don't panic — just flush the serial buffer, log the error, and retry up to 3 times. If the error is a connection-level failure (timeout, connection reset), close and reopen the Modbus context rather than hammering a dead link.

4. Exceeding the Register Limit

The Modbus specification allows up to 125 registers per read (FC 03/04) and 2000 coils (FC 01/02). In practice, many PLCs choke at lower limits — some only handle 50–60 registers per request reliably. Cap your coalesced read size at a conservative number (50 is safe for virtually all PLCs) and test with your specific hardware.

5. Mixing Immediate and Batched Tags

Some tags (like alarm states, emergency stops, or pump status changes) need to be sent to the cloud immediately — not held in a batch for 60 seconds. These "do not batch" tags should be delivered to the MQTT layer as soon as they're read, bypassing the batching pipeline. But they can still participate in coalesced reads; the immediate/batched distinction is about delivery, not reading.

How machineCDN Handles This

The machineCDN edge daemon implements all of these optimizations natively. Tags are automatically sorted by address at configuration load time, coalesced into contiguous read groups respecting function code boundaries and interval constraints, and read with retry logic tuned for industrial serial links. Both Modbus RTU and Modbus TCP are supported with automatic protocol detection — the daemon probes the PLC at startup to determine the device type, serial number, and communication protocol before configuring the polling loop.

The result: a single edge gateway on a Teltonika RUT9 industrial router can poll hundreds of tags from multiple devices with sub-second cycle times, even on 9600 baud serial links.

Conclusion

Contiguous register optimization is not optional for production IIoT deployments. The performance difference between naive per-register polling and properly coalesced reads is an order of magnitude. The algorithm is straightforward — sort by address, group by function code and interval, cap at the register limit, and handle gaps intelligently. Get this right, and your serial bus goes from bottleneck to barely loaded.

The tags are just data points. How you read them determines whether your IIoT system is a science project or production infrastructure.

Modbus Polling Optimization: Register Grouping, Retry Logic, and Multi-Device Scheduling [2026 Guide]

· 15 min read

Modbus Register Polling Optimization

Modbus is 46 years old and still the most commonly deployed industrial protocol on the planet. It runs in power plants, water treatment facilities, HVAC systems, plastics factories, and pharmaceutical clean rooms. Its simplicity is its superpower — and its trap.

Because Modbus is conceptually simple (read some registers, write some registers), engineers tend to implement polling in the most straightforward way possible: loop through tags, read each one, repeat. This works fine for 10 tags on one device. It falls apart spectacularly at 200 tags across eight devices on a congested RS-485 bus.

This guide covers the polling optimization techniques that separate hobbyist implementations from production-grade edge gateways — the kind that power platforms like machineCDN across thousands of connected machines.

The Four Function Codes That Matter

Before optimizing anything, you need to understand how Modbus maps register addresses to function codes. This mapping is the foundation of every optimization strategy.

Address RangeFunction CodeRead TypeRegister Type
0 – 65,535FC 01Read CoilsDiscrete Output (1-bit)
100,000 – 165,536FC 02Read Discrete InputsDiscrete Input (1-bit)
300,000 – 365,536FC 04Read Input RegistersAnalog Input (16-bit)
400,000 – 465,536FC 03Read Holding RegistersAnalog Output (16-bit)

The critical insight: You cannot mix function codes in a single Modbus request. A read of holding registers (FC 03) and a read of input registers (FC 04) are always two separate transactions, even if the registers are numerically adjacent when you strip the prefix.

This means your first optimization step is grouping tags by function code. A tag list with 50 holding registers and 10 input registers requires at minimum 2 requests, not 1 — no matter how clever your batching.

Address Decoding in Practice

Many Modbus implementations use the address prefix convention to encode both the register type and the function code:

  • Address 404000 → Function Code 3, register 4000
  • Address 304000 → Function Code 4, register 4000
  • Address 4000 → Function Code 1, coil 4000
  • Address 104000 → Function Code 2, discrete input 4000

The register address sent on the wire is the address modulo the range base. So 404000 becomes register 4000 in the actual Modbus PDU. Getting this decoding wrong is the #1 cause of "I can read the same register in my Modbus scanner tool but not in my gateway" issues.

Contiguous Register Grouping

The single most impactful optimization in Modbus polling is contiguous register grouping — combining multiple sequential register reads into a single bulk read.

Why It Matters: The Overhead Math

Every Modbus transaction has fixed overhead:

ComponentRTU (Serial)TCP
Request frame8 bytes12 bytes (MBAP header + PDU)
Response frame header5 bytes9 bytes
Turnaround delay3.5 char times (RTU)~1ms (TCP)
Response data2 × N registers2 × N registers
Inter-frame gap3.5 char times (RTU)N/A
Total overhead per request~50ms at 9600 baud~2-5ms

For RTU at 9600 baud, each individual register read (request + response + delays) takes roughly 50ms. Reading 50 registers individually = 2.5 seconds. Reading them as one bulk request of 50 contiguous registers = ~120ms. That's a 20x improvement.

Grouping Algorithm

The practical algorithm for contiguous grouping:

  1. Sort tags by function code, then by register address within each group
  2. Walk the sorted list and identify contiguous runs (where addr[n+1] <= addr[n] + elem_count[n])
  3. Enforce a maximum group size — the Modbus spec allows up to 125 registers (250 bytes) per FC 03/04 read, but practical implementations should cap at 50-100 to stay within device buffer limits
  4. Handle gaps intelligently — if two tags are separated by 3 unused registers, it's cheaper to read the gap (3 extra registers × 2 bytes = 6 bytes) than to issue a separate request (50ms+ overhead)

Gap Tolerance: The Break-Even Point

When should you read through a gap versus splitting into two requests?

For Modbus TCP, the overhead of a separate request is ~2-5ms. Each extra register costs ~0.02ms. Break-even: ~100-250 register gap — almost always worth reading through.

For Modbus RTU at 9600 baud, the overhead is ~50ms. Each register costs ~2ms. Break-even: ~25 registers — read through anything smaller, split anything larger.

For Modbus RTU at 19200 baud, overhead drops to ~25ms, each register ~1ms. Break-even: ~25 registers — similar ratio holds.

Practical recommendation: Set your gap tolerance to 20 registers for RTU and 100 registers for TCP. You'll read a few bytes of irrelevant data but dramatically reduce transaction count.

Multi-Register Data Types

Many industrial values span multiple consecutive registers:

Data TypeRegistersBytesCommon Use
INT16 / UINT1612Discrete values, status codes
INT32 / UINT3224Counters, accumulated values
FLOAT32 (IEEE 754)24Temperatures, pressures, flows
FLOAT6448High-precision measurements

A 32-bit float at register 4002 occupies registers 4002 and 4003. Your grouping algorithm must account for elem_count — reading only register 4002 gives you half a float, which decodes to a nonsensical value.

Byte Ordering Nightmares

This is where Modbus gets genuinely painful. The Modbus spec defines big-endian register ordering, but says nothing about how multi-register values should be assembled. Different manufacturers use different conventions:

Byte OrderRegister OrderNameWho Uses It
Big-endianHigh word firstAB CDMost European PLCs, Siemens
Big-endianLow word firstCD ABSome Asian manufacturers
Little-endianHigh word firstBA DCRare
Little-endianLow word firstDC BASome legacy equipment

A temperature reading of 42.5°C stored as IEEE 754 float 0x42AA0000:

  • AB CD: Register 4002 = 0x422A, Register 4003 = 0x0000 → ✅ 42.5
  • CD AB: Register 4002 = 0x0000, Register 4003 = 0x422A → ✅ 42.5 (if you swap)
  • BA DC: Register 4002 = 0x2A42, Register 4003 = 0x0000 → ❌ Garbage without byte-swap

The only reliable approach: During commissioning, write a known value (e.g., 100.0 = 0x42C80000) to a test register and verify that your gateway decodes it correctly. Document the byte order per device — it will save you hours later.

Production-grade platforms like machineCDN handle byte ordering at the device configuration level, so each connected machine can have its own byte-order profile without requiring custom parsing logic.

Intelligent Retry Logic

Network errors happen. Serial bus collisions happen. PLCs get busy and respond late. Your retry strategy determines whether a transient error becomes a data gap or a transparent recovery.

The Naive Approach (Don't Do This)

for each tag:
result = read_register(tag)
if failed:
retry 3 times immediately

Problems:

  • Hammers a struggling device with back-to-back requests
  • Blocks all other reads while retrying
  • Doesn't distinguish between transient errors (timeout) and permanent errors (wrong address)

A Better Approach: Error-Classified Retry

Different errors deserve different responses:

ErrorSeverityAction
Timeout (ETIMEDOUT)TransientRetry with backoff, reconnect if persistent
Connection reset (ECONNRESET)ConnectionClose connection, reconnect, resume
Connection refused (ECONNREFUSED)InfrastructureBack off significantly (device may be rebooting)
Broken pipe (EPIPE)ConnectionReconnect immediately
Bad file descriptor (EBADF)InternalRecreate context from scratch
Illegal data address (Modbus exception 02)PermanentDon't retry — tag is misconfigured
Device busy (Modbus exception 06)TransientRetry after delay

Retry Count and Timing

For batch reads (contiguous groups), a reasonable strategy:

  1. First attempt: Read the full register group
  2. On failure: Wait 50ms (RTU) or 10ms (TCP), retry the same group
  3. Second failure: Wait 100ms, retry once more
  4. Third failure: Log the error, mark the group as failed for this cycle, move to next group
  5. After a connection-level error (timeout, reset, refused):
    • Close the Modbus context
    • Set device state to "disconnected"
    • On next poll cycle, attempt reconnection
    • If reconnection succeeds, flush any stale data in the serial buffer before resuming reads

Critical detail for RTU: After a timeout or error, always flush the serial buffer before retrying. Stale bytes from a partial response can corrupt the next transaction's framing, causing a cascade of CRC errors.

Inter-Request Delay

Modbus RTU requires a 3.5 character-time silence between frames. At 9600 baud, this is approximately 4ms. At 19200 baud, it's 2ms.

Many implementations add a fixed 50ms delay between requests as a safety margin. This works but is wasteful — on a 100-tag system, you're spending 5 seconds just on inter-request delays.

Better approach: Use a 5ms delay at 9600 baud and a 2ms delay at 19200 baud. Monitor CRC error rates — if they increase, lengthen the delay. Some older devices need more silence time than the spec requires.

Multi-Device Polling Scheduling

When your edge gateway talks to multiple Modbus devices (common in manufacturing — one PLC per machine line, plus temperature controllers, VFDs, and meters), polling strategy becomes a scheduling problem.

Round-Robin: Simple but Wasteful

The naive approach:

while true:
for each device:
for each tag_group in device:
read(tag_group)
sleep(poll_interval)

Problem: If you have 8 devices with different priorities, the critical machine's data is delayed by reads to 7 other devices.

Priority-Based with Interval Tiers

A better model uses per-tag read intervals:

TierIntervalTypical TagsPurpose
Critical1-5 secMachine running/stopped, alarm bits, emergency statesImmediate operational awareness
Process30-60 secTemperatures, pressures, RPMs, flow rates, power consumptionTrend analysis, anomaly detection
Diagnostic5-60 minFirmware version, serial numbers, configuration values, cumulative countersAsset management

Implementation: Maintain a last_read_timestamp per tag (or per tag group). On each poll loop, only read groups where now - last_read > interval.

This dramatically reduces bus traffic. In a typical plastics manufacturing scenario with 8 machines:

  • Without tiers: 400 register reads every 5 seconds = 80 reads/sec
  • With tiers: 80 critical + 40 process + 2 diagnostic = ~14 reads/sec average

That's a 5.7x reduction in bus utilization.

Change-Based Transmission

For data going to the cloud, there's another optimization layer: compare-on-change. Many industrial values don't change between reads — a setpoint stays at 350°F for hours, a machine status stays "running" for the entire shift.

The strategy:

  1. Read the register at its configured interval (always — you need to know the current value)
  2. Compare the new value against the last transmitted value
  3. Only transmit to the cloud if:
    • The value has changed, OR
    • A maximum time-without-update has elapsed (heartbeat)

For boolean tags (machine running, alarm active), compare every read and transmit immediately on change — these are the signals that matter most for operational response.

For analog tags (temperature, pressure), you can add a deadband: only transmit if the value has changed by more than X% or Y absolute units. A temperature reading that bounces between 349.8°F and 350.2°F doesn't need to generate 60 cloud messages per hour.

machineCDN's edge agent implements this compare-and-transmit pattern natively, batching changed values into optimized payloads that minimize both bandwidth and cloud ingestion costs.

RTU vs TCP: Polling Strategy Differences

Modbus RTU (Serial RS-485)

  • Half-duplex: Only one device can transmit at a time
  • Single bus: All devices share the same wire pair
  • Addressing: Slave address 1-247 (broadcast at 0)
  • Speed: Typically 9600-115200 baud
  • Critical constraint: Bus contention — you MUST wait for a complete response (or timeout) before addressing another device
  • CRC: 16-bit CRC appended to every frame

RTU polling tips:

  • Set timeouts based on maximum expected response size. For 125 registers at 9600 baud: (125 * 2 bytes * 10 bits/byte) / 9600 = ~260ms plus overhead ≈ 300-500ms timeout
  • Never set the timeout below the theoretical transmission time — you'll get phantom timeouts
  • If one device on the bus goes unresponsive, its timeout blocks ALL other devices. Aggressive timeout + retry is better than patient timeout + no retry.

Modbus TCP

  • Full-duplex: Request and response can overlap (on different connections)
  • Multi-connection: Each device gets its own TCP socket
  • No contention: Parallel reads to different devices
  • Speed: 100Mbps+ network bandwidth (practically unlimited for Modbus payloads)
  • Transaction ID: The MBAP header includes a transaction ID for matching responses to requests

TCP polling tips:

  • Use persistent connections — TCP handshake + Modbus connection setup adds 10-50ms per connection. Reconnect only on error.
  • You CAN poll multiple TCP devices simultaneously using non-blocking sockets or threads. This is a massive advantage over RTU.
  • Set TCP keepalive on the socket — some industrial firewalls and managed switches close idle connections after 60 seconds.
  • The Modbus/TCP unit identifier field is usually ignored (set to 0xFF) for direct device connections, but matters if you're going through a TCP-to-RTU gateway.

Bandwidth Optimization: Binary vs JSON Batching

Once you've read your registers, the data needs to get to the cloud. The payload format matters enormously at scale.

JSON Format (Human-Readable)

{
"groups": [{
"ts": 1709330400,
"device_type": 5000,
"serial_number": 1106336053,
"values": [
{"id": 1, "v": 4.4, "st": 0},
{"id": 2, "v": 162.5, "st": 0},
{"id": 3, "v": 158.3, "st": 0}
]
}]
}

For 3 values: ~200 bytes.

Binary Format (Machine-Optimized)

A well-designed binary format encodes the same data in:

  • 4 bytes: timestamp (uint32)
  • 2 bytes: device type (uint16)
  • 4 bytes: serial number (uint32)
  • Per value: 2 bytes (tag ID) + 1 byte (status) + 4 bytes (value) = 7 bytes

For 3 values: 31 bytes — an 84% reduction.

Over cellular connections (common for remote industrial sites), this difference is enormous. A machine reporting 50 values every 60 seconds:

  • JSON: ~3.3 KB/min → 4.8 MB/day → 144 MB/month
  • Binary: ~0.5 KB/min → 0.7 MB/day → 21 MB/month

That's the difference between a $10/month cellular plan and a $50/month plan — multiplied by every connected machine.

The batching approach also matters. Instead of transmitting each read result immediately, accumulate values into a batch and transmit when either:

  • The batch reaches a size threshold (e.g., 4KB)
  • A time threshold expires (e.g., 30 seconds since batch started)

This amortizes the MQTT/HTTP overhead across many data points and enables efficient compression.

Store-and-Forward: Surviving Connectivity Gaps

Industrial environments have unreliable connectivity — cellular modems reboot, VPN tunnels flap, WiFi access points go down during shift changes. Your polling shouldn't stop when the cloud connection drops.

A robust edge gateway implements a local buffer:

  1. Poll and batch as normal, regardless of cloud connectivity
  2. When connected: Transmit batches immediately
  3. When disconnected: Store batches in a ring buffer (sized to the available memory)
  4. When reconnected: Drain the buffer in chronological order before transmitting live data

Buffer Sizing

The buffer should be sized to survive your typical outage duration:

Data Rate1-Hour Buffer8-Hour Buffer24-Hour Buffer
1 KB/min60 KB480 KB1.4 MB
10 KB/min600 KB4.8 MB14.4 MB
100 KB/min6 MB48 MB144 MB

For embedded gateways with 256MB RAM, a 2MB ring buffer comfortably handles 8-24 hours of typical industrial data at modest polling rates. The key design decision is what happens when the buffer fills: either stop accepting new data (gap in the oldest data) or overwrite the oldest data (gap in the newest data). For most IIoT use cases, overwriting oldest is preferred — recent data is more actionable than historical data.

Putting It All Together: A Production Polling Architecture

Here's what a production-grade Modbus polling pipeline looks like:

┌─────────────────────────────────────────────────────┐
│ POLL SCHEDULER │
│ Per-tag intervals → Priority queue → Due tags │
└──────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│ REGISTER GROUPER │
│ Sort by FC → Find contiguous runs → Build groups │
└──────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│ MODBUS READER │
│ Execute reads → Retry on error → Reconnect logic │
└──────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│ VALUE PROCESSOR │
│ Byte-swap → Type conversion → Scaling → Compare │
└──────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│ BATCH ENCODER │
│ Group by timestamp → Encode (JSON/binary) → Size │
└──────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│ STORE-AND-FORWARD BUFFER │
│ Ring buffer → Page management → Drain on connect │
└──────────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────────┐
│ MQTT PUBLISHER │
│ QoS 1 → Async delivery → ACK tracking │
└─────────────────────────────────────────────────────┘

This is the architecture that machineCDN's edge agent implements. Each layer is independently testable, and failures at any layer don't crash the pipeline — they produce graceful degradation (data gaps in the cloud, not system crashes on the factory floor).

Benchmarks: Before and After Optimization

Real numbers from a plastics manufacturing deployment with 8 Modbus RTU devices on a 19200 baud RS-485 bus, 320 total tags:

MetricUnoptimizedOptimizedImprovement
Full poll cycle time48 sec6.2 sec7.7x faster
Requests per cycle3204287% fewer
Bus utilization94%12%Room for growth
Data gaps per day15-200-1Near-zero
Cloud bandwidth (daily)180 MB28 MB84% reduction
Avg tag staleness48 sec6 sec8x fresher

The unoptimized system couldn't even complete a poll cycle in under a minute. The optimized system polls every 6 seconds with headroom to add more devices.

Final Recommendations

  1. Always group contiguous registers — this is non-negotiable for production systems
  2. Use tiered polling intervals — not every tag needs the same update rate
  3. Implement error-classified retry — don't retry permanent errors, do retry transient ones
  4. Use binary encoding for cellular — JSON is fine for LAN-connected gateways
  5. Size your store-and-forward buffer for your realistic outage window
  6. Flush the serial buffer after errors — this prevents CRC cascades on RTU
  7. Document byte ordering per device — test with known values during commissioning
  8. Monitor bus utilization — stay below 30% to leave headroom for retries and growth

Modbus isn't going away. But the difference between a naive implementation and an optimized one is the difference between a system that barely works and one that scales to hundreds of machines without breaking a sweat.


machineCDN's edge agent handles Modbus RTU and TCP with optimized register grouping, binary batching, and store-and-forward buffering out of the box. Connect your PLCs in minutes, not weeks. Get started →

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 →