完成曲线绘制

This commit is contained in:
lxbpxylps@126.com 2021-10-30 16:37:19 +08:00
parent bc1fccc16b
commit 06a541bbbb
9 changed files with 489 additions and 179 deletions

25
aboutwindow.py Normal file
View File

@ -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(
'<center><b>NodeHost</b></center><br>' +
'<br>' +
'本软件为分布式环境监测系统的上位机软件<br>' +
'由 GML-Group 制作'
)
layout = QHBoxLayout()
layout.addWidget(self.textbrowser)
self.setLayout(layout)
self.setWindowModality(Qt.ApplicationModal)

View File

@ -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
}
}
{"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}}

143
drawwindow.py Normal file
View File

@ -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'])

BIN
gml.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

71
index.html Normal file
View File

@ -0,0 +1,71 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>分布式环境监测</title>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js">
</script>
<style>
.container {
width: 60%;
margin: 10% auto 0;
background-color: #f0f0f0;
padding: 2% 5%;
border-radius: 10px
}
ul {
padding-left: 20px;
}
ul li {
line-height: 2.3
}
a {
color: #20a53a
}
</style>
</head>
<body>
<div class="container">
<h1>分布式环境监测数据页面</h1>
<h3>数据由网关向节点收集,上传至远程服务器</h3>
<ul>
<li>
<time></time>
</li>
<li>
<node2></node2>
</li>
<li>
<node3></node3>
</li>
<li>
<node4></node4>
</li>
<li>
<node5></node5>
</li>
<script>
$(document).ready(function () {
$.ajaxSetup({ cache: false });
setInterval(function () {
$.getJSON("/data.json", function (data) {
$("time").text("数据更新时间: " + data.time);
$("node2").text("节点 2: 湿度: " + data.node2.humi + "% 温度: " + data.node2.temp + "°C 光照度: " + data.node2.light + "lx");
$("node3").text("节点 3: 湿度: " + data.node3.humi + "% 温度: " + data.node3.temp + "°C 光照度: " + data.node3.light + "lx");
$("node4").text("节点 4: 湿度: " + data.node4.humi + "% 温度: " + data.node4.temp + "°C 光照度: " + data.node4.light + "lx");
$("node5").text("节点 5: 湿度: " + data.node5.humi + "% 温度: " + data.node5.temp + "°C 光照度: " + data.node5.light + "lx");
});
}, 100);
});
</script>
</ul>
</div>
</body>
</html>

163
main.py
View File

@ -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_())

161
mainwindow.py Normal file
View File

@ -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)

80
serialhandler.py Normal file
View File

@ -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)

1
server.bat Normal file
View File

@ -0,0 +1 @@
python -m http.server 8520