1. 项目概述与硬件选型

大家好,今天我想分享一个完整的物联网监控系统实战项目,基于ESP32、MicroPython、阿里云和微信小程序。这个系统能够实时采集环境数据(温湿度、光照、距离等),通过阿里云平台进行数据流转,最终在微信小程序上展示和控制。我自己在实际项目中多次使用这个方案,稳定性相当不错,特别适合智能家居、环境监测这类场景。

先说说为什么选择ESP32作为主控芯片。ESP32是乐鑫推出的一款高性能Wi-Fi+蓝牙双模芯片,采用40纳米工艺,功耗低且性能强大。片上集成了天线开关、射频巴伦、功率放大器等功能,外围电路只需要20多个元件就能工作。更重要的是,它的工作温度范围在-40°C到+125°C之间,适应性强,还支持动态电压调整,消除外部电路缺陷。对于物联网项目来说,ESP32既能作为独立系统运行,也能作为其他MCU的从设备,通过SPI/SDIO或I2C、UART等总线提供网络功能。

传感器方面,我选择了DHT11温湿度传感器、HC-SR04超声波测距模块、光敏电阻和WS2812B彩灯。DHT11是数字输出的单总线温湿度传感器,抗干扰能力强,响应速度快,完全标定,直接输出数字信号,不需要额外校准。超声波模块用来测距,通过触发信号控制,自动发送8个40kHz方波,检测回响信号的高电平时间来计算距离。光敏电阻则根据环境光线改变电阻值,光线越强电阻越小,通过ADC读取电压值就能知道光照强度。

2. MicroPython环境搭建与基础编程

MicroPython是Python 3的精简高效实现,专为微控制器设计,用C语言编写,可以在资源受限的设备上运行。它包含了核心Python标准库,也提供了操作硬件的特定模块,比如蓝牙、机器控制等,让嵌入式开发变得像写Python脚本一样简单。

首先,我们需要在ESP32上安装MicroPython固件。通过esptool.py工具可以轻松完成烧录。烧录完成后,就可以用Thonny IDE或者类似的工具来编写和上传代码了。连接Wi-Fi是第一步,MicroPython的network模块让这个过程变得非常简单:

import network
import time

