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
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.
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.