0716更新

main
zcwBit 5 months ago
parent 1ff43e4347
commit a5ec3feadd

@ -14,7 +14,6 @@ from ctypes.wintypes import MSG
from win32 import win32api, win32gui from win32 import win32api, win32gui
from win32.lib import win32con from win32.lib import win32con
from model.ProjectModel.VarManage import GlobalVarManager from model.ProjectModel.VarManage import GlobalVarManager
from windoweffect import WindowEffect, MINMAXINFO, NCCALCSIZE_PARAMS
from UI.Main.MainLeft import MainLeft from UI.Main.MainLeft import MainLeft
from UI.Main.MainTop import MainTop from UI.Main.MainTop import MainTop
from ..ProjectManages.ProjectWidget import ProjectWidgets from ..ProjectManages.ProjectWidget import ProjectWidgets
@ -368,71 +367,7 @@ class MainWindow(QMainWindow):
event.accept() event.accept()
def nativeEvent(self, eventType, message):
""" 接受windows发送的信息 """
msg = MSG.from_address(message.__int__())
if msg.message == win32con.WM_NCHITTEST:
# 解决 issue #2 and issue #7
r = self.devicePixelRatioF()
xPos = (win32api.LOWORD(msg.lParam) -
self.frameGeometry().x()*r) % 65536
yPos = win32api.HIWORD(msg.lParam) - self.frameGeometry().y()*r
w, h = self.width()*r, self.height()*r
lx = xPos < self.BORDER_WIDTH
rx = xPos + 9 > w - self.BORDER_WIDTH
ty = yPos < self.BORDER_WIDTH
by = yPos > h - self.BORDER_WIDTH
if lx and ty:
return True, win32con.HTTOPLEFT
elif rx and by:
return True, win32con.HTBOTTOMRIGHT
elif rx and ty:
return True, win32con.HTTOPRIGHT
elif lx and by:
return True, win32con.HTBOTTOMLEFT
elif ty:
return True, win32con.HTTOP
elif by:
return True, win32con.HTBOTTOM
elif lx:
return True, win32con.HTLEFT
elif rx:
return True, win32con.HTRIGHT
elif msg.message == win32con.WM_NCCALCSIZE:
if self.__isWindowMaximized(msg.hWnd):
self.__monitorNCCALCSIZE(msg)
return True, 0
elif msg.message == win32con.WM_GETMINMAXINFO:
if self.__isWindowMaximized(msg.hWnd):
window_rect = win32gui.GetWindowRect(msg.hWnd)
if not window_rect:
return False, 0
# get the monitor handle
monitor = win32api.MonitorFromRect(window_rect)
if not monitor:
return False, 0
# get the monitor info
__monitorInfo = win32api.GetMonitorInfo(monitor)
monitor_rect = __monitorInfo['Monitor']
work_area = __monitorInfo['Work']
# convert lParam to MINMAXINFO pointer
info = cast(msg.lParam, POINTER(MINMAXINFO)).contents
# adjust the size of window
info.ptMaxSize.x = work_area[2] - work_area[0]
info.ptMaxSize.y = work_area[3] - work_area[1]
info.ptMaxTrackSize.x = info.ptMaxSize.x
info.ptMaxTrackSize.y = info.ptMaxSize.y
# modify the upper left coordinate
info.ptMaxPosition.x = abs(window_rect[0] - monitor_rect[0])
info.ptMaxPosition.y = abs(window_rect[1] - monitor_rect[1])
return True, 1
return QWidget.nativeEvent(self, eventType, message)
def __isWindowMaximized(self, hWnd) -> bool: def __isWindowMaximized(self, hWnd) -> bool:

@ -216,23 +216,36 @@ class ProjectButtonDelegate(QItemDelegate):
"请先保存工程", "请先保存工程",
QMessageBox.Yes) QMessageBox.Yes)
return return
self.loginWidget = LoginWidget()
self.loginWidget.show()
ProjectManage.switchProject(str(self.parent().model.datas[sender.index[0]][1]))
#初始化读取数据库数据
# 初始化读取数据库数据,添加进度条
progress = QtWidgets.QProgressDialog()
progress.setLabelText("正在加载工程数据...")
progress.setCancelButton(None)
progress.setMinimum(0)
progress.setMaximum(0)
progress.setWindowModality(QtCore.Qt.ApplicationModal)
progress.setMinimumDuration(0)
progress.show()
QtWidgets.QApplication.processEvents()
ProjectManage.switchProject(str(self.parent().model.datas[sender.index[0]][1]))
modelLists = ['ModbusTcpMasterTable', 'ModbusTcpSlaveTable', 'ModbusRtuMasterTable', \ modelLists = ['ModbusTcpMasterTable', 'ModbusTcpSlaveTable', 'ModbusRtuMasterTable', \
'ModbusRtuSlaveTable', 'HartTable', 'TcRtdTable', 'AnalogTable', 'HartSimulateTable', 'userTable'] 'ModbusRtuSlaveTable', 'HartTable', 'TcRtdTable', 'AnalogTable', 'HartSimulateTable', 'userTable']
for l in modelLists: for l in modelLists:
Globals.getValue(l).model.initTable() Globals.getValue(l).model.initTable()
QtWidgets.QApplication.processEvents()
profibusTabWidgets = ['DP主站', 'DP从站', 'PA主站', 'PA从站'] profibusTabWidgets = ['DP主站', 'DP从站', 'PA主站', 'PA从站']
for widget in profibusTabWidgets: for widget in profibusTabWidgets:
Globals.getValue(widget).switchProject() Globals.getValue(widget).switchProject()
QtWidgets.QApplication.processEvents()
Globals.getValue('HistoryWidget').exchangeProject()
progress.close()
QtWidgets.QApplication.processEvents()
self.parent().proxy.invalidate() self.parent().proxy.invalidate()
self.loginWidget = LoginWidget()
self.loginWidget.show()
def edit_action(self): def edit_action(self):

