AdvancedClimateSystems / uModbus

Python implementation of the Modbus protocol.
Mozilla Public License 2.0
211 stars 82 forks source link

Simulating a PLC #73

Open piertoni opened 5 years ago

piertoni commented 5 years ago

Hi, I would like to simulate a PLC. In my case when I read an address, let's say 10 words starting from 0x200, I get some values back (I.E. a string containing the Producer), instead when I read 10 words from 0x201 I will get completely different values. Which is the proper way to realize this? For what I have understood with app.route I can only define one value at a time. Is there a way to get the lenght of the request at application level?

The workaround that came to my mind was to trigger the modification of regs_word when the first register is requested, like:

    @app.route(slave_ids=[1],function_codes=[3], addresses=(0x200,1) ):
    def producer(slave_id, function_code, address):
        regs_word[0x200] = 0xaabb # first registers
        regs_word[0x201] = 0x2231 # prepares other registers
        regs_word[0x202] = 0x231a # prepares other registers
        # ...
        return regs_word[0x200]

    @app.route(slave_ids=[1],function_codes=[3], addresses=(0x201,15) ):
    def other_regs(slave_id, function_code, address):
        return regs_word[address]

Thanks

OrangeTux commented 5 years ago

Hello @piertoni,

Thanks for your question. From what I understand you want the value of a register to be depending on the start address of the request. Am I correct?

For what I have understood with app.route I can only define one value at a time.

It is correct that the the handlers should return only 1 value. If a client requests multiple registers than the handler is executed multiple times using a different value for each address.

But wouldn't something like this work for your?

@app.route(slave_ids=[1],function_codes=[3], addresses=list(range(0x200,0x215))):
    def producer(slave_id, function_code, address):
        if address == 0x200:
            # prepare all your other registers

            regs_word[0x200] = 0xaabb # first registers
            regs_word[0x201] = 0x2231 # prepares other registers
            regs_word[0x202] = 0x231a # prepares other registers

     return regs_word[0x200]
piertoni commented 5 years ago

Hello @piertoni,

Thanks for your question. From what I understand you want the value of a register to be depending on the start address of the request. Am I correct?

Exactly, in fact I am replicating the PLC behavior and if I read 16 regs starting from 0x200 I get one string I.E. Producer name, If I read 16 regs starting from 0x201 I get another string I.E. production date. Is not really a standard modbus behavior from my point of view... but is like this :smile:

For what I have understood with app.route I can only define one value at a time.

It is correct that the the handlers should return only 1 value. If a client requests multiple registers than the handler is executed multiple times using a different value for each address.

But wouldn't something like this work for your?

@app.route(slave_ids=[1],function_codes=[3], addresses=list(range(0x200,0x215))):
    def producer(slave_id, function_code, address):
        if address == 0x200:
            # prepare all your other registers

            regs_word[0x200] = 0xaabb # first registers
            regs_word[0x201] = 0x2231 # prepares other registers
            regs_word[0x202] = 0x231a # prepares other registers

     return regs_word[0x200]

Yes, this will work better than my code! Thanks! It's a hack anyway but for a simulator I think is fine. Really thanks!

piertoni commented 5 years ago

mmm thinking better I don't know if this (in general) will work because should be something like this:

    @app.route(slave_ids=[1],function_codes=[3], addresses=list(range(0x200,0x215))):
    def producer(slave_id, function_code, address):
        if address == 0x200:
            # prepare all your other registers

            regs_word[0x200] = 0xaabb # first registers
            regs_word[0x201] = 0x2231 # prepares other registers
            regs_word[0x202] = 0x231a # prepares other registers

     return regs_word[address]

    @app.route(slave_ids=[1],function_codes=[3], addresses=list(range(0x201,0x216))):
    def production_date(slave_id, function_code, address):
        if address == 0x201:
            # prepare all your other registers

            regs_word[0x200] = 0x1111 # first registers
            regs_word[0x201] = 0x0000 # prepares other registers
            regs_word[0x202] = 0x123b # prepares other registers

     return regs_word[address]

In fact the region are overlapping... I don't think will work, what do you think?

OrangeTux commented 5 years ago

It's a hack anyway but for a simulator I think is fine.

I am not sure if there is a non-hacky option. To be fair, your requirement of having registers values depend on the start address seem a little bit odd for me.

In fact the region are overlapping... I don't think will work, what do you think?

I am not exactly how this will work out, but I don't think it will work as you want it to work. At least uModbus is not intended to work that way when I created this package.

piertoni commented 5 years ago

I am not sure if there is a non-hacky option. To be fair, your requirement of having registers values depend on the start address seem a little bit odd for me.

Yeah, I agree, that seems odd to me too... but ask Wago producers... :smile:

In fact the region are overlapping... I don't think will work, what do you think?

I am not exactly how this will work out, but I don't think it will work as you want it to work. At least uModbus is not intended to work that way when I created this package.

If you think that it is possible to retrieve the length of the request just tell me where to start and eventually I can write the code. If not I will close the issue, thank you for uModbus, is cool! :smile:

OrangeTux commented 5 years ago

If you think that it is possible to retrieve the length of the request just tell me where to start and eventually I can write the code.

Assuming you run a Modbus TCP server than you could create your own request handler that extends from the umodbus.server.tcp.RequestHandler. If you than override than the umodbus.server.AbstractRequestHandler.execute_route() you have access to the info you want.