2.9寸墨水屏制作互联网时钟填坑手记

mac2024-04-12  46

制作要求:显示公历日期/农历日期/天气/风向/温度/节日/提醒(屏幕较小,先显示这么多)。派加电即可显示,派正常关机屏幕白    屏(休眠)以保护墨水屏,天气相关信息每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目录,我本来是想放在主程序同目录的,目前还不知道怎么改。

这么个小东西,花了三天时间研究代码,填各种坑,玩代码真是很辛苦,没办法,喜欢。从中也学习和复习了很多东西。

最后上一张多半成品的图:

 

最新回复(0)