仅作为本人学习《物联网开发实战》的学习笔记,原课程链接:极客时间《物联网开发实战》——郭朝斌

文章目录

  • 第一步:通信技术
  • 第二步:选择开发板
  • 第三步:准备 MicroPython 环境
  • 第四步:搭建光照传感器硬件电路
  • 第五步:编写蓝牙程序
  • 第六步:验证光照传感器

第一步:通信技术

郭老师建议选择 BLE 低功耗蓝牙技术作为光照传感器设备的通信手段。因为传感器的部署位置比较灵活,所以不能直接用电源线,而是需要用到无线通信技术,又因为 BLE 的功耗比 Wi-Fi 低,所以我们选择 BLE 通信技术。

BLE 设备可以在 4 种模式下工作:

  1. 广播模式(Boradcaster),单纯的广播模式。这种模式下设备不可以被连接,只能够以一定的时间间隔把数据广播出来,工其他设备使用,比如手机扫描处理。
  2. 从机模式(Peripheral),这种模式下设备仍然可以广播数据,同时也可以被连接。建立连接后,双方可以进行双向通信。
  3. 主机模式(Central),这种模式下设备不能进行广播,但是可以扫描周围的蓝牙广播包,发现其他设备,然后主动对这些设备进行发起连接。
  4. 观察者模式(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 Pinclass 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_payloadfrom 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 = bleself._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 = Noneself._advertise()def _irq(self, event, data):# Track connections so we can send notifications.if event == _IRQ_CENTRAL_CONNECT:conn_handle, _, _ = dataself._connections.add(conn_handle)elif event == _IRQ_CENTRAL_DISCONNECT:conn_handle, _, _ = dataself._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 = datadef 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 = 0xFE95fc = 0x0010pid = 0x0002fcnt = 0x01mac = self._ble.config('mac')objid = 0x1007objlen = 0x03objval = densityservice_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 bluetoothdef main():ble = bluetooth.BLE()ble.active(True)ble_light = BLELightSensor(ble)light = LightSensor(35)light_density = light.value()i = 0while True:# Write every second, notify every 10 seconds.i = (i + 1) % 10ble_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 payloadpayload += 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.xmlif appearance:_append(_ADV_TYPE_APPEARANCE, struct.pack("<h", appearance))if service_data:_append(_ADV_TYPE_SERVICE_DATA, service_data)return payloaddef decode_field(payload, adv_type):i = 0result = []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 resultdef 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 servicesdef 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__() 函数中设定的,

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

《物联网开发实战》18 场景联动:智能电灯如何感知光线?(上)(学习笔记)相关推荐

  1. “物联网开发实战”学习笔记-(二)手机控制智能电灯

    "物联网开发实战"学习笔记-(二)手机控制智能电灯 如果搭建好硬件平台后,这一次我们的任务主要是调试好智能电灯,并且连接到腾讯云的物联网平台. 腾讯云物联网平台 腾讯物联网平台的优 ...

  2. “物联网开发实战”学习笔记-(四)智能音箱制作和语音控制

    "物联网开发实战"学习笔记-(四)智能音箱制作和语音控制 这次来造一个属于自己的智能音箱,这里详细介绍了智能音箱的语音控制的实现. 智能音箱的技术架构 智能音箱主要涉及拾音.前端信 ...

  3. 【物联网开发实战】- 设备上云方案详解?

    [物联网开发实战]- 如何让设备上云? 物联网开发过程中遇到的第一个棘手问题就是:如何让设备连接上云? 我们以智能洗衣机为例,至少包含:温度/水位等传感器,主控MCU(Microcontroller ...

  4. 课程分享:鸿蒙HarmonyOS系统及物联网开发实战课程(附课程视频及源码下载)

    课程名称: 鸿蒙HarmonyOS系统及物联网开发实战课程 课程介绍: HarmonyOS 是一款面向万物互联时代的.全新的分布式操作系统.在传统的单设备系统能力基础上,HarmonyOS 提出了基于 ...

  5. “物联网开发实战”学习笔记-(五)根据土壤湿度、环境温湿度和光照强度自动浇花

    "物联网开发实战"学习笔记-(五)根据土壤湿度.环境温湿度和光照强度自动浇花 研究场景需求 自动浇花这个场景,很明显是为了自动控制水泵,及时给植物补充水分.同时用户的目的并不只是浇 ...

  6. 《物联网开发实战》09 边缘中心:物联网网关有多重要?(学习笔记)

    仅作为本人学习<物联网开发实战>的学习笔记,原课程链接:极客时间<物联网开发实战>--郭朝斌 不是所有的设备都使用 Wi-Fi 这类可以直接接入互联网从而与云平台通信的通信技术 ...

  7. 物联网开发实战解读 何为杀手级应用?

    作为当下乃至未来信息技术的重要组成,传感器.大数据和云计算的结合,催生了一个万物互联的物联网时代,然而物联网概念早已提出,但始终缺少真正的落地项目,如何打破技术壁垒是每一个技术团队都在思考的问题,本次 ...

  8. 树莓派 Pico仅4美元, IoT物联网开发实战

    树莓派基金会发布了基于一款全新的 RP2040 芯片构建的首款微控制器级产品:Raspberry Pi Pico,售价仅 4 美元. 如果你使用过 Arduino 或支持 MicroPython 的开 ...

  9. 《微信小程序商城开发实战》笔者的新书,欢迎各位粉丝上京东购买

    编辑推荐 在当今移动互联网大潮中,微信应用凭借其庞大的用户基数和极强的用户黏性牢牢地占据着移动App应用的头把交椅之位,据相关统计,微信月活跃用户已达10亿之多,且占据着中国用户30%以上的应用时间, ...

最新文章

  1. iOS - 支持水平 / 垂直显示自动滚动的跑马灯控件 --- SKAutoScrollLabel 的使用和实现
  2. Facebook 应用机器学习团队专访:人工智能在 Facebook 中的应用
  3. 醉酒删库:几杯红酒下肚,7小时数据消失...
  4. centos java程序_刚得到一台centos7服务器,作为Java程序员应该做的事
  5. node-gulp插件
  6. [Swift通天遁地]一、超级工具-(13)使用PKHUD制作各种动态提示窗口
  7. 真香!谷歌终与美国国防部合作,签署百万美金云服务合同
  8. 物联网全面崛起 LED企业大有可为
  9. Ubuntu 16.04安装vsftpd 并开启ftp服务
  10. RxJava Map操作详解
  11. CVE-2017-8046 复现与分析
  12. 逃离北上广:你以为回到小城市就非常幸福了吗?
  13. LEARNING ACTIONABLE REPRESENTATIONS WITH GOAL-CONDITIONED POLICIES
  14. 谷哥学术2022年2月资源分享下载列表 08/20
  15. Variation calling and annotation
  16. 深入浅出 RPC - 浅出篇
  17. SK海力士加入日韩美三国财团 收购东芝芯片业务
  18. vue.js的简单自动求和计算
  19. YUV420存储为BMP和JPG图片
  20. JAVA常用类之Arrays工具类

热门文章

  1. 使用C#版OpenCV进行圆心求取
  2. C#对接----韵达开发平台--取电子面单
  3. 一个很好地下载影像资料的网站
  4. 定制Eclipse的Content assist(代码补全),比如空格键不上屏
  5. SPSS(八)logistic回归(图文+数据集)
  6. Web Worker,Service Worker,Web Worker
  7. Matlab GUI界面编程小白学习笔记
  8. Java 实现文章汉字关键词(违禁词)识别1.0
  9. 每日一课——Linux命令——删除文件与文件夹
  10. 引流方法有几种,推广引流有哪些方法?盘点8种最有效的引流方法