@ -65,172 +65,94 @@ class HistoryTrend(object):
class TrendWidgets(QWidget): class TrendWidgets(QWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super(TrendWidgets, self).__init__(parent) super(TrendWidgets, self).__init__(parent)
self.memLsit = [] # 所有mem列表 # self.setAttribute(Qt.WA_StyledBackground, True) # 移除无效属性
self.setAttribute(Qt.WA_StyledBackground, True)
self.gridLayout = QtWidgets.QGridLayout(self) self.gridLayout = QtWidgets.QGridLayout(self)
self.gridLayout.setObjectName("gridLayout") self.gridLayout.setObjectName("gridLayout")
self.timeBox = QtWidgets.QComboBox() # 变量列表
self.timeBox.setObjectName("timeBox") self.varListWidget = QListWidget()
self.timeBox.activated.connect(self.refreshList) self.varListWidget.setObjectName("varListWidget")
self.varListWidget.itemDoubleClicked.connect(self.showVarTrend)
self.listview = QListView() self.gridLayout.addWidget(self.varListWidget, 0, 0, 15, 4)
self.listview.setObjectName("trendListView")
self.model = QStandardItemModel()
self.listview.setContextMenuPolicy(Qt.CustomContextMenu)
self.listview.customContextMenuRequested.connect(self.listContext)
self.model.itemChanged.connect(self.itemCheckstate)
# self.widget = QtWidgets.QWidget(self)
# 趋势图WebView
self.trendWebView = QWebEngineView() self.trendWebView = QWebEngineView()
self.trendWebView.setObjectName("trendWebView") self.trendWebView.setObjectName("trendWebView")
self.proxy = QtCore.QSortFilterProxyModel(self)
# self.listview.proxy = self.proxy
self.proxy.setSourceModel(self.model)
self.listview.setModel(self.proxy)
self.delBtn = QtWidgets.QPushButton(QIcon(':/static/delete.png'), '删除记录', self)
self.delBtn.setObjectName('delBtn')
self.delBtn.setIconSize(QSize(22, 22))
self.delBtn.clicked.connect(self.deleteMem)
self.gridLayout.addWidget(self.timeBox, 0, 0, 1, 3)
self.gridLayout.addWidget(self.delBtn, 0, 3, 1, 1)
self.gridLayout.addWidget(self.listview, 1, 0, 14, 4)
self.gridLayout.addWidget(self.trendWebView, 0, 4, 15, 22) self.gridLayout.addWidget(self.trendWebView, 0, 4, 15, 22)
self.gridLayout.setSpacing(10) self.gridLayout.setSpacing(10)
self.gridLayout.setContentsMargins(20, 20, 20, 20) self.gridLayout.setContentsMargins(20, 20, 20, 20)
Globals.setValue('HistoryWidget', self)
def exchangeProject(self):
Globals.setValue('HistoryWidget', self) self.historyDB = Globals.getValue('historyDBManage')
self.refreshVarList()
def deleteMem(self): def deleteMem(self):
if not self.memLsit: pass # 历史mem相关功能已废弃
return
self.historyDB.mem = self.memLsit[self.timeBox.currentIndex()]
self.historyDB.deleteMem()
InfluxMem.deleteMem(self.historyDB.mem)
self.getMems()
self.exchangeProject()
self.model.clear()
def itemCheckstate(self): def itemCheckstate(self):
# 勾选变量复选框事件 pass # 历史mem相关功能已废弃
allItems = self.model.findItems('*', Qt.MatchWildcard)
itemCheckedList = []
for item in allItems:
if item.checkState() == Qt.Checked:
itemCheckedList.append(item.text())
if not itemCheckedList:
return
self.lineTrend = HistoryTrend(self.trendWebView)
self.createHtml(itemCheckedList)
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot(str)
def on_lineEdit_textChanged(self, text): def on_lineEdit_textChanged(self, text):
# 搜索框文字变化函数 pass # 历史mem相关功能已废弃
search = QtCore.QRegExp(text,
QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.RegExp
)
self.proxy.setFilterKeyColumn(0)
self.proxy.setFilterRegExp(search)
def listContext(self, position): def listContext(self, position):
# 点击右键删除事件 pass # 历史mem相关功能已废弃
menu = QtWidgets.QMenu()
listDel = QtWidgets.QAction('删除')
listDel.triggered.connect(self.delVarRecard)
menu.addAction(listDel)
menu.exec_(self.listview.mapToGlobal(position))
def delVarRecard(self): def delVarRecard(self):
# 删除变量记录 pass # 历史mem相关功能已废弃
# count = self.listWidget.count()
# cb_list = [self.listWidget.itemWidget(self.listWidget.item(i))
# for i in range(count)]
index = self.listview.currentIndex()
varName = self.model.item(index.row()).text()
self.model.removeRow(index.row())
# self.memName = self.timeBox.currentText()
self.historyDB.mem = self.memLsit[self.timeBox.currentIndex()]
self.historyDB.deleteFun(str(varName))
def exchangeProject(self):
self.timeBox.clear()
self.memLsit = []
self.getMems()
self.refreshList(0)
self.trendWebView.setHtml('')
def getMems(self): def getMems(self):
# 获取所有的趋势表 pass # 历史mem相关功能已废弃
mems = InfluxMem.get_all()
self.proName = Globals.getValue('currentPro')
if not self.proName:
return
self.timeBox.clear()
if mems is 'Error':
return
for x in mems:
time = datetime.datetime.fromtimestamp(float(x.mem)).strftime("%Y-%m-%d %H:%M:%S")
if self.timeBox.findText(time) == -1:
self.timeBox.addItem(time)
self.memLsit.append(x.mem)
self.historyDB = HistoryDBManage(bucket = self.proName, isCelery = False)
# self.refreshList(self.timeBox.currentIndex())
def refreshList(self, index): def refreshList(self, index):
# 更新变量列表 pass # 历史mem相关功能已废弃
self.model.clear()
if self.memLsit == []:
return
self.historyDB.mem = self.memLsit[index]
# self.historyDB.startTime = int(float(self.memLsit[index]))
# print(self.memLsit[index])
tagSet = self.historyDB.queryVarName()
# if not tagSet:
# QMessageBox.information(self, '提示', '当前工程历史趋势已损坏')
# return
for tag in tagSet:
item = QStandardItem(str(tag))
item.setCheckable(True)
self.model.appendRow(item)
self.lineTrend = HistoryTrend(self.trendWebView)
def createHtml(self, varNames): def createHtml(self, varNames):
# 创建趋势图网页 pass # 历史mem相关功能已废弃
self.historyDB.mem = self.memLsit[self.timeBox.currentIndex()]
xAxisList = [] def refreshVarList(self):
allData = [] # 显示进度条
for var in varNames: self.varListWidget.clear()
varData, xAxis = self.historyDB.queryFun(str(var)) varNames = []
xAxisList.append(xAxis) try:
allData.append(varData) sql = f'SELECT DISTINCT("varName") FROM "{self.historyDB.table}"'
xAxis = max(xAxisList, key=len) df = self.historyDB.client.query(sql, mode="pandas")
self.lineTrend.addXAxis(xAxis) import pandas as pd
[self.lineTrend.addYAxis(varNames[index], varData) for index, varData in enumerate(allData)] if isinstance(df, pd.DataFrame) and 'varName' in df.columns:
# self.trendWebView.reload() varNames = df['varName'].tolist()
except Exception as e:
print(f"获取变量名失败: {e}")
varNames = []
for name in varNames:
item = QListWidgetItem(str(name))
self.varListWidget.addItem(item)
def showVarTrend(self, item):
varName = item.text()
# 查询该变量历史数据
data, timeList = self.historyDB.queryVarHistory(varName)
if not data or not timeList:
self.trendWebView.setHtml('<h3>无历史数据</h3>')
return
# 构建趋势图
line = Line(init_opts=opts.InitOpts(width='900px', height='500px'))
line.add_xaxis(timeList)
line.add_yaxis(varName, data)
line.set_global_opts(
title_opts=opts.TitleOpts(title=f"{varName} 历史趋势"),
tooltip_opts=opts.TooltipOpts(trigger="axis"),
xaxis_opts=opts.AxisOpts(type_="category", boundary_gap=False),
datazoom_opts=[opts.DataZoomOpts(xaxis_index=0, range_start=0, range_end=100)],
)
html = line.render_embed()
page = QWebEnginePage(self.trendWebView) page = QWebEnginePage(self.trendWebView)
page.setHtml('<meta charset="utf-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">\n' + self.lineTrend.html) page.setHtml('<meta charset="utf-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">\n' + html)
self.trendWebView.setPage(page) self.trendWebView.setPage(page)
self.trendWebView.reload() self.trendWebView.reload()

@ -47,6 +47,8 @@ class Client(object):
from model.ProjectModel.ProjectManage import ProjectManage from model.ProjectModel.ProjectManage import ProjectManage
popen = Globals.getValue('popen') popen = Globals.getValue('popen')
popenBeat = Globals.getValue('beatPopen') popenBeat = Globals.getValue('beatPopen')
histroyMan = Globals.getValue('historyDBManage')
histroyMan.close()
if popen: if popen:
popen.kill() popen.kill()
if popenBeat: if popenBeat:

@ -1,13 +1,14 @@
import os import os
import time import time
from datetime import datetime from datetime import datetime, timezone, timedelta
from influxdb_client import InfluxDBClient, Point, WritePrecision, BucketsApi from influxdb_client_3 import InfluxDBClient3, Point, WriteOptions, write_client_options
from influxdb_client.client.write_api import SYNCHRONOUS
from influxdb_client.client.util import date_utils
from influxdb_client.client.util.date_utils import DateHelper
import dateutil.parser import dateutil.parser
from dateutil import tz from dateutil import tz
import pandas as pd
import requests
import queue
import threading
@ -17,106 +18,146 @@ def parseDate(date_string: str):
return dateutil.parser.parse(date_string).astimezone(tz.gettz('ETC/GMT-8')) return dateutil.parser.parse(date_string).astimezone(tz.gettz('ETC/GMT-8'))
class HistoryDBManage(): class HistoryDBManage:
org = "DCS" def __init__(self, database='dcs', table='history', host="http://localhost:8181", token="", org="dcs"):
# bucket = "history" token = self.getAPIToken() if not token else token
url = "http://localhost:8086" self.database = database
self.table = table
def __init__(self, bucket, mem = None, isCelery = True): self.host = host
self.getToken(isCelery) self.token = token
self.client = InfluxDBClient(url = self.url, token = self.token, org = self.org) self.org = org
self.writeApi = self.client.write_api(write_options = SYNCHRONOUS)
self.deleteApi = self.client.delete_api() # 写入回调
self.queryApi = self.client.query_api() def onSuccess(conf, data, exception=None):
self.bucketApi = self.client.buckets_api() now_ms = datetime.now(timezone(timedelta(hours=8)))
self.mem = mem # 统一转为字符串
self.bucket = bucket if isinstance(data, bytes):
date_utils.date_helper = DateHelper() data = data.decode(errors="ignore")
date_utils.date_helper.parse_date = parseDate elif not isinstance(data, str):
# if self.mem: data = str(data)
self.startTime = 1680534 for line in data.split('\n'):
# print(line)
def __del__(self): # m = re.search(r'enqueue_time_ms=([0-9]+)', line)
self.client.close() m = line.split(' ')[-1][:-3]
if m:
def getToken(self, isCelery): enqueue_time_ms = int(m)
if not isCelery: # 转为datetime对象
with open('Static/InfluxDB.api', 'r', encoding='utf-8') as f: # print(enqueue_time_ms / 1000)
self.token = f.read() enqueue_time_dt = datetime.fromtimestamp(enqueue_time_ms /1000000, tz=timezone(timedelta(hours=8)))
else: # delay = now_ms - enqueue_time_ms
with open('../../../Static/InfluxDB.api', 'r', encoding='utf-8') as f: print(f"写入延迟: {1} ms (enqueue_time={enqueue_time_dt}, now={now_ms})")
self.token = f.read() def onError(conf, data, exception=None):
print("InfluxDB写入失败")
def onRetry(conf, data, exception=None):
print("InfluxDB写入重试")
def writeFun(self, varName, value):
try: # 官方推荐批量写入配置
value = int(value) self.client = InfluxDBClient3(
point = Point(self.mem).tag("varName", varName).field("value", value).time(datetime.utcnow(), WritePrecision.NS) host=host,
self.writeApi.write(self.bucket, self.org, point) token=token,
except Exception as e: org=org,
print(e) database=database,
BucketsApi(self.client).create_bucket(bucket_name = self.bucket, org = self.org) write_client_options=write_client_options(
write_options=WriteOptions(batch_size=5000, flush_interval=2000), # flush_interval单位ms
def queryFun(self, varName): # success_callback=onSuccess,
# startTime = time.mktime(time.strptime("%Y-%m-%d %H:%M:%S", mem)) error_callback=onError,
data = [] retry_callback=onRetry
timeList = [] )
query = ' from(bucket:"{}")\ )
|> range(start: {})\
|> filter(fn:(r) => r._measurement == "{}")\ self.writeQueue = queue.Queue()
|> filter(fn:(r) => r.varName == "{}")\ self._stopWriteThread = threading.Event()
|> filter(fn:(r) => r._field == "value" )'.format(self.bucket, self.startTime, self.mem, varName) self.writeThread = threading.Thread(target=self._writeWorker, daemon=True)
results = self.queryApi.query(query, org = self.org) self.writeThread.start()
for result in results:
for record in result.records: @classmethod
data.append(record['_value']) def getAPIToken(cls):
timeList.append(record['_time'].strftime("%H:%M:%S:%f")[:-3]) try:
return data, timeList with open('Static/InfluxDB.api', 'r', encoding='utf-8') as f:
token = f.read()
def deleteMem(self): except Exception as e:
self.deleteApi.delete(start = '1970-01-01T00:00:00Z', stop = '2099-01-01T00:00:00Z', predicate = '_measurement={}'.format(self.mem), bucket = self.bucket, org = self.org, ) print(f"读取token文件失败: {e}")
token = ""
def deleteFun(self, varName): return token
self.deleteApi.delete(start = '1970-01-01T00:00:00Z', stop = '2099-01-01T00:00:00Z', predicate = '_measurement={} AND varName={}'.format(self.mem, varName), bucket = self.bucket, org = self.org, )
def createDatabaseIfNotExists(self):
def deleteBucket(self): try:
bucket = self.bucketApi.find_bucket_by_name(self.bucket) sql = f'SELECT 1 FROM "{self.table}" LIMIT 1'
self.bucketApi.delete_bucket(bucket) self.client.query(sql)
except Exception:
def queryMem(self): pass
query = 'from(bucket: "{}") |> range(start: 0) |> keys(column: " _measurement")'.format(self.bucket)
result = self.queryApi.query(query) def writeVarValue(self, varName, value):
return result # 入队时记录当前时间
enqueue_time = datetime.now(timezone(timedelta(hours=8)))
def queryVarName(self): # print(enqueue_time)
tagSet = set() self.writeQueue.put((varName, value, enqueue_time))
query = ' from(bucket:"{}")\
|> range(start: {})\ def _writeWorker(self):
|> filter(fn:(r) => r._measurement == "{}")\ while not self._stopWriteThread.is_set():
|> filter(fn:(r) => r._field == "value")'.format(self.bucket, self.startTime, self.mem) try:
try: varName, value, enqueue_time = self.writeQueue.get()
results = self.queryApi.query(query, org = self.org) # print(self.writeQueue.qsize())
except Exception as e: except queue.Empty:
BucketsApi(self.client).create_bucket(bucket_name = self.bucket, org = self.org) # print(1111)
return tagSet continue
for result in results: # 用Point对象构建数据点时间用入队时间
for record in result.records: point = Point(self.table).tag("varName", varName).field("value", float(value)).time(enqueue_time)
tagSet.add(record['varName']) self.client.write(point)
return tagSet
def queryVarHistory(self, varName, startTime=None, endTime=None, limit=1000):
where = f'"varName" = \'{varName}\''
if startTime:
where += f" AND time >= '{startTime}'"
if endTime:
where += f" AND time <= '{endTime}'"
sql = f'SELECT time, value FROM "{self.table}" WHERE {where} ORDER BY time LIMIT {limit}'
try:
df = self.client.query(sql, mode="pandas")
import pandas as pd
if isinstance(df, pd.DataFrame):
data = df["value"].tolist() if "value" in df.columns else []
timeList = df["time"].tolist() if "time" in df.columns else []
else:
data, timeList = [], []
except Exception as e:
print(f"查询结果处理失败: {e}")
data, timeList = [], []
return data, timeList
@classmethod
def deleteTable(cls, table):
token = cls.getAPIToken()
host = 'http://localhost:8181'
url = f"{host.rstrip('/')}/api/v3/configure/table?db={'dcs'}&table={table}"
headers = {
'Authorization': f'Bearer {token}'
}
response = requests.delete(url, headers=headers)
if response.status_code != 200:
print(f"删除失败: {response.status_code} {response.text}")
else:
print(f"已删除表 {table} 的所有历史数据。")
def stopWriteThread(self):
self._stopWriteThread.set()
self.writeThread.join(timeout=1)
def close(self):
self.stopWriteThread()
self.client.close()
if __name__ == '__main__': if __name__ == '__main__':
t = time.time() db = HistoryDBManage(
# t = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(t)) database="dcs",
print(t) table="p1",
h = HistoryDBManage(mem = t, bucket = 'history') host="http://localhost:8181",
h.writeFun('test1', 1) token="apiv3_ynlNTgq_OX164srSzjYXetWZJGOpgokFJbp_JaToWYlzwIPAZboPxKt4ss6vD1_4jj90QOIDnRDodQSJ66m3_g",
h.writeFun('test1', 2) org="dcs"
h.writeFun('test1', 3) )
h.queryFun('test1') data, times = db.queryVarHistory("有源/无源4-20mA输入通道1")
h.queryMem() print(data, times)
h.deleteMem() db.close()
# h.deleteBucket()

@ -113,6 +113,8 @@ class ProjectManage(object):
except OSError as e: except OSError as e:
print(e) print(e)
Project.deleteProject(name = name) Project.deleteProject(name = name)
histroyTableName = 'p' + name
HistoryDBManage.deleteTable(table=histroyTableName)
@classmethod @classmethod
def switchProject(self, name): def switchProject(self, name):
@ -140,7 +142,9 @@ class ProjectManage(object):
Globals.setValue('currentProDB', projectDB) Globals.setValue('currentProDB', projectDB)
Globals.setValue('currentProType', 1)#切换工程 Globals.setValue('currentProType', 1)#切换工程
protocolManage = ProtocolManage() histroyTableName = 'p' + name
historyDBManage = HistoryDBManage(table=histroyTableName)
Globals.setValue('historyDBManage', historyDBManage)
# protocolManage.writeVariableValue('无源4-20mA输出通道1', 150) # protocolManage.writeVariableValue('无源4-20mA输出通道1', 150)
# protocolManage.writeVariableValue('有源24V数字输出通道12', 1) # protocolManage.writeVariableValue('有源24V数字输出通道12', 1)
# protocolManage.writeVariableValue('热偶输出2', 40) # protocolManage.writeVariableValue('热偶输出2', 40)
@ -148,7 +152,10 @@ class ProjectManage(object):
# print(protocolManage.readVariableValue('有源/无源4-20mA输入通道2')) # print(protocolManage.readVariableValue('有源/无源4-20mA输入通道2'))
# print(protocolManage.readVariableValue('无源24V数字输入通道2')) # print(protocolManage.readVariableValue('无源24V数字输入通道2'))
# print(a) # print(a)
# print(Globals.getValue('historyDBManage'))
protocolManage = ProtocolManage()
Globals.setValue('protocolManage', protocolManage) Globals.setValue('protocolManage', protocolManage)
Globals.getValue('HistoryWidget').exchangeProject()
# if Globals.getValue('FFThread').isRunning(): # if Globals.getValue('FFThread').isRunning():
# Globals.getValue('FFThread').reStart() # Globals.getValue('FFThread').reStart()

@ -9,9 +9,21 @@ import openpyxl
from utils.DBModels.ProtocolModel import ModbusTcpMasterVar, ModbusRtuMasterVar, ModbusRtuSlaveVar,\ from utils.DBModels.ProtocolModel import ModbusTcpMasterVar, ModbusRtuMasterVar, ModbusRtuSlaveVar,\
ModbusTcpSlaveVar, HartVar, TcRtdVar, AnalogVar, HartSimulateVar ModbusTcpSlaveVar, HartVar, TcRtdVar, AnalogVar, HartSimulateVar
from PyQt5.Qt import QFileDialog, QMessageBox from PyQt5.Qt import QFileDialog, QMessageBox
from protocol.ProtocolManage import ProtocolManage
# ID 从站地址 变量名 变量描述 变量类型 寄存器地址 工程量下限 工程量上限 # ID 从站地址 变量名 变量描述 变量类型 寄存器地址 工程量下限 工程量上限
# 获取ProtocolManage单例或全局对象假设为全局唯一实际项目可根据实际情况调整
def getProtocolManager():
protocolManagerInstance = Globals.getValue('protocolManage')
if protocolManagerInstance is None:
protocolManagerInstance = ProtocolManage()
return protocolManagerInstance
class ModbusVarManage(object): class ModbusVarManage(object):
ModBusVarClass = { ModBusVarClass = {
'ModbusTcpMaster': ModbusTcpMasterVar, 'ModbusTcpMaster': ModbusTcpMasterVar,
@ -42,6 +54,8 @@ class ModbusVarManage(object):
modbusVarType =self.getVarClass(modbusType)() modbusVarType =self.getVarClass(modbusType)()
modbusVarType.createVar(varName = varName, varType = varType, des = des, address = address, modbusVarType.createVar(varName = varName, varType = varType, des = des, address = address,
slaveID = slaveID, min = min, max = max, order = order, varModel = varModel) slaveID = slaveID, min = min, max = max, order = order, varModel = varModel)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
# def importModbusVar(self, path): # def importModbusVar(self, path):
@ -147,6 +161,8 @@ class ModbusVarManage(object):
modbusVarType =self.getVarClass(modbusType)() modbusVarType =self.getVarClass(modbusType)()
modbusVarType.createVar(varName = varName, varType = varType, des = des, address = address, modbusVarType.createVar(varName = varName, varType = varType, des = des, address = address,
slaveID = slaveID, min = min, max = max, order = order, varModel = varModel) slaveID = slaveID, min = min, max = max, order = order, varModel = varModel)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
@ -155,6 +171,8 @@ class ModbusVarManage(object):
name = str(name) name = str(name)
# print(name) # print(name)
self.getVarClass(modbusType).deleteVar(name = name) self.getVarClass(modbusType).deleteVar(name = name)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
@ -182,18 +200,24 @@ class ModbusVarManage(object):
else: else:
self.getVarClass(modbusType).update(varName = Nname, description = des, varType = varType, address = address, slaveID = slaveID, self.getVarClass(modbusType).update(varName = Nname, description = des, varType = varType, address = address, slaveID = slaveID,
min = min, max = max, order = order, varModel = varModel).where(self.getVarClass(modbusType).varName == name).execute() min = min, max = max, order = order, varModel = varModel).where(self.getVarClass(modbusType).varName == name).execute()
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def editOrder(self, name, order, modbusType): def editOrder(self, name, order, modbusType):
name = str(name) name = str(name)
order = str(order) order = str(order)
self.getVarClass(modbusType).update(order = order).where(self.getVarClass(modbusType).varName == name).execute() self.getVarClass(modbusType).update(order = order).where(self.getVarClass(modbusType).varName == name).execute()
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def editVarModel(self, name, varModel, modbusType): def editVarModel(self, name, varModel, modbusType):
name = str(name) name = str(name)
varModel = str(varModel) varModel = str(varModel)
self.getVarClass(modbusType).update(varModel = varModel).where(self.getVarClass(modbusType).varName == name).execute() self.getVarClass(modbusType).update(varModel = varModel).where(self.getVarClass(modbusType).varName == name).execute()
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def getAllVar(self, modbusType): def getAllVar(self, modbusType):
@ -230,6 +254,8 @@ class HartVarManage(object):
else: else:
hartVarType = HartVar() hartVarType = HartVar()
hartVarType.createVar(varName = varName, des = des, varModel = varModel) hartVarType.createVar(varName = varName, des = des, varModel = varModel)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def getAllVar(self): def getAllVar(self):
@ -249,6 +275,8 @@ class HartVarManage(object):
name = str(name) name = str(name)
# print(name) # print(name)
HartVar.deleteVar(name = name) HartVar.deleteVar(name = name)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
@ -269,12 +297,16 @@ class HartVarManage(object):
return return
else: else:
HartVar.update(varName = Nname, description = des, varModel = varModel).where(HartVar.varName == name).execute() HartVar.update(varName = Nname, description = des, varModel = varModel).where(HartVar.varName == name).execute()
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def editVarModel(self, name, varModel): def editVarModel(self, name, varModel):
name = str(name) name = str(name)
print('修改变量模型',name) print('修改变量模型',name)
HartVar.update(varModel = str(varModel)).where(HartVar.varName == name).execute() HartVar.update(varModel = str(varModel)).where(HartVar.varName == name).execute()
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
@ -299,6 +331,8 @@ class HartVarManage(object):
else: else:
hartVarType = HartVar() hartVarType = HartVar()
hartVarType.createVar(varName = varName, des = des, varModel = varModel) hartVarType.createVar(varName = varName, des = des, varModel = varModel)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
class TcRtdManage(object): class TcRtdManage(object):
@ -317,6 +351,8 @@ class TcRtdManage(object):
else: else:
tcRtdVarType = TcRtdVar() tcRtdVarType = TcRtdVar()
tcRtdVarType.createVar(varName = varName, channelNumber=channelNumber, des = des, varType = varType, min = min, max = max, compensationVar = compensationVar, varModel = varModel) tcRtdVarType.createVar(varName = varName, channelNumber=channelNumber, des = des, varType = varType, min = min, max = max, compensationVar = compensationVar, varModel = varModel)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def getAllVar(self): def getAllVar(self):
@ -336,6 +372,8 @@ class TcRtdManage(object):
name = str(name) name = str(name)
# print(name) # print(name)
TcRtdVar.deleteVar(name = name) TcRtdVar.deleteVar(name = name)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
@ -360,6 +398,8 @@ class TcRtdManage(object):
return return
else: else:
TcRtdVar.update(varName=Nname, channelNumber = channelNumber, description=des, varType=varType, min=min, max=max, compensationVar = compensationVar).where(TcRtdVar.varName == name).execute() TcRtdVar.update(varName=Nname, channelNumber = channelNumber, description=des, varType=varType, min=min, max=max, compensationVar = compensationVar).where(TcRtdVar.varName == name).execute()
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def getByName(self, name): def getByName(self, name):
@ -384,11 +424,15 @@ class TcRtdManage(object):
def editvarType(self, name, varType): def editvarType(self, name, varType):
name = str(name) name = str(name)
TcRtdVar.update(varType = str(varType)).where(TcRtdVar.varName == name).execute() TcRtdVar.update(varType = str(varType)).where(TcRtdVar.varName == name).execute()
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def editVarModel(self, name, varModel): def editVarModel(self, name, varModel):
name = str(name) name = str(name)
TcRtdVar.update(varModel = str(varModel)).where(TcRtdVar.varName == name).execute() TcRtdVar.update(varModel = str(varModel)).where(TcRtdVar.varName == name).execute()
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def importVarForm(self, varName, channelNumber, varType, des, min, max, compensationVar, varModel): def importVarForm(self, varName, channelNumber, varType, des, min, max, compensationVar, varModel):
@ -398,6 +442,8 @@ class TcRtdManage(object):
else: else:
tcRtdVarType = TcRtdVar() tcRtdVarType = TcRtdVar()
tcRtdVarType.createVar(varName = varName, channelNumber=channelNumber, des = des, varType = varType, min = min, max = max, compensationVar = compensationVar, varModel = varModel) tcRtdVarType.createVar(varName = varName, channelNumber=channelNumber, des = des, varType = varType, min = min, max = max, compensationVar = compensationVar, varModel = varModel)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
class AnalogManage(object): class AnalogManage(object):
def __init__(self): def __init__(self):
@ -415,6 +461,8 @@ class AnalogManage(object):
# else: # else:
analogVarType = AnalogVar() analogVarType = AnalogVar()
analogVarType.createVar(varName = varName, channelNumber = channelNumber, des = des, varType = varType, min = min, max = max, varModel = varModel) analogVarType.createVar(varName = varName, channelNumber = channelNumber, des = des, varType = varType, min = min, max = max, varModel = varModel)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def getAllVar(self): def getAllVar(self):
@ -434,6 +482,8 @@ class AnalogManage(object):
name = str(name) name = str(name)
# print(name) # print(name)
AnalogVar.deleteVar(name = name) AnalogVar.deleteVar(name = name)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
@ -456,11 +506,15 @@ class AnalogManage(object):
return return
else: else:
AnalogVar.update(varName=Nname, channelNumber =channelNumber, description=des, varType=varType, min=min, max=max).where(AnalogVar.varName == name).execute() AnalogVar.update(varName=Nname, channelNumber =channelNumber, description=des, varType=varType, min=min, max=max).where(AnalogVar.varName == name).execute()
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def editVarModel(self, name, varModel): def editVarModel(self, name, varModel):
name = str(name) name = str(name)
AnalogVar.update(varModel = str(varModel)).where(AnalogVar.varName == name).execute() AnalogVar.update(varModel = str(varModel)).where(AnalogVar.varName == name).execute()
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def getByName(self, name): def getByName(self, name):
# 查询指定变量信息 # 查询指定变量信息
@ -510,6 +564,8 @@ class AnalogManage(object):
else: else:
analogVarType = AnalogVar() analogVarType = AnalogVar()
analogVarType.createVar(varName = varName, channelNumber = channelNumber, des = des, varType = varType, min = min, max = max, varModel = varModel) analogVarType.createVar(varName = varName, channelNumber = channelNumber, des = des, varType = varType, min = min, max = max, varModel = varModel)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@ -528,6 +584,8 @@ class HartSimulateVarManage(object):
else: else:
hartSimulateVarType = HartSimulateVar() hartSimulateVarType = HartSimulateVar()
hartSimulateVarType.createVar(varName = varName, des = des,varModel = varModel) hartSimulateVarType.createVar(varName = varName, des = des,varModel = varModel)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def getAllVar(self): def getAllVar(self):
@ -547,6 +605,8 @@ class HartSimulateVarManage(object):
name = str(name) name = str(name)
# print(name) # print(name)
HartSimulateVar.deleteVar(name = name) HartSimulateVar.deleteVar(name = name)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
@ -566,11 +626,15 @@ class HartSimulateVarManage(object):
return return
else: else:
HartSimulateVar.update(varName = Nname, description = des, varModel = varModel).where(HartSimulateVar.varName == name).execute() HartSimulateVar.update(varName = Nname, description = des, varModel = varModel).where(HartSimulateVar.varName == name).execute()
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def editVarModel(self, name, varModel): def editVarModel(self, name, varModel):
name = str(name) name = str(name)
HartSimulateVar.update(varModel = str(varModel)).where(HartSimulateVar.varName == name).execute() HartSimulateVar.update(varModel = str(varModel)).where(HartSimulateVar.varName == name).execute()
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
@classmethod @classmethod
def getByName(self, name): def getByName(self, name):
# 查询指定变量信息 # 查询指定变量信息
@ -593,6 +657,8 @@ class HartSimulateVarManage(object):
else: else:
hartSimulateVarType = HartSimulateVar() hartSimulateVarType = HartSimulateVar()
hartSimulateVarType.createVar(varName = varName, des = des,varModel = varModel) hartSimulateVarType.createVar(varName = varName, des = des,varModel = varModel)
# 操作后刷新缓存
getProtocolManager().refreshVarCache()
class GlobalVarManager(object): class GlobalVarManager(object):

@ -7,6 +7,9 @@ from protocol.TCP.TCPVarManage import *
from protocol.TCP.TemToMv import temToMv from protocol.TCP.TemToMv import temToMv
from protocol.RPC.RpcClient import RpcClient from protocol.RPC.RpcClient import RpcClient
from protocol.RPC.RpcServer import RpcServer from protocol.RPC.RpcServer import RpcServer
from utils import Globals
import threading
import time
class ProtocolManage(object): class ProtocolManage(object):
"""通讯变量查找类,用于根据变量名在数据库模型中检索变量信息""" """通讯变量查找类,用于根据变量名在数据库模型中检索变量信息"""
@ -25,31 +28,61 @@ class ProtocolManage(object):
self.writeRTD = [0] * 8 self.writeRTD = [0] * 8
self.RpcClient = None self.RpcClient = None
self.RpcServer = None self.RpcServer = None
self.varInfoCache = {} # 保持驼峰命名
self.historyDBManage = Globals.getValue('historyDBManage')
self.variableValueCache = {} # {varName: value}
self.refreshVarCache()
self.cacheLock = threading.Lock()
self.readThreadStop = threading.Event()
self.readThread = threading.Thread(target=self._backgroundReadAllVariables, daemon=True)
self.readThread.start()
def clearVarCache(self):
"""清空变量信息缓存"""
self.varInfoCache.clear()
@classmethod def refreshVarCache(self):
def lookupVariable(cls, variableName): """重新加载所有变量信息到缓存(可选实现)"""
self.varInfoCache.clear()
for modelClass in self.MODEL_CLASSES:
try:
for varInstance in modelClass.select():
varName = getattr(varInstance, 'varName', None)
if varName:
varData = {}
for field in varInstance._meta.sorted_fields:
fieldName = field.name
varData[fieldName] = getattr(varInstance, fieldName)
self.varInfoCache[varName] = {
'modelType': modelClass.__name__,
'variableData': varData
}
except Exception as e:
print(f"刷新缓存时出错: {modelClass.__name__}: {e}")
def lookupVariable(self, variableName):
""" """
根据变量名检索变量信息 根据变量名检索变量信息优先查缓存
:param variableName: 要查询的变量名 :param variableName: 要查询的变量名
:return: 包含变量信息和模型类型的字典如果未找到返回None :return: 包含变量信息和模型类型的字典如果未找到返回None
""" """
for modelClass in cls.MODEL_CLASSES: if variableName in self.varInfoCache:
# print(111)
return self.varInfoCache[variableName]
for modelClass in self.MODEL_CLASSES:
varInstance = modelClass.getByName(variableName) varInstance = modelClass.getByName(variableName)
if varInstance: if varInstance:
# 获取变量字段信息
varData = {} varData = {}
for field in varInstance._meta.sorted_fields: for field in varInstance._meta.sorted_fields:
fieldName = field.name fieldName = field.name
varData[fieldName] = getattr(varInstance, fieldName) varData[fieldName] = getattr(varInstance, fieldName)
result = {
return { 'modelType': modelClass.__name__,
'model_type': modelClass.__name__, 'variableData': varData
'variable_data': varData
} }
self.varInfoCache[variableName] = result # 写入缓存
return result
return None return None
def setClentMode(self, clentName, rabbitmqHost='localhost'): def setClentMode(self, clentName, rabbitmqHost='localhost'):
@ -105,8 +138,8 @@ class ProtocolManage(object):
return False return False
return False return False
modelType = varInfo['model_type'] modelType = varInfo['modelType']
info = varInfo['variable_data'] info = varInfo['variableData']
# print(info) # print(info)
@ -181,51 +214,46 @@ class ProtocolManage(object):
print(f"写入变量值失败: {str(e)}") print(f"写入变量值失败: {str(e)}")
return False return False
# 添加读取函数 def _backgroundReadAllVariables(self, interval=0.5):
def readVariableValue(self, variableName): while not self.readThreadStop.is_set():
""" allVarNames = list(self.getAllVariableNames())
根据变量名读取变量值根据变量类型进行不同处理留空 for varName in allVarNames:
value = self._readVariableValueOriginal(varName)
:param variableName: 变量名 with self.cacheLock:
:return: 读取的值或None失败时 self.variableValueCache[varName] = value
""" time.sleep(interval)
#
def getAllVariableNames(self):
# 直接从缓存获取所有变量名,降低与数据库模型的耦合
return list(self.varInfoCache.keys())
def _readVariableValueOriginal(self, variableName):
# 完全保留原有读取逻辑
varInfo = self.lookupVariable(variableName) varInfo = self.lookupVariable(variableName)
value = None
if not varInfo: if not varInfo:
if self.RpcServer: if self.RpcServer:
print(variableName, 1111111111111111111)
existsVar, clientNames = self.RpcServer.existsVar(variableName) existsVar, clientNames = self.RpcServer.existsVar(variableName)
if existsVar: if existsVar:
# print(clientNames, 1111111111111111) value = float(self.RpcServer.getVarValue(clientNames[0], variableName)['value'])
value =float(self.RpcServer.getVarValue(clientNames[0], variableName)['value']) # return value
return value
else: else:
return None return None
return None return None
modelType = varInfo['modelType']
modelType = varInfo['model_type'] info = varInfo['variableData']
info = varInfo['variable_data']
try: try:
# 拆分为独立的协议条件判断 # 拆分为独立的协议条件判断
if modelType == 'ModbusTcpMasterVar': if modelType == 'ModbusTcpMasterVar':
# 读取操作(留空)
pass pass
elif modelType == 'ModbusTcpSlaveVar': elif modelType == 'ModbusTcpSlaveVar':
pass pass
elif modelType == 'ModbusRtuMasterVar': elif modelType == 'ModbusRtuMasterVar':
pass pass
elif modelType == 'ModbusRtuSlaveVar': elif modelType == 'ModbusRtuSlaveVar':
pass pass
# HART协议变量处理
elif modelType == 'HartVar': elif modelType == 'HartVar':
pass pass
# 温度/RTD变量处理
elif modelType == 'TcRtdVar': elif modelType == 'TcRtdVar':
channel = int(info['channelNumber']) - 1 channel = int(info['channelNumber']) - 1
varType = info['varType'] varType = info['varType']
@ -236,44 +264,47 @@ class ProtocolManage(object):
value = self.tcpVarManager.simRTDData[channel] value = self.tcpVarManager.simRTDData[channel]
elif varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A']: elif varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A']:
value = self.tcpVarManager.simTCData[channel] value = self.tcpVarManager.simTCData[channel]
if self.RpcClient: # if self.RpcClient:
self.RpcClient.setVarContent(variableName, value, info['min'], info['max'], info['varType']) # self.RpcClient.setVarContent(variableName, value, info['min'], info['max'], info['varType'])
return value # return value
else: else:
if varType == 'PT100': if varType == 'PT100':
value = self.writeRTD[channel] value = self.writeRTD[channel]
elif varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A']: elif varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A']:
value = self.writeTC[channel] value = self.writeTC[channel]
if self.RpcClient: # if self.RpcClient:
self.RpcClient.setVarContent(variableName, value, info['min'], info['max'], info['varType']) # self.RpcClient.setVarContent(variableName, value, info['min'], info['max'], info['varType'])
return value # return value
# 模拟量变量处理
elif modelType == 'AnalogVar': elif modelType == 'AnalogVar':
channel = int(info['channelNumber']) - 1 channel = int(info['channelNumber']) - 1
varType = info['varType'] varType = info['varType']
# print(varType, channel)
varModel = info['varModel'] varModel = info['varModel']
model = self.getModelType(varModel) model = self.getModelType(varModel)
value = self.tcpVarManager.readValue(varType, channel, model=model) value = self.tcpVarManager.readValue(varType, channel, model=model)
if varType in ['AI','AO']: if varType in ['AI','AO']:
# print(value)
value = self.getRealAI(value, info['max'], info['min']) value = self.getRealAI(value, info['max'], info['min'])
# return value
elif modelType == 'HartSimulateVar':
pass
if value is not None:
if self.RpcClient: if self.RpcClient:
self.RpcClient.setVarContent(variableName, value, info['min'], info['max'], info['varType']) self.RpcClient.setVarContent(variableName, value, info['min'], info['max'], info['varType'])
self.historyDBManage.writeVarValue(variableName, value)
# print('sucess')
return value return value
# print(1) else:
return None
# HART模拟变量处理
elif modelType == 'HartSimulateVar':
pass
# print(1111111111111111)
return None # 暂时返回None
except Exception as e: except Exception as e:
print(f"读取变量值失败: {str(e)}") print(f"读取变量值失败: {str(e)}")
return str(e) return None
def readVariableValue(self, variableName):
# 优先从缓存读取,未命中则走原有逻辑
with self.cacheLock:
if variableName in self.variableValueCache:
return self.variableValueCache[variableName]
return self._readVariableValueOriginal(variableName)
# def sendTrigger(self, variableName, value, timeoutMS): # def sendTrigger(self, variableName, value, timeoutMS):
@ -284,6 +315,7 @@ class ProtocolManage(object):
def shutdown(self): def shutdown(self):
self.tcpVarManager.shutdown() self.tcpVarManager.shutdown()
def getRealAO(self, value,highValue, lowValue): def getRealAO(self, value,highValue, lowValue):

@ -45,6 +45,8 @@ class TCPVarManager:
""" """
try: try:
result = self.communicator.readAIDI() result = self.communicator.readAIDI()
self.AIDATA = result[0]
self.DIDATA = result[1]
# print(result) # print(result)
if result is None: if result is None:
logging.warning("Failed to read AI/DI data") logging.warning("Failed to read AI/DI data")
@ -61,12 +63,12 @@ class TCPVarManager:
:param callback: 数据回调函数 :param callback: 数据回调函数
""" """
self.stop_event = threading.Event() self.stop_event = threading.Event()
self.readThread = threading.Thread( # self.readThread = threading.Thread(
target=self._read_worker, # target=self._read_worker,
args=(interval, callback), # args=(interval, callback),
daemon=True # daemon=True
) # )
self.readThread.start() # self.readThread.start()
logging.info(f"Started periodic read every {interval*1000}ms") logging.info(f"Started periodic read every {interval*1000}ms")
def _read_worker(self, interval, callback): def _read_worker(self, interval, callback):
@ -225,6 +227,7 @@ class TCPVarManager:
def readValue(self, variableType, channel, model = localModel): def readValue(self, variableType, channel, model = localModel):
# channel = channel # channel = channel
self.readAiDi()
if variableType == "AI": if variableType == "AI":
# print(self.AIDATA) # print(self.AIDATA)
if model == SimModel: if model == SimModel:

@ -22,7 +22,7 @@ def _init():#初始化
_globalDict['AnalogThread'] = 0 _globalDict['AnalogThread'] = 0
_globalDict['FFSimulateThread'] = 0 _globalDict['FFSimulateThread'] = 0
_globalDict['HartSimulateThread'] = 0 _globalDict['HartSimulateThread'] = 0
_globalDict['HistoryWidget'] = 0 _globalDict['HistoryWidget'] = None
_globalDict['projectNumber'] = None _globalDict['projectNumber'] = None
_globalDict['username'] = None _globalDict['username'] = None
_globalDict['MainWindow'] = None _globalDict['MainWindow'] = None
@ -32,6 +32,7 @@ def _init():#初始化
_globalDict['blockManage'] = None _globalDict['blockManage'] = None
_globalDict['protocolManage'] = None _globalDict['protocolManage'] = None
_globalDict['historyDBManage'] = None
# 确保初始化 # 确保初始化
_init() _init()

@ -0,0 +1,2 @@
from .window_effect import WindowEffect
from .c_structures import *

@ -0,0 +1,135 @@
# coding:utf-8
from ctypes import POINTER, Structure, c_int
from ctypes.wintypes import DWORD, HWND, ULONG, POINT, RECT, UINT
from enum import Enum
class WINDOWCOMPOSITIONATTRIB(Enum):
WCA_UNDEFINED = 0
WCA_NCRENDERING_ENABLED = 1
WCA_NCRENDERING_POLICY = 2
WCA_TRANSITIONS_FORCEDISABLED = 3
WCA_ALLOW_NCPAINT = 4
WCA_CAPTION_BUTTON_BOUNDS = 5
WCA_NONCLIENT_RTL_LAYOUT = 6
WCA_FORCE_ICONIC_REPRESENTATION = 7
WCA_EXTENDED_FRAME_BOUNDS = 8
WCA_HAS_ICONIC_BITMAP = 9
WCA_THEME_ATTRIBUTES = 10
WCA_NCRENDERING_EXILED = 11
WCA_NCADORNMENTINFO = 12
WCA_EXCLUDED_FROM_LIVEPREVIEW = 13
WCA_VIDEO_OVERLAY_ACTIVE = 14
WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15
WCA_DISALLOW_PEEK = 16
WCA_CLOAK = 17
WCA_CLOAKED = 18
WCA_ACCENT_POLICY = 19
WCA_FREEZE_REPRESENTATION = 20
WCA_EVER_UNCLOAKED = 21
WCA_VISUAL_OWNER = 22
WCA_LAST = 23
class ACCENT_STATE(Enum):
""" Client area status enumeration class """
ACCENT_DISABLED = 0
ACCENT_ENABLE_GRADIENT = 1
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2
ACCENT_ENABLE_BLURBEHIND = 3 # Aero effect
ACCENT_ENABLE_ACRYLICBLURBEHIND = 4 # Acrylic effect
ACCENT_ENABLE_HOSTBACKDROP = 5 # Mica effect
ACCENT_INVALID_STATE = 6
class ACCENT_POLICY(Structure):
""" Specific attributes of client area """
_fields_ = [
("AccentState", DWORD),
("AccentFlags", DWORD),
("GradientColor", DWORD),
("AnimationId", DWORD),
]
class WINDOWCOMPOSITIONATTRIBDATA(Structure):
_fields_ = [
("Attribute", DWORD),
# Pointer() receives any ctypes type and returns a pointer type
("Data", POINTER(ACCENT_POLICY)),
("SizeOfData", ULONG),
]
class DWMNCRENDERINGPOLICY(Enum):
DWMNCRP_USEWINDOWSTYLE = 0
DWMNCRP_DISABLED = 1
DWMNCRP_ENABLED = 2
DWMNCRP_LAS = 3
class DWMWINDOWATTRIBUTE(Enum):
DWMWA_NCRENDERING_ENABLED = 1
DWMWA_NCRENDERING_POLICY = 2
DWMWA_TRANSITIONS_FORCEDISABLED = 3
DWMWA_ALLOW_NCPAINT = 4
DWMWA_CAPTION_BUTTON_BOUNDS = 5
DWMWA_NONCLIENT_RTL_LAYOUT = 6
DWMWA_FORCE_ICONIC_REPRESENTATION = 7
DWMWA_FLIP3D_POLICY = 8
DWMWA_EXTENDED_FRAME_BOUNDS = 9
DWMWA_HAS_ICONIC_BITMAP = 10
DWMWA_DISALLOW_PEEK = 11
DWMWA_EXCLUDED_FROM_PEEK = 12
DWMWA_CLOAK = 13
DWMWA_CLOAKED = 14
DWMWA_FREEZE_REPRESENTATION = 15
DWMWA_PASSIVE_UPDATE_MODE = 16
DWMWA_USE_HOSTBACKDROPBRUSH = 17
DWMWA_USE_IMMERSIVE_DARK_MODE = 18
DWMWA_WINDOW_CORNER_PREFERENCE = 19
DWMWA_BORDER_COLOR = 20
DWMWA_CAPTION_COLOR = 21
DWMWA_TEXT_COLOR = 22
DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 23
DWMWA_LAST = 24
class MARGINS(Structure):
_fields_ = [
("cxLeftWidth", c_int),
("cxRightWidth", c_int),
("cyTopHeight", c_int),
("cyBottomHeight", c_int),
]
class MINMAXINFO(Structure):
_fields_ = [
("ptReserved", POINT),
("ptMaxSize", POINT),
("ptMaxPosition", POINT),
("ptMinTrackSize", POINT),
("ptMaxTrackSize", POINT),
]
class PWINDOWPOS(Structure):
_fields_ = [
('hWnd', HWND),
('hwndInsertAfter', HWND),
('x', c_int),
('y', c_int),
('cx', c_int),
('cy', c_int),
('flags', UINT)
]
class NCCALCSIZE_PARAMS(Structure):
_fields_ = [
('rgrc', RECT*3),
('lppos', POINTER(PWINDOWPOS))
]

@ -0,0 +1,222 @@
# coding:utf-8
import sys
from ctypes import POINTER, c_bool, c_int, pointer, sizeof, WinDLL, byref
from ctypes.wintypes import DWORD, LONG, LPCVOID
from win32 import win32api, win32gui
from win32.lib import win32con
from .c_structures import (
ACCENT_POLICY,
ACCENT_STATE,
MARGINS,
DWMNCRENDERINGPOLICY,
DWMWINDOWATTRIBUTE,
WINDOWCOMPOSITIONATTRIB,
WINDOWCOMPOSITIONATTRIBDATA,
)
class WindowEffect:
""" A class that calls Windows API to realize window effect """
def __init__(self):
# Declare the function signature of the API
self.user32 = WinDLL("user32")
self.dwmapi = WinDLL("dwmapi")
self.SetWindowCompositionAttribute = self.user32.SetWindowCompositionAttribute
self.DwmExtendFrameIntoClientArea = self.dwmapi.DwmExtendFrameIntoClientArea
self.DwmSetWindowAttribute = self.dwmapi.DwmSetWindowAttribute
self.SetWindowCompositionAttribute.restype = c_bool
self.DwmExtendFrameIntoClientArea.restype = LONG
self.DwmSetWindowAttribute.restype = LONG
self.SetWindowCompositionAttribute.argtypes = [
c_int,
POINTER(WINDOWCOMPOSITIONATTRIBDATA),
]
self.DwmSetWindowAttribute.argtypes = [c_int, DWORD, LPCVOID, DWORD]
self.DwmExtendFrameIntoClientArea.argtypes = [c_int, POINTER(MARGINS)]
# Initialize structure
self.accentPolicy = ACCENT_POLICY()
self.winCompAttrData = WINDOWCOMPOSITIONATTRIBDATA()
self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value
self.winCompAttrData.SizeOfData = sizeof(self.accentPolicy)
self.winCompAttrData.Data = pointer(self.accentPolicy)
def setAcrylicEffect(self, hWnd, gradientColor: str = "F2F2F299", isEnableShadow: bool = True, animationId: int = 0):
""" Add the acrylic effect to the window
Parameters
----------
hWnd: int or `sip.voidptr`
Window handle
gradientColor: str
Hexadecimal acrylic mixed color, corresponding to four RGBA channels
isEnableShadow: bool
Enable window shadows
animationId: int
Turn on matte animation
"""
hWnd = int(hWnd)
# Acrylic mixed color
gradientColor = (
gradientColor[6:]
+ gradientColor[4:6]
+ gradientColor[2:4]
+ gradientColor[:2]
)
gradientColor = DWORD(int(gradientColor, base=16))
# matte animation
animationId = DWORD(animationId)
# window shadow
accentFlags = DWORD(0x20 | 0x40 | 0x80 |
0x100) if isEnableShadow else DWORD(0)
self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_ACRYLICBLURBEHIND.value
self.accentPolicy.GradientColor = gradientColor
self.accentPolicy.AccentFlags = accentFlags
self.accentPolicy.AnimationId = animationId
# enable acrylic effect
self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData))
def setMicaEffect(self, hWnd):
""" Add the mica effect to the window (Win11 only)
Parameters
----------
hWnd: int or `sip.voidptr`
Window handle
"""
if sys.getwindowsversion().build < 22000:
raise Exception("The mica effect is only available on Win11")
hWnd = int(hWnd)
margins = MARGINS(-1, -1, -1, -1)
self.DwmExtendFrameIntoClientArea(hWnd, byref(margins))
self.DwmSetWindowAttribute(hWnd, 1029, byref(c_int(1)), 4)
self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_HOSTBACKDROP.value
self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData))
def setAeroEffect(self, hWnd):
""" Add the aero effect to the window
Parameters
----------
hWnd: int or `sip.voidptr`
Window handle
"""
hWnd = int(hWnd)
self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_BLURBEHIND.value
self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData))
def removeBackgroundEffect(self, hWnd):
""" Remove background effect
Parameters
----------
hWnd: int or `sip.voidptr`
Window handle
"""
hWnd = int(hWnd)
self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_DISABLED.value
self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData))
@staticmethod
def moveWindow(hWnd):
""" Move the window
Parameters
----------
hWnd: int or `sip.voidptr`
Window handle
"""
hWnd = int(hWnd)
win32gui.ReleaseCapture()
win32api.SendMessage(
hWnd, win32con.WM_SYSCOMMAND, win32con.SC_MOVE + win32con.HTCAPTION, 0
)
def addShadowEffect(self, hWnd):
""" Add DWM shadow to window
Parameters
----------
hWnd: int or `sip.voidptr`
Window handle
"""
hWnd = int(hWnd)
margins = MARGINS(-1, -1, -1, -1)
self.DwmExtendFrameIntoClientArea(hWnd, byref(margins))
def addMenuShadowEffect(self, hWnd):
""" Add DWM shadow to menu
Parameters
----------
hWnd: int or `sip.voidptr`
Window handle
"""
hWnd = int(hWnd)
self.DwmSetWindowAttribute(
hWnd,
DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY.value,
byref(c_int(DWMNCRENDERINGPOLICY.DWMNCRP_ENABLED.value)),
4,
)
margins = MARGINS(-1, -1, -1, -1)
self.DwmExtendFrameIntoClientArea(hWnd, byref(margins))
def removeShadowEffect(self, hWnd):
""" Remove DWM shadow from the window
Parameters
----------
hWnd: int or `sip.voidptr`
Window handle
"""
hWnd = int(hWnd)
self.DwmSetWindowAttribute(
hWnd,
DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY.value,
byref(c_int(DWMNCRENDERINGPOLICY.DWMNCRP_DISABLED.value)),
4,
)
@staticmethod
def removeMenuShadowEffect(hWnd):
""" Remove shadow from pop-up menu
Parameters
----------
hWnd: int or `sip.voidptr`
Window handle
"""
hWnd = int(hWnd)
style = win32gui.GetClassLong(hWnd, win32con.GCL_STYLE)
style &= ~0x00020000 # CS_DROPSHADOW
win32api.SetClassLong(hWnd, win32con.GCL_STYLE, style)
@staticmethod
def addWindowAnimation(hWnd):
""" Enables the maximize and minimize animation of the window
Parameters
----------
hWnd : int or `sip.voidptr`
Window handle
"""
hWnd = int(hWnd)
style = win32gui.GetWindowLong(hWnd, win32con.GWL_STYLE)
win32gui.SetWindowLong(
hWnd,
win32con.GWL_STYLE,
style
| win32con.WS_MAXIMIZEBOX
| win32con.WS_CAPTION
| win32con.CS_DBLCLKS
| win32con.WS_THICKFRAME,
)
Loading…
Cancel
Save