ornl-epics / etherip

Java lib. for Ethernet/IP (AllenBradley ControlLogix, Compact Logix PLCs)
Eclipse Public License 1.0
111 stars 69 forks source link

Example for Boolean Array #29

Closed TheFern2 closed 5 years ago

TheFern2 commented 5 years ago

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?

cwchien commented 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,

  1. given the original array size is S (it's 300 here), and batch size is B (it's 50 here)
  2. find out the number of batches (S/B) and the remainder if there's any (R = S % B)
  3. readTag("TagName[B * batchIndex]", short(B)) in for loop
  4. and the last if there's any readTag("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 :)

TheFern2 commented 5 years ago

Thanks for the detailed response @cwchien!

TheFern2 commented 5 years ago

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);
TheFern2 commented 5 years ago

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
cwchien commented 5 years ago

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()
    }
}
TheFern2 commented 5 years ago

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
cwchien commented 5 years ago

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).

TheFern2 commented 5 years ago

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: