Skip to main content

PLC Alarm Word Decoding: How to Extract Bit-Level Alarm States for IIoT Monitoring [2026]

· 12 min read

Most plant engineers understand alarms at the HMI level — a red indicator lights up, a buzzer sounds, someone walks over to the machine. But when you connect PLCs to an IIoT platform for remote monitoring, you hit a fundamental data representation problem: PLCs don't store alarms as individual boolean values. They pack them into 16-bit registers called alarm words.

A single uint16 register can encode 16 different alarm conditions. A chiller with 10 refrigeration circuits might have 30+ alarm word registers — encoding hundreds of individual alarm states. If your IIoT platform doesn't understand this encoding, you'll either miss critical alarms or drown in meaningless raw register values.

This guide explains how alarm word decoding works at the edge, why it matters for reliable remote monitoring, and how to implement it without flooding your cloud platform with unnecessary data.

What Is an Alarm Word?

An alarm word is a 16-bit unsigned integer register where each bit represents a distinct alarm condition. When a bit is 1, that alarm is active. When it's 0, the alarm is clear.

For example, a chiller might expose this alarm word at Modbus register 300162:

BitAlarm Condition
0Low refrigerant pressure
1High discharge pressure
2Compressor overload
3Flow switch fault
4Freeze protection
5High condenser pressure
6Oil pressure fault
7Phase reversal
8–15Reserved / additional alarms

If the register reads 0x0015 (binary 0000000000010101), bits 0, 2, and 4 are set — meaning low refrigerant pressure, compressor overload, and freeze protection are all active simultaneously.

Why Pack Alarms Into Words?

PLC memory is finite and registers are precious. A PLC with 16 alarm conditions could dedicate 16 individual coils (1-bit registers), but that consumes 16 addresses from the register map. An alarm word encodes the same information in a single register.

More importantly, alarm words let the PLC update all related alarms atomically. When a fault cascade occurs (low refrigerant triggers high discharge, which triggers compressor overload), the PLC can update a single register in one scan cycle rather than updating 3 separate coils across multiple cycles. This eliminates race conditions where an IIoT gateway might read some alarms mid-update.

The Shift-and-Mask Extraction Pattern

To extract a single alarm bit from an alarm word, you need two operations:

  1. Right-shift the register value by the bit position
  2. AND-mask the result with 0x01 to isolate the single bit
alarm_active = (alarm_word >> bit_position) & mask

For the chiller example above, extracting the "Compressor overload" alarm (bit 2):

alarm_word = 0x0015        // binary: 0000000000010101
shifted = 0x0015 >> 2 // binary: 0000000000000101 = 0x0005
masked = 0x0005 & 0x01 // binary: 0000000000000001 = 1 (ACTIVE)

The mask value is typically 0x01 for single-bit extraction, but can be wider for multi-bit status fields. A 2-bit machine state field (e.g., bits 4–5 encoding 00=off, 01=idle, 10=running, 11=fault) would use a mask of 0x03 and a shift of 4:

machine_state = (status_word >> 4) & 0x03

Calculated Tags: Edge-Side Decoding

The key architectural decision is where to perform alarm word decoding: at the edge or in the cloud.

Decoding at the edge is almost always the right answer:

  • Bandwidth reduction: Instead of transmitting a raw uint16 value that the cloud must decode, the edge sends individual boolean alarm tags. But more importantly, it only sends changed alarm bits — see the next section.
  • Latency: A compressor overload alarm decoded at the edge can trigger a local alert or safety action in milliseconds. Sending it to the cloud for decoding adds round-trip latency.
  • Simplicity: Cloud analytics engineers shouldn't need to understand PLC register-level bit packing. They should see named alarm tags like circuit_1_compressor_overload = true.

How Calculated Tags Work

A well-designed edge gateway supports a concept called "calculated tags" — virtual tags whose values are computed from a parent tag using mathematical operations. For alarm word decoding, the configuration looks conceptually like this:

Parent tag (the alarm word):

  • Register: 300162
  • Type: uint16
  • Poll interval: 1 second
  • Compare: enabled (only process on change)

Calculated child tags:

  • low_refrigerant — shift: 0, mask: 0x01, type: bool
  • high_discharge — shift: 1, mask: 0x01, type: bool
  • compressor_overload — shift: 2, mask: 0x01, type: bool
  • flow_switch_fault — shift: 3, mask: 0x01, type: bool
  • freeze_protection — shift: 4, mask: 0x01, type: bool

When the gateway reads a new value from the alarm word register:

  1. It compares the raw uint16 value to the previous reading
  2. If unchanged: Nothing happens — no child tags are evaluated
  3. If changed: It evaluates each calculated tag by applying its shift/mask operation
  4. For each calculated tag, it compares the new boolean value to the previous boolean value
  5. Only changed child tags are delivered to the data pipeline

