812|2

10

帖子

6

TA的资源

一粒金砂(中级)

楼主
 

【得捷电子Follow me第2期】任务4:分任务1:日历&时钟 [复制链接]

  本帖最后由 青蛙2009 于 2023-9-4 23:16 编辑

【得捷电子Follow me第2期】任务4:分任务1:日历&时钟

完成一个可通过互联网更新的万年历时钟,并显示当地的天气信息。

接线:

编程语言和环境用CircuitPython:

开发板:

Adafruit Feather ESP32-S3 TFT

运行环境:

Adafruit CircuitPython 8.2.3 on 2023-08-11

编辑器:

mu-editor

用到的额外模块:

adafruit_bitmap_font

adafruit_display_text

adafruit_requests.mpy
adafruit_ticks.mpy

还需要在code.py同目录下创建一个secrets.py,存放要连接的wifi名称跟密码。

代码解析:

以下是secrets.py文件完整代码:(相应位置要改成自己的wifi名称跟密码,其他几个暂时没用上)

secrets={
    "ssid":"要连接的wifi名称",
    "password":"要连接的wifi密码",
    'aio_username' : 'adafruit io账号',# 暂时用不上
    'aio_key' : 'adafruit io API key',# 暂时用不上
    'weather_key': 'StmLNJLbxIN0acv7f', #心知天气api key
    'timezone' : "America/New_York", # http://worldtimeapi.org/timezones
}

以下是code.py文件完整代码:

#adafruit_display_text/
#adafruit_bitmap_font/
#adafruit_requests.mpy
#adafruit_ticks.mpy
import board
import displayio
import terminalio
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
import time
import ipaddress
import ssl
import wifi
import socketpool
import adafruit_requests
import asyncio
import rtc
import struct

try:
    from secrets import secrets
except ImportError:
    print("need secrets.py, please add them!")
    raise

#百度获取当前ip所在地址
URL_LOCATION="https://qifu-api.baidubce.com/ip/local/geo/v1/district?"
#心知天气获取指定经纬度天气预报 第一个%s是纬度,第二个%s是经度
URL_WEATHER = "http://api.seniverse.com/v3/weather/now.json?key=%s&location=%s:%s&language=zh-Hans&unit=c"
NTP_ADDR = "ntp.tencent.com"
NTP_PORT = 123

print("wifi conneting...", secrets["ssid"])
wifi.radio.connect(secrets["ssid"],secrets["password"])

pool=socketpool.SocketPool(wifi.radio)
sslCtx=ssl.create_default_context()

utc=0

#获取经纬度,顺便获取到时区
def getLocation():
    requests=adafruit_requests.Session(pool, sslCtx)
    print("current IP city getting...")
    response=requests.get(URL_LOCATION)
    json=response.json()
    data=json["data"]
    lng=data["lng"]
    lat=data["lat"]
    print(json)
    print("ip: %s"%(json["ip"],))
    print("city:%s %s %s"%(data["prov"],data["city"],data["district"]))
    print("lng: %s, lat: %s"%(lng,lat))
    global utc
    utc=int(data['timezone'][3:])
    return (lng, lat)

#获取天气信息
def getWeather(lng, lat):
    requests=adafruit_requests.Session(pool, sslCtx)
    url = URL_WEATHER%(secrets["weather_key"], lat, lng)
    print("weather getting...: ", url)
    response=requests.get(url)
    json=response.json()
    print(json)
    return json

def getNTPTime(pool):
    print("getting network time...")
    sc = pool.socket(socketpool.SocketPool.AF_INET, socketpool.SocketPool.SOCK_DGRAM)
    sc.settimeout(3)
    #网上抄的NTP请求包
    #data = struct.pack("!BBBBIIIQQQQ",3<<6|3<<3|3,1,10,1,1,10,0,0,0,0,0)
    data = struct.pack("!BBBBIIIQQQQ",0x1b,0,0,0,0,0,0,0,0,0,0)
    buf=bytearray(48)
    print("ntp recving...")
    sc.sendto(data, (NTP_ADDR, NTP_PORT))
    t0=time.time()
    n,addr=sc.recvfrom_into(buf)
    sc.close()
    print("recv: ",n,addr)
    TIME1970  = 0x83aa7e80
    t = struct.unpack("!12I", buf)[10]
    t1=time.time()
    print("used: ",t1-t0)
    t -= TIME1970
    return t

tileWidth=60
tileHeight=59

lcd=board.DISPLAY
cv=displayio.Group()

bmpWeather=displayio.OnDiskBitmap("/img/tile.bmp")
#bmpWeather.pixel_shader.make_transparent(2)
tileWeather=displayio.TileGrid(bmpWeather, pixel_shader=bmpWeather.pixel_shader,
                               tile_width=tileWidth, tile_height=tileHeight)
tileWeather.x=lcd.width-tileWidth-5
tileWeather.y=5#(lcd.height-tileHeight)//2
maxInd=tileWeather.bitmap.width//tileWidth
tileWeather[0]=maxInd-1
cv.append(tileWeather)

animWidth=72
animHeight=50
bmpAnim=displayio.OnDiskBitmap("/img/anim.bmp")
#bmpWeather.pixel_shader.make_transparent(2)
tileAnim=displayio.TileGrid(bmpAnim, pixel_shader=bmpAnim.pixel_shader,
                               tile_width=animWidth, tile_height=animHeight)
tileAnim.x=15
tileAnim.y=lcd.height-animHeight-2
animMaxInd=tileAnim.bitmap.width//animWidth
tileAnim[0]=animMaxInd-1



print('font loading...')
st=time.monotonic()
font=bitmap_font.load_font('font/LiSu-24.pcf')
fontNumBig=bitmap_font.load_font('font/Jokerman-Regular-58.pcf')
fontNumSmall=bitmap_font.load_font('font/Jokerman-Regular-24.pcf')
print('font loaded! %fms'%((time.monotonic()-st)*1000))
print('font bounding box (w, h, xoffset, yoffset): ', font.get_bounding_box())
print('fontNumBig bounding box (w, h, xoffset, yoffset): ', fontNumBig.get_bounding_box())
print('fontNumSmall bounding box (w, h, xoffset, yoffset): ', fontNumSmall.get_bounding_box())

lbDate=label.Label(font, text='2023年10月1日', color=0x444444)
lbTime=label.Label(fontNumBig, text='22:38', color=0xaaffff)
lbSec=label.Label(fontNumBig, text='45', color=0x001111)
lbWeather=label.Label(font, text='未知', color=0xaa00aa)
bmpMask=displayio.Bitmap(lcd.width-lbWeather.width-10, lbWeather.height+2,1)
palette=displayio.Palette(1)
palette[0]=0
weatherMask=displayio.TileGrid(bmpMask, pixel_shader=palette, x=0)
lbTemp=label.Label(font, text='28℃', color=0xaaaa00)

lbDate.x=5
lbDate.y=15
lbTime.x=5
lbTime.y=65
lbSec.y=105
lbSec.x=lbTime.width-60
lbWeather.x = lcd.width - lbWeather.width - 5
lbWeather.y=tileWeather.y+tileHeight+10
weatherMask.x=0
weatherMask.y=lbWeather.y-lbWeather.bounding_box[3]-lbWeather.bounding_box[1]-1
print(lbWeather.bounding_box)
lbTemp.x=lcd.width-lbTemp.width-10
lbTemp.y=lbWeather.height + lbWeather.y + 10

cv.append(lbWeather)
cv.append(weatherMask)
cv.append(tileAnim)

cv.append(lbDate)
cv.append(lbSec)
cv.append(lbTime)
cv.append(lbTemp)

lcd.root_group=cv


async def updateTime():
    while True:
        print('time updating... ')
        while True:
            try:
                t = getNTPTime(pool)
                break;
            except:
                print('ntp time get failed, retry 10s later!')
                #10s后重试
                await asyncio.sleep(10)
        rtc.RTC().datetime=time.localtime(t + utc*3600)
        await asyncio.sleep(24*60*60)

