Adds unit testing. In the process of converting from scaled integers to using floating point values with a BinaryPayloadBuilder class
221 lines
8.8 KiB
Python
221 lines
8.8 KiB
Python
#!/usr/bin/env python
|
|
'''
|
|
Pymodbus Server With Callbacks
|
|
--------------------------------------------------------------------------
|
|
|
|
This is an example of adding callbacks to a running modbus server
|
|
when a value is written to it. In order for this to work, it needs
|
|
a device-mapping file.
|
|
'''
|
|
# ---------------------------------------------------------------------------#
|
|
# import the modbus libraries we need
|
|
# ---------------------------------------------------------------------------#
|
|
from pymodbus.server.async import StartTcpServer
|
|
from pymodbus.device import ModbusDeviceIdentification
|
|
from pymodbus.datastore import ModbusSparseDataBlock
|
|
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
|
|
from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
|
|
from pymodbus.payload import BinaryPayloadBuilder
|
|
from pymodbus.constants import Endian
|
|
|
|
# ---------------------------------------------------------------------------#
|
|
# import the python libraries we need
|
|
# ---------------------------------------------------------------------------#
|
|
import pymongo
|
|
from pymongo import MongoClient
|
|
from pycomm_helper import utils as plc
|
|
from time import time as now
|
|
# ---------------------------------------------------------------------------#
|
|
# configure the service logging
|
|
# ---------------------------------------------------------------------------#
|
|
import logging
|
|
logging.basicConfig()
|
|
log = logging.getLogger()
|
|
log.setLevel(logging.DEBUG)
|
|
|
|
PLC_IP_ADDRESS = '10.20.4.7'
|
|
|
|
# ---------------------------------------------------------------------------#
|
|
# Datatype Mapping
|
|
# ---------------------------------------------------------------------------#
|
|
|
|
|
|
|
|
def getTagsFromDB():
|
|
client = MongoClient()
|
|
db = client.tag_data
|
|
tags = db.tag_vals
|
|
print("Found {} tags in the database".format(tags.count()))
|
|
di_tags_cur = tags.find({'register_type': 'di'})
|
|
di_tags = list(di_tags_cur)
|
|
di_tags_num = di_tags_cur.count()
|
|
print("{} Digital Inputs".format(di_tags_num))
|
|
|
|
co_tags_cur = tags.find({'register_type': 'co'})
|
|
co_tags = list(co_tags_cur)
|
|
co_tags_num = co_tags_cur.count()
|
|
print("{} Coils".format(co_tags_num))
|
|
|
|
ir_tags_cur = tags.find({'register_type': 'ir'})
|
|
ir_tags = list(ir_tags_cur)
|
|
ir_tags_num = ir_tags_cur.count()
|
|
print("{} Input Registers".format(ir_tags_num))
|
|
|
|
hr_tags_cur = tags.find({'register_type': 'hr'})
|
|
hr_tags = list(hr_tags_cur)
|
|
hr_tags_num = hr_tags_cur.count()
|
|
print("{} Holding Registers".format(hr_tags_num))
|
|
|
|
return {'di': di_tags, 'co': co_tags, 'ir': ir_tags, 'hr': hr_tags}
|
|
|
|
|
|
# ---------------------------------------------------------------------------#
|
|
# create your custom data block with callbacks
|
|
# ---------------------------------------------------------------------------#
|
|
class DigitalTagDataBlock(ModbusSparseDataBlock):
|
|
''' A datablock that stores the new value in memory
|
|
and passes the operation to a message queue for further
|
|
processing.
|
|
'''
|
|
|
|
def __init__(self, register_type, tag_list):
|
|
'''
|
|
'''
|
|
values = {}
|
|
self.register_type = register_type
|
|
|
|
for t in tag_list:
|
|
try:
|
|
values[t['register_number']] = t['val']
|
|
except KeyError:
|
|
values[t['register_number']] = 911
|
|
# print("Initialized DigitalTagDataBlock for {} with values {}".format(self.register_type, values))
|
|
super(DigitalTagDataBlock, self).__init__(values)
|
|
|
|
def getValues(self, address, count=1):
|
|
client = MongoClient()
|
|
db = client.tag_data
|
|
tags = db.tag_vals
|
|
|
|
if count > 1:
|
|
for i in range(address, address + count):
|
|
tag_found = tags.find_one({'register_number': i, 'register_type': self.register_type})
|
|
print("{} = {}".format(tag_found['tag_name'], tag_found['val']))
|
|
super(DigitalTagDataBlock, self).setValues(address, tag_found['val'])
|
|
else:
|
|
tag_found = tags.find_one({'register_number': address, 'register_type': self.register_type})
|
|
print("{} = {}".format(tag_found['tag_name'], tag_found['val']))
|
|
super(DigitalTagDataBlock, self).setValues(address, tag_found['val'])
|
|
|
|
return super(DigitalTagDataBlock, self).getValues(address, count=count)
|
|
|
|
def setValues(self, address, value):
|
|
''' Sets the requested values of the datastore
|
|
|
|
:param address: The starting address
|
|
:param values: The new values to be set
|
|
'''
|
|
client = MongoClient()
|
|
db = client.tag_data
|
|
tags = db.tag_vals
|
|
tag_name = tags.find_one({'register_number': address, 'register_type': self.register_type})['tag_name']
|
|
plc.writeTag(PLC_IP_ADDRESS, tag_name, value)
|
|
|
|
|
|
class AnalogTagDataBlock(ModbusSparseDataBlock):
|
|
''' A datablock that stores the new value in memory
|
|
and passes the operation to a message queue for further
|
|
processing.
|
|
'''
|
|
|
|
def __init__(self, register_type, tag_list):
|
|
'''
|
|
'''
|
|
values = {}
|
|
self.register_type = register_type
|
|
register_values = {}
|
|
for t in tag_list:
|
|
builder = BinaryPayloadBuilder(endian=Endian.Little)
|
|
try:
|
|
if t['tag_type'] == 'SINT':
|
|
builder.add_8bit_int(t['val_actual'])
|
|
elif t['tag_type'][:3] == 'INT':
|
|
builder.add_16bit_int(t['val_actual'])
|
|
elif t['tag_type'] == 'REAL':
|
|
builder.add_32bit_float(t['val_actual'])
|
|
|
|
register_values[t['register_number']] = builder.to_registers
|
|
except KeyError:
|
|
register_values[t['register_number']] = []
|
|
print("No value for register #{} - {}".format(t['register_number'], t['tag_name']))
|
|
|
|
|
|
|
|
# print("Initialized AnalogTagDataBlock for {} with values {}".format(self.register_type, values))
|
|
super(AnalogTagDataBlock, self).__init__(values)
|
|
|
|
def getValues(self, address, count=1):
|
|
client = MongoClient()
|
|
db = client.tag_data
|
|
tags = db.tag_vals
|
|
|
|
if count > 1:
|
|
for i in range(address, address + count):
|
|
tag_found = tags.find_one({'register_number': i, 'register_type': self.register_type})
|
|
if tag_found:
|
|
print("{} = {}".format(tag_found['tag_name'], tag_found['val']))
|
|
super(AnalogTagDataBlock, self).setValues(address, tag_found['val'])
|
|
super(AnalogTagDataBlock, self).setValues(address + 3, 0)
|
|
else:
|
|
tag_found = tags.find_one({'register_number': address, 'register_type': self.register_type})
|
|
if tag_found:
|
|
print("{} = {}".format(tag_found['tag_name'], tag_found['val']))
|
|
super(AnalogTagDataBlock, self).setValues(address, tag_found['val'])
|
|
super(AnalogTagDataBlock, self).setValues(address + 3, 0)
|
|
|
|
return super(AnalogTagDataBlock, self).getValues(address, count=count)
|
|
|
|
def setValues(self, address, value):
|
|
''' Sets the requested values of the datastore
|
|
|
|
:param address: The starting address
|
|
:param values: The new values to be set
|
|
'''
|
|
client = MongoClient()
|
|
db = client.tag_data
|
|
tags = db.tag_vals
|
|
tag_name = tags.find_one({'register_number': address, 'register_type': self.register_type})['tag_name']
|
|
plc.writeTag(PLC_IP_ADDRESS, tag_name, value)
|
|
|
|
|
|
def main():
|
|
# ---------------------------------------------------------------------------#
|
|
# initialize your data store
|
|
# ---------------------------------------------------------------------------#
|
|
tags_in_db = getTagsFromDB()
|
|
di_block = DigitalTagDataBlock('di', tags_in_db['di'])
|
|
co_block = DigitalTagDataBlock('co', tags_in_db['co'])
|
|
hr_block = AnalogTagDataBlock('hr', tags_in_db['hr'])
|
|
ir_block = AnalogTagDataBlock('ir', tags_in_db['ir'])
|
|
store = ModbusSlaveContext(di=di_block, co=co_block, hr=hr_block, ir=ir_block)
|
|
context = ModbusServerContext(slaves=store, single=True)
|
|
|
|
# ---------------------------------------------------------------------------#
|
|
# initialize the server information
|
|
# ---------------------------------------------------------------------------#
|
|
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'
|
|
|
|
# ---------------------------------------------------------------------------#
|
|
# run the server you want
|
|
# ---------------------------------------------------------------------------#
|
|
StartTcpServer(context, identity=identity, address=("localhost", 5020))
|
|
|
|
if __name__ == '__main__':
|
|
main()
|