Skip to main content

ISA-95 and IIoT Integration: Bridging IT and OT in Modern Manufacturing

· 9 min read
MachineCDN Team
Industrial IoT Experts

ISA-95 was created in the late 1990s to solve a simple problem: how should enterprise systems (ERP) communicate with plant floor systems (PLCs and SCADA)? Two decades later, IIoT platforms have disrupted the neat hierarchical model that ISA-95 defined. Data now flows from sensors directly to the cloud, bypassing every layer in between. The question for manufacturing engineers in 2026 isn't whether ISA-95 is still relevant — it's how to reconcile a framework built for hierarchical, on-premises architectures with the reality of cloud-native, edge-computing IIoT platforms.

JSON-Based PLC Tag Configuration: Building Maintainable IIoT Device Templates [2026]

· 12 min read

If you've ever stared at a spreadsheet of 200 PLC register addresses trying to figure out which ones your SCADA system is actually polling, you know the pain. Traditional tag configuration — hardcoded in ladder logic comments, scattered across HMI screens, buried in proprietary configuration tools — doesn't scale.

The solution that's gaining traction in modern IIoT deployments is declarative, JSON-based tag configuration. Instead of configuring your data collection logic in opaque proprietary formats, you define your device's entire tag map as a structured JSON document. This approach brings version control, template reuse, and automated validation to the industrial data layer.

In this guide, we'll walk through the architecture of a production-grade JSON tag configuration system, drawing from real patterns used in industrial edge gateways connecting to Allen-Bradley Micro800 PLCs via EtherNet/IP and to various devices via Modbus RTU and TCP.

JSON-based PLC tag configuration for IIoT

Why JSON for PLC Tag Configuration?

The traditional approach to configuring PLC data collection involves vendor-specific tools: RSLinx for Allen-Bradley, TIA Portal for Siemens, or proprietary gateway configurators. These tools work, but they create several problems at scale:

  • No version control. You can't git diff a proprietary binary config file.
  • No templating. When you deploy the same machine type across 50 sites, you're manually recreating the same configuration 50 times.
  • No validation. Typos in register addresses don't surface until runtime.
  • No automation. You can't script the generation of configurations from a master device database.

JSON solves all of these. A tag configuration becomes a text file that can be:

  • Stored in Git with full change history
  • Templated per device type (one JSON per machine model)
  • Validated against a schema before deployment
  • Generated programmatically from engineering databases

Anatomy of a Tag Configuration Document

A well-structured PLC tag configuration document needs to capture several layers of information:

Device-Level Metadata

Every configuration file should identify the device type it applies to, carry a version string for change tracking, and specify the protocol:

{
"device_type": 1010,
"version": "a3f7b2c",
"name": "Continuous Blender Model X",
"protocol": "ethernet-ip",
"plctags": [ ... ]
}

The device_type field is a numeric identifier that maps to a specific machine model. When an edge gateway auto-detects a PLC (by reading a known register), it uses this type ID to look up the correct configuration file. The version field — ideally a short Git hash — lets you track which configuration version is running on each gateway in the field.

For Modbus devices, you'd also include protocol-specific parameters:

{
"device_type": 5000,
"version": "b8e1d4a",
"name": "Temperature Control Unit",
"protocol": "modbus-rtu",
"base_addr": 48,
"baud": 9600,
"parity": "even",
"data_bits": 8,
"stop_bits": 1,
"byte_timeout": 4,
"resp_timeout": 100,
"plctags": [ ... ]
}

Notice the serial link parameters are part of the same document. This is deliberate — you want a single source of truth for "how to talk to this device and what to read from it."

Tag Definitions: The Core Data Model

Each tag in the configuration represents a single data point you want to collect from the PLC. A complete tag definition captures:

{
"name": "barrel_zone1_temp",
"id": 42,
"type": "float",
"ecount": 2,
"sindex": 0,
"interval": 5,
"compare": true,
"do_not_batch": false
}

Let's break down each field:

name — A human-readable identifier for the tag. For EtherNet/IP (CIP) devices, this is the actual PLC tag name. For Modbus, it's a descriptive label since Modbus uses numeric addresses.

id — A numeric identifier used in the wire protocol when transmitting data to the cloud. Using compact integer IDs instead of string names dramatically reduces payload sizes — critical when you're sending telemetry over cellular connections.

type — The data type of the register value. Common types include:

TypeSizeRangeUse Case
bool1 byte0 or 1Alarm states, run/stop status
int81 byte-128 to 127Small counters, mode selectors
uint81 byte0 to 255Status codes, alarm bytes
int162 bytes-32,768 to 32,767Temperature (×10), pressure
uint162 bytes0 to 65,535RPM, flow rate, raw ADC values
int324 bytes±2.1 billionProduction counters, energy
uint324 bytes0 to 4.2 billionLifetime counters, timestamps
float4 bytesIEEE 754Temperature, weight, setpoints

ecount (element count) — How many consecutive elements to read. For a single register, this is 1. For a 32-bit float stored across two Modbus registers, this is 2. For an array of 10 temperature readings, this is 10.

sindex (start index) — The starting element index for array reads. Combined with ecount, this lets you read slices of PLC arrays without pulling the entire array.

interval — How often (in seconds) to poll this tag. This is where you make intelligent decisions about bandwidth:

  • 1 second: Critical alarms, emergency stops, safety interlocks
  • 5 seconds: Process temperatures, pressures, flows
  • 30 seconds: Setpoints, mode selectors (change infrequently)
  • 300 seconds: Configuration parameters, serial numbers

compare — When true, the gateway compares each new reading against the previous value and only transmits if the value changed. This is the single most impactful optimization for reducing bandwidth and cloud ingestion costs.

do_not_batch — When true, the value is transmitted immediately rather than being accumulated into a batch payload. Use this for critical alarms that need sub-second cloud visibility.

Modbus Address Conventions

For Modbus devices, each tag also carries an addr field that encodes both the register address and the function code:

{
"name": "process_temp",
"id": 10,
"addr": 400100,
"type": "float",
"ecount": 2,
"interval": 5,
"compare": true
}

The address convention follows a well-established pattern:

Address RangeModbus Function CodeRegister Type
0 – 65,536FC 01Coils (read/write)
100,000 – 165,536FC 02Discrete Inputs (read)
300,000 – 365,536FC 04Input Registers (read)
400,000 – 465,536FC 03Holding Registers (R/W)

So addr: 400100 means "holding register at address 100, read via function code 3." This convention eliminates ambiguity about which Modbus function to use — the address itself encodes it.

Why this matters: A common source of bugs in Modbus deployments is using the wrong function code. Someone configures a tag to read address 100 with FC 03 when the device exposes it as an input register (FC 04). With the address convention above, the function code is implicit and unambiguous.

Advanced Patterns: Calculated and Dependent Tags

Simple register reads cover 80% of use cases. But industrial devices often pack multiple boolean values into a single 16-bit alarm word, or have tags whose values only matter when a parent tag changes.

Calculated Tags: Extracting Bits from Alarm Words

Many PLCs pack 16 individual alarm flags into a single uint16 register. Rather than reading 16 separate coils, you read one register and extract the bits:

{
"name": "alarm_word_1",
"id": 50,
"addr": 400200,
"type": "uint16",
"ecount": 1,
"interval": 1,
"compare": true,
"calculated": [
{
"name": "high_temp_alarm",
"id": 51,
"type": "bool",
"shift": 0,
"mask": 1
},
{
"name": "low_pressure_alarm",
"id": 52,
"type": "bool",
"shift": 1,
"mask": 1
},
{
"name": "motor_overload",
"id": 53,
"type": "bool",
"shift": 2,
"mask": 1
}
]
}

When alarm_word_1 is read, the gateway automatically:

  1. Reads the raw uint16 value
  2. For each calculated tag, applies the right-shift and mask to extract the bit
  3. Compares the extracted boolean against its previous value
  4. Only transmits if the bit actually changed

This is vastly more efficient than polling 16 individual coils — one Modbus read instead of 16, with identical semantic output.

Dependent Tags: Event-Driven Secondary Reads

Some tags only need to be read when a related tag changes. For example, you might have a machine_state register that changes between IDLE, RUNNING, and FAULT. When it changes, you want to immediately read a block of diagnostic registers — but you don't want to poll those diagnostics every cycle when the machine state is stable.

{
"name": "machine_state",
"id": 100,
"addr": 400001,
"type": "uint16",
"ecount": 1,
"interval": 1,
"compare": true,
"dependents": [
{
"name": "fault_code",
"id": 101,
"addr": 400010,
"type": "uint16",
"ecount": 1,
"interval": 60
},
{
"name": "fault_timestamp",
"id": 102,
"addr": 400011,
"type": "uint32",
"ecount": 2,
"interval": 60
}
]
}

When machine_state changes, the gateway forces an immediate read of all dependent tags, regardless of their normal polling interval. This gives you:

  • Low latency on state transitions — fault diagnostics arrive within 1 second of the fault occurring
  • Low bandwidth during steady state — diagnostic registers are only polled every 60 seconds when nothing is happening

Contiguous Register Optimization

One of the most impactful optimizations in Modbus data collection is contiguous register grouping. Instead of making separate Modbus read requests for each tag, the gateway sorts tags by address and groups adjacent registers into single bulk reads.

Consider these tags:

[
{ "name": "temp_1", "addr": 400100, "ecount": 1 },
{ "name": "temp_2", "addr": 400101, "ecount": 1 },
{ "name": "temp_3", "addr": 400102, "ecount": 1 },
{ "name": "pressure", "addr": 400103, "ecount": 2 }
]

A naive implementation makes four separate Modbus requests. An optimized one makes one request: read 5 registers starting at address 400100. The response contains all four values, which are dispatched to the correct tag definitions.

For this optimization to work, the configuration system must:

  1. Sort tags by address at load time, not at runtime
  2. Validate that function codes match — you can't group a coil read (FC 01) with a holding register read (FC 03)
  3. Respect maximum packet sizes — Modbus TCP allows up to 125 registers per read; some devices are more restrictive
  4. Respect polling intervals — only group tags that share the same polling interval

The performance difference is dramatic. A typical PLC with 50 Modbus tags might require 50 individual reads (50 × ~10ms = 500ms per cycle) or 5 grouped reads (5 × ~10ms = 50ms per cycle). That's a 10× improvement in polling speed.

IEEE 754 Float Handling: The Register Order Problem

Reading 32-bit floating-point values over Modbus is notoriously tricky because the Modbus specification doesn't define register byte ordering for multi-register values. A float spans two 16-bit registers, and different PLCs may store them in different orders:

  • Big-endian (AB CD): Register N contains the high word, N+1 the low word
  • Little-endian (CD AB): Register N contains the low word, N+1 the high word
  • Mid-endian (BA DC or DC BA): Each word's bytes are swapped

Your tag configuration should support specifying the byte order, or at least document which convention your gateway assumes. Most libraries (libmodbus, for example) provide helper functions like modbus_get_float() that assume big-endian by default — but always verify against your specific PLC.

Pro tip: When commissioning a new device, read a register where you know the expected value (e.g., a temperature setpoint showing 72.0°F on the HMI). If the gateway reads 72.0, your byte order is correct. If it reads 2.388e-38 or 1.23e+12, you have a byte-order mismatch.

Binary vs. JSON Telemetry Encoding

Once you've collected your tag values, you need to transmit them. Your configuration should support both JSON and binary encoding, with the choice driven by bandwidth constraints:

JSON encoding is human-readable and debuggable:

{
"groups": [{
"ts": 1709500800,
"device_type": 1010,
"serial_number": 85432,
"values": [
{ "id": 42, "values": [72.3] },
{ "id": 43, "values": [true] }
]
}]
}

Binary encoding is 3-5× smaller. A typical binary frame packs:

  • 1-byte header marker
  • 4-byte group count
  • Per group: 4-byte timestamp, 2-byte device type, 4-byte serial number, 4-byte value count
  • Per value: 2-byte tag ID, 1-byte status, 1-byte value count, 1-byte value size, then raw value bytes

A batch that's 2,000 bytes in JSON might be 400 bytes in binary. Over a cellular connection billed per megabyte, that savings compounds fast.

