Régulièrement on nous demande si les YoctoHubs supportent le protocole Modbus TCP. La réponse est non, car le CPU des YoctoHubs est trop petit pour que l'on puisse ajouter le code du serveur Modbus TCP. La solution que nous recommandons est d'écrire un petit serveur Modbus TCP en Python à l'aide de la librairie pymodbus et d'utiliser notre librairie pour communiquer avec les modules Yoctopuce. C'est exactement ce que l'on a fait cette semaine.
Nous allons donc vous présenter un petit serveur Modbus TCP, qui publie les valeurs des capteurs Yoctopuce qui sont connectés sur les ports USB. Nous allons faire tourner ce petit serveur sur un Raspberry Pi, mais comme c'est du Python il devrait fonctionner sur n'importe quel OS, y compris Windows.
Pour configurer le mapping entre les modules Yoctopuce et les numéros de registre, ce serveur utilise un petit fichier de configuration device_mapping.txt. Chaque ligne correspond à un mapping, et un mapping est composé de trois champs : le numéro de registre, l'hardware ID du senseur Yoctopuce, et l'encodage de la valeur. Les encodages supportés sont int8, int16, int32, float 16 et float32.
Voici par exemple un fichier de configuration pour trois senseurs Yoctopuce:
0x0000,METEOMK2-114F07.temperature,float32 0x0002,METEOMK2-114F07.humidity,int32 0x0004,YPROXIM1-81237.proximity1,int32
L’installation
Pour installer ce petit serveur Modbus TCP, il faut cloner le code depuis GitHub:
git clone https://github.com/yoctopuce-examples/ymodbustcp.git cd ymodbustcp
Et installer à l'aide de pip les librairies utilisées par le serveur, à savoir pymodbus et yoctopuce.
pip install pymodbus pip install yoctopuce
Il faut ensuite éditer le fichier device_mapping.txt pour qu'il corresponde aux modules Yoctopuce présents sur la machine.
Dans notre cas, nous voulons publier la température et l'humidité du Yocto-Meteo-V2 METEOMK2-114F07 ainsi que la luminosité du Yocto-Light-V3 LIGHTMK3-C0905 :
0x0000,METEOMK2-114F07.temperature,float32 0x0002,METEOMK2-114F07.humidity,int32 0x0004,LIGHTMK3-C0905.lightSensor,int32
Il ne reste plus qu'à lancer le serveur :
./ymodbustcp.py
Et voilà, nos modules Yoctopuce sont utilisables dans une infrastructure Modbus TCP. Par exemple, la valeur de nos senseurs peut être lue à l'aide de l'application Simply Modbus TCP Client
La valeur de nos senseurs peut être lue à l'aide de l'application Simply Modbus TCP
L'implémentation
Le code source de ce serveur Modbus TCP est disponible sur Github :
https://github.com/yoctopuce-examples/ymodbustcp
Le code est basé sur l'exemple "Callback Server" de la librairie pymodbus. Cette librairie permet d'écrire facilement un client ou un serveur Modbus TCP, c'est du reste pour cette raison que nous avons décidé d'utiliser Python pour écrire ce petit serveur.
Pour mettre à jour les registres Modus du serveur, il faut simplement créer une classe qui hérite de ModbusSparseDataBlock et qui implémente la méthode setValues.
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
Ce serveur n'est pas une solution à utiliser telle quelle en production, mais il devrait vous permettre de réaliser vos tests et autres projets internes. Nous avons gardé le code le plus simple possible afin que vous puissiez facilement adapter ce serveur à vos besoins.
En conclusion, même si les YoctoHubs ne supportent pas nativement Modbus TCP, il est possible de contourner cette limitation à l'aide d'un Raspberry Pi et de ce serveur.