HonestQiao 发表于 2023-9-7 10:16:13

FireBeetle 2 ESP32-S3:基于蓝牙和ESP-NOW的自组网灯控系统

本帖最后由 HonestQiao 于 2023-9-7 10:18 编辑

在官方的 DFROBOT FireBeetle 2 ESP32-S3 Board进阶教程 中,提供了ESP-NOW数据传输 的教程。ESP-NOW是乐鑫开发的终端数据传输、无连接的快速通讯技术,适用于智能灯、遥控控制、传感器数据回传等场景。




通过ESP-NOW,多个ESP32设备之间,可以进行直接进项相互连接,而不依赖于WiFi等其他外部设备。

一、概要
这篇分享,选用了以下的3款有代表性的ESP32开发板,组成一个自组网灯控系统:


上图中的三块开办板分别为:

[*]最左边的,是咱们的主角,FireBeetle 2 Board ESP32-S3(N16R8)开发板(带摄像头),作为主控设备接入系统
[*]中间的,某友商CORE-ESP32S3开发板,有两个灯,可以用于交替闪烁,作为从设备1接入系统
[*]最右边的,是乐鑫官方的ESP32-C3-DevKitM-1,板载的是WS2812B灯,可以变换颜色,作为从设备2接入系统


要实现这个自组网灯控系统,需要完成的基本功能如下:

[*]设备可以自动发现
[*]设备自动发现以后,可以自动连接通信
[*]以上功能不依赖于其他设备;当然,开发的时候还是需要。


在这篇分享里面,设备自动发现,使用了蓝牙来实现。

[*]当从设备启动后,会自动设置自身的蓝牙广播名称,短名称为LIGHT_GROUP_????,最后的????,根据设备自身蓝牙硬件地址前4位来设置,长名称为LIGHT_GROUP_????,最后的????,根据设备自身WiFi硬件地址来设置



[*]当主设备启动后,会自动扫描周边的蓝牙设备,发现LIGHT_GROUP_开头的设备,就会自动连接上去套近乎。



[*]从设备收到主设备的连接信息后,就自动停止蓝牙广播,并开启ESP-NOW接受,等待主设备发送控制指令。


ESP-NOW相互之间发送数据,需要发送方知道接收方的WiFi硬件地址地址,而通过上述的蓝牙扫描过程,能够自动获得从设备的WiFi硬件地址,从而供ESP-NOW通信使用。

如果是近距离的自组网,可以直接使用蓝牙。但蓝牙毕竟通信距离有限,ESP-NOW在没有阻碍环境中,轻松达到100米以上的通信距离,使用场景会扩展很多。


二、实现环境
这篇分享的自组网灯控系统,基于circuitpython环境来完成。只要是安装circuitpython的ESP32,都可以接入这个自组网灯控系统。
circuitpython的官方为:CircuitPython,它是micropython的一个分支。

要使用蓝牙功能,还需要安装adafruit_ble模块,可以从 Libraries (circuitpython.org) 下载了,解压其中的 adafruit_ble 模块文件放到开发板的 /lib/ 目录中。

如果是要驱动板载WS2812B,则还需要 neopixel 模块,同样从上述 Libraries 中解压了放到开发板上。

三、实现代码

主控 DFROBOT FireBeetle 2 ESP32-S3 Board 代码:espnow_light_group_master.py:

from adafruit_ble import BLERadio

from adafruit_ble.advertising import Advertisement
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
import binascii
import time
import espnow
import board
import digitalio

ble = BLERadio()
print("scanning")
found = set()
scan_responses = set()
peers = set()

scan_times = 0
while True:
    scan_times += 1
    if scan_times>3:
      break
    print("scan_times: %d" % scan_times)
    for advertisement in ble.start_scan(ProvideServicesAdvertisement, Advertisement, timeout=3):
      # 检查scan_response是否以LIGHT_GROUP开头
      if not advertisement.complete_name:
            continue
      elif not advertisement.complete_name.startswith("LIGHT_GROUP"):
            continue

      #dir(advertisement.complete_name)
      #print(advertisement.complete_name)
      #print(advertisement.complete_name.startswith("LIGHT_GROUP"))
      # break
      # 检查scan_responses 或者 addr 是否已经扫描过
      addr = advertisement.address
      if advertisement.scan_response and addr not in scan_responses:
            scan_responses.add(addr)
      elif not advertisement.scan_response and addr not in found:
            found.add(addr)
      else:
            continue

      # 输出相关信息
      dir(advertisement)
      print(addr, advertisement, advertisement.scan_response)
      print("\t" + repr(advertisement))
      print("\t" + repr(advertisement.scan_response))
      print()
      
      # 连接然后断开
      conn = ble.connect(advertisement)
      while not conn.connected:
            pass
      conn.disconnect()
      
      peers.add(binascii.unhexlify(advertisement.complete_name))

    time.sleep(1)

