【得捷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)
# 连接至wifi
def connect_wifi():
# 不断尝试连接到配置的WIFI
while not station.isconnected():
print("Connecting...")
station.connect(wifi_ssid, wifi_password) # 连接至配置的WIFI AP
time.sleep(10)
# 连接成功,输出IP地址
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信号质量
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}' # 信号强度
}
# print(ujson.dumps(json_data))
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():
# 连接wifi
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:
# client.wait_msg()
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任务
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) # 设定 PWM 频率为 10KHz
curtain_reverse_pwm = machine.PWM(machine.Pin(47)) # 窗帘逆向控制(关闭)
curtain_reverse_pwm.freq(10000) # 设定 PWM 频率为 10KHz
station = network.WLAN(network.STA_IF)
station.active(True)
# 连接至wifi
def connect_wifi():
# 不断尝试连接到配置的WIFI
while not station.isconnected():
print("Connecting...")
station.connect(wifi_ssid, wifi_password) # 连接至配置的WIFI AP
time.sleep(10)
# 连接成功,输出IP地址
print("WIFI连接成功,IP地址:", station.ifconfig()[0])
'''
MQTT 连接
'''
# 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:
# 窗帘控制使用百分比数值,需要转换为占空比数值,相差10.23倍。因此简单相乘即可
curtain_state *= 10
else: # 非百分比范围,重置值
curtain_state = 0
# 窗帘有三种状态:打开,静止,关闭。其中打开和关闭状态可以通过PWM进行控制。
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: # 消息为0即为关闭状态
print("关闭换气扇")
ventilator_pin.off()
else: # 非0即为打开状态
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():
# 连接wifi
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:
# client.wait_msg()
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信号质量
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任务
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)
# button.switch_to_input(pull=digitalio.Pull.UP)
""" 递推平均滤波法(又称滑动平均滤波法)"""
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主题 ###
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事件 ###
'''
MQTT 连接成功
'''
def connected(client, userdata, flags, rc):
# This function will be called when the client is connected
# successfully to the broker.
print(f"Connected to MQTT broker [{mqtt_host}:{mqtt_port}]! Listening for topic changes on {curtain_topic}, {ventilator_topic}")
# Subscribe to all changes on the onoff_feed.
# client.subscribe(curtain_topic)
'''
MQTT 连接断开
'''
def disconnected(client, userdata, rc):
# This method is called when the client is disconnected
print(f"Disconnected from MQTT broker [{mqtt_host}:{mqtt_port}]!")
'''
接收到订阅主题消息
'''
def message(client, topic, message):
# This method is called when a topic the client is subscribed to
# has a new 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():
### MQTT 连接配置 ###
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文件服务器
|