A bit-coded PLC status word becomes a single state integer in Ignition through one expression tag. getBit() extracts each bit, and binEnum() returns the 1-based position of the first true argument, or 0 when none is true. Tag history on that expression tag records only state transitions. A gateway script that inserts rows into a database duplicates the historian and moves the work into Jython. Use one only if the checks below rule out the expression tag.
Check 1: whether anything needs past states
Prerequisite: know who consumes the state value. That could be a live display, a trend, a downtime report, or an alarm pipeline.
- Reading: does any trend, report, or query need historical state values?
- If no: put the expression in a property binding on the display component. No tag is created and nothing is stored. Go to Check 2 only to choose the expression itself.
- If yes: build an expression tag with history enabled. Continue to Check 2.
Storage load is lower than it looks. With history sample mode set to on-change and a discrete deadband, the historian writes one row each time the state integer changes. Scan rate does not drive the row count. A machine that changes state a few hundred times per shift produces a few hundred rows per shift. The status word's own history is not required. Historize the derived state and leave the raw word off unless bit-level forensics are needed.
Check 2: one-hot word or overlapping bits
Prerequisite: the PLC program or the device manual that defines each bit of the status word.
Reading: watch the word in the Tag Browser through several machine cycles and compare it against the bit definitions.
| Observed word pattern | Meaning | Mapping to use |
|---|---|---|
| Exactly one bit set at any time | One-hot state word |
binEnum() directly, bit 0 as first argument |
| Word equals 0 | No state active (idle, or PLC not yet running) |
binEnum() returns 0; reserve 0 as "no state" in your state table |
| Two or more bits set together | Overlapping status flags, or a transition overlap in the PLC scan | Order binEnum() arguments by priority, or flag the condition as invalid |
For a one-hot word, the state integer equals the bit number plus 1. For overlapping bits, binEnum() reports the first true argument in the list. List the highest-priority bit first. For example, put a fault bit ahead of a running bit. Keep a written map, because the result now reflects argument position rather than bit number.
Use a single-bit test to catch words that should be one-hot but are not. The expression x & (x - 1) is zero only when x has zero or one bit set. Any nonzero result means more than one bit is on. Use this test to return a sentinel such as -1, so a PLC logic error shows up in history instead of hiding behind the lowest-numbered bit.
Check 3: signed 16-bit word inside an Integer tag
Prerequisite: the PLC data type of the status word (signed INT or unsigned WORD) and the Ignition tag data type.
- Reading: view the tag value while bit 15 is set.
- If the value is negative (range -32768 to 32767): the word arrives signed. In a 32-bit Integer tag, sign extension sets bits 16 to 31 whenever bit 15 is on.
- If the value is 32768 or higher (range 0 to 65535): the word arrives unsigned and no correction is needed.
Sign extension does not affect getBit() on positions 0 to 15. It does break the single-bit test, because the extended upper bits make x & (x - 1) nonzero. Mask the word with & 65535 before any whole-word arithmetic or comparison.
Expression tag versus gateway script and database writes
| Approach | Execution | History | Failure mode under bursts |
|---|---|---|---|
| Expression tag, event-driven | Expression engine, separate from the tag event script pool | Built-in historian, on-change | Re-evaluates on each source change |
| Tag Value Changed event script plus DB insert | Jython, tag event script pool | Custom table, custom pruning | Per-tag event queue can overflow; check the missedEvents flag |
| Gateway Tag Change event script plus DB insert | Jython, gateway event scripting | Custom table, custom pruning | Queued changes delay or collapse under load |
Expression tags run in a different execution pool from tag event scripts. Evaluating an expression costs far less than running Jython. The scripted route also forces you to build the table schema, handle database outages, and manage pruning, all of which the historian already does. Do not call runScript() inside the expression. Doing so pulls Jython back into the expression path and brings the scripting load and failure modes with it.
Building the state expression tag
Prerequisites: the full status word tag path, a configured history provider, and the bit-to-state map from Check 2.
- Create an expression tag in the same folder as the status word. Set its data type to Integer. Confirm: the tag appears with Bad or Uncertain quality until the expression is entered.
- Set the execution mode to event-driven so the tag re-evaluates when the status word changes. Confirm: no tag group polling is assigned.
- Enter the expression using a relative reference to the source tag (one-hot form shown, with a -1 sentinel for overlapping bits). Confirm: quality goes Good and the value matches the current word.
Replaceif( (({[.]StatusWord} & 65535) & (({[.]StatusWord} & 65535) - 1)) != 0, -1, binEnum( getBit({[.]StatusWord}, 0), getBit({[.]StatusWord}, 1), getBit({[.]StatusWord}, 2), getBit({[.]StatusWord}, 3) // continue through bit 15 as the word definition requires ) )StatusWordwith your tag name. Remove the comment line before saving. For priority mapping, drop theif()wrapper and reorder thegetBit()arguments, highest priority first. - Enable history. Select the storage provider, set the deadband style to discrete, and set sample mode to on-change. Confirm: the history icon shows on the tag in the Tag Browser.
- Record the state table (0 = none, 1 = bit 0, ... 16 = bit 15, -1 = invalid) in the tag documentation field. Confirm: displays and reports decode from this table, not from raw bit numbers.
Exercising every bit and confirming stored transitions
Prerequisite: point the expression at a memory tag copy of the status word, not a live PLC address on a running machine.
Random integers are a weak test. A draw from 0 to 65535 (or -32768 to 32767 for a signed tag) almost always has several bits set, so it exercises only the -1 branch. Walk a single bit through each position, then add deliberate overlaps. From the Script Console:
import time
path = ["[provider]Folder/StatusWord"] # replace with your memory tag path
for n in range(16):
v = 1 << n
if v > 32767: # signed Int tag: bit 15 must be written as negative
v = v - 65536
system.tag.writeBlocking(path, [v])
time.sleep(2) # hold longer than the history storage interval
system.tag.writeBlocking(path, [3]) # bits 0 and 1 together
time.sleep(2)
system.tag.writeBlocking(path, [0])
- Watch the state tag during the walk. Confirm: it steps through 1 to 16 in order and stays positive on bit 15.
- Check the overlap write. Confirm: the state tag reads -1, or the priority-ordered position if you removed the sentinel.
- Check the final write of 0. Confirm: the state tag reads 0.
- Query the state tag in a trend or with a history query over the test window. Confirm: exactly 19 stored transitions (16 single bits, the overlap, 0, plus the starting value). There are no duplicate rows between writes. A missing bit-15 row means the mask or signed write is wrong.
FAQ
How do I convert a bit status word to a single state number in Ignition?
Create an event-driven expression tag using binEnum(getBit({[.]StatusWord},0), getBit({[.]StatusWord},1), ...). It returns bit number plus 1 for a one-hot word, and 0 when no bit is set.
How do I log state changes without a gateway script?
Enable history on the expression tag with a discrete deadband and on-change sample mode. The historian then stores one row per state change and handles storage and pruning.
How do I handle a status word where more than one bit can be on?
Order the binEnum() arguments by priority so the most important bit wins. Alternatively, test (x & 65535) & ((x & 65535) - 1) != 0 and return -1 to flag overlapping bits.
Why does my 16-bit PLC status word show negative in Ignition?
The PLC word is a signed INT, so bit 15 set produces -32768 to -1. In a 32-bit Integer tag this sign-extends the upper bits. Mask with & 65535 before whole-word comparisons; getBit() on bits 0 to 15 is unaffected.
How do I test every bit of a status word tag?
Write 1 << n for n = 0 to 15 to a memory tag copy with system.tag.writeBlocking(), writing bit 15 as -32768 on a signed tag. Then confirm the state tag steps 1 to 16 and history holds one row per step.