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

制作要求:显示公历日期/农历日期/天气/风向/温度/节日/提醒(屏幕较小,先显示这么多)。派加电即可显示,派正常关机屏幕白    屏(休眠)以保护墨水屏,天气相关信息每12小时更新一次。天气信息来源于爬取的内容,每天用cron(定时任务)定时爬取,爬取时间在墨水屏刷新前半个小时。

注意事项:根据微雪官方的说法,目前只有黑白墨水屏支持局部刷新,可以做时钟显示,其他三色屏是不支持局部刷新的。这个2.9寸的屏局部刷新是0.2秒,全部刷新是2秒。而且墨水屏最好24小时要全刷一次,不然会留残影,严重导致屏幕不可修复的损坏,所以我设定是每天全刷新两次。原程序代码设定,全刷时,屏幕闪两次黑屏两次,局刷没有明显变化。

改进计划:

  1.  显示不同地区的基本天气。
  2. 定制闹钟并显示。
  3. 红外感应没人时屏幕休眠或显示其他内容。
  4. 手机控制屏幕显示不同内容(高级功能)。
  5. 语音控制显示内容(高级功能)。

硬件及软件:   

  1. 1、2.9inch e-Paper Module(微雪2.9寸墨水屏带驱动,祼屏不行。)
  2. 树梅派3B(以后想改成zero w货还没到,先用这个)
  3. raspbian系统最新版,python3.7,BCM2835, wiringPi,PIL,borax.

制作过程:

  • 硬件连接:(raspbian系统的安装及操作方法这里不讲,请自行百度)
  1. 连接派:(注意vcc是接在3.3V上,而不是5V,我也不知道接到5V上会不会坏,反正我没敢)

        对应引脚

      图片不是3B的不过一样,对应40脚的图.

        2、开启派SPI口。

  • 软件安装:(raspbian系统的安装及操作方法这里不讲,请自行百度)
  1. 相关库下载及安装:说明:由于我是在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()

    需安装 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目录,我本来是想放在主程序同目录的,目前还不知道怎么改。

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

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

 

  • 5
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kim5659

你的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值