CODESYS gives a function block no flag that says which inputs the caller wired. Give ranged numeric inputs a sentinel initial value outside their valid range, declare BOOL or full-range inputs as REFERENCE TO and test them with __ISVALIDREF, latch the used-input mask on the first execution, and gate the algorithm on that mask. Guard the zero-input case so the division never runs on an empty mask.
A CODESYS function block with a fixed input list never learns from the runtime which inputs the caller wired. An input left open simply carries its declared initial value for the life of the instance, so a 0 nobody connected is indistinguishable from a 0 somebody wrote. Two documented techniques recover the connection state: an out-of-range sentinel initial value for numeric inputs with a known valid range, and a REFERENCE TO input tested once with __ISVALIDREF for BOOL and full-range inputs. Build the mask once on the first execution, gate the algorithm on it, and give the no-inputs-wired case its own branch. The steps below set that up in one block and end with a check table.
How CODESYS sees an unwired input
An input the caller leaves open keeps the initial value it was declared with, for the life of the instance. Nothing in the runtime flags that state, so the block body has to derive it.
The first technique is a sentinel. Declare the input's initial value outside the range real data can take, and treat a first execution that still reads the sentinel as an unwired input. The second is a reference. Declare the input REFERENCE TO <type> and test it with __ISVALIDREF, the operator that reports whether a reference refers to a valid value; the operator reference in the online help for your CODESYS version documents the check.
Spell __ISVALIDREF with its double leading underscore; the underscores are part of the name. If your compiler reports it as an undeclared identifier, check the spelling against the online help for your version before changing anything else.
Pick the method for each input
Decide the method per input before you write declarations. The sentinel method needs a value the caller can never legitimately supply. The reference method needs no spare value, but it changes the declaration and the call-site syntax your CODESYS version expects.
| Input type | Sentinel initial value | REFERENCE TO + __ISVALIDREF |
|---|---|---|
| Numeric with a documented valid range, for example 0.0 to 100.0 % | Works: pick a value outside the range | Works |
| Full-range integer, every value legal | No spare value exists | Works |
| BOOL | Not usable, both values are legal data | Works |
| Caller may wire a literal constant | Works | Needs a variable to take the address |
A third option removes inference altogether: add a count or enable-mask input and let the caller state how many inputs are in use. It costs one parameter and fits any data type. Write each input's valid range on paper first; an input with no out-of-range value goes to the reference method or the count input.
What you need
- Each input's documented valid range, from the block specification you or its author wrote.
- One sentinel constant per data type, declared inside the block.
- A BOOL init flag and one BOOL mask bit per input.
- A test program with one instance of the block and a watch view on its variables.
- For reference inputs, the call-site binding syntax your CODESYS version documents for REFERENCE TO.
Latch the used-input mask on the first execution
Instance memory makes a one-time test possible: values stored in the block survive from call to call, the same memory that lets multiline serial reads accumulate across cycles. Use it once, on the first execution.
- Write down each numeric input's valid range. Choose one sentinel outside every range it serves and declare it once as a constant, so all inputs of that type share it. For REAL inputs limited to 0.0 to 100.0 %, -1.0 is a sentinel.
- Set the sentinel as each input's initial value in the declaration.
- Declare a BOOL init flag and a BOOL array with one bit per input.
- On the first execution only, compare each numeric input to the sentinel, store the result in its mask bit, then set the init flag. Testing every cycle fails the first time a wired variable legitimately carries the sentinel, and it burns scan time.
- For REFERENCE TO inputs, call __ISVALIDREF on the first execution instead of a value comparison, and store TRUE only where the reference is valid.
- Place the latch after the logic that writes the block's inputs. If those inputs may be unpopulated at first scan, add an enable input and run the latch on its first rising edge.
- Dereference a reference input only where its stored mask bit is TRUE.
Gate the algorithm on the mask
Consume the stored mask, never a fresh sentinel comparison, so unused inputs drop out of both the sum and the count. The dereference discipline is the one used for pointers to struct members in structured text: mask bit first, value second.
The zero-input case needs its own decision. With no input wired, the count is 0 and the division must not run. Decide the block's behaviour for that case in the design: hold the last valid output and clear a valid flag, or set an error output. Write the branch explicitly so the call site never sees a divide by zero.
Example: a 4-input averaging block with inputs 1 and 3 wired
Inputs: four REAL inputs in an averaging block. The block's specification gives a valid range of 0.0 to 100.0 %; the sentinel is -1.0 %, declared once, outside that range. The test wires inputs 1 and 3 to process values of 10.0 % and 20.0 % and leaves inputs 2 and 4 open.
Rule in words: on the first execution, any input still reading the sentinel was never wired.
Arithmetic: the latch stores the mask as TRUE, FALSE, TRUE, FALSE. The gated sum is 10.0 + 20.0 + 0.0 + 0.0 = 30.0 %. The count is 2. The average is 30.0 / 2 = 15.0 %.
Result: the block outputs 15.0 % built from the two wired inputs, with the valid flag set.
Decision: with all four inputs open the count would be 0, so the block skips the division, holds its last output, and keeps the valid flag clear. Any input that can legitimately read -1.0 % cannot use this method; declare that input REFERENCE TO and test it with __ISVALIDREF.
How to check the block
Run each case on a fresh instance. The latch is static per instance, so re-download or reinitialize between cases to bring the init flag back to FALSE, then compare the mask and the output after the first cycle.
| Case | Expected mask | Expected behaviour |
|---|---|---|
| All four inputs wired | TRUE, TRUE, TRUE, TRUE | Calculation uses all four |
| Inputs 1 and 3 wired | TRUE, FALSE, TRUE, FALSE | Calculation uses 1 and 3 only |
| No inputs wired | FALSE, FALSE, FALSE, FALSE | No division, defined fallback output |
| Input wired to a constant equal to the sentinel | That bit reads FALSE | Shows why the sentinel sits outside the valid range |
| Input wired after the first execution by online change | Mask unchanged | Mask rebuilds only after the init flag resets |
All five rows must match before the block goes into a released project.
Recommendation: Use the sentinel method for numeric inputs that have a documented valid range, with one shared constant per data type, and declare BOOL and full-range inputs as REFERENCE TO and test them with __ISVALIDREF once on the first execution. Gate every calculation on the stored mask, and give the zero-input case an explicit branch that skips the division and defines the output. When inputs can be unpopulated at first scan or rewired by online change, add an enable input, latch on its first rising edge, and keep a re-init input so the mask can be rebuilt without deleting the instance.
Questions
What happens if a wired input carries the sentinel value?
The first-execution test reads it as unwired and the algorithm skips it. You catch this at commissioning: the check table shows a mask bit FALSE on an input you know is wired. Move the sentinel further outside the valid range, or declare that input REFERENCE TO and test it with __ISVALIDREF instead of a value comparison.
What happens if I read a reference input that was never assigned?
The reference holds no valid address, so the access raises a runtime exception on the target instead of returning a zero. The failure appears when the block runs, not when it compiles, which is why the mask test comes first. Store __ISVALIDREF once, and dereference only where the stored bit is TRUE, in every branch of the algorithm.
How do I pick up an input that is wired after the first execution?
You do not with the latch alone. The mask was built once, so a connection added by an online change stays invisible until the init flag resets or the instance reinitializes. If wiring changes during commissioning, add a re-init input that clears the flag, or latch on the first rising edge of an enable input you pulse after rewiring.
Does a sentinel initial value work on a BOOL input?
No. BOOL offers only TRUE and FALSE, and both are values a caller can legitimately send, so no initial value separates an open input from a wired one. Declare the input REFERENCE TO BOOL and test it with __ISVALIDREF on the first execution, or add a count or enable-mask input and let the caller state how many inputs are in use.
Planning information, not legal or engineering advice. Rules differ by place: check codes and permits with your local authority and your licensed trades.