回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页
best-icon

【Mind+ Python】基于Pyecharts + Flask + Pinpong 智能家居数字大屏系统 简单

头像 Hockel 2021.09.08 1732 6
project-image

前言

之前在做项目的时候,遇到了一个问题:就是我想将单片机板子的传感器数据,实时上传到物联网平台,然后进行数字大屏显示。一开始使用的是onenet平台→多协议接入→应用管理来进行设置,奈何里面的一个组件少的可怜!

project-image

又看到它家的‘数据可视化View功能!被他酷炫的数字大屏模板深深吸引了。

project-image

心想这不就我要找的模板吗?于是信誓旦旦的准备大概一场的时候:在我们选择一个2D项目模板,准备创建项目的时候,它跳出来一个对话框“提示氪金”(ps:新用户有7天的专业版试用资格,我当时要做项目的时候,已经过了!)

project-image

眼看着比赛的时间节点就快到了,于是咬咬牙,就买了两个月!

project-image

买了一个企业版的会员,买完回来发现:“里面的有些功能不能用!需要升级专业版会员!(腾讯svip直呼:“内行”)”没有办法了,只能展示用这个了。勉强将项目如期完成!

project-image

数字项目大屏项目做完后,我再想能不能自己来写一个数字大屏demo呢?恰好DF的‘’Mind+Python编程与智能设计大赛'正在进行中,于是就开始着手去实现这个想法!本教程将会带领你从零开始搭建一个:基于Pyecharts + Flask + Pinpong 智能家居数字大屏系统的项目!

准备阶段

步骤1 安装pyecharts

概况

Echarts 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而 Python 是一门富有表达力的语言,很适合用于数据处理。当数据分析遇上数据可视化时,pyecharts 诞生了。

特性

简洁的 API 设计,使用如丝滑般流畅,支持链式调用囊括了 30+ 种常见图表,应有尽有支持主流 Notebook 环境,Jupyter Notebook 和 JupyterLab可轻松集成至 Flask,Django 等主流 Web 框架高度灵活的配置项,可轻松搭配出精美的图表详细的文档和示例,帮助开发者更快的上手项目多达 400+ 地图文件以及原生的百度地图,为地理数据可视化提供强有力的支持 具体开发文档详见pyecharts开发文档

如何安装?

方法一:

在mind+的python模式下:点击“库管理”→"图表"→"pyecharts"点击安装即可

project-image

方法二:pip安裝

在mind+的python模式下:点击“库管理”→"pip模式"下:

输入:pip install pyecharts

(ps:因为我这里已经安装了,界面中提示了欢迎已经存在)

project-image

步骤2 安装falsk

简介:

Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合MVC模式进行开发,开发人员分工合作,小型团队在短时间内就可以完成功能丰富的中小型网站或Web服务的实现。另外,Flask还有很强的定制性,用户可以根据自己的需求来添加相应的功能,在保持核心功能简单的同时实现功能的丰富与扩展,其强大的插件库可以让用户实现个性化的网站定制,开发出功能强大的网站。

基本模式:

Flask的基本模式为在程序里将一个视图函数分配给一个URL,每当用户访问这个URL时,系统就会执行给该URL分配好的视图函数,获取函数的返回值并将其显示到浏览器上,其工作过程见图。

project-image

特点:

1、简单,Flask的路由以及路由函数由修饰器设定,开发人员不需要借助其他文件匹配;2、配置灵活,有多种方法配置,不同环境的配置也非常方便;环境部署简单,Flask运行不需要借助其他任何软件,只需要安装了Python的IDE,在命令行运行即可。只需要在Python中导入相应包即可满足所有需求;3、入门简单,通过官方指南便可以清楚的了解Flask的运行流程;4、低耦合,Flask可以兼容多种数据库、模板。 更多详情:flask开发文档

如何安装Flask?

在mind+的python模式下:点击“库管理”→"pip模式"下:

输入:pip install Flask

project-image

步骤3 安裝pinpong

简介:

pinpong库是一套控制开源硬件主控板的Python库,基于Firmata协议并兼容MicroPython语法,5分钟即可让你上手使用Python控制开源硬件。

借助于pinpong库,直接用Python代码就能给各种常见的开源硬件编程。其原理是给开源硬件烧录一个特定的固件,使开源硬件可以通过串口与电脑通讯,执行各种命令。

pinpong库的名称由“Pin”和“Pong”组成,“Pin”指引脚,“PinPong”为“乒乓球”的谐音,指信号的往复。

特点:

