【得捷Follow me第4期】综合实践之智能家居控制器
[复制链接]
本帖最后由 鲜de芒果 于 2024-2-22 16:27 编辑
1 任务说明
这次我准备使用 W5500-EVB-Pico 作为一个控制器, 通过 MQTT 协议控制智能家居中的其它设备。
2 硬件准备
本综合实践使用到的器件有:
- ESP32-C3-Mini-1U模块及开发板 + DHT11温湿度传感器模块 + ENS160空气质量传感器模块 组成的卧室环境与空气质量数据采集。
- W5500-EVB-Pico + 摇杆电位器 组成的控制中心,用于控制窗帘的打开与关闭以及换气扇的打开与关闭。
- ESP32-S3模块与开发板 + 减速马达 + 继电器 组成的智能家居控制设备。用于控制窗帘的打开与关闭以及换气扇的打开与关闭。
该项目使用的三个模块之间相互独立,通过对接到智能家居总控平台实现联动控制。
3 智能家居平台对接
与智能家居平台对接使用MQTT协议,ESP32C3 连接到WIFI后,通过 MQTT 将传感器数据上报至智能家居平台。
想要实现任务介绍中的功能,首先需要在智能家居平台建立相应的设备,并建立接收环境数据相应的 MQTT 主题和与之相对应的环境数据上报的JSON数据格式即可实现对接。这里我定义的数据结构为:
- {
- "temperature": "16.75", // 温度
- "humidity": "69.82", // 湿度
- "aqi": 2, // 空气质量等级(Air Quality Index)
- "tvoc": 343, // 有机挥发物浓度(Total Volatile Organic Compounds)
- "eCO2": 842, // 二氧化碳浓度
- "wifi_rssi": "-41.00" // WIFI信号强度
- }
4 功能代码
4.1 环境数据采集
环境数据采集的流程比较简单,只需要初始化好I2C,然后通过I2C读取空气质量数据。使用MQTT将预定格式的数据上报至智能家居平台即可。如需要根据空气质量情况自动控制其它智能家居设备联动的话,只需要在智能家居平台上进行配置相应的执行逻辑即可。例如:室内空气中二氧化碳浓度过高,则自动打开换气扇或新风机。直到室内二氧化碳浓度降低至预定值再关闭。
功能实现采用 MicroPython 开发,固件使用 ESP32_GENERIC_C3 通用固件,实现代码如下:
- import dht
- import time
- import ujson
- import asyncio
- import machine
- import network
- import ubinascii
-
- from button import Button
- from ENS160 import myENS160
- from umqtt.simple import MQTTClient
-
- report_interval = 10
-
- wifi_ssid = "@PHICOMM_34"
- wifi_password = "12345678"
-
- station = network.WLAN(network.STA_IF)
- station.active(True)
-
- def connect_wifi():
-
- while not station.isconnected():
- print("Connecting...")
- station.connect(wifi_ssid, wifi_password)
- time.sleep(10)
-
-
- print("WIFI连接成功,IP地址:", station.ifconfig()[0])
-
-
- dht11_sensor=dht.DHT11(machine.Pin(9))
- ens160_sersor=myENS160()
-
- def get_sensor_data():
- temperature = 0
- humidity = 0
- aqi = 0
- tvoc = 0
- eCO2 = 0
- wifi_rssi = 0
-
-
- dht11_sensor.measure()
- temperature=dht11_sensor.temperature()
- humidity=dht11_sensor.humidity()
-
- tvoc=ens160_sersor.getTVOC()
- aqi=ens160_sersor.getAQI()
- eCO2=ens160_sersor.getECO2()
-
- wifi_rssi = station.status('rssi')
-
- json_data = {
- 'temperature': f'{temperature:.2f}',
- 'humidity': f'{humidity:.2f}',
- 'aqi': aqi,
- 'tvoc': tvoc,
- 'eCO2': eCO2,
- 'wifi_rssi': f'{wifi_rssi:.2f}'
- }
-
- return ujson.dumps(json_data)
-
-
- '''
- MQTT 连接
- '''
- client = None
- CLIENT_ID = ubinascii.hexlify(machine.unique_id())
- MQTT_SERVER = '192.168.2.120'
- async def mqtt_client():
-
- connect_wifi()
- print("connected wifi")
- global client
- client = MQTTClient(client_id=CLIENT_ID, server=MQTT_SERVER,user="esp32c3",password="123456")
- client.connect()
- print("mqtt client connected")
- while True:
-
- await asyncio.sleep_ms(100)
-
-
-
- async def report_sensor_data():
- print("report_sensor_data()")
- while True:
- await asyncio.sleep(report_interval)
-
-
- data = get_sensor_data()
- print("publish: ", data)
- client.publish(b"sensor/esp32c3/bedroom/state", data)
-
-
- async def main():
- tasks = []
-
-
- mqtt_task = asyncio.create_task(
- mqtt_client()
- )
- tasks.append(mqtt_task)
-
-
- report_task = asyncio.create_task(
- report_sensor_data()
- )
- tasks.append(report_task)
-
-
- await asyncio.gather(i for i in tasks)
-
-
- if __name__ == "__main__":
- print("run in main")
-
- asyncio.run(main())
4.2 窗帘与换气扇控制设备
窗帘与换气扇设备控制使用自己打样的控制板,基于 ESP32-S3 模块制作而成,板载有两路继电器模块,一路减速马达驱动模块等。软件实现原理基于MQTT通信,订阅两个控制设备的主题。智能家居平台中的其它设备需要控制窗帘和换气扇时,向相应的主题发送指定即可实现联动控制。
- 窗帘控制:窗帘的控制有三种状态,打开、关闭、停止。而控制窗帘的打开与关闭使用的是减速电机,因此设计时使用 -100 ~ 100 表示正反转的占空比,当值为0时表示窗帘的减速电机停止动作。正值表示窗帘进行关闭动作,负值表示窗帘打开动作。正负值越大,占空比越高,减速电机转动越快。基于以上逻辑,需要打开窗帘时,向 MQTT 主题【device/esp32s3/curtain/control】发送 -100 ~ 100 之间的数值即可控制窗帘动作。
- 换气扇控制:换气扇控制设计的比较简单,只有两种状态,便于理解,指定值为 0,1。0 表示关闭换气扇,1 表示打开换气扇。因此需要控制打开换气扇时,只需要向 MQTT 主题 【device/esp32s3/ventilator/control】发送 1 即可打开换气扇。
功能实现采用 MicroPython 开发,固件使用 ESP32_GENERIC_S3 通用固件,实现代码如下:
- import dht
- import time
- import ujson
- import asyncio
- import machine
- import network
- import ubinascii
-
- from umqtt.simple import MQTTClient
-
- report_interval = 10
-
- wifi_ssid = "@PHICOMM_34"
- wifi_password = "12345678"
-
- ventilator_pin = machine.Pin(46, machine.Pin.OUT, value=0)
- curtain_forward_pwm = machine.PWM(machine.Pin(21))
- curtain_forward_pwm.freq(10000)
- curtain_reverse_pwm = machine.PWM(machine.Pin(47))
- curtain_reverse_pwm.freq(10000)
-
- station = network.WLAN(network.STA_IF)
- station.active(True)
-
- def connect_wifi():
-
- while not station.isconnected():
- print("Connecting...")
- station.connect(wifi_ssid, wifi_password)
- time.sleep(10)
-
-
- print("WIFI连接成功,IP地址:", station.ifconfig()[0])
-
-
- '''
- MQTT 连接
- '''
-
- def message_callback(topic, msg):
- print(topic, msg)
- if topic == MQTT_TOPIC_CURTAIN_CONTROL:
- curtain_state = int(msg.decode())
- print("窗帘控制: %s" %(curtain_state))
- if -100 <= curtain_state <= 100:
-
- curtain_state *= 10
- else:
- curtain_state = 0
-
-
- if 0 == curtain_state:
- print("停止 %d" %curtain_state)
- curtain_forward_pwm.duty_u16(curtain_state)
- curtain_reverse_pwm.duty_u16(curtain_state)
- elif 0 < curtain_state:
- print("关闭 %d" %(abs(curtain_state)))
- curtain_forward_pwm.duty_u16(0)
- curtain_reverse_pwm.duty_u16(abs(curtain_state))
- elif 0 > curtain_state:
- print("打开 %d" %(abs(curtain_state)))
- curtain_forward_pwm.duty_u16(abs(curtain_state))
- curtain_reverse_pwm.duty_u16(0)
- else:
- print("未知")
-
- elif topic == MQTT_TOPIC_VENTILATOR_CONTROL:
- ventilator_state = int(msg.decode())
- print("换气扇控制: %s" %(ventilator_state))
-
- if 0 == ventilator_state:
- print("关闭换气扇")
- ventilator_pin.off()
- else:
- print("打开换气扇")
- ventilator_pin.on()
- else:
- print("未知指令: %s ---> %s" %(topic.decode(), msg.decode()))
-
- client = None
- CLIENT_ID = ubinascii.hexlify(machine.unique_id())
- MQTT_SERVER = '192.168.2.120'
- MQTT_TOPIC_CURTAIN_CONTROL = b"device/esp32s3/curtain/control"
- MQTT_TOPIC_VENTILATOR_CONTROL = b"device/esp32s3/ventilator/control"
- async def mqtt_client():
-
- connect_wifi()
- print("connected wifi")
- global client
- client = MQTTClient(client_id=CLIENT_ID, server=MQTT_SERVER,user="esp32c3",password="123456")
- client.connect()
- print("mqtt client connected")
- client.set_callback(message_callback)
- client.subscribe(MQTT_TOPIC_CURTAIN_CONTROL)
- client.subscribe(MQTT_TOPIC_VENTILATOR_CONTROL)
-
- while True:
-
- await asyncio.sleep_ms(10)
- client.check_msg()
-
-
-
- async def report_sensor_data():
- print("report_sensor_data()")
- while True:
- await asyncio.sleep(report_interval)
-
-
- wifi_rssi = station.status('rssi')
-
-
- data = {
- 'wifi_rssi': f'{wifi_rssi:.2f}'
- }
- print("publish: ", data)
- client.publish(b"sensor/esp32s3/bedroom/state", ujson.dumps(data))
-
-
- async def main():
- tasks = []
-
-
- mqtt_task = asyncio.create_task(
- mqtt_client()
- )
- tasks.append(mqtt_task)
-
-
- report_task = asyncio.create_task(
- report_sensor_data()
- )
- tasks.append(report_task)
-
-
- await asyncio.gather(i for i in tasks)
-
-
- if __name__ == "__main__":
- print("run in main")
-
- asyncio.run(main())
4.3 控制器实现
控制器使用 W5500-EVB-Pico + 摇杆电位器实现,连接网络成功后,读取摇杆电位器与摇杆按键的状态,向 MQTT 相应的主题发送指令值,窗帘与换气扇设备接收到指令后即可控制窗帘或换气扇执行相应动作。
由于窗帘只有一个方向,因此只需要用到摇杆的一个轴即可。这里使用摇杆的 X 轴来控制窗帘左右移动。摇杆按键用于控制换气扇的开关,按下一次打开换气扇,再按下一次关闭换气扇,依此类推。
功能实现采用 CircuitPython 开发,固件使用 wiznet_w5500_evb_pico 官方固件,版本:9.0.0-beta.2 下载地址:https://downloads.circuitpython.org/bin/wiznet_w5500_evb_pico/en_US/adafruit-circuitpython-wiznet_w5500_evb_pico-en_US-9.0.0-beta.2.uf2 实现代码如下:
- import os
- import time
- import board
- import busio
- import asyncio
- import analogio
- import digitalio
- import displayio
- import terminalio
- import adafruit_displayio_ssd1306
-
- from adafruit_display_text import label
- import adafruit_minimqtt.adafruit_minimqtt as MQTT
- import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket
-
- import task2
-
-
- ''' 摇杆引脚初始化 '''
- x = analogio.AnalogIn(board.GP27)
- y = analogio.AnalogIn(board.GP26)
- led = digitalio.DigitalInOut(board.LED)
- led.direction = digitalio.Direction.OUTPUT
- button = digitalio.DigitalInOut(board.GP6)
-
-
-
- """ 递推平均滤波法(又称滑动平均滤波法)"""
- FILTER_N = 12
- x_buf = []
- def filter_x(value):
- global x_buf
- x_buf.append(value)
- if FILTER_N < len(x_buf):
- x_buf = x_buf[1:]
-
- x_sum = sum(x_buf)
- return int(x_sum / FILTER_N)
-
- y_buf = []
- def filter_y(value):
- global y_buf
- y_buf.append(value)
- if FILTER_N < len(y_buf):
- y_buf = y_buf[1:]
-
- y_sum = sum(y_buf)
- return int(y_sum / FILTER_N)
-
- ADC_VALUE_MIN = 0
- ADC_VALUE_MAX = 65535
- RANGE_MIN = -100
- RANGE_MAX = 100
- def map(value, source_min = ADC_VALUE_MIN, source_max = ADC_VALUE_MAX, dest_min = RANGE_MIN, dest_max = RANGE_MAX):
- return (value - source_min) * (dest_max - dest_min) / (source_max - source_min) + dest_min
-
-
-
- mqtt_host = os.getenv("MQTT_HOST")
- mqtt_port = os.getenv("MQTT_PORT")
- mqtt_user = os.getenv("MQTT_USER")
- mqtt_pass = os.getenv("MQTT_PASS")
-
- curtain_topic = "device/esp32s3/curtain/control"
-
- ventilator_topic = "device/esp32s3/ventilator/control"
-
-
-
- '''
- MQTT 连接成功
- '''
- def connected(client, userdata, flags, rc):
-
-
- print(f"Connected to MQTT broker [{mqtt_host}:{mqtt_port}]! Listening for topic changes on {curtain_topic}, {ventilator_topic}")
-
-
-
-
-
- '''
- MQTT 连接断开
- '''
- def disconnected(client, userdata, rc):
-
- print(f"Disconnected from MQTT broker [{mqtt_host}:{mqtt_port}]!")
-
-
- '''
- 接收到订阅主题消息
- '''
- def message(client, topic, message):
-
-
- print(f"New message on topic {topic}: {message}")
- if topic == curtain_topic:
- print(f"synchronizing curtain: {message}")
- elif topic == ventilator_topic:
- print(f"synchronizing ventilator: {message}")
-
- mqtt_client = None
- async def init():
-
- socket.set_interface(task2.eth)
- MQTT.set_socket(socket, task2.eth)
-
- global mqtt_client
- mqtt_client = MQTT.MQTT(
- broker=mqtt_host,
- port=mqtt_port,
- username=mqtt_user,
- password=mqtt_pass
- )
-
-
- mqtt_client.on_connect = connected
- mqtt_client.on_disconnect = disconnected
- mqtt_client.on_message = message
- print("MQTT init done.")
- mqtt_client.connect()
- mqtt_client.subscribe(ventilator_topic)
-
-
- async def monitor_control():
- ventilator_state = False
- while True:
- print("X value is: %d, Y value is: %d" %(map(filter_x(x.value)), map(filter_y(y.value))))
- mqtt_client.publish(curtain_topic, int(map(filter_x(x.value))))
- if not button.value:
- if not ventilator_state:
- led.value = True
- mqtt_client.publish(ventilator_topic, 1)
- else:
- led.value = False
- mqtt_client.publish(ventilator_topic, 0)
-
- ventilator_state = not ventilator_state
-
- await asyncio.sleep_ms(10)
5 效果展示
概览
换气扇控制
窗帘控制
6 总结
RP2040 的 ADC 还是比较能满足摇杆的采样需求,本次使用 W5500-EVB-Pico 作为主控制器,缺点是不能随心所欲地移动。但作为窗帘与换气扇的控制器,将其置于 86 面板中也是一种不错的选择。
7 项目源码
W5500-EVB-Pico.zip
(74.63 KB, 下载次数: 3)
8 演示视频
9 传送门
【得捷Follow me第4期】入门任务:开发环境搭建
【得捷Follow me第4期】基础任务一:完成主控板W5500初始化(静态IP配置)
【得捷Follow me第4期】基础任务二:主控板建立TCPIP或UDP服务器
【得捷Follow me第4期】进阶任务:从NTP服务器同步时间
【得捷Follow me第4期】简易FTP文件服务器
|