11 - Points from settings
11 - Points from settings
A pump controller set up from an HMI cannot have a Modbus address in its
program. The flow meter might be unit 3 holding register 4100 as a FLOAT32 in
CDAB order on one site, and unit 1 input register 0 as a UINT32 in litres on
the next. MODBUS_POINT and MODBUS_POINTS read (or write) a value whose every
detail comes from settings, read again every scan, so a different meter is a
settings change and not a PLC change.
Pins first
MODBUS_POINT - one point:
| Pin | ||
|---|---|---|
NODE |
in-out | which node |
UNIT |
in, BYTE | the device's address, 1..247 |
FC |
in, MODBUS_PT_FC | ..._FC01_READ_COILS, ..._FC02_READ_DISCRETE_INPUTS, ..._FC03_READ_HOLDING_REGISTERS, ..._FC04_READ_INPUT_REGISTERS; ..._FC05_WRITE_COIL, ..._FC06_WRITE_REGISTER, ..._FC16_WRITE_REGISTERS |
ADDRESS |
in, UINT | the register or coil, numbered from 0 |
DTYPE |
in, MODBUS_PT_TYPE | MODBUS_PT_BIT, _INT16, _UINT16, _INT32, _UINT32, _FLOAT32, _REG_BIT |
ORDER |
in, MODBUS_ORDER | word order of a 32-bit value (see 05) |
BIT |
in, USINT | _REG_BIT only: which bit of the register |
SCALE, OFFSET |
in, REAL | value = raw × SCALE + OFFSET |
EVERY |
in, TIME | how often to read it - or to send a write again |
STALE |
in, TIME | no good answer this long and it is lost; T#0s = 3 × EVERY |
TIMEOUT |
in, TIME | this device's answer time limit; T#0s = measured |
WRITE_VALUE |
in, REAL | a write point's value, in engineering units |
ENABLE |
in, BOOL | FALSE = not fitted here |
VALUE |
out, REAL | engineering units; the last good one held while not ok |
Q |
out, BOOL | VALUE <> 0 |
OK |
out, BOOL | good, or a little late. Interlock on this |
QUALITY |
out, MODBUS_QUALITY | UNKNOWN, GOOD, UNCERTAIN, BAD |
AGE, FAILURES, ERROR, STATUS |
out | how long, how often, why |
MODBUS_POINTS - the same, as two tables: SETTINGS : ARRAY OF MODBUS_PT_CFG
(one row a point: unit, fc, address, dtype, order, bit, scale,
offset, every, stale, timeout, enable) and POINTS : ARRAY OF MODBUS_PT_STS (w to write; v, q, ok, quality, age, failures,
result). Outputs: GOOD_COUNT, LOST_COUNT, FIRST_LOST, ERROR, STATUS,
STATUS_TEXT (pt 3 unit 5 lost).
A table of field points
VAR_GLOBAL RETAIN
FIELD_CFG : ARRAY[1..16] OF MODBUS_PT_CFG; (* the site's points: from the HMI *)
END_VAR
VAR_GLOBAL
FIELD : ARRAY[1..16] OF MODBUS_PT_STS; (* not retained *)
END_VAR
field(TRANSPORT := MODBUS_TRANSPORT#MODBUS_ARDUINO_SERIAL, SERIAL_BAUD := 9600);
points(NODE := field, SETTINGS := FIELD_CFG, POINTS := FIELD);
flowLps := FIELD[1].v; flowOk := FIELD[1].ok;
FIELD[4].w := BOOL_TO_REAL(valveOpen); (* a remote relay, FC05 *)
Retain the settings, never the status: the status changes every scan, and a
retained struct that changes every scan rewrites the retained store every save
period.
The table is fixed when the program starts. Its length is how many points the
project can have; an unused row is enable := FALSE.
A lost device is never a valid 0
The rule the IO map relies on:
ok |
v |
quality |
|
|---|---|---|---|
| never answered | FALSE | 0 | UNKNOWN - a commissioning fault: check unit and address |
| answered, on time | TRUE | the value | GOOD |
| answered, late (one interval's grace has gone, or one failure) | TRUE | the value | UNCERTAIN |
no good answer for stale, or two failures in a row |
FALSE | the last good value | BAD |
enable FALSE |
FALSE | - | UNKNOWN |
| settings that cannot work | FALSE | - | UNKNOWN, result MODBUS_INVALID_ARGUMENT |
A function mapped to a point that is not ok takes its fail-safe. Nothing reads
a dead radio as a valid OFF or a valid 0.
Changing settings while running
Every setting is read every scan. A new unit, function code, address, type,
word order or bit is a different point: its value, age and failures are
dropped, quality goes back to UNKNOWN and the next read goes to the new
place. A reply still on its way for the old address is discarded. Scale,
offset, every, stale and timeout change in place.
MODBUS_WATCH follows a new UNIT, FC, ADDRESS or ORDER the same way.
Writes
FC05 (a coil), FC06 (one register: INT16 / UINT16) and FC16 (one or
two registers: any register type) write w (or WRITE_VALUE), scaled back:
raw = (value − offset) / scale, rounded and held to the type's range. A
write goes as soon as the value changes, and again every every, so a remote
output with a watchdog is kept alive and one that lost power is told again.
ok on a write point means the last write was acknowledged.
A REG_BIT is never written: setting one bit of a register would be a
read-modify-write that races every other writer of that register. Use a coil,
or MODBUS_WRITE with FC 22.
Radio devices (the LoRa mesh)
The LoRa mesh HAT joins the field bus, so a device behind the mesh is just
another unit on the same MODBUS_NODE - with answers that take seconds.
- Set
timeoutto the worst round trip you expect, one hop or several:
2 to 5 s. The client then waits that long for that unit. Without it the
client's first, generous timeout is about 200 ms and the device is never
heard. - Set
everyto seconds or minutes: soil and rain sensors 60 s, a pond
level 30 s, an irrigator fail-safe 10 to 15 s. - Set
staleto about 3 ×every, so one lost reply isUNCERTAIN, not a
trip. - The longest
timeoutamong a unit's rows is that unit's. Rows without one
are measured. - One request is on a serial bus at a time. A radio reply holds the wired
devices meanwhile, so a fast protection read (a mag meter used for no-flow)
does not belong on a bus that carries radio traffic: put it on its own port,
or give its protection delay more than the worst radio round trip. - A device that stops answering is asked less often - up to eight intervals,
and never more than a minute apart - so it does not take the bus from the
live ones, and is found again soon after it comes back.
The protocol underneath
ModbusPoint and ModbusClient::setUnitTimeout() in the modbee-modbus
library. The per-unit timeout fixed a real fault: the client's last expiry pass
used to judge every outstanding request against the cold-start timeout, so a
unit with a longer timeout of its own was cut off at about 200 ms anyway.
Next: 07 - Block reference