【得捷电子Follow me第2期】任务1-5的完成思路及代码方面的建议
[复制链接]
0.视频
follow me 3
1.介绍任务
大家好,我是TaoEngine,来自于安徽工业大学的一个专业与爱好不对口的软硬件爱好者。很高兴能参加本次由得捷电子举办的Follow me第2期活动。
在此我想先说抱歉,因为物流地址变迁的原因,导致物流进展缓慢,我直到10月末才收到货,而后又因为学校的安排及考试耽误到现在才完成任务并提交。在此非常感谢社区仍为我提供了这次机会!
Adafruit ESP32-S3 TFT Feather是一块非常值得把玩的一块开发板。它既保留了ESP32的强大可玩性,又不同于我之前一直在玩的nodemcu,这主要体现在它搭载了很多有意思的外设:一块高清tft彩屏,一颗板载的WS2812B灯珠,以及我非常喜欢的一个可以自定义功能的按键(任务的很多地方我都用到了这个按键来定义不同功能)。此外,它搭载的circuitpython又使我眼前一亮,它采用的上传代码的方式非常现代化,分区上传的代码既让我写代码有非常高的可读性,同时又让之前用过micropython的我感觉使用方式不割裂。
下面,就让我来介绍我的任务完成情况:
2.分任务点介绍
任务1:控制屏幕显示中文(必做任务)
先上代码:
import time
import board
import digitalio
import displayio
import terminalio
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
print("ESP32-S3 Chinese Application")
#字体的加载
font = bitmap_font.load_font("zh_cn.pcf")
#字存放处
text_group = displayio.Group()
text_en = label.Label(terminalio.FONT, text="Follow Me 2", color=0xFFFFFF, x=20, y=20)
text_cn = label.Label(font, text="一起解锁开发板超能力", color=0xFFFFFF, x=20, y=20)
#先隐藏中文
text_cn.hidden = True
text_en.hidden = False
#放置字在屏幕上
text_group.append(text_cn)
text_group.append(text_en)
display = board.DISPLAY
display.show(text_group)
#定义按钮
button_switch_lang_pin= board.BUTTON
button_switch_lang = digitalio.DigitalInOut(button_switch_lang_pin)
#按钮事件
button_switch_lang.direction = digitalio.Direction.INPUT
button_switch_lang.pull = digitalio.Pull.UP
button_pressed = False
#按下按钮时
while True:
#按钮第一次被按下
if not button_switch_lang.value:
button_pressed = True
#视频介绍的消除抖动操作
time.sleep(0.1)
#按钮按下后的动作
if not button_switch_lang.value:
if text_cn.hidden and not text_en.hidden:
text_cn.hidden = False
text_en.hidden = True
elif text_en.hidden and not text_cn.hidden:
text_cn.hidden = True
text_en.hidden = False
else:
button_pressed = False
注意:这里要提供字体文件,制作方法在视频up那里讲的很清楚。
在这次任务中,我想试着用过按钮完成中,英文的切换。先前看到“跟着做”视频中,视频up讲到了字是可以通过方法 text.hidden 来隐藏或显示的。这就非常迎合我想要实现的功能。但是我后来在开发任务4的时候,发现也可以使用 display.show 的方式来实现文字的显隐。最后我总结了下,简单的显示效果用 text.hidden 非常方便,但涉及界面ui的切换,使用 display.show 会更好管理各元素
任务2:网络功能使用(必做任务)
上代码:
import os
import time
import wifi
import board
import digitalio
print("ESP32-S3 WiFi Application")
#定义按钮
button_switch_ap_wifi_pin= board.BUTTON
button_switch_ap_wifi = digitalio.DigitalInOut(button_switch_ap_wifi_pin)
#按钮事件
button_switch_ap_wifi.direction = digitalio.Direction.INPUT
button_switch_ap_wifi.pull = digitalio.Pull.UP
button_pressed = False
#现在启动的WiFi模式
wifi_ap_mode = "wifi"
print("Press button to switch to wifi mode or AP mode")
#按下按钮时
while True:
#按钮第一次被按下
if not button_switch_ap_wifi.value:
button_pressed = True
#视频介绍的消除抖动操作
time.sleep(0.1)
#按钮按下后的动作
if not button_switch_ap_wifi.value:
#WiFi模式的操作
if wifi_ap_mode == "wifi":
wifi_ap_mode = "ap"
#停止AP
wifi.radio.stop_ap()
print("="*3+"wifi mode"+"="*3+"\nAvailable WiFi:")
#列出可用的WiFi
for network in wifi.radio.start_scanning_networks():
print(str(network.ssid, "utf-8"))
#省电,停用WiFi搜索
wifi.radio.stop_scanning_networks()
#连上WiFi
print("Connecting to " + os.getenv('WIFI'))
wifi.radio.connect(os.getenv('WIFI'), os.getenv('PWD'))
#连上就停止阻塞
print("Connected!")
#AP模式的操作
elif wifi_ap_mode == "ap":
wifi_ap_mode = "wifi"
print("="*3+"AP mode"+"="*3+"\nSSID ESP32\nPWD 12345678\n")
#打开热点
wifi.radio.start_ap("ESP32", "12345678")
print("AP started!")
#开关没有按下
else:
button_pressed = False
注意:这个代码需要在“settings.toml”填上“WIFI”和“PWD”,分别是WiFi名称和密码。
这里我想说下它这个WiFi连接方式和我之前用micropython的区别:micropython连上WiFi后可以直接提供套接字来给其他的库使用,但据我了解,micropython只能创建一个套接字。而在circuitpython中,连上网络是要采用socketpool这一新方法来在一个pool池子中获取套接字。这一方法看似复杂了些,但是这样能防止其他程序共用一个套接字而导致的网络混乱。虽然我不知道这样理解对不对,但是在micropython中我着实遇到了套接字被某一个函数占用后其他功能不能运行的bug。这确实能提高真的到了生产环境后程序的可靠性,我还是挺喜欢这种机制的。
任务3:控制WS2812B(必做任务)
继续上代码:
import time
import board
import digitalio
import neopixel
print("ESP32-S3 WS2812B Application")
#定义按钮
button_switch_WS2812B_pin= board.BUTTON
button_switch_WS2812B = digitalio.DigitalInOut(button_switch_WS2812B_pin)
#按钮事件
button_switch_WS2812B.direction = digitalio.Direction.INPUT
button_switch_WS2812B.pull = digitalio.Pull.UP
button_pressed = False
#定义灯
pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
pixel.brightness = 0.3
#选择的颜色
COLORS = [
(255,0,0),#红
(0,255,0),#绿
(0,0,255),#蓝
]
#灯模式
color_list = 0
color = COLORS[color_list]
print("Press button to switch WS2812B")
#按下按钮时
while True:
#按钮第一次被按下
if not button_switch_WS2812B.value:
button_pressed = True
#视频介绍的消除抖动操作
time.sleep(0.1)
#按钮按下后的动作
if not button_switch_WS2812B.value:
color_list += 1
if color_list == 3:
color_list -= 3
#换颜色
color = COLORS[color_list]
pixel.fill(color)
else:
button_pressed = False
任务4:从下方5个分任务中选择1个感兴趣的完成即可(必做任务)
我选的是分任务1:日历&时钟(没时间学习其他扩展模块的对应功能了,不然我还是更倾向于做下面的分任务的)。
这次代码比较复杂,我会讲下其重点的代码部分:
import rtc
import ssl
import time
import wifi
import board
import digitalio
import displayio
import socketpool
import terminalio
import adafruit_ntp
import adafruit_requests
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
print("ESP32-S3 Calendar&Clock Application")
#字体的加载
font = bitmap_font.load_font("zh_cn.pcf")
#布局存放处
text_group = displayio.Group()
text_title = label.Label(font, text="现在是", color=0xFFFFFF, x=10, y=10)
text_group.append(text_title)
text_weather = label.Label(font, text="天气:?", color=0xFFFFFF, x=195, y=10)
text_group.append(text_weather)
text_calendar_year = label.Label(font, text="1900", color=0xFFFFFF, x=10, y=40)
text_group.append(text_calendar_year)
text_calendar = label.Label(font, text="01/01", color=0xFFFFFF, x=42, y=60, scale=5)
text_group.append(text_calendar)
text_calendar_nongli = label.Label(font, text="农历?年", color=0xFFFFFF, x=60, y=116)
text_group.append(text_calendar_nongli)
text_clock_day = label.Label(font, text="01/01", color=0xFFFFFF, x=50, y=10)
text_group.append(text_clock_day)
text_clock = label.Label(font, text="00:00", color=0xFFFFFF, x=42, y=60, scale=5)
text_group.append(text_clock)
#显示隐藏日历功能
def show_calendar(state:bool):
text_calendar_year.hidden = not state
text_calendar.hidden = not state
text_calendar_nongli.hidden = not state
#显示隐藏时钟功能
def show_clock(state:bool):
text_clock_day.hidden = not state
text_clock.hidden = not state
#定义屏幕
display = board.DISPLAY
#关闭显示
show_calendar(False)
show_clock(False)
#定义按钮
button_switch_calendar_clock_pin= board.BUTTON
button_switch_calendar_clock = digitalio.DigitalInOut(button_switch_calendar_clock_pin)
#按钮事件
button_switch_calendar_clock.direction = digitalio.Direction.INPUT
button_switch_calendar_clock.pull = digitalio.Pull.UP
button_pressed = False
#True就是启动日历
calendar_clock = True
#第一次按按钮
first_button = False
#连上WiFi
print("Connecting to " + os.getenv('WIFI'))
wifi.radio.connect(os.getenv('WIFI'), os.getenv('PWD'))
#连上就停止阻塞
print("Connected!")
#调用ntp更新时间
print("Updating time...")
while True:
try:
pool = socketpool.SocketPool(wifi.radio)
ntp = adafruit_ntp.NTP(pool, tz_offset=-4)
rtc.RTC().datetime = ntp.datetime
print("Updated!")
break
except:
print("Fail to update time, retry...")
time.sleep(1)
print("Press button to enter to calendar and clock...")
#一些api,包括农历获取,天气获取,还包含了防止过度访问api的保护措施
URL_nongli = "https://api.ahfi.cn/api/nlrq"
get_nongli = 6001
URL_tianqi = "https://www.haotechs.cn/ljh-wx/weather?adcode=340500"
get_tianqi = 3001
pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())
#按下按钮时
while True:
#按钮第一次被按下
if not button_switch_calendar_clock.value:
button_pressed = True
#视频介绍的消除抖动操作
time.sleep(0.1)
#按钮按下后的动作
cal = rtc.RTC().datetime
year = cal[0]
text_calendar_year.text = str(year)
if not button_switch_calendar_clock.value:
if not first_button:
#显示屏幕
display.show(text_group)
first_button = True
#预先响应界面
response = requests.get(URL_nongli)
text_calendar_nongli.text = response.json()["data"]["lunar"]
response = requests.get(URL_tianqi)
text_weather.text = "天气:"+response.json()["result"]["weather"]
if calendar_clock:
show_calendar(True)
show_clock(False)
#ntp获取日期
while True:
try:
cal = rtc.RTC().datetime
year = cal[0]
mon = cal[1]
day = cal[2]
hour = cal[3]
minute = cal[4]
text_calendar.text = str(mon)+"/"+str(day)
time.sleep(0.1)
if get_nongli >= 6000:
#十分钟更新一次
response = requests.get(URL_nongli)
text_calendar_nongli.text = response.json()["data"]["lunar"]
get_nongli = 0
if get_tianqi >= 3000:
#五分钟更新一次
response = requests.get(URL_tianqi)
text_weather.text = "天气:"+response.json()["result"]["weather"]
get_tianqi = 0
get_nongli += 1
get_tianqi += 1
except:
pass
if not button_switch_calendar_clock.value:
break
else:
show_calendar(False)
show_clock(True)
#ntp获取时间
while True:
try:
cal = rtc.RTC().datetime
year = cal[0]
mon = cal[1]
day = cal[2]
hour = cal[3]
minute = cal[4]
text_clock_day.text = str(mon)+"/"+str(day)
text_clock.text = str(hour+12)+":"+str(minute)
time.sleep(0.1)
if get_nongli >= 6000:
#十分钟更新一次
response = requests.get(URL_nongli)
text_calendar_nongli.text = response.json()["data"]["lunar"]
get_nongli = 0
if get_tianqi >= 3000:
#五分钟更新一次
response = requests.get(URL_tianqi)
text_weather.text = "天气:"+response.json()["result"]["weather"]
get_tianqi = 0
get_nongli += 1
get_tianqi += 1
except:
pass
if not button_switch_calendar_clock.value:
break
calendar_clock = not calendar_clock
else:
button_pressed = False
注意:这里要提供字体文件,制作方法在视频up那里讲的很清楚。
注意:这个代码需要在“settings.toml”填上“WIFI”和“PWD”,分别是WiFi名称和密码。
注意:我使用的是免费API,不保证其可用性和可靠性,如果不能用了请自行修改。
这个代码我主要用到了ntp功能来获取网络时间(由adafruit提供的ntp模块个人感觉不是怎么太稳定,建议adafruit能不能添加一个可以自定义ntp服务器的功能)。网上关于使用circuitpython连接ntp服务器的教程很少,我本来是想自己写一个实现连接ntp服务器并获取时间的功能,但奈何自己技术有限不能有效格式化服务器推送的时间。最后我找到了adafruit官方能够实现该功能的核心代码(这里需要在lib文件夹里拷一个adafruit_ntp.mpy文件)。
while True:
try:
pool = socketpool.SocketPool(wifi.radio)
ntp = adafruit_ntp.NTP(pool, tz_offset=-4)
rtc.RTC().datetime = ntp.datetime
print("Updated!")
break
except:
print("Fail to update time, retry...")
time.sleep(1)
...
datetime = rtc.RTC().datetime
这里需要让ESP32的内置时钟同步ntp时间后,用python内置的标准方法 time.datetime 格式化时间(即 rtc.RTC().datetime )即可。
然后是对API的JSON数据格式转为dict字典的方法:
response = requests.get(URL_nongli)
text_calendar_nongli.text = response.json()["data"]["lunar"]
我本来是准备想找到能够解释JSON格式的库,后来从别人的实例学习发现request方法自带解析JSON功能(其实我还是希望这个方法还是独立比较好)。
最后,字体是可以通过scale参数进行放大的,但是放大倍速越大,像素化现象越明显,如果想要消除这种现象,建议还是生成一个像素大小比较小的字体(我用的是10pt),text_group也能通过scale放大,同样有像素化问题。
任务5:通过网络控制WS2812B(可选任务,非必做)
这里我将使用巴法云平台将ESP32与米家连接起来并使用米家控制灯的颜色,基于任务2改写而来。
import os
import ssl
import time
import wifi
import board
import neopixel
import displayio
import terminalio
import socketpool
import adafruit_imageload
from adafruit_display_text import label
import adafruit_minimqtt.adafruit_minimqtt as MQTT
print("ESP32-S3 mqtt MIJIA Application")
#连上WiFi
print("Connecting to " + os.getenv('WIFI'))
wifi.radio.connect(os.getenv('WIFI'), os.getenv('PWD'))
#连上就停止阻塞
print("Connected!")
print("="*3+"Please enter to MIJIA"+"="*3)
time.sleep(3)
#定义灯
pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
pixel.brightness = 0.3
#显示“已接入米家”
MIJIAimg, palette = adafruit_imageload.load("mijia.bmp", bitmap=displayio.Bitmap, palette=displayio.Palette)
tile_grid = displayio.TileGrid(MIJIAimg, pixel_shader=palette)
#显示状态
CMD = label.Label(terminalio.FONT, text="Ready", color=0xFFFFFF, x=20, y=120)
#放置画布
group = displayio.Group()
group.append(tile_grid)
group.append(CMD)
board.DISPLAY.show(group)
#定义mqtt客户端
def connect(mclient, userdata, flags, rc):
#连上mqtt后
mclient.subscribe(os.getenv('SUBSCRIBE_NAME'))
def message(mclient, feed_id, payload):
#接收到消息后
global CMD
if payload == "on":
#灯变红色
pixel.fill((255,0,0))
CMD.color = 0xFF0000
CMD.text = "Red Light"
elif payload == "on#100":
#灯变绿色
pixel.fill((0,255,0))
CMD.color = 0x00FF00
CMD.text = "Green Light"
elif payload == "on#1":
#灯变蓝色
pixel.fill((0,0,255))
CMD.color = 0x0000FF
CMD.text = "Blue Light"
elif payload == "off":
#关灯
pixel.fill((0,0,0))
CMD.color = 0x000000
CMD.text = "Ready"
pool = socketpool.SocketPool(wifi.radio)
mqtt_client = MQTT.MQTT(
broker="bemfa.com",
port=9501,
client_id=os.getenv('ID'),
username="",
password="",
socket_pool=pool,
ssl_context=ssl.create_default_context(),
)
#连上mqtt绑定的请求
mqtt_client.on_connect = connect
mqtt_client.on_message = message
#连接mqtt
CMD.color = 0xFF0000
CMD.text = "Connecting to MQTT Cloud..."
mqtt_client.connect()
CMD.color = 0x00FF00
CMD.text = "Connected!"
#一些主进程
while True:
try:
mqtt_client.loop()
except:
CMD.color = 0xFF0000
CMD.text = "Failed! Retry to connect..."
mqtt_client.connect()
CMD.color = 0x00FF00
CMD.text = "Connected!"
注意:这个代码需要在“settings.toml”填上“WIFI”,“PWD”,“ID”和“SUBSCRIBE_NAME”,分别是WiFi名称,密码,用户私钥和订阅名单。
注意:我使用的是巴法云API,需要自己注册,点击进入巴法云mqtt控制台(非恰饭内容)。
点击这里看接入教程
这里我想讲一下mqtt接入代码里非常关键的一步。
mqtt_client = MQTT.MQTT(
broker="bemfa.com",
port=9501,
client_id="用户私钥",
username="",
password="",
socket_pool=pool,
ssl_context=ssl.create_default_context(),
)
巴法云API的mqtt接入是不需要用户名和密码的,但网上关于circuitpython的mqtt接入教程大多数都是用的adafruit.io接入的,所以client_id常常被博主忽略。这里需要将巴法云给的用户私钥填入client_id中(就这步我踩坑了)
这一次,ESP32已接入米家(
(代码中的mijia.bmp,保存在分区中的根目录即可)
3.心得体会
这次Follow me第2期活动让我受益很多,让我了解到一个新的开发体系circuitpython,并让我在了解这一全新的开发平台的同时学到了很多新工具,这让我锻炼自己编程能力的同时,又增强了我对代码的理解能力和提出新解决方案的能力。感谢得捷电子,为我提供了一个能够让我了解到新技术和提升能力的一个平台。
附:任务代码
|