drunsinn / pyLSV2

A pure Python3 implementation of the LSV2 protocol
MIT License
64 stars 24 forks source link

Reading PLC memory via memory address returns different values than reading via data path #48

Open Jul-Sto opened 1 year ago

Jul-Sto commented 1 year ago

Reading PLC memory via memory address like

con.read_plc_memory(356, pyLSV2.MemoryType.DWORD, 1)

returns the following:

when accessing the same addresses via data path at the same time it returns the following:

con.read_data_path("/PLC/memory/D/356")

I'm not sure if I've missed anything, but up to pyLSV2 version 0.7.7 the values were the same.

I've discovered this on an iTNC530 / 606420 04 SP20. Help would be much appreciated.

drunsinn commented 1 year ago

Oh, that seems to be a bug that was introduced with #40. I will look into it and get back to you.

drunsinn commented 1 year ago

Can you please check if there are any warnings or errors in the log?

Jul-Sto commented 1 year ago

I'm not getting any warnings or errors.

The output for addresses 356, 364, 368 and 360 doesn't change, it's always [4210752250] when reading via read_plc_memory() function.

drunsinn commented 1 year ago

Ok, this seems to be more of a problem with the documentation than with th e code.

To get the same results with read_plc_memory and read_data_path you have to adjust for the actual size of the data type in memory. The current implementation of read_plc_memory is taking care of that but the data path doesn't since it is decoded on the control. A double word (DWORD) is four bytes long, therefore the address has to be multiplied by four.

Example:

for mem_address in [0, 1, 2, 4, 8, 12, 68, 69, 151, 300, 368]:
    v1 = lsv2.read_plc_memory(mem_address, pyLSV2.MemoryType.DWORD, 1)[0]
    v2 = lsv2.read_data_path("/PLC/memory/D/%d" % (mem_address * 4))
    assert v1 == v2
rafasimple commented 1 year ago

WHEN im trying this print("## double word: {}".format(con.read_plc_memory(8016, MemoryType.DWORD, 50))), return only 0 values i dont know why,this is the value in the cnc +66977274

npalmerDNX commented 1 year ago

I am seeing this same problem just with WORD as well, both calls worked and returned the same non-zero value in 0.7.7 but the latest versions are hitting this.

>>> con.read_plc_memory(39416, mem_type=MemoryType.WORD)
DEBUG:LSV2 Client:system parameters already in memory, return previous values
DEBUG:LSV2 TCP:telegram to transmit: command CMD.A_LG payload length 9 bytes data: bytearray(b'\x00\x00\x00\tA_LGPLCDEBUG\x00')
DEBUG:LSV2 TCP:received block of data with length 8
DEBUG:LSV2 Client:expected response received: RSP.T_OK
DEBUG:LSV2 Client:login executed successfully for login PLCDEBUG
DEBUG:root:memory type allows 126 elements per telegram, split request into 1 group(s)
DEBUG:root:current transfer group 0 has 1 elements
DEBUG:LSV2 TCP:telegram to transmit: command CMD.R_MB payload length 5 bytes data: bytearray(b'\x00\x00\x00\x05R_MB\x00\x0140\x02')
DEBUG:LSV2 TCP:received block of data with length 10
DEBUG:LSV2 Client:expected response received: RSP.S_MB
DEBUG:root:read 1 value(s) from address 39416
DEBUG:root:read a total of 1 value(s)
[0]

>>> con.read_data_path("/PLC/memory/W/39416")
DEBUG:LSV2 TCP:telegram to transmit: command CMD.A_LG payload length 5 bytes data: bytearray(b'\x00\x00\x00\x05A_LGDATA\x00')
DEBUG:LSV2 TCP:received block of data with length 8
DEBUG:LSV2 Client:expected response received: RSP.T_OK
DEBUG:LSV2 Client:login executed successfully for login DATA
DEBUG:LSV2 TCP:telegram to transmit: command CMD.R_DP payload length 24 bytes data: bytearray(b'\x00\x00\x00\x18R_DP\x00\x00\x00\x00\\PLC\\memory\\W\\39416\x00')
DEBUG:LSV2 TCP:received block of data with length 14
DEBUG:LSV2 Client:expected response received: RSP.S_DP
INFO:LSV2 Client:successfully read data path: \PLC\memory\W\39416 and got value '21'
21
drunsinn commented 1 year ago