pinpong库的设计,是为了让开发者在开发过程中不用被繁杂的硬件型号束缚,而将重点转移到软件的实现。哪怕程序编写初期用Arduino开发,部署时改成了掌控板,只要修改一下硬件的参数就能正常运行,实现了“一次编写处处运行”。

当前PinPong库正在快速更新中,已支持Arduino系列uno、leonardo、mega2560,ESP32系列掌控板(handpy)以及micro:bit板,传感器支持50+,其他主控板及更多扩展库将逐步支持。

更多详情:pinpong开发手册

如何安装pinpong?

方法一:

在mind+的python模式下:点击“库管理”→"推荐库"→"硬件控制"→"pinpong"点击安装即可

project-image

方法二:

在mind+的python模式下:点击“库管理”→"pip模式"下:

输入:pip install pinpong

project-image

材料清单

  • Romeo 三合一Arduino兼容控制器 X1 链接
  • Gravity: I2C BME280环境传感器 (温度,湿度,气压) X1 链接

程序设计

step1:新建一个flask项目

在mind+的右侧项目文件:

1、新建一个文件 pyecharts-flask-demo

2、在pyecharts-flask-demo文件夹下 创建 templates文件夹

project-image

Step 2: 拷贝 pyecharts 模板

将 pyecharts 模板,位于 “C:\Users\83731\Documents\mindplus-py\environment\Python3.6.5-64\Lib\site-packages\pyecharts\render\templates”拷贝至刚新建的 templates 文件夹

(ps:这里的index.html文件是我自己新建的,后面会讲到)

project-image

Flask 前后端分离

Step 3: 新建一个 HTML 文件

新建 HTML 文件保存位于项目根目录的 templates 文件夹,这里以如下 index.html 为例. 主要用到了 jquery 和 pyecharts 管理的 echarts.min.js 依赖!后面用到水球图和词云需要用到echarts-liquidfill.min.js,echarts-wordcloud.min.js

这里你可能需要一点HTML,CSS,javascript等前端的知识,有兴趣的老师可以去w3school了解一下!(PS:本人也是小白一个,所设计的界面很low,希望大神勿喷!)

前端主动向后端进行数据刷新

定时刷新的核心在于 HTML 的 setInterval 方法。