Putting It All Together: Configuration Lifecycle

A production deployment follows this lifecycle:

  1. Template creation: For each machine model, create a JSON tag configuration. Store it in Git.
  2. Deployment: Push configurations to edge gateways via your device management platform. The gateway monitors the config file and reloads automatically when it changes.
  3. Auto-detection: When the gateway starts, it queries the PLC for its device type (a known register). It then matches the type to the correct configuration file.
  4. Validation: At load time, validate register addresses (no duplicates, valid ranges), data types, and interval values. Reject invalid configs before they cause runtime errors.
  5. Runtime: The gateway polls tags according to their configured intervals, applies change detection, groups contiguous registers, and batches values for transmission.

How machineCDN Handles Tag Configuration

machineCDN's edge gateway uses this exact pattern — JSON-based device templates that are automatically selected based on PLC auto-detection. Each machine type in a plastics manufacturing facility (blenders, dryers, granulators, chillers, TCUs) has its own configuration template with pre-mapped tags, optimized polling intervals, and calculated alarm decomposition.

When a new machine is connected, the gateway detects the PLC type, loads the matching template, and starts collecting data — typically in under 30 seconds with zero manual configuration. For plants running 20+ machines across 5 different models, this eliminates weeks of commissioning time.

Common Pitfalls

1. Overlapping addresses. Two tags pointing to the same register with different IDs will cause confusion in your data pipeline. Validate for uniqueness at load time.

2. Wrong element count for floats. A 32-bit float on Modbus requires ecount: 2 (two 16-bit registers). Setting ecount: 1 gives you garbage data.

3. Polling too fast on serial links. Modbus RTU over RS-485 at 9600 baud can handle roughly 10-15 register reads per second. If you configure 50 tags at 1-second intervals, you'll never keep up. Budget your polling rate against your link speed.

4. Missing change detection on high-volume tags. Without compare: true, every reading gets transmitted. For a tag polled every second, that's 86,400 data points per day — even if the value never changed.

5. Batch timeout too long. If your batch timeout is 60 seconds but an alarm fires, it won't reach the cloud for up to a minute unless that alarm tag has do_not_batch: true.

Conclusion

JSON-based tag configuration isn't just a nice-to-have — it's a fundamental enabler for scaling IIoT deployments. It brings software engineering best practices (version control, templating, validation, automation) to a domain that has traditionally relied on manual, vendor-specific tooling.

The key design principles are:

  • One file per device type with version tracking
  • Rich tag metadata covering data types, intervals, and delivery modes
  • Hierarchical relationships for calculated and dependent tags
  • Protocol-aware addressing that encodes function codes implicitly
  • Contiguous register grouping for optimal Modbus performance

Get this foundation right, and you'll spend your time analyzing machine data instead of debugging data collection.

Machine Changeover Time Tracking with IIoT: How to Cut Setup Time and Boost OEE

· 8 min read
MachineCDN Team
Industrial IoT Experts

Changeover time — the gap between the last good part of one run and the first good part of the next — is one of manufacturing's most persistent productivity killers. In most plants, changeovers consume 10-30% of available production time. Worse, most manufacturers don't actually measure changeover time accurately. They estimate. They round up. They accept "about two hours" when the actual time ranges from 45 minutes to four hours depending on the shift, the operator, and the product.

MachineCDN vs Braincube: AI Manufacturing Platform Comparison for Process Optimization

· 8 min read
MachineCDN Team
Industrial IoT Experts

Braincube has built a reputation as an AI-powered manufacturing optimization platform, particularly strong in process industries like chemicals, metals, paper, and food & beverage. MachineCDN takes a different approach — cloud-native IIoT with edge connectivity, real-time monitoring, and AI-powered predictive maintenance designed for fast deployment across any manufacturing vertical. If you're evaluating both, here's an honest comparison of what each delivers.

MachineCDN vs Savigent: IIoT Analytics Platform vs Manufacturing Execution System

· 8 min read
MachineCDN Team
Industrial IoT Experts

