【得捷电子Follow me第2期】CircuitPython入门到完成任务1-5详细教程
[复制链接]
本帖最后由 ttxiaotaoge 于 2023-8-24 11:19 编辑
本帖记录我这两天使用CircuitPython固件完成任务1-5的过程
可完成任务:
任务1:控制屏幕显示中文(必做任务)----- 完成屏幕的控制,并且能显示中文
任务2:网络功能使用(必做任务)----- 完成网络功能的使用,能够创建热点和连接到WiFi
任务3:控制WS2812B(必做任务)---- 使用按键控制板载Neopixel LED的显示和颜色切换
任务4:从下方5个分任务中选择1个感兴趣的完成即可(必做任务)
■ 分任务1:日历&时钟——完成一个可通过互联网更新的万年历时钟,并显示当地的天气信息
■ 分任务2:WS2812B效果控制——完成一个Neopixel(12灯珠或以上)控制器,通过按键和屏幕切换展示效果
任务5:通过网络控制WS2812B(可选任务,非必做)----- 结合123,在手机上通过网络控制板载Neopixel LED的显示和颜色切换,屏幕同步显示状态
先贴上实验现象给大伙看下,由于活动提供的板子还没到,只能用手头上现有的板子给大家演示,实际代码区别仅是部分IO的定义不同和显示的坐标不同,可以轻松移植
本套固件支持功能:
1. 代码运行后将自动联网,如联网成功会自动同步网络时间和获取天气信息并显示在LCD屏幕上,时间显示为经过同步的RTC内部时钟,天气地区配置在代码中设定,使用知心天气API
若未成功联网将会开启AP模式,默认为SSID为ESP32无密码。显示屏会显示当前网络模式和访问ip,同时显示本地时间(RTC内部时钟)
2. 电脑or手机在同一网络下(esp32连接同一个wifi or 连接esp32发出的wifi)访问‘ip’:1080/client 可进入WEB控制中心,该界面下可控制板载的RGB灯,控制灯光效果,LCD屏将同步显示当前RGB颜色配置。
3. 板载按键也可以控制灯光效果,同时将状态上报WEB控制中心
4. 支持web编辑界面,可直接访问IP进入相关circuitpyhon菜单。
相关演示如下:
播放器加载失败: 未检测到Flash Player,请到 安装
演示
---
代码解析
先看下代码结构,主要为lib,fonts, code.py, settings.toml
其中lib里存放了我们需要的一些外部库(来源adafruit-circuitpython-bundle-8.x-mpy-20230815)
fonts中存放了我们用到的中文字体库(格式为pcf,通过fontforge和bdftopcf font converter (adafruit.github.io)进行转换),字体生成具体方法可参考官方教程Use FontForge | Custom Fonts for CircuitPython Displays | Adafruit Learning System
code.py就是我们的实际工作代码了。
settings.toml 将宏定义我们需要连接的wifi名称和密码(注意esp32只能连接2.4G频段的wifi),以及使用web编辑器的连接密码
下面贴下我们code.py中的源代码,并对他进行初步讲解。
- import board
- import digitalio
- import terminalio
- import busio
- import neopixel
- import rtc
- import time
-
- import wifi
- import socketpool
- import ssl
- import adafruit_requests
- from adafruit_httpserver import Server, Request, Response, Websocket, GET
-
- import displayio
- from adafruit_st7735r import ST7735R
- from adafruit_bitmap_font import bitmap_font
- from adafruit_display_text.label import Label
-
-
- displayio.release_displays()
- spi = busio.SPI(board.GPIO18, board.GPIO17)
- tft_cs = board.GPIO14
- tft_dc = board.GPIO15
- display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=board.GPIO16)
- display = ST7735R(display_bus, width=128, height=160,rowstart=0,colstart=0,rotation=0,bgr = 1)
-
- my_display_group = displayio.Group()
- print("Display Init Success\n")
-
-
-
- wificonnect_State = wifi.radio.connected
- print("wificonnect_State",wificonnect_State)
- if(wificonnect_State == False):
- wifi.radio.start_ap(ssid = 'Esp32', password = '')
-
- Wifi_Mode = 'STA' if(wificonnect_State == True) else 'AP'
- Wifi_ip = wifi.radio.ipv4_address if(wificonnect_State == True) else wifi.radio.ipv4_address_ap
-
-
-
- i = 0
- Enter_flag = False
- def Button0_Work():
- global i
- global Enter_flag
- global RGBr, RGBg, RGBb
- if(Button0.value == False and Enter_flag == False):
- time.sleep(0.05)
- if(Button0.value == False and Enter_flag == False):
- i+=1
- RGBr, RGBg, RGBb = 8*i, 8*i, 8*i
- pixels.fill((RGBr, RGBg, RGBb))
- Enter_flag = True
- print("pixels change\n",i)
- if(i>20):
- i = 0
- elif(Button0.value == True and Enter_flag == True):
- time.sleep(0.05)
- if(Button0.value == True and Enter_flag == True):
- Enter_flag = False
-
-
-
- def parse_weather(weather_str):
-
- area = weather_str['results'][0]['location']['name']
- temp = weather_str['results'][0]['now']['temperature']
- weather = weather_str['results'][0]['now']['text']
- return area, temp, weather
-
-
- def parse_time(datetime_str):
-
- date_str = datetime_str.split("T")[0]
- time_str = datetime_str.split("T")[1].split(".")[0]
- hour, minute, second = map(int, time_str.split(":"))
- date = date_str
- return hour, minute, second, date
-
-
- def Update_Data():
- global r
- global current_area, current_temp, current_weather
- current_area = current_temp = current_weather = None
- r = rtc.RTC()
-
- if(wificonnect_State == True):
-
- requests = adafruit_requests.Session(pool, ssl.create_default_context())
- response_Weather = requests.get(WEATHER_DATA_SOURCE)
- data_Weather = response_Weather.json()
- response_Weather= requests.get(WEATHER_DATA_SOURCE)
- response_Weather.socket.close()
- current_area, current_temp, current_weather = parse_weather(data_Weather)
- print("WeatherUpdate Success\n")
-
-
-
- requests = adafruit_requests.Session(pool, ssl.create_default_context())
- response_clock = requests.get(TIME_DATA_SOURCE)
- data_clock = response_clock.json()
- response_clock= requests.get(WEATHER_DATA_SOURCE)
- response_clock.socket.close()
- current_hour, current_minute, current_second, current_date = parse_time(data_clock["datetime"])
- r.datetime = time.struct_time((int(current_date[0:4]), int(current_date[5:7]), int(current_date[8:10]), \
- current_hour, current_minute, current_second, 0, -1, -1))
- print("ClockTimeUpdate Success\n")
-
-
-
-
- pixel_pin = board.GPIO10
- num_pixels = 16
- pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.2, auto_write=True)
- pixels.fill((8, 8, 8))
- print("RGBDriver Init Success\n")
-
-
-
- Button0 = digitalio.DigitalInOut(board.GPIO0)
- Button0.direction = digitalio.Direction.INPUT
- print("Button0 Init Success\n")
-
-
-
- font_file = "fonts/test16-16.pcf"
-
-
-
-
- font_Chinese = bitmap_font.load_font(font_file)
- print("fonts Init Success\n")
-
-
- text_Wifi = "WiFi:{}\n{}".format(Wifi_Mode,Wifi_ip)
- text_Wifi_title = Label(terminalio.FONT, text=text_Wifi, color = 0xffffff)
- text_Wifi_title.x = 5
- text_Wifi_title.y = 5
- my_display_group.append(text_Wifi_title)
-
-
- RGBr, RGBg, RGBb = 255,255,255
- text_RGB = "RGB:{:03}:{:03}:{:03}\nKEY:{:02}".format(RGBr, RGBg, RGBb, i)
- text_RGB_title = Label(terminalio.FONT, text=text_RGB, color = (RGBr, RGBg, RGBb))
- text_RGB_title.x = 5
- text_RGB_title.y = 30
- my_display_group.append(text_RGB_title)
-
-
-
-
-
- TIME_DATA_SOURCE = "http://worldtimeapi.org/api/timezone/Asia/Shanghai"
- WEATHER_DATA_SOURCE = "http://api.seniverse.com/v3/weather/now.json?key=SBmmNRnXdiTFi8UQw&location=nanjing&language=zh-Hans&unit=c"
- pool = socketpool.SocketPool(wifi.radio)
- server = Server(pool, debug=True)
-
-
-
- websocket: Websocket = None
- next_message_time = time.monotonic()
-
- HTML_TEMPLATE = """
- <html lang="en">
- <head>
- <!--注释:强制html使用utf-8编码-->
- <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
- <title>Web控制中心</title>
- </head>
- <body>
- <p>按键状态: <strong>-</strong></p>
- <p>RGB 颜色: <input type="color"></p>
- <script>
- const Button_count = document.querySelector('strong');
- const colorPicker = document.querySelector('input[type="color"]');
-
- let ws = new WebSocket('ws://' + location.host + '/connect-websocket');
-
- ws.onopen = () => console.log('WebSocket connection opened');
- ws.onclose = () => console.log('WebSocket connection closed');
- ws.onmessage = event => Button_count.textContent = event.data;
- ws.onerror = error => Button_count.textContent = error;
-
- colorPicker.oninput = debounce(() => ws.send(colorPicker.value), 200);
- <!--注释:创建一个延时函数,消除RGB调整抖动-->
- function debounce(callback, delay = 1000) {
- let timeout
- return (...args) => {
- clearTimeout(timeout)
- timeout = setTimeout(() => {
- callback(...args)
- }, delay)
- }
- }
- </script>
- </body>
- </html>
- """
-
- @server.route("/client", GET)
- def client(request: Request):
- return Response(request, HTML_TEMPLATE, content_type="text/html")
-
- @server.route("/connect-websocket", GET)
- def connect_client(request: Request):
- global websocket
- if websocket is not None:
- print("websocket.close")
- websocket.close()
-
- websocket = Websocket(request)
- return websocket
-
-
-
- Update_Data()
-
-
-
- time_label_text = "{}-{:02}-{:02}\n"\
- "时间:{:02}:{:02}:{:02}\n"\
- "地区:{}\n"\
- "温度:{}\n"\
- "天气:{}\n".format(r.datetime.tm_year, r.datetime.tm_mon, r.datetime.tm_mday, \
- r.datetime.tm_hour, r.datetime.tm_min, r.datetime.tm_sec, \
- current_area, current_temp, current_weather)
- time_label = Label(font = font_Chinese, text=time_label_text, color = 0xff00ff)
- time_label.x = 10
- time_label.y = 60
- my_display_group.append(time_label)
-
-
-
- display.show(my_display_group)
- print("display show Success\n")
-
-
-
- if(wificonnect_State == True):
- server.start(str(wifi.radio.ipv4_address), port = 1080)
- else:
- server.start(str(wifi.radio.ipv4_address_ap), port = 1080)
-
- time.sleep(2)
- while True:
-
- server.poll()
-
- if websocket is not None:
- if (data := websocket.receive(True)) is not None:
- RGBr, RGBg, RGBb = int(data[1:3], 16), int(data[3:5], 16), int(data[5:7], 16)
- pixels.fill((RGBr, RGBg, RGBb))
-
-
- if websocket is not None and next_message_time < time.monotonic():
- time.sleep(0.2)
- if websocket is not None:
- Button_count = i
- websocket.send_message(str(Button_count))
- next_message_time = time.monotonic() + 1
-
-
-
- text_RGB_title.color = (RGBr, RGBg, RGBb)
- text_RGB_title.text = "RGB:{:03}:{:03}:{:03}\nKEY:{:02}".format(RGBr, RGBg, RGBb, i)
- time_label.text = "{}-{:02}-{:02}\n"\
- "时间:{:02}:{:02}:{:02}\n"\
- "地区:{}\n"\
- "温度:{}\n"\
- "天气:{}\n".format(r.datetime.tm_year, r.datetime.tm_mon, r.datetime.tm_mday, \
- r.datetime.tm_hour, r.datetime.tm_min, r.datetime.tm_sec, \
- current_area, current_temp, current_weather)
- Button0_Work()
-
-
-
-
-
-
-
-
第一部分,添加需要的库文件和初始屏幕,如果是使用活动开发板的同学,可以直接在初始化的地方输入 display = board.DISPLAY即可
由于我使用的ST3375驱动的160x128的屏幕,所以需要添加如下代码
- import board
- import digitalio
- import terminalio
- import busio
- import neopixel
- import rtc
- import time
-
- import wifi
- import socketpool
- import ssl
- import adafruit_requests
- from adafruit_httpserver import Server, Request, Response, Websocket, GET
-
- import displayio
- from adafruit_st7735r import ST7735R
- from adafruit_bitmap_font import bitmap_font
- from adafruit_display_text.label import Label
-
-
- displayio.release_displays()
- spi = busio.SPI(board.GPIO18, board.GPIO17)
- tft_cs = board.GPIO14
- tft_dc = board.GPIO15
- display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=board.GPIO16)
- display = ST7735R(display_bus, width=128, height=160,rowstart=0,colstart=0,rotation=0,bgr = 1)
-
- my_display_group = displayio.Group()
- print("Display Init Success\n")
-
第二部分,判断WiFi状态并设定WiFi状态
-
- wificonnect_State = wifi.radio.connected
- print("wificonnect_State",wificonnect_State)
- if(wificonnect_State == False):
- wifi.radio.start_ap(ssid = 'Esp32', password = '')
-
- Wifi_Mode = 'STA' if(wificonnect_State == True) else 'AP'
- Wifi_ip = wifi.radio.ipv4_address if(wificonnect_State == True) else wifi.radio.ipv4_address_ap
-
第三部分,构造封装一些我们需要用到的函数,为了让代码更加清晰
-
- i = 0
- Enter_flag = False
- def Button0_Work():
- global i
- global Enter_flag
- global RGBr, RGBg, RGBb
- if(Button0.value == False and Enter_flag == False):
- time.sleep(0.05)
- if(Button0.value == False and Enter_flag == False):
- i+=1
- RGBr, RGBg, RGBb = 8*i, 8*i, 8*i
- pixels.fill((RGBr, RGBg, RGBb))
- Enter_flag = True
- print("pixels change\n",i)
- if(i>20):
- i = 0
- elif(Button0.value == True and Enter_flag == True):
- time.sleep(0.05)
- if(Button0.value == True and Enter_flag == True):
- Enter_flag = False
-
-
-
- def parse_weather(weather_str):
-
- area = weather_str['results'][0]['location']['name']
- temp = weather_str['results'][0]['now']['temperature']
- weather = weather_str['results'][0]['now']['text']
- return area, temp, weather
-
-
- def parse_time(datetime_str):
-
- date_str = datetime_str.split("T")[0]
- time_str = datetime_str.split("T")[1].split(".")[0]
- hour, minute, second = map(int, time_str.split(":"))
- date = date_str
- return hour, minute, second, date
-
-
- def Update_Data():
- global r
- global current_area, current_temp, current_weather
- current_area = current_temp = current_weather = None
- r = rtc.RTC()
-
- if(wificonnect_State == True):
-
- requests = adafruit_requests.Session(pool, ssl.create_default_context())
- response_Weather = requests.get(WEATHER_DATA_SOURCE)
- data_Weather = response_Weather.json()
- response_Weather= requests.get(WEATHER_DATA_SOURCE)
- response_Weather.socket.close()
- current_area, current_temp, current_weather = parse_weather(data_Weather)
- print("WeatherUpdate Success\n")
-
-
-
- requests = adafruit_requests.Session(pool, ssl.create_default_context())
- response_clock = requests.get(TIME_DATA_SOURCE)
- data_clock = response_clock.json()
- response_clock= requests.get(WEATHER_DATA_SOURCE)
- response_clock.socket.close()
- current_hour, current_minute, current_second, current_date = parse_time(data_clock["datetime"])
- r.datetime = time.struct_time((int(current_date[0:4]), int(current_date[5:7]), int(current_date[8:10]), \
- current_hour, current_minute, current_second, 0, -1, -1))
- print("ClockTimeUpdate Success\n")
-
第四部分,相关模块的初始化,具体操作可以看代码中的相关注释
其中API获取网络天气和时间,需要根据自身的需求进行修改,包含知心天气的KEY和地区
TIME_DATA_SOURCE = "http://worldtimeapi.org/api/timezone/Asia/Shanghai"
WEATHER_DATA_SOURCE = "http://api.seniverse.com/v3/weather/now.json?key=SBmmNRnXdiTFi8UQw&location=nanjing&language=zh-Hans&unit=c"
-
- pixel_pin = board.GPIO10
- num_pixels = 16
- pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.2, auto_write=True)
- pixels.fill((8, 8, 8))
- print("RGBDriver Init Success\n")
-
-
-
- Button0 = digitalio.DigitalInOut(board.GPIO0)
- Button0.direction = digitalio.Direction.INPUT
- print("Button0 Init Success\n")
-
-
-
- font_file = "fonts/test16-16.pcf"
-
-
-
-
- font_Chinese = bitmap_font.load_font(font_file)
- print("fonts Init Success\n")
-
-
- text_Wifi = "WiFi:{}\n{}".format(Wifi_Mode,Wifi_ip)
- text_Wifi_title = Label(terminalio.FONT, text=text_Wifi, color = 0xffffff)
- text_Wifi_title.x = 5
- text_Wifi_title.y = 5
- my_display_group.append(text_Wifi_title)
-
-
- RGBr, RGBg, RGBb = 255,255,255
- text_RGB = "RGB:{:03}:{:03}:{:03}\nKEY:{:02}".format(RGBr, RGBg, RGBb, i)
- text_RGB_title = Label(terminalio.FONT, text=text_RGB, color = (RGBr, RGBg, RGBb))
- text_RGB_title.x = 5
- text_RGB_title.y = 30
- my_display_group.append(text_RGB_title)
-
-
-
-
-
- TIME_DATA_SOURCE = "http://worldtimeapi.org/api/timezone/Asia/Shanghai"
- WEATHER_DATA_SOURCE = "http://api.seniverse.com/v3/weather/now.json?key=SBmmNRnXdiTFi8UQw&location=nanjing&language=zh-Hans&unit=c"
- pool = socketpool.SocketPool(wifi.radio)
- server = Server(pool, debug=True)
-
-
-
- websocket: Websocket = None
- next_message_time = time.monotonic()
-
- HTML_TEMPLATE = """
- <html lang="en">
- <head>
- <!--注释:强制html使用utf-8编码-->
- <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
- <title>Web控制中心</title>
- </head>
- <body>
- <p>按键状态: <strong>-</strong></p>
- <p>RGB 颜色: <input type="color"></p>
- <script>
- const Button_count = document.querySelector('strong');
- const colorPicker = document.querySelector('input[type="color"]');
-
- let ws = new WebSocket('ws://' + location.host + '/connect-websocket');
-
- ws.onopen = () => console.log('WebSocket connection opened');
- ws.onclose = () => console.log('WebSocket connection closed');
- ws.onmessage = event => Button_count.textContent = event.data;
- ws.onerror = error => Button_count.textContent = error;
-
- colorPicker.oninput = debounce(() => ws.send(colorPicker.value), 200);
- <!--注释:创建一个延时函数,消除RGB调整抖动-->
- function debounce(callback, delay = 1000) {
- let timeout
- return (...args) => {
- clearTimeout(timeout)
- timeout = setTimeout(() => {
- callback(...args)
- }, delay)
- }
- }
- </script>
- </body>
- </html>
- """
-
- @server.route("/client", GET)
- def client(request: Request):
- return Response(request, HTML_TEMPLATE, content_type="text/html")
-
- @server.route("/connect-websocket", GET)
- def connect_client(request: Request):
- global websocket
- if websocket is not None:
- print("websocket.close")
- websocket.close()
-
- websocket = Websocket(request)
- return websocket
-
-
-
- Update_Data()
-
-
-
- time_label_text = "{}-{:02}-{:02}\n"\
- "时间:{:02}:{:02}:{:02}\n"\
- "地区:{}\n"\
- "温度:{}\n"\
- "天气:{}\n".format(r.datetime.tm_year, r.datetime.tm_mon, r.datetime.tm_mday, \
- r.datetime.tm_hour, r.datetime.tm_min, r.datetime.tm_sec, \
- current_area, current_temp, current_weather)
- time_label = Label(font = font_Chinese, text=time_label_text, color = 0xff00ff)
- time_label.x = 10
- time_label.y = 60
- my_display_group.append(time_label)
-
-
-
- display.show(my_display_group)
- print("display show Success\n")
-
第五部分,在wile循环中更新屏幕的显示状态,和相应web控制中心的操作指令
- while True:
-
- server.poll()
-
- if websocket is not None:
- if (data := websocket.receive(True)) is not None:
- RGBr, RGBg, RGBb = int(data[1:3], 16), int(data[3:5], 16), int(data[5:7], 16)
- pixels.fill((RGBr, RGBg, RGBb))
-
-
- if websocket is not None and next_message_time < time.monotonic():
- time.sleep(0.2)
- if websocket is not None:
- Button_count = i
- websocket.send_message(str(Button_count))
- next_message_time = time.monotonic() + 1
-
-
-
- text_RGB_title.color = (RGBr, RGBg, RGBb)
- text_RGB_title.text = "RGB:{:03}:{:03}:{:03}\nKEY:{:02}".format(RGBr, RGBg, RGBb, i)
- time_label.text = "{}-{:02}-{:02}\n"\
- "时间:{:02}:{:02}:{:02}\n"\
- "地区:{}\n"\
- "温度:{}\n"\
- "天气:{}\n".format(r.datetime.tm_year, r.datetime.tm_mon, r.datetime.tm_mday, \
- r.datetime.tm_hour, r.datetime.tm_min, r.datetime.tm_sec, \
- current_area, current_temp, current_weather)
- Button0_Work()
至此,该代码就实现了我们follow me第二期的1到5的全部功能啦,是不是非常简单呢
在这两天的学习中还是遇到了很多小bug的,建议大家从以下几个官方网站获取API的相关教程
Adafruit Learning System 在这个网站中有大量的实战案例和api的使用教程,建议直接搜索需要用到的模块来参考示例,如直接搜索fonts来了解如何生成字体库
CircuitPython — Adafruit CircuitPython 8.2.0 documentation 这个是circuitpyhon固件的使用文档,可以在里面找到所有api的使用说明,了解其传入传出的参数及配置先后顺序
这个官方仓库中包含了我们需要用到的所有库文件以及对应的例程,可以参考其中的内容进行代码的编写和集成。
这次分享就到这里了,在最后附上我们本次工程的全部代码供大家参考。(活动提供的板子,板载内存可以使用的空间只有2M左右,请按照需求添加lib和fonts文件)
|