@npalmerDNX as mentioned in an earlier comment, the addressing works different between the data_path and plc_memory. In the case of a WORD, the factor is two.

grafik

In earlier versions of pyLSV2 the calculation of the correct memory address of the read_plc_memory function was wrong which lead to an offset. This meant that bigger chunks of memory returned broken values. For read_data_path the calculation of the offset has to be done by the application.

npalmerDNX commented 1 year ago

I am including 2 screenshots of behavior that I am seeing and trying to validate against. I used the Inventcom testing tool for PLC data and noticed that my W20 shows 0 and W40 show 8. image image

I ran some test code using the pyLSV2 library to see if I could map those properly, but I noticed that the PLC call in the library returns an 8 not a 0 where as the datapath W20 instance (without changing the memory address like you show above) results in 0. If I apply the address adjustment like you mentioned it would get getting datapath W40 which is 8. I believe that the memory addressing used for the read_plc_memory is double the value that it should be.

def scan_plc(con, start=10000, end=19999):
    results = []
    for wnum in range(start, end, 1):
        count = con.read_plc_memory(first_element=wnum, mem_type=MemoryType.WORD)
        print(f"PLC W{wnum} = {count}")
        results.append((wnum,count))
    return results

def scan_datapath(con, start=10000, end=19999):
    results = []
    for wnum in range(start, end, 1):
        if (wnum % 2) != 0:
            continue
        count = con.read_data_path(f"/PLC/memory/W/{wnum}")
        print(f"Datapath W{wnum} = {count}")
        results.append((wnum,count))
    return results

res = scan_datapath(conObj, 10, 42) Datapath W10 = 0 Datapath W12 = 30776 Datapath W14 = 25901 Datapath W16 = 0 Datapath W18 = 0 Datapath W20 = 0 Datapath W22 = 0 Datapath W24 = 0 Datapath W26 = 0 Datapath W28 = 0 Datapath W30 = 0 Datapath W32 = 2 Datapath W34 = 0 Datapath W36 = 0 Datapath W38 = 8 Datapath W40 = 8 res = scan_plc(conObj, 10, 42) PLC W10 = [0] PLC W11 = [0] PLC W12 = [0] PLC W13 = [0] PLC W14 = [0] PLC W15 = [0] PLC W16 = [2] PLC W17 = [0] PLC W18 = [0] PLC W19 = [8] PLC W20 = [8] PLC W21 = [0] PLC W22 = [0] PLC W23 = [32767] PLC W24 = [0] PLC W25 = [0] PLC W26 = [0] PLC W27 = [0] PLC W28 = [0] PLC W29 = [0] PLC W30 = [0] PLC W31 = [0] PLC W32 = [0] PLC W33 = [0] PLC W34 = [0] PLC W35 = [0] PLC W36 = [0] PLC W37 = [0] PLC W38 = [0] PLC W39 = [0] PLC W40 = [0] PLC W41 = [0]

drunsinn commented 1 year ago

This is very strange. If you look at https://github.com/drunsinn/pyLSV2/blob/dc06e43c1f826be84169fb6b6058f804fc689688/tests/test_plc_read.py#L112 you can see the test I added to make sure the memory addressing works as intended. I would have expected this test to fail if there was a problem. Even changing the list of more or less random values to range(0, 500) for all three tests still does not fail.

I think you also have a bit of a problem in your code. The modulo comparison basically skips every second element which results in an offset. Take a look at this and its output:

import pyLSV2
lsv2 = pyLSV2.LSV2("192.168.56.102", port=19000, timeout=5, safe_mode=False)
lsv2.connect()