When manufacturing engineers evaluate platforms to digitize their factory floor, two very different approaches emerge: IIoT analytics platforms like MachineCDN that connect directly to your machines, and Manufacturing Execution Systems (MES) like Savigent that orchestrate workflows across your production process. Understanding the difference is critical before you commit budget and engineering time.

MachineCDN vs Wonderware (Schneider Electric AVEVA): Legacy SCADA vs Cloud-Native IIoT for Manufacturing

· 8 min read
MachineCDN Team
Industrial IoT Experts

Wonderware has been a fixture on factory floors since the 1980s. Now part of Schneider Electric's AVEVA portfolio, it remains one of the most widely deployed SCADA/HMI platforms in manufacturing. But the manufacturing world has shifted. Cloud-native IIoT platforms like MachineCDN are challenging the assumptions that made Wonderware dominant — and many plants are discovering that the gap between legacy SCADA and modern IIoT is wider than they expected.

The Maintenance Maturity Model: From Reactive to Prescriptive — Where Does Your Plant Actually Stand?

· 10 min read
MachineCDN Team
Industrial IoT Experts

Every manufacturing plant claims to be "doing predictive maintenance." In reality, most are somewhere between reactive and preventive, with a few vibration sensors they call "predictive" because a vendor told them to.

This isn't a criticism — it's a diagnostic. Understanding where you actually are on the maintenance maturity model is the first step to getting where you need to be. And more importantly, understanding which level makes sense for your plant, because not every operation needs to reach the peak.

Modbus Float Encoding: How to Correctly Read IEEE 754 Values from Industrial PLCs [2026]

· 11 min read

If you've spent any time integrating PLCs with an IIoT platform, you've encountered the moment: you read a temperature register that should show 72.5°F, but instead you get 1,118,044,160. Or worse — NaN. Or a negative number that makes zero physical sense.

Welcome to the Modbus float encoding problem. It's the #1 source of confusion in industrial data integration, and it trips up experienced engineers just as often as beginners.

This guide goes deep on how 32-bit floating-point values are actually stored and transmitted over Modbus — covering register pairing, word-swap variants, byte ordering, and the practical techniques that production IIoT systems use to get correct readings from heterogeneous equipment fleets.

Why Modbus and Floats Don't Play Nicely Together

The original Modbus specification (1979) defined only 16-bit registers. Each holding register (4xxxx) or input register (3xxxx) stores exactly one unsigned 16-bit word — values from 0 to 65,535.

But modern PLCs need to represent temperatures like 215.7°F, flow rates like 3.847 GPM, and pressures like 127.42 PSI. A 16-bit integer can't hold these values with the precision operators need.

The solution: pack an IEEE 754 single-precision float (32 bits) across two consecutive Modbus registers. Simple enough in theory. In practice, it's a minefield.

The IEEE 754 Layout

A 32-bit float uses this bit structure:

Bit:  31  30..23   22..0
S EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM
│ │ └── Mantissa (23 bits)
│ └── Exponent (8 bits, biased by 127)
└── Sign (1 bit: 0=positive, 1=negative)

The float value 72.5 encodes as 0x42910000:

  • Sign: 0 (positive)
  • Exponent: 10000101 (133 - 127 = 6)
  • Mantissa: 00100010000000000000000

That 32-bit value needs to be split across two 16-bit registers. Here's where the problems start.

The Four Word-Order Variants

Different PLC manufacturers split 32-bit floats into register pairs using different byte and word ordering. There are four possible arrangements, and encountering all four in a single plant is common:

Variant 1: Big-Endian (AB CD) — "Network Order"

The most intuitive layout. The high word occupies the lower register address.

Register N  :  0x4291  (bytes A, B)
Register N+1: 0x0000 (bytes C, D)

Reconstruct: (Register_N << 16) | Register_N+10x42910000 → 72.5

Used by: Many Allen-Bradley/Rockwell PLCs, Schneider Modicon M340/M580, some Siemens devices.

Variant 2: Little-Endian Word Swap (CD AB)

The low word comes first. This is surprisingly common.

Register N  :  0x0000  (bytes C, D)
Register N+1: 0x4291 (bytes A, B)

Reconstruct: (Register_N+1 << 16) | Register_N0x42910000 → 72.5

