本帖最后由 HonestQiao 于 2022-11-11 10:39 编辑
行空板板载的丰富接口,能够让我们很方便的接入多种传感器。简单的网络连接方式,能够让我们快捷的连接到网络。而自带的GUI屏,可以很方便我们进行显示。
今天的分享,就是基于行空板的实时天气牌,具体如下:
下面,就给大家分享制作步骤。
目录:
一、硬件材料
二、原理图
三、实物连线
四、SHT31数据读取
五、实时天气信息获取
六、屏幕操作
七、代码编写
八、最终作品
九、总结
一、硬件材料
- 主控板:行空板
- 温湿度传感器:DFRobot SHT31
- 连接线:I2C连接线
我直接使用的是DFRobot版本的SHT31,配送专用连接线。
使用其他的I2C的SHT3X传感器,也完全可以。
二、原理图
行空板使用标准PH2.0接口,也不用原理图了,找对接口插上就行了。
三、实物接线
如果是自己的传感器的话,注意:
四、SHT31数据读取
以前也在Linux下面使用过SHT31传感器,有使用到的Python库。
经过仔细研读行空板Python开发的教程后,得了,不用自己弄了,行空板自带了。
行空板使用的Python硬件操作库为 pinpong,具体资料可见:行空板官方文档 - pinpong库 (unihiker.com)
pinpong库的使用资料,可以查看:欢迎访问 pinpong python库教程文档! — pinpong 0.1 文档
查看pinpong的更新记录,发现已经支持了SHT31:
只是教程里面,并没有使用案例;
既然没有使用案例,那就自己看库好了。
在https://pypi.org/ 搜索到 pinpong库:
注意,不是:https://pypi.org/project/pingpong/
在pypi的pinpong页面,下载该扩展库,解压后,找到如下文件:
这个就是SHT31的支持库。
在其中,有几个关键调用,可以方便我们快捷的使用:
-
sht31 = SHT31(i2c_addr=0x45):初始化
-
sht31.readTemperatureAndHumidity(0):预读取温湿度数据
-
temp = sht31.temp_c():获得温度值
-
humi = sht31.humidity():获得湿度值
五、实时天气信息获取
通过SHT31,我们可以获取当前环境的温湿度。但是,我们还想获得更多的天气相关的信息,这就需要通过网络获取了。
在网上查找天气信息API的时候,通过 https://www.cnblogs.com/dummersoul/p/12174601.html 了解到了
和风天气,可以提供天气API,并且免费用户每天有1000次的调用次数,使用没有障碍。
要使用该免费天气API,需要注册账户,获取数据请求的加密key,以及了解对应的接口,具体步骤如下:
1. 访问 和风天气 注册账号并登陆:
网址:https://dev.qweather.com
2. 创建免费项目
一个用户,只有一个免费项目名额。
3. 查看项目
4. 创建key
注意,此处应该选择Web Api
创建完成后会显示生成的Key:
复制该Key备用,我们的Python程序中,将要使用到。
5. 了解使用方式
天气信息的具体调用接口,可以查看:https://dev.qweather.com/docs/api/weather/
因为要显示实时天气信息,所以使用的实时天气Api接口如下:
调用该接口后,将会返回对应的天气信息。
例如:
- 北京的LocationID为:101010100
- 天津的LocationID为:101030100
那么,通过下面的方式,就能获取对应的测试数据了:
注意:测试前,需要先使用ssh连接到行空板,可参考官方教程:https://wiki.unihiker.com/ssh
# 获取北京的实时天气信息
curl -L -X GET --compressed 'https://devapi.qweather.com/v7/weather/now?location=101010100&key=上一步生成的Key'
# 获取天津的实时天气信息
curl -L -X GET --compressed 'https://devapi.qweather.com/v7/weather/now?location=101030100&key=上一步生成的Key'
实际返回如下:
上述命令中的Key,要换成你在上一步中生成的Key。
关于返回的信息中,各字段的具体含义,可以查看:https://dev.qweather.com/docs/api/weather/weather-now/
然后,在Python中,使用request来获取上述网址返回的信息,然后使用json进行界面,就能得到对应的数据了:
6. 获取指定地区的LocationID
在上面的API请求中,有一个LocationID参数,这个需要我们提前获取。
而和风天气,也提供了GeoAPI,可以方便我们获取指定城市的Geo信息,具体说明如下:
在行空板上,实际操作如下:
# 获取北京的Geo信息
curl -L -X GET --compressed 'https://geoapi.qweather.com/v2/city/lookup?location=beijing&key=上一步生成的Key'
# 获取天津的Geo信息
curl -L -X GET --compressed 'https://geoapi.qweather.com/v2/city/lookup?location=tianjin&key=上一步生成的Key'
实际返回如下:
同样的,可以在Python中,通过request和json扩展,来获取并解析。
7. 天气图标获取
和风天气,也提供了天气相关的图标,但是是svg格式的,不能在行空板上直接使用,需要转换为png或者jpg格式。
Iconv库地址:https://github.com/qwd/Icons
要转换为png格式,使用如下的方法:
# 创建实时天气信息牌的工作目录
mkdir ~/projects/weather
cd ~/projects/weather
# 获取天气图标库
git clone https://github.com/qwd/Icons.git
cd Icons/icons
# 转换天气图标
mkdir ../../imgs/
for i in $(ls *.svg);do convert $i $i.png;done
for i in $(ls *.svg);do convert $i.png -fuzz 2% -transparent white ../../imgs/$i.png;done
# 查看转换结果
ls ../../imgs/
最后的结果如下:
另外,还需要在/root/projects/weather/imgs/下面,放一张bg.jpg,大小为240x320,做为背景图片,可以用下图:
六、屏幕操作
行空板自身的系统,提供了屏幕操作的库,使用很方便。
制作这个实时天气信息牌,使用到的调用如下:
-
gui=GUI():实例化屏幕对象
-
gui.on_key_click('a', lambda: exit()):绑定按键,按A键,调用exit()函数
-
gui.draw_text(x=60, y=0, text='实时天气情况'):在屏幕指定位置(60,0)输出文字
-
gui.draw_image(x=0, y=0, w=240, h=320, image=Image.open(bg_file)):在屏幕指定位置(0,0),显示图像(路径为bg_file),大小为240x320
-
gui.draw_digit(x=20, y=y, color="red", font_size=12, text=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())):使用数码管字体,显示当前时间
-
temp_obj.config(text="%s℃" % ret['temp']):更新指定对象的值
关于上述调用的具体说明,可以查看行空板的教程:https://wiki.unihiker.com/unihiker_python_lib_2
其中,按键调用的作用,是为了方便按键,退出程序的运行。
七、编写代码
现在,各项准备工作就绪,就可以编写实际的代码了。
具体代码如下:
# -*- coding: UTF-8 -*-
import os,sys
import time
from pinpong.board import Board
from pinpong.libs.dfrobot_sht31 import SHT31
from unihiker import GUI
from PIL import Image
import requests
import json
# 和风天气API服务:https://dev.qweather.com/docs/api/weather/
weather_type = ['now', '3d', '7d', '24h', '48th'] # 实时、天-3天、天-7天、小时-24小时、小时48小时
weather_icon_dir = "/root/projects/weather/imgs"
key = '*******前面生成的Key*********'
LocationName = "天津"
LocationID = 0
def get_location(where):
url = 'https://geoapi.qweather.com/v2/city/lookup?location=%s&key=%s' \
% (where, key) # 和风天气平台GeoAPI
response = requests.get(url)
if response.status_code == 200:
data = json.loads(response.text)
if "code" in data and data["code"] == '200':
if len(data["location"])>0:
# 获取到地址,取第一个
LocationName = data["location"][0]["adm1"]
LocationID = data["location"][0]["id"]
return LocationName, LocationID
return [False, False]
def get_weather(weather_type, LocationID, where):
url = "https://devapi.qweather.com/v7/weather/%s?location=%s&key=%s" \
% (weather_type, LocationID, key) # 和风天气平台天气API
response = requests.get(url)
if response.status_code == 200:
data = json.loads(response.text)
if weather_type == 'now':
if "code" in data and data["code"]=='200':
print("当前时间:%s" % time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
print("%s的实况天气:当前气温为%s摄氏度,体感温度为%s摄氏度,天气状况%s(%s),%s风向,风力%s级,风速每%s公里/时,相对湿度%%%s,大气压强为%s,能见度%s公里,云量%s"
% (where,
data["now"]['temp'],
data["now"]['feelsLike'],
data["now"]['text'],
data["now"]['icon'],
data["now"]['windDir'],
data["now"]['windScale'],
data["now"]['windSpeed'],
data["now"]['humidity'],
data["now"]['pressure'],
data["now"]['vis'],
data["now"]['cloud']
)
)
return data["now"]
return False
def exit():
global isEnd
isEnd = True
if __name__ == '__main__':
# 初始化板子
Board("UNIHIKER").begin()
# 初始化显示屏
gui=GUI()
isEnd = False
gui.on_key_click('a', lambda: exit())
gui.on_key_click('b', lambda: exit())
bg_file = "%s/bg.jpg" % weather_icon_dir
bg_obj = gui.draw_image(x=0, y=0, w=240, h=320, image=Image.open(bg_file))
title_obj = gui.draw_text(x=60, y=0, text='实时天气情况')
# 读取温湿度信息
sht31 = SHT31(i2c_addr=0x45)
sht31.readTemperatureAndHumidity(0)
temp = sht31.temp_c()
humi = sht31.humidity()
print("环境温度:%f 湿度:%f\n" % (temp, humi))
# 获取地理位置信息
LocationName, LocationID = get_location(LocationName)
if LocationName == False:
print("城市Geo信息获取失败")
else:
print("城市:%s ID:%s\n" % (LocationName, LocationID))
ret = get_weather(weather_type[0], LocationID, LocationName)
if ret == False:
print("城市天气信息获取失败")
else:
# %s的实况天气:当前气温为%s摄氏度,体感温度为%s摄氏度,天气状况%s,%s风向,风力%s级,风速每%s公里/时,相对湿度%%%s,大气压强为%s,能见度%s公里,云量%s
x1 = 20
x2 = 120
y = 20
y_hight = 20
gui.draw_text(x=x1, y=y, text="当前地区", font_size=13)
name_obj = gui.draw_text(x=x2, y=y, text=LocationName, font_size=13)
y = y + y_hight
gui.draw_text(x=x1, y=y, text='当前气温', font_size=13)
temp_obj = gui.draw_text(x=x2, y=y, text="%s℃" % ret['temp'], font_size=13)
y = y + y_hight
gui.draw_text(x=x1, y=y, text='体感温度', font_size=13)
feelsLike_obj = gui.draw_text(x=x2, y=y, text="%s℃" % ret['feelsLike'], font_size=13)
y = y + y_hight
gui.draw_text(x=x1, y=y, text='环境温度', font_size=13)
temp_here_obj = gui.draw_text(x=x2, y=y, text="%0.1f℃" % temp, font_size=13)
y = y + y_hight
gui.draw_text(x=x1, y=y, text='环境湿度', font_size=13)
humi_here_obj = gui.draw_text(x=x2, y=y, text="%0.1f%%" % humi, origin='top_left', font_size=13)
y = y + y_hight + 10
gui.draw_text(x=x1, y=y, text='天气状况', font_size=13)
text_obj = gui.draw_text(x=x2, y=y, text=ret['text'], font_size=13)
# 天气图标
icon_file = "%s/%s.svg.png" % (weather_icon_dir, ret['icon'])
icon_obj = gui.draw_image(x=180, y=y, w=32, h=32, image=Image.open(icon_file))
y = y + y_hight
gui.draw_text(x=x1, y=y, text='风向', font_size=13)
windDir_obj = gui.draw_text(x=x2, y=y, text=ret['windDir'], font_size=13)
y = y + y_hight
gui.draw_text(x=x1, y=y, text='风力', font_size=13)
windScale_obj = gui.draw_text(x=x2, y=y, text="%s级" % ret['windScale'], font_size=13)
y = y + y_hight
gui.draw_text(x=x1, y=y, text='风速', font_size=13)
windSpeed_obj = gui.draw_text(x=x2, y=y, text="%sm/s" % ret['windSpeed'], font_size=13)
y = y + y_hight
gui.draw_text(x=x1, y=y, text='相对湿度', font_size=13)
humidity_obj = gui.draw_text(x=x2, y=y, text="%s%%" % ret['humidity'], font_size=13)
y = y + y_hight
gui.draw_text(x=x1, y=y, text='大气压强', font_size=13)
pressure_obj = gui.draw_text(x=x2, y=y, text="%0.1fkPa" % (float(ret['pressure'])/1000), font_size=13)
y = y + y_hight
gui.draw_text(x=x1, y=y, text='能见度', font_size=13)
vis_obj = gui.draw_text(x=x2, y=y, text="%s米" % ret['vis'], font_size=13)
y = y + y_hight
gui.draw_text(x=x1, y=y, text='云量', font_size=13)
cloud_obj = gui.draw_text(x=x2, y=y, text="%s%%" % ret['cloud'], font_size=13)
y = y + y_hight + 10
time_obj = gui.draw_digit(x=20, y=y, color="red", font_size=12, text=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
weather_api_time = time.time()
while True:
if isEnd:
sys.exit(0)
break
time_obj.config(text=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
# 读取环境温湿度信息
sht31.readTemperatureAndHumidity(0)
temp = sht31.temp_c()
humi = sht31.humidity()
temp_here_obj.config(text="%0.1f℃" % temp)
humi_here_obj.config(text="%0.1f%%" % humi)
if time.time() - weather_api_time > 60:
# 获取网络天气信息
weather_api_time = time.time()
ret = get_weather(weather_type[0], LocationID, LocationName)
if not ret == False:
icon_file = "%s/%s.svg.png" % (weather_icon_dir, ret['icon'])
icon_obj.config(image=Image.open(icon_file))
temp_obj.config(text="%s℃" % ret['temp'])
feelsLike_obj.config(text="%s℃" % ret['feelsLike'])
text_obj.config(text=ret['text'])
windDir_obj.config(text=ret['windDir'])
windScale_obj.config(text="%s级" % ret['windScale'])
windSpeed_obj.config(text="%sm/s" % ret['windSpeed'])
humidity_obj.config(text="%s%%" % ret['humidity'])
pressure_obj.config(text="%0.1fkPa" % (float(ret['pressure'])/1000))
vis_obj.config(text="%s米" % ret['vis'])
cloud_obj.config(text="%s%%" % ret['cloud'])
time.sleep(1)
上述代码的逻辑,从上往下,比较明了,主要为:
- 调用相关的库
- 定义天气信息相关参数
- 定义函数:
- 定义获取geo信息的函数
- 定义获取天气信息的函数
- 定义按键处理函数
- 主调用:
- 初始化板子
- 初始化显示屏,设置按键监听
- 在屏幕上显示背景图片,以及标题信息
- 读取SHT31的数据并显示
- 获取设定地区的地理信息
- 获取对应LocationID地区的实时天气信息并显示
- 循环
- 更新时间
- 更新SHT31的温湿度信息并更新环境温湿度信息
- 检测当前时间,如果与上一次获取天气信息相比,如果超过1分钟,则再次获取
- 延时1秒
八、实际作品
将上述代码保存到/root/projects/weather/weather.py中,就可以在行空板上,通过屏幕操作,直接使用了。
1. 在行空板上操作,通过切换应用程序,进入实时天气信息牌的工作目录:
2. 在目录中,运行weather.py
3. 执行成功后的界面
4. 退出程序
按A或者B键,程序会退出,并显示命令行输出的信息:
5 视频操作演示
行空板实时天气信息牌
九、总结
这个实时天气信息牌,通过网络与本地传感器的结合,通过行空板pinpng硬件库的支持和GUI图形库的支持,得以简单而又完美的实现。
行空板,真的是系出名门。不仅是学习Python物联网编程的神器,同样也是Python学习的神器,人人都值得拥有一块!!!