基于Python的智能家居控制系统开发设备联动与场景管理插图

基于Python的智能家居控制系统开发:从设备联动到场景管理实战

大家好,作为一名折腾智能家居多年的开发者,我常常觉得市面上的中心化控制系统要么太“重”,要么不够灵活。于是,我决定用Python自己动手,打造一个轻量、可高度定制化的智能家居控制核心。今天,我就和大家分享一下如何用Python实现设备联动与场景管理,过程中踩过的坑和收获的惊喜,都会一一奉上。

我的核心思路是:用一个Python服务作为“大脑”,通过各厂商的开放API或本地协议(如MQTT)与设备通信,然后基于事件驱动模型,实现“如果...就...”的联动规则和复杂的场景一键执行。这个方案的优势在于,你可以完全掌控逻辑,无缝集成不同品牌的设备,甚至接入一些“野生”的DIY硬件。

一、搭建基础:环境与通信框架

首先,我们需要一个可靠的通信基础。我选择了 asyncio 来处理并发,用 aiohttp 调用HTTP API,用 paho-mqttasyncio-mqtt 来连接那些支持本地MQTT的设备(比如很多ESPHome或Tasmota固件的设备)。

第一步,创建项目并安装核心依赖:

mkdir smart_home_brain && cd smart_home_brain
python -m venv venv
source venv/bin/activate  # Windows: venvScriptsactivate
pip install aiohttp asyncio-mqtt pydantic

接下来,我们定义一个基础的设备模型。使用 pydantic 来做数据验证和序列化非常方便:

# models/device.py
from pydantic import BaseModel
from typing import Any, Optional
from enum import Enum

class DeviceType(Enum):
    LIGHT = "light"
    SWITCH = "switch"
    SENSOR = "sensor"
    CLIMATE = "climate"

class Device(BaseModel):
    id: str
    name: str
    type: DeviceType
    state: dict[str, Any] = {}  # 如 {"on": True, "brightness": 255}
    attributes: dict[str, Any] = {} # 如 {"ip": "192.168.1.100", "model": "yeelight"}
    provider: str  # 设备来源,如 "yeelight_local", "mqtt_esphome"

    def update_state(self, new_state: dict):
        """更新设备状态"""
        self.state.update(new_state)
        print(f"[Device] {self.name} 状态更新: {self.state}")

踩坑提示:一开始我把状态更新做得太复杂,试图对比每一次变化。后来发现,对于联动逻辑,通常只需要知道最新状态。所以一个简单的字典更新就够了,但记得要深拷贝如果需要历史记录。

二、核心引擎:事件总线与联动规则

智能家居的“智能”体现在自动化联动上。我设计了一个简单的事件总线(Event Bus)来解耦设备状态变化和联动动作。当传感器状态改变、时间到达或手动触发时,就向总线发布一个事件。联动规则引擎监听这些事件,并执行预定义的动作。

# core/event_bus.py
import asyncio
from typing import Callable, Any
import logging

logger = logging.getLogger(__name__)

class EventBus:
    def __init__(self):
        self._listeners = {}

    def on(self, event_type: str, callback: Callable):
        """注册事件监听器"""
        if event_type not in self._listeners:
            self._listeners[event_type] = []
        self._listeners[event_type].append(callback)

    async def emit(self, event_type: str, **data):
        """异步触发事件"""
        logger.info(f"事件触发: {event_type}, 数据: {data}")
        if event_type in self._listeners:
            for callback in self._listeners[event_type]:
                try:
                    # 异步执行回调,避免阻塞
                    asyncio.create_task(callback(**data))
                except Exception as e:
                    logger.error(f"执行事件 {event_type} 的回调失败: {e}")

# 定义一些标准事件类型
EVENT_DEVICE_STATE_CHANGED = "device.state_changed"
EVENT_TIME_SCHEDULE = "time.schedule"
EVENT_MANUAL_TRIGGER = "manual.trigger"

有了事件总线,我们就可以定义联动规则了。我将规则定义为“触发器(Trigger)+ 条件(Condition)+ 动作(Action)”的模式。

# core/rule_engine.py
from core.event_bus import EventBus, EVENT_DEVICE_STATE_CHANGED
from models.device import Device

class Rule:
    def __init__(self, name, trigger, condition=None, actions=None):
        self.name = name
        self.trigger = trigger  # 例如: {"type": EVENT_DEVICE_STATE_CHANGED, "device_id": "motion_sensor", "state": {"motion": True}}
        self.condition = condition or (lambda **kwargs: True) # 可选的判断函数
        self.actions = actions or [] # 要执行的动作函数列表

    async def evaluate(self, event_data):
        """评估规则是否执行"""
        # 检查触发器类型和设备ID是否匹配
        if event_data.get('type') != self.trigger['type']:
            return False
        if event_data.get('device_id') != self.trigger.get('device_id'):
            return False
        # 检查状态条件(简单实现,可扩展)
        trigger_state = self.trigger.get('state', {})
        for key, value in trigger_state.items():
            if event_data.get('state', {}).get(key) != value:
                return False
        # 执行自定义条件判断
        if not self.condition(**event_data):
            return False
        # 执行所有动作
        for action in self.actions:
            await action(**event_data)
        return True

class RuleEngine:
    def __init__(self, event_bus: EventBus):
        self.event_bus = event_bus
        self.rules = []
        # 监听所有设备状态变化事件
        self.event_bus.on(EVENT_DEVICE_STATE_CHANGED, self._handle_event)

    async def _handle_event(self, **event_data):
        for rule in self.rules:
            await rule.evaluate(event_data)

    def add_rule(self, rule: Rule):
        self.rules.append(rule)
        print(f"[RuleEngine] 规则已添加: {rule.name}")