Used by: Many Modbus TCP devices, Conch controls, various Asian-manufactured PLCs.

Variant 3: Byte-Swapped Big-Endian (BA DC)

Each 16-bit word has its bytes reversed, but word order is normal.

Register N  :  0x9142  (bytes B, A)
Register N+1: 0x0000 (bytes D, C)

This requires swapping bytes within each word before combining.

Used by: Some older Emerson/Fisher devices, certain Yokogawa controllers.

Variant 4: Byte-Swapped Little-Endian (DC BA)

The least intuitive: both word order and byte order are reversed.

Register N  :  0x0000  (bytes D, C)
Register N+1: 0x9142 (bytes B, A)

Used by: Rare, but you'll find it in some legacy Fuji and Honeywell equipment.

How Production IIoT Systems Handle This

In a real manufacturing environment, you don't get to choose which word order your equipment uses. A single plant might have:

  • TCU (Temperature Control Units) using Modbus RTU at 9600 baud, storing floats in registers 404000-404056 with big-endian word order
  • Portable chillers on Modbus TCP port 502, using 16-bit integers (no float encoding needed)
  • Batch blenders speaking EtherNet/IP natively, where float handling is built into the CIP protocol
  • Dryers with Modbus TCP and CD-AB word swapping

A well-designed edge gateway handles this with per-device configuration. The key insight: float decoding is a device-level property, not a global setting. Each equipment type gets its own configuration that specifies:

  1. Protocol (Modbus RTU, Modbus TCP, or EtherNet/IP)
  2. Register address (which pair of registers holds the float)
  3. Element count — set to 2 for a 32-bit float spanning two registers
  4. Data type — explicitly declared as float vs. int16 vs. uint32

Here's a generic configuration example for a temperature control unit reading float values over Modbus RTU:

{
"protocol": "modbus-rtu",
"tags": [
{
"name": "Delivery Temperature",
"register": 4002,
"type": "float",
"element_count": 2,
"poll_interval_sec": 60
},
{
"name": "Mold Temperature",
"register": 4004,
"type": "float",
"element_count": 2,
"poll_interval_sec": 60
},
{
"name": "Flow Rate",
"register": 4008,
"type": "float",
"element_count": 2,
"poll_interval_sec": 60
}
]
}

Notice the element_count: 2. This tells the gateway: "read two consecutive registers starting at this address, then combine them into a single 32-bit float." Getting this wrong is the most common source of incorrect readings.

The modbus_get_float() Trap

If you're using libmodbus (the most common C library for Modbus), you'll encounter modbus_get_float() and its variants:

  • modbus_get_float_abcd() — big-endian (most standard)
  • modbus_get_float_dcba() — fully reversed
  • modbus_get_float_badc() — byte-swapped, word-normal
  • modbus_get_float_cdab() — word-swapped, byte-normal

The default modbus_get_float() function uses CDAB ordering (word-swapped). This catches many engineers off guard — they read two registers, call modbus_get_float(), and get garbage because their PLC uses ABCD ordering.

Rule of thumb: Always test with a known value. Write 72.5 to a register pair in your PLC, read both registers as raw uint16 values, and observe which bytes are where. Then select the appropriate decode function.

Practical Decoding in C

Here's how you'd manually decode a float from two Modbus registers, handling the common big-endian case:

// Big-endian (ABCD): high word in register[0], low word in register[1]
float decode_float_be(uint16_t reg_high, uint16_t reg_low) {
uint32_t combined = ((uint32_t)reg_high << 16) | (uint32_t)reg_low;
float result;
memcpy(&result, &combined, sizeof(float));
return result;
}

// Word-swapped (CDAB): low word in register[0], high word in register[1]
float decode_float_ws(uint16_t reg_low, uint16_t reg_high) {
uint32_t combined = ((uint32_t)reg_high << 16) | (uint32_t)reg_low;
float result;
memcpy(&result, &combined, sizeof(float));
return result;
}

Never use pointer casting (*(float*)&combined). It violates strict aliasing rules and can produce incorrect results on optimizing compilers. Always use memcpy.

Element Count and Register Math

