当前位置:网站首页>《物联网开发实战》18 场景联动:智能电灯如何感知光线?(上)(学习笔记)
《物联网开发实战》18 场景联动:智能电灯如何感知光线?(上)(学习笔记)
2022-06-11 21:39:00 【小辉_Super】
仅作为本人学习《物联网开发实战》的学习笔记,原课程链接:极客时间《物联网开发实战》——郭朝斌
第一步:通信技术
郭老师建议选择 BLE 低功耗蓝牙技术作为光照传感器设备的通信手段。因为传感器的部署位置比较灵活,所以不能直接用电源线,而是需要用到无线通信技术,又因为 BLE 的功耗比 Wi-Fi 低,所以我们选择 BLE 通信技术。
BLE 设备可以在 4 种模式下工作:
- 广播模式(Boradcaster),单纯的广播模式。这种模式下设备不可以被连接,只能够以一定的时间间隔把数据广播出来,工其他设备使用,比如手机扫描处理。
- 从机模式(Peripheral),这种模式下设备仍然可以广播数据,同时也可以被连接。建立连接后,双方可以进行双向通信。
- 主机模式(Central),这种模式下设备不能进行广播,但是可以扫描周围的蓝牙广播包,发现其他设备,然后主动对这些设备进行发起连接。
- 观察者模式(Observer),这种模式下设备像主机模式一样,也不进行广播,而是扫描周围的蓝牙广播包,但不同的地方是,它不会与从机建立连接。一般收集蓝牙设备广播包的网关就是在这种模式下工作的。
本讲中,光照传感器只需要提供光照强度数据就行了,所以我们可以让它工作在广播模式下。
第二步:选择开发板
开发板依然是 NodeMCU,需要使用基于 ESP32 芯片的 NodeMCU 开发板,它同时支持 Wi-Fi 和低功耗蓝牙通信技术,还有很多 ADC 接口。
第三步:准备 MicroPython 环境
环境搭建可以参考 《物联网开发实战》16 实战准备:如何搭建硬件开发环境?(学习笔记)
第四步:搭建光照传感器硬件电路
下面是郭老师的连线图:

郭老师选择的是基于 PT550 环保型光敏二极管的光照传感器元器件,它的灵敏度更高,测量范围是 0Lux~6000Lux。
这个元器件通过信号管脚输出模拟量,我们读取 NodeMCU ESP32 的 ADC 模数转换器的数值(ADC7,GPIO35),就可以得到光照的强度。这个数值越大,说明光照强度越大。
ADC 支持的最大精度为 12 bit,对应十进制为 0~4095,我们需要将电压值与 ADC 值做一个线性转换,可以参考下面的代码(摘自原文)
from machine import ADC
from machine import Pin
class LightSensor():
def __init__(self, pin):
self.light = ADC(Pin(pin))
def value(self):
value = self.light.read()
print("Light ADC value:", value)
return int(value/4096*6000)
第五步:编写蓝牙程序
NodeMCU ESP32 的固件已经集成了 BLE 功能,但我们还需要给广播包数据定义一定的格式,让其他设备可以顺利地解析使用扫描到的数据。
如何定义蓝牙广播包的格式呢?郭老师推荐了小米定制的 MiBeacon 蓝牙协议。
为了方便用户在使用米家APP 和蓝牙网关时,能快速发现并与BLE 设备建立连接,小米IoT 平台在BLE 设备的广播中(基于 BLE 协议4.0),添加了小米服务数据(ServiceData UUID 0xFE95,即Mibeacon),使BLE 设备在广播数据时能够标识设备自己的身份和类型,能够及时被用户或蓝牙网关识别和连接;此外,为了更好地提高BLE 设备智能化的能力,BLE MiBeacon 协议还支持开发者根据实际的使用需要,选择添加Object 字段,通过网关向小米IoT 平台上报BLE 设备的事件信息和状态信息(属性),实现设备状态远程上报和智能联动等功能。
https://iot.mi.com/
MiBeacon 蓝牙协议的广播包格式是基于 BLE 的 GAP(Generic Access Profile)制定的。GAP 控制了蓝牙的广播和连接,也就是控制了设备如何被发现,以及如何交互。
具体来说,GAP 定义了两种方式来让设备广播数据:
一个是广播数据(Advertising Data payload),这个是必须的,数据长度是 31 个字节;
另一个是扫描回复数据(Scan Response payload),它基于蓝牙主机设备(比如手机)发出的扫描请求(Scan Request)来回复一些额外的信息。数据长度和广播数据一样。
(注意,蓝牙 5.0 中有扩展的广播数据,数据长度等特性与此不同,但这里不涉及,所以不再介绍。)
所以,只要含有以下指定信息的广播报文,就可以认为是符合 MiBeacon 蓝牙协议的。
1 . Advertising Data 中 Service Data(0x16)含有 Mi Service UUID 的广播包,UUID 是 0xFE95。
2 . Scan Response 中 Manufacturer Specific Data(0xFF)含有小米公司识别码的广播包,识别码 ID 是 0x038F。
其中,无论是在 Advertising Data 中,还是 Scan Response 中,均采用统一格式定义。据图的广播报文格式定义,可以参考下面的表格。——原文
| 名称 | 长度(byte) | 是否必须 | 描述 |
|---|---|---|---|
| Frame Control | 2 | 必须 | 控制位 |
| Product ID | 2 | 必须 | 产品 ID,需要在小米 IoT 开发平台申请 |
| Frame Counter | 1 | 必须 | 序号,用于去重 |
| MAC Address | 6 | 基于 Frame Control | 设备 Mac 地址 |
| Capability | 1 | 基于 Frame Control | 设备能力 |
| I/O capability | 2 | 基于 Capacity | I/O 能力,目前只有高安全级 BLE 接入才会用到此字段 |
| Object | n(根据实际需求) | 基于 Frame Control | 触发事件或广播属性 |
| Random Number | 3 | 基于 Frame Control | 如果加密则为必选字段,与 Frame Counter 合并成为 4 字节 Counter,用于防重放 |
| Message Integrity Check | 4 | 基于 Frame Control | 如果加密则为必选字段,MIC 四字节 |
由于我们要给光照传感器增加广播光照强度数据的能力,所以需要重点关注 Object 的定义。

