diff --git a/aboutwindow.py b/aboutwindow.py new file mode 100644 index 0000000..47c7505 --- /dev/null +++ b/aboutwindow.py @@ -0,0 +1,25 @@ +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtGui import * + + +class AboutWindow(QWidget): + def __init__(self, parent=None): + super(AboutWindow, self).__init__(parent) + + self.setWindowTitle('关于') + self.setWindowIcon(QIcon('gml.png')) + self.setStyleSheet("QTextBrowser{font-size: 12pt;}") + + self.textbrowser = QTextBrowser() + self.textbrowser.setHtml( + '
NodeHost

' + + '
' + + '本软件为分布式环境监测系统的上位机软件
' + + '由 GML-Group 制作' + ) + + layout = QHBoxLayout() + layout.addWidget(self.textbrowser) + self.setLayout(layout) + self.setWindowModality(Qt.ApplicationModal) diff --git a/data.json b/data.json index 858ebf6..9c5601a 100644 --- a/data.json +++ b/data.json @@ -1,23 +1 @@ -{ - "time": "2021-10-24 00:43:56", - "node2": { - "humi": 44.9, - "temp": 25.2, - "light": 360.8 - }, - "node3": { - "humi": 43.2, - "temp": 26.8, - "light": 25.8 - }, - "node4": { - "humi": 43.4, - "temp": 26.6, - "light": 25.8 - }, - "node5": { - "humi": 44.5, - "temp": 25.6, - "light": 30.0 - } -} \ No newline at end of file +{"time": "2021-10-30 16:25:15", "node2": {"humi": 40.9, "temp": 25.6, "light": 389.2}, "node3": {"humi": 44.5, "temp": 26.7, "light": 14.2}, "node4": {"humi": 46.3, "temp": 26.0, "light": 12.5}, "node5": {"humi": 46.5, "temp": 25.3, "light": 13.3}} \ No newline at end of file diff --git a/drawwindow.py b/drawwindow.py new file mode 100644 index 0000000..98868f3 --- /dev/null +++ b/drawwindow.py @@ -0,0 +1,143 @@ +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtChart import QChart, QChartView, QLineSeries, QDateTimeAxis, QValueAxis + + +class DrawWindow(QWidget): + def __init__(self): + super().__init__() + + self.setGeometry(200, 200, 600, 400) + self.setWindowIcon(QIcon("gml.png")) + + self.humi_series = QLineSeries() + self.temp_series = QLineSeries() + self.light_series = QLineSeries() + + ctime = QDateTime.currentDateTime() + + humi_chart = QChart() + humi_chart.setTheme(QChart.ChartThemeLight) + humi_chart.addSeries(self.humi_series) + humi_chart.setTitle("时间-湿度变化曲线") + + temp_chart = QChart() + temp_chart.setTheme(QChart.ChartThemeLight) + temp_chart.addSeries(self.temp_series) + temp_chart.setTitle("时间-温度变化曲线") + + light_chart = QChart() + light_chart.setTheme(QChart.ChartThemeLight) + light_chart.addSeries(self.light_series) + light_chart.setTitle("时间-光照度变化曲线") + + self.humi_dtaxisX = QDateTimeAxis() + self.temp_dtaxisX = QDateTimeAxis() + self.light_dtaxisX = QDateTimeAxis() + self.humi_vlaxisY = QValueAxis() + self.temp_vlaxisY = QValueAxis() + self.light_vlaxisY = QValueAxis() + self.humi_dtaxisX.setMin(ctime.addSecs(0)) + self.humi_dtaxisX.setMax(ctime.addSecs(-60)) + self.temp_dtaxisX.setMin(ctime.addSecs(0)) + self.temp_dtaxisX.setMax(ctime.addSecs(-60)) + self.light_dtaxisX.setMin(ctime.addSecs(0)) + self.light_dtaxisX.setMax(ctime.addSecs(-60)) + self.humi_vlaxisY.setMin(40) + self.humi_vlaxisY.setMax(50) + self.temp_vlaxisY.setMin(20) + self.temp_vlaxisY.setMax(30) + self.light_vlaxisY.setMin(0) + self.light_vlaxisY.setMax(100) + + self.humi_dtaxisX.setFormat("hh:mm:ss") + self.humi_dtaxisX.setTickCount(10) + self.temp_dtaxisX.setFormat("hh:mm:ss") + self.temp_dtaxisX.setTickCount(10) + self.light_dtaxisX.setFormat("hh:mm:ss") + self.light_dtaxisX.setTickCount(10) + self.humi_vlaxisY.setTickCount(10) + self.temp_vlaxisY.setTickCount(10) + self.light_vlaxisY.setTickCount(1) + + self.humi_dtaxisX.setTitleText("时间") + self.temp_dtaxisX.setTitleText("时间") + self.light_dtaxisX.setTitleText("时间") + self.humi_vlaxisY.setTitleText("湿度") + self.temp_vlaxisY.setTitleText("温度") + self.light_vlaxisY.setTitleText("光照度") + + self.humi_vlaxisY.setGridLineVisible(True) + self.humi_vlaxisY.setGridLineColor(Qt.gray) + self.temp_vlaxisY.setGridLineVisible(True) + self.temp_vlaxisY.setGridLineColor(Qt.gray) + self.light_vlaxisY.setGridLineVisible(True) + self.light_vlaxisY.setGridLineColor(Qt.gray) + self.humi_dtaxisX.setGridLineVisible(True) + self.humi_dtaxisX.setGridLineColor(Qt.gray) + self.temp_dtaxisX.setGridLineVisible(True) + self.temp_dtaxisX.setGridLineColor(Qt.gray) + self.light_dtaxisX.setGridLineVisible(True) + self.light_dtaxisX.setGridLineColor(Qt.gray) + + humi_chart.addAxis(self.humi_dtaxisX, Qt.AlignBottom) + humi_chart.addAxis(self.humi_vlaxisY, Qt.AlignLeft) + temp_chart.addAxis(self.temp_dtaxisX, Qt.AlignBottom) + temp_chart.addAxis(self.temp_vlaxisY, Qt.AlignLeft) + light_chart.addAxis(self.light_dtaxisX, Qt.AlignBottom) + light_chart.addAxis(self.light_vlaxisY, Qt.AlignLeft) + + self.humi_series.attachAxis(self.humi_dtaxisX) + self.humi_series.attachAxis(self.humi_vlaxisY) + self.temp_series.attachAxis(self.temp_dtaxisX) + self.temp_series.attachAxis(self.temp_vlaxisY) + self.light_series.attachAxis(self.light_dtaxisX) + self.light_series.attachAxis(self.light_vlaxisY) + + humi_chartview = QChartView(humi_chart) + temp_chartview = QChartView(temp_chart) + light_chartview = QChartView(light_chart) + + layout = QGridLayout() + layout.addWidget(humi_chartview, 1, 1) + layout.addWidget(temp_chartview, 1, 2) + layout.addWidget(light_chartview, 2, 1) + self.setLayout(layout) + + self.node_seq = 2 + + def start(self, node_seq: int): + self.setWindowTitle(f"节点{node_seq}数据监测图") + self.node_seq = node_seq + self.show() + + def addPoint(self, node_seq: int, node_data: dict): + + if self.node_seq == node_seq: + ctime = QDateTime.currentDateTime() + self.humi_dtaxisX.setMin(ctime.addSecs(-60)) + self.humi_dtaxisX.setMax(ctime.addSecs(0)) + self.temp_dtaxisX.setMin(ctime.addSecs(-60)) + self.temp_dtaxisX.setMax(ctime.addSecs(0)) + self.light_dtaxisX.setMin(ctime.addSecs(-60)) + self.light_dtaxisX.setMax(ctime.addSecs(0)) + + if self.humi_vlaxisY.max() - 2 < node_data['humi'] or self.humi_vlaxisY.min() + 2 > node_data['humi']: + self.humi_vlaxisY.setMin(node_data['humi'] * 0.8) + self.humi_vlaxisY.setMax(node_data['humi'] * 1.2) + + if self.temp_vlaxisY.max() - 2 < node_data['temp'] or self.temp_vlaxisY.min() + 2 > node_data['temp']: + self.temp_vlaxisY.setMin(node_data['temp'] * 0.8) + self.temp_vlaxisY.setMax(node_data['temp'] * 1.2) + + if self.light_vlaxisY.max() - 20 < node_data['light'] or self.light_vlaxisY.min() + 20 > node_data['light']: + self.light_vlaxisY.setMin(node_data['light'] * 0.8) + self.light_vlaxisY.setMax(node_data['light'] * 1.2) + + self.humi_series.append( + ctime.toMSecsSinceEpoch(), node_data['humi']) + self.temp_series.append( + ctime.toMSecsSinceEpoch(), node_data['temp']) + self.light_series.append( + ctime.toMSecsSinceEpoch(), node_data['light']) diff --git a/gml.png b/gml.png new file mode 100644 index 0000000..433a34c Binary files /dev/null and b/gml.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..600bfc7 --- /dev/null +++ b/index.html @@ -0,0 +1,71 @@ + + + + + + 分布式环境监测 + + + + + +
+

分布式环境监测数据页面

+

数据由网关向节点收集,上传至远程服务器

+ +
+ + + \ No newline at end of file diff --git a/main.py b/main.py index 73e0bf1..7d34ad7 100644 --- a/main.py +++ b/main.py @@ -1,165 +1,16 @@ import sys -import time -import json +import ctypes -import serial -import serial.tools.list_ports from PyQt5.QtWidgets import * -from PyQt5.QtCore import * -from PyQt5.QtGui import * - -class MainWidget(QWidget): - def __init__(self, parent=None): - super(MainWidget, self).__init__(parent) - - self.setWindowTitle('Demo') - - self.label_node2 = QLabel( - '节点:2 湿度: % 温度: °C 光照度: lx ') - self.label_node3 = QLabel( - '节点:3 湿度: % 温度: °C 光照度: lx ', self) - self.label_node4 = QLabel( - '节点:4 湿度: % 温度: °C 光照度: lx ', self) - self.label_node5 = QLabel( - '节点:5 湿度: % 温度: °C 光照度: lx ', self) - - self.combobox = QComboBox() - - self.port_list = serial.tools.list_ports.comports() - for port in self.port_list: - self.combobox.addItem(port.description) - - self.btnStart = QPushButton('开始') - self.btnExit = QPushButton('退出') - self.btnScanPort = QPushButton('刷新串口') - - # 实例化多线程对象 - self.thread = Worker(self.port_list[0].name) - - # 把控件放置在栅格布局中 - layout = QGridLayout(self) - layout.addWidget(self.label_node2, 1, 1, 1, 4) - layout.addWidget(self.label_node3, 2, 1, 1, 4) - layout.addWidget(self.label_node4, 3, 1, 1, 4) - layout.addWidget(self.label_node5, 4, 1, 1, 4) - layout.addWidget(self.btnScanPort, 6, 1, 1, 1) - layout.addWidget(self.combobox, 6, 2, 1, 1) - layout.addWidget(self.btnStart, 6, 3, 1, 1) - layout.addWidget(self.btnExit, 6, 4, 1, 1) - - # 信号与槽函数的连接 - self.thread.sign_node.connect(self.slotUpdateNode) - self.combobox.currentIndexChanged.connect(self.slotSelectPort) - self.btnScanPort.clicked.connect(self.slotScanPort) - self.btnStart.clicked.connect(self.slotStart) - self.btnExit.clicked.connect(self.slotStop) - - def slotScanPort(self): - self.port_list = serial.tools.list_ports.comports() - self.combobox.clear() - for port in self.port_list: - self.combobox.addItem(port.description) - self.thread.port_name = self.port_list[0].name - - def slotSelectPort(self, index): - self.thread.port_name = self.port_list[index].name - - def slotUpdateNode(self, node_seq, node_str): - # 更新 - if (node_seq == 2): - self.label_node2.setText(node_str) - if (node_seq == 3): - self.label_node3.setText(node_str) - if (node_seq == 4): - self.label_node4.setText(node_str) - if (node_seq == 5): - self.label_node5.setText(node_str) - - def slotStart(self): - self.combobox.setEnabled(False) - self.btnScanPort.setEnabled(False) - self.btnStart.setEnabled(False) - self.btnExit.setEnabled(True) - self.thread.start() - - def slotStop(self): - self.btnExit.setEnabled(False) - del self.thread - sys.exit() - - -class Worker(QThread): - sign_node = pyqtSignal(int, str) - port_name: str - - def __init__(self, port_name, parent=None): - super(Worker, self).__init__(parent) - # 设置工作状态与初始num数值 - self.port_name = port_name - self.working = True - - def __del__(self): - # 线程状态改变与线程终止 - self.working = False - self.wait() - - def run(self): - last_str = '' - - self.com = serial.Serial(self.port_name, 115200) - - while self.working == True: - - serial_str = self.com.readline().decode('utf-8') - - if (serial_str.find('ata') != -1): - continue - - str_list = serial_str.split('&') # 分割字符串 - - if len(str_list) != 5: # 数据缺失 - print('数据缺失!') - continue - - if (serial_str != last_str): # 检测数据是否重复 - last_str = serial_str - - try: # 转换数据 - seq: int = int(str_list[0]) - humi: float = round(float(str_list[1]), 1) - temp: float = round(float(str_list[2]), 1) - light: float = round(float(str_list[3]), 1) - - except Exception: - print('数据错误!') - continue - - print(f"节点:{seq} 湿度:{humi}% 温度:{temp}°C 光照度:{light}lx") - - # 写入 json 文件 - data = json.loads( - open('./data.json', 'r', encoding='utf-8').read()) - - data['node' + str(seq)]['humi'] = humi - data['node' + str(seq)]['temp'] = temp - data['node' + str(seq)]['light'] = light - data['time'] = time.strftime( - '%Y-%m-%d %H:%M:%S', time.localtime(time.time())) - - with open('./data.json', 'w') as fp: - fp.write(json.dumps(data)) - fp.close() - - # 获取文本 - node_str = f"节点:{seq} 湿度:{humi}% 温度:{temp}°C 光照度:{light}lx" - # 发射信号 - self.sign_node.emit(seq, node_str) +from mainwindow import MainWindow if __name__ == '__main__': + # 防止系统显示为 Python 图标 + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("NoidHost") + app = QApplication(sys.argv) - app.setStyleSheet("QLabel{font-size: 18pt;}") - demo = MainWidget() - demo.show() + main_widget = MainWindow() + main_widget.show() sys.exit(app.exec_()) diff --git a/mainwindow.py b/mainwindow.py new file mode 100644 index 0000000..8adb1a6 --- /dev/null +++ b/mainwindow.py @@ -0,0 +1,161 @@ +import sys + +import serial +import serial.tools.list_ports +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtGui import * + +from drawwindow import DrawWindow +from aboutwindow import AboutWindow +from serialhandler import SerialHandler + + +class MainWindow(QMainWindow): + def __init__(self, parent=None): + super(MainWindow, self).__init__(parent) + + self.setWindowTitle('NoidHost') + self.setWindowIcon(QIcon('gml.png')) + + self.statusbar = self.statusBar() + self.statusbar.showMessage('等待连接') + + menubar = self.menuBar() + + # 菜单栏-文件 + exit_action = QAction('&退出', self) + exit_action.setShortcut('Ctrl+Q') + exit_action.setStatusTip('退出应用') + exit_action.triggered.connect(lambda: sys.exit()) + + menubar_file = menubar.addMenu('文件') + menubar_file.addAction(exit_action) + + # 菜单栏-绘图 + draw_node2_action = QAction('&节点2', self) + draw_node3_action = QAction('&节点3', self) + draw_node4_action = QAction('&节点4', self) + draw_node5_action = QAction('&节点5', self) + + self.node2_drawwindow = DrawWindow() + self.node3_drawwindow = DrawWindow() + self.node4_drawwindow = DrawWindow() + self.node5_drawwindow = DrawWindow() + + menubar_draw = menubar.addMenu('绘图') + menubar_draw.addAction(draw_node2_action) + menubar_draw.addAction(draw_node3_action) + menubar_draw.addAction(draw_node4_action) + menubar_draw.addAction(draw_node5_action) + + draw_node2_action.triggered.connect( + lambda: self.drawStart(2)) + draw_node3_action.triggered.connect( + lambda: self.drawStart(3)) + draw_node4_action.triggered.connect( + lambda: self.drawStart(4)) + draw_node5_action.triggered.connect( + lambda: self.drawStart(5)) + + # 菜单栏-关于 + self.aboutwindow = AboutWindow() + about_action = QAction('&关于', self) + about_action.setShortcut('Ctrl+A') + about_action.setStatusTip('关于') + about_action.triggered.connect(lambda: self.aboutwindow.show()) + + menubar_about = menubar.addMenu('关于') + menubar_about.addAction(about_action) + + self.label_node2 = QLabel( + '节点:2 湿度: % 温度: °C 光照度: lx ') + self.label_node3 = QLabel( + '节点:3 湿度: % 温度: °C 光照度: lx ', self) + self.label_node4 = QLabel( + '节点:4 湿度: % 温度: °C 光照度: lx ', self) + self.label_node5 = QLabel( + '节点:5 湿度: % 温度: °C 光照度: lx ', self) + + self.combobox = QComboBox() + + self.port_list = serial.tools.list_ports.comports() + for port in self.port_list: + self.combobox.addItem(port.description) + + self.btnStart = QPushButton('开始') + self.btnExit = QPushButton('退出') + self.btnScanPort = QPushButton('刷新串口') + + # 实例化多线程对象 + self.handler = SerialHandler(self.port_list[0].name) + + # 把控件放置在栅格布局中 + layout = QGridLayout() + layout.addWidget(self.label_node2, 1, 1, 1, 4) + layout.addWidget(self.label_node3, 2, 1, 1, 4) + layout.addWidget(self.label_node4, 3, 1, 1, 4) + layout.addWidget(self.label_node5, 4, 1, 1, 4) + layout.addWidget(self.btnScanPort, 5, 1, 1, 1) + layout.addWidget(self.combobox, 5, 2, 1, 1) + layout.addWidget(self.btnStart, 5, 3, 1, 1) + layout.addWidget(self.btnExit, 5, 4, 1, 1) + + self.widget = QWidget() + self.widget.setLayout(layout) + self.widget.setStyleSheet("QLabel{font-size: 18pt;}") + self.setCentralWidget(self.widget) + + # 信号与槽函数的连接 + self.handler.sign_node.connect(self.slotUpdateNode) + self.combobox.currentIndexChanged.connect(self.slotSelectPort) + self.btnScanPort.clicked.connect(self.slotScanPort) + self.btnStart.clicked.connect(self.slotStart) + self.btnExit.clicked.connect(lambda: sys.exit()) + + def slotScanPort(self): + self.port_list = serial.tools.list_ports.comports() + self.combobox.clear() + for port in self.port_list: + self.combobox.addItem(port.description) + self.handler.port_name = self.port_list[0].name + + def slotSelectPort(self, index): + self.handler.port_name = self.port_list[index].name + + def slotUpdateNode(self, node_seq: int, node_data: dict): + # 更新 + if (node_seq == 2): + self.label_node2.setText( + f"节点:2 湿度:{node_data['humi']}% 温度:{node_data['temp']}°C 光照度:{node_data['light']}lx") + if (node_seq == 3): + self.label_node3.setText( + f"节点:3 湿度:{node_data['humi']}% 温度:{node_data['temp']}°C 光照度:{node_data['light']}lx") + if (node_seq == 4): + self.label_node4.setText( + f"节点:4 湿度:{node_data['humi']}% 温度:{node_data['temp']}°C 光照度:{node_data['light']}lx") + if (node_seq == 5): + self.label_node5.setText( + f"节点:5 湿度:{node_data['humi']}% 温度:{node_data['temp']}°C 光照度:{node_data['light']}lx") + + def slotStart(self): + self.combobox.setEnabled(False) + self.btnScanPort.setEnabled(False) + self.btnStart.setEnabled(False) + self.btnExit.setEnabled(True) + self.statusbar.showMessage('已连接') + self.handler.start() + + def drawStart(self, node_seq: int): + if node_seq == 2: + self.handler.sign_node.connect(self.node2_drawwindow.addPoint) + self.node2_drawwindow.start(node_seq) + elif node_seq == 3: + self.handler.sign_node.connect(self.node3_drawwindow.addPoint) + self.node3_drawwindow.start(node_seq) + elif node_seq == 4: + self.handler.sign_node.connect(self.node4_drawwindow.addPoint) + self.node4_drawwindow.start(node_seq) + elif node_seq == 5: + self.handler.sign_node.connect(self.node5_drawwindow.addPoint) + self.node5_drawwindow.start(node_seq) diff --git a/serialhandler.py b/serialhandler.py new file mode 100644 index 0000000..d761717 --- /dev/null +++ b/serialhandler.py @@ -0,0 +1,80 @@ +import time +import json + +import serial +import serial.tools.list_ports +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * +from PyQt5.QtGui import * + + +class SerialHandler(QThread): + sign_node = pyqtSignal(int, dict) + port_name: str + + def __init__(self, port_name, parent=None): + super(SerialHandler, self).__init__(parent) + self.port_name = port_name + self.working = True + + def __del__(self): + # 线程状态改变与线程终止 + self.working = False + self.wait() + + def run(self): + last_str = '' # 记录上一次收到的字符串 + self.com = serial.Serial(self.port_name, 115200) + + while self.working == True: + + serial_str = self.com.readline().decode('utf-8') + str_list = serial_str.split('&') # 分割字符串 + + if len(str_list) != 5: # 数据缺失 + print('数据缺失!') + continue + + if (serial_str != last_str): # 检测数据是否重复 + last_str = serial_str + + try: # 转换数据 + seq: int = int(str_list[0]) + humi: float = round(float(str_list[1]), 1) + temp: float = round(float(str_list[2]), 1) + light: float = round(float(str_list[3]), 1) + + except Exception: + print('数据错误!') + continue + + # 检测数据合理性 + if humi > 100 or temp > 50 or light > 100000: + print('数据不合理!') + continue + + print(f"节点:{seq} 湿度:{humi}% 温度:{temp}°C 光照度:{light}lx") + + # 写入 json 文件 + data = json.loads( + open('./data.json', 'r', encoding='utf-8').read()) + + data['node' + str(seq)]['humi'] = humi + data['node' + str(seq)]['temp'] = temp + data['node' + str(seq)]['light'] = light + data['time'] = time.strftime( + '%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + + with open('./data.json', 'w') as fp: + fp.write(json.dumps(data)) + fp.close() + + # 获取文本 + node_data = { + "seq": seq, + "humi": humi, + "temp": temp, + "light": light + } + # 发射信号 + self.sign_node.emit(seq, node_data) diff --git a/server.bat b/server.bat new file mode 100644 index 0000000..5080519 --- /dev/null +++ b/server.bat @@ -0,0 +1 @@ +python -m http.server 8520 \ No newline at end of file