One subtle but critical detail: when you configure a tag to read a float, the element count tells the gateway how many 16-bit registers to request in a single Modbus transaction.

For a single float:

  • Element count = 2 (two 16-bit registers = 32 bits)
  • Read function code 3 (holding registers) or 4 (input registers)
  • The response contains 4 bytes of data

For an array of 8 floats (e.g., reading recipe values from a batch blender):

  • Element count = 16 (8 floats × 2 registers each)
  • Single Modbus read request for 16 consecutive registers
  • Far more efficient than 8 separate read requests

This is where contiguous register optimization matters. If you have tags at registers 4000, 4002, 4004, 4006, 4008 — all 2-element floats — a smart gateway combines them into a single Modbus read of 10 registers instead of 5 separate reads. This reduces bus traffic by 60-80% on RTU networks where every transaction costs 5-20ms of serial turnaround time.

Modbus RTU vs TCP: Float Handling Differences

RTU (Serial)

Serial Modbus has strict timing requirements. The inter-frame gap (3.5 character times of silence) separates messages. At 9600 baud with 8N1 encoding:

  • 1 character = 11 bits (start + 8 data + parity + stop)
  • 1 character time = 11/9600 = 1.146ms
  • 3.5 character silence = ~4ms

When reading float values over RTU, response timeout configuration matters. A typical setup:

Baud:             9600
Parity: None
Data bits: 8
Stop bits: 1
Byte timeout: 4ms (gap between consecutive bytes)
Response timeout: 100ms (total time to receive response)

If your byte timeout is too tight, the response may be split into two frames, and the second register of your float pair gets dropped. If you're seeing correct first-register values but garbage in the combined float, increase byte timeout to 5-8ms.

TCP (Ethernet)

Modbus TCP eliminates timing issues but introduces transaction ID management. Each request gets a transaction ID that the slave echoes back. For float reads, the process is identical — request 2 registers, get 4 bytes back — but the framing is handled by TCP, so there's no byte-timeout concern.

The default Modbus TCP port is 502. Some devices use non-standard ports; always verify with the equipment manual.

Common Pitfalls and Troubleshooting

1. Reading Zero Where You Expect a Float

Symptom: Register pair returns 0x0000 0x0000 → 0.0

Likely cause: Wrong register address. Remember the Modbus address convention:

  • Addresses 400001-465536 use function code 3 (read holding registers)
  • Addresses 300001-365536 use function code 4 (read input registers)
  • The actual register number = address - 400001 (for holding) or address - 300001 (for input)

A tag configured at address 404000 maps to holding register 4000 (function code 3). If you accidentally use function code 4, you're reading input register 4000 instead — a completely different value.

2. Reading Extreme Values

Symptom: You get values like 4.5e+28 or -3.2e-15

Likely cause: Wrong word order. You're combining registers in the wrong sequence. Try swapping the two registers and recomputing.

3. Getting NaN or Inf

Symptom: NaN (0x7FC00000) or Inf (0x7F800000)

Likely causes:

  • Word-order mismatch producing an exponent field of all 1s
  • Reading a register that doesn't actually contain a float (it's a raw integer)
  • Sensor disconnected — some PLCs write NaN to indicate a failed sensor

4. Values That Are Close But Off By a Factor

Symptom: You read 7250.0 instead of 72.5

Likely cause: The PLC stores values as scaled integers, not floats. Many older PLCs store temperature as an integer × 100 (so 72.5°F = 7250). Check the PLC documentation for scaling factors. This is especially common with Modbus devices that use single registers (element count = 1) for process values.

5. Intermittent Corrupt Readings

Symptom: 99% of readings are correct, but occasionally you get wild values.

Likely cause: On Modbus RTU, this is usually CRC errors that weren't caught, or electrical noise on the RS-485 bus. Add retry logic — read the registers, if the float value is outside physical bounds (e.g., temperature > 500°F for a plastics process), retry up to 3 times before logging an error.

Real-World Benchmarks

In production IIoT deployments monitoring plastics manufacturing equipment, typical float-read performance:

