Reference — Data Types
EtherNet/IP carries typed CIP values. This adapter decodes the CIP elementary types into JSON, both
for scheduled poll reads (explicit messaging) and for class-1 implicit-I/O (push) assembly fields. Every
signal declares its type; for push fields it also declares a byte offset (and, for a boolean, a
bit). The conversion is the same pure codec in both directions and both modes.
Supported CIP types
Section titled “Supported CIP types”type |
CIP type | On-wire JSON (read) | Write input |
|---|---|---|---|
bool |
BOOL | boolean | boolean |
sint |
SINT (int8) | number | number (int) |
usint |
USINT (uint8) | number | number (int) |
int |
INT (int16) | number | number (int) |
uint |
UINT (uint16) | number | number (int) |
dint |
DINT (int32) | number | number (int) |
udint |
UDINT (uint32) | number | number (int) |
lint |
LINT (int64) | number | number (int) |
ulint |
ULINT (uint64) | number | number (int) |
real |
REAL (float32) | number | number |
lreal |
LREAL (float64) | number | number |
These are all the value types the adapter handles. string/SSTRING, structures and UDTs (including
Logix STRING, which is a UDT), and multi-dimensional arrays are not supported — they are rejected
at config-parse time, and sb/browse marks such tags supported: false.
Arrays
Section titled “Arrays”arrayCount: N reads (or writes) a 1-D array of N elements of the signal’s type; the value is a
JSON array. N is an integer from 1 to 65535 (the CIP Read Tag element count is a 16-bit field); a
value outside that range is a configuration error, and in an sb/read/sb/write explicit signal ref it
is a BAD_ARGS refusal. A write must supply exactly N elements — a wrong length is rejected. A read
must come back with exactly N elements: a reply carrying a different number is a BAD sample whose
qualityRaw names the expected and received counts, never a shorter array published as GOOD.
arrayCount: 1 publishes the bare value rather than a one-element array — the same shape whichever
representation the device serves the tag in. Multi-dimensional arrays are not supported.
BOOL array signals are experimental. Two wire representations exist for them, and the adapter reads
whichever one the device declares. A device that serves one byte per element is read directly. A Logix
controller stores a BOOL[n] tag as a packed DWORD array, and the adapter unpacks it: element n is
bit (n mod 32) of word (n / 32), so arrayCount: N needs ceil(N/32) words and the bits past N in
the final word are the controller’s padding and are dropped. arrayCount: 40 against a BOOL[64] tag
therefore publishes the first 40 booleans. A packed reply carrying any other number of words is a BAD
sample whose qualityRaw names the counts in words. The published value has the same JSON shape in
either representation, including the bare value for arrayCount: 1.
The representation is a property of the device, not something you configure: the adapter observes it from
the reply and shapes the following reads to match. sb/signals reports it per signal as observedType.
A write to a tag observed packed is refused, with the reason in the entry’s error — setting some
bits of a shared word requires a masked read-modify-write. Each BOOL array signal logs a warning at
startup. Scalar bool signals and bool array fields of a push assembly are not affected — a push
layout is a byte map you declare, not Logix tag storage.
Scale & offset
Section titled “Scale & offset”For numeric types (not bool), a linear transform maps device units to engineering units:
- Read:
published = raw × scale + offset(polloffset; pushvalueOffset). Applied element-wise for arrays. With no scale/offset, an integer type keeps native integer precision on the wire; with a scale it becomes a float. - Write: the inverse,
device = (value − offset) / scale, then range-checked against the CIP type — an out-of-range value is a typed error, never a silent clamp. An unscaled fractional value written to an integer type is rejected; a scaled result is rounded, then range-checked.
Push field addressing (bit, offset)
Section titled “Push field addressing (bit, offset)”A push field decodes out of the assembly byte buffer at its offset:
bit(0–7) — extract one bit of the byte atoffsetas a boolean (boolonly, single element). Fields may overlap, so a status byte and its individual bits can all read the sameoffset.- A field’s bytes must fit inside the assembly
sizeBytes; this is validated at startup.
Quality normalization
Section titled “Quality normalization”Every sample carries a normalized quality plus qualityRaw (the native detail):
quality |
When |
|---|---|
GOOD |
A successful read/decode. |
BAD |
A read failure, a wire type that does not match the configured type (qualityRaw = DECODE type mismatch (expected <configured>, device declares <wire type>)), or a reply carrying a different number of elements than the configuration declared (qualityRaw = DECODE element count mismatch (expected N, got M); for a packed BOOL array the counts are words, expected N packed dword(s) for M bools, got K). Poll: also NO_DATA/UNRESOLVED_REF on sb/read; push: NO_FRAME when no frame has arrived. |
UNCERTAIN |
A value that goes non-finite (NaN/inf) after scale/offset — qualityRaw = NON_FINITE_AFTER_SCALE, value null. For an array, any non-finite element makes the whole reading UNCERTAIN. |
A non-GOOD sample always publishes (a failure is information); it is never suppressed by the deadband.
Published identity
Section titled “Published identity”Each SouthboundSignalUpdate / read result carries:
signal.name— the configured name (also the sanitizeddata-class channel token).signal.id— the stable canonical id: poll = the CIP tag path verbatim (LINE_SPEED); push =a<assembly>/<offset>/<type>[.<bit>](a100/4/real,a100/0/bool.1).signal.address— the protocol-native handle: poll{ tagPath, type, arrayCount?, slot? }; push{ assembly, offset, type, bit?, arrayCount?, slot? }.
Value typing notes
Section titled “Value typing notes”- Integers use the full 64-bit range; a consumer whose JSON parser uses IEEE-754 doubles (e.g. JavaScript) may lose precision above 2^53.
boolis a JSON boolean; everything else is a JSON number (or an array of numbers).- EtherNet/IP carries no device-side timestamp, so
sourceTsis never emitted;serverTsis the adapter’s read/receive time.
Protocol scope
Section titled “Protocol scope”- Poll uses CIP explicit messaging — one request per signal per cycle (there is no
multiple-service-packet batching, so large tag counts favor fewer, larger poll groups at longer
intervals). The connection is unconnected explicit messaging by default, or CIP connected messaging
with
connection.connected: true. - Push uses CIP class-1 implicit I/O — the device produces its input assembly cyclically at the RPI; the adapter maps configured byte-offset fields to signals. There is no wire-discoverable field map; the layout is configuration.
- Security — poll instances support TLS (CIP Security) on the explicit-messaging path via
connection.security(mutual X.509, TCP2221; AEAD suites only — TLS 1.3, and TLS 1.2 ECDHE with AES-GCM or ChaCha20-Poly1305). Plaintext TCP44818is the default; class-1 implicit I/O is plaintext UDP2222(a push instance configured with TLS is rejected at startup). Secure the network segment for plaintext deployments.