829浏览
查看: 829|回复: 0

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

[复制链接]
本帖最后由 HonestQiao 于 2023-9-7 10:18 编辑

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


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

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

一、概要
这篇分享,选用了以下的3款有代表性的ESP32开发板,组成一个自组网灯控系统:
FireBeetle 2 ESP32-S3:基于蓝牙和ESP-NOW的自组网灯控系统图2

上图中的三块开办板分别为:
  • 最左边的,是咱们的主角,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:
  1. from adafruit_ble import BLERadio
  2. from adafruit_ble.advertising import Advertisement
  3. from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
  4. import binascii
  5. import time
  6. import espnow
  7. import board
  8. import digitalio
  9. ble = BLERadio()
  10. print("scanning")
  11. found = set()
  12. scan_responses = set()
  13. peers = set()
  14. scan_times = 0
  15. while True:
  16.     scan_times += 1
  17.     if scan_times>3:
  18.         break
  19.     print("scan_times: %d" % scan_times)
  20.     for advertisement in ble.start_scan(ProvideServicesAdvertisement, Advertisement, timeout=3):
  21.         # 检查scan_response是否以LIGHT_GROUP开头
  22.         if not advertisement.complete_name:
  23.             continue
  24.         elif not advertisement.complete_name.startswith("LIGHT_GROUP"):
  25.             continue
  26.         #dir(advertisement.complete_name)
  27.         #print(advertisement.complete_name)
  28.         #print(advertisement.complete_name.startswith("LIGHT_GROUP"))
  29.         # break
  30.         # 检查scan_responses 或者 addr 是否已经扫描过
  31.         addr = advertisement.address
  32.         if advertisement.scan_response and addr not in scan_responses:
  33.             scan_responses.add(addr)
  34.         elif not advertisement.scan_response and addr not in found:
  35.             found.add(addr)
  36.         else:
  37.             continue
  38.         # 输出相关信息
  39.         dir(advertisement)
  40.         print(addr, advertisement, advertisement.scan_response)
  41.         print("\t" + repr(advertisement))
  42.         print("\t" + repr(advertisement.scan_response))
  43.         print()
  44.         
  45.         # 连接然后断开
  46.         conn = ble.connect(advertisement)
  47.         while not conn.connected:
  48.             pass
  49.         conn.disconnect()
  50.         
  51.         peers.add(binascii.unhexlify(advertisement.complete_name[12:]))
  52.     time.sleep(1)
  53. print("scan done")
  54. time.sleep(1)
  55. led = digitalio.DigitalInOut(board.GPIO21)
  56. led.direction = digitalio.Direction.OUTPUT
  57. e = espnow.ESPNow()
  58. for peer_mac in peers:
  59.     print("peer_mac add:", [hex(i) for i in peer_mac], ["%02X" % i for i in peer_mac], peer_mac)
  60.     peer = espnow.Peer(mac=peer_mac)
  61.     e.peers.append(peer)
  62. count = 0
  63. while True:
  64.     print("espnow send: %d" % count)
  65.     e.send("data: %d" % count)
  66.     if count%2:
  67.         led.value = True
  68.     else:
  69.         led.value = False
  70.     time.sleep(1)
  71.     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:
FireBeetle 2 ESP32-S3:基于蓝牙和ESP-NOW的自组网灯控系统图3
所以上述代码中,这个LED作为领头的灯,其他从设备跟着它起舞。


从设备1代码:espnow_light_group_receiver.py【板载普通LED设备】
  1. from adafruit_ble import BLERadio
  2. from adafruit_ble.advertising import Advertisement
  3. import wifi
  4. import time
  5. import espnow
  6. import board
  7. import digitalio
  8. ble = BLERadio()
  9. mac_addr = wifi.radio.mac_address
  10. ble_addr = ble.address_bytes
  11. print("mac_addr: ", [hex(i) for i in mac_addr], ["%02X" % i for i in mac_addr])
  12. print("ble_addr:", [hex(i) for i in ble_addr], ["%02X" % i for i in ble_addr])
  13. ble_name = "LIGHT_GROUP_%s" % "".join(["%02X" % i for i in ble_addr[0:3]])
  14. ble_name_full = "LIGHT_GROUP_%s" % "".join(["%02X" % i for i in mac_addr[0:]])
  15. scan_response = b""
  16. print(ble_name)
  17. print(repr(scan_response))
  18. advertisement = Advertisement()
  19. advertisement.complete_name = ble_name_full
  20. advertisement.name = ble_name
  21. #advertisement.short_name = ble_name
  22. advertisement.connectable = True
  23. while True:
  24.     print(advertisement, scan_response)
  25.     ble.start_advertising(advertisement, scan_response=scan_response)
  26.     if True:
  27.         while not ble.connected:
  28.             pass
  29.         print("connected")
  30.         while ble.connected:
  31.             pass
  32.         print("disconnected")
  33.     ble.stop_advertising()
  34.     break
  35. LED_AB = hasattr(board, 'LEDA') and hasattr(board, 'LEDB')
  36. if LED_AB:
  37.     leda = digitalio.DigitalInOut(board.LEDA)
  38.     leda.direction = digitalio.Direction.OUTPUT
  39.     ledb = digitalio.DigitalInOut(board.LEDB)
  40.     ledb.direction = digitalio.Direction.OUTPUT
  41. else:
  42.     led = digitalio.DigitalInOut(board.LED)
  43.     led.direction = digitalio.Direction.OUTPUT
  44. e = espnow.ESPNow()
  45. while True:
  46.     if e:
  47.         packet = e.read()
  48.         print(packet)
  49.         
  50.         data = packet.msg.decode()
  51.         if data.startswith("data: "):
  52.             val = int(data[6:])
  53.             if val %2 :
  54.                 if LED_AB:
  55.                     leda.value = True
  56.                     ledb.value = False
  57.                 else:
  58.                     led.value = True
  59.             else:
  60.                 if LED_AB:
  61.                     leda.value = False
  62.                     ledb.value = True
  63.                 else:
  64.                     led = False
  65.     else:
  66.         pass