根据 MiBeacon 的定义,光照传感器的 Object ID 是 0x1007,数据长度 3 个字节,数值范围是 0~120000。
下面是郭老师提供的参考代码【略做了修改,不然无法在我的板子上运行】:
#file: ble_lightsensor.py
import bluetooth
import struct
import time
from ble_advertising import advertising_payload
from micropython import const
_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_INDICATE_DONE = const(20)
_FLAG_READ = const(0x0002)
_FLAG_NOTIFY = const(0x0010)
_ADV_SERVICE_DATA_UUID = 0xFE95
_SERVICE_UUID_ENV_SENSE = 0x181A
_CHAR_UUID_AMBIENT_LIGHT = 'FEC66B35-937E-4938-9F8D-6E44BBD533EE'
# Service environmental sensing
_ENV_SENSE_UUID = bluetooth.UUID(_SERVICE_UUID_ENV_SENSE)
# Characteristic ambient light density
_AMBIENT_LIGHT_CHAR = (
bluetooth.UUID(_CHAR_UUID_AMBIENT_LIGHT),
_FLAG_READ | _FLAG_NOTIFY ,
)
_ENV_SENSE_SERVICE = (
_ENV_SENSE_UUID,
(_AMBIENT_LIGHT_CHAR,),
)
# https://specificationrefs.bluetooth.com/assigned-values/Appearance%20Values.pdf
_ADV_APPEARANCE_GENERIC_AMBIENT_LIGHT = const(1344)
class BLELightSensor:
def __init__(self, ble, name='Nodemcu'):
self._ble = ble
self._ble.active(True)
self._ble.irq(self._irq)
((self._handle,),) = self._ble.gatts_register_services((_ENV_SENSE_SERVICE,))
self._connections = set()
time.sleep_ms(500)
self._payload = advertising_payload(
name=name, services=[_ENV_SENSE_UUID], appearance=_ADV_APPEARANCE_GENERIC_AMBIENT_LIGHT
)
self._sd_adv = None
self._advertise()
def _irq(self, event, data):
# Track connections so we can send notifications.
if event == _IRQ_CENTRAL_CONNECT:
conn_handle, _, _ = data
self._connections.add(conn_handle)
elif event == _IRQ_CENTRAL_DISCONNECT:
conn_handle, _, _ = data
self._connections.remove(conn_handle)
# Start advertising again to allow a new connection.
self._advertise()
elif event == _IRQ_GATTS_INDICATE_DONE:
conn_handle, value_handle, status = data
def set_light(self, light_den, notify=False):
self._ble.gatts_write(self._handle, struct.pack("!h", int(light_den)))
self._sd_adv = self.build_mi_sdadv(light_den)
self._advertise()
if notify:
for conn_handle in self._connections:
if notify:
# Notify connected centrals.
self._ble.gatts_notify(conn_handle, self._handle)
def build_mi_sdadv(self, density):
uuid = 0xFE95
fc = 0x0010
pid = 0x0002
fcnt = 0x01
mac = self._ble.config('mac')
objid = 0x1007
objlen = 0x03
objval = density
service_data = struct.pack("<3HB",uuid,fc,pid,fcnt)+mac[1]+struct.pack("<H2BH",objid,objlen,0,objval)
print("Service Data:",service_data)
return advertising_payload(service_data=service_data)
def _advertise(self, interval_us=500000):
self._ble.gap_advertise(interval_us, adv_data=self._payload)
time.sleep_ms(100)
print("sd_adv",self._sd_adv)
if self._sd_adv is not None:
print("sdddd_adv",self._sd_adv)
self._ble.gap_advertise(interval_us, adv_data=self._sd_adv)
# File: main.py
from ble_lightsensor import BLELightSensor
from lightsensor import LightSensor
import time
import bluetooth
def main():
ble = bluetooth.BLE()
ble.active(True)
ble_light = BLELightSensor(ble)
light = LightSensor(35)
light_density = light.value()
i = 0
while True:
# Write every second, notify every 10 seconds.
i = (i + 1) % 10
ble_light.set_light(light_density, notify=i == 0)
print("Light Lux:", light_density)
light_density = light.value()
time.sleep_ms(1000)
if __name__ == "__main__":
main()
除了上文提到的 3 个 Python 脚本,还需要一个 ble_advertising.py,可以到 MicroPython 官方的 Bluetooth 例子中获取,地址:https://github.com/micropython/micropython/tree/master/examples/bluetooth
广播 service data 这一功能我调了很久都没成功,最后发现。。。。:
下面是我使用 ble_advertising.py 脚本文件:
# Helpers for generating BLE advertising payloads.
from micropython import const
import struct
import bluetooth
# Advertising payloads are repeated packets of the following form:
# 1 byte data length (N + 1)
# 1 byte type (see constants below)
# N bytes type-specific data
_ADV_TYPE_FLAGS = const(0x01)
_ADV_TYPE_NAME = const(0x09)
_ADV_TYPE_UUID16_COMPLETE = const(0x3)
_ADV_TYPE_UUID32_COMPLETE = const(0x5)
_ADV_TYPE_UUID128_COMPLETE = const(0x7)
_ADV_TYPE_UUID16_MORE = const(0x2)
_ADV_TYPE_UUID32_MORE = const(0x4)
_ADV_TYPE_UUID128_MORE = const(0x6)
_ADV_TYPE_APPEARANCE = const(0x19)
_ADV_TYPE_SERVICE_DATA = const(0x16)
# Generate a payload to be passed to gap_advertise(adv_data=...).
def advertising_payload(limited_disc=False, br_edr=False, name=None, services=None, appearance=0, service_data = None):
payload = bytearray()
def _append(adv_type, value):
nonlocal payload
payload += struct.pack("BB", len(value) + 1, adv_type) + value
_append(
_ADV_TYPE_FLAGS,
struct.pack("B", (0x01 if limited_disc else 0x02) + (0x18 if br_edr else 0x04)),
)
if name:
_append(_ADV_TYPE_NAME, name)
if services:
for uuid in services:
b = bytes(uuid)
if len(b) == 2:
_append(_ADV_TYPE_UUID16_COMPLETE, b)
elif len(b) == 4:
_append(_ADV_TYPE_UUID32_COMPLETE, b)
elif len(b) == 16:
_append(_ADV_TYPE_UUID128_COMPLETE, b)
# See org.bluetooth.characteristic.gap.appearance.xml
if appearance:
_append(_ADV_TYPE_APPEARANCE, struct.pack("<h", appearance))
if service_data:
_append(_ADV_TYPE_SERVICE_DATA, service_data)
return payload
def decode_field(payload, adv_type):
i = 0
result = []
while i + 1 < len(payload):
if payload[i + 1] == adv_type:
result.append(payload[i + 2 : i + payload[i] + 1])
i += 1 + payload[i]
return result
def decode_name(payload):
n = decode_field(payload, _ADV_TYPE_NAME)
return str(n[0], "utf-8") if n else ""
def decode_services(payload):
services = []
for u in decode_field(payload, _ADV_TYPE_UUID16_COMPLETE):
services.append(bluetooth.UUID(struct.unpack("<h", u)[0]))
for u in decode_field(payload, _ADV_TYPE_UUID32_COMPLETE):
services.append(bluetooth.UUID(struct.unpack("<d", u)[0]))
for u in decode_field(payload, _ADV_TYPE_UUID128_COMPLETE):
services.append(bluetooth.UUID(u))
return services
def demo():
payload = advertising_payload(
name="micropython",
services=[bluetooth.UUID(0x181A), bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")],
)
print(payload)
print(decode_name(payload))
print(decode_services(payload))
if __name__ == "__main__":
demo()
第六步:验证光照传感器
接下来我们需要验证设备有没有正常工作,首先使用串口终端查看程序运行情况,终端上打印了设备发送的 Service Data,以及光照强度和光照传感器 ADC 值:

接下来用手机下载一个蓝牙调试 APP,原文推荐了 3 款(LightBlue、nRFConnect、BLEScanner),也可以随便下一个其他类似的软件。
设备的蓝牙名称为 “Nodemcu”,这是在 BLELightSensor 类的 __init__() 函数中设定的,

查看蓝牙的广播包,感觉有点问题。。。(难道是调试助手的问题?)

边栏推荐
- In the future, cloud expansion technology is expected to be selected as a specialized, special and new enterprise in Shanghai
- JVM|运行时数据区;程序计数器(PC寄存器);
- 实验10 Bezier曲线生成-实验提高-控制点生成B样条曲线
- JVM class loader; Parental delegation mechanism
- Release of version 5.6 of rainbow, add multiple installation methods, and optimize the topology operation experience
- LeetCode-32-最长有效括号
- LeetCode-43-字符串相乘
- RPA+低代码为何是加速财务数字化转型之利器?
- Test plans and test cases
- Leetcode-155-minimum stack
猜你喜欢

Leetcode-32- longest valid bracket
![BZOJ3189 : [Coci2011] Slika](/img/46/c3aa54b7b3e7dfba75a7413dfd5b68.png)
BZOJ3189 : [Coci2011] Slika

Leetcode-155-minimum stack

EndnoteX9簡介及基本教程使用說明

Jenkins+allure integrated report construction

网络连接正常但百度网页打不开显示无法访问此网站解决方案

The network connection is normal, but Baidu web page can not be opened and displayed. You can't access this website solution

Codeworks round 744 (Div. 3) problem solving Report

servlet获取表单数据

The upcoming launch of the industry's first retail digital innovation white paper unlocks the secret of full link digital success
随机推荐
The network connection is normal, but Baidu web page can not be opened and displayed. You can't access this website solution
Jenkins+allure integrated report construction
189. 轮转数组
Builder pattern
Answer fans' questions | count the number and frequency of letters in the text
一步步把 SAP UI5 应用部署到 SAP BTP Kyma 运行环境中去
Supplementary questions for the training ground on September 11, 2021
[Part 14] source code analysis and application details of completionservice class [key]
BZOJ3189 : [Coci2011] Slika
Introduction à endnotex9 et instructions pour les tutoriels de base
How does the chief financial officer of RPA find the "super entrance" of digital transformation?
JVM|前言介绍
行而不辍,未来可期|云扩科技入选上海市专精特新企业
JVM class loader; Parental delegation mechanism
Software test plan
快速排序的非递归写法
如何使用 SAP Kyma 控制台手动发送 SAP Commerce Cloud Mock 应用暴露的事件
快速排序的优化
领先企业推进智慧财务的同款效率工具,赶快了解一下?
Why is rpa+ low code a powerful tool to accelerate the digital transformation of finance?