代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>智能家居</title>
	<script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts.min.js"></script>
    <script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>
    <script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts-liquidfill.min.js"></script>
	<script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts-wordcloud.min.js"></script>
	<style>
	
		h1{font-family:"隶书";color:#9AB3F5;font-size:50px;margin-align:center;line-height:80px;}
		div{box-shadow:0 10px 20px rgba(191, 216, 184,0.19), 0 6px 6px rgba(191, 216, 184,0.23);border-radius:5px;background:#E8F6EF;position:relative; }
		#liquid{width:800px; height:400px;margin-left:50px; margin-top:20px;float: left;}
		#pressure{width:500px; height:400px;margin-left:40px; margin-top:20px; float: left;}
		#altitude{width:500px; height:400px;margin-left:40px; margin-top:20px;float: left;}
		#line{width:800px; height:400px;margin-top:20px;margin-left:50px;float: left;}
		#bar{width:500px; height:400px;margin-left:40px;margin-top:20px;float: left;}
		#wordcloud{width:500px; height:400px;margin-left:40px;margin-top:20px;float: left;}
		.ttop{width:100%;height:90px;text-align:center; margin-top:-20px;background:#6F69AC;}
</style>
</head>
<body >
	<div class="ttop" style="">
		<h1 > 基于Pyecharts + Flask + Pingpong 智能家居数字大屏系统</h1>
	</div>
		<div id="liquid" ></div>
		<div id="pressure"></div>
		<div id="altitude"></div>
		<div id="line"></div>
		<div id="bar"> </div>
		<div id="wordcloud"> </div>
	
	
    <script>
        var liquid = echarts.init(document.getElementById('liquid'), 'white', {renderer: 'canvas'});

        $(
            function () {
                fetchliquidData(liquid);
                setInterval(fetchliquidData, 2000);
            }
        );

        function fetchliquidData() {
            $.ajax({
                type: "GET",
                url: "http://127.0.0.1:5000/liquidChart",
				dataType:'json',
				success: function (result) {
			
					var jsonResult = JSON.stringify(result);
					var jsonResult =  JSON.parse(jsonResult,function(key,value){
						if(key === "formatter"){
							console.log(value);
							return eval('('+value+')');
						}else{
							return value;
						}
					});
					console.log(jsonResult);	
					liquid.setOption(jsonResult);
				},
			
			});
		}
    </script>
	
    <script>
        var pressure = echarts.init(document.getElementById('pressure'), 'white', {renderer: 'canvas'});

        $(
            function () {
                fetchpressureData(pressure);
                setInterval(fetchpressureData, 2000);
            }
        );

        function fetchpressureData() {
            $.ajax({
                type: "GET",
                url: "http://127.0.0.1:5000/pressureChart",
				dataType:'json',
				success: function (result) {
					pressure.setOption(result);
				},
			
			});
		}
    </script>
	
    <script>
        var altitude = echarts.init(document.getElementById('altitude'), 'white', {renderer: 'canvas'});

        $(
            function () {
                fetchaltitudeData(altitude);
                setInterval(fetchaltitudeData, 2000);
            }
        );

        function fetchaltitudeData() {
            $.ajax({
                type: "GET",
                url: "http://127.0.0.1:5000/altitudeChart",
				dataType:'json',
				success: function (result) {
					altitude.setOption(result);
				},
			
			});
		}
    </script>
	
    <script>
        var line = echarts.init(document.getElementById('line'), 'white', {renderer: 'canvas'});
        var old_data = [];
        $(
            function () {
                fetchlineData(line);
                setInterval(getDynamicData, 2000);
            }
        );

        function fetchlineData() {
            $.ajax({
                type: "GET",
                url: "http://127.0.0.1:5000/lineChart",
                dataType: "json",
                success: function (result) {
                    line.setOption(result);
                    old_data = line.getOption().series[0].data;
                }
            });
        }

        function getDynamicData() {
            $.ajax({
                type: "GET",
                url: "http://127.0.0.1:5000/lineDynamicData",
                dataType: "json",
                success: function (result) {
                    old_data.push([result.name, result.value]);
                    line.setOption({
                        series: [{data: old_data}]
                    });
                }
            });
        }

    </script>
	
	<script>
        var bar = echarts.init(document.getElementById('bar'), 'white', {renderer: 'canvas'});

        $(
            function () {
                fetchbarData(bar);
                setInterval(fetchbarData, 2000);
            }
        );

        function fetchbarData() {
            $.ajax({
                type: "GET",
                url: "http://127.0.0.1:5000/barChart",
				dataType:'json',
				success: function (result) {
					bar.setOption(result);
				},
			
			});
		}
    </script>
	
	<script>
        var wordcloud = echarts.init(document.getElementById('wordcloud'), 'white', {renderer: 'canvas'});

        $(
            function () {
                fetchwordcloudData(wordcloud);
                setInterval(fetchwordcloudData, 2000);
            }
        );

        function fetchwordcloudData() {
            $.ajax({
                type: "GET",
                url: "http://127.0.0.1:5000/wordcloudChart",
				dataType:'json',
				success: function (result) {
					wordcloud.setOption(result);
				},
			
			});
		}
    </script>
</body>
</html>

Step 4: 新建app.py文件

请将下面的代码保存为 app.py 文件并移至项目的根目录下。

该程序主要用pingpong来获取传感器的数据,然后将数据放到pyecharts的chart中。然后通过falsk web框架每个两秒将数据发送到前端!浏览器输入:http://127.0.0.1:5000/。进行页面显示。主要用到了get请求来获取数据。

代码


from random import randrange
# 导入Flask  相关库文件
from flask import Flask, render_template
from flask.json import jsonify

# 导入pyecharts 相关库文件
from pyecharts import options as opts
from pyecharts.charts import Grid, Liquid,Gauge,Line,Pie,Bar,WordCloud
from pyecharts.faker import Faker
from pyecharts.commons.utils import JsCode
from pyecharts.globals import ThemeType
# 导入pinpong库相关文件
from pinpong.board import Board,Pin
from pinpong.libs.dfrobot_bme280 import BME280

import time

Board("uno").begin() 

# BME280 初始化
bme = BME280()


app = Flask(__name__, static_folder="templates")

date=[0,0,0,0]

# 创建水球图
def create_liquid_charts(temp,humi) ->Liquid:
    l1 = (
    Liquid()
    .add("湿度", # 系列名称
        [humi], # 系列数值
    center=["48%", "50%"], # 设置x,y坐标值
    label_opts=opts.LabelOpts( # 标签配置项
            font_size=35,  # 标签大小
             position="inside",
    )
    
    )
    .set_global_opts(title_opts=opts.TitleOpts(title="温湿度电量水球图")) # 提示框组件配置项
    )

    l2 = (
    Liquid()
    .add(
        "温度",
        [temp],
        center=["18%", "50%"],
        color=["#FFA500"],
        label_opts=opts.LabelOpts(
            font_size=35,
            formatter=JsCode(
                """function (param) {
                    return (Math.floor(param.value * 10000) / 100) + '℃';
                }"""
            ),
            position="inside",
        ),
    )
    )

    l3=(
    Liquid()
    .add("电量", [0.8], 
    center=["78%", "50%"],
    color=["#00FA9A"], # 水球图波浪颜色
    label_opts=opts.LabelOpts(
            font_size=35,
             position="inside",
    )
    )

    )
 # 通过grid 并行多图将三个水球合并在一张图上
    grid = Grid().add(l1, grid_opts=opts.GridOpts()).add(l2, grid_opts=opts.GridOpts()).add(l3, grid_opts=opts.GridOpts())

    return grid
# 创建仪表盘
 
def creat_pressure_chart(press) -> Gauge:
    c = (
        Gauge()
        .add("", 
        [("压强", press)],
        min_=['0'], # 最小值
        max_=['2000'], # 最大值
        split_number=5,
        axisline_opts=opts.AxisLineOpts( # 设置仪表盘颜色及宽度
            linestyle_opts=opts.LineStyleOpts(
                color=[(0.3, "#67e0e3"), (0.7, "#37a2da"), (1, "#fd666d")], width=30
            )
        ),
        detail_label_opts=opts.LabelOpts(formatter="{value}  Pa"),) # 格式
        .set_global_opts(title_opts=opts.TitleOpts(title="压强仪表盘"))
    )
    return c 
# 创建海拔仪表盘
def creat_altitude_chart(alti) -> Gauge:
    
    c1 =(
        Gauge()
        .add("", 
        [("海拔", alti)],
        min_=['0'], # 最小值
        max_=['1000'], # 最大值
        split_number=5,
        axisline_opts=opts.AxisLineOpts( # 设置仪表盘颜色及宽度
            linestyle_opts=opts.LineStyleOpts(
                color=[(0.3, "#F3EAC2"), (0.7, "#F5B461"), (1, "#EC524B")], width=30
            )
        ),
        detail_label_opts=opts.LabelOpts(formatter="{value}  M"),) # 格式
        .set_global_opts(title_opts=opts.TitleOpts(title="海拔仪表盘"))
    )
    
    return c1
# 创建折现图  
def line_base() -> Line:
    line = (
        Line()
        .add_xaxis(["{}".format(i) for i in range(10)])
        .add_yaxis(
            series_name="",
            y_axis=[randrange(20, 35) for _ in range(10)],
            is_smooth=True,
            label_opts=opts.LabelOpts(is_show=False),
        )
        .set_global_opts(
            title_opts=opts.TitleOpts(title="温度动态数据显示"),
            xaxis_opts=opts.AxisOpts(type_="value"),
            yaxis_opts=opts.AxisOpts(type_="value"),
        )
    )
    return line
# 创建主创图
def creat_bar_chart(temp,humi,press,alti) -> Bar:
    
    c = (
        Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT))
        .add_xaxis(["温度","湿度","压强","海拔"])
        .add_yaxis("小明家", [temp,humi,press,alti])
        .set_global_opts(title_opts=opts.TitleOpts(title="BME280数据显示"))
    )
    return c 

# 词条
data = [
    ("hockel", "2004"),
    ("智能家居", "1500"),
    ("少儿编程", "777"),
    ("人工智能", "666"),
    ("python", "200"),
    ("mind+", "555"),
    ("Erised", "1204"),
    ("温度", "50"),
    ("湿度", "40"),
    ("压强", "21"),
    ("海拔", "30"),
    ("BME280", "21"),
    ("pyecharts", "99"),
    ("arduino", "50"),
    ("flask", "80"),
    ("html", "60"),
    ("css", "20"),
    ("js", "70"),
    ("pinpong", "120"),
   
]
# 创建词云
def creat_wordcloud_chart() -> WordCloud:
    c = (
    WordCloud()
    .add(series_name="所用技术", data_pair=data, word_size_range=[6, 66])
    .set_global_opts(
        title_opts=opts.TitleOpts(
            title="所用技术", title_textstyle_opts=opts.TextStyleOpts(font_size=23)
        ),
        tooltip_opts=opts.TooltipOpts(is_show=True),
    )
    )
    return c 

@app.route("/")
def index():
    return render_template("index.html")


@app.route("/liquidChart")
def get_liquid_chart():
    date[0] =  bme.temp_c()
    date[1] =  bme.humidity()
    date[2] =  bme.press_pa()
    date[3] =  bme.cal_altitudu()
    temp = date[0] / 100
    humi = date[1] / 100
    c = create_liquid_charts(temp,humi)
    return c.dump_options_with_quotes()

# 压强仪表盘
@app.route("/pressureChart")
def get_pressure_chart():
    press = date[2] / 100
    c = creat_pressure_chart(press)
    return c.dump_options_with_quotes()

# 海拔仪表盘
@app.route("/altitudeChart")
def get_altitude_chart():
    alti =  date[3]
    c = creat_altitude_chart(alti)
    return c.dump_options_with_quotes()
# 动态图
@app.route("/lineChart")
def get_line_chart():
    c = line_base()
    return c.dump_options_with_quotes()

idx = 9
@app.route("/lineDynamicData")
def update_line_data():
    global idx
    idx = idx + 1
    return jsonify({"name": idx, "value": date[0]})

# 柱状图
@app.route("/barChart")
def get_bar_chart():
    c = creat_bar_chart(date[0],date[1],date[2]/100,date[3])
    return c.dump_options_with_quotes()

# 词云
@app.route("/wordcloudChart")
def get_wordcloud_chart():
    c = creat_wordcloud_chart()
    return c.dump_options_with_quotes()



if __name__ == "__main__":
    app.run()
   

演示效果

FAQ :

1、Gauge 仪表板显示图例与数据重叠

project-image

这个是pyecharts V1.9.0的BUG!在V2.0.0将被修复!

解决方法:

找到pyecharts的安装目录(右击右侧项目:打开文件所在位置即可找到):

C:\Users\83731\Documents\mindplus-py\environment\Python3.6.5-64\Lib\site-packages\

然后修改pyecharts\charts\basic_charts\chart.py文件

project-image

2、pyecharts中的水球图不显示问题:

在遇到这个问题之后,了解一下echarts,才知道echarts-liquidfill是第三方插件,需要单独导入

需要在index.html文件中标签内加入:


3、水球图在进行数据精度显示是时:单位不能显示!

在看了pyecharts文档时有一处写到:

注: 目前由于 json 数据类型的问题,无法将 pyecharts 中的 JSCode 类型的数据转换成 json 数据格式返回到前端页面中使用。因此在使用前后端分离的情况下尽量避免使用 JSCode 进行画图。

代码
l2 = (
    Liquid()
    .add(
        "温度",
        [0.3254],
        center=["18%", "50%"],
        color=["#FFA500"],
        label_opts=opts.LabelOpts(
            font_size=35,
            formatter=JsCode(
                """function (param) {
                    return (Math.floor(param.value * 10000) / 100) + '℃';
                }"""
            ),
            position="inside",
        ),
    )
    )
project-image

我这这里jscode返回的对象是str:str! 然而我们需要调用jscode的函数!我们需要在html的文档中进行数据处理:将str:str 转换成 str:function().找到我们调用的liquid的script标签进行如下更改!

代码
 function fetchliquidData() {
            $.ajax({
                type: "GET",
                url: "http://127.0.0.1:5000/liquidChart",
				dataType:'json',
				success: function (result) {
			
					var jsonResult = JSON.stringify(result);
					jsonResult =  JSON.parse(jsonResult,function(key,value){
						if(key === "formatter"){
							console.log(value);
							return eval('('+value+')');
						}else{
							return value;
						}
					});
					console.log(jsonResult);	
					liquid.setOption(jsonResult);
				},

4.用grid组件无法将liquid和gauge并列合并在同一图上!

使用grid时,一般情况下,永远不要先给grid加入一个没有x y轴的图,比如饼图、仪表盘、地图等等。

如果你想加的,可以参考这篇文章:pyecharts丨页面布局工具——grid注意事项和overlap的使用

总结

整个项目做下来!被Flask的前后端分离数据显示问题搞得崩溃!主要还是因为自己的前端知识有点薄弱。为此自己恶补了一晚的前端的html,css,js等知识!对falsk web框架和pyecharts有了进一步的了解!屏幕前的你,有没有学吐了呢?反正我是秃了!

更多教程欢迎关注个人博客:www.hockel.club

微信公众账号:跟着hockel玩科创

project-image

评论

user-avatar
  • 白凡

    白凡2021.09.27

    大佬666

    0
    • 赵文栋

      赵文栋2021.09.20

      我这边是做科技教育培训行业的,想要获得你的联系方式,是否方便添加一下微信13701338504,这个是我的微信,看合作一下

      0
      • 许培享

        许培享2021.09.09

        碉!我想学可能学不来啊

        1
        • Hockel

          Hockel2021.09.09

          没有没有😂我也是从零开始。遇到很多大坑,项目差点就搁浅了。🤭

      • 木子呢

        木子呢2021.09.09

        0
        • WSY

          WSY2021.09.09

          优秀!

          0