1
votes

I am trying to use my raspberry pi as a modbus slave and fetch data from my regular windows machine (representing the modbus master). Right now both slave and master are running on the same (windows) machine, to try things out beforehand.

This is the modbus slave I've adapted to my needs from the pymodbus updating server example:

    #!/usr/bin/env python
# --------------------------------------------------------------------------- #
# import the modbus libraries we need
# --------------------------------------------------------------------------- #
from pymodbus.server.asynchronous import StartTcpServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer

# --------------------------------------------------------------------------- #
# import the twisted libraries we need
# --------------------------------------------------------------------------- #
from twisted.internet.task import LoopingCall

# --------------------------------------------------------------------------- #
# configure the service logging
# --------------------------------------------------------------------------- #
import logging, random
logging.basicConfig(format="%(message)s")
log = logging.getLogger()
log.setLevel(logging.INFO)

# --------------------------------------------------------------------------- #
# define your callback process
# --------------------------------------------------------------------------- #
def update_temperature(kw):
    context = kw["context"]
    slave = kw["slave_info"] 
    functionCode_read = 3
    functionCode_write = 6

    # coils     :   0x0000  : fc=01 (read) fc=05 (write)
    # inputs    :   0x2710  : fc=02 (read)
    # inp.reg.  :   0x7530  : fc=04 (read)  
    # hold.reg. :   0x9C40  : fc=03 (read) fc=06 (write)

    values = context[slave["id"]].getValues(functionCode_read, slave["holding_registers"])
    log.info("old temperature values: " + str(values))

    for i in range(len(values)): # 
        if values[i] <= 16:
            values[i] = values[i]+1 if (random.random() >= 0.3) else values[i]-1
        elif values[i] >= 26:
            values[i] = values[i]+1 if (random.random() >= 0.7) else values[i]-1
        else:
            values[i] = values[i]+1 if (random.random() >= 0.5) else values[i]-1

    log.info("new temperature values: " + str(values))
    context[slave["id"]].setValues(functionCode_write, slave["holding_registers"], values)

def update_window_contact(kw):
    context = kw["context"]
    slave = kw["slave_info"] 
    functionCode_read = 1
    functionCode_write = 5

    # coils     :   0x0000  : fc=01 (read) fc=05 (write)
    # inputs    :   0x2710  : fc=02 (read)
    # inp.reg.  :   0x7530  : fc=04 (read)  
    # hold.reg. :   0x9C40  : fc=03 (read) fc=06 (write)

    values = context[slave["id"]].getValues(functionCode_read, slave["coils"])
    log.info("old contact values: " + str(values))

    for i in range(len(values)):
        if values[i] == 0:
            values[i] = 1 if (random.random() >= 0.95) else 0
        else: # value is 1
            values[i] = 0 if (random.random() >= 0.95) else 1

    log.info("new contact values: " + str(values))
    context[slave["id"]].setValues(functionCode_write, slave["coils"], values)


def run_updating_server():
    # ----------------------------------------------------------------------- # 
    # initializing devices
    # ----------------------------------------------------------------------- # 
    temperature_slave = { # temperature sensor
        "id": 0x01, 
        "coils": 0x0000,
        "inputs": 0x2710,
        "input_registers": 0x7530,
        "holding_registers": 0x9C40
    }
    contact_slave = { # window sensor
        "id": 0x02,
        "coils": 0x0000,
        "inputs": 0x2710,
        "input_registers": 0x7530,
        "holding_registers": 0x9C40
    } 

    store = {
        temperature_slave["id"]: ModbusSlaveContext( 
            co=ModbusSequentialDataBlock(address=temperature_slave["coils"], values=[None]),                  # coils  / discrete outp. (adress=0)
            di=ModbusSequentialDataBlock(address=temperature_slave["inputs"], values=[None]),              # inputs / discrete input (adress=10000)
            ir=ModbusSequentialDataBlock(address=temperature_slave["input_registers"], values=[None]),     # input register / analog input (adress=30000)
            hr=ModbusSequentialDataBlock(address=temperature_slave["holding_registers"], values=[16]),   # hold. register / analog outp. (adress=40000)
            zero_mode=True),
        contact_slave["id"]: ModbusSlaveContext( 
            co=ModbusSequentialDataBlock(address=temperature_slave["coils"], values=[1]),               # coils  / discrete outp. (adress=0)
            di=ModbusSequentialDataBlock(address=temperature_slave["inputs"], values=[None]),              # inputs / discrete input (adress=10000)
            ir=ModbusSequentialDataBlock(address=temperature_slave["input_registers"], values=[None]),     # input register / analog input (adress=30000)
            hr=ModbusSequentialDataBlock(address=temperature_slave["holding_registers"], values=[None]),     # hold. register / analog outp. (adress=40000)
            zero_mode=True)
    } 
    context = ModbusServerContext(slaves=store, single=False)

    # ----------------------------------------------------------------------- # 
    # initialize the server information
    # ----------------------------------------------------------------------- # 
    identity = ModbusDeviceIdentification()
    identity.VendorName = 'raspi2b'
    identity.ProductCode = 'r2b'
    identity.VendorUrl = 'http://hs-mannheim.de'
    identity.ProductName = 'Pymodbus Slave-Server'
    identity.ModelName = '2b'
    identity.MajorMinorRevision = '1.0.0'

    # ----------------------------------------------------------------------- # 
    # run the server you want
    # ----------------------------------------------------------------------- # 
    loop_temperature = LoopingCall(f=update_temperature, kw={"context":context, "slave_info": temperature_slave}) 
    loop_temperature.start(20, now=False) # initially delay by 1 sec

    loop_contact = LoopingCall(f=update_window_contact, kw={"context":context, "slave_info": contact_slave}) 
    loop_contact.start(20, now=False) # initially delay by 1 sec

    StartTcpServer(context, identity=identity, address=("localhost", 5020))

