pymodbus-dev / pymodbus

A full modbus protocol written in python
Other
2.18k stars 895 forks source link

ModbusSimulatorContext doesn't create correct register layout with `"shared blocks": False` option #1960

Closed realrushen closed 5 months ago

realrushen commented 5 months ago

Versions

Pymodbus Specific

Description

I'm using datastore simulator example with modified demo_config that you can see below. With this configuration im expecting that server will create 2 blocks: holding registers with addres 1-8 and input registers with address 1-20. All registers should have uint16 type with default value 1.

When im runing script im getting exception:

Traceback (most recent call last):
  File "D:\code\MyProjects\pymodbus-sandbox\datastore_simulator.py", line 217, in <module>
    asyncio.run(main(), debug=True)
  File "C:\Users\karakchiev\AppData\Local\Programs\Python\Python38\lib\asyncio\runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "C:\Users\karakchiev\AppData\Local\Programs\Python\Python38\lib\asyncio\base_events.py", line 616, in run_until_complete
    return future.result()
  File "D:\code\MyProjects\pymodbus-sandbox\datastore_simulator.py", line 212, in main
    run_args = setup_simulator(cmdline=cmdline)
  File "D:\code\MyProjects\pymodbus-sandbox\datastore_simulator.py", line 184, in setup_simulator
    context = ModbusSimulatorContext(setup, actions)
  File "D:\code\MyProjects\pymodbus-sandbox\venv\lib\site-packages\pymodbus\datastore\simulator.py", line 469, in __init__
    Setup(self).setup(config, custom_actions)
  File "D:\code\MyProjects\pymodbus-sandbox\venv\lib\site-packages\pymodbus\datastore\simulator.py", line 354, in setup
    self.handle_types()
  File "D:\code\MyProjects\pymodbus-sandbox\venv\lib\site-packages\pymodbus\datastore\simulator.py", line 285, in handle_types
    raise RuntimeError(f'Error "{section}" {start}, {stop} illegal')
RuntimeError: Error "uint16" 1, 28 illegal

If i change "uint16": [[1, 28]] to uint16": [[1, 27]] server starting and listeting to requests.

Then im trying to request one input register from address 1 with modpoll, but it doesn't exist:

(venv) PS D:\code\MyProjects\pymodbus-sandbox> modpoll -1 -p 5020 -t 3 -r 1 -c 1 localhost
modpoll 3.10 - FieldTalk(tm) Modbus(R) Master Simulator
Copyright (c) 2002-2021 proconX Pty Ltd
Visit https://www.modbusdriver.com for Modbus libraries and tools.

Protocol configuration: MODBUS/TCP, FC4
Slave configuration...: address = 1, start reference = 1, count = 1
Communication.........: localhost, port 5020, t/o 1.00 s, poll rate 1000 ms
Data type.............: 16-bit register, input register table

-- Polling slave...
Illegal Data Address exception response!

But from address 2 with count 27 it returns full block of data:

(venv) PS D:\code\MyProjects\pymodbus-sandbox> modpoll -1 -p 5020 -t 3 -r 2 -c 27 localhost
modpoll 3.10 - FieldTalk(tm) Modbus(R) Master Simulator
Copyright (c) 2002-2021 proconX Pty Ltd
Visit https://www.modbusdriver.com for Modbus libraries and tools.

Protocol configuration: MODBUS/TCP, FC4
Slave configuration...: address = 1, start reference = 2, count = 27
Communication.........: localhost, port 5020, t/o 1.00 s, poll rate 1000 ms
Data type.............: 16-bit register, input register table

-- Polling slave...
[2]: 1
[3]: 1
[4]: 1
[5]: 1
[6]: 1
[7]: 1
[8]: 1
[9]: 1
[10]: 1
[11]: 1
[12]: 1
[13]: 1
[14]: 1
[15]: 1
[16]: 1
[17]: 1
[18]: 1
[19]: 1
[20]: 1
[21]: 1
[22]: 1
[23]: 1
[24]: 1
[25]: 1
[26]: 1
[27]: 1
[28]: 1

Code and Logs

# simulator config
demo_config = {
    "setup": {
        "co size": 0,
        "di size": 0,
        "hr size": 8,
        "ir size": 20,
        "shared blocks": False,
        "type exception": False,
        "defaults": {
            "value": {
                "bits": 0x0001,
                "uint16": 1,
                "uint32": 45000,
                "float32": 127.4,
                "string": "X",
            },
            "action": {
                "bits": None,
                "uint16": None,
                "uint32": None,
                "float32": None,
                "string": None,
            },
        },
    },
    "invalid": [
    ],
    "write": [
        [1, 8],
    ],
    "bits": [

    ],
    "uint16": [
       [1, 28]
    ],
    "uint32": [
    ],
    "float32": [
    ],
    "string": [
    ],
    "repeat": [],
}
janiversen commented 5 months ago

You have shared block false, so you only have 20 input registers not 28. Input registers can be addressed as 1 to 20.

janiversen commented 5 months ago

Please look at the simulator documentation where we explain the difference between the memory model and the addressing model.

I will make a test case, based on your config file....but at least 28 is clearly illegal since it's a memory address one too high.

janiversen commented 5 months ago

We have a rather large test set of both shared / non-shared (please see test_simulator.py) that have not found any problems, but I will try to add the problems you describe.

realrushen commented 5 months ago

You have shared block false, so you only have 20 input registers not 28. Input registers can be addressed as 1 to 20.

If I have only 20 input registers, why i can read them? My expectations was that they should not exist, and server should respond with Illegal Data Address exception response.

We have a rather large test set of both shared / non-shared (please see test_simulator.py) that have not found any problems, but I will try to add the problems you describe.

As i can see it has only "shared blocks": True configuration. I cant find in code where you change that parameter. It seems that the case of "shared blocks": False" has not been tested yet.

janiversen commented 5 months ago

Look in test_simulator.py, we change shared to false in the code.

The problem is that you read with a starting address that are legal and a count, that takes you into illegal terretory, that is something we do not test. And please remember you claim the register layout is false, which it is not, since you totally forgot register 0.

PR #1967 adds a test, of the address range (0, max_register and max_register+1), just to be sure.