复制代码


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

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


从设备2代码:espnow_light_group_receiver_ws2812b.py【板载WS2812B设备】
  1. from adafruit_ble import BLERadio
  2. from adafruit_ble.advertising import Advertisement
  3. import wifi
  4. import time
  5. import espnow
  6. import board
  7. import digitalio
  8. import neopixel
  9. ble = BLERadio()
  10. mac_addr = wifi.radio.mac_address
  11. ble_addr = ble.address_bytes
  12. print("mac_addr: ", [hex(i) for i in mac_addr], ["%02X" % i for i in mac_addr])
  13. print("ble_addr:", [hex(i) for i in ble_addr], ["%02X" % i for i in ble_addr])
  14. ble_name = "LIGHT_GROUP_%s" % "".join(["%02X" % i for i in ble_addr[0:3]])
  15. ble_name_full = "LIGHT_GROUP_%s" % "".join(["%02X" % i for i in mac_addr[0:]])
  16. scan_response = b""
  17. print(ble_name)
  18. print(repr(scan_response))
  19. advertisement = Advertisement()
  20. advertisement.complete_name = ble_name_full
  21. advertisement.name = ble_name
  22. #advertisement.short_name = ble_name
  23. advertisement.connectable = True
  24. while True:
  25.     print(advertisement, scan_response)
  26.     ble.start_advertising(advertisement, scan_response=scan_response)
  27.     if True:
  28.         while not ble.connected:
  29.             pass
  30.         print("connected")
  31.         while ble.connected:
  32.             pass
  33.         print("disconnected")
  34.     ble.stop_advertising()
  35.     break
  36. # WS2812B设置
  37. pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
  38. # 亮度
  39. pixel.brightness = 0.5
  40. colors = [
  41.     "red", (255, 0, 0),
  42.     "green", (0, 255, 0),
  43.     "blue", (0, 0, 255),
  44.     "cyan", (0, 255, 255),
  45.     "purple", (255, 0, 255),
  46.     "yellow", (255, 255, 0),
  47.     "white", (255, 255, 255),
  48.     "black", (0, 0, 0),
  49. ]
  50. color_num = int(len(colors)/2) - 1
  51. black_index = int(len(colors)/2)-1
  52. color_index = 0
  53. e = espnow.ESPNow()
  54. while True:
  55.     if e:
  56.         packet = e.read()
  57.         print(packet)
  58.         
  59.         data = packet.msg.decode()
  60.         if data.startswith("data: "):
  61.             val = int(data[6:])
  62.             run_index = 0
  63.             if val % 2 == 1:
  64.                 run_index = color_index*2
  65.                 color_index += 1
  66.                 if color_index>=color_num:
  67.                     color_index = 0
  68.             else:
  69.                 run_index = black_index*2
  70.             print("colors[%d] is %s" % (run_index, colors[run_index]), colors[run_index+1])
  71.             pixel.fill(colors[run_index+1])
  72.     else:
  73.         pass
复制代码

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

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

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

四、运行效果:
为了观察运行状态,使用了串口工具连接三个开发板进行控制。
实际调试完成后,可以放到circuitpython的启动脚本中,开机自动运行。
FireBeetle 2 ESP32-S3:基于蓝牙和ESP-NOW的自组网灯控系统图4

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

然后,先依次运行从设备的代码:
FireBeetle 2 ESP32-S3:基于蓝牙和ESP-NOW的自组网灯控系统图5

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

然后再运行主设备的代码:
FireBeetle 2 ESP32-S3:基于蓝牙和ESP-NOW的自组网灯控系统图6

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

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

然后,我们可以看三个开发板上的板载LED,已经同步起舞了:

五、总结
这篇分享,只是对蓝牙和ESP-NOW的一点小小应用。
在自组网灯控系统的代码中,直接暴露了WiFi硬件地址,实际使用中,可以通过蓝牙scan_responses信息,来返回经过加密的WiFi硬件地址信息,提高安全性。
前面也说过,灯控的部分,只是做了简单的处理,以便演示。在实际使用中,可以根据实际需要,进行具体逻辑的设计。
另外,在实际使用时,可以使用锂电池供电,从而可以方便进行现场具体环境的布置,达到刚好的效果。

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail