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

myTag

[n]

one element, the one at index n

myTag[4] - the fifth element

[lo..hi]

an array of the elements lo through hi, both included

myTag[0..7] - eight elements

[n;base]

one element, in an array the PLC declares as starting at base

myTag[1;1] - the first element

[lo..hi;base]

a range, in an array declared as starting at base

myTag[4..7;1] - four elements, starting at the fourth

[…][…]

one bracket per dimension

myTag[1..2][0..5]

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 uint 8, so it cannot start past 255

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]:BYTE is 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.