You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

675 lines
25 KiB
Python

from utils.DBModels.ProtocolModel import TCPSetting, RTUSetting
from .tcpmaster_example import TcpMaster
from .rtumaster_example import RTUMaster
from .tcpslave_example import TCPSlave
from .rtuslave_example import RTUSlave
from modbus_tk import hooks
import threading
import time
class ModbusManager:
"""Modbus 通讯管理器,负责管理所有 Modbus TCP/RTU 主站和从站"""
def __init__(self):
# Modbus 通讯实例
self.modbusTcpMaster = None
self.modbusRtuMaster = None
self.modbusTcpSlave = None
self.modbusRtuSlave = None
# 线程管理
self.modbusReadThreads = {} # 存储各个Modbus读取线程
self.modbusStopEvents = {} # 存储各个Modbus停止事件
self.modbusLocks = {} # 存储各个Modbus的锁
# 变量缓存回调
self.variableValueCache = None
self.cacheLock = None
self.varInfoCache = None
# 报文记录
self.messageHistory = {
'send': [], # 发送的报文
'receive': [] # 接收的报文
}
self.maxMessageCount = 100 # 最大保存报文数量
self.messageLock = threading.Lock()
# 设置报文捕获 hooks
self._setupHooks()
def setVariableCache(self, variableValueCache, cacheLock, varInfoCache):
"""设置变量缓存引用"""
self.variableValueCache = variableValueCache
self.cacheLock = cacheLock
self.varInfoCache = varInfoCache
# ==================== 启动/停止方法 ====================
def startModbusTcpMaster(self):
"""启动 Modbus TCP 主站"""
try:
if self.modbusTcpMaster is not None:
self.modbusTcpMaster.master.close()
self.modbusTcpMaster = None
# 从数据库获取TCP设置
tcpSettings = self._getTcpSettings('master')
if not tcpSettings:
return False
# 创建TCP主站实例
# print(tcpSettings['host'], tcpSettings['port'])
self.modbusTcpMaster = TcpMaster(
host=tcpSettings['host'],
port=tcpSettings['port']
)
# 启动读取线程
self._startModbusReadThread('TCP_MASTER', tcpSettings['frequency'])
return True
except Exception as e:
print(f"启动 Modbus TCP 主站失败: {str(e)}")
return False
def stopModbusTcpMaster(self):
"""停止 Modbus TCP 主站"""
try:
if self.modbusTcpMaster is None:
return True
# 停止读取线程
self._stopModbusReadThread('TCP_MASTER')
# 清理主站实例
self.modbusTcpMaster = None
return True
except Exception as e:
return False
def startModbusRtuMaster(self):
"""启动 Modbus RTU 主站"""
try:
if self.modbusRtuMaster is not None:
self.modbusRtuMaster.master.close()
self.modbusRtuMaster = None
# 从数据库获取RTU设置
rtuSettings = self._getRtuSettings('master')
if not rtuSettings:
return False
# 创建RTU主站实例
self.modbusRtuMaster = RTUMaster(
port=rtuSettings['port'],
baudrate=rtuSettings['baudrate'],
bytesize=rtuSettings['byteSize'],
parity=rtuSettings['parity'][0], # 取第一个字符
stopbits=rtuSettings['stopbits']
)
# 启动读取线程
self._startModbusReadThread('RTU_MASTER', rtuSettings['frequency'])
return True
except Exception as e:
return False
def stopModbusRtuMaster(self):
"""停止 Modbus RTU 主站"""
try:
if self.modbusRtuMaster is None:
return True
# 停止读取线程
self._stopModbusReadThread('RTU_MASTER')
# 清理主站实例
self.modbusRtuMaster = None
return True
except Exception as e:
return False
def startModbusTcpSlave(self):
"""启动 Modbus TCP 从站"""
try:
if self.modbusTcpSlave is not None:
self.modbusTcpSlave.server.close()
self.modbusTcpSlave = None
# 从数据库获取TCP设置
tcpSettings = self._getTcpSettings('slave')
if not tcpSettings:
return False
# 创建TCP从站实例
self.modbusTcpSlave = TCPSlave(
address=tcpSettings['host'],
port=tcpSettings['port']
)
# 添加默认从站ID
# self.modbusTcpSlave.addSlave(1)
return True
except Exception as e:
return False
def stopModbusTcpSlave(self):
"""停止 Modbus TCP 从站"""
try:
if self.modbusTcpSlave is None:
return True
# 停止从站服务器
if hasattr(self.modbusTcpSlave, 'server'):
self.modbusTcpSlave.server.stop()
self.modbusTcpSlave = None
return True
except Exception as e:
return False
def startModbusRtuSlave(self):
"""启动 Modbus RTU 从站"""
try:
if self.modbusRtuSlave is not None:
self.modbusRtuSlave.server.close()
self.modbusRtuSlave = None
# 从数据库获取RTU设置
rtuSettings = self._getRtuSettings('slave')
if not rtuSettings:
return False
# 创建RTU从站实例
self.modbusRtuSlave = RTUSlave(
port=rtuSettings['port'],
baudrate=rtuSettings['baudrate'],
bytesize=rtuSettings['byteSize'],
parity=rtuSettings['parity'][0], # 取第一个字符
stopbits=rtuSettings['stopbits']
)
# 启动从站服务器
self.modbusRtuSlave.start()
# 添加默认从站ID
# self.modbusRtuSlave.addSlave(1)
return True
except Exception as e:
return False
def stopModbusRtuSlave(self):
"""停止 Modbus RTU 从站"""
try:
if self.modbusRtuSlave is None:
return True
# 停止从站服务器
if hasattr(self.modbusRtuSlave, 'server'):
self.modbusRtuSlave.server.stop()
self.modbusRtuSlave = None
return True
except Exception as e:
return False
def stopAllModbus(self):
"""停止所有 Modbus 通讯"""
results = []
results.append(self.stopModbusTcpMaster())
results.append(self.stopModbusRtuMaster())
results.append(self.stopModbusTcpSlave())
results.append(self.stopModbusRtuSlave())
return all(results)
def getModbusStatus(self):
"""获取所有 Modbus 通讯状态"""
return {
'tcpMaster': self.modbusTcpMaster is not None,
'rtuMaster': self.modbusRtuMaster is not None,
'tcpSlave': self.modbusTcpSlave is not None,
'rtuSlave': self.modbusRtuSlave is not None
}
# ==================== 读写方法 ====================
def writeModbusTcpMasterValue(self, info, value):
"""写入TCP主站变量值"""
try:
if self.modbusTcpMaster is None:
return False
slaveId = int(info['slaveID'])
address = int(info['address'])
varType = int(info['varType'])
order = info['order']
return self._writeModbusValue(self.modbusTcpMaster, slaveId, address, varType, value, order)
except Exception as e:
print(f"写入TCP主站变量失败: {str(e)}")
return False
def writeModbusRtuMasterValue(self, info, value):
"""写入RTU主站变量值"""
try:
if self.modbusRtuMaster is None:
return False
slaveId = int(info['slaveID'])
address = int(info['address'])
varType = int(info['varType'])
order = info['order']
return self._writeModbusValue(self.modbusRtuMaster, slaveId, address, varType, value, order)
except Exception as e:
print(f"写入RTU主站变量失败: {str(e)}")
return False
def writeModbusTcpSlaveValue(self, info, value):
"""写入TCP从站变量值"""
try:
if self.modbusTcpSlave is None:
print("Modbus TCP 从站未启动")
return False
slaveId = int(info['slaveID'])
address = int(info['address'])
varType = int(info['varType'])
order = info['order']
return self._writeModbusSlaveValue(self.modbusTcpSlave, slaveId, address, varType, value, order)
except Exception as e:
print(f"写入TCP从站变量失败: {str(e)}")
return False
def writeModbusRtuSlaveValue(self, info, value):
"""写入RTU从站变量值"""
try:
if self.modbusRtuSlave is None:
print("Modbus RTU 从站未启动")
return False
slaveId = int(info['slaveID'])
address = int(info['address'])
varType = int(info['varType'])
order = info['order']
return self._writeModbusSlaveValue(self.modbusRtuSlave, slaveId, address, varType, value, order)
except Exception as e:
print(f"写入RTU从站变量失败: {str(e)}")
return False
def readModbusTcpSlaveValue(self, info):
"""读取TCP从站变量值"""
try:
if self.modbusTcpSlave is None:
return None
slaveId = int(info['slaveID'])
address = int(info['address'])
varType = int(info['varType'])
order = info['order']
return self._readModbusSlaveValue(self.modbusTcpSlave, slaveId, address, varType, order)
except Exception as e:
print(f"读取TCP从站变量失败: {str(e)}")
return None
def readModbusRtuSlaveValue(self, info):
"""读取RTU从站变量值"""
try:
if self.modbusRtuSlave is None:
return None
slaveId = int(info['slaveID'])
address = int(info['address'])
varType = int(info['varType'])
order = info['order']
return self._readModbusSlaveValue(self.modbusRtuSlave, slaveId, address, varType, order)
except Exception as e:
print(f"读取RTU从站变量失败: {str(e)}")
return None
# ==================== 私有方法 ====================
def _getTcpSettings(self, tcpType):
"""从数据库获取TCP设置"""
try:
setting = TCPSetting.select().where(TCPSetting.tcpType == tcpType).first()
if setting:
return {
'host': setting.host,
'port': setting.port,
'frequency': float(setting.frequency),
'offset': setting.offset
}
else:
# 如果没有设置,创建默认设置
newSetting = TCPSetting()
newSetting.initSetting(tcpType)
return {
'host': newSetting.host,
'port': newSetting.port,
'frequency': float(newSetting.frequency),
'offset': newSetting.offset
}
except Exception as e:
print(f"获取TCP设置失败: {str(e)}")
return None
def _getRtuSettings(self, tcpType):
"""从数据库获取RTU设置"""
try:
setting = RTUSetting.select().where(RTUSetting.tcpType == tcpType).first()
if setting:
return {
'port': setting.port,
'byteSize': setting.byteSize,
'baudrate': setting.baudrate,
'stopbits': setting.stopbits,
'parity': setting.parity,
'frequency': float(setting.frequency),
'offset': setting.offset
}
else:
# 如果没有设置,创建默认设置
newSetting = RTUSetting()
newSetting.initSetting(tcpType)
return {
'port': newSetting.port,
'byteSize': newSetting.byteSize,
'baudrate': newSetting.baudrate,
'stopbits': newSetting.stopbits,
'parity': newSetting.parity,
'frequency': float(newSetting.frequency),
'offset': newSetting.offset
}
except Exception as e:
print(f"获取RTU设置失败: {str(e)}")
return None
def _startModbusReadThread(self, protocolType, frequency):
"""启动Modbus读取线程"""
if protocolType in self.modbusReadThreads:
return
stopEvent = threading.Event()
lock = threading.Lock()
self.modbusStopEvents[protocolType] = stopEvent
self.modbusLocks[protocolType] = lock
readThread = threading.Thread(
target=self._modbusReadWorker,
args=(protocolType, frequency, stopEvent, lock),
daemon=True
)
self.modbusReadThreads[protocolType] = readThread
readThread.start()
def _stopModbusReadThread(self, protocolType):
"""停止Modbus读取线程"""
if protocolType in self.modbusStopEvents:
self.modbusStopEvents[protocolType].set()
if protocolType in self.modbusReadThreads:
self.modbusReadThreads[protocolType].join(timeout=2)
del self.modbusReadThreads[protocolType]
if protocolType in self.modbusStopEvents:
del self.modbusStopEvents[protocolType]
if protocolType in self.modbusLocks:
del self.modbusLocks[protocolType]
def _modbusReadWorker(self, protocolType, frequency, stopEvent, lock):
"""Modbus读取工作线程"""
interval = 1.0 / frequency if frequency > 0 else 1.0
while not stopEvent.is_set():
try:
with lock:
self._readModbusVariables(protocolType)
except Exception as e:
print(f"Modbus读取线程错误 ({protocolType}): {str(e)}")
time.sleep(interval)
def _readModbusVariables(self, protocolType):
"""读取指定协议类型的所有Modbus变量"""
if protocolType == 'TCP_MASTER':
self._readTcpMasterVariables()
elif protocolType == 'RTU_MASTER':
self._readRtuMasterVariables()
def _readTcpMasterVariables(self):
"""读取TCP主站变量"""
if self.modbusTcpMaster is None or self.varInfoCache is None:
return
for varName, varInfo in self.varInfoCache.items():
if varInfo['modelType'] == 'ModbusTcpMasterVar':
try:
info = varInfo['variableData']
slaveId = int(info['slaveID'])
address = int(info['address'])
varType = int(info['varType'])
order = info['order']
value = self._readModbusValue(self.modbusTcpMaster, slaveId, address, varType, order)
if value != 'error' and self.variableValueCache is not None and self.cacheLock is not None:
with self.cacheLock:
self.variableValueCache[varName] = value
print(varName, value)
except Exception as e:
print(f"读取TCP主站变量失败 {varName}: {str(e)}")
def _readRtuMasterVariables(self):
"""读取RTU主站变量"""
if self.modbusRtuMaster is None or self.varInfoCache is None:
return
for varName, varInfo in self.varInfoCache.items():
if varInfo['modelType'] == 'ModbusRtuMasterVar':
try:
info = varInfo['variableData']
slaveId = int(info['slaveID'])
address = int(info['address'])
varType = int(info['varType'])
order = info['order']
value = self._readModbusValue(self.modbusRtuMaster, slaveId, address, varType, order)
if value != 'error' and self.variableValueCache is not None and self.cacheLock is not None:
with self.cacheLock:
self.variableValueCache[varName] = value
except Exception as e:
print(f"读取RTU主站变量失败 {varName}: {str(e)}")
def _readModbusValue(self, master, slaveId, address, varType, order):
"""通用Modbus值读取方法"""
try:
# varType: 1=线圈, 2=离散输入, 3=输入寄存器, 4=保持寄存器
if varType == 0: # 线圈
return master.readCoils(slaveId, address, 1)
elif varType == 1: # 离散输入
return master.readInputCoils(slaveId, address, 1)
elif varType == 3: # 输入寄存器
return master.readInputRegisters(slaveId, address, 1, order)
elif varType == 4: # 保持寄存器
return master.readHoldingRegisters(slaveId, address, 1, order)
else:
return 'error'
except Exception as e:
print(f"读取Modbus值失败: {str(e)}")
return 'error'
def _writeModbusValue(self, master, slaveId, address, varType, value, order):
"""通用Modbus主站值写入方法"""
try:
# varType: 0=线圈, 1=离散输入, 3=输入寄存器, 4=保持寄存器
if varType == 0: # 线圈
result = master.writeSingleCoil(slaveId, address, value)
return result != 'error'
elif varType == 4: # 保持寄存器
result = master.writeSingleRegister(slaveId, address, value, order)
return result != 'error'
else:
print(f"不支持写入的变量类型: {varType}")
return False
except Exception as e:
print(f"写入Modbus主站值失败: {str(e)}")
return False
def _writeModbusSlaveValue(self, slave, slaveId, address, varType, value, order):
"""通用Modbus从站值写入方法"""
try:
# 根据变量类型确定存储区名称
blockName = self._getModbusBlockName(varType, address)
if not blockName:
return False
# 转换地址为存储区内部地
slave.setValue(slaveId, blockName, address, value, order)
return True
except Exception as e:
print(f"写入Modbus从站值失败: {str(e)}")
return False
def _readModbusSlaveValue(self, slave, slaveId, address, varType, order):
"""通用Modbus从站值读取方法"""
try:
# 根据变量类型确定存储区名称
blockName = self._getModbusBlockName(varType, address)
if not blockName:
return None
return slave.readValue(slaveId, blockName, address, order)
except Exception as e:
print(f"读取Modbus从站值失败: {str(e)}")
return None
def _getModbusBlockName(self, varType, address):
"""根据变量类型和地址获取Modbus存储区名称"""
# varType: 0=线圈, 1=离散输入, 3=输入寄存器, 4=保持寄存器
if varType == 0: # 线圈 (0-9999)
return '0'
elif varType == 1: # 离散输入 (10000-19999)
return '1'
elif varType == 3: # 输入寄存器 (30000-39999)
return '3'
elif varType == 4: # 保持寄存器 (40000-49999)
return '4'
else:
print(f"未知的变量类型: {varType}")
return None
# ==================== 报文记录方法 ====================
def _setupHooks(self):
"""设置 modbus_tk hooks 来捕获报文"""
try:
def captureRequest(args):
"""捕获请求报文数据"""
try:
if args and len(args) > 0:
# args[-1] 通常包含报文数据
data = args[-1]
if isinstance(data, (list, tuple)):
hexData = ' '.join([f'{b:02X}' for b in data])
elif isinstance(data, bytes):
hexData = ' '.join([f'{b:02X}' for b in data])
else:
hexData = str(data)
self._addMessage('receive', f"接收请求: {hexData}")
except Exception as e:
pass
def captureResponse(args):
"""捕获响应报文数据"""
try:
if args and len(args) > 0:
# args[-1] 通常包含报文数据
data = args[-1]
if isinstance(data, (list, tuple)):
hexData = ' '.join([f'{b:02X}' for b in data])
elif isinstance(data, bytes):
hexData = ' '.join([f'{b:02X}' for b in data])
else:
hexData = str(data)
self._addMessage('send', f"发送响应: {hexData}")
except Exception as e:
pass
# 使用正确的 hook 名称
hooks.install_hook('modbus.Server.before_handle_request', captureRequest)
hooks.install_hook('modbus.Server.after_handle_request', captureResponse)
except Exception as e:
print(f"设置 hooks 失败: {e}")
# 如果 hooks 设置失败,不影响正常通讯功能
pass
def _addMessage(self, messageType, content):
"""添加报文记录"""
try:
with self.messageLock:
timestamp = time.strftime('%H:%M:%S')
message = f"[{timestamp}] {content}"
if messageType == 'send':
self.messageHistory['send'].append(message)
if len(self.messageHistory['send']) > self.maxMessageCount:
self.messageHistory['send'].pop(0)
elif messageType == 'receive':
self.messageHistory['receive'].append(message)
if len(self.messageHistory['receive']) > self.maxMessageCount:
self.messageHistory['receive'].pop(0)
except Exception as e:
pass
def getMessages(self):
"""获取报文信息"""
try:
with self.messageLock:
return {
'send': self.messageHistory['send'].copy(),
'receive': self.messageHistory['receive'].copy()
}
except Exception as e:
return {'send': [], 'receive': []}
def clearMessages(self):
"""清空报文记录"""
try:
with self.messageLock:
self.messageHistory['send'].clear()
self.messageHistory['receive'].clear()
except Exception as e:
pass