[作品提交] 【得捷电子Follow me第2期】项目汇总

eew_sqNFJw   2023-11-10 02:43 楼主

一  任务汇总视频

    

二 项目介绍

      这次任务主要完成了五个任务分别是控制屏幕显示中文,网络功能使用,控制WS2812B,日历&时钟,通过网络控制WS2812B。使用到的硬件是Adafruit ESP32-S3 TFT Feather  开发板使用乐鑫ESP32-S3芯片,支持WiFi和蓝牙,内置 IPS TFT 彩色显示屏 ESP32-S3 开发板,4 MB闪存和2 MB PSRAM 

三 项目任务详细介绍

      任务一  控制屏幕显示中文

       在Adafruit ESP32-S3 TFT Feather开发版上显示中文其实很简单只要引用相应的库,然后引用字体文件就可以很容易的显示中文

       效果图

       

IMG_3208.JPG

       先把代码贴上来

       

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还有一个是在开发版上起一个热点,现在我们讲解一下第一个任务

   效果图

   

IMG_3225.JPG
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显示等功能。接着我们讲解第二个子任务起网络热点功能

IMG_3221.JPG
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

    

IMG_3219.JPG
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 编辑

回复评论 (1)

CircuitPython的图形界面适合开发UI交互较丰富的项目,这个是很大的优势

点赞  2023-11-12 08:51
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复