for wnum in range(0, 42, 1):
    v1 = lsv2.read_plc_memory(first_element=wnum, mem_type=pyLSV2.MemoryType.WORD)[0]
    if (wnum % 2) == 0:
        v2 = lsv2.read_data_path(f"/PLC/memory/W/{wnum}")
    else:
        v2 = "skipp"
    v3 = lsv2.read_data_path(f"/PLC/memory/W/{wnum*2}")
    print(f"Address {wnum:>5}: PLC {v1:>7} - DP*1 {v2:>7} - DP*2 {v3:>7}")
Address     0: PLC       0 - DP*1       0 - DP*2       0
Address     1: PLC       0 - DP*1   skipp - DP*2       0
Address     2: PLC     256 - DP*1       0 - DP*2     256
Address     3: PLC     770 - DP*1   skipp - DP*2     770
Address     4: PLC     255 - DP*1     256 - DP*2     255
Address     5: PLC       0 - DP*1   skipp - DP*2       0
Address     6: PLC     150 - DP*1     770 - DP*2     150
Address     7: PLC       0 - DP*1   skipp - DP*2       0
Address     8: PLC       0 - DP*1     255 - DP*2       0
Address     9: PLC       0 - DP*1   skipp - DP*2       0
Address    10: PLC       0 - DP*1       0 - DP*2       0
Address    11: PLC       0 - DP*1   skipp - DP*2       0
Address    12: PLC       0 - DP*1     150 - DP*2       0
Address    13: PLC       0 - DP*1   skipp - DP*2       0
Address    14: PLC       0 - DP*1       0 - DP*2       0
Address    15: PLC       0 - DP*1   skipp - DP*2       0
Address    16: PLC       0 - DP*1       0 - DP*2       0
Address    17: PLC       0 - DP*1   skipp - DP*2       0
Address    18: PLC       0 - DP*1       0 - DP*2       0
Address    19: PLC       0 - DP*1   skipp - DP*2       0
Address    20: PLC       0 - DP*1       0 - DP*2       0
Address    21: PLC       0 - DP*1   skipp - DP*2       0
Address    22: PLC   10080 - DP*1       0 - DP*2   10080
Address    23: PLC       0 - DP*1   skipp - DP*2       0
Address    24: PLC       0 - DP*1       0 - DP*2       0
Address    25: PLC       0 - DP*1   skipp - DP*2       0
Address    26: PLC       0 - DP*1       0 - DP*2       0
Address    27: PLC       0 - DP*1   skipp - DP*2       0
Address    28: PLC       0 - DP*1       0 - DP*2       0
Address    29: PLC       0 - DP*1   skipp - DP*2       0
Address    30: PLC       0 - DP*1       0 - DP*2       0
Address    31: PLC       0 - DP*1   skipp - DP*2       0
Address    32: PLC       0 - DP*1       0 - DP*2       0
Address    33: PLC       0 - DP*1   skipp - DP*2       0
Address    34: PLC       0 - DP*1       0 - DP*2       0
Address    35: PLC       0 - DP*1   skipp - DP*2       0
Address    36: PLC       0 - DP*1       0 - DP*2       0
Address    37: PLC       0 - DP*1   skipp - DP*2       0
Address    38: PLC       0 - DP*1       0 - DP*2       0
Address    39: PLC       0 - DP*1   skipp - DP*2       0
Address    40: PLC       0 - DP*1       0 - DP*2       0
Address    41: PLC       0 - DP*1   skipp - DP*2       0

As I see it, by multiplying the address for the data path with two I get the same result as with 'read_plc_memory'. The approach of skipping every other address gives an offset.

npalmerDNX commented 1 year ago

I ran your test script and I see that the values of PLC and DP2 are the same, but they are not the representation of what the W# is. I have included the screenshot run at the same time to show that the PLC tool is showing a value that equals DP1 for W20 while the values shown from the test script PLC and DP2 are equal to DP1 of W40 You can see this pattern hold true for W6 PLC, DP2 vs W12 DP1 and W7 PLC,DP2 vs W14 DP1

image image

