Using Yoctopuce sensors with Modbus TCP

Using Yoctopuce sensors with Modbus TCP

Customers regularly ask us if YoctoHubs support the Modbus TCP protocol. The answer is negative, because the CPU of the YoctoHubs is too small to add the code of the Modbus TCP server in it. The solution that we recommend is to write a small Modbus TCP server in Python with the pymodbus library and to use our library to communicate with the Yoctopuce modules. This is exactly what we did this week.



We are therefore going to show you a small Modbus TCP server, which publishes the values of the Yoctopuce sensors which are connected on the USB ports. We are going to run this small server on a Raspberry Pi, but as we wrote it in Python it should work on any OS, including Windows.

To configure the mapping between the Yoctopuce modules and the register numbers, this server uses a small configuration file named device_mapping.txt. Each line corresponds to a mapping and a mapping is made of three fields: the register number, the hardware ID of the Yoctopuce sensor, and the value encoding. Supported encodings are int8, int16, int32, float 16, and float32.

Here is for example a configuration file for three Yoctopuce sensors:

0x0000,METEOMK2-114F07.temperature,float32 0x0002,METEOMK2-114F07.humidity,int32 0x0004,YPROXIM1-81237.proximity1,int32



Installation


To install this small Modbus TCP server, you must clone the code from GitHub:

git clone https://github.com/yoctopuce-examples/ymodbustcp.git cd ymodbustcp



And install with pip the libraries used by the server, that is pymodbus and yoctopuce.

pip install pymodbus pip install yoctopuce



Then you must edit the device_mapping.txt file so that it corresponds to the Yoctopuce modules present on the machine.

In the present case, we want to publish the temperature and the humidity of the Yocto-Meteo-V2 METEOMK2-114F07, as well as the luminosity of the Yocto-Light-V3 LIGHTMK3-C0905:

0x0000,METEOMK2-114F07.temperature,float32 0x0002,METEOMK2-114F07.humidity,int32 0x0004,LIGHTMK3-C0905.lightSensor,int32



Then you only have to launch the server::

./ymodbustcp.py



And there you are, you can use our Yoctopuce modules in a Modbus TCP infrastructure. For example, you can read the value of our sensors with the Simply Modbus TCP Client application.

You can read the value of our sensors with the Simply Modbus TCP application
You can read the value of our sensors with the Simply Modbus TCP application



Implementation


The source code of this Modbus TCP server is available on GitHub:
https://github.com/yoctopuce-examples/ymodbustcp

The code is based on the "Callback Server" example of the pymodbus library. This library allows you to easily write a Modbus TCP client or server, that's why we decided to use Python to write this small server.

To update the Modus registers of the server, you must simply create a class which inherits from ModbusSparseDataBlock and which implements the setValues method.

class YocotpuceBinding(object):
  def __init__(self, reg_no, hwid, encoding):
    self.reg_addr = reg_no
    self.hwid = hwid
    self.ysensor = YSensor.FindSensor(hwid)
    self.encoding = encoding
    self.reg_len = 2
    if self.encoding == 'int8':
        self.reg_len = 1
    elif self.encoding == 'int32' or self.encoding == 'float32':
        self.reg_len = 2

  def encode_value(self, val):
    builder = BinaryPayloadBuilder(byteorder=Endian.Big)
    if self.encoding == 'int8':
        builder.add_8bit_int(int(val))
    elif self.encoding == 'int16':
        builder.add_16bit_int(int(val))
    elif self.encoding == 'int32':
        builder.add_32bit_int(int(val))
    elif self.encoding == 'float16':
        builder.add_16bit_float(val)
    elif self.encoding == 'float32':
        builder.add_32bit_float(val)
    ba = builder.to_registers()
    return ba

  def update_measure(self, org_val, address, count):
    end_addr = address + count
    # skip device that are outside the range
    if end_addr <= self.reg_addr:
        return
    if address > (self.reg_addr + self.reg_len):
        return
    # get sensor value
    val = self.ysensor.get_currentValue()
    full_register = self.encode_value(val)
    # and update the corresponding register
    offset = self.reg_addr
    for word in full_register:
        org_val[offset] = word
        offset += 1

  def get_hwid(self):
    return self.hwid

  def get_reglen(self):
    return self.reg_len


class YoctopuceDataBlock(ModbusSequentialDataBlock):
  def __init__(self, devices):
    self.devices = devices
    start = 0xffff
    end = 0
    for reg in devices.keys():
        reglen = devices[reg].get_reglen()
        if reg < start:
            start = reg
        if reg + reglen > end:
            end = reg + reglen
    values = [0] * (end - start)
    super(YoctopuceDataBlock, self).__init__(start, values)

  def getValues(self, address, count=1):
    for reg in self.devices.keys():
        self.devices[reg].update_measure(self.values, address, count)
    values = super(YoctopuceDataBlock, self).getValues(address, count)
    return values



Conclusion


This server is not a solution that you can use as is in production, but it should enable you to perform your tests and create other internal projects. We kept the code as simple as possible so that you can easily adapt this server to your own needs.

To conclude, even if YoctoHubs don't natively support Modbus TCP, you can work around this limitation with the help of a Raspberry Pi and this server.

Add a comment No comment yet Back to blog












Yoctopuce, get your stuff connected.