brainelectronics / micropython-modbus

MicroPython Modbus RTU Slave/Master and TCP Server/Slave library
GNU General Public License v3.0
104 stars 45 forks source link

Value limits accepted by Slave on the Holding Register (HREGS) #69

Open beyonlo opened 1 year ago

beyonlo commented 1 year ago

Description

Is there a way to set what are the values allowed and/or min and max values allowed on the Slave?

"HREGS": {
    "EXAMPLE_HREG": {
        "register": 93,
        "len": 9,
        "val": [1, 38, 0, 1600, 2150, 5067, 2564, 8450, 3456]
    }

Example 1: The address 93 is number 29. I would like that address accept just min value 1 and max value 8

Example 2: The address 96 is number 1600. I would like that address accept just one of this list [1600, 2100, 2300, 5000, 132, 8003]

So, if ModBus Master try to set a different value than that allowed, will be not accepted by the Slave, and an error will be returned to the Master.

Is possible to do that or something similar?

Thank you!

Reproduction steps

1. 2. 3. ...

MicroPython version

1.19.1

MicroPython board

ESP32

MicroPython Modbus version

2.4.0rc62.dev56

Relevant log output

No response

User code

No response

Additional informations

No response

brainelectronics commented 1 year ago

@beyonlo I see your need on this topic. As of now, according to the docs

Callbacks can be registered to be executed after setting a register with on_set_cb or to be executed before getting a register with on_get_cb.

https://github.com/brainelectronics/micropython-modbus/blob/fa6430c9737cefda9cc744a6f78b9a7d1cbacaa8/umodbus/modbus.py#L246-L252

Maybe we could introduce a return value to the callbacks and stop executing further steps if the return value is e.g. False. This would of course only work for the on_get_cb as it is executed before the data is requested, but makes no sense to stop reading/returning data. A new callback would be necessary to be executed before setting the data. Like this e.g.

if self._register_dict[reg_type][address].get('pre_set_cb', 0):
    _cb = self._register_dict[reg_type][address]['on_set_cb']
    if _cb(reg_type=reg_type, address=address, val=val):
        return request.send_exception(Const.ILLEGAL_DATA_VALUE)

# the following code exists already
self._set_changed_register(reg_type=reg_type,
                           address=address,
                           value=val)
if self._register_dict[reg_type][address].get('on_set_cb', 0):
    _cb = self._register_dict[reg_type][address]['on_set_cb']
    _cb(reg_type=reg_type, address=address, val=val)

to be used as (not tested)

def my_holding_register_get_cb(reg_type, address, val):
    print('Custom callback, called on getting {} at {}, currently: {}'.
          format(reg_type, address, val))

def my_holding_register_set_cb(reg_type, address, val):
    print('Custom callback, called on setting {} at {} to: {}'.
          format(reg_type, address, val))

def my_holding_register_pre_set_cb(reg_type, address, val):
    print('Custom callback, called on setting {} at {} to: {}'.
          format(reg_type, address, val))

    if val not in range(0, 101):
        return False
    else:
        return True

client.add_hreg(
    address=93,
    value=19,
    on_pre_set_cb=my_holding_register_pre_set_cb,
    on_set_cb=my_holding_register_set_cb,
    on_get_cb=my_holding_register_get_cb
)
beyonlo commented 1 year ago

@brainelectronics Excellent! Thank you for the example :)

beyonlo commented 1 year ago

@GimmickNG could you please to port this example for the your async examples?

GimmickNG commented 1 year ago

@beyonlo Looking at this, I'm not sure it needs to specifically be ported for the async examples - the only changes required seem to be adding the pre_get_cb to modbus.py and then creating the example for that. That is, whatever works for the sync version will in theory work exactly the same for the async version with no changes required at all, since the async version is just an extension of the sync client/server.

beyonlo commented 1 year ago

@GimmickNG Could you please to create/add an example for that? Maybe just add an entry to the host_tests.py? So, I can test it for you :)

Thank you in advance!