一 任务汇总视频
二 项目介绍
这次任务主要完成了五个任务分别是控制屏幕显示中文,网络功能使用,控制WS2812B,日历&时钟,通过网络控制WS2812B。使用到的硬件是Adafruit ESP32-S3 TFT Feather 开发板使用乐鑫ESP32-S3芯片,支持WiFi和蓝牙,内置 IPS TFT 彩色显示屏 ESP32-S3 开发板,4 MB闪存和2 MB PSRAM 。
三 项目任务详细介绍
任务一 控制屏幕显示中文
在Adafruit ESP32-S3 TFT Feather开发版上显示中文其实很简单只要引用相应的库,然后引用字体文件就可以很容易的显示中文
效果图
先把代码贴上来
import board
from io import BytesIO
import displayio
import busio
from digitalio import DigitalInOut
import adafruit_imageload
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
display = board.DISPLAY
group = displayio.Group(scale=1)
color = 0xffffff
font = bitmap_font.load_font("/font/font.pcf")
date = label.Label(font, text="欢迎参加得捷电子", color=color)
date.x = 0
date.y = 30
group.append(date)
date = label.Label(font, text="Follow me第2期", color=color)
date.x = 0
date.y = 60
group.append(date)
display.show(group)
while True:
pass
现在给大家详细讲解一下代码
创建display
display = board.DISPLAY
创建group布局容器并设置放大倍率
group = displayio.Group(scale=1)
设置字体颜色为白色
color = 0xffffff
加载字体文件
font = bitmap_font.load_font("/font/font.pcf")
创建文本框并且设置在屏幕中显示的位置
label = label.Label(font, text="欢迎参加得捷电子", color=color)
label.x = 0
label.y = 30
#将文本框添加到group中
group.append(label)
屏幕显示
display.show(group)
任务二 网络功能使用
这个任务主要分为2个子任务,一个是连接wifi并且通过http请求当前网络动态公网ip还有一个是在开发版上起一个热点,现在我们讲解一下第一个任务
效果图
import board
from io import BytesIO
import displayio
import busio
from digitalio import DigitalInOut
import adafruit_imageload
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
import socketpool
import adafruit_requests
import ssl
import wifi
import time
from adafruit_datetime import datetime, date,timezone,timedelta
import json
TEXT_URL= "http://vv.video.qq.com/checktime?otype=json"
pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())
response = requests.get(TEXT_URL)
timestamp = time.time()
txt = response.text.split("QZOutputJson=")[1]
print(txt)
#print(txt)
data = json.loads(txt.replace(";",""))
print(data["ip"])
display = board.DISPLAY
group = displayio.Group(scale=1)
color = 0xffffff
font = bitmap_font.load_font("/font/font.pcf")
date = label.Label(font, text=data["ip"], color=color)
date.x = 0
date.y = 30
group.append(date)
display.show(group)
while True:
pass
该代码主要实现了从网络时间服务器获取时间戳数据,解析出IP信息,并在显示器上显示这个IP,初始化网络请求会话session,用于发送HTTP请求。具体流程如下
a. 向时间服务器的URL发送GET请求,获取包含时间戳数据的响应。
b. 从响应中提取出JSON字符串,并解析为JSON数据。
c. 从JSON数据中获取IP信息。
d. 初始化显示库,创建显示组group,设置显示参数。
e. 使用显示库的Label组件显示取得的IP信息。
f. 循环显示这个Label。
总体上,利用了网络请求库、JSON解析和显示库,实现了获取网络数据,提取信息,并在显示器上输出的完整流程。主要运用了CircuitPython的网络编程、JSON处理和GUI显示等功能。接着我们讲解第二个子任务起网络热点功能
import board
from io import BytesIO
import displayio
import busio
from digitalio import DigitalInOut
import adafruit_imageload
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
import socketpool
import adafruit_requests
import ssl
import wifi
ssid = "eeworld"
password = "12345678"
print("ssid:"+ssid)
print("pwd:"+password)
wifi.radio.start_ap(ssid, password)
while True:
pass
这段代码是用于创建一个 WiFi 访问点(AP)的,首先定义了ssid和password两个变量,分别表示要创建的WiFi网络的名称和密码。然后打印出这两个变量的值,用于调试和确认。
关键的一行代码是:
wifi.radio.start_ap(ssid, password)
这个start_ap方法属于wifi模块中的radio对象,它可以用来创建一个软AP。方法的参数就是前面定义的ssid和password。这样就会用这两个参数创建一个名称为"eeworld",密码为"12345678"的WiFi网络。最后一个while True空循环语句,目的是保持代码持续运行不退出。
任务三 WS2812B效果控制
在adafruit esp32 开发版中控制WS2812B也非常容易只要使用neopixel库和adafruit_led_animation库便可轻松控制
import board
import neopixel
from adafruit_led_animation.animation.blink import Blink
import adafruit_led_animation.color as color
pixel_pin = board.NEOPIXEL
# Change to match the number of pixels you have attached to your board.
num_pixels = 1
pixels = neopixel.NeoPixel(pixel_pin, num_pixels)
blink = Blink(pixels, 0.5, color.RED)
while True:
blink.animate()
pass
这段代码的主要功能是利用NeoPixel库控制RGB LED,并在while循环中调用Blink对象的animate方法来实现闪烁效果。首先导入了neopixel库,并创建了一个NeoPixel对象pixels,连接到板子上的NEOPIXEL引脚,指定了LED数量为1个。然后创建了一个Blink类的对象blink,传入参数为刚刚创建的pixels对象,设置点亮时间0.5秒,颜色为红色。在while True循环中,调用blink对象的animate方法来点亮LED。这段代码会让连接在NEOPIXEL引脚上的一个RGB LED以0.5秒的间隔闪烁红色。
任务四 日历&时钟
这个任务主要实现了一个时钟日历功能,可以查看当前日期时间还有查看当前的天气,温度,湿度,pm2.5
import board
from io import BytesIO
import displayio
import busio
from digitalio import DigitalInOut
import adafruit_imageload
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
import socketpool
import adafruit_requests
import ssl
import wifi
import time
from adafruit_datetime import datetime, date,timezone,timedelta
import json
import re
from adafruit_display_shapes.roundrect import RoundRect
import asyncio
import rtc
display = board.DISPLAY
group = displayio.Group(scale=1)
color = 0xffffff
font = bitmap_font.load_font("/font/font.pcf")
TEXT_URL= "http://vv.video.qq.com/checktime?otype=json"
pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())
response = requests.get(TEXT_URL)
timestamp = time.time()
txt = response.text.split("QZOutputJson=")[1]
print(txt)
#print(txt)
data = json.loads(txt.replace(";",""))
local_time = datetime.fromtimestamp(data["t"])
rtc.RTC().datetime=time.localtime(data["t"] + 8*3600)
token = ""
headers = {"token":token}
image, palette = adafruit_imageload.load("/images/bg1.png")
# 是否开启透明
#palette.make_transparent(0)
# 创建图片布局
grid = displayio.TileGrid(image, pixel_shader=palette)
grid.x = display.width // 2 - grid.tile_width // 2
grid.y = display.height // 2 - grid.tile_height // 2
# 创建空白背景
# 将图片布局添加到图像组,由于是第一个添加的,默认是最下层
group.append(grid)
#image, palette = adafruit_imageload.load("/images/taikongren.gif")
#grid = displayio.TileGrid(image, pixel_shader=palette)
#group.append(grid)
time1 = label.Label(font, text="00:00:00", color=0xcad0b4, scale=2)
time1.x = 10
time1.y = 50
date = label.Label(font, text="1967年01月01日", color=0xcad0b4, scale=1)
date.x = 10
date.y = 15
weather = label.Label(font, text="晴", color=0x4B4B4B, scale=1)
weather.x = 20
weather.y = 85
roundrect = RoundRect(10, 70,weather.bounding_box[2]+113, 30,5,fill=0x33CC66, outline=0x33CC66)
#roundrect.width = address.bounding_box[2]
group.append(roundrect)
wind = label.Label(font, text="东北风1级", color=0x4B4B4B, scale=1)
wind.x = 20
wind.y = 120
roundrect1 = RoundRect(10, 105,wind.bounding_box[2]+40, 30,5,fill=0xFF9966, outline=0xFF9966)
#roundrect.width = address.bounding_box[2]
group.append(roundrect1)
group.append(date)
group.append(time1)
group.append(wind)
group.append(weather)
image, palette = adafruit_imageload.load("/images/6.png")
# 是否开启透明
palette.make_transparent(0)
# 创建图片布局
grid = displayio.TileGrid(image, pixel_shader=palette)
grid.x = 160
grid.y = 70
group.append(grid)
image, palette = adafruit_imageload.load("/images/3.png")
# 是否开启透明
palette.make_transparent(0)
# 创建图片布局
grid = displayio.TileGrid(image, pixel_shader=palette)
grid.x = 160
grid.y = 90
group.append(grid)
image, palette = adafruit_imageload.load("/images/7.png")
# 是否开启透明
palette.make_transparent(0)
# 创建图片布局
grid = displayio.TileGrid(image, pixel_shader=palette)
grid.x = 160
grid.y = 110
group.append(grid)
temp = label.Label(font, text="18.5°", color=0x1296db, scale=1)
temp.x = 190
temp.y = 80
group.append(temp)
humidity = label.Label(font, text="81", color=0x1296db, scale=1)
humidity.x = 190
humidity.y = 100
group.append(humidity)
pm25 = label.Label(font, text="68", color=0x1296db, scale=1)
pm25.x = 190
pm25.y = 120
group.append(pm25)
display.show(group)
def updateWeather():
url= "https://api.ip138.com/weather/?code=310000&token="+token
response = requests.get(url)
data = response.json()
weather.text = data['data']['weather']
wind.text = data['data']['wind']
humidity.text = data['data']['humidity']
temp.text = data['data']['temp']
pm25.text = data['data']['pm25']
async def updateWeatherTask():
val = 1
while True:
updateWeather()
#time.sleep(1)
await asyncio.sleep(3600)
async def updateTimeTask():
val = 1
while True:
t = time.localtime(time.time())
date.text='%d年%02d月%02d日'%(t.tm_year, t.tm_mon, t.tm_mday)
time1.text='%02d:%02d:%02d'%(t.tm_hour, t.tm_min,t.tm_sec)
#time.sleep(1)
await asyncio.sleep(1)
async def main():
task1 = asyncio.create_task(updateWeatherTask())
task2 = asyncio.create_task(updateTimeTask())
await asyncio.gather(
task2,
task1
)
asyncio.run(main())
这段代码主要是使用了网络请求、时间、天气API、GUI和异步编程,实现了一个可以实时显示时间和天气的程序,并实时更新,具体流程如下
a.初始化显示器,创建显示组group。
b.发送网络请求获取时间戳数据,解析出时间信息,同步到RTC。
c.加载图片作为背景,添加到group。
d. 创建多个Label组件显示时间、天气等信息,添加到group。
e. 将group显示到显示器上。
f.定义了updateWeather()函数从网络获取最新天气数据,更新显示。
g.定义了两个异步任务updateTimeTask()和updateWeatherTask(),分别每秒和每3600秒更新时间和天气。
h.在main()异步函数中同时执行这两个任务。
i.使用asyncio.run启动主异步逻辑。
任务五 通过网络控制WS2812B
要实现网络控制WS2812B的功能,我选择的方案是通过微信小程序来控制rgb led,通讯协议使用mqtt,因此还需要一个mqtt的服务端,我没有选择公共的mqtt服务,而是使用.net core 搭建了一套简单的mqtt服务,同时支持mqtt和socket两种方式
mqtt服务端的实现代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using MQTTnet;
using MQTTnet.AspNetCore;
using MQTTnet.Server;
namespace mqtt
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services
.AddHostedMqttServer(mqttServer => mqttServer.WithoutDefaultEndpoint())
.AddMqttConnectionHandler()
.AddConnections();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapConnectionHandler<MqttConnectionHandler>(
"/mqtt",
httpConnectionDispatcherOptions => httpConnectionDispatcherOptions.WebSockets.SubProtocolSelector =
protocolList =>
protocolList.FirstOrDefault() ?? string.Empty);
});
app.UseMqttServer(server =>
{
// Todo: Do something with the server
});
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MQTTnet;
using MQTTnet.AspNetCore;
using MQTTnet.Client;
using MQTTnet.Server;
namespace mqtt
{
public class Program
{
public static Task Main(string[] args)
{
//CreateHostBuilder(args).Build().Run();
return BuildWebHost(args).RunAsync();
}
private static IWebHost BuildWebHost(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.UseKestrel(o =>
{
o.ListenAnyIP(1883, l => l.UseMqtt());
o.ListenAnyIP(5000); // default http pipeline
})
.UseStartup<Startup>()
.Build();
}
/*public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});*/
}
}
该代码构建了一个支持MQTT和HTTP双协议的ASP.NET Core主机,可以用来开发集成两种服务的Web应用程序,主要工作是利用.NET Core框架搭建主机并配置服务端口
小程序代码
// index.js
import mqtt from "../../mqtt4.1.0.js"
const options = {
keepalive: 30,
//protocolVersion: 4, //MQTT V3.1.1
connectTimeout: 4000,
clientId: 'sdfkskr43',//这个地方最好用一个随机字符串方法生成
port: 5000,
username: '',
password: '',
}
//此处需要用wxs,请注意!!!
var client = mqtt.connect('wx://127.0.0.1/mqtt', options)
client.on('connect', (e) => {
console.log('服务器连接成功', e)
})
Page({
data:{
colorData: {
//基础色相,即左侧色盘右上顶点的颜色,由右侧的色相条控制
hueData: {
colorStopRed: 255,
colorStopGreen: 0,
colorStopBlue: 0,
},
//选择点的信息(左侧色盘上的小圆点,即你选择的颜色)
pickerData: {
x: 0, //选择点x轴偏移量
y: 480, //选择点y轴偏移量
red: 0,
green: 0,
blue: 0,
hex: '#000000'
},
//色相控制条的位置
barY: 0
},
rpxRatio: 1 //此值为你的屏幕CSS像素宽度/750,单位rpx实际像素
}
,
onLoad() {
var _this = this
wx.getSystemInfo({
success(res) {
_this.setData({
rpxRatio: res.screenWidth / 750
})
}
})
},
//选择改色时触发(在左侧色盘触摸或者切换右侧色相条)
onChangeColor(e) {
//返回的信息在e.detail.colorData中
this.setData({
colorData: e.detail.colorData
})
var rgbdata = {}
rgbdata.R = this.data.colorData.pickerData.red
rgbdata.G = this.data.colorData.pickerData.green
rgbdata.B = this.data.colorData.pickerData.blue
client.publish('rgbTopic',JSON.stringify(rgbdata), { qos: 2 }, function (err) {
console.log('send', err)
})
}
})
这段代码使用微信小程序的MQTT库实现了一个简单的RGB颜色控制示例,主要功能有:
a. 初始化并连接MQTT客户端
使用mqtt.connect创建MQTT客户端,指定连接参数如客户端id、服务器地址等,并绑定connect事件回调打印连接成功日志。
b.小程序页面初始化
在onLoad回调中获取系统信息,设置rpx转换比例。
c.实现颜色选择组件
页面json中引入微信提供的color-picker组件,在wxml中渲染。在js中通过onChangeColor回调获取选中的颜色值。
d.通过MQTT发布RGB数据
在onChangeColor中将颜色值转换为RGB对象,通过MQTT的publish方法发布到rgbTopic主题上,实现和服务端的通信。
e.MQTT配置
使用ES6语法导入MQTT包,指定连接和订阅选项如keepalive时间、用户名密码等。
该代码演示了微信小程序中通过MQTT与后端通信的方式,实现了一个简单的RGB颜色控制和同步的示例,主要涉及小程序的页面开发、MQTT的使用以及组件的绑定应用。
import board
from io import BytesIO
import displayio
import busio
from digitalio import DigitalInOut
import adafruit_imageload
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
import socketpool
import adafruit_requests
import ssl
import wifi
import time
from adafruit_datetime import datetime, date,timezone,timedelta
import json
import re
from adafruit_display_shapes.roundrect import RoundRect
import asyncio
import rtc
from adafruit_esp32spi import adafruit_esp32spi
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
import adafruit_minimqtt.adafruit_minimqtt as MQTT
import neopixel
from adafruit_led_animation.animation.blink import Blink
import adafruit_led_animation.color as color
pixel_pin = board.NEOPIXEL
num_pixels = 1
pixels = neopixel.NeoPixel(pixel_pin, num_pixels)blink = Blink(pixels, 0.5, color.ORANGE)
def connect(mqtt_client, userdata, flags, rc):
mqtt_client.subscribe("rgbTopic")
def disconnect(mqtt_client, userdata, rc):
print("Disconnected")
def subscribe(mqtt_client, userdata, topic, granted_qos):
print("Subscribed to {0} with QOS level {1}".format(topic, granted_qos))
def unsubscribe(mqtt_client, userdata, topic, pid):
print("Unsubscribed from {0} with PID {1}".format(topic, pid))
def publish(mqtt_client, userdata, topic, pid):
print("Published to {0} with PID {1}".format(topic, pid))
def message(client, topic, message):
if(topic == "rgbTopic"):
jsonData = json.loads(message)
r = int(jsonData["R"])
g = int(jsonData["G"])
b = int(jsonData["B"])
global blink
blink = Blink(pixels, speed=0.5, color=(r,g,b))
pool = socketpool.SocketPool(wifi.radio)
mqtt_client = MQTT.MQTT(
broker="192.168.0.111",
port=1883,
username='',
password='',
socket_pool=pool,
ssl_context=ssl.create_default_context()
)
mqtt_client.on_connect = connect
mqtt_client.on_disconnect = disconnect
mqtt_client.on_subscribe = subscribe
mqtt_client.on_unsubscribe = unsubscribe
mqtt_client.on_publish = publish
mqtt_client.on_message = message
mqtt_client.connect()
while True:
blink.animate()
mqtt_client.loop()
这段代码实现了一个通过MQTT控制RGB彩灯的示例,主要功能包括:
a. 初始化WiFi、MQTT客户端对象。
使用socketpool、SSL等模块建立网络连接,创建MQTT客户端并绑定相关回调函数。
b.初始化彩灯对象。
创建一个NeoPixel对象代表彩灯,并创建一个Blink对象用来控制闪烁。
c.实现MQTT的消息处理。
绑定on_message回调,在收到mqtt信息时将JSON解析为RGB值,更新Blink对象的颜色。
d.主循环。
轮询调用blink.animate()更新彩灯状态,并调用mqtt_client.loop()监听消息。
该示例演示了如何利用MQTT协议实现RGB彩灯的远程控制。主要步骤是建立网络连接,解析MQTT消息设置彩灯状态,并循环更新。利用MQTT可以实现设备的远程监控和控制。
四 任务的心得体会
CircuitPython生态已经很成熟,Adafruit提供了大量针对自己开发板的CircuitPython库。我在使用时也发现CircuitPython的图形界面很方便,适合开发UI交互较丰富的项目。该开发板集成度高,很多外设接口直接可用。开发时需要留意一些细节,如显示屏的刷新模式、内存使用等。需要用一些技巧来优化代码,提高性能。总体来说,这个开发板硬件强大,软件易用性也高。Adafruit提供的开发资源丰富,可以让开发效率大大提升。后续我也会继续使用这个板子做些有趣的项目。也感谢得捷电子和eeworld能给我这么机会来参与这个活动。
本帖最后由 eew_sqNFJw 于 2023-11-3 01:19 编辑