实战经验:这里的条件判断(condition)我设计成了一个可调用的函数,这带来了极大的灵活性。例如,你可以写一个条件函数,只在工作日晚上7点后且室外天暗了(通过光照传感器判断)才执行开灯动作。

三、设备集成与场景管理实战

现在,我们把设备、事件和规则串联起来。假设我们集成了一个Yeelight智能灯和一个DIY的人体传感器(通过MQTT上报)。

首先,写一个设备管理器来统一管理所有设备实例:

# managers/device_manager.py
class DeviceManager:
    def __init__(self, event_bus: EventBus):
        self.devices = {}  # device_id -> Device object
        self.event_bus = event_bus

    def register_device(self, device: Device):
        self.devices[device.id] = device
        print(f"[DeviceManager] 设备注册: {device.name}({device.id})")

    async def update_device_state(self, device_id: str, new_state: dict):
        if device_id in self.devices:
            device = self.devices[device_id]
            old_state = device.state.copy()
            device.update_state(new_state)
            # 状态变化时,触发事件
            await self.event_bus.emit(
                EVENT_DEVICE_STATE_CHANGED,
                type=EVENT_DEVICE_STATE_CHANGED,
                device_id=device_id,
                device_name=device.name,
                old_state=old_state,
                state=new_state
            )

然后,我们定义一个具体的“回家场景”。这个场景由一条规则触发:当人体传感器在晚上6点到12点之间检测到运动时,且客厅灯是关闭状态,就自动开启客厅灯并调到舒适亮度。

# scenes/back_home_scene.py
import asyncio
from datetime import datetime, time

async def turn_on_light(device_id, brightness=300, **kwargs):
    # 这里应该包含调用真实设备API的代码,例如通过Yeelight的LAN控制协议
    print(f"[动作] 正在打开灯 {device_id}, 亮度 {brightness}")
    # 模拟API调用
    # async with aiohttp.ClientSession() as session:
    #     async with session.post(f'http://{device_ip}/api/light', json={"on": True, "bri": brightness}) as resp:
    #         pass
    await asyncio.sleep(0.1) # 模拟网络延迟

def is_evening_and_weekday(**kwargs):
    """条件函数:判断是否是工作日晚上"""
    now = datetime.now()
    is_weekday = now.weekday() < 5  # 0-4是周一到周五
    evening_start = time(18, 0)
    evening_end = time(23, 59)
    return is_weekday and evening_start <= now.time() <= evening_end

# 构建规则
back_home_rule = Rule(
    name="晚上回家自动开灯",
    trigger={
        "type": EVENT_DEVICE_STATE_CHANGED,
        "device_id": "livingroom_motion_sensor",
        "state": {"motion": True}
    },
    condition=is_evening_and_weekday, # 附加时间条件
    actions=[
        lambda **data: turn_on_light("yeelight_desklamp", brightness=300)
    ]
)

踩坑提示:在动作函数中直接进行网络IO(如HTTP请求)时,一定要使用异步函数(async def)和await,否则会阻塞整个事件循环,导致系统响应变慢。我最初用了同步请求,当同时触发多个规则时,系统几乎卡死。

四、系统组装与未来展望

最后,我们在主程序中把所有模块组装起来:

# main.py
import asyncio
from core.event_bus import EventBus
from core.rule_engine import RuleEngine, Rule
from managers.device_manager import DeviceManager
from models.device import Device, DeviceType
from scenes.back_home_scene import back_home_rule

async def main():
    # 1. 初始化核心组件
    event_bus = EventBus()
    device_manager = DeviceManager(event_bus)
    rule_engine = RuleEngine(event_bus)

    # 2. 模拟注册一些设备
    desk_lamp = Device(id="yeelight_desklamp", name="书桌灯", type=DeviceType.LIGHT, provider="yeelight")
    motion_sensor = Device(id="livingroom_motion_sensor", name="客厅人体传感器", type=DeviceType.SENSOR, provider="mqtt")
    device_manager.register_device(desk_lamp)
    device_manager.register_device(motion_sensor)

    # 3. 添加场景规则
    rule_engine.add_rule(back_home_rule)

    print("智能家居控制系统启动成功!")
    # 4. 模拟传感器触发事件
    print("模拟:人体传感器检测到运动...")
    await device_manager.update_device_state("livingroom_motion_sensor", {"motion": True})

    # 保持主循环运行,在实际应用中这里会连接MQTT、启动Web服务器等
    await asyncio.sleep(2)

if __name__ == "__main__":
    asyncio.run(main())

运行这个程序,你会看到规则被成功触发,执行了开灯动作。当然,这是一个高度简化的演示。在实际项目中,你还需要:

  1. 持久化:将设备配置和联动规则保存到数据库或文件中。
  2. 更丰富的触发器:集成定时触发器、天气触发器(调用天气API)等。
  3. 用户界面:可以搭配一个简单的Web界面(用FastAPI或Flask)来添加设备和编辑规则。
  4. 稳定性:增加重试机制、异常处理和健康检查。

通过这个项目,我不仅实现了符合自己习惯的自动化,更重要的是,我完全掌控了家里的“数字神经中枢”。它可能没有商业系统那么华丽,但每一行代码都知其所以然,这种成就感是无与伦比的。希望这篇教程能给你带来启发,欢迎一起交流,在开源社区里完善这个“智慧大脑”!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。