Closed TheFern2 closed 5 years ago
I will split big array into small ones, ex. 50 elements in a batch.
If "Bools" is the tag name, then
plc.readTag("Bools[0]", short(50))
, plc.readTag("Bools[50]", short(50))
, ..., plc.readTag("Bools[250]", short(50))
In general, the steps are following,
readTag("TagName[B * batchIndex]", short(B))
in for loopreadTag("TagName[S-R]", short(R))
That's the way I use to read Boolean/Number/String array (I use batch size 10 for String)
hope it helps :)
Thanks for the detailed response @cwchien!
I tried as your suggestion and it's not working for me. I can readTag("Bools") and get the first 32bits. If I then do:
plc.readTag("Bools[31]", short(32))
I always get etherip.data.CipException: Status: 0x05 In other words I can only get the first 32bits of a boolean array. Anything higher than 32 else is a cip error.
EtherNetIP plc;
try{
plc = new EtherNetIP("10.0.2.15", 2);
plc.connectTcp();
CIPData tempTag = plc.readTag("BoolArr[0]");
System.out.println(tempTag.getNumber(0).byteValue());
System.out.println(Integer.toBinaryString(tempTag.getNumber(0).intValue()));
} catch (Exception e) {
System.out.println(e);
}
May 14, 2019 2:35:55 PM etherip.protocol.Connection <init>
INFO: Connecting to 10.0.2.15:0xAF12
32
10000100000000000001000000100000
The following will yield etherip.data.CipException: Status: 0x05
CIPData tempTag = plc.readTag("BoolArr[31]", (short)32);
Also I see from #6 that reading one bool item from an array is possible, but I haven't been able to do that.
Finest log when trying to call plc.readTag("BoolArr[32]" or anything after BoolArr[9]
etherip.data.CipException: Status: 0x05 [Path destination unknown, The path is referencing an object class, instance or structure element that is not known or is not contained in the processing node. Path processing shall stop when a path destination unknown error is encountered.] - Extended: 0x00
May 14, 2019 3:12:15 PM etherip.protocol.Connection <init>
INFO: Connecting to 10.0.2.15:0xAF12
May 14, 2019 3:12:15 PM etherip.protocol.TcpConnection write
FINER: Protocol Encoding
Encapsulation Header
UINT command : RegisterSession (0x0065)
UINT length : 4
UDINT session : 0x00000000
UDINT status : 0x00000000
USINT context[8] : '00000001'
UDINT options : 0x00000000
USINT protocol : 1
USINT options : 0
May 14, 2019 3:12:15 PM etherip.protocol.TcpConnection write
FINEST: Data sent (28 bytes):
0000 - 65 00 04 00 00 00 00 00 00 00 00 00 30 30 30 30 - e...........0000
0010 - 30 30 30 31 00 00 00 00 01 00 00 00 - 0001........
May 14, 2019 3:12:15 PM etherip.protocol.TcpConnection read
FINEST: Data read (28 bytes):
0000 - 65 00 04 00 01 00 02 24 00 00 00 00 30 30 30 30 - e......$....0000
0010 - 30 30 30 31 00 00 00 00 01 00 00 00 - 0001........
May 14, 2019 3:12:16 PM etherip.protocol.TcpConnection read
FINER: Protocol Decoding
Encapsulation Header
UINT command : RegisterSession (0x0065)
UINT length : 4
UDINT session : 0x24020001
UDINT status : 0x00000000 (OK)
USINT context[8] : '00000001'
UDINT options : 0x00000000
May 14, 2019 3:12:16 PM etherip.protocol.TcpConnection write
FINER: Protocol Encoding
Encapsulation Header
UINT command : SendRRData (0x006F)
UINT length : 46
UDINT session : 0x24020001
UDINT status : 0x00000000
USINT context[8] : '00000002'
UDINT options : 0x00000000
Send RR Data
UDINT interface handle : 0
UINT timeout : 0
UINT count (addr., data): 2
UINT address type : 0x0 (UCMM)
UINT address length : 0x00
UINT data type : 0xB2 (Unconnected Message)
UINT data length : 30
MR Request
USINT service : CM_Unconnected_Send (0x52)
USINT path : Path (2 el) Class(0x20 0x6) ConnectionManager, instance(0x24) 1
CM_Unconnected_Send
USINT tick_time : 10
USINT ticks : -16
UINT message size : 16
\/\/\/ embedded message \/\/\/ (16 bytes)
MR Request
USINT service : CIP_ReadData (0x4C)
USINT path : Path Symbol(0x91) 'BoolArr[32]', 0x00
USINT elements : 1
/\/\/\ embedded message /\/\/\
USINT path size : 1 words
USINT reserved : 0
USINT port 1, slot 2
May 14, 2019 3:12:16 PM etherip.protocol.TcpConnection write
FINEST: Data sent (70 bytes):
0000 - 6F 00 2E 00 01 00 02 24 00 00 00 00 30 30 30 30 - o......$....0000
0010 - 30 30 30 32 00 00 00 00 00 00 00 00 00 00 02 00 - 0002............
0020 - 00 00 00 00 B2 00 1E 00 52 02 20 06 24 01 0A F0 - ........R. .$...
0030 - 10 00 4C 06 91 07 42 6F 6F 6C 41 72 72 00 28 20 - ..L...BoolArr.(
0040 - 01 00 01 00 01 02 - ......
May 14, 2019 3:12:16 PM etherip.protocol.TcpConnection read
FINEST: Data read (44 bytes):
0000 - 6F 00 14 00 01 00 02 24 00 00 00 00 30 30 30 30 - o......$....0000
0010 - 30 30 30 32 00 00 00 00 00 00 00 00 00 00 02 00 - 0002............
0020 - 00 00 00 00 B2 00 04 00 CC 00 05 00 - ............
May 14, 2019 3:12:16 PM etherip.protocol.TcpConnection read
FINER: Protocol Decoding
Encapsulation Header
UINT command : SendRRData (0x006F)
UINT length : 20
UDINT session : 0x24020001
UDINT status : 0x00000000 (OK)
USINT context[8] : '00000002'
UDINT options : 0x00000000
Received RR Data
UDINT interface handle : 0
UINT timeout : 0
UINT count (addr., data): 2
UINT address type : 0x0 (UCMM)
UINT address length : 0
UINT data type : 0xB2 (Unconnected Message)
UINT data length : 4
MR Response
USINT service : CIP_ReadData_Reply (0xCC)
USINT reserved : 0x0
USINT status : 0x5 ()
USINT ext. stat. size : 0x0
Process finished with exit code 0
I think you might confuse CIPData.Type.BITS with CIPData.Type.BOOL, they can both represent boolean values but use different bytes.
CIPData.Type.BITS use one BIT to represent each boolean value, it actually DINT in Logix5000 internally. That's why they are always a factor of 32.
Here's my Groovy code to read boolean values, it maybe give you some ideas.
private static List<Number> cipDataToNumbers(final CIPData data) {
(0..<data.elementCount).collect { data.getNumber(it) }
}
private static List<Boolean> cipDataToBooleans(final CIPData data) {
switch (data.type) {
case CIPData.Type.BOOL:
// BOOL use one byte to represent boolean value, TRUE is 0xFF (which means -1), FALSE is 0x00
return (cipDataToNumbers(data)*.byteValue()).collect { it == (0xFF as byte) }
case CIPData.Type.BITS:
return (cipDataToNumbers(data)*.intValue()).collect { bits ->
Integer.toBinaryString(bits as int).padLeft(32, '0') // BITS left padded 0 to 32 bits
.reverse().toCharArray().collect { it == '1' } // string reversed to match bits order
}.flatten() as List<Boolean>
default:
throw new IllegalArgumentException("${data.type} can not convert to Boolean")
}
}
List<Boolean> readBooleanTag(final String tagName, final int elementCount) {
final EtherNetIP plc = new EtherNetIP(plcIp, 0)
try {
plc.connectTcp()
final CIPData.Type tagType = plc.readTag(tagName).type
switch (tagType) {
case CIPData.Type.BOOL:
if (elementCount == 1) { // single element
return cipDataToBooleans(plc.readTag(tagName))
} else { // multiple elements (50 for each group to prevent overflow)
final List<List<Integer>> groupByEvery50 = (0..<elementCount).collate(50, true)
return groupByEvery50.indexed().collect { groupIdx, group ->
cipDataToBooleans(plc.readTag("${tagName}[${groupIdx * 50}]", group.size() as short))
}.flatten() as List<Boolean>
}
case CIPData.Type.BITS:
if (elementCount <= 32) { // single BITS (it's actually DINT)
return cipDataToBooleans(plc.readTag(tagName)).take(elementCount)
} else { // multiple BITS
final List<List<Integer>> groupByEvery32 = (0..<elementCount).collate(32, true)
return (groupByEvery32.indexed().collect { groupIdx, group ->
cipDataToBooleans(plc.readTag("${tagName}[${groupIdx}]"))
}.flatten() as List<Boolean>).take(elementCount)
}
default:
throw new IllegalArgumentException("$tagName datatype is $tagType so that can not get boolean value")
}
} catch (e) {
log.warn("Error occurred while reading boolean tag $tagName ($elementCount count) : ${e.message}")
throw e
} finally {
plc.close()
}
}
Once again a huge thanks, this makes sense now. I will play around with this code, but I think you set me on the right path! Also thanks for sharing your code snippets!
Once small question, most of this code is using stream syntax right? flatten, collect, etc. Does this only work with certain version of Java, I am using Java 11.0.3 due to using updated JavaFX.
Edit: Nvm I am an idiot, please take note if you use SoftLogix 5800 you will have issues reading individual items from boolean arrays. I just rigged up an L73 on my lab, and I can read any individual item on an array. I've been using this, and a python library with no issues until now. Lesson learned.
List<String> myTagList = new ArrayList<>();
myTagList.add("BoolArr[4]");
myTagList.add("BoolArr[5]");
myTagList.add("Start");
myTagList.add("MyBools[312]");
myTagList.add("Stop");
myTagList.add("Level_01");
DINT (0x00C4)
1
DINT (0x00C4)
0
BOOL (0x00C1)
-1
DINT (0x00C4)
1
BOOL (0x00C1)
0
REAL (0x00CA)
34.7
I use Groovy language (some kind of "enhanced" Java), it brings fluent api and other powerful features into Java since Java 6. Like Kotlin, Groovy also needs JVM.
The stream-like syntax you see here is actually Closures, and Groovy added lots of API into well-known Java basic classes, called Groovy Development Kit enhancements.
I use Groovy 2.5.x with Java 8, it's handy for script, Java project, Spring Boot, ..., and so on. :)
The biggest reason I use Groovy but not Kotlin is that Groovy is fully compatible with Java syntax (which means you can paste a Java code and compile it without issues).
When you said groovy, I just thought you meant cool lmao. Didn't think about another language. Glad you clarified it, it looks interesting. I will make sure to check it out. I didn't know there were so many jvm languages besides kotlin. Is what I get for living under the rock of Java. :smile:
I am trying to read bool[300] array, I read about the 500bytes limit for plc. Ideally I'd like to read one at a time, but that fails due to the buffer limit. What is the best way to read an array? I couldn't find an example on the test classes.
I tried, reading a smaller array of 32 indices: plc.readTag("Bools", short(32)) plc.readTag("Bools")
Should I use another method for arrays?