Addressing arrays
Every PLC4X driver selects array elements the same way, in PLC4J and in PLC4Go alike. The address itself differs per protocol - an S7 data block, a Modbus register, an OPC UA node - but what comes after it does not:
<driver-specific address>[<selection>]:<TYPE>
The selection sits before the type. Where a driver’s address carries no type, it ends the address.
The same address means the same thing in either language: one address, written once, used from Java or Go.
The forms
| Written | Means | Example |
|---|---|---|
(omitted) |
the whole value - a scalar, or every element of an array |
|
|
one element, the one at index |
|
|
an array of the elements |
|
|
one element, in an array the PLC declares as starting at |
|
|
a range, in an array declared as starting at |
|
|
one bracket per dimension |
|
A single index is not an array
myTag[4] selects one element and gives you a scalar. myTag[4..4] selects a range that happens
to hold one element and gives you a list of one. The difference is deliberate: it is how a tool
reading getArrayInfo() decides whether to render a value or a list.
Arrays that do not start at zero
IEC 61131 lets an array be declared ARRAY[1..10] OF BYTE, and the indices the PLC program shows
you then start at 1. Write those indices, and add ;base so the driver knows where the data
really begins:
%DB42:28.0[4..7;1]:BYTE
That reads four bytes, starting three elements past the address - because element 4 of an array declared from 1 is the fourth.
Some drivers already know the declared bounds from the device: ADS, UMAS and OPC-UA
learn them from the symbol table or the address space. There you can simply write the declared
indices, and ;base becomes a statement of intent the driver checks against what the device says.
A base that disagrees is reported, because it means the address was written against a different
layout than the PLC has.
Multi-dimensional arrays
Two spellings are accepted and mean the same thing - the second is what Allen-Bradley and others use:
g_matI16_2x3[1..2][3..4]
g_matI16_2x3[1..2,3..4]
getAddressString() always produces the first, so an address you read back from a tag is in one
canonical spelling regardless of how it was written.
A range may only be the last dimension. a[1].b[2..5] is fine - one path to one structure,
then a run of its elements. a[1..3].b is not: member b of three separate elements is not one
contiguous read, and no protocol here fetches that in a single request. The same goes for
a[1..3][2], a strided slice.
What each driver can express
The notation is the same everywhere; what a protocol can carry is not.
| Driver | Dimensions | Whole array | Notes |
|---|---|---|---|
OPC-UA |
many |
yes |
Omitting the selection asks for the whole node |
ADS |
many |
yes |
Declared bounds come from the symbol table |
UMAS |
many |
yes |
Selecting elements is not implemented yet and is reported |
EtherNet/IP |
1 |
no |
A CIP index is a |
S7, Modbus, SLMP |
1 |
no |
Memory offsets; the base is resolved into the address |
Simulated, Profinet |
1 |
no |
Can only select from the first element |
Firmata |
1 |
no |
No type suffix, so the selection ends the address |
KNXnet/IP |
1 |
no |
Device addresses only; a property selection moves the start index |
Where a protocol cannot express what you wrote, the driver says so. It will not quietly read something else.
The two bindings do not carry the same set of drivers, and a driver present in both is not always at the same depth. Where a driver in both accepts a selection, the notation and its meaning are identical; what differs is which drivers accept one at all:
-
PLC4Go has no Profinet driver.
-
PLC4Go’s OPC-UA driver takes no selection - its addresses are a NodeId and a type. PLC4J’s, which is where this notation came from, takes the full grammar.
-
PLC4Go’s UMAS driver accepts a bare index inside a symbolic path (
myVar[1].member[2]) but not a range; PLC4J’s parses ranges and reports selecting elements as unsupported. -
PLC4Go’s KNXnet/IP driver has the device address forms in the table above. PLC4J’s has no equivalent.
Brackets that are not array selections
Three address forms use square brackets for something else entirely. They are unchanged by this notation, and the notation’s own spellings are not accepted there:
-
C-Bus writes the arguments of a CAL command in brackets -
recall=[param, count]. The count is an argument of the command, not a selection appended to an address. -
KNXnet/IP group addresses write a set of group addresses to match -
1/[2-3]/4, and[1,3]/2/[4-6]. That is a filter over addresses, not a selection within one value. -
BACnet/IP writes a property’s array index -
ANALOG_VALUE,2/PRESENT_VALUE[3]. This is an index, and already means what an index means here, so nothing about it changed.
Why ; and not something else
The ; before a declared base is the one separator the notation adds. It is kept as ; for
three reasons:
-
OPC UA addresses already use
;as the separator of the standard NodeId string form (ns=2;i=MyInt, from OPC UA Part 6). That is not PLC4X’s to redefine - people copy those strings out of their tooling - so;was already part of the vocabulary. -
It stays visually distinct from
:, which separates the type.%DB42:28.0[4..7;1]:BYTEis readable; the same address with four colons doing three different jobs is not. -
:is structural in S7, Modbus, SLMP, EtherNet/IP and ADS addresses, whereas;is structural in only one driver. Reusing the less overloaded character inside brackets keeps the grammar unambiguous.
Upgrading
The brackets used to come after the type on several drivers, where they meant a count:
holding-register:1:INT[4] (1)
holding-register:1[0..3]:INT (2)
| 1 | before - four registers |
| 2 | now |
An address in the old form no longer parses, and the error tells you what to write instead. That
is deliberate: [4] used to mean "four elements" and now means "the fifth", so an address that
still parsed would quietly return different data.
Firmata is the exception. Its addresses carry no type (3[4]), so its brackets did
not move and there is nothing to reject. 3[4] used to mean four pins from pin 3 and now means
one pin, the fifth. Rewrite these as 3[0..3].
|
PLC4Go’s ADS driver is a second exception. Its [n] used to be a count of n
elements, unlike PLC4J’s, so MAIN.g_arr[3] read three elements and now reads one - the element
at index 3. Rewrite these as MAIN.g_arr[0..2]. Its [a:b] start-and-count form, which PLC4J
never had, is gone: write [a..a+b-1]. These two changes are also what makes the same ADS
address mean the same thing in both languages, which it did not before.
|
A count of zero no longer has a spelling anywhere. Several drivers used to accept [0] and
reject it as "quantity must be greater than zero"; a range is written with the indices it covers,
so there is no way to ask for no elements at all, and [0] now selects the first one.