Custom function codes

729 views
Skip to first unread message

Ben Price

unread,
Feb 18, 2020, 8:53:46 AM2/18/20
to pymodbus
Hello,

Is it possible to send a Modbus TCP message with a non-standard function code?  I have a solar battery inverter that uses 0x42 (66) as the function code to control it's operating state. An example message from the documentation might be:

0x 01 42 01 00 55 55 87 56

Which puts it into Standby mode.  Or:

0x 01 42 01 02 05 DC DB 30

which instructs it to charge at 1.5kW.

Is it possible to send custom messages like this using pymodbus?  I've been looking at the examples, especially this one https://pymodbus.readthedocs.io/en/latest/source/example/custom_message.html, and I believe the answer to be yes but I can't get my head around the library enough from the documentation to work out how to adapt that example to my use case.

If I can get a pointer in the right direction I can probably work the implementation details out for myself, but right now I'm drawing blanks!

Yours,
Ben Price.

Galen Collins

unread,
Feb 18, 2020, 12:53:21 PM2/18/20
to pymo...@googlegroups.com
Ben, 

Yes you can. Simply create a request and response by inheriting from the base classes, filling in the implementation of what your function does, and then you just have to call client.execute(request).


--
You received this message because you are subscribed to the Google Groups "pymodbus" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pymodbus+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pymodbus/0e8dcb49-45b2-4090-8cb7-a61fed0e148f%40googlegroups.com.

Ben Price

unread,
Feb 18, 2020, 3:29:29 PM2/18/20
to pymodbus
Hi,

Thanks - here is what I have so far:

class CustomModbusResponse(ModbusResponse):
    function_code
= 66

    def __init__(self, values=None, **kwargs):
       
ModbusResponse.__init__(self, **kwargs)
       
self.values = values or []

   
def encode(self):
       
""" Encodes response pdu

        :returns: The encoded packet message
        """
        result = int2byte(len(self.values) * 2)
       
for register in self.values:
            result
+= struct.pack('>H', register)
       
return result

   
def decode(self, data):
       
""" Decodes response pdu

        :param data: The packet data to decode
        """
        byte_count = byte2int(data[0])
       
self.values = []
       
for i in range(1, byte_count + 1, 2):
           
self.values.append(struct.unpack('>H', data[i:i + 2])[0])


class CustomModbusRequest(ModbusRequest):
    function_code
= 66

    def __init__(self, address=None, **kwargs):
       
ModbusRequest.__init__(self, **kwargs)
       
self.address = address
       
self.count = 1

    def encode(self):
       
return struct.pack('>HH', self.address, self.count)

   
def decode(self, data):
       
self.address, self.count = struct.unpack('>HH', data)

   
def execute(self, context):
       
if not (1 <= self.count <= 0x7d0):
           
return self.doException(ModbusExceptions.IllegalValue)
       
if not context.validate(self.function_code, self.address, self.count):
           
return self.doException(ModbusExceptions.IllegalAddress)
        values
= context.getValues(self.function_code, self.address,
                                   self.count)
       
return CustomModbusResponse(values)



def set_charge():
    client
= ModbusClient('10.170.1.118', port=502)
    client
.connect()
    client
.register(CustomModbusResponse)
    request
= CustomModbusRequest(address=258, unit=UNIT)
    result
= client.execute(request)
   
print(result.values)
    client
.close()


That seems to work in so far as it sends a message with the right FC (0x42) but I can't see how to set the message payload.  I've tried

request = CustomModbusRequest(address=258, value=1500, unit=UNIT)


when creating the object but that doesn't seem to have any affect on the message that is sent.  It doesn't throw an error, just no change to the sent data so I'm thinking the value is not being included.

I'm going to go poking around in the library source to see if I can work out what I'm missing, but if anyone can set me straight quicker it'd be appriciated!

On Tuesday, 18 February 2020 17:53:21 UTC, Bashwork wrote:
Ben, 

Yes you can. Simply create a request and response by inheriting from the base classes, filling in the implementation of what your function does, and then you just have to call client.execute(request).


On Tue, Feb 18, 2020, 5:53 AM Ben Price <xyl...@gmail.com> wrote:
Hello,

Is it possible to send a Modbus TCP message with a non-standard function code?  I have a solar battery inverter that uses 0x42 (66) as the function code to control it's operating state. An example message from the documentation might be:

0x 01 42 01 00 55 55 87 56

Which puts it into Standby mode.  Or:

0x 01 42 01 02 05 DC DB 30

which instructs it to charge at 1.5kW.

Is it possible to send custom messages like this using pymodbus?  I've been looking at the examples, especially this one https://pymodbus.readthedocs.io/en/latest/source/example/custom_message.html, and I believe the answer to be yes but I can't get my head around the library enough from the documentation to work out how to adapt that example to my use case.

If I can get a pointer in the right direction I can probably work the implementation details out for myself, but right now I'm drawing blanks!

Yours,
Ben Price.

--
You received this message because you are subscribed to the Google Groups "pymodbus" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pymo...@googlegroups.com.

Ben Price

unread,
Feb 18, 2020, 4:38:40 PM2/18/20
to pymodbus
I've got as far as modifying my custom request class to:

class CustomModbusRequest(ModbusRequest):
    function_code
= 66

    def __init__(self, address=None, values=None, **kwargs):
       
ModbusRequest.__init__(self, **kwargs)
       
self.address = address
       
if not values:
            values
= []
       
elif not hasattr(values, '__iter__'):
            values
= [values]
       
self.values = values
       
self.byte_count = (len(self.values) + 7) // 8
        self.count = 1

    def encode(self):
       
return struct.pack('>HH', self.address, self.count)

   
def decode(self, data):
       
self.address, self.count = struct.unpack('>HH', data)

   
def execute(self, context):

        count
= len(self.values)
       
if not (1 <= count <= 0x7d0):
           
return self.doException(ModbusExceptions.IllegalValue)
       
if self.byte_count != (count + 7) // 8:
           
return self.doException(ModbusExceptions.IllegalValue)
       
if not context.validate(self.function_code, self.address, count):
           
return self.doException(ModbusExceptions.IllegalAddress)
        values
= context.setValues(self.function_code, self.address, self.values)
       
# values = context.getValues(self.function_code, self.address, count)
        return CustomModbusResponse(values)

And run out of steam for the night.  Will look again tomorrow!
Reply all
Reply to author
Forward
0 new messages