原文链接:https://dsx2016.com/?p=1493
公众号:大师兄2016
前言
上期文章描述使用vue+echarts设置温度仪表盘并自定义区段颜色
本期文章接着用python flask设置socket服务和前端通信实时获取温度数据
主要内容为
-
后端设置flask服务并启用socket
-
前端使用socket.io(websocket)实时获取温度数据
-
arduino和python usb端口通信的关闭和独占访问模式(仅POSIX)
设置flask
安装依赖flask
pip install flask
安装依赖flask_cors
pip install flask_cors
安装依赖flask_socketio
pip install flask_socketio
以下代码使用python flask启动服务并支持socket
# 引入flask
from flask import Flask
# 引入跨域设置
from flask_cors import CORS
# 引入socketio
from flask_socketio import SocketIO
from flask_socketio import send, emit
# 实例化app
app = Flask(__name__)
# 设置跨域
CORS(app)
# 设置socket
socketio = SocketIO(app, cors_allowed_origins='*')
# 运行主程序
if __name__ == '__main__':
# app.run()
socketio.run(app)
服务启动后,默认地址为
前端socket
安装依赖socket.io
npm install socket.io
vue-cli项目中引用socket.io client
import io from "socket.io-client";
建立socket连接
格式const socket = io(server url);
const socket = io("http://127.0.0.1:5000");
设置事件
服务端定义事件接收
-
使用装饰器@socketio.on
-
事件名自定义,此处为hello
-
收到socket信息后打印数据到控制台台
-
emit为发送给前端定义的事件响应
@socketio.on('hello')
def hello(str):
print("接收到的数据为->", str)
emit('reply', "reply server")
前端定义事件接收
-
使用socket.on定义事件名
-
自定义事件名为replay
-
收到socket信息后打印数据到控制台台
socket.on("reply", data => {
console.log("reply", data);
});
事件通信测试
前端发起hello事情,并传递参数字符串world
socket.emit("hello", "world");
后端控制台日志如图
前端收到回应如图
实时数据
原先设计为
-
储存到sqlite温湿度数据和时间戳
-
前端通过http轮询或者socket获取最后一次数据库数据
-
比对当前时间戳,误差3秒左右为可用数据
-
时间跨度大,则图表置灰,数据不可用(因为不是当前的温度)
-
设置为动态更新折线图
现在改为
-
通过socket每秒一次通信事情开启usb串口,获取数据
-
服务器获取到温湿度数据后通过socket事情传给前端,然后关闭串口
-
前端拿到数据后绘制/更新仪表盘数据(不用折线图),然后一秒后再次请求数据(频率前端自行控制)
-
循环往复
实际代码
前端socket.vue,具体细节参考注释
<template>
<section class="socket">
<!-- 用于渲染仪表盘的DOM -->
<div id="main" class="main"></div>
</section>
</template>
<script>
import io from "socket.io-client";
export default {
name: `socket`,
data() {
return {
myChart: null,
option: {
tooltip: {
formatter: "{a} <br/>{b} : {c}℃"
},
toolbox: {
feature: {
restore: {},
saveAsImage: {}
}
},
series: [
{
name: "当前温度",
type: "gauge",
min: 0,
max: 40,
detail: { formatter: "{value}℃" },
data: [{ value: 26, name: "温度" }],
axisLine: {
lineStyle: {
color: [
[0.5, "#4dabf7"],
[0.65, "#69db7c"],
[0.8, "#ffa94d"],
[1, "#ff6b6b"]
]
}
}
}
]
}
};
},
created() {
// 页面初始化时建立socket连接
this.conSocket();
},
mounted() {
// DOM更新后渲染仪表盘图表
this.$nextTick(() => {
this.initEcharts();
});
},
methods: {
conSocket() {
let that = this;
console.log(`开始建立socket连接`);
const socket = io("http://127.0.0.1:5000");
// 第一次请求获取温度数据
socket.emit("getData", "world");
// 接收温度数据socket响应
socket.on("reply", data => {
console.log("reply", data);
// 获取温度并设置到echarts参数
that.option.series[0].data[0].value = data.temperature || 0;
// 更新echarts
let echarts = require("echarts");
let myChart = echarts.init(document.getElementById("main"));
myChart.setOption(that.option, true);
// 更新后再次获取服务端最新温度数据
socket.emit("getData", "world");
});
},
// 初始化仪表盘
initEcharts() {
let that = this;
// 基于准备好的dom,初始化echarts实例
let echarts = require("echarts");
let myChart = echarts.init(document.getElementById("main"));
// 绘制图表
myChart.setOption(that.option);
}
}
};
</script>
<style lang="less" scoped>
.socket {
.main {
width: 500px;
height: 500px;
}
}
</style>
服务端代码app.py,具体细节参考注释
# 引入flask
from flask import Flask
# 引入跨域设置
from flask_cors import CORS
# 引入socketio
from flask_socketio import SocketIO
from flask_socketio import send, emit
# 引入串口库(注意是serial,不是pyserial)
import serial
# 引入json库
import json
# 引入时间
import time
# 实例化app
app = Flask(__name__)
# 设置跨域
CORS(app)
# 设置socket
socketio = SocketIO(app, cors_allowed_origins='*')
@socketio.on('getData')
# 获取温湿度数据
def getUsbData(strData):
print("接收到的数据为>>>", strData)
# 设置端口变量和值
serialPosrt = "COM3"
# 设置波特率变量和值
baudRate = 9600
# 设置超时时间,单位为s
timeout = 0.5
# 获取端口数据
ser = serial.Serial(serialPosrt, baudRate, timeout=timeout, exclusive=True)
# 循环获取数据
while ser.is_open:
# 读取接收到的数据的第一行
strData = ser.readline()
# 把拿到的数据转为字符串(串口接收到的数据为bytes字符串类型,需要转码字符串类型)
strJson = str(strData, encoding='utf-8')
# 如果有数据,则进行json转换
if strJson:
# 只有当检测到字符串中含有温湿度字符名时才进行json转码,其他的字符串内容不作操作
if "temperature" in strJson:
# print("当前接受到的数据位->", strJson)
# 字符串转为json(每个字符串变量名必须为双引号包括,而不是单引号)
jsonData = json.loads(strJson)
# print("转码成功,当前类型为->", type(jsonData))
# 温度
temperature = jsonData["temperature"]
# 湿度
humidity = jsonData["humidity"]
# 把数据传给前端
emit('reply', {
"temperature": temperature,
"humidity": humidity,
})
# 关闭串口
ser.close()
else:
print("当前接收到的数据为空")
# 运行主程序
if __name__ == '__main__':
# app.run()
socketio.run(app)
tips
serial.Serial新加参数exclusive为True
Exclusive(bool)–设置互斥访问模式(仅POSIX)。如果端口已经以独占访问模式打开,则不能以独占访问模式打开端口。
这样在重复socket连接flask打开串口时,就不会因为独占模式报错无权限开启端口
如果需要优化,也可以加上try except 进行异常处理
本文功能优先,暂不细化
ser = serial.Serial(serialPosrt, baudRate, timeout=timeout, exclusive=True)
is_open:获取串行端口的状态,无论它是否打开
while ser.is_open: 循环获取数据(条件)
当每次通信成功获取和传递数据后,及时关闭串口,条件就不成立,就不会一直读取arduino数据,只有当需要的时候才获取
ser.close():关闭串口
serial.Serial方法会默认init和open端口,所以要使用close开关闭端口
文档地址
pyserial文档地址
https://pyserial.readthedocs.io/en/latest/pyserial_api.html
flask-socketio文档地址
https://flask-socketio.readthedocs.io/en/latest/
socket.io文档地址
总结
本文描述了
-
flask服务端socket代码
-
前端socket和echarts图表代码
-
技术构思流程和技术细节tips
其他前置条件
-
usb连接arduino获取温湿度(按之前的文章做好准备工作)
-
代码中的端口,设备名,其他细节根据自己环境修改
至此,一个实用arduino获取温湿度计,并通过usb串口实现在web显示温度实时仪表盘图表的功能完成
其他湿度,光感,声音等传感器数据都可以用本demo提供的思路和方法来完成实时图表和数据通信以及储存功能
下期计划
-
web面板使用控制arduino的led灯的开关(反向控制下位机)
-
蓝牙通信和wifi通信等通信协议尝试和深入(脱离有线,开始无线互联)
END.