06/01/2015, 09:07:49
Bon, voici un exemple d'utilisation de pymodbus en esclave (mode serveur, donc). J'ai utilisé ici le protocole Tcp, mais c'est la même chose en Udp, Rtu ou Ascii...
Côté maître, rien de compliqué :
Côté esclave, ça donne :
Comme StartTcpServer() est bloquant, j'ai créé un thread qui dump régulièrement l'état des registres (tu noteras au passage le décalage du mapping d'adresse, propre au protocole modbus, mais qui peut être bypassé).
Note qu'un même serveur peut réagir en tant que plusieurs noeuds slaves. C'est bien expliqué dans le lien que j'avais posté au dessus.
Voici une autre version qui utilise un callback custom ; en gros, on surcharge la classe ModbusSequentialDataBlock (ou ModbusSparseDataBlock, si tu veux un block à trous), et on implémente notre propre méthode setValue(), laquelle sera appelée lorsque l'esclave reçoit une demande de modification. À partir de là, tu peux déclencher des choses en fonction du registre qui a été modifié.
Dès que j'ai un moment, je vois comment intégrer ça à un FunctionalBlock de pKNyX. Mais bon, rien de sorcier, en fait...
N'hésites pas à poser des questions si besoin.
PS : je confirme que pymodbus est vraiment très puissant, et très bien écrit. Les autres implémentations sont bien plus brouillons, et incomplètes...
Côté maître, rien de compliqué :
Code :
# -*- coding: utf-8 -*-
""" master
"""
from pymodbus.client.sync import ModbusTcpClient
client = ModbusTcpClient("localhost", 5020)
client.connect()
# Read discrete inputs
print "read discrete inputs"
print client.read_discrete_inputs(0, 3, unit=0).bits
print
# Read-write coils
print "read coils"
print client.read_coils(0, 3, unit=0).bits
print "write coils [False, False, False]"
client.write_coils(0, [False, False, False], unit=0)
print "read coils"
print client.read_coils(0, 3, unit=0).bits
print
# Read input registers
print "read inputs registers"
print client.read_input_registers(0, 3, unit=0).registers
print
# Read-write holding registers
print "read holding registers"
print client.read_holding_registers(0, 3, unit=0).registers
print "write holding registers [7, 8, 9]"
client.write_registers(0, [7, 8, 9], unit=0)
print "read holding registers"
print client.read_holding_registers(0, 3, unit=0).registers
client.close()
Côté esclave, ça donne :
Code :
#!/usr/bin/env python
""" slave
"""
import thread
import time
from pymodbus.server.async import StartTcpServer
#from pymodbus.server.async import StartSerialServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
#from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
store = ModbusSlaveContext(
di = ModbusSequentialDataBlock(1, [True, True, True]), # discrete inputs
co = ModbusSequentialDataBlock(1, [True, True, True]), # coils outputs
hr = ModbusSequentialDataBlock(1, [1, 2, 3]), # holding registers
ir = ModbusSequentialDataBlock(1, [11, 12, 13])) # input registers
context = ModbusServerContext(slaves=store, single=True)
identity = ModbusDeviceIdentification()
identity.VendorName = 'Pymodbus'
identity.ProductCode = 'PM'
identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
identity.ProductName = 'Pymodbus Server'
identity.ModelName = 'Pymodbus Server'
identity.MajorMinorRevision = '1.0'
def dump():
global store
while True:
print
print "d=%s" % store.store['d'].getValues(1, 3)
print "c=%s" % store.store['c'].getValues(1, 3)
print "h=%s" % store.store['h'].getValues(1, 3)
print "i=%s" % store.store['i'].getValues(1, 3)
time.sleep(3)
thread.start_new_thread(dump, ())
StartTcpServer(context, identity=identity, address=("localhost", 5020))
#StartSerialServer(context, identity=identity, port='/dev/pts/3', framer=ModbusRtuFramer)
#StartSerialServer(context, identity=identity, port='/dev/pts/3', framer=ModbusAsciiFramer)
Comme StartTcpServer() est bloquant, j'ai créé un thread qui dump régulièrement l'état des registres (tu noteras au passage le décalage du mapping d'adresse, propre au protocole modbus, mais qui peut être bypassé).
Note qu'un même serveur peut réagir en tant que plusieurs noeuds slaves. C'est bien expliqué dans le lien que j'avais posté au dessus.
Voici une autre version qui utilise un callback custom ; en gros, on surcharge la classe ModbusSequentialDataBlock (ou ModbusSparseDataBlock, si tu veux un block à trous), et on implémente notre propre méthode setValue(), laquelle sera appelée lorsque l'esclave reçoit une demande de modification. À partir de là, tu peux déclencher des choses en fonction du registre qui a été modifié.
Code :
#!/usr/bin/env python
""" slave cb
"""
import thread
import time
from pymodbus.server.async import StartTcpServer
#from pymodbus.server.async import StartSerialServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
#from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
class CallbackDataBlock(ModbusSequentialDataBlock):
def setValues(self, address, value):
"""
"""
super(CallbackDataBlock, self).setValues(address, value)
print "CallbackDataBlock.setValues(): address=%s, value=%s" % (address, value)
store = ModbusSlaveContext(
di = CallbackDataBlock(1, [True, True, True]), # discrete inputs
co = CallbackDataBlock(1, [True, True, True]), # coils outputs
hr = CallbackDataBlock(1, [1, 2, 3]), # holding registers
ir = CallbackDataBlock(1, [11, 12, 13])) # input registers
context = ModbusServerContext(slaves=store, single=True)
identity = ModbusDeviceIdentification()
identity.VendorName = 'Pymodbus'
identity.ProductCode = 'PM'
identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
identity.ProductName = 'Pymodbus Server'
identity.ModelName = 'Pymodbus Server'
identity.MajorMinorRevision = '1.0'
StartTcpServer(context, identity=identity, address=("localhost", 5020))
#StartSerialServer(context, identity=identity, port='/dev/pts/3', framer=ModbusRtuFramer)
#StartSerialServer(context, identity=identity, port='/dev/pts/3', framer=ModbusAsciiFramer)
Dès que j'ai un moment, je vois comment intégrer ça à un FunctionalBlock de pKNyX. Mais bon, rien de sorcier, en fait...
N'hésites pas à poser des questions si besoin.
PS : je confirme que pymodbus est vraiment très puissant, et très bien écrit. Les autres implémentations sont bien plus brouillons, et incomplètes...