【得捷电子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图贴到黑色背景图上,再转,这样效果会好很多。
效果展示:
任务视频演示
任务源码
|