【得捷电子Follow me第2期】 纳尼还要任务汇总2
贪图小便宜,跑来参加白嫖活动,没想到到是苦力活,下次不来了
本期项目包含:
完成屏幕的控制,并且能显示中文
搭配器件:Adafruit ESP32-S3 TFT Feather
1、用FontForge造一个点阵字库。
2、circuitpython写代码
#adafruit_display_text/
#adafruit_bitmap_font/
import board
import displayio
import terminalio
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
import time
WIDTH=240
HEIGHT=135
lcd=board.DISPLAY
cv=displayio.Group()
print('font loading...')
st=time.monotonic()
font=bitmap_font.load_font('font/LiSu-32.pcf')
print('font loaded! %fms'%((time.monotonic()-st)*1000))
fb=font.get_bounding_box()
print('font bounding box (w, h, xoffset, yoffset): ', fb)
s0='年月日时分秒晴多云晴间多云晴间多云大部多云大部多云阴阵雨雷阵雨雷阵雨伴有冰雹小雨中雨大雨暴雨大暴雨特大暴雨冻雨雨夹雪阵雪小雪中雪大雪暴雪浮尘扬沙沙尘暴强沙尘暴雾霾风大风飓风热带风暴龙卷风冷热未知℃。'
s0=list(set([i for i in s0]))
s0.sort()
s0=''.join(s0)
s1=''.join([chr(i) for i in range(33,48)])
s2=''.join([chr(i) for i in range(48,65)])
s3=''.join([chr(i) for i in range(65,128)])
print('word count: ',len(s0)+len(s1)+len(s2)+len(s3))
print('label creating...')
st=time.monotonic()
#lb0=label.Label(terminalio.FONT, text=s, color=0xffffff)
lb0=label.Label(font, text=s0, color=0xffffff)
lb1=label.Label(font, text=s0, color=0x7f7f7f)
lb2=label.Label(font, text=s1, color=0xaaffaa)
lb3=label.Label(font, text=s2, color=0xaaaaff)
lb4=label.Label(font, text=s3, color=0xffaaaa)
print('label created! %fms'%((time.monotonic()-st)*1000))
print('label bounding box (x, y, w, h)', lb0.bounding_box)
box=lb0.bounding_box
print('lb0 bounding box (x, y, w, h)', box)
gp1=displayio.Group(scale=1, x=0, y=0)#, x=(lcd.width-lb0.width*scale)//2, y=(lcd.height)//2)
gp1.append(lb1)
gp1.append(lb0)
gp1.append(lb2)
gp1.append(lb3)
gp1.append(lb4)
cv.append(gp1)
lb1.x=lcd.width
lb0.x=lb0.x+1
lb0.y=-box[1]
lb1.y=lb0.y+1
lb2.y=fb[1]+5
lb2.x=lcd.width
lb3.y=(fb[1]+5)*2
lb3.x=lcd.width
lb4.y=(fb[1]+5)*3
lb4.x=lcd.width
c0=0
c1=0
c2=0
lcd.root_group=cv
while True:
lb0.x-=2
lb1.x=lb0.x+1
if lb0.x<-lb0.width:
lb0.x=lcd.width
lb2.x-=20
if lb2.x<-lb2.width:
lb2.x=lcd.width
lb3.x-=10
if lb3.x<-lb3.width:
lb3.x=lcd.width
lb4.x-=4
if lb4.x<-lb4.width:
lb4.x=lcd.width
c0+=3
c1+=7
c2+=11
if c0>255:
c0-=255
if c1>255:
c1-=255
if c2>255:
c2-=255
lb0.color=((c0<<16) + (c1<<8) + c2)
time.sleep(0.008)
使用adafruit_bitmap_font模块用来加载点阵字库文件
使用adafruit_display_text模块创建label用来显示点阵字
大体流程如下:
#displayio.release_displays()
#spi=board.SPI()
#lcd_bus=displayio.FourWire(spi, command=board.TFT_DC, chip_select=board.TFT_CS)
#lcd=ST7789(lcd_bus, rotation=270, width=WIDTH, height=HEIGHT, rowstart=40, colstart=53)
#用默认的display,省去重新创建一个,还省去引用ST7789的库
lcd=board.DISPLAY
#加载字体
font=bitmap_font.load_font('font/LiSu-32.pcf')
#创建label对象,需要字体
lb0=label.Label(font, text=s0, color=0xffffff)
#建个组,主要用来放文本,并对文本进行缩放
gp1=displayio.Group(scale=1, x=0, y=0)
#label放到组里
gp1.append(lb0)
#再建个组,用来在屏幕直接显示的对象
cv=displayio.Group()
#把gp1组加到cv组里
cv.append(gp1)
#在屏幕显示cv组
lcd.root_group=cv
完成网络功能的使用,能够创建热点和连接到WiFi
搭配器件:Adafruit ESP32-S3 TFT Feather
1、直接写代码
import wifi
import socketpool
import struct
import time
import rtc
import ipaddress
import digitalio
import board
try:
from secrets import secrets
except ImportError:
print("need secrets.py, please add them!")
raise
#NTP_ADDR = "ntp.ntsc.ac.cn"
NTP_ADDR = "ntp.tencent.com"
#NTP_ADDR = "ntp3.aliyun.com"
NTP_PORT = 123
UTC = 8
HOT_POINT_SSID = "MY-ESP32-S3"
HOT_POINT_PASSWORD = "12345678"
HOT_POINT_IP = "192.168.111.1"
#扫描wifi,这里用不上,只是展示一下。
for network in wifi.radio.start_scanning_networks():
print("\t%s\t\tRSSI: %d\tChannel: %d"%(str(network.ssid, "utf-8"), network.rssi, network.channel))
wifi.radio.stop_scanning_networks()
print("wifi conneting...")
#连接secrets里面指定的wifi
wifi.radio.connect(secrets["ssid"],secrets["password"])
pool = socketpool.SocketPool(wifi.radio)
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
t = time.localtime(t)
return t
def startHotPoint():
print("wifi hot point starting...")
ip=ipaddress.IPv4Address(HOT_POINT_IP)
mask=ipaddress.IPv4Address("255.255.255.0")
gateway=wifi.radio.ipv4_gateway
wifi.radio.set_ipv4_address_ap(ipv4=ip, netmask=mask, gateway=gateway)
wifi.radio.start_ap(HOT_POINT_SSID, HOT_POINT_PASSWORD)
print("Hot Point: ", wifi.radio.ap_active)
print("SSID: ", HOT_POINT_SSID)
print("Password: ", HOT_POINT_PASSWORD)
print("ap gateway: ", wifi.radio.ipv4_gateway_ap)
print("ap subnet: ", wifi.radio.ipv4_subnet_ap)
print("ap ipv4: ", wifi.radio.ipv4_address_ap)
t = getNTPTime(pool)
rtc.RTC().datetime=t
t0 = time.localtime(time.time())
btn=digitalio.DigitalInOut(board.BOOT0)
btn.switch_to_input(digitalio.Pull.UP)
isHotPointStarted = False
while True:
if not btn.value:
if not isHotPointStarted:
startHotPoint()
isHotPointStarted=True
else:
wifi.radio.stop_ap()
isHotPointStarted=False
print("Hot point stopped!")
time.sleep(1)
t1 = time.localtime(time.time())
if t1.tm_sec!=t0.tm_sec:
t0=t1
print("%d-%02d-%02d %02d:%02d:%02d"%(t1.tm_year, t1.tm_mon, t1.tm_mday, t1.tm_hour + UTC, t1.tm_min, t1.tm_sec))
time.sleep(0.2)
连接wifi只要这么一句代码:wifi.radio.connect(secrets["ssid"],secrets["password"])
创建wifi热点的功能只要这么一句代码:wifi.radio.start_ap(HOT_POINT_SSID, HOT_POINT_PASSWORD)
扫描wifi可以用这一句代码:wifi.radio.start_scanning_networks()
完成一个可通过互联网更新的万年历时钟,并显示当地的天气信息
搭配器件:Adafruit ESP32-S3 TFT Feather
结合任务1,任务2,就能做日历时钟了。
直接上代码:
#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、先通过百度获取当前连接的外网ip,跟所在的城市经纬度,还有时区。
2、通过心知天气用经纬度查天气信息。
3、用socket通过NTP协议获取NTP网络标准时间。
4、画只小狗秀一下
使用按键控制板载Neopixel LED的显示和颜色切换
搭配器件:Adafruit ESP32-S3 TFT Feather,两个电位计,一些杜邦线
#用两个电位计控制neopixel RGB彩灯
import time
import analogio
import digitalio
import board
import neopixel
from rainbowio import colorwheel
rx=analogio.AnalogIn(board.A0)
ry=analogio.AnalogIn(board.A1)
led=neopixel.NeoPixel(board.NEOPIXEL, 1)
def getV(pin):
return pin.value/61000
def getHue():
return colorwheel(getV(rx)*255)
def getBrightness():
v = round(getV(ry),2)
return v
while True:
hue=getHue()
brightness=getBrightness()
led[0]=hue
led.brightness=brightness
print((hue, brightness))
time.sleep(0.02)
获取电位计的值,然后一个控制亮度,一个再转hue值控制色度。
在手机上通过网络控制板载Neopixel LED的显示和颜色切换,屏幕同步显示状态
搭配器件:Adafruit ESP32-S3 TFT Feather
#用httpserver模块建个web服务器,mywebsite是服务器根目录,随后的文件访问都会被自动改到在该目录下进行。
server=Server(pool, 'mywebsite', debug=True)
server.start(HOST, PORT)
#添加”/“的访问响应,重定向到直接读取index.html。客户端浏览器会自动访问index.html。
@server.route('/', METHODS.GET)
def rootRedirect(req):
return Redirect(req, '/index.html', permanent=True)
#在添加个‘/rgb’的响应,把普通http请求的socket升级成websocket,
请求头如果带了Sec-WebSocket-Protocol,需要回复一下,不然升级不了。当然也可以在客户端发请求时不带这个请求头,就不用回复了
只运行一个websocket连接,所以后面加了些判断是否已经存在连接,如果是,就关掉原来的连接。
@server.route('/rgb', METHODS.GET)
def upgradeWS(req, *args):
h=Headers()
if 'Sec-WebSocket-Protocol' in req.headers:
h.update({'Sec-WebSocket-Protocol':'json'})
global ws
if ws is not None:
ws.close()
ws=Websocket(req, h)
return ws
中间一堆到while循环那里,就是一些led设置啊,按键设置啊,屏幕显示啊,就不说了。
#服务器轮询,websocket也是轮询,websocket收到数据就解析成json,先判断是否有‘get’请求,有就回复当前led颜色,没有就提取rgb,存入ledcolor全局变量,
没直接更新led,因为直接更新led会耗时间,websocket请求太快的话,circuitpython好像收到一帧信息不立即返回,而是卡住收一段时间,效果不是很好,所以
我设了个30帧的更新频率,这样客户端快速操作时,esp32也可以快速响应。
while True:
server.poll()
if ws is not None:
try:
if (data:=ws.receive()) is not None:
try:
data = json.loads(data)
if(g:=data.get('get')) is not None:
ws.send_message(json.dumps({'rgb':ledcolor}))
elif(rgb:=data.get('rgb')) is not None:
#print(rgb)
if type(rgb)==int:
need2update = True
ledcolor=rgb
except:
pass
except:
ws.close()
ws=None
......
et=time.monotonic()
dt = et-st
st=et
fps-=dt
#30帧更新频率, 60帧有点快,偶尔卡一下
if(fps<0):
fps=1/30
if need2update:
need2update=False
setLedColor(ledcolor)
if btnv0==True and btnv1==False:
switchDisplay()
btnv0=btnv1
......
对本活动的心得体会:
活动还是可以的,上手比较容易,就是任务要求不太清楚,害我发了一次又一次的贴子,下次不来了
下面是各个任务源码汇总: