作为一名深耕智能家居领域多年的开发者,我深知当前市场上智能家居产品面临的最大痛点——生态割裂。每个品牌都有自己的APP和控制协议,用户不得不安装一堆应用来管理不同设备。这完全违背了"智能"的初衷。今天我要分享的,就是如何用Python+Flask+Vue这套技术栈,打造一个真正统一、开放的智能家居控制系统。
这个系统最核心的价值在于:通过标准化的API接口,将不同品牌、不同协议的智能设备统一接入到一个平台中。用户不再需要记住哪个设备用哪个APP控制,所有操作都能在一个界面完成。我们还会加入能耗监测、场景联动等高级功能,让系统不仅能执行命令,还能主动优化家庭环境。
系统采用经典的前后端分离架构:
code复制[设备层] ←MQTT/HTTP→ [后端服务] ←REST API→ [前端应用]
↑ ↑
[数据库] [数据分析]
后端使用Python+Flask构建,主要负责:
前端使用Vue.js开发,实现:
选择Flask作为后端框架主要基于以下考虑:
Vue.js的优势则在于:
设备接入是系统最基础也最关键的部分。我们设计了通用的设备抽象模型:
python复制class SmartDevice:
def __init__(self, device_id, device_type, protocol):
self.id = device_id
self.type = device_type # 灯光、空调、传感器等
self.protocol = protocol # MQTT/HTTP/CoAP
self.status = {} # 当前状态
self.capabilities = [] # 设备能力
def connect(self):
"""根据协议类型建立连接"""
if self.protocol == 'MQTT':
self.client = mqtt.Client()
self.client.connect(broker_ip)
def update_status(self, new_status):
"""更新设备状态"""
self.status.update(new_status)
db.session.commit()
对于不同协议的设备,我们实现了对应的适配器:
python复制# MQTT设备适配器
class MQTTAdapter:
def __init__(self, topic):
self.topic = topic
def send_command(self, command):
client.publish(f"{self.topic}/cmd", json.dumps(command))
def listen(self, callback):
client.subscribe(f"{self.topic}/status")
client.on_message = callback
场景联动是提升用户体验的关键功能。我们设计了一个基于规则的引擎:
python复制class SceneEngine:
def __init__(self):
self.rules = [] # 存储所有场景规则
def add_rule(self, condition, actions):
"""添加场景规则"""
self.rules.append({
'condition': condition,
'actions': actions
})
def evaluate(self, context):
"""评估所有规则"""
for rule in self.rules:
if eval(rule['condition'], {}, context):
self.execute_actions(rule['actions'])
def execute_actions(self, actions):
"""执行动作序列"""
for action in actions:
device = get_device(action['device_id'])
device.send_command(action['command'])
示例场景规则配置:
json复制{
"name": "回家模式",
"condition": "time.hour >= 18 and context['presence'] == 'arrived'",
"actions": [
{
"device_id": "light_1",
"command": {"power": "on", "brightness": 70}
},
{
"device_id": "ac_1",
"command": {"power": "on", "temp": 26, "mode": "cool"}
}
]
}
使用ECharts实现能耗数据的可视化展示:
javascript复制// 能耗趋势图
function initEnergyChart() {
const chart = echarts.init(document.getElementById('energy-chart'));
const option = {
tooltip: { trigger: 'axis' },
legend: { data: ['用电量', '用水量'] },
xAxis: { type: 'category', data: [] },
yAxis: [{ type: 'value', name: '用电量(kWh)' },
{ type: 'value', name: '用水量(L)' }],
series: [
{
name: '用电量',
type: 'line',
data: [],
smooth: true
},
{
name: '用水量',
type: 'line',
yAxisIndex: 1,
data: [],
smooth: true
}
]
};
// 从API获取数据
fetch('/api/energy?days=7')
.then(res => res.json())
.then(data => {
option.xAxis.data = data.dates;
option.series[0].data = data.electricity;
option.series[1].data = data.water;
chart.setOption(option);
});
}
在原有设计基础上,我们做了以下优化:
sql复制ALTER TABLE device_status
ADD INDEX idx_device_time (device_id, update_time);
sql复制CREATE TABLE sensor_data (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
device_id BIGINT NOT NULL,
data_type VARCHAR(20) NOT NULL,
value FLOAT NOT NULL,
unit VARCHAR(10),
create_time DATETIME NOT NULL,
FOREIGN KEY (device_id) REFERENCES device(id)
) PARTITION BY RANGE (TO_DAYS(create_time)) (
PARTITION p202301 VALUES LESS THAN (TO_DAYS('2023-02-01')),
PARTITION p202302 VALUES LESS THAN (TO_DAYS('2023-03-01')),
...
);
为减轻数据库压力,我们实现了多级缓存:
python复制# 更新设备状态到Redis
def update_device_cache(device_id, status):
r = redis.Redis()
r.hset(f'device:{device_id}', mapping=status)
r.expire(f'device:{device_id}', 3600) # 1小时过期
python复制# 每日能耗统计任务
@app.cli.command('calc-daily-stats')
def calculate_daily_stats():
yesterday = datetime.now() - timedelta(days=1)
stats = {
'total_energy': sum_energy_for_day(yesterday),
'peak_hour': get_peak_hour(yesterday),
'top_devices': get_top_consumers(yesterday, limit=3)
}
save_daily_stats(yesterday, stats)
python复制@app.route('/api/device/register', methods=['POST'])
def register_device():
device_key = request.headers.get('X-Device-Key')
if not verify_device_key(device_key):
return jsonify({'error': 'Invalid device key'}), 401
...
python复制def role_required(role):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if current_user.role != role:
return jsonify({'error': 'Forbidden'}), 403
return f(*args, **kwargs)
return decorated_function
return decorator
python复制# 不好的写法:N+1查询问题
devices = Device.query.all()
for device in devices:
status = DeviceStatus.query.filter_by(device_id=device.id).first()
# 优化写法:使用join一次获取
devices = db.session.query(Device, DeviceStatus).\
outerjoin(DeviceStatus, Device.id == DeviceStatus.device_id).\
all()
python复制from flask_compress import Compress
Compress(app) # 自动压缩响应数据
python复制from celery import Celery
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
@celery.task
def log_device_action(device_id, action):
# 记录设备操作日志
log = DeviceLog(device_id=device_id, action=action)
db.session.add(log)
db.session.commit()
推荐使用Docker Compose进行部署:
yaml复制version: '3'
services:
web:
build: .
ports:
- "5000:5000"
depends_on:
- redis
- mysql
environment:
FLASK_ENV: production
redis:
image: redis:alpine
ports:
- "6379:6379"
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
使用Prometheus+Grafana监控系统健康状态:
python复制from prometheus_flask_exporter import PrometheusMetrics
metrics = PrometheusMetrics(app)
metrics.info('app_info', 'Application info', version='1.0')
在实际开发过程中,我总结了以下几个重要经验:
设备状态可能会因为网络问题与实际状态不同步。解决方案是实现一个状态确认机制:发送控制命令后,设备需要回复状态更新,如果没有收到回复,系统会标记状态为"未知"并尝试重新获取。
python复制# 错误的时区处理
db_time = datetime.now() # 服务器时区
# 正确的做法
from pytz import timezone
user_tz = timezone('Asia/Shanghai')
db_time = datetime.now(user_tz)
这个项目从设计到实现大约用了3个月时间,期间最大的挑战是如何平衡系统的灵活性和易用性。通过采用模块化设计和合理的抽象,最终实现了一个既支持多种设备接入,又保持用户界面简洁的控制系统。