制作要求:显示公历日期/农历日期/天气/风向/温度/节日/提醒(屏幕较小,先显示这么多)。派加电即可显示,派正常关机屏幕白 屏(休眠)以保护墨水屏,天气相关信息每12小时更新一次。天气信息来源于爬取的内容,每天用cron(定时任务)定时爬取,爬取时间在墨水屏刷新前半个小时。
注意事项:根据微雪官方的说法,目前只有黑白墨水屏支持局部刷新,可以做时钟显示,其他三色屏是不支持局部刷新的。这个2.9寸的屏局部刷新是0.2秒,全部刷新是2秒。而且墨水屏最好24小时要全刷一次,不然会留残影,严重导致屏幕不可修复的损坏,所以我设定是每天全刷新两次。原程序代码设定,全刷时,屏幕闪两次黑屏两次,局刷没有明显变化。
改进计划:
- 显示不同地区的基本天气。
- 定制闹钟并显示。
- 红外感应没人时屏幕休眠或显示其他内容。
- 手机控制屏幕显示不同内容(高级功能)。
- 语音控制显示内容(高级功能)。
硬件及软件:
- 1、2.9inch e-Paper Module(微雪2.9寸墨水屏带驱动,祼屏不行。)
- 树梅派3B(以后想改成zero w货还没到,先用这个)
- raspbian系统最新版,python3.7,BCM2835, wiringPi,PIL,borax.
制作过程:
- 硬件连接:(raspbian系统的安装及操作方法这里不讲,请自行百度)
- 连接派:(注意vcc是接在3.3V上,而不是5V,我也不知道接到5V上会不会坏,反正我没敢)
对应引脚
图片不是3B的不过一样,对应40脚的图.
2、开启派SPI口。
- 软件安装:(raspbian系统的安装及操作方法这里不讲,请自行百度)
- 相关库下载及安装:说明:由于我是在root用户下操作的,所以下面的命令都没有加sudo。由于raspbian默认用户是pi,所以给最后面的工作增加了一点麻烦,不过我还是按我的思路和实现方法来说明,以免造成误导。
安装BCM2835
wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.60.tar.gz
tar zxvf bcm2835-1.60.tar.gz
cd bcm2835-1.60/
./configure
make
make check
make install
安装wiringPi
apt-get install wiringpi #对于树莓派4B可能需要进行升级:
cd /tmp
wget https://project-downloads.drogon.net/wiringpi-latest.deb
dpkg -i wiringpi-latest.deb
gpio -v
# 运行gpio -v会出现2.52版本,如果没有出现说明安装出错
安装Python函数库
apt-get update
apt-get install python3-pip
apt-get install python3-pil
apt-get install python3-numpy
pip3 install RPi.GPIO
pip3 install spidev
如果是最新的系统,大部分的python库都已安装。
微雪官方测试程序下载:
git clone https://github.com/waveshare/e-Paper
cd e-Paper/RaspberryPi\&JetsonNano/
执行测试程序:python3 epd_2in9_text.py
到此就应该可以看以墨水屏有显示了。基础工作已做完。
以上内容来自微雪官方网址:http://www.waveshare.net/wiki/2.9inch_e-Paper_Module
2.必须库下载及安装
PIL安装:(PIL是Python一个强大方便的图像处理库)
pip install Pillow
农历库Borax1.3安装 (Borax是一个的 Python3 开发工具集合库,不限于显示农历)
pip install borax
到此环境已搭建完成,下面是关键的代码部分。
- 关键代码理解及解释:
def nianyueri():
#年月日星期
time_draw.rectangle((5, 5, 185, 25), fill = 255)
time_draw.text((5, 5), time.strftime('%Y年%m月%d日 %a'), font = font18, fill = 0)
newimage = time_image.crop([5, 5, 185, 25])
time_image.paste(newimage, (5,5))
墨水屏的显示原理是画图,跟其他的显示设备不一样。
在这里我定义了一个函数,因为后面还要用到这些代码,原测试程序里不是,测试代码实现的功能也比较简单。
time_draw.rectangle((5, 5, 185, 25), fill = 255)
这行是画一个矩形,(5, 5, 185, 25)显示是左上角x,y坐标,和右下角x,y坐标。fill=255是白色填充。还有一个参数outline=’black’我觉得很有用,在布局的时候可以帮助定位。
time_draw.text((5, 5), time.strftime('%Y年%m月%d日 %a'), font = font18, fill = 0)
在这个框内画的内容:
time.strftime('%Y年%m月%d日 %a')显示当前日期及星期,格式为:XXXX年XX月XX日
%a为英文星期的简写,如周一显示:Mon。font=font18为字体大小(font后面解释)。fill=0为黑色填充。
newimage = time_image.crop([5, 5, 185, 25])这个我没认真研究,估计要显示的新内容。
time_image.paste(newimage, (5,5))也没研究,估计是在(5,5)这个位置显示newimage内容。
特别说明:显示内容和布局是必须要改的,其他不用改,在布局的时候有几个关键的地方要注意:
1.字体的大小。字体的大小要不断的试。而且大小直接影响布局。
font54 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 54)
font24 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 24)
font18 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 18)
font14 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 14)
我这里定义了4种字体大小,示例程序只带了一种字体font.ttc,位置在上一级的pic目录下其他字体没安装也没试。
2.矩形online参数。加了这个参数后有利于看显示情况,再进行调整。
3.每设置完一个显示区域最后再增加一条。
epd.display(epd.getbuffer(time_image))对不起,这条也没懂,getbuffer我看函数说明也没看懂。必须要加,但不是每一个都加,这个有时间延迟,如果很显示一个区域都加这个,每个都会延迟1秒左右显示 ,看起来延好看。但如果加了计数的话,每段代码的执行时间就延长,计数时间也就长(原示例代码是执行5次退出。)。
4.相关函数:
nianyueri() #显示年月日星期
shijian() #显示时间
nongli() #显示农历
tianqi() #显示天气
wendu() #显示温度
fengxiang() #显示风向
shidu() #显示湿度
richeng() #显示日程(节日等,可自定义)
整个内容分成2大块,常刷新的,时间,和其他不常刷的,比如年月日星期/农历/天气/温度。。。时间的刷新是通过循环实现的。其他只要显示在那就可以了,不刷新内容不会消失(墨水屏的特点。)。
农历的显示:
def nongli():
#农历位置
today=LunarDate.today()
time_draw.rectangle((195, 5, 295, 25), fill = 255)
time_draw.text((195, 5), today.strftime('农 %M月%D'), font = font18, fill = 0)
newimage = time_image.crop([195, 5, 295, 25])
time_image.paste(newimage, (195,5))
today=LunarDate.today()
这段代码必须放在一起,不然农历过了0点也不会变。
5、必须要提一点:天气相关数据的显示。天气是从天气网上爬下来的,代码是从别人那拿来用的,不是我写的。网上有很多这样的代码,但多数都不太好用,这个正好符合要求。后面帖出代码。感谢原作者!
- 全部原代码,一共126行:
#!/usr/bin/python
# -*- coding:utf-8 -*-
import sys
import os
#定义路径变量
picdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'pic')
libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib')
if os.path.exists(libdir):
sys.path.append(libdir)
from waveshare_epd import epd2in9
import time
from PIL import Image,ImageDraw,ImageFont
import traceback
from borax.calendars.lunardate import LunarDate
#打开天气数据文件,并读取内容赋值给a,b,c三个变量,以供函数调用。
f=open('/root/weather.txt','r')
a=f.readline()
a=a.rstrip()
b=f.readline()
b=b.rstrip()
c=f.readline()
c=c.rstrip()
f.close()
def nianyueri():
#年月日星期位置
time_draw.rectangle((5, 5, 185, 25), fill = 255)
time_draw.text((5, 5), time.strftime('%Y年%m月%d日 %a'), font = font18, fill = 0)
newimage = time_image.crop([5, 5, 185, 25])
time_image.paste(newimage, (5,5))
def nongli():
#农历位置
today=LunarDate.today()
time_draw.rectangle((195, 5, 295, 25), fill = 255)
time_draw.text((195, 5), today.strftime('农 %M月%D'), font = font18, fill = 0)
newimage = time_image.crop([195, 5, 295, 25])
time_image.paste(newimage, (195,5))
def tianqi():
#天气位置
time_draw.rectangle((180,35, 295, 52), fill = 255)
time_draw.text((180, 35), a, font = font14, fill = 0)
newimage = time_image.crop([180, 35, 295, 52])
time_image.paste(newimage, (180,35))
def wendu():
#温度位置
time_draw.rectangle((180,55, 295, 72), fill = 255)
time_draw.text((180, 55), b, font = font14, fill = 0)
newimage = time_image.crop([180, 55, 295, 72])
time_image.paste(newimage, (180,55))
def fengxiang():
#风向位置
time_draw.rectangle((180,75, 295, 92), fill = 255)
time_draw.text((180, 75), c, font = font14, fill = 0)
newimage = time_image.crop([180, 75, 295, 92])
time_image.paste(newimage, (180,75))
def shidu():
#湿度位置
time_draw.rectangle((5,60, 35, 80), fill = 255,outline='black')
time_draw.text((5, 60), time.strftime('%S'), font = font18, fill = 0)
newimage = time_image.crop([5, 60, 35, 80])
time_image.paste(newimage, (5,602))
def richeng():
time_draw.rectangle((15,99, 170, 120), fill = 255)
time_draw.text((15, 99), time.strftime('闹闹是个小屁孩!'), font = font18, fill = 0)
newimage = time_image.crop([15, 99, 170, 120])
time_image.paste(newimage, (15,99))
def shijian():
#时间位置
time_draw.rectangle((35,30, 170, 80), fill = 255)
time_draw.text((35, 30), time.strftime('%H:%M'), font = font54, fill = 0)
newimage = time_image.crop([35, 30, 170, 85])
time_image.paste(newimage, (35,30))
#测试的时候可以放try ,except,真正完成没有必要。
try:
#屏幕初始化
epd = epd2in9.EPD()
epd.init(epd.lut_full_update)
epd.Clear(0xFF)
#定义4个尺寸字体。
font54 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 54)
font24 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 24)
font18 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 18)
font14 = ImageFont.truetype(os.path.join(picdir, 'Font.ttc'), 14)
# partial update局刷初始化
epd=epd2in9.EPD()
epd.init(epd.lut_partial_update)
epd.Clear(0xFF)
#定义两个变量,以供调用。
time_image = Image.new('1', (epd.height, epd.width), 255)
time_draw = ImageDraw.Draw(time_image)
#执行不变化的项目,如:日期,农历,天气等。
nianyueri()
nongli()
tianqi()
wendu()
fengxiang()
shidu()
richeng()
epd.display(epd.getbuffer(time_image))
#用循环来显示时间,因为时间必须要限时变化的。
while (True):
shijian()
epd.display(epd.getbuffer(time_image))
#如果时间在11点30和0点1秒进行全局刷新。更新所有数据。
if time.strftime('%H%M%S')=='000001' or time.strftime('%H%M%S')=='113000':
epd.init(epd.lut_full_update)
epd.Clear(0xFF)
epd.init(epd.lut_partial_update)
epd.Clear(0xFF)
f=open('/root/weather.txt','r')
a=f.readline()
a=a.rstrip()
b=f.readline()
b=b.rstrip()
c=f.readline()
c=c.rstrip()
f.close()
nianyueri()
nongli()
tianqi()
wendu()
fengxiang()
shidu()
richeng()
epd.display(epd.getbuffer(time_image))
epd.init(epd.lut_full_update)
epd.Clear(0xFF)
epd.sleep()
except KeyboardInterrupt:
epd2in9.epdconfig.module_exit()
exit()
- 天气爬虫原代码:如下内容参考:https://cloud.tencent.com/developer/article/1495735感谢原作者!
需安装 requests,bs4,re
# coding: utf-8
import requests
from bs4 import BeautifulSoup
import re
def getWeath(city_code):
try:
# print(city_code)
url = 'http://www.weather.com.cn/weather/%s.shtml'%city_code
ret = requests.get(url)
except BaseException as e:
print(e)
return {}
ret.encoding = 'utf-8'
soup = BeautifulSoup(ret.text, 'html.parser')
tagToday = soup.find('p', class_ = "tem") #第一个包含class="tem"的p标签即为存放今天天气数据的标签
try:
temperatureHigh = tagToday.span.string #有时候这个最高温度是不显示的,此时利用第二天的最高温度代替。
except AttributeError:
temperatureHigh = tagToday.find_next('p', class_="tem").span.string #获取第二天的最高温度代替
temperatureLow = tagToday.i.string #获取最低温度
temperatureLow=temperatureLow[:-1]
temperatureHigh=temperatureHigh+'°C'
weather = soup.find('p', class_ = "wea").string #获取天气
wind = soup.find('p', class_ = "win") #获取风力
clothes = soup.find('li', class_ = "li3 hot") #穿衣指数
#print('最低温度:' + temperatureLow)
#print('最高温度:' + temperatureHigh)
#print('天气:' + weather)
#print('温度:'+temperatureLow +'-'+ temperatureHigh)
#print('风力:' + wind.i.string)
#print('穿衣:' + clothes.a.span.string + "," + clothes.a.p.string)
fileHandle=open('weather.txt','w')
fileHandle.write('天气:' + weather)
fileHandle.write('\n温度:'+temperatureLow +'-'+ temperatureHigh)
fileHandle.write('\n风力:' + wind.i.string)
fileHandle.close()
return {'温度':temperatureHigh + '/' + temperatureLow
, '天气':weather
, '风力':wind.i.string
, '穿衣':clothes.a.span.string + ',' + clothes.a.p.string}
def strDic(dic):
str_weather = ''
for key in dic:
str_weather += key + ':' + dic[key]
str_weather += '\n'
return str_weather
if __name__ == "__main__":
wea_str = strDic(getWeath(101280601))
wea_str = strDic(getWeath(101280601))#上面这串数字(101280601)是关键,这个是深圳的代码,如果查询其他城市的代码可以在http://www.weather.com.cn网上查询到。比如:http://www.weather.com.cn/weather1d/101020100.shtml那串数字代表上海,替换掉上面的字数不可以爬到上海的天气了。
利用cron定时任务完成爬取任务。
编辑/etc/crontab文件。
vim /etc/crontab
添加:11点和23点自动爬取天气信息。
* 23,11 * * * root python3 ~/e-Paper/RaspberryPi\&JetsonNano/python/examples/中国天气网爬取深圳天气.py
生成的weather.txt文件系统会放在/root/目录.
- 开机自动运行代码的实现。
这是个大坑。查了很多方法都不行,我猜想是因为我一直用的root用户操作,如果用Pi用户可能会好点?但到后来想改已经来不急了,硬头皮上吧,最后找到一个方法完美实现,方法如下:
1、建立start.sh脚本。
#!/bin/sh
sleep 10
sudo python3 /root/e-Paper/RaspberryPi\&JetsonNano/python/examples/epd_2in9_test.py
2、保存后给可执行权限。
Sudo chmod 777 start.sh
说明:sleep 10很关键,根据资料说,如果不延迟,直接运行可能会让某些进程无法启动,从而影响系统的运行。这里用的是绝对路径,相对路径不行。而且在epd_2in9_text.py程序里面的文件也要用绝对路径。还要提一点,就是路径里那个&符号,要转义(前面加\),不然系统会找不到文件。
3、修改/etc/rc.local文件
在exit 0前写入:
./home/pi/start.sh &
说明:./(点和斜杠)表示脚本直接执行。&丢到后台去运行,据说这个也很关键。
- 写在最后
本例实现的各个文件位置:
1、epd_2in9_test.py(主程序)
/root/e-Paper/RaspberryPi\&JetsonNano/python/examples/epd_2in9_test.py
2、天气爬取程序位置同上。
3、weather.txt文件。/root/weather.txt因为自动爬取程序生成这个文件自动放在root目录,我本来是想放在主程序同目录的,目前还不知道怎么改。
这么个小东西,花了三天时间研究代码,填各种坑,玩代码真是很辛苦,没办法,喜欢。从中也学习和复习了很多东西。
最后上一张多半成品的图: