Python项目-智能家居控制系统的设计与实现
1. 引言
随着物联网技术的快速发展,智能家居系统已经成为现代家庭生活的重要组成部分。本文将详细介绍一个基于Python的智能家居控制系统的设计与实现过程,该系统能够实现对家庭设备的集中管理和智能控制,提升家居生活的便捷性和舒适度。
2. 系统架构设计
2.1 总体架构
智能家居控制系统采用分层架构设计,主要包括以下几个层次:
- 设备层:各种智能家居设备,如灯光、空调、窗帘、安防设备等
- 通信层:负责设备与控制中心之间的数据传输,支持WiFi、蓝牙、Zigbee等多种通信协议
- 控制层:系统的核心,处理来自用户的指令并转发给相应的设备
- 应用层:提供Web界面和移动应用,供用户进行交互操作
- 智能层:基于机器学习算法,实现系统的自动化决策和智能场景控制
2.2 核心模块设计
系统主要包含以下核心模块:
- 设备管理模块:负责设备的注册、状态监控和控制
- 用户管理模块:处理用户认证、权限管理等功能
- 场景控制模块:实现自定义场景的创建和执行
- 数据分析模块:收集和分析用户行为数据,为智能决策提供支持
- API接口模块:提供RESTful API,支持第三方应用集成
3. 技术选型
3.1 编程语言与框架
- 后端:Python 3.9
- Web框架:Flask 2.0.1
- WebSocket:Flask-SocketIO
- 数据库:SQLite(开发环境)/ PostgreSQL(生产环境)
- ORM:SQLAlchemy
- 前端:HTML5 + CSS3 + JavaScript
- 前端框架:Vue.js 3.0
- UI组件库:Element Plus
3.2 硬件通信技术
- WiFi通信:使用MQTT协议
- 蓝牙通信:使用PyBluez库
- Zigbee通信:通过串口与Zigbee协调器通信
3.3 智能算法
- 用户行为分析:基于Scikit-learn的聚类算法
- 场景推荐:基于协同过滤的推荐算法
- 异常检测:基于统计和机器学习的异常检测算法
4. 系统实现
4.1 设备管理模块实现
设备管理是系统的基础模块,负责管理所有智能设备。以下是设备基类的实现:
# device_manager.py
from abc import ABC, abstractmethod
import uuid
import timeclass Device(ABC):"""智能设备基类"""def __init__(self, name, location, device_type):self.device_id = str(uuid.uuid4())self.name = nameself.location = locationself.type = device_typeself.status = "offline"self.last_updated = time.time()@abstractmethoddef turn_on(self):pass@abstractmethoddef turn_off(self):passdef get_status(self):return {"device_id": self.device_id,"name": self.name,"location": self.location,"type": self.type,"status": self.status,"last_updated": self.last_updated}def update_status(self, status):self.status = statusself.last_updated = time.time()class LightDevice(Device):"""灯光设备类"""def __init__(self, name, location, brightness=100):super().__init__(name, location, "light")self.brightness = brightnessself.color = "white"def turn_on(self):self.update_status("on")return Truedef turn_off(self):self.update_status("off")return Truedef set_brightness(self, brightness):if 0 <= brightness <= 100:self.brightness = brightnessreturn Truereturn Falsedef set_color(self, color):self.color = colorreturn Truedef get_status(self):status = super().get_status()status.update({"brightness": self.brightness,"color": self.color})return statusclass DeviceManager:"""设备管理器"""def __init__(self):self.devices = {}def add_device(self, device):self.devices[device.device_id] = devicereturn device.device_iddef remove_device(self, device_id):if device_id in self.devices:del self.devices[device_id]return Truereturn Falsedef get_device(self, device_id):return self.devices.get(device_id)def get_all_devices(self):return [device.get_status() for device in self.devices.values()]def get_devices_by_type(self, device_type):return [device.get_status() for device in self.devices.values() if device.type == device_type]def get_devices_by_location(self, location):return [device.get_status() for device in self.devices.values() if device.location == location]
4.2 场景控制模块实现
场景控制模块允许用户创建自定义场景,实现多设备的联动控制:
# scene_controller.py
import time
import jsonclass Scene:"""场景类"""def __init__(self, name, description=""):self.scene_id = str(uuid.uuid4())self.name = nameself.description = descriptionself.actions = []self.created_at = time.time()self.last_executed = Nonedef add_action(self, device_id, action, params=None):if params is None:params = {}self.actions.append({"device_id": device_id,"action": action,"params": params})def remove_action(self, index):if 0 <= index < len(self.actions):self.actions.pop(index)return Truereturn Falsedef get_details(self):return {"scene_id": self.scene_id,"name": self.name,"description": self.description,"actions": self.actions,"created_at": self.created_at,"last_executed": self.last_executed}class SceneController:"""场景控制器"""def __init__(self, device_manager):self.scenes = {}self.device_manager = device_managerdef create_scene(self, name, description=""):scene = Scene(name, description)self.scenes[scene.scene_id] = scenereturn scene.scene_iddef delete_scene(self, scene_id):if scene_id in self.scenes:del self.scenes[scene_id]return Truereturn Falsedef get_scene(self, scene_id):return self.scenes.get(scene_id)def get_all_scenes(self):return [scene.get_details() for scene in self.scenes.values()]def execute_scene(self, scene_id):scene = self.scenes.get(scene_id)if not scene:return Falseresults = []for action in scene.actions:device = self.device_manager.get_device(action["device_id"])if device:if action["action"] == "turn_on":result = device.turn_on()elif action["action"] == "turn_off":result = device.turn_off()elif action["action"] == "set_brightness" and hasattr(device, "set_brightness"):result = device.set_brightness(action["params"].get("brightness", 100))elif action["action"] == "set_color" and hasattr(device, "set_color"):result = device.set_color(action["params"].get("color", "white"))else:result = Falseresults.append({"device_id": action["device_id"],"action": action["action"],"success": result})scene.last_executed = time.time()return results
4.3 API接口实现
使用Flask框架实现RESTful API接口:
# app.py
from flask import Flask, request, jsonify
from flask_socketio import SocketIO
from device_manager import DeviceManager, LightDevice
from scene_controller import SceneController
import jsonapp = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*")# 初始化设备管理器和场景控制器
device_manager = DeviceManager()
scene_controller = SceneController(device_manager)# 设备管理API
@app.route('/api/devices', methods=['GET'])
def get_devices():return jsonify(device_manager.get_all_devices())@app.route('/api/devices', methods=['POST'])
def add_device():data = request.jsonif data.get('type') == 'light':device = LightDevice(name=data.get('name', 'New Light'),location=data.get('location', 'Living Room'),brightness=data.get('brightness', 100))device_id = device_manager.add_device(device)return jsonify({"device_id": device_id}), 201return jsonify({"error": "Unsupported device type"}), 400@app.route('/api/devices/<device_id>', methods=['GET'])
def get_device(device_id):device = device_manager.get_device(device_id)if device:return jsonify(device.get_status())return jsonify({"error": "Device not found"}), 404@app.route('/api/devices/<device_id>/control', methods=['POST'])
def control_device(device_id):device = device_manager.get_device(device_id)if not device:return jsonify({"error": "Device not found"}), 404data = request.jsonaction = data.get('action')if action == 'turn_on':result = device.turn_on()elif action == 'turn_off':result = device.turn_off()elif action == 'set_brightness' and hasattr(device, 'set_brightness'):result = device.set_brightness(data.get('brightness', 100))elif action == 'set_color' and hasattr(device, 'set_color'):result = device.set_color(data.get('color', 'white'))else:return jsonify({"error": "Invalid action"}), 400# 通过WebSocket广播设备状态变化socketio.emit('device_update', device.get_status())return jsonify({"success": result})# 场景管理API
@app.route('/api/scenes', methods=['GET'])
def get_scenes():return jsonify(scene_controller.get_all_scenes())@app.route('/api/scenes', methods=['POST'])
def create_scene():data = request.jsonscene_id = scene_controller.create_scene(name=data.get('name', 'New Scene'),description=data.get('description', ''))# 添加场景动作scene = scene_controller.get_scene(scene_id)for action in data.get('actions', []):scene.add_action(device_id=action.get('device_id'),action=action.get('action'),params=action.get('params', {}))return jsonify({"scene_id": scene_id}), 201@app.route('/api/scenes/<scene_id>/execute', methods=['POST'])
def execute_scene(scene_id):results = scene_controller.execute_scene(scene_id)if results is False:return jsonify({"error": "Scene not found"}), 404return jsonify({"results": results})# 主程序入口
if __name__ == '__main__':# 添加一些测试设备living_room_light = LightDevice("客厅主灯", "客厅")bedroom_light = LightDevice("卧室灯", "卧室")device_manager.add_device(living_room_light)device_manager.add_device(bedroom_light)# 创建一个测试场景night_mode_id = scene_controller.create_scene("夜间模式", "睡前自动设置的场景")night_mode = scene_controller.get_scene(night_mode_id)night_mode.add_action(living_room_light.device_id, "turn_off")night_mode.add_action(bedroom_light.device_id, "set_brightness", {"brightness": 30})# 启动服务器socketio.run(app, host='0.0.0.0', port=5000, debug=True)
4.4 前端界面实现
使用Vue.js和Element Plus构建用户界面:
<!-- static/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>智能家居控制系统</title><link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css"><link rel="stylesheet" href="/static/css/style.css">
</head>
<body><div id="app"><el-container><el-header><h1>智能家居控制系统</h1></el-header><el-container><el-aside width="250px"><el-menu default-active="devices"><el-menu-item index="devices" @click="activeTab = 'devices'"><i class="el-icon-cpu"></i><span>设备管理</span></el-menu-item><el-menu-item index="scenes" @click="activeTab = 'scenes'"><i class="el-icon-magic-stick"></i><span>场景控制</span></el-menu-item><el-menu-item index="statistics" @click="activeTab = 'statistics'"><i class="el-icon-data-line"></i><span>数据统计</span></el-menu-item><el-menu-item index="settings" @click="activeTab = 'settings'"><i class="el-icon-setting"></i><span>系统设置</span></el-menu-item></el-menu></el-aside><el-main><!-- 设备管理页面 --><div v-if="activeTab === 'devices'"><el-row :gutter="20"><el-col :span="24"><el-card><template #header><div class="card-header"><span>设备列表</span><el-button type="primary" size="small" @click="showAddDeviceDialog">添加设备</el-button></div></template><el-table :data="devices" style="width: 100%"><el-table-column prop="name" label="设备名称"></el-table-column><el-table-column prop="location" label="位置"></el-table-column><el-table-column prop="type" label="类型"></el-table-column><el-table-column prop="status" label="状态"><template #default="scope"><el-tag :type="scope.row.status === 'on' ? 'success' : 'info'">{{ scope.row.status === 'on' ? '开启' : '关闭' }}</el-tag></template></el-table-column><el-table-column label="操作"><template #default="scope"><el-button size="small" :type="scope.row.status === 'on' ? 'danger' : 'success'"@click="controlDevice(scope.row.device_id, scope.row.status === 'on' ? 'turn_off' : 'turn_on')">{{ scope.row.status === 'on' ? '关闭' : '开启' }}</el-button><el-button size="small" type="primary" @click="showDeviceDetailDialog(scope.row)">详情</el-button></template></el-table-column></el-table></el-card></el-col></el-row></div><!-- 场景控制页面 --><div v-if="activeTab === 'scenes'"><el-row :gutter="20"><el-col :span="24"><el-card><template #header><div class="card-header"><span>场景列表</span><el-button type="primary" size="small" @click="showAddSceneDialog">创建场景</el-button></div></template><el-table :data="scenes" style="width: 100%"><el-table-column prop="name" label="场景名称"></el-table-column><el-table-column prop="description" label="描述"></el-table-column><el-table-column label="操作"><template #default="scope"><el-button size="small" type="success"@click="executeScene(scope.row.scene_id)">执行</el-button><el-button size="small" type="primary" @click="showSceneDetailDialog(scope.row)">详情</el-button></template></el-table-column></el-table></el-card></el-col></el-row></div></el-main></el-container></el-container><!-- 添加设备对话框 --><el-dialog title="添加设备" v-model="addDeviceDialogVisible"><el-form :model="newDevice" label-width="100px"><el-form-item label="设备名称"><el-input v-model="newDevice.name"></el-input></el-form-item><el-form-item label="位置"><el-input v-model="newDevice.location"></el-input></el-form-item><el-form-item label="设备类型"><el-select v-model="newDevice.type" placeholder="请选择设备类型"><el-option label="灯光" value="light"></el-option><el-option label="空调" value="ac" disabled></el-option><el-option label="窗帘" value="curtain" disabled></el-option></el-select></el-form-item></el-form><template #footer><span class="dialog-footer"><el-button @click="addDeviceDialogVisible = false">取消</el-button><el-button type="primary" @click="addDevice">确定</el-button></span></template></el-dialog></div><script src="https://unpkg.com/vue@next"></script><script src="https://unpkg.com/element-plus"></script><script src="https://unpkg.com/axios/dist/axios.min.js"></script><script src="https://cdn.socket.io/4.4.1/socket.io.min.js"></script><script src="/static/js/app.js"></script>
</body>
</html>
// static/js/app.js
const { createApp, ref, onMounted } = Vue;const app = createApp({setup() {const activeTab = ref('devices');const devices = ref([]);const scenes = ref([]);const addDeviceDialogVisible = ref(false);const newDevice = ref({name: '',location: '',type: 'light'});// 初始化WebSocket连接const socket = io();// 监听设备状态更新socket.on('device_update', (data) => {const index = devices.value.findIndex(d => d.device_id === data.device_id);if (index !== -1) {devices.value[index] = data;}});// 获取所有设备const fetchDevices = async () => {try {const response = await axios.get('/api/devices');devices.value = response.data;} catch (error) {console.error('获取设备列表失败:', error);ElMessage.error('获取设备列表失败');}};// 获取所有场景const fetchScenes = async () => {try {const response = await axios.get('/api/scenes');scenes.value = response.data;} catch (error) {console.error('获取场景列表失败:', error);ElMessage.error('获取场景列表失败');}};// 控制设备const controlDevice = async (deviceId, action, params = {}) => {try {await axios.post(`/api/devices/${deviceId}/control`, {action,...params});ElMessage.success(`设备操作成功: ${action}`);} catch (error) {console.error('设备操作失败:', error);ElMessage.error('设备操作失败');}};// 执行场景const executeScene = async (sceneId) => {try {const response = await axios.post(`/api/scenes/${sceneId}/execute`);ElMessage.success('场景执行成功');} catch (error) {console.error('场景执行失败:', error);ElMessage.error('场景执行失败');}};// 添加设备const addDevice = async () => {try {await axios.post('/api/devices', newDevice.value);ElMessage.success('设备添加成功');addDeviceDialogVisible.value = false;fetchDevices();// 重置表单newDevice.value = {name: '',location: '',type: 'light'};} catch (error) {console.error('添加设备失败:', error);ElMessage.error('添加设备失败');}};// 显示添加设备对话框const showAddDeviceDialog = () => {addDeviceDialogVisible.value = true;};// 显示设备详情对话框const showDeviceDetailDialog = (device) => {// 实现详情对话框逻辑console.log('设备详情:', device);};// 显示添加场景对话框const showAddSceneDialog = () => {// 实现添加场景对话框逻辑console.log('添加场景');};// 显示场景详情对话框const showSceneDetailDialog = (scene) => {// 实现场景详情对话框逻辑console.log('场景详情:', scene);};// 页面加载时获取数据onMounted(() => {fetchDevices();fetchScenes();});return {activeTab,devices,scenes,addDeviceDialogVisible,newDevice,controlDevice,executeScene,addDevice,showAddDeviceDialog,showDeviceDetailDialog,showAddSceneDialog,showSceneDetailDialog};}
});app.use(ElementPlus);
app.mount('#app');
5. 系统测试
5.1 单元测试
使用Python的unittest框架对各个模块进行单元测试:
# tests/test_device_manager.py
import unittest
from device_manager import DeviceManager, LightDeviceclass TestDeviceManager(unittest.TestCase):def setUp(self):self.device_manager = DeviceManager()self.light = LightDevice("测试灯", "测试房间")def test_add_device(self):device_id = self.device_manager.add_device(self.light)self.assertIsNotNone(device_id)self.assertEqual(len(self.device_manager.devices), 1)def test_get_device(self):device_id = self.device_manager.add_device(self.light)device = self.device_manager.get_device(device_id)self.assertEqual(device, self.light)def test_remove_device(self):device_id = self.device_manager.add_device(self.light)result = self.device_manager.remove_device(device_id)self.assertTrue(result)self.assertEqual(len(self.device_manager.devices), 0)def test_get_all_devices(self):self.device_manager.add_device(self.light)devices = self.device_manager.get_all_devices()self.assertEqual(len(devices), 1)self.assertEqual(devices[0]["name"], "测试灯")def test_light_device_functions(self):self.light.turn_on()self.assertEqual(self.light.status, "on")self.light.set_brightness(50)self.assertEqual(self.light.brightness, 50)self.light.set_color("blue")self.assertEqual(self.light.color, "blue")self.light.turn_off()self.assertEqual(self.light.status, "off")if __name__ == '__main__':unittest.main()
6. 系统扩展与优化
6.1 安全性增强
-
用户认证与授权:实现JWT认证机制,确保API接口的安全访问
-
HTTPS支持:配置SSL证书,启用HTTPS加密通信
-
设备通信加密:对设备与控制中心之间的通信进行加密
-
日志审计:记录所有关键操作,便于安全审计
6.2 功能扩展
-
语音控制:集成语音识别模块,支持语音指令控制
-
移动应用:开发配套的移动应用,实现随时随地控制
-
智能算法优化:引入更先进的机器学习算法,提升系统智能化水平
-
多协议支持:扩展对更多设备通信协议的支持,如Z-Wave、KNX等
6.3 性能优化
-
异步处理:使用异步框架处理设备通信,提高系统响应速度
-
缓存机制:引入Redis缓存,减轻数据库负担
-
分布式部署:实现系统的分布式部署,提高可扩展性和可用性
-
微服务架构:将系统拆分为多个微服务,便于独立扩展和维护
7. 总结与展望
本文详细介绍了一个基于Python的智能家居控制系统的设计与实现过程。该系统采用分层架构设计,实现了设备管理、场景控制、用户界面等核心功能,并通过多种通信协议支持各类智能设备的接入和控制。
系统的主要特点包括:
-
模块化设计:系统各模块职责明确,便于扩展和维护
-
多协议支持:支持WiFi、蓝牙、Zigbee等多种通信协议
-
智能化控制:基于机器学习算法实现智能场景推荐和自动化控制
-
友好的用户界面:提供Web界面和移动应用,操作简单直观
未来,系统可以在以下方面进行进一步的发展:
-
边缘计算:将部分计算任务下放到边缘设备,减轻中心服务器负担
-
AI增强:引入深度学习和强化学习算法,提升系统智能化水平
-
生态系统集成:与主流智能家居生态系统(如Apple HomeKit、Google Home、Amazon Alexa等)进行集成
-
能源管理:增加能源消耗监控和优化功能,实现绿色节能
智能家居作为物联网的重要应用场景,将随着技术的发展不断演进。本系统为智能家居的实现提供了一种可行的解决方案,希望能为相关领域的研究和实践提供参考。
源代码
Directory Content Summary
Source Directory: ./smart_home_system
Directory Structure
smart_home_system/README.mdrequirements.txtrun.pyapp/database.pydevice_manager.pyscene_manager.py__init__.pyroutes/api.py__init__.pyconfig/static/css/styles.cssjs/app.jstemplates/index.html
File Contents
README.md
# 智能家居控制系统这是一个基于Python的智能家居控制系统,实现了对家庭设备的集中管理和智能控制。## 功能特点- 设备管理:添加、编辑、删除和控制各种智能设备
- 位置管理:管理设备所在的位置
- 数据统计:查看设备状态和分布统计
- 设备历史:记录设备状态变化历史
- 响应式界面:适配不同尺寸的屏幕## 支持的设备类型- 灯光设备:控制开关、亮度和颜色
- 温控设备:控制温度和工作模式## 技术栈### 后端
- Python 3.9+
- Flask Web框架
- SQLite数据库### 前端
- Vue.js 3
- Bootstrap 5
- Axios HTTP客户端## 安装与运行1. 克隆或下载项目代码2. 安装依赖
```bash
pip install -r requirements.txt
运行应用
python run.py
在浏览器中访问
http://localhost:5000
项目结构
smart_home_system/
├── app/ # 应用代码
│ ├── __init__.py # 应用初始化
│ ├── database.py # 数据库模块
│ ├── device_manager.py # 设备管理模块
│ └── routes/ # API路由
│ ├── __init__.py
│ └── api.py # API接口定义
├── config/ # 配置文件
├── static/ # 静态资源
│ ├── css/ # CSS样式
│ │ └── styles.css # 主样式表
│ └── js/ # JavaScript代码
│ └── app.js # 前端应用
├── templates/ # HTML模板
│ └── index.html # 主页面
├── README.md # 项目说明
├── requirements.txt # 依赖列表
└── run.py # 应用入口
API接口
设备管理
GET /api/devices - 获取所有设备
GET /api/devices/<device_id> - 获取单个设备
POST /api/devices - 添加设备
PUT /api/devices/<device_id> - 更新设备
DELETE /api/devices/<device_id> - 删除设备
POST /api/devices/<device_id>/control - 控制设备
GET /api/devices/<device_id>/history - 获取设备历史
位置管理
GET /api/locations - 获取所有位置
POST /api/locations - 添加位置
设备类型
GET /api/device-types - 获取所有设备类型
扩展与自定义
添加新设备类型
在 device_manager.py 中创建新的设备类型类,继承 DeviceType 基类
实现必要的方法:create_device, get_properties_schema, validate_command, execute_command
在 DeviceFactory 中注册新设备类型
自定义数据库
默认使用SQLite数据库,如需使用其他数据库:修改 database.py 中的数据库连接和操作代码
更新 requirements.txt 添加相应的数据库驱动
requirements.txt
Flask==2.2.3
Flask-Cors==3.0.10
Werkzeug==2.2.3
Jinja2==3.1.2
MarkupSafe==2.1.2
itsdangerous==2.1.2
click==8.1.3
colorama==0.4.6
5. README File
run.py
"""
Main entry point for Smart Home System
"""
from app import create_appapp = create_app()if __name__ == '__main__':app.run(host='0.0.0.0', port=5000, debug=True)
app\database.py
"""
Database module for Smart Home System
Handles database operations for device management
"""
import sqlite3
import json
import os
import datetime
from pathlib import Pathclass Database:def __init__(self, db_path=None):"""Initialize database connection"""if db_path is None:# Create database in the project directorybase_dir = Path(__file__).resolve().parent.parentdb_path = os.path.join(base_dir, 'smart_home.db')self.db_path = db_pathself.connection = Noneself.cursor = Noneself._connect()self._create_tables()def _connect(self):"""Connect to the SQLite database"""try:self.connection = sqlite3.connect(self.db_path)self.connection.row_factory = sqlite3.Row # Return rows as dictionariesself.cursor = self.connection.cursor()except sqlite3.Error as e:print(f"Database connection error: {e}")raisedef _create_tables(self):"""Create necessary tables if they don't exist"""try:# Devices tableself.cursor.execute('''CREATE TABLE IF NOT EXISTS devices (id TEXT PRIMARY KEY,name TEXT NOT NULL,type TEXT NOT NULL,location TEXT,status TEXT DEFAULT 'offline',properties TEXT,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')# Device history table for logging state changesself.cursor.execute('''CREATE TABLE IF NOT EXISTS device_history (id INTEGER PRIMARY KEY AUTOINCREMENT,device_id TEXT NOT NULL,status TEXT NOT NULL,properties TEXT,timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,FOREIGN KEY (device_id) REFERENCES devices (id))''')# Locations tableself.cursor.execute('''CREATE TABLE IF NOT EXISTS locations (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT NOT NULL UNIQUE,description TEXT)''')# Scenes tableself.cursor.execute('''CREATE TABLE IF NOT EXISTS scenes (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT NOT NULL UNIQUE,description TEXT,icon TEXT DEFAULT 'lightbulb',created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')# Scene actions table - stores device states for each sceneself.cursor.execute('''CREATE TABLE IF NOT EXISTS scene_actions (id INTEGER PRIMARY KEY AUTOINCREMENT,scene_id INTEGER NOT NULL,device_id TEXT NOT NULL,action_type TEXT NOT NULL,action_params TEXT,FOREIGN KEY (scene_id) REFERENCES scenes (id) ON DELETE CASCADE,FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE)''')# Insert default locations if they don't existdefault_locations = ['Living Room', 'Bedroom', 'Kitchen', 'Bathroom', 'Office', 'Hallway']for location in default_locations:self.cursor.execute('''INSERT OR IGNORE INTO locations (name) VALUES (?)''', (location,))self.connection.commit()except sqlite3.Error as e:print(f"Error creating tables: {e}")raisedef close(self):"""Close the database connection"""if self.connection:self.connection.close()# Device operationsdef add_device(self, device_data):"""Add a new device to the database"""try:# Convert properties dict to JSON stringif 'properties' in device_data and isinstance(device_data['properties'], dict):device_data['properties'] = json.dumps(device_data['properties'])# Get current timestampcurrent_time = datetime.datetime.now().isoformat()self.cursor.execute('''INSERT INTO devices (id, name, type, location, status, properties, created_at, last_updated)VALUES (?, ?, ?, ?, ?, ?, ?, ?)''', (device_data['id'],device_data['name'],device_data['type'],device_data.get('location', 'Unknown'),device_data.get('status', 'offline'),device_data.get('properties', '{}'),current_time,current_time))self.connection.commit()return Trueexcept sqlite3.Error as e:print(f"Error adding device: {e}")self.connection.rollback()return Falsedef update_device(self, device_id, update_data):"""Update device information"""try:# Get current device datacurrent_device = self.get_device(device_id)if not current_device:return False# Prepare update fieldsupdate_fields = []update_values = []for key, value in update_data.items():if key == 'properties' and isinstance(value, dict):# Merge with existing propertiescurrent_props = json.loads(current_device['properties']) if current_device['properties'] else {}current_props.update(value)value = json.dumps(current_props)if key in ['name', 'type', 'location', 'status', 'properties']:update_fields.append(f"{key} = ?")update_values.append(value)# Add last_updated timestampupdate_fields.append("last_updated = ?")update_values.append(datetime.datetime.now().isoformat())# Add device_id for WHERE clauseupdate_values.append(device_id)# Execute update queryif update_fields:query = f'''UPDATE devices SET {', '.join(update_fields)}WHERE id = ?'''self.cursor.execute(query, update_values)# Log state change if status or properties changedif 'status' in update_data or 'properties' in update_data:self._log_device_history(device_id, update_data.get('status', current_device['status']),update_data.get('properties', current_device['properties']))self.connection.commit()return Truereturn Falseexcept sqlite3.Error as e:print(f"Error updating device: {e}")self.connection.rollback()return Falsedef delete_device(self, device_id):"""Delete a device from the database"""try:self.cursor.execute('DELETE FROM devices WHERE id = ?', (device_id,))self.cursor.execute('DELETE FROM device_history WHERE device_id = ?', (device_id,))self.cursor.execute('DELETE FROM scene_actions WHERE device_id = ?', (device_id,))self.connection.commit()return Trueexcept sqlite3.Error as e:print(f"Error deleting device: {e}")self.connection.rollback()return Falsedef get_device(self, device_id):"""Get a device by ID"""try:self.cursor.execute('SELECT * FROM devices WHERE id = ?', (device_id,))device = self.cursor.fetchone()if device:device_dict = dict(device)# Parse properties JSONif device_dict['properties']:device_dict['properties'] = json.loads(device_dict['properties'])return device_dictreturn Noneexcept sqlite3.Error as e:print(f"Error getting device: {e}")return Nonedef get_all_devices(self):"""Get all devices"""try:self.cursor.execute('SELECT * FROM devices ORDER BY name')devices = self.cursor.fetchall()result = []for device in devices:device_dict = dict(device)# Parse properties JSONif device_dict['properties']:device_dict['properties'] = json.loads(device_dict['properties'])result.append(device_dict)return resultexcept sqlite3.Error as e:print(f"Error getting devices: {e}")return []def get_devices_by_type(self, device_type):"""Get devices by type"""try:self.cursor.execute('SELECT * FROM devices WHERE type = ? ORDER BY name', (device_type,))devices = self.cursor.fetchall()result = []for device in devices:device_dict = dict(device)# Parse properties JSONif device_dict['properties']:device_dict['properties'] = json.loads(device_dict['properties'])result.append(device_dict)return resultexcept sqlite3.Error as e:print(f"Error getting devices by type: {e}")return []def get_devices_by_location(self, location):"""Get devices by location"""try:self.cursor.execute('SELECT * FROM devices WHERE location = ? ORDER BY name', (location,))devices = self.cursor.fetchall()result = []for device in devices:device_dict = dict(device)# Parse properties JSONif device_dict['properties']:device_dict['properties'] = json.loads(device_dict['properties'])result.append(device_dict)return resultexcept sqlite3.Error as e:print(f"Error getting devices by location: {e}")return []def _log_device_history(self, device_id, status, properties):"""Log device state change to history"""try:if isinstance(properties, dict):properties = json.dumps(properties)self.cursor.execute('''INSERT INTO device_history (device_id, status, properties)VALUES (?, ?, ?)''', (device_id, status, properties))return Trueexcept sqlite3.Error as e:print(f"Error logging device history: {e}")return Falsedef get_device_history(self, device_id, limit=50):"""Get device history"""try:self.cursor.execute('''SELECT * FROM device_historyWHERE device_id = ?ORDER BY timestamp DESCLIMIT ?''', (device_id, limit))history = self.cursor.fetchall()result = []for entry in history:entry_dict = dict(entry)# Parse properties JSONif entry_dict['properties']:entry_dict['properties'] = json.loads(entry_dict['properties'])result.append(entry_dict)return resultexcept sqlite3.Error as e:print(f"Error getting device history: {e}")return []# Location operationsdef get_all_locations(self):"""Get all locations"""try:self.cursor.execute('SELECT * FROM locations ORDER BY name')locations = self.cursor.fetchall()return [dict(location) for location in locations]except sqlite3.Error as e:print(f"Error getting locations: {e}")return []def add_location(self, name, description=None):"""Add a new location"""try:self.cursor.execute('''INSERT INTO locations (name, description)VALUES (?, ?)''', (name, description))self.connection.commit()return self.cursor.lastrowidexcept sqlite3.Error as e:print(f"Error adding location: {e}")self.connection.rollback()return None# Scene operationsdef add_scene(self, scene_data):"""Add a new scene"""try:name = scene_data.get('name')description = scene_data.get('description')icon = scene_data.get('icon', 'lightbulb')current_time = datetime.datetime.now().isoformat()self.cursor.execute('''INSERT INTO scenes (name, description, icon, created_at, last_updated)VALUES (?, ?, ?, ?, ?)''', (name, description, icon, current_time, current_time))scene_id = self.cursor.lastrowid# Add scene actions if providedactions = scene_data.get('actions', [])for action in actions:self.add_scene_action(scene_id, action)self.connection.commit()return scene_idexcept sqlite3.Error as e:print(f"Error adding scene: {e}")self.connection.rollback()return Nonedef update_scene(self, scene_id, update_data):"""Update scene information"""try:update_fields = []update_values = []for key, value in update_data.items():if key in ['name', 'description', 'icon']:update_fields.append(f"{key} = ?")update_values.append(value)# Add last_updated timestampupdate_fields.append("last_updated = ?")update_values.append(datetime.datetime.now().isoformat())# Add scene_id for WHERE clauseupdate_values.append(scene_id)# Execute update queryif update_fields:query = f'''UPDATE scenes SET {', '.join(update_fields)}WHERE id = ?'''self.cursor.execute(query, update_values)# Update scene actions if providedif 'actions' in update_data:# Delete existing actionsself.cursor.execute('DELETE FROM scene_actions WHERE scene_id = ?', (scene_id,))# Add new actionsfor action in update_data['actions']:self.add_scene_action(scene_id, action)self.connection.commit()return Truereturn Falseexcept sqlite3.Error as e:print(f"Error updating scene: {e}")self.connection.rollback()return Falsedef delete_scene(self, scene_id):"""Delete a scene"""try:self.cursor.execute('DELETE FROM scenes WHERE id = ?', (scene_id,))self.cursor.execute('DELETE FROM scene_actions WHERE scene_id = ?', (scene_id,))self.connection.commit()return Trueexcept sqlite3.Error as e:print(f"Error deleting scene: {e}")self.connection.rollback()return Falsedef get_scene(self, scene_id):"""Get a scene by ID"""try:self.cursor.execute('SELECT * FROM scenes WHERE id = ?', (scene_id,))scene = self.cursor.fetchone()if not scene:return Nonescene_dict = dict(scene)# Get scene actionsscene_dict['actions'] = self.get_scene_actions(scene_id)return scene_dictexcept sqlite3.Error as e:print(f"Error getting scene: {e}")return Nonedef get_all_scenes(self):"""Get all scenes"""try:self.cursor.execute('SELECT * FROM scenes ORDER BY name')scenes = self.cursor.fetchall()result = []for scene in scenes:scene_dict = dict(scene)scene_dict['actions'] = self.get_scene_actions(scene_dict['id'])result.append(scene_dict)return resultexcept sqlite3.Error as e:print(f"Error getting scenes: {e}")return []def add_scene_action(self, scene_id, action_data):"""Add a scene action"""try:device_id = action_data.get('device_id')action_type = action_data.get('action_type')action_params = action_data.get('action_params', {})if isinstance(action_params, dict):action_params = json.dumps(action_params)self.cursor.execute('''INSERT INTO scene_actions (scene_id, device_id, action_type, action_params)VALUES (?, ?, ?, ?)''', (scene_id, device_id, action_type, action_params))return self.cursor.lastrowidexcept sqlite3.Error as e:print(f"Error adding scene action: {e}")return Nonedef get_scene_actions(self, scene_id):"""Get actions for a scene"""try:self.cursor.execute('''SELECT * FROM scene_actions WHERE scene_id = ?''', (scene_id,))actions = self.cursor.fetchall()result = []for action in actions:action_dict = dict(action)# Parse action_params JSONif action_dict['action_params']:action_dict['action_params'] = json.loads(action_dict['action_params'])result.append(action_dict)return resultexcept sqlite3.Error as e:print(f"Error getting scene actions: {e}")return []
app\device_manager.py
"""
Device Manager Module for Smart Home System
Handles device operations and communication
"""
import uuid
import time
import json
from .database import Databaseclass DeviceManager:"""Device Manager class for handling device operations"""def __init__(self, db=None):"""Initialize the device manager"""self.db = db if db else Database()self.devices = {} # In-memory cache of devicesself._load_devices_from_db()def _load_devices_from_db(self):"""Load devices from database into memory"""devices = self.db.get_all_devices()for device in devices:self.devices[device['id']] = devicedef register_device(self, name, device_type, location=None, properties=None):"""Register a new device"""if properties is None:properties = {}device_id = str(uuid.uuid4())device_data = {'id': device_id,'name': name,'type': device_type,'location': location,'status': 'offline','properties': properties}# Add to databaseif self.db.add_device(device_data):# Add to in-memory cacheself.devices[device_id] = device_datareturn device_idreturn Nonedef update_device_status(self, device_id, status, properties=None):"""Update device status and properties"""if device_id not in self.devices:return Falseupdate_data = {'status': status}if properties:update_data['properties'] = properties# Update databaseif self.db.update_device(device_id, update_data):# Update in-memory cacheself.devices[device_id].update(update_data)self.devices[device_id]['last_updated'] = time.time()return Truereturn Falsedef get_device(self, device_id):"""Get device by ID"""if device_id in self.devices:return self.devices[device_id]# Try to get from databasedevice = self.db.get_device(device_id)if device:self.devices[device_id] = devicereturn devicereturn Nonedef get_all_devices(self):"""Get all devices"""return list(self.devices.values())def get_devices_by_type(self, device_type):"""Get devices by type"""return [device for device in self.devices.values() if device['type'] == device_type]def get_devices_by_location(self, location):"""Get devices by location"""return [device for device in self.devices.values() if device['location'] == location]def delete_device(self, device_id):"""Delete a device"""if device_id not in self.devices:return False# Delete from databaseif self.db.delete_device(device_id):# Delete from in-memory cachedel self.devices[device_id]return Truereturn Falsedef update_device_properties(self, device_id, properties):"""Update device properties"""if device_id not in self.devices:return False# Get current propertiescurrent_props = self.devices[device_id].get('properties', {})# Merge with new propertiesif isinstance(properties, dict):current_props.update(properties)# Update databaseupdate_data = {'properties': current_props}if self.db.update_device(device_id, update_data):# Update in-memory cacheself.devices[device_id]['properties'] = current_propsself.devices[device_id]['last_updated'] = time.time()return Truereturn Falsedef get_device_history(self, device_id, limit=50):"""Get device history"""return self.db.get_device_history(device_id, limit)def get_all_locations(self):"""Get all locations"""return self.db.get_all_locations()def add_location(self, name, description=None):"""Add a new location"""return self.db.add_location(name, description)# Device type implementations
class DeviceType:"""Base class for device types"""@staticmethoddef create_device(device_manager, name, location, **kwargs):"""Create a device of this type"""raise NotImplementedError("Subclasses must implement this method")@staticmethoddef get_properties_schema():"""Get JSON schema for device properties"""raise NotImplementedError("Subclasses must implement this method")@staticmethoddef validate_command(command, params):"""Validate a command for this device type"""raise NotImplementedError("Subclasses must implement this method")@staticmethoddef execute_command(device, command, params):"""Execute a command on a device"""raise NotImplementedError("Subclasses must implement this method")class LightDevice(DeviceType):"""Light device type implementation"""@staticmethoddef create_device(device_manager, name, location, **kwargs):"""Create a light device"""brightness = kwargs.get('brightness', 100)color = kwargs.get('color', 'white')properties = {'brightness': brightness,'color': color,'supported_commands': ['turn_on', 'turn_off', 'set_brightness', 'set_color']}return device_manager.register_device(name, 'light', location, properties)@staticmethoddef get_properties_schema():"""Get JSON schema for light properties"""return {"type": "object","properties": {"brightness": {"type": "integer","minimum": 0,"maximum": 100,"description": "Brightness level (0-100)"},"color": {"type": "string","description": "Light color (name or hex code)"}}}@staticmethoddef validate_command(command, params):"""Validate a command for light device"""if command == 'turn_on' or command == 'turn_off':return Trueif command == 'set_brightness':if 'brightness' not in params:return Falsebrightness = params.get('brightness')return isinstance(brightness, int) and 0 <= brightness <= 100if command == 'set_color':return 'color' in paramsreturn False@staticmethoddef execute_command(device, command, params):"""Execute a command on a light device"""device_id = device['id']device_manager = DeviceManager()if command == 'turn_on':return device_manager.update_device_status(device_id, 'on')if command == 'turn_off':return device_manager.update_device_status(device_id, 'off')if command == 'set_brightness':brightness = params.get('brightness', 100)properties = {'brightness': brightness}return device_manager.update_device_properties(device_id, properties)if command == 'set_color':color = params.get('color', 'white')properties = {'color': color}return device_manager.update_device_properties(device_id, properties)return Falseclass ThermostatDevice(DeviceType):"""Thermostat device type implementation"""@staticmethoddef create_device(device_manager, name, location, **kwargs):"""Create a thermostat device"""current_temp = kwargs.get('current_temp', 22)target_temp = kwargs.get('target_temp', 22)mode = kwargs.get('mode', 'off') # off, heat, cool, autoproperties = {'current_temp': current_temp,'target_temp': target_temp,'mode': mode,'supported_commands': ['set_temperature', 'set_mode']}return device_manager.register_device(name, 'thermostat', location, properties)@staticmethoddef get_properties_schema():"""Get JSON schema for thermostat properties"""return {"type": "object","properties": {"current_temp": {"type": "number","description": "Current temperature in Celsius"},"target_temp": {"type": "number","minimum": 5,"maximum": 35,"description": "Target temperature in Celsius"},"mode": {"type": "string","enum": ["off", "heat", "cool", "auto"],"description": "Thermostat mode"}}}@staticmethoddef validate_command(command, params):"""Validate a command for thermostat device"""if command == 'set_temperature':if 'temperature' not in params:return Falsetemp = params.get('temperature')return isinstance(temp, (int, float)) and 5 <= temp <= 35if command == 'set_mode':if 'mode' not in params:return Falsemode = params.get('mode')return mode in ['off', 'heat', 'cool', 'auto']return False@staticmethoddef execute_command(device, command, params):"""Execute a command on a thermostat device"""device_id = device['id']device_manager = DeviceManager()if command == 'set_temperature':temperature = params.get('temperature')properties = {'target_temp': temperature}# If device is off, turn it on in heat or cool modeif device['properties'].get('mode') == 'off':properties['mode'] = 'auto'return device_manager.update_device_properties(device_id, properties)if command == 'set_mode':mode = params.get('mode')properties = {'mode': mode}# Update status based on modestatus = 'on' if mode != 'off' else 'off'device_manager.update_device_status(device_id, status)return device_manager.update_device_properties(device_id, properties)return False# Factory for creating devices of different types
class DeviceFactory:"""Factory class for creating devices of different types"""_device_types = {'light': LightDevice,'thermostat': ThermostatDevice}@classmethoddef register_device_type(cls, type_name, device_class):"""Register a new device type"""cls._device_types[type_name] = device_class@classmethoddef create_device(cls, device_manager, type_name, name, location, **kwargs):"""Create a device of the specified type"""if type_name not in cls._device_types:raise ValueError(f"Unknown device type: {type_name}")device_class = cls._device_types[type_name]return device_class.create_device(device_manager, name, location, **kwargs)@classmethoddef get_device_types(cls):"""Get all registered device types"""return list(cls._device_types.keys())@classmethoddef get_properties_schema(cls, type_name):"""Get properties schema for a device type"""if type_name not in cls._device_types:raise ValueError(f"Unknown device type: {type_name}")device_class = cls._device_types[type_name]return device_class.get_properties_schema()@classmethoddef execute_command(cls, device, command, params):"""Execute a command on a device"""device_type = device['type']if device_type not in cls._device_types:raise ValueError(f"Unknown device type: {device_type}")device_class = cls._device_types[device_type]# Validate commandif not device_class.validate_command(command, params):return False# Execute commandreturn device_class.execute_command(device, command, params)
app\scene_manager.py
"""
Scene Manager for Smart Home System
Handles scene operations and execution
"""
import uuid
from .device_manager import DeviceManagerclass SceneManager:def __init__(self, db=None, device_manager=None):"""Initialize Scene Manager"""from .database import Databaseself.db = db if db else Database()self.device_manager = device_manager if device_manager else DeviceManager(self.db)def create_scene(self, name, description=None, icon=None, actions=None):"""Create a new scene"""if not name:raise ValueError("Scene name is required")scene_data = {'name': name,'description': description,'icon': icon or 'lightbulb','actions': actions or []}scene_id = self.db.add_scene(scene_data)if scene_id:return self.get_scene(scene_id)return Nonedef update_scene(self, scene_id, update_data):"""Update an existing scene"""if not scene_id:return False# Validate scene existsscene = self.get_scene(scene_id)if not scene:return False# Update sceneif self.db.update_scene(scene_id, update_data):return self.get_scene(scene_id)return Nonedef delete_scene(self, scene_id):"""Delete a scene"""return self.db.delete_scene(scene_id)def get_scene(self, scene_id):"""Get a scene by ID"""return self.db.get_scene(scene_id)def get_all_scenes(self):"""Get all scenes"""return self.db.get_all_scenes()def activate_scene(self, scene_id):"""Activate a scene by executing all its actions"""scene = self.get_scene(scene_id)if not scene:return Falseresults = []for action in scene['actions']:device_id = action['device_id']action_type = action['action_type']action_params = action['action_params']# Get devicedevice = self.device_manager.get_device(device_id)if not device:results.append({'device_id': device_id,'success': False,'message': 'Device not found'})continue# Execute actiontry:if action_type == 'set_status':status = action_params.get('status')if status:self.device_manager.update_device_status(device_id, status)results.append({'device_id': device_id,'success': True,'action': 'set_status','value': status})elif action_type == 'set_property':property_name = action_params.get('property')property_value = action_params.get('value')if property_name and property_value is not None:properties = {property_name: property_value}self.device_manager.update_device_properties(device_id, properties)results.append({'device_id': device_id,'success': True,'action': 'set_property','property': property_name,'value': property_value})elif action_type == 'execute_command':command = action_params.get('command')params = action_params.get('params', {})if command:# This would call the device-specific command execution# For now, we'll just update the device propertiesself.device_manager.update_device_properties(device_id, params)results.append({'device_id': device_id,'success': True,'action': 'execute_command','command': command,'params': params})else:results.append({'device_id': device_id,'success': False,'message': f'Unsupported action type: {action_type}'})except Exception as e:results.append({'device_id': device_id,'success': False,'message': str(e)})return {'scene_id': scene_id,'scene_name': scene['name'],'success': any(result['success'] for result in results),'results': results}def add_device_to_scene(self, scene_id, device_id, action_type, action_params):"""Add a device action to a scene"""scene = self.get_scene(scene_id)if not scene:return False# Validate device existsdevice = self.device_manager.get_device(device_id)if not device:return False# Create actionaction = {'device_id': device_id,'action_type': action_type,'action_params': action_params}# Add action to sceneaction_id = self.db.add_scene_action(scene_id, action)return action_id is not Nonedef remove_device_from_scene(self, scene_id, action_id):"""Remove a device action from a scene"""# This would require adding a method to delete a specific action# For now, we'll update the scene with all actions except the one to removescene = self.get_scene(scene_id)if not scene:return False# Filter out the action to removeupdated_actions = [action for action in scene['actions'] if action['id'] != action_id]# Update scene with new actionsreturn self.db.update_scene(scene_id, {'actions': updated_actions})
app_init_.py
"""
Smart Home System Application
"""
from flask import Flask
from flask_cors import CORSdef create_app():"""Create and configure the Flask application"""app = Flask(__name__, static_folder='../static',template_folder='../templates')# Enable CORSCORS(app)# Load configurationapp.config.from_mapping(SECRET_KEY='dev',DATABASE=None,)# Register blueprintsfrom .routes import apiapp.register_blueprint(api.bp)return app
app\routes\api.py
"""
API routes for Smart Home System
"""
from flask import Blueprint, request, jsonify, current_app
from ..device_manager import DeviceManager, DeviceFactory
from ..scene_manager import SceneManagerbp = Blueprint('api', __name__, url_prefix='/api')# Initialize device manager
device_manager = DeviceManager()
# Initialize scene manager
scene_manager = SceneManager(device_manager=device_manager)@bp.route('/devices', methods=['GET'])
def get_devices():"""Get all devices or filter by type/location"""device_type = request.args.get('type')location = request.args.get('location')if device_type:devices = device_manager.get_devices_by_type(device_type)elif location:devices = device_manager.get_devices_by_location(location)else:devices = device_manager.get_all_devices()return jsonify(devices)@bp.route('/devices/<device_id>', methods=['GET'])
def get_device(device_id):"""Get a device by ID"""device = device_manager.get_device(device_id)if not device:return jsonify({'error': 'Device not found'}), 404return jsonify(device)@bp.route('/devices', methods=['POST'])
def create_device():"""Create a new device"""data = request.jsonif not data:return jsonify({'error': 'No data provided'}), 400# Required fieldsname = data.get('name')device_type = data.get('type')location = data.get('location')if not name or not device_type:return jsonify({'error': 'Name and type are required'}), 400# Check if device type is supportedif device_type not in DeviceFactory.get_device_types():return jsonify({'error': f'Unsupported device type: {device_type}'}), 400# Create devicetry:device_id = DeviceFactory.create_device(device_manager, device_type, name, location, **data.get('properties', {}))if not device_id:return jsonify({'error': 'Failed to create device'}), 500# Get the created devicedevice = device_manager.get_device(device_id)return jsonify(device), 201except Exception as e:return jsonify({'error': str(e)}), 500@bp.route('/devices/<device_id>', methods=['PUT'])
def update_device(device_id):"""Update a device"""device = device_manager.get_device(device_id)if not device:return jsonify({'error': 'Device not found'}), 404data = request.jsonif not data:return jsonify({'error': 'No data provided'}), 400# Update nameif 'name' in data:device['name'] = data['name']# Update locationif 'location' in data:device['location'] = data['location']# Update propertiesif 'properties' in data and isinstance(data['properties'], dict):device_manager.update_device_properties(device_id, data['properties'])# Update in databasedevice_manager.db.update_device(device_id, {'name': device['name'],'location': device['location']})return jsonify(device_manager.get_device(device_id))@bp.route('/devices/<device_id>', methods=['DELETE'])
def delete_device(device_id):"""Delete a device"""device = device_manager.get_device(device_id)if not device:return jsonify({'error': 'Device not found'}), 404if device_manager.delete_device(device_id):return jsonify({'message': 'Device deleted successfully'})return jsonify({'error': 'Failed to delete device'}), 500@bp.route('/devices/<device_id>/control', methods=['POST'])
def control_device(device_id):"""Control a device"""device = device_manager.get_device(device_id)if not device:return jsonify({'error': 'Device not found'}), 404data = request.jsonif not data:return jsonify({'error': 'No data provided'}), 400command = data.get('command')params = data.get('params', {})if not command:return jsonify({'error': 'Command is required'}), 400# Check if command is supported by devicesupported_commands = device.get('properties', {}).get('supported_commands', [])if command not in supported_commands:return jsonify({'error': f'Unsupported command: {command}'}), 400# Execute commandtry:result = DeviceFactory.execute_command(device, command, params)if result:# Get updated deviceupdated_device = device_manager.get_device(device_id)return jsonify({'message': 'Command executed successfully','device': updated_device})return jsonify({'error': 'Failed to execute command'}), 500except Exception as e:return jsonify({'error': str(e)}), 500@bp.route('/devices/<device_id>/history', methods=['GET'])
def get_device_history(device_id):"""Get device history"""device = device_manager.get_device(device_id)if not device:return jsonify({'error': 'Device not found'}), 404limit = request.args.get('limit', 50, type=int)history = device_manager.get_device_history(device_id, limit)return jsonify(history)@bp.route('/device-types', methods=['GET'])
def get_device_types():"""Get all supported device types"""device_types = DeviceFactory.get_device_types()# Get schema for each device typeresult = {}for device_type in device_types:try:schema = DeviceFactory.get_properties_schema(device_type)result[device_type] = {'name': device_type,'schema': schema}except Exception:result[device_type] = {'name': device_type,'schema': {}}return jsonify(result)@bp.route('/locations', methods=['GET'])
def get_locations():"""Get all locations"""locations = device_manager.db.get_all_locations()return jsonify(locations)@bp.route('/locations', methods=['POST'])
def add_location():"""Add a new location"""data = request.jsonif not data:return jsonify({'error': 'No data provided'}), 400name = data.get('name')description = data.get('description')if not name:return jsonify({'error': 'Name is required'}), 400location_id = device_manager.db.add_location(name, description)if location_id:return jsonify({'id': location_id,'name': name,'description': description}), 201return jsonify({'error': 'Failed to add location'}), 500# Scene routes
@bp.route('/scenes', methods=['GET'])
def get_scenes():"""Get all scenes"""scenes = scene_manager.get_all_scenes()return jsonify(scenes)@bp.route('/scenes/<scene_id>', methods=['GET'])
def get_scene(scene_id):"""Get a scene by ID"""scene = scene_manager.get_scene(scene_id)if not scene:return jsonify({'error': 'Scene not found'}), 404return jsonify(scene)@bp.route('/scenes', methods=['POST'])
def create_scene():"""Create a new scene"""data = request.jsonif not data:return jsonify({'error': 'No data provided'}), 400name = data.get('name')description = data.get('description')icon = data.get('icon')actions = data.get('actions', [])if not name:return jsonify({'error': 'Name is required'}), 400try:scene = scene_manager.create_scene(name, description, icon, actions)if scene:return jsonify(scene), 201return jsonify({'error': 'Failed to create scene'}), 500except Exception as e:return jsonify({'error': str(e)}), 500@bp.route('/scenes/<scene_id>', methods=['PUT'])
def update_scene(scene_id):"""Update a scene"""scene = scene_manager.get_scene(scene_id)if not scene:return jsonify({'error': 'Scene not found'}), 404data = request.jsonif not data:return jsonify({'error': 'No data provided'}), 400try:updated_scene = scene_manager.update_scene(scene_id, data)if updated_scene:return jsonify(updated_scene)return jsonify({'error': 'Failed to update scene'}), 500except Exception as e:return jsonify({'error': str(e)}), 500@bp.route('/scenes/<scene_id>', methods=['DELETE'])
def delete_scene(scene_id):"""Delete a scene"""scene = scene_manager.get_scene(scene_id)if not scene:return jsonify({'error': 'Scene not found'}), 404if scene_manager.delete_scene(scene_id):return jsonify({'message': 'Scene deleted successfully'})return jsonify({'error': 'Failed to delete scene'}), 500@bp.route('/scenes/<scene_id>/activate', methods=['POST'])
def activate_scene(scene_id):"""Activate a scene"""scene = scene_manager.get_scene(scene_id)if not scene:return jsonify({'error': 'Scene not found'}), 404result = scene_manager.activate_scene(scene_id)if result and result.get('success'):return jsonify(result)return jsonify({'error': 'Failed to activate scene', 'details': result}), 500@bp.route('/scenes/<scene_id>/devices', methods=['POST'])
def add_device_to_scene(scene_id):"""Add a device to a scene"""scene = scene_manager.get_scene(scene_id)if not scene:return jsonify({'error': 'Scene not found'}), 404data = request.jsonif not data:return jsonify({'error': 'No data provided'}), 400device_id = data.get('device_id')action_type = data.get('action_type')action_params = data.get('action_params', {})if not device_id or not action_type:return jsonify({'error': 'Device ID and action type are required'}), 400# Check if device existsdevice = device_manager.get_device(device_id)if not device:return jsonify({'error': 'Device not found'}), 404if scene_manager.add_device_to_scene(scene_id, device_id, action_type, action_params):return jsonify({'message': 'Device added to scene successfully'})return jsonify({'error': 'Failed to add device to scene'}), 500
app\routes_init_.py
static\css\styles.css
/* Smart Home System CSS *//* General Styles */
body {font-family: 'Microsoft YaHei', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background-color: #f8f9fa;
}.card {box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);transition: all 0.3s ease;
}.card:hover {box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);transform: translateY(-2px);
}/* Device Card Styles */
.card.border-success {border-width: 2px;
}.card.border-danger {border-width: 2px;
}/* Color Button Styles */
.color-btn {width: 30px;height: 30px;border-radius: 50%;border: 1px solid #dee2e6;
}.color-btn:hover {transform: scale(1.1);
}/* Status Badge Styles */
.badge.bg-success {background-color: #28a745 !important;
}.badge.bg-danger {background-color: #dc3545 !important;
}.badge.bg-secondary {background-color: #6c757d !important;
}/* Progress Bar Animation */
.progress-bar {transition: width 1s ease;animation: progress-bar-stripes 1s linear infinite;
}@keyframes progress-bar-stripes {0% {background-position: 1rem 0;}100% {background-position: 0 0;}
}/* Custom Scrollbar */
::-webkit-scrollbar {width: 8px;
}::-webkit-scrollbar-track {background: #f1f1f1;
}::-webkit-scrollbar-thumb {background: #888;border-radius: 4px;
}::-webkit-scrollbar-thumb:hover {background: #555;
}/* Modal Animation */
.modal.fade .modal-dialog {transition: transform 0.3s ease-out;transform: translateY(-50px);
}.modal.show .modal-dialog {transform: none;
}/* Toast Notification */
.toast {opacity: 1 !important;
}/* Responsive Adjustments */
@media (max-width: 768px) {.card-footer {flex-direction: column;gap: 10px;align-items: center;}.btn-group {width: 100%;}
}
3. JavaScript Application
static\js\app.js
/*** Smart Home System - Frontend Application*/// API Base URL
const API_BASE_URL = '/api';// Bootstrap components
let toastElement;
let addDeviceModal;
let editDeviceModal;
let deviceHistoryModal;
let addLocationModal;
let editLocationModal;// Create Vue application
const app = Vue.createApp({data() {return {// UI stateactiveTab: 'devices',// Datadevices: [],locations: [],deviceTypes: [],deviceHistory: [],// FiltersdeviceTypeFilter: '',locationFilter: '',statusFilter: '',// Selected itemsselectedDevice: null,editingDevice: null,editingLocation: null,// Form datanewDevice: {name: '',type: 'light',location: '',properties: {brightness: 100,color: 'white',current_temp: 22,target_temp: 22,mode: 'off'}},newLocation: {name: '',description: ''}};},computed: {filteredDevices() {return this.devices.filter(device => {let matchesType = true;let matchesLocation = true;let matchesStatus = true;if (this.deviceTypeFilter) {matchesType = device.type === this.deviceTypeFilter;}if (this.locationFilter) {matchesLocation = device.location === this.locationFilter;}if (this.statusFilter) {matchesStatus = device.status === this.statusFilter;}return matchesType && matchesLocation && matchesStatus;});}},methods: {// Device operationsasync loadDevices() {try {const response = await axios.get(`${API_BASE_URL}/devices`);this.devices = response.data;} catch (error) {this.showToast('加载设备失败', 'error');console.error('Error loading devices:', error);}},async loadDeviceTypes() {try {const response = await axios.get(`${API_BASE_URL}/device-types`);this.deviceTypes = Object.keys(response.data);} catch (error) {this.showToast('加载设备类型失败', 'error');console.error('Error loading device types:', error);}},async addDevice() {try {// Prepare properties based on device typelet properties = {};if (this.newDevice.type === 'light') {properties = {brightness: parseInt(this.newDevice.properties.brightness),color: this.newDevice.properties.color};} else if (this.newDevice.type === 'thermostat') {properties = {current_temp: parseFloat(this.newDevice.properties.current_temp),target_temp: parseFloat(this.newDevice.properties.target_temp),mode: this.newDevice.properties.mode};}const response = await axios.post(`${API_BASE_URL}/devices`, {name: this.newDevice.name,type: this.newDevice.type,location: this.newDevice.location,properties: properties});// Add new device to listthis.devices.push(response.data);// Reset formthis.resetNewDeviceForm();// Close modaladdDeviceModal.hide();this.showToast('设备添加成功');} catch (error) {this.showToast('添加设备失败', 'error');console.error('Error adding device:', error);}},async updateDevice() {if (!this.editingDevice) return;try {const deviceId = this.editingDevice.id;await axios.put(`${API_BASE_URL}/devices/${deviceId}`, {name: this.editingDevice.name,location: this.editingDevice.location});// Update device in listconst index = this.devices.findIndex(d => d.id === deviceId);if (index !== -1) {this.devices[index].name = this.editingDevice.name;this.devices[index].location = this.editingDevice.location;}// Close modaleditDeviceModal.hide();this.showToast('设备更新成功');} catch (error) {this.showToast('更新设备失败', 'error');console.error('Error updating device:', error);}},async deleteDevice(device) {if (!confirm(`确定要删除设备 "${device.name}" 吗?`)) return;try {await axios.delete(`${API_BASE_URL}/devices/${device.id}`);// Remove device from listthis.devices = this.devices.filter(d => d.id !== device.id);this.showToast('设备删除成功');} catch (error) {this.showToast('删除设备失败', 'error');console.error('Error deleting device:', error);}},async toggleDevice(device) {const command = device.status === 'on' ? 'turn_off' : 'turn_on';try {const response = await axios.post(`${API_BASE_URL}/devices/${device.id}/control`, {command: command});// Update device in listconst index = this.devices.findIndex(d => d.id === device.id);if (index !== -1) {this.devices[index] = response.data.device;}this.showToast(`设备${command === 'turn_on' ? '开启' : '关闭'}成功`);} catch (error) {this.showToast(`设备${command === 'turn_on' ? '开启' : '关闭'}失败`, 'error');console.error('Error controlling device:', error);}},async setBrightness(deviceId, brightness) {try {const response = await axios.post(`${API_BASE_URL}/devices/${deviceId}/control`, {command: 'set_brightness',params: {brightness: parseInt(brightness)}});// Update device in listconst index = this.devices.findIndex(d => d.id === deviceId);if (index !== -1) {this.devices[index] = response.data.device;}} catch (error) {this.showToast('设置亮度失败', 'error');console.error('Error setting brightness:', error);}},async setColor(deviceId, color) {try {const response = await axios.post(`${API_BASE_URL}/devices/${deviceId}/control`, {command: 'set_color',params: {color: color}});// Update device in listconst index = this.devices.findIndex(d => d.id === deviceId);if (index !== -1) {this.devices[index] = response.data.device;}} catch (error) {this.showToast('设置颜色失败', 'error');console.error('Error setting color:', error);}},async adjustTemperature(deviceId, delta) {const device = this.devices.find(d => d.id === deviceId);if (!device) return;const currentTemp = device.properties.target_temp;const newTemp = Math.min(Math.max(currentTemp + delta, 5), 35);try {const response = await axios.post(`${API_BASE_URL}/devices/${deviceId}/control`, {command: 'set_temperature',params: {temperature: newTemp}});// Update device in listconst index = this.devices.findIndex(d => d.id === deviceId);if (index !== -1) {this.devices[index] = response.data.device;}} catch (error) {this.showToast('设置温度失败', 'error');console.error('Error setting temperature:', error);}},async setThermostatMode(deviceId, mode) {try {const response = await axios.post(`${API_BASE_URL}/devices/${deviceId}/control`, {command: 'set_mode',params: {mode: mode}});// Update device in listconst index = this.devices.findIndex(d => d.id === deviceId);if (index !== -1) {this.devices[index] = response.data.device;}} catch (error) {this.showToast('设置模式失败', 'error');console.error('Error setting mode:', error);}},async loadDeviceHistory(deviceId) {try {const response = await axios.get(`${API_BASE_URL}/devices/${deviceId}/history`);this.deviceHistory = response.data;} catch (error) {this.showToast('加载设备历史记录失败', 'error');console.error('Error loading device history:', error);}},// Location operationsasync loadLocations() {try {const response = await axios.get(`${API_BASE_URL}/locations`);this.locations = response.data;} catch (error) {this.showToast('加载位置失败', 'error');console.error('Error loading locations:', error);}},async addLocation() {try {await axios.post(`${API_BASE_URL}/locations`, {name: this.newLocation.name,description: this.newLocation.description});// Reload locationsawait this.loadLocations();// Reset formthis.newLocation = {name: '',description: ''};// Close modaladdLocationModal.hide();this.showToast('位置添加成功');} catch (error) {this.showToast('添加位置失败', 'error');console.error('Error adding location:', error);}},// UI operationsshowAddDeviceModal() {this.resetNewDeviceForm();addDeviceModal.show();},showEditDeviceModal(device) {this.editingDevice = { ...device };editDeviceModal.show();},showDeviceHistory(device) {this.selectedDevice = device;this.deviceHistory = [];deviceHistoryModal.show();this.loadDeviceHistory(device.id);},showAddLocationModal() {this.newLocation = {name: '',description: ''};addLocationModal.show();},showEditLocationModal(location) {this.editingLocation = { ...location };editLocationModal.show();},resetNewDeviceForm() {this.newDevice = {name: '',type: 'light',location: '',properties: {brightness: 100,color: 'white',current_temp: 22,target_temp: 22,mode: 'off'}};},filterDevices() {// This method is called when filters change// The actual filtering is done in the computed property},getDeviceCountByType(type) {return this.devices.filter(device => device.type === type).length;},getDeviceCountByLocation(location) {return this.devices.filter(device => device.location === location).length;},getStatusBadgeClass(status) {if (status === 'on') return 'bg-success';if (status === 'off') return 'bg-secondary';return 'bg-danger';},getStatusText(status) {if (status === 'on') return '开启';if (status === 'off') return '关闭';return '离线';},formatDate(dateString) {if (!dateString) return '';const date = new Date(dateString);return date.toLocaleString('zh-CN', {year: 'numeric',month: '2-digit',day: '2-digit',hour: '2-digit',minute: '2-digit',second: '2-digit'});},showToast(message, type = 'success') {const toastEl = document.getElementById('toast');const toastMessageEl = document.getElementById('toastMessage');toastMessageEl.textContent = message;toastEl.classList.remove('bg-success', 'bg-danger');toastEl.classList.add(type === 'success' ? 'bg-success' : 'bg-danger');const toast = new bootstrap.Toast(toastEl);toast.show();}},mounted() {// Initialize Bootstrap componentstoastElement = document.getElementById('toast');addDeviceModal = new bootstrap.Modal(document.getElementById('addDeviceModal'));editDeviceModal = new bootstrap.Modal(document.getElementById('editDeviceModal'));deviceHistoryModal = new bootstrap.Modal(document.getElementById('deviceHistoryModal'));addLocationModal = new bootstrap.Modal(document.getElementById('addLocationModal'));editLocationModal = new bootstrap.Modal(document.getElementById('editLocationModal'));// Load datathis.loadDevices();this.loadDeviceTypes();this.loadLocations();}
});// Mount Vue application
app.mount('#app');
4. Requirements File
templates\index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>智能家居控制系统 - 设备管理</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css"><link rel="stylesheet" href="/static/css/styles.css">
</head>
<body><div id="app"><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container-fluid"><a class="navbar-brand" href="#"><i class="bi bi-house-gear-fill me-2"></i>智能家居控制系统</a><button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav"><li class="nav-item"><a class="nav-link active" href="#" @click="activeTab = 'devices'"><i class="bi bi-grid-3x3-gap-fill me-1"></i>设备管理</a></li><li class="nav-item"><a class="nav-link" href="#" @click="activeTab = 'locations'"><i class="bi bi-geo-alt-fill me-1"></i>位置管理</a></li><li class="nav-item"><a class="nav-link" href="#" @click="activeTab = 'statistics'"><i class="bi bi-bar-chart-fill me-1"></i>数据统计</a></li></ul></div></div></nav><div class="container mt-4"><!-- Devices Tab --><div v-if="activeTab === 'devices'"><div class="d-flex justify-content-between align-items-center mb-4"><h2>设备管理</h2><button class="btn btn-primary" @click="showAddDeviceModal"><i class="bi bi-plus-lg me-1"></i>添加设备</button></div><!-- Filters --><div class="row mb-4"><div class="col-md-4"><div class="input-group"><span class="input-group-text">类型</span><select class="form-select" v-model="deviceTypeFilter" @change="filterDevices"><option value="">全部</option><option v-for="type in deviceTypes" :value="type">{{ type }}</option></select></div></div><div class="col-md-4"><div class="input-group"><span class="input-group-text">位置</span><select class="form-select" v-model="locationFilter" @change="filterDevices"><option value="">全部</option><option v-for="location in locations" :value="location.name">{{ location.name }}</option></select></div></div><div class="col-md-4"><div class="input-group"><span class="input-group-text">状态</span><select class="form-select" v-model="statusFilter" @change="filterDevices"><option value="">全部</option><option value="on">开启</option><option value="off">关闭</option><option value="offline">离线</option></select></div></div></div><!-- Devices List --><div class="row" v-if="filteredDevices.length > 0"><div class="col-md-4 mb-4" v-for="device in filteredDevices" :key="device.id"><div class="card h-100" :class="{'border-success': device.status === 'on', 'border-danger': device.status === 'offline'}"><div class="card-header d-flex justify-content-between align-items-center"><h5 class="card-title mb-0">{{ device.name }}</h5><span class="badge" :class="getStatusBadgeClass(device.status)">{{ getStatusText(device.status) }}</span></div><div class="card-body"><p class="card-text"><i class="bi bi-tag-fill me-2"></i>类型: {{ device.type }}</p><p class="card-text"><i class="bi bi-geo-alt-fill me-2"></i>位置: {{ device.location || '未设置' }}</p><!-- Light Device Controls --><div v-if="device.type === 'light' && device.status !== 'offline'"><div class="mb-3"><label class="form-label">亮度: {{ device.properties.brightness }}%</label><input type="range" class="form-range" min="0" max="100" step="1":value="device.properties.brightness"@change="setBrightness(device.id, $event.target.value)"></div><div class="mb-3"><label class="form-label">颜色:</label><div class="d-flex"><button class="btn btn-sm color-btn me-1" v-for="color in ['white', 'red', 'green', 'blue', 'yellow']":style="{backgroundColor: color}"@click="setColor(device.id, color)"></button></div></div></div><!-- Thermostat Device Controls --><div v-if="device.type === 'thermostat' && device.status !== 'offline'"><div class="mb-3"><label class="form-label">当前温度: {{ device.properties.current_temp }}°C</label></div><div class="mb-3"><label class="form-label">目标温度: {{ device.properties.target_temp }}°C</label><div class="d-flex align-items-center"><button class="btn btn-sm btn-outline-primary me-2" @click="adjustTemperature(device.id, -1)"><i class="bi bi-dash"></i></button><span class="fs-5">{{ device.properties.target_temp }}°C</span><button class="btn btn-sm btn-outline-primary ms-2" @click="adjustTemperature(device.id, 1)"><i class="bi bi-plus"></i></button></div></div><div class="mb-3"><label class="form-label">模式:</label><div class="btn-group w-100"><button class="btn btn-sm" :class="device.properties.mode === 'off' ? 'btn-primary' : 'btn-outline-primary'"@click="setThermostatMode(device.id, 'off')">关闭</button><button class="btn btn-sm" :class="device.properties.mode === 'heat' ? 'btn-primary' : 'btn-outline-primary'"@click="setThermostatMode(device.id, 'heat')">制热</button><button class="btn btn-sm" :class="device.properties.mode === 'cool' ? 'btn-primary' : 'btn-outline-primary'"@click="setThermostatMode(device.id, 'cool')">制冷</button><button class="btn btn-sm" :class="device.properties.mode === 'auto' ? 'btn-primary' : 'btn-outline-primary'"@click="setThermostatMode(device.id, 'auto')">自动</button></div></div></div></div><div class="card-footer d-flex justify-content-between"><button class="btn btn-sm" :class="device.status === 'on' ? 'btn-danger' : 'btn-success'"@click="toggleDevice(device)":disabled="device.status === 'offline'">{{ device.status === 'on' ? '关闭' : '开启' }}</button><div><button class="btn btn-sm btn-info me-1" @click="showDeviceHistory(device)"><i class="bi bi-clock-history"></i></button><button class="btn btn-sm btn-primary me-1" @click="showEditDeviceModal(device)"><i class="bi bi-pencil"></i></button><button class="btn btn-sm btn-danger" @click="deleteDevice(device)"><i class="bi bi-trash"></i></button></div></div></div></div></div><div class="alert alert-info" v-else>没有找到符合条件的设备。</div></div><!-- Locations Tab --><div v-if="activeTab === 'locations'"><div class="d-flex justify-content-between align-items-center mb-4"><h2>位置管理</h2><button class="btn btn-primary" @click="showAddLocationModal"><i class="bi bi-plus-lg me-1"></i>添加位置</button></div><div class="row"><div class="col-md-4 mb-4" v-for="location in locations" :key="location.id"><div class="card h-100"><div class="card-header"><h5 class="card-title mb-0">{{ location.name }}</h5></div><div class="card-body"><p class="card-text" v-if="location.description">{{ location.description }}</p><p class="card-text" v-else>无描述</p><div class="mt-3"><h6>设备数量:</h6><p>{{ getDeviceCountByLocation(location.name) }} 个设备</p></div></div><div class="card-footer d-flex justify-content-end"><button class="btn btn-sm btn-primary me-1" @click="showEditLocationModal(location)"><i class="bi bi-pencil"></i></button><button class="btn btn-sm btn-danger" @click="deleteLocation(location)":disabled="getDeviceCountByLocation(location.name) > 0"><i class="bi bi-trash"></i></button></div></div></div></div></div><!-- Statistics Tab --><div v-if="activeTab === 'statistics'"><h2 class="mb-4">数据统计</h2><div class="row"><div class="col-md-4 mb-4"><div class="card h-100"><div class="card-header bg-primary text-white"><h5 class="card-title mb-0">设备总览</h5></div><div class="card-body"><div class="d-flex justify-content-between mb-3"><span>总设备数:</span><span class="fw-bold">{{ devices.length }}</span></div><div class="d-flex justify-content-between mb-3"><span>在线设备:</span><span class="fw-bold">{{ devices.filter(d => d.status !== 'offline').length }}</span></div><div class="d-flex justify-content-between mb-3"><span>开启设备:</span><span class="fw-bold">{{ devices.filter(d => d.status === 'on').length }}</span></div><div class="d-flex justify-content-between"><span>离线设备:</span><span class="fw-bold">{{ devices.filter(d => d.status === 'offline').length }}</span></div></div></div></div><div class="col-md-4 mb-4"><div class="card h-100"><div class="card-header bg-success text-white"><h5 class="card-title mb-0">设备类型分布</h5></div><div class="card-body"><div v-for="type in deviceTypes" :key="type" class="mb-3"><div class="d-flex justify-content-between mb-1"><span>{{ type }}:</span><span class="fw-bold">{{ getDeviceCountByType(type) }}</span></div><div class="progress"><div class="progress-bar" role="progressbar":style="{width: (getDeviceCountByType(type) / devices.length * 100) + '%'}":aria-valuenow="getDeviceCountByType(type)"aria-valuemin="0" :aria-valuemax="devices.length"></div></div></div></div></div></div><div class="col-md-4 mb-4"><div class="card h-100"><div class="card-header bg-info text-white"><h5 class="card-title mb-0">位置分布</h5></div><div class="card-body"><div v-for="location in locations" :key="location.id" class="mb-3"><div class="d-flex justify-content-between mb-1"><span>{{ location.name }}:</span><span class="fw-bold">{{ getDeviceCountByLocation(location.name) }}</span></div><div class="progress"><div class="progress-bar bg-info" role="progressbar":style="{width: (getDeviceCountByLocation(location.name) / devices.length * 100) + '%'}":aria-valuenow="getDeviceCountByLocation(location.name)"aria-valuemin="0" :aria-valuemax="devices.length"></div></div></div></div></div></div></div></div></div><!-- Add Device Modal --><div class="modal fade" id="addDeviceModal" tabindex="-1" aria-hidden="true"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">添加设备</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body"><form @submit.prevent="addDevice"><div class="mb-3"><label class="form-label">设备名称</label><input type="text" class="form-control" v-model="newDevice.name" required></div><div class="mb-3"><label class="form-label">设备类型</label><select class="form-select" v-model="newDevice.type" required><option v-for="type in deviceTypes" :value="type">{{ type }}</option></select></div><div class="mb-3"><label class="form-label">位置</label><select class="form-select" v-model="newDevice.location"><option value="">未设置</option><option v-for="location in locations" :value="location.name">{{ location.name }}</option></select></div><!-- Light device properties --><div v-if="newDevice.type === 'light'"><div class="mb-3"><label class="form-label">亮度</label><input type="range" class="form-range" min="0" max="100" step="1" v-model="newDevice.properties.brightness"><div class="text-center">{{ newDevice.properties.brightness }}%</div></div><div class="mb-3"><label class="form-label">颜色</label><select class="form-select" v-model="newDevice.properties.color"><option value="white">白色</option><option value="red">红色</option><option value="green">绿色</option><option value="blue">蓝色</option><option value="yellow">黄色</option></select></div></div><!-- Thermostat device properties --><div v-if="newDevice.type === 'thermostat'"><div class="mb-3"><label class="form-label">当前温度 (°C)</label><input type="number" class="form-control" v-model="newDevice.properties.current_temp" min="0" max="40"></div><div class="mb-3"><label class="form-label">目标温度 (°C)</label><input type="number" class="form-control" v-model="newDevice.properties.target_temp" min="5" max="35"></div><div class="mb-3"><label class="form-label">模式</label><select class="form-select" v-model="newDevice.properties.mode"><option value="off">关闭</option><option value="heat">制热</option><option value="cool">制冷</option><option value="auto">自动</option></select></div></div><div class="text-end"><button type="button" class="btn btn-secondary me-2" data-bs-dismiss="modal">取消</button><button type="submit" class="btn btn-primary">添加</button></div></form></div></div></div></div><!-- Edit Device Modal --><div class="modal fade" id="editDeviceModal" tabindex="-1" aria-hidden="true"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">编辑设备</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body"><form @submit.prevent="updateDevice" v-if="editingDevice"><div class="mb-3"><label class="form-label">设备名称</label><input type="text" class="form-control" v-model="editingDevice.name" required></div><div class="mb-3"><label class="form-label">位置</label><select class="form-select" v-model="editingDevice.location"><option value="">未设置</option><option v-for="location in locations" :value="location.name">{{ location.name }}</option></select></div><div class="text-end"><button type="button" class="btn btn-secondary me-2" data-bs-dismiss="modal">取消</button><button type="submit" class="btn btn-primary">保存</button></div></form></div></div></div></div><!-- Device History Modal --><div class="modal fade" id="deviceHistoryModal" tabindex="-1" aria-hidden="true"><div class="modal-dialog modal-lg"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">设备历史记录</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body"><h6 v-if="selectedDevice">{{ selectedDevice.name }}</h6><div class="table-responsive"><table class="table table-striped"><thead><tr><th>时间</th><th>状态</th><th>属性</th></tr></thead><tbody><tr v-for="entry in deviceHistory" :key="entry.id"><td>{{ formatDate(entry.timestamp) }}</td><td><span class="badge" :class="getStatusBadgeClass(entry.status)">{{ getStatusText(entry.status) }}</span></td><td><pre v-if="entry.properties" class="mb-0">{{ JSON.stringify(entry.properties, null, 2) }}</pre><span v-else>-</span></td></tr></tbody></table></div><div class="alert alert-info" v-if="deviceHistory.length === 0">没有历史记录。</div></div></div></div></div><!-- Add Location Modal --><div class="modal fade" id="addLocationModal" tabindex="-1" aria-hidden="true"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">添加位置</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body"><form @submit.prevent="addLocation"><div class="mb-3"><label class="form-label">位置名称</label><input type="text" class="form-control" v-model="newLocation.name" required></div><div class="mb-3"><label class="form-label">描述</label><textarea class="form-control" v-model="newLocation.description" rows="3"></textarea></div><div class="text-end"><button type="button" class="btn btn-secondary me-2" data-bs-dismiss="modal">取消</button><button type="submit" class="btn btn-primary">添加</button></div></form></div></div></div></div><!-- Edit Location Modal --><div class="modal fade" id="editLocationModal" tabindex="-1" aria-hidden="true"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">编辑位置</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body"><form @submit.prevent="updateLocation" v-if="editingLocation"><div class="mb-3"><label class="form-label">位置名称</label><input type="text" class="form-control" v-model="editingLocation.name" required></div><div class="mb-3"><label class="form-label">描述</label><textarea class="form-control" v-model="editingLocation.description" rows="3"></textarea></div><div class="text-end"><button type="button" class="btn btn-secondary me-2" data-bs-dismiss="modal">取消</button><button type="submit" class="btn btn-primary">保存</button></div></form></div></div></div></div><!-- Toast Notifications --><div class="toast-container position-fixed bottom-0 end-0 p-3"><div id="toast" class="toast" role="alert" aria-live="assertive" aria-atomic="true"><div class="toast-header"><strong class="me-auto">通知</strong><button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button></div><div class="toast-body" id="toastMessage"></div></div></div></div><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script><script src="https://cdn.jsdelivr.net/npm/vue@3.2.45/dist/vue.global.js"></script><script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script><script src="/static/js/app.js"></script>
</body>
</html>相关文章:
Python项目-智能家居控制系统的设计与实现
1. 引言 随着物联网技术的快速发展,智能家居系统已经成为现代家庭生活的重要组成部分。本文将详细介绍一个基于Python的智能家居控制系统的设计与实现过程,该系统能够实现对家庭设备的集中管理和智能控制,提升家居生活的便捷性和舒适度。 2…...
RDP连接无法复制粘贴问题的排查与解决指南
RDP连接无法复制粘贴问题的排查与解决指南 问题描述注意事项排查原因检查RDP剪贴板进程是否正常检查组策略设置检查权限和安全设置检查网络连接 解决方式重启rdpclip.exe进程启用RDP剪贴板重定向调整组策略设置检查并调整安全设置更新驱动程序和系统检查网络连接使用其他远程连…...
IDEA与Maven使用-学习记录(持续补充...)
1. 下载与安装 以ideaIU-2021.3.1为例,安装步骤: 以管理员身份启动ideaIU-2021.3.1修改安装路径为:D:\Program Files\JetBrains\IntelliJ IDEA 2021.3.1勾选【创建桌面快捷方式】(可选)、【打开文件夹作为项目】&…...
Go 语言封装 HTTP 请求的 Curl 工具包
文章目录 Go 语言封装 HTTP 请求的 Curl 工具包🏗️ 工具包结构简介核心结构体定义初始化函数 🌟 功能实现1. 设置请求头2. 构建请求3. 发送请求4. 发送 GET 请求5. 发送 POST 请求6. 发送 PUT 请求7. 发送 DELETE 请求8. 读取响应体 💡 实现…...
RK3568 SD卡调试记录
文章目录 1、环境介绍2、概念理清3、原理图查看4、dts配置5、验证6、SD卡启动6.1、启动优先级6.2、启动流程6.2.1、Maskrom(Boot ROM)启动优先级6.2.2、Pre-loader(SPL)启动优先级 6.3、如何从sd卡启动?6.3.1、制作sd启动卡6.3.2、sd卡启动 7、总结 1、环境介绍 硬…...
高效获取历史行情数据:xtquant的实战应用
高效获取历史行情数据:xtquant的实战应用 🚀量化软件开通 🚀量化实战教程 在量化交易领域,历史行情数据是构建和测试交易策略的基础。无论是回测策略的有效性,还是进行市场分析,高质量的历史数据都是不可…...
【python爬虫】酷狗音乐爬取练习
注意:本次爬取的音乐仅有1分钟试听,仅作学习爬虫的原理,完整音乐需要自行下载客户端。 一、 初步分析 登陆酷狗音乐后随机选取一首歌,在请求里发现一段mp3文件,复制网址,确实是我们需要的url。 复制音频的…...
阿里云 DataWorks面试题集锦及参考答案
目录 简述阿里云 DataWorks 的核心功能模块及其在企业数据治理中的作用 简述 DataWorks 的核心功能模块及其应用场景 解释 DataWorks 中工作空间、项目、业务流程的三层逻辑关系 解释 DataWorks 中的 “节点”、“工作流” 和 “依赖关系” 设计 解释 DataWorks 中 “周期任…...
uniapp+Vue3 开发小程序的下载文件功能
小程序下载文件,可以先预览文件内容,然后在手机上打开文件的工具中选择保存。 简单示例:(复制到HBuilder直接食用即可) <template><view class"container-detail"><view class"example…...
Apache Log4j 2
目录 1. Apache Log4j 2 简介 1.1 什么是Log4j 2? 1.2 Log4j 2 的主要特性 2. Log4j 2 的核心组件 2.1 Logger 2.2 Appender 2.3 Layout 2.4 Filter 2.5 Configuration 3. Log4j 2 的配置 4. Log4j 2 的使用示例 4.1 Maven 依赖 4.2 示例代码 4.3 输出…...
4.2.2 ArrayList类
ArrayList类与List类的用法差不多,提供的方法也差不多。但是与List不同的是,ArrayList可以包含任意类型的数据,但是相应的,要使用包含的数据,就必须对数据做相应的装箱和拆箱(关于装箱和拆箱,请…...
L1-088 静静的推荐
L1-088 静静的推荐 - 团体程序设计天梯赛-练习集 (pintia.cn) 题解 这里代码很简单,但是主要是循环里面的内容很难理解,下面是关于循环里面的内容理解: 这里 n 10 表示有 10 个学生,k 2 表示企业接受 2 批次的推荐名单&#…...
普及听力保健知识竞赛
普及听力保健知识竞赛 热点指数:★★★ 日期:3月3日 关键词:爱耳护耳、听力健康、耳部保健、听力科普 适合行业:医疗健康、健康护理、教育培训、公益组织 推荐功能:答题、H5宣传 宣传方向:广泛普及听力…...
小结: IGMP协议
IGMP(Internet Group Management Protocol)协议详解 IGMP(Internet Group Management Protocol)是IPv4 组播(Multicast)通信的控制协议,主要用于主机和路由器之间的组播成员管理。IGMP 允许主机…...
Dify 本地部署教程
目录 一、下载安装包 二、修改配置 三、启动容器 四、访问 Dify 五、总结 本篇文章主要记录 Dify 本地部署过程,有问题欢迎交流~ 一、下载安装包 从 Github 仓库下载最新稳定版软件包,点击下载~,当然也可以克隆仓库或者从仓库里直接下载zip源码包。 目前最新版本是V…...
ConcurrentHashMap从源码总结使用注意事项(源码)
ConcurrentHashMap实现原理 目录 ConcurrentHashMap实现原理核心源码解读(1)数据结构: 采用数组链表/红黑树(2)初始化(3)并发扩容(4)put 操作流程(5)计数 siz…...
前端 UI 框架发展史
上一小节我们了解了前端 UI 框架的作用和意义,接下来我们再来了解前端 UI 框架的发展历史。 虽然是讲历史,但我不想讲得太复杂,也不打算搞什么编年史记录啥的,毕竟我们不是来学历史的。 我会简单描述一下前端 UI 框架的发展历程…...
【工控】线扫相机小结 第五篇
背景介绍 线扫相机通过光栅尺的脉冲触发, 我在调试线扫过程中,发现图像被拉伸,预设调节分配器。图像正常后,我提高的相机的扫描速度(Y轴动的更快了)。 动的更快的发现,图像变短了(以…...
AI与SEO关键词智能解析
内容概要 人工智能技术正重塑搜索引擎优化的底层逻辑,其核心突破体现在关键词解析维度的结构性升级。通过机器学习算法对海量搜索数据的动态学习,AI不仅能够识别传统TF-IDF模型中的高频词汇,更能捕捉语义网络中隐含的关联特征。下表展示了传…...
STM32---FreeRTOS消息队列
一、简介 1、队列简介: 队列:是任务到任务,任务到中断、中断到任务数据交流的一种机制(消息传递)。 FreeRTOS基于队列,实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
32位寻址与64位寻址
32位寻址与64位寻址 32位寻址是什么? 32位寻址是指计算机的CPU、内存或总线系统使用32位二进制数来标识和访问内存中的存储单元(地址),其核心含义与能力如下: 1. 核心定义 地址位宽:CPU或内存控制器用32位…...
深度解析:etcd 在 Milvus 向量数据库中的关键作用
目录 🚀 深度解析:etcd 在 Milvus 向量数据库中的关键作用 💡 什么是 etcd? 🧠 Milvus 架构简介 📦 etcd 在 Milvus 中的核心作用 🔧 实际工作流程示意 ⚠️ 如果 etcd 出现问题会怎样&am…...