if __name__ == "__main__":
    run_updating_server()

It basically just updates temperature values and a window sensor's on/off-values. Now when I try to fetch the data vie the modbus master, which I again adapted from the pymodbus synchronous client example I get an error that says:

'[Input/Output] Modbus Error: [Invalid Message] Incomplete message received, expected at least 8 bytes (0 received)' from the assert-statement in line 48 and 52

Here's my client implementation:

#!/usr/bin/env python
# --------------------------------------------------------------------------- #
# import the various server implementations
# --------------------------------------------------------------------------- #
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
from influxdb import InfluxDBClient
# --------------------------------------------------------------------------- #
# configure the client logging
# --------------------------------------------------------------------------- #
import logging, json, time
FORMAT = ('%(asctime)-15s %(threadName)-15s '
          '%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.INFO)


def run_sync_client():
    # ------------------------------------------------------------------------#
    # choose the client you want
    # ------------------------------------------------------------------------#
    client = ModbusClient("localhost", port=5020) #'192.168.0.10'
    client.connect()

    # ------------------------------------------------------------------------#
    # specify slave to query
    # ------------------------------------------------------------------------#
    temperature_slave = { # temperature sensor
        "id": 0x01, 
        "coils": 0x0000,
        "inputs": 0x2710,
        "input_registers": 0x7530,
        "holding_registers": 0x9C40
    }
    contact_slave = { # window sensor
        "id": 0x02,
        "coils": 0x0000,
        "inputs": 0x2710,
        "input_registers": 0x7530,
        "holding_registers": 0x9C41
    } 

    # ----------------------------------------------------------------------- #
    # example requests
    # ----------------------------------------------------------------------- #
    log.debug("Read from a Coil")
    co_data = client.read_coils(address=contact_slave["coils"], count=1, SLAVE=contact_slave["id"])
    assert(not co_data.isError())     # test that we are not an error

    log.debug("Read holding registers")
    hr_data = client.read_holding_registers(address=temperature_slave["holding_registers"], count=1, SLAVE=temperature_slave["id"])
    assert(not hr_data.isError())     # test that we are not an error

    # ----------------------------------------------------------------------- #
    # close the client
    # ----------------------------------------------------------------------- #
    client.close()

if __name__ == "__main__":
    run_sync_client()

I don't think I've fully understood the modbus protocol, so I have a hard time figuring out why this error occurs. If somebody could help me understand it better and ideally solve this issue I'd be really glad.

1

1 Answers

0
votes

using the unit keyword argument instead of SLAVE when reading the register/coil solves this issue:

so instead of

co_data = client.read_coils(address=contact_slave["coils"], count=1, SLAVE=contact_slave["id"])
assert(not co_data.isError())

log.debug("Read holding registers")
hr_data = client.read_holding_registers(address=temperature_slave["holding_registers"], count=1, SLAVE=temperature_slave["id"])

write

co_data = client.read_coils(address=contact_slave["coils"], count=1, UNIT=contact_slave["id"])
assert(not co_data.isError())

log.debug("Read holding registers")
hr_data = client.read_holding_registers(address=temperature_slave["holding_registers"], count=1, UNIT=temperature_slave["id"])