Address     0: PLC       0 - DP*1       0 - DP*2       0
Address     1: PLC       0 - DP*1   skipp - DP*2       0
Address     2: PLC       0 - DP*1       0 - DP*2       0
Address     3: PLC       0 - DP*1   skipp - DP*2       0
Address     4: PLC       0 - DP*1       0 - DP*2       0
Address     5: PLC       0 - DP*1   skipp - DP*2       0
Address     6: PLC  -21487 - DP*1       0 - DP*2  -21487
Address     7: PLC   25901 - DP*1   skipp - DP*2   25901
Address     8: PLC       0 - DP*1       0 - DP*2       0
Address     9: PLC       0 - DP*1   skipp - DP*2       0
Address    10: PLC       0 - DP*1       0 - DP*2       0
Address    11: PLC       0 - DP*1   skipp - DP*2       0
Address    12: PLC       0 - DP*1  -21487 - DP*2       0
Address    13: PLC       0 - DP*1   skipp - DP*2       0
Address    14: PLC       0 - DP*1   25901 - DP*2       0
Address    15: PLC       0 - DP*1   skipp - DP*2       0
Address    16: PLC       2 - DP*1       0 - DP*2       2
Address    17: PLC       0 - DP*1   skipp - DP*2       0
Address    18: PLC       0 - DP*1       0 - DP*2       0
Address    19: PLC      10 - DP*1   skipp - DP*2      10
Address    20: PLC      10 - DP*1       0 - DP*2      10
Address    21: PLC       0 - DP*1   skipp - DP*2       0
Address    22: PLC       0 - DP*1       0 - DP*2       0
Address    23: PLC   32767 - DP*1   skipp - DP*2   32767
Address    24: PLC       0 - DP*1       0 - DP*2       0
Address    25: PLC       0 - DP*1   skipp - DP*2       0
Address    26: PLC       0 - DP*1       0 - DP*2       0
Address    27: PLC       0 - DP*1   skipp - DP*2       0
Address    28: PLC       0 - DP*1       0 - DP*2       0
Address    29: PLC       0 - DP*1   skipp - DP*2       0
Address    30: PLC       0 - DP*1       0 - DP*2       0
Address    31: PLC       0 - DP*1   skipp - DP*2       0
Address    32: PLC       0 - DP*1       2 - DP*2       0
Address    33: PLC       0 - DP*1   skipp - DP*2       0
Address    34: PLC       0 - DP*1       0 - DP*2       0
Address    35: PLC       0 - DP*1   skipp - DP*2       0
Address    36: PLC       0 - DP*1       0 - DP*2       0
Address    37: PLC       0 - DP*1   skipp - DP*2       0
Address    38: PLC       0 - DP*1      10 - DP*2       0
Address    39: PLC       0 - DP*1   skipp - DP*2       0
Address    40: PLC       0 - DP*1      10 - DP*2       0
Address    41: PLC       0 - DP*1   skipp - DP*2       0
drunsinn commented 1 year ago

I still quite follow your reasoning. According to the memory tables on the control the address values are correct. If you check the screenshot I posted here, word 11 is the same as via the library as on the control.

If you are not happy with the current state you are more than welcome to create pull request for a new function that use another addressing scheme. Please note that since the current implementation works as far as I can see, and some other application might already use it the way it is now, I don't want to break compatibility.

npalmerDNX commented 1 year ago

image image I ran your test against my 530 simulator and I am still seeing the same results where the Address of IDX 20 returns 0 based on the PLC call but IDX10 shows the correct part count value. This also led me to review your screenshot a bit more to see where I was going wrong when I noticed that you were requesting W11 but the screenshot shows that the value is stored in W22 (second line Word 20 Column +2)

image

drunsinn commented 1 year ago

I interpreted the blue column numbers a bit differently. Since I have no manual I assume that these numbers count the number of bytes from the start of memory. The increments change when switching between byte, word and double from 1 to 2 to 4 but the line numbers only change when switching from byte to word or double. This lead me to believe that they count the number of bytes instead of the address of the memory value. Again, if you prefer a function that uses the numbering scheme that counts bytes instead of the memory values I have no problem integrating a pull request. The current behavior is not broken, it works for all intents and purposes. Changing the existing behaviour is not a direction I want to take.