def wifi_connect(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(ssid, password)
    
    max_wait = 10
    while max_wait > 0:
        if wlan.isconnected():
            break
        max_wait -= 1
        print('等待连接...')
        time.sleep(1)
    
    if wlan.isconnected():
        print('连接成功,IP地址:', wlan.ifconfig()[0])
    else:
        print('连接失败')
        raise RuntimeError('网络连接失败')

# 使用示例
wifi_connect('你的Wi-Fi名称', '你的Wi-Fi密码')

传感器读取也很直观。以DHT11为例,MicroPython有现成的库支持,只需要几行代码就能获取温湿度数据:

import dht
import machine

sensor = dht.DHT11(machine.Pin(4))  # 假设DHT11连接在GPIO4

def read_dht():
    try:
        sensor.measure()
        temp = sensor.temperature()
        hum = sensor.humidity()
        return temp, hum
    except Exception as e:
        print("读取传感器失败:", e)
        return None, None

# 调用示例
temperature, humidity = read_dht()
if temperature is not None:
    print(f"温度: {temperature}°C, 湿度: {humidity}%")

3. 传感器数据采集与处理

传感器数据的准确采集是整个系统的基础。DHT11通过单总线协议通信,主机(ESP32)发送开始信号后,DHT11响应并传输40位数据(16位湿度+16位温度+8位校验和)。校验和正确后,数据才有效。虽然MicroPython封装了这些细节,但了解原理对调试有帮助。

超声波传感器(HC-SR04)的工作方式不同。它需要MCU给TRIG引脚一个至少10us的高电平脉冲,然后模块自动发送8个40kHz的超声波,并检测回波。ECHO引脚输出高电平的持续时间就是超声波从发射到返回的时间。计算距离的公式是:距离 = (高电平时间 * 声速340m/s) / 2。实际代码中,可以用time_pulse_us函数来测量高电平时间:

from machine import Pin, time_pulse_us

class Ultrasonic:
    def __init__(self, trigger_pin, echo_pin):
        self.trigger = Pin(trigger_pin, Pin.OUT)
        self.echo = Pin(echo_pin, Pin.IN)
    
    def distance_cm(self):
        self.trigger.value(0)
        time.sleep_us(5)
        self.trigger.value(1)
        time.sleep_us(10)
        self.trigger.value(0)
        
        pulse_time = time_pulse_us(self.echo, 1, 30000)  # 最大等待30ms
        if pulse_time < 0:
            return -1  # 超时或错误
        
        return (pulse_time * 0.0343) / 2  # 声速每微秒0.0343厘米

# 使用示例
sonic = Ultrasonic(trig_pin=5, echo_pin=18)
distance = sonic.distance_cm()
print(f"距离: {distance} cm")

光敏电阻需要ADC读取。ESP32有多个ADC引脚,可以直接读取模拟电压值。光线越强,电压越高,ADC值越大:

from machine import ADC, Pin

light_sensor = ADC(Pin(34))  # 使用GPIO34
light_sensor.atten(ADC.ATTN_11DB)  # 设置11dB衰减,量程0-3.6V

def read_light():
    return light_sensor.read()  # 返回0-4095的值

light_value = read_light()
print(f"光照强度: {light_value}")

4. 阿里云物联网平台接入

阿里云物联网平台是项目的核心枢纽,负责设备管理、数据收集和指令下发。首先需要在阿里云上创建产品和设备,获取设备的三元组(ProductKey、DeviceName、DeviceSecret),这是设备身份认证的关键。

MQTT协议是物联网最常用的轻量级通信协议,基于发布/订阅模式。设备发布消息到主题(Topic),平台或其他设备订阅主题接收消息。阿里云规定了特定的Topic格式,例如设备上报数据的Topic是/sys/{productKey}/{deviceName}/thing/event/property/post,平台下发指令的Topic是/sys/{productKey}/{deviceName}/thing/service/property/set。

MicroPython中可以使用umqtt.simple库连接阿里云。需要注意的是,阿里云要求使用TLS加密连接,端口为8883:

from umqtt.simple import MQTTClient
import ubinascii
import machine
import time

# 阿里云连接参数
product_key = "你的产品Key"
device_name = "你的设备名称"
device_secret = "你的设备Secret"

server = f"{product_key}.iot-as-mqtt.cn-shanghai.aliyuncs.com"
port = 8883
client_id = f"{device_name}|securemode=2,signmethod=hmacsha256|"
username = f"{device_name}&{product_key}"

# 计算密码(重要)
def calculate_password():
    import hmac
    import hashlib
    content = f"clientId{device_name}deviceName{device_name}productKey{product_key}"
    return hmac.new(device_secret.encode(), content.encode(), hashlib.sha256).hexdigest()

password = calculate_password()

# 连接MQTT
def connect_aliyun():
    client = MQTTClient(client_id, server, port, username, password, keepalive=60)
    client.connect()
    print("连接到阿里云IoT平台")
    return client

# 上报数据
def report_data(client, data):
    topic = f"/sys/{product_key}/{device_name}/thing/event/property/post"
    payload = {
        "id": 123,
        "version": "1.0",
        "params": data,
        "method": "thing.event.property.post"
    }
    client.publish(topic, str(payload).replace("'", '"'))
    print("数据已上报:", data)

# 示例使用
try:
    client = connect_aliyun()
    sensor_data = {"Temperature": 25, "Humidity": 60}
    report_data(client, sensor_data)
except Exception as e:
    print("连接或上报失败:", e)

5. 微信小程序开发与数据展示

微信小程序作为用户界面,需要实时显示传感器数据并提供控制功能。小程序通过WebSocket连接阿里云平台,订阅相关Topic来接收数据。

首先在小程序后台配置服务器域名(阿里云MQTT地址),然后使用WX-MQTT库连接:

// 小程序端MQTT连接
import MQTT from './utils/wx-mqtt.js' // 需要引入第三方库

Page({
  data: {
    temperature: 0,
    humidity: 0,
    connected: false
  },
  
  onLoad() {
    this.connectMQTT()
  },
  
  connectMQTT() {
    const client = MQTT.connect('wxs://your-aliyun-address.mqtt.aliyuncs.com', {
      clientId: 'your-client-id',
      username: 'your-username',
      password: 'your-password'
    })
    
    client.on('connect', () => {
      this.setData({ connected: true })
      console.log('连接成功')
      // 订阅主题
      client.subscribe('/sys/your-productKey/your-deviceName/thing/event/property/post')
    })
    
    client.on('message', (topic, message) => {
      const data = JSON.parse(message)
      if (data.params) {
        this.setData({
          temperature: data.params.Temperature || 0,
          humidity: data.params.Humidity || 0
        })
      }
    })
    
    this.mqttClient = client
  },
  
  controlLED(e) {
    const value = e.detail.value ? 1 : 0
    const payload = {
      "id": Date.now(),
      "version": "1.0",
      "params": { "LEDSwitch": value },
      "method": "thing.service.property.set"
    }
    this.mqttClient.publish(
      '/sys/your-productKey/your-deviceName/thing/service/property/set',
      JSON.stringify(payload)
    )
  },
  
  onUnload() {
    if (this.mqttClient) {
      this.mqttClient.end()
    }
  }
})

小程序界面可以用简单的组件展示数据和控制按钮:

<view class="container">
  <text>物联网监控系统</text>
  <view class="data-card">
    <text>温度: {{temperature}}°C</text>
    <text>湿度: {{humidity}}%</text>
  </view>
  <view class="control-area">
    <text>LED控制</text>
    <switch checked="{{ledStatus}}" bindchange="controlLED" />
  </view>
</view>

6. 系统集成与数据流转

整个系统的数据流转路径是:ESP32采集传感器数据 → 通过MQTT发布到阿里云 → 阿里云通过规则引擎流转数据 → 微信小程序订阅数据并展示。同时,小程序的控制指令反向流转到ESP32。

阿里云规则引擎是关键环节,它负责将设备上报的数据转发到其他服务。在阿里云控制台创建规则,编写SQL处理数据:

-- 示例SQL,处理上报数据
SELECT 
    deviceName() as deviceName,
    timestamp('yyyy-MM-dd HH:mm:ss') as time,
    params.Temperature as temperature,
    params.Humidity as humidity
FROM 
    "/sys/+/+/thing/event/property/post"

然后设置数据目的地,可以是另一个Topic、数据库或函数计算。对于小程序访问,最好将数据转发到另一个Topic,比如/user/your-app/data,小程序订阅这个Topic获取数据。

ESP32端需要同时订阅控制Topic,接收小程序指令:

def subscribe_control(client):
    control_topic = f"/sys/{product_key}/{device_name}/thing/service/property/set"
    client.subscribe(control_topic)

def handle_message(topic, msg):
    print("收到消息:", topic, msg)
    try:
        data = json.loads(msg)
        if 'params' in data and 'LEDSwitch' in data['params']:
            led_value = data['params']['LEDSwitch']
            control_led(led_value)  # 控制实际LED
    except Exception as e:
        print("处理消息错误:", e)

# 在主循环中设置回调
client.set_callback(handle_message)
subscribe_control(client)

# 保持连接并等待消息
while True:
    client.check_msg()
    time.sleep(1)

7. 实际应用与优化建议

在实际部署中,我发现有几个点需要特别注意。首先是网络稳定性,ESP32的Wi-Fi连接有时会断开,需要添加重连机制:

def ensure_connected(client, max_retries=5):
    for i in range(max_retries):
        try:
            if not client.is_connected():
                client.connect()
                print("MQTT重新连接成功")
                subscribe_control(client)  # 重新订阅
                return True
        except Exception as e:
            print(f"重连失败 {i+1}/{max_retries}: {e}")
            time.sleep(5)
    return False

# 在主循环中使用
if not client.is_connected():
    if not ensure_connected(client):
        machine.reset()  # 严重错误,重启设备

其次是数据上报频率,过于频繁会浪费资源,过于稀疏会丢失细节。建议根据数据变化程度动态调整:

last_report_time = 0
report_interval = 10000  # 10秒

def should_report(now, last_time, interval, values_changed):
    # 时间到了或者数据变化大时上报
    return (now - last_time >= interval) or values_changed

current_time = time.ticks_ms()
if should_report(current_time, last_report_time, report_interval, data_changed):
    report_data(client, sensor_data)
    last_report_time = current_time

电源管理也很重要,特别是电池供电的场景。ESP32支持轻睡眠模式,可以显著降低功耗:

import esp32
from machine import Pin

# 设置唤醒源(比如按键)
esp32.wake_on_ext0(pin=Pin(4), level=0)

# 进入睡眠
print("进入睡眠,10秒后唤醒")
machine.deepsleep(10000)  # 毫秒

最后是错误处理,网络操作很容易失败,必须有完善的异常处理:

def safe_report(client, data, max_retries=3):
    for attempt in range(max_retries):
        try:
            report_data(client, data)
            return True
        except Exception as e:
            print(f"上报失败 {attempt+1}/{max_retries}: {e}")
            time.sleep(2)
    return False

# 使用示例
if not safe_report(client, sensor_data):
    print("上报严重失败,需要检查连接")

这个系统我已经在实际项目中使用了半年多,稳定性很好。最初版本遇到过多线程问题,后来改为单线程事件循环,避免了大部分并发bug。数据上报频率根据环境变化动态调整,安静时每分钟报一次,变化剧烈时每5秒一次,平衡了实时性和能耗。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