movdir=-3
async def updateWeather():
    while True:
        print('weather updating... ')
        while True:
            try:
                lng,lat = getLocation()
                break
            except:
                print('location get failed, retry 10s later!')
                await asyncio.sleep(10)
        while True:
            try:
                weather = getWeather(lng, lat)
                break
            except:
                print('weather get failed, retry 10s later!')
                await asyncio.sleep(10)

        now=weather['results'][0]['now']

        ind=int(now['code'])
        tileWeather[0]= ind if ind < maxInd else maxInd-1
        lbWeather.text='%s'%(now['text'])#'雷阵雨伴有冰雹'
        lbTemp.text='%s℃'%(now['temperature'])
        lbWeather.x = lcd.width
        global movdir
        movdir=-3
        await asyncio.sleep(30*60)

async def update():
    t0 = time.localtime(time.time())
    while True:
        t1 = time.localtime(time.time())
        if t1.tm_sec!=t0.tm_sec:
            t0=t1
            lbDate.text='%d年%02d月%02d日'%(t1.tm_year, t1.tm_mon, t1.tm_mday)
            lbTime.text='%d:%02d'%(t1.tm_hour, t1.tm_min)
            lbSec.text='%02d'%(t1.tm_sec)


        global movdir
        lbWeather.x += movdir
        if(lbWeather.width>(lcd.width-bmpMask.width)):
            if ((movdir<0 and lbWeather.x+lbWeather.width<lcd.width) or
                (movdir>0 and lbWeather.x>bmpMask.width)):
                movdir=-movdir
        else:
            if ((movdir>0 and lbWeather.x+lbWeather.width>lcd.width) or
                (movdir<0 and lbWeather.x<bmpMask.width)):
                movdir=-movdir

        tileAnim[0]=(tileAnim[0]+1)%animMaxInd
        await asyncio.sleep(0.1)




async def main():
    task2=asyncio.create_task(updateWeather())
    task1=asyncio.create_task(updateTime())
    task3=asyncio.create_task(update())
    await asyncio.gather(task1, task2, task3)

asyncio.run(main())

好多东西跟任务1任务2差不多的就不再讲了。

心知天气的API key最好还是自己注册一下用自己,免费的,不过有请求次数限制。

百度的获取经纬度不用什么key。其实心知天气的API获取天气的location是可以填IP,应该是会自动通过IP获取位置。

不过我们不知道外网IP,还是得要百度的api来获取。我没试过,大概是要这样的。

整体思路是:先通过百度api获取经纬度、地址、时区,用经纬度通过心知天气api获取天气信息,用NTP获取网络时间加上时区。

我用了异步函数,实际上只有await asyncio.sleep(xxx)这句是异步,网络请求,文件IO依旧的同步的,所以效果不大,就是方便不同定时操作而已。

网络请求失败时会10秒后自动重新请求,更新时间是每24小时更新一次,更新天气是每半个钟更新一次。

其他的一大串就是哪些花里胡哨的动态效果了。

本来想搞个旋转的太空人的,网上找了一圈,居然找不到。

就找了只小狗的gif,自己用python脚本处理了一下,反转一下颜色,转成bmp,拼成图块,省去加载gif的模块。

心知天气的图标是从它的文档里面下载下来的,png格式的,不好用,找了些加载png的模块,加载不了,最后还是用python脚本全转成bmp格式,拼成图块了。

直接png转bmp效果是不好看的,png是带半透明的,所以要先把png图贴到黑色背景图上,再转,这样效果会好很多。

效果展示:

 

任务视频演示

任务源码

最新回复

直接png转bmp效果是不好看的,png是带半透明的,所以要先把png图贴到黑色背景图上,再转,这样效果会好很多。 效果果然不错,感谢分享!   详情 回复 发表于 2023-9-5 09:39
点赞(1) 关注(1)
 
 

回复
举报

143

帖子

4

TA的资源

一粒金砂(高级)

沙发
 

优秀啊

 

 
 
 

回复

6976

帖子

11

TA的资源

版主

板凳
 

直接png转bmp效果是不好看的,png是带半透明的,所以要先把png图贴到黑色背景图上,再转,这样效果会好很多。

效果果然不错,感谢分享!

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表