本帖最后由 HonestQiao 于 2023-9-7 10:18 编辑  
 
在官方的 DFROBOT FireBeetle 2 ESP32-S3 Board  进阶教程 中,提供了ESP-NOW数据传输 的教程。ESP-NOW是乐鑫开发的终端数据传输、无连接的快速通讯技术,适用于智能灯、遥控控制、传感器数据回传等场景。 
 
 
  
 
通过ESP-NOW,多个ESP32设备之间,可以进行直接进项相互连接,而不依赖于WiFi等其他外部设备。 
 
一、概要 
这篇分享,选用了以下的3款有代表性的ESP32开发板,组成一个自组网灯控系统: 
  
 
上图中的三块开办板分别为: 
 
 
要实现这个自组网灯控系统,需要完成的基本功能如下: 
- 设备可以自动发现
 - 设备自动发现以后,可以自动连接通信
 - 以上功能不依赖于其他设备;当然,开发的时候还是需要。
 
 
  
 
在这篇分享里面,设备自动发现,使用了蓝牙来实现。 
- 当从设备启动后,会自动设置自身的蓝牙广播名称,短名称为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[12:]))
 - 
 -     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:", [hex(i) for i in peer_mac], ["%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: ", [hex(i) for i in mac_addr], ["%02X" % i for i in mac_addr])
 - print("ble_addr:", [hex(i) for i in ble_addr], ["%02X" % i for i in ble_addr])
 - 
 - ble_name = "LIGHT_GROUP_%s" % "".join(["%02X" % i for i in ble_addr[0:3]])
 - ble_name_full = "LIGHT_GROUP_%s" % "".join(["%02X" % i for i in mac_addr[0:]])
 - 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[6:])
 -             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: ", [hex(i) for i in mac_addr], ["%02X" % i for i in mac_addr])
 - print("ble_addr:", [hex(i) for i in ble_addr], ["%02X" % i for i in ble_addr])
 - 
 - ble_name = "LIGHT_GROUP_%s" % "".join(["%02X" % i for i in ble_addr[0:3]])
 - ble_name_full = "LIGHT_GROUP_%s" % "".join(["%02X" % i for i in mac_addr[0:]])
 - 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[6:])
 -             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[run_index]), colors[run_index+1])
 -             pixel.fill(colors[run_index+1])
 -     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,已经同步起舞了: 
 
五、总结 
这篇分享,只是对蓝牙和ESP-NOW的一点小小应用。 
在自组网灯控系统的代码中,直接暴露了WiFi硬件地址,实际使用中,可以通过蓝牙scan_responses信息,来返回经过加密的WiFi硬件地址信息,提高安全性。 
前面也说过,灯控的部分,只是做了简单的处理,以便演示。在实际使用中,可以根据实际需要,进行具体逻辑的设计。 
另外,在实际使用时,可以使用锂电池供电,从而可以方便进行现场具体环境的布置,达到刚好的效果。 
 
 |