This two-level comparison (parent changed? → child changed?) is critical for efficiency. A chiller running normally might trigger its alarm word register to flip a single bit every few hours. Without change detection, the gateway would transmit 16 alarm tags every second — 1.38 million unnecessary data points per day. With change detection: maybe 10 data points per day.

Change Detection vs Time-Interval Delivery

Industrial data falls into two categories that demand different delivery strategies:

Alarm Data: Change-Based Delivery

Alarm states are inherently event-driven. You care when an alarm transitions from clear to active (or back). You don't care that the alarm word was 0x0000 for the 86,400th consecutive second.

Change-based delivery means:

  • Read the alarm word every poll cycle (1 second)
  • Compare to the previous value
  • Only transmit when it differs

This pattern applies to:

  • Alarm words (uint16 → multiple booleans)
  • Machine status registers (running/stopped/fault)
  • Digital inputs (door switches, safety interlocks)
  • Setpoint registers (only change when operator adjusts)

Process Data: Time-Interval Delivery

Temperatures, pressures, flow rates, and other analog values change continuously. Here, you want a regular time series regardless of whether the value moved:

  • Read every N seconds (typically 10–60 seconds for temperatures)
  • Always transmit, regardless of comparison
  • This produces a continuous time-series record for trending and analytics

The Hourly Full Refresh

Even with change-based delivery, a well-designed system performs a full refresh periodically — re-transmitting all tag values regardless of change status. This serves two purposes:

  1. Catching missed updates: If a network hiccup caused a transition to be lost, the full refresh will resynchronize
  2. Proof of life: A cloud platform receiving regular full updates from an edge gateway knows the connection and PLC are both healthy

A common pattern is to reset the "last known value" for all tags every hour, forcing the next poll cycle to treat every tag as new and transmit everything.

Dependent Tags: Triggering Reads on Change

Alarm words unlock another powerful pattern: dependent tags. When a parent tag changes value, the gateway can trigger immediate reads of related tags — even if those dependent tags aren't scheduled for their normal poll cycle.

Real-world example: A chiller's alarm word (register 300162) changes, indicating a new fault. The gateway has been polling temperature registers (300001–300161) on a 60-second interval. But now there's an alarm — the operator needs to see the current temperatures immediately, not in 58 seconds.

Dependent tag configuration:

  • Parent: Alarm word (300162), compare: enabled
  • Dependents: Temperature registers (300001–300161), force-read on parent change

When the alarm word transitions, the gateway:

  1. Detects the parent change
  2. Finalizes the current data batch (to avoid mixing timestamps)
  3. Force-reads all dependent tags immediately
  4. Delivers the alarm + contextual temperatures in rapid succession

The operator sees the alarm AND the temperatures that caused it — arriving within the same second, even though temperatures normally only update once per minute.

Batching Alarm Data: Do Not Batch Critical Alarms

Most IIoT edge gateways batch data — collecting multiple tag updates into a single payload before transmitting. This reduces message count and overhead for routine telemetry. Typical batch parameters:

  • Batch size: 4,000 bytes
  • Batch timeout: 60 seconds
  • Whichever limit hits first triggers transmission

But alarm data should bypass the batch. When a compressor overload alarm fires, you don't want it sitting in a batch buffer for up to 60 seconds before transmission. Critical alarm tags should be marked as "do not batch" and delivered immediately as standalone messages.

This creates a two-tier delivery architecture:

  • Batched data: Routine telemetry (temperatures, pressures, flow rates) — efficient bulk delivery
  • Unbatched data: Alarms, status changes, safety interlocks — immediate single-message delivery

The tradeoff is slightly higher message overhead for alarms. But an alarm that arrives 60 seconds late is an alarm that might as well not exist.

Scaling to Multi-Circuit Equipment

Industrial equipment like central chillers, multi-zone HVAC systems, and injection molding machines with multiple heating zones often have repetitive register structures — the same set of tags repeated for each circuit, zone, or cavity.

A 10-circuit chiller might have:

  • 16 temperature/pressure tags per circuit (registers 300001–300160)
  • 3 alarm words per circuit (registers 300163–300192)
  • 1 global alarm word (register 300162)
  • 2 system status coils (registers 1, 100001–100003)
  • Configuration registers (holding registers 400200–400525)

That's 192 input registers + 30 alarm words + 4 coils + 330 holding registers — over 550 tags from a single machine.

Grouping by Register Space

The gateway groups these by:

  1. Coils (FC 01): Registers 0–9 → one read of 10 bits
  2. Discrete inputs (FC 02): Registers 100001–100003 → one read of 3 bits
  3. Input registers (FC 04): Registers 300001–300192 → four reads (keeping each under 50 registers)
  4. Holding registers (FC 03): Registers 400200–400525 → seven reads