ProtocolFloat Read TimeRegisters per RequestEffective Throughput
Modbus RTU @ 960015-25ms2 (single float)~40 floats/sec
Modbus RTU @ 960030-45ms50 (contiguous block)~1,000 values/sec
Modbus TCP2-5ms2 (single float)~200 floats/sec
Modbus TCP3-8ms125 (max block)~15,000 values/sec
EtherNet/IP1-3msN/A (native types)~5,000+ tags/sec

The lesson: Modbus RTU float reads are slow individually but scale well with contiguous reads. If you have 30 float tags spread across non-contiguous addresses, it's 30 × 20ms = 600ms per polling cycle. Group your tags by contiguous address blocks to minimize transactions.

Best Practices for Production Systems

  1. Declare types explicitly in configuration. Never auto-detect float vs. integer — always specify the data type per tag.

  2. Use element count = 2 for floats. This is the most common source of misconfiguration. A float is 2 registers, always.

  3. Test with known values during commissioning. Before going live, write a known float (like 123.456) to the PLC and verify the IIoT platform reads it correctly.

  4. Document word order per device type. Build a device-specific configuration library. A TrueTemp TCU uses ABCD, a GP Chiller uses raw int16 — capture this per equipment model.

  5. Implement bounds checking. If a temperature reading suddenly shows 10,000°F, that's not a process event — it's a decode error. Log it, don't alert on it.

  6. Add retry logic for RTU reads. Serial networks are noisy. Retry failed reads up to 3 times before reporting an error status.

  7. Batch contiguous registers. Instead of reading registers 4000-4001, then 4002-4003, then 4004-4005 as three separate transactions, read 4000-4005 as a single 6-register request.

How machineCDN Handles Float Encoding

machineCDN's edge gateway is built to handle the float encoding problem across heterogeneous equipment fleets. Each device type gets a configuration profile that explicitly declares register addresses, data types, element counts, and polling intervals — eliminating the guesswork that causes most float decoding failures.

The platform supports Modbus RTU, Modbus TCP, and EtherNet/IP natively, with automatic protocol detection during initial device discovery. When a new PLC is connected, the gateway attempts EtherNet/IP first (reading the device type tag directly), then falls back to Modbus TCP on port 502. This dual-protocol detection means a single gateway can service mixed equipment floors without manual protocol configuration.

For plastics manufacturers running TCUs, chillers, blenders, dryers, and conveying systems, machineCDN provides pre-built device profiles that include correct register maps, data types, and word-order settings — so the float encoding problem is solved before commissioning begins.


Getting float encoding right is the foundation of trustworthy IIoT data. Every OEE calculation, every alarm threshold, every predictive maintenance model depends on correct readings from the plant floor. Invest the time to verify your decoding — the downstream value is enormous.

MQTT Last Will and Testament for Industrial Device Health Monitoring [2026]

· 12 min read

MQTT Last Will and Testament for Industrial Device Health

In industrial environments, knowing that a device is offline is just as important as knowing what it reports when it's online. A temperature sensor that silently stops publishing doesn't trigger alarms — it creates a blind spot. And in manufacturing, blind spots kill uptime.

MQTT's Last Will and Testament (LWT) mechanism solves this problem at the protocol level. When properly implemented alongside birth certificates, status heartbeats, and connection watchdogs, LWT transforms MQTT from a simple pub/sub pipe into a self-diagnosing industrial nervous system.

This guide covers the practical engineering behind LWT in industrial deployments — not just the theory, but the real-world patterns that survive noisy factory networks.

MQTT QoS Levels for Industrial Telemetry: Choosing the Right Delivery Guarantee [2026]

· 11 min read

When an edge gateway publishes a temperature reading from a plastics extruder running at 230°C, does it matter if that message arrives exactly once, at least once, or possibly not at all? The answer depends on what you're doing with the data — and getting it wrong can mean either lost production insights or a network drowning in redundant traffic.

MQTT's Quality of Service (QoS) levels are one of the most misunderstood aspects of industrial IoT deployments. Most engineers default to QoS 1 for everything, which is rarely optimal. This guide breaks down each level with real industrial scenarios, bandwidth math, and patterns that actually work on factory floors where cellular links drop and PLCs generate thousands of data points per second.