OPC Server and modbus
近期做一个企业内的危险源数据上传的项目,除了需要从传感器采集数据还有从modbus协议的设备采集数据,偶尔也有需要从OPC server取数的需求.
1.OPC Server
简单来说,OPC是一套标准,其目的是把PLC特定的协议(如Modbus,Profibus等)抽象成为标准化的接口,作为“中间人”的角色把通用的OPC“读写”请求转换成具体的设备协议来与HMI/SCADA系统直接对接 基于这个标准.
我们采用python的opcua的库构建自己的 opc server.
from opcua import ua, Server
# 设置我们的opc服务器
server = Server()
server.set_endpoint("opc.tcp://0.0.0.0:4840/freeopcua/server/")
# 设置我们的命名空间(namespace), 当然也可以不用设置
uri = "http://ai.kingstars.cn"
idx = server.register_namespace(uri)
# 获取对象节点(node)
objects = server.get_objects_node()
# 定义好我们的地址空间
myobj = objects.add_object(idx, "MyObject")
myvar = myobj.add_variable(idx, "MyVariable", 6.7)
myvar.set_writable() # 设置 MyVariable 可写
# 启动服务!
server.start()
下一步,我们模拟一个不断变化的数值
try:
count = 0
while True:
time.sleep(1)
count += 0.1
myvar.set_value(count)
finally:
#close connection, remove subcsriptions, etc
server.stop()
这样一个OPC server就构建完成了.
2. opc to modbus and modbus tcp server
下一步构建一个读取OPC服务的 client,并且以 modbus tcp server把 opc的数值发布出去,从而实现OPC2modbus的功能.
from pymodbus.server.asynchronous import StartTcpServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer
from opcua import Client
# 配置 Modbus server
# Host address of the OPC Server
opc_host = "opc.tcp://localhost:4840/freeopcua/server/"
# Modbus Server的bind ip及端口
modbus_host = "0.0.0.0"
modbus_port = 5020
# Modbus server 信息标识
identity = ModbusDeviceIdentification()
identity.VendorName = 'kingstars'
identity.ProductCode = 'WEIOT'
identity.VendorUrl = 'http://ai.kingstars.cn'
identity.ProductName = 'OPC to Modbus'
identity.ModelName = 'O2M0.1'
identity.MajorMinorRevision = '0.0.1'
# 更新频率
update_inteval = 1.5
下一步更新 opc_clinet及对应的函数
opc_client: Client
def initiate_client():
global opc_client
opc_client = Client(opc_host)
opc_client.connect()
return opc_client
def shutdown_client():
global opc_client
opc_client.disconnect()
def updating_writer(context):
root = opc_client.get_root_node()
# 从 OPC Client 获取数据
myvar = root.get_child(["0:Objects", "2:MyObject", "2:MyVariable"])
# 更新 Modbus 数据
register = 3
slave_id = 0x00 # 从站id
address = 0x00
value = myvar.get_value()
context[slave_id].setValues(register, address, [int(value)])
def initiate_server(identity, inteval, host, port):
# 初始化 modbus server及初始数据
store = ModbusSlaveContext(
di=ModbusSequentialDataBlock.create(),
co=ModbusSequentialDataBlock.create(),
hr=ModbusSequentialDataBlock.create(),
ir=ModbusSequentialDataBlock.create(),)
context = ModbusServerContext(slaves=store, single=True)
# 开始循环及回调
loop = LoopingCall(f=updating_writer, context=context)
loop.start(inteval, now=False)
# 启动 Modbus server
print("Modbus Server started. Press Cntl + C to stop...")
StartTcpServer(context, identity=identity, address=(host, port))
以上代码具备OPC client,负责从opc server获取数据,然后启动 modbus server.
外部设备通过modbus tcp即可获取opc server数据.
主代码如下:
if __name__ == "__main__":
try:
print("Initializing...")
print("Starting OPC Client.")
initiate_client()
print(f"Client listening at: {opc_host}")
print("-"*70 + "\n")
print("Starting Modbus server.")
print(f"Server hosted at: {modbus_host}:{modbus_port}")
initiate_server(identity, update_inteval, modbus_host, modbus_port)
finally:
print("Sutting down...")
shutdown_client()
3. 测试
- 启动opc server,模拟实际场景中的OPC服务器,其中设备名称:设备变量 --> MyObject:MyVariable, MyVariable每秒累加.
- 启动 Modbus server, Modbus server先通过opc clinet采集opc server的设备变量,然后以Modbus TCP服务发布出去,modbus: register = 3,从站id slave_id = 0x00,地址 address = 0x00.
- 通过外部工具调用 modbus tcp,在0x00,就可以取出不断变化的opc server的变量.