The critical optimization: alarm words (300162–300192) share the same FC 04 address space as temperatures (300001–300161), but they have different polling intervals (1 second vs 60 seconds). The grouping engine must split them into separate read groups despite being in the same register space.

Binary Encoding for Alarm Telemetry

When alarm data travels from the edge gateway to the cloud, encoding format matters. JSON is human-readable but verbose:

{"id": 162, "values": [21], "ts": 1709337600}

That's ~50 bytes for a single tag update. For 550 tags at 60-second intervals, you're looking at 27,500 bytes per minute — manageable on ethernet, but painful on a cellular backhaul with a 50MB/month data plan.

Binary encoding packs the same information into roughly 8 bytes:

[tag_id: 2 bytes][status: 1 byte][count: 1 byte][size: 1 byte][value: 2 bytes]

That's an 84% reduction in payload size. For alarm-heavy deployments with hundreds of tags across dozens of machines, the difference between binary and JSON encoding can be the difference between a $15/month and a $150/month cellular data plan.

Key binary format considerations:

  • Fixed-size headers enable zero-copy parsing at the receiver
  • Big-endian byte ordering (network byte order) for cross-platform compatibility
  • A format marker byte (like 0xF7) at the start of each batch for frame synchronization
  • Group-level timestamps — one timestamp per batch group, not per tag

Error Handling for Alarm Registers

Alarm registers deserve special error handling because a missed alarm is a safety issue:

Connection Loss Detection

If the Modbus connection drops (timeout, CRC error, cable disconnect), the gateway should:

  1. Attempt reconnection with a retry count (typically 3 attempts)
  2. Deliver a link state tag indicating the connection is down
  3. Never assume alarms are clear — if you can't read the alarm word, you don't know the alarm state. The last known state should persist, not reset to zero.

Error Escalation

After 3 consecutive read failures on the same register, the gateway should:

  1. Report the tag as errored (not zero — zero means "no alarms")
  2. Close and re-establish the Modbus connection
  3. Log the error with the Modbus exception code or errno for diagnostics

Common Modbus error codes for alarm-related issues:

  • ETIMEDOUT: PLC didn't respond — check wiring, address, baud rate
  • ECONNRESET: Connection dropped mid-read — electrical noise, loose connector
  • ECONNREFUSED: TCP port 502 rejected — PLC firewall, wrong IP
  • EPIPE: Socket broken — PLC rebooted mid-session

Practical Alarm Monitoring Architecture

Putting it all together, here's the architecture for reliable alarm monitoring at scale:

┌─────────────────────────────────────────────────┐
│ PLC │
│ Alarm Words: 300162–300192 (uint16) │
│ Temperatures: 300001–300161 (int16) │
│ Status Coils: 1, 100001–100003 (bool) │
│ Config: 400200–400525 (int16) │
└──────────────────┬──────────────────────────────┘
│ Modbus TCP / RTU
┌──────────────────▼──────────────────────────────┐
│ Edge Gateway │
│ │
│ ┌─ Polling Engine ─────────────────────────┐ │
│ │ • Sorted tag list by address │ │
│ │ • Contiguous register grouping │ │
│ │ • Interval-based scheduling │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌─ Alarm Decoder ──────────────────────────┐ │
│ │ • Shift + mask per calculated tag │ │
│ │ • Two-level change detection │ │
│ │ • Dependent tag triggers │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌─ Delivery Engine ────────────────────────┐ │
│ │ • Batched: temperatures (60s window) │ │
│ │ • Unbatched: alarms (immediate) │ │
│ │ • Binary encoding (8 bytes per tag) │ │
│ │ • Hourly full refresh │ │
│ └──────────────────────────────────────────┘ │
│ │
└──────────────────┬──────────────────────────────┘
│ MQTT (TLS) / Binary payload
┌──────────────────▼──────────────────────────────┐
│ Cloud Platform │
│ • Named alarm tags (not raw registers) │
│ • Alarm timeline with context data │
│ • Notification rules per alarm type │
└──────────────────────────────────────────────────┘

How machineCDN Handles Alarm Words

machineCDN's edge gateway natively supports alarm word decoding with calculated tags. The tag configuration specifies parent alarm word registers and their child boolean tags with shift/mask parameters. Change detection operates at both the parent and child level, ensuring only actual alarm transitions are transmitted.

Critical alarms bypass the batch pipeline entirely, arriving at the cloud platform within seconds of the PLC detecting the fault. Dependent tag chains ensure alarm events are accompanied by the contextual process data (temperatures, pressures, flow rates) that maintenance engineers need to diagnose the root cause — without waiting for the next scheduled poll cycle.

The result: a remote monitoring system that treats alarms with the urgency they deserve, while keeping routine telemetry efficient and bandwidth-friendly.


Need to monitor alarm states from PLCs across multiple facilities? See how machineCDN decodes and delivers alarm data at the edge →