print("scan done")

time.sleep(1)

led = digitalio.DigitalInOut(board.GPIO21)
led.direction = digitalio.Direction.OUTPUT

e = espnow.ESPNow()
for peer_mac in peers:
    print("peer_mac add:", , ["%02X" % i for i in peer_mac], peer_mac)
    peer = espnow.Peer(mac=peer_mac)
    e.peers.append(peer)

count = 0
while True:
    print("espnow send: %d" % count)
    e.send("data: %d" % count)
    if count%2:
      led.value = True
    else:
      led.value = False
    time.sleep(1)
    count+=1



上述代码中,核心就是通过 ble.start_scan(ProvideServicesAdvertisement, Advertisement, timeout=3) 扫描蓝牙设备,然后检查 返回蓝牙设备信息中的complete_name,是不是以LIGHT_GROUP开头,如果是的,就会记录下来对应的信息,表示用于通信的设备。

扫描完成后,就开始通过ESP-NOW发送数据【data: 发送次数】
从设备会根据发送次数,来确定点灯闪烁的顺序,或者WS2812B颜色切换的顺序。

DFROBOT FireBeetle 2 ESP32-S3 Board开发板上的板载LED,连接到了GPIO21:

所以上述代码中,这个LED作为领头的灯,其他从设备跟着它起舞。


从设备1代码:espnow_light_group_receiver.py【板载普通LED设备】

from adafruit_ble import BLERadio
from adafruit_ble.advertising import Advertisement
import wifi
import time
import espnow
import board
import digitalio

ble = BLERadio()

mac_addr = wifi.radio.mac_address
ble_addr = ble.address_bytes

print("mac_addr: ", , ["%02X" % i for i in mac_addr])
print("ble_addr:", , ["%02X" % i for i in ble_addr])

ble_name = "LIGHT_GROUP_%s" % "".join(["%02X" % i for i in ble_addr])
ble_name_full = "LIGHT_GROUP_%s" % "".join(["%02X" % i for i in mac_addr])
scan_response = b""

print(ble_name)
print(repr(scan_response))

advertisement = Advertisement()
advertisement.complete_name = ble_name_full
advertisement.name = ble_name
#advertisement.short_name = ble_name
advertisement.connectable = True


while True:
    print(advertisement, scan_response)
    ble.start_advertising(advertisement, scan_response=scan_response)
    if True:
      while not ble.connected:
            pass
      print("connected")
      while ble.connected:
            pass
      print("disconnected")
    ble.stop_advertising()
    break

LED_AB = hasattr(board, 'LEDA') and hasattr(board, 'LEDB')

if LED_AB:
    leda = digitalio.DigitalInOut(board.LEDA)
    leda.direction = digitalio.Direction.OUTPUT

    ledb = digitalio.DigitalInOut(board.LEDB)
    ledb.direction = digitalio.Direction.OUTPUT
else:
    led = digitalio.DigitalInOut(board.LED)
    led.direction = digitalio.Direction.OUTPUT

e = espnow.ESPNow()
while True:
    if e:
      packet = e.read()
      print(packet)
      
      data = packet.msg.decode()
      if data.startswith("data: "):
            val = int(data)
            if val %2 :
                if LED_AB:
                  leda.value = True
                  ledb.value = False
                else:
                  led.value = True
            else:
                if LED_AB:
                  leda.value = False
                  ledb.value = True
                else:
                  led = False
    else:
      pass







在上述代码中,首先开启自身蓝牙广播,然后等待蓝牙连接。
一旦有蓝牙连接,就停止蓝牙广播,转入ESP-NOW接收状态。

CORE-ESP32S3开发板有两个LED,所以上述代码中,会自动根据LED的情况进行设置。
如果是单LED的设备,就跟主设备一样,亮灭亮灭闪烁。
如果是有LEDA、LEDB,则会交替闪烁。
因此,上述代码,不只是用于CORE-ESP32S3开发板,其他型号的也可以,通用的。


从设备2代码:espnow_light_group_receiver_ws2812b.py【板载WS2812B设备】

from adafruit_ble import BLERadio
from adafruit_ble.advertising import Advertisement
import wifi
import time
import espnow
import board
import digitalio
import neopixel

ble = BLERadio()

mac_addr = wifi.radio.mac_address
ble_addr = ble.address_bytes

print("mac_addr: ", , ["%02X" % i for i in mac_addr])
print("ble_addr:", , ["%02X" % i for i in ble_addr])

ble_name = "LIGHT_GROUP_%s" % "".join(["%02X" % i for i in ble_addr])
ble_name_full = "LIGHT_GROUP_%s" % "".join(["%02X" % i for i in mac_addr])
scan_response = b""

print(ble_name)
print(repr(scan_response))

advertisement = Advertisement()
advertisement.complete_name = ble_name_full
advertisement.name = ble_name
#advertisement.short_name = ble_name
advertisement.connectable = True


while True:
    print(advertisement, scan_response)
    ble.start_advertising(advertisement, scan_response=scan_response)
    if True:
      while not ble.connected:
            pass
      print("connected")
      while ble.connected:
            pass
      print("disconnected")
    ble.stop_advertising()
    break

# WS2812B设置
pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
# 亮度
pixel.brightness = 0.5

colors = [
    "red", (255, 0, 0),
    "green", (0, 255, 0),
    "blue", (0, 0, 255),
    "cyan", (0, 255, 255),
    "purple", (255, 0, 255),
    "yellow", (255, 255, 0),
    "white", (255, 255, 255),
    "black", (0, 0, 0),
]

color_num = int(len(colors)/2) - 1
black_index = int(len(colors)/2)-1
color_index = 0

e = espnow.ESPNow()
while True:
    if e:
      packet = e.read()
      print(packet)
      
      data = packet.msg.decode()
      if data.startswith("data: "):
            val = int(data)
            run_index = 0
            if val % 2 == 1:
                run_index = color_index*2
                color_index += 1
                if color_index>=color_num:
                  color_index = 0
            else:
                run_index = black_index*2

            print("colors[%d] is %s" % (run_index, colors), colors)
            pixel.fill(colors)
    else:
      pass






上述代码的逻辑,和板载普通LED设备类似,就是LED控制改成了WS2812B控制,自动切换颜色。注意,驱动WS2812B需要neopixel lib文件。

在上述三部分代码中,灯控的部分,只是做了简单的处理,以便演示。
在实际使用中,可以根据实际需要,进行具体逻辑的设计。

将上述代码文件,以及需要的lib文件,到开发板上,就可以准备运行了。

四、运行效果:
为了观察运行状态,使用了串口工具连接三个开发板进行控制。
实际调试完成后,可以放到circuitpython的启动脚本中,开机自动运行。


上图中,最上面的为主设备,下面的为从设备。
DFROBOT FireBeetle 2 ESP32-S3 Board的固件,用了类似的YD-ESP32-S3的,可以通用,有空了我再定制一个。

然后,先依次运行从设备的代码:


可以看到,输出了蓝牙和wifi设备硬件地址,以及对应的蓝牙广播名称。

然后再运行主设备的代码:


运行后,自动扫描到了从设备,并进行连接。
扫描完毕后,开始ESP-NPW数据发送。

从设备上面,也输出了连接和连接关闭的日志,以及接收到ESP-NOW数据的信息。

然后,我们可以看三个开发板上的板载LED,已经同步起舞了:
https://www.bilibili.com/video/BV1K8411B7nS/?share_source=copy_web&vd_source=dd3afa784d4f6118125640815885322b
五、总结
这篇分享,只是对蓝牙和ESP-NOW的一点小小应用。
在自组网灯控系统的代码中,直接暴露了WiFi硬件地址,实际使用中,可以通过蓝牙scan_responses信息,来返回经过加密的WiFi硬件地址信息,提高安全性。
前面也说过,灯控的部分,只是做了简单的处理,以便演示。在实际使用中,可以根据实际需要,进行具体逻辑的设计。
另外,在实际使用时,可以使用锂电池供电,从而可以方便进行现场具体环境的布置,达到刚好的效果。

页: [1]
查看完整版本: FireBeetle 2 ESP32-S3:基于蓝牙和ESP-NOW的自组网灯控系统