前言
当夜幕降临,城市中的高楼大厦变身为巨大的显示屏,通过窗户的灯光展示动态图案、文字甚至动画,这种震撼的视觉效果不仅是城市夜景的亮点,更是物联网技术、分布式控制系统的完美展现。本文将深入探讨如何实现这样一个大楼灯光矩阵显示系统,从技术可行性分析到完整的代码实现。
一、技术可行性分析
1.1 核心概念
将大楼的每个窗户视为一个"像素点",通过控制每个房间的灯光开关和颜色,整栋大楼就变成了一个巨型的LED显示屏。这个概念的实现需要解决以下核心问题:
- 硬件层:智能灯光设备的选型与部署
- 网络层:可靠的通信协议与网络架构
- 控制层:分布式控制系统的设计
- 应用层:图形渲染与显示算法
1.2 技术优势
- 视觉冲击力强:建筑物本身的大尺寸提供了无与伦比的展示效果
- 成本相对可控:利用现有建筑和智能照明系统
- 可扩展性好:模块化设计易于维护和升级
- 环保节能:使用LED智能灯具,可编程控制
1.3 技术挑战
- 同步性要求:数百个灯光节点需要精确同步
- 网络稳定性:需要可靠的网络通信
- 分辨率限制:受建筑窗户布局限制
- 功耗管理:大规模灯光系统的电力管理
二、系统架构设计
2.1 整体架构
┌─────────────────────────────────────────┐
│ Web控制台/移动端应用 │
└───────────────┬─────────────────────────┘
│ HTTP/WebSocket
┌───────────────┴─────────────────────────┐
│ 中央控制服务器 │
│ - 图形渲染引擎 │
│ - 设备管理模块 │
│ - 指令分发系统 │
└───────────────┬─────────────────────────┘
│ MQTT/CoAP
┌───────────────┴─────────────────────────┐
│ 楼层控制器集群 │
│ (每层一个或多个控制器) │
└───────────────┬─────────────────────────┘
│ Zigbee/BLE/WiFi
┌───────────────┴─────────────────────────┐
│ 智能灯光节点 │
│ (每个房间/窗户一个智能灯泡) │
└─────────────────────────────────────────┘
2.2 技术选型
- 智能灯具:支持RGB调色的WiFi/Zigbee智能灯泡
- 通信协议:MQTT(消息队列)+ WebSocket(实时通信)
- 后端语言:Python(Flask/FastAPI)+ Node.js可选
- 前端框架:React/Vue.js + Canvas API
- 数据库:Redis(缓存)+ MongoDB(配置存储)
三、核心功能实现
3.1 图形渲染引擎
首先实现一个将图像转换为灯光矩阵数据的渲染引擎:
python
# graphics_renderer.py
from PIL import Image, ImageDraw, ImageFont
import numpy as np
from typing import List, Tuple
class BuildingLightRenderer:
"""大楼灯光图形渲染引擎"""
def __init__(self, width: int, height: int):
"""
初始化渲染器
:param width: 大楼宽度方向的窗户数量
:param height: 大楼高度方向的窗户数量
"""
self.width = width
self.height = height
self.canvas = Image.new('RGB', (width, height), color='black')
def load_image(self, image_path: str) -> np.ndarray:
"""
加载并缩放图像到建筑物分辨率
:param image_path: 图像文件路径
:return: RGB数组
"""
img = Image.open(image_path)
# 缩放到建筑物的分辨率
img = img.resize((self.width, self.height), Image.Resampling.LANCZOS)
return np.array(img)
def render_text(self, text: str, font_size: int = 10,
color: Tuple[int, int, int] = (255, 255, 255)) -> np.ndarray:
"""
渲染文字
:param text: 要显示的文字
:param font_size: 字体大小
:param color: 文字颜色(R,G,B)
:return: RGB数组
"""
img = Image.new('RGB', (self.width, self.height), color='black')
draw = ImageDraw.Draw(img)
try:
# 尝试使用系统字体
font = ImageFont.truetype("arial.ttf", font_size)
except:
font = ImageFont.load_default()
# 计算文字位置使其居中
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = (self.width - text_width) // 2
y = (self.height - text_height) // 2
draw.text((x, y), text, fill=color, font=font)
return np.array(img)
def render_pattern(self, pattern_type: str) -> np.ndarray:
"""
渲染预定义图案
:param pattern_type: 图案类型 (wave, gradient, checkerboard等)
:return: RGB数组
"""
img = np.zeros((self.height, self.width, 3), dtype=np.uint8)
if pattern_type == 'wave':
# 波浪图案
for y in range(self.height):
for x in range(self.width):
wave = int(127 * (1 + np.sin(x / self.width * 4 * np.pi)))
img[y, x] = [wave, wave, 255]
elif pattern_type == 'gradient':
# 渐变图案
for y in range(self.height):
for x in range(self.width):
r = int(255 * x / self.width)
b = int(255 * y / self.height)
img[y, x] = [r, 0, b]
elif pattern_type == 'checkerboard':
# 棋盘图案
block_size = max(1, min(self.width, self.height) // 8)
for y in range(self.height):
for x in range(self.width):
if ((x // block_size) + (y // block_size)) % 2 == 0:
img[y, x] = [255, 255, 255]
return img
def create_animation_frames(self, text: str, frame_count: int = 30) -> List[np.ndarray]:
"""
创建滚动文字动画帧
:param text: 要显示的文字
:param frame_count: 动画帧数
:return: 帧数组列表
"""
frames = []
font_size = min(self.height // 2, 15)
for i in range(frame_count):
img = Image.new('RGB', (self.width * 2, self.height), color='black')
draw = ImageDraw.Draw(img)
try:
font = ImageFont.truetype("arial.ttf", font_size)
except:
font = ImageFont.load_default()
# 文字从右向左滚动
x_offset = self.width - int((self.width * 2) * i / frame_count)
y_offset = self.height // 3
draw.text((x_offset, y_offset), text, fill=(0, 255, 255), font=font)
# 裁剪到建筑物尺寸
cropped = img.crop((0, 0, self.width, self.height))
frames.append(np.array(cropped))
return frames
def matrix_to_light_commands(self, matrix: np.ndarray) -> List[dict]:
"""
将图像矩阵转换为灯光控制指令
:param matrix: RGB图像矩阵
:return: 灯光控制指令列表
"""
commands = []
for y in range(self.height):
for x in range(self.width):
r, g, b = matrix[y, x]
# 判断是否点亮(亮度阈值)
brightness = (int(r) + int(g) + int(b)) / 3
state = 'on' if brightness > 30 else 'off'
command = {
'node_id': f'light_{x}_{y}',
'x': x,
'y': y,
'state': state,
'color': {
'r': int(r),
'g': int(g),
'b': int(b)
},
'brightness': min(100, int(brightness / 2.55))
}
commands.append(command)
return commands
3.2 MQTT控制服务
实现基于MQTT的灯光控制服务:
python
# mqtt_controller.py
import paho.mqtt.client as mqtt
import json
import time
from typing import List, Callable
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class BuildingLightController:
"""大楼灯光MQTT控制器"""
def __init__(self, broker_host: str = 'localhost',
broker_port: int = 1883,
username: str = None,
password: str = None):
"""
初始化MQTT控制器
:param broker_host: MQTT代理服务器地址
:param broker_port: MQTT代理服务器端口
:param username: 用户名
:param password: 密码
"""
self.broker_host = broker_host
self.broker_port = broker_port
self.client = mqtt.Client(client_id="building_light_controller")
if username and password:
self.client.username_pw_set(username, password)
self.client.on_connect = self._on_connect
self.client.on_message = self._on_message
self.client.on_disconnect = self._on_disconnect
self.connected = False
self.message_callbacks = []
def _on_connect(self, client, userdata, flags, rc):
"""连接回调"""
if rc == 0:
self.connected = True
logger.info(f"成功连接到MQTT代理: {self.broker_host}:{self.broker_port}")
# 订阅状态反馈主题
self.client.subscribe("building/lights/+/status")
else:
logger.error(f"连接失败,错误码: {rc}")
def _on_message(self, client, userdata, msg):
"""消息接收回调"""
try:
payload = json.loads(msg.payload.decode())
logger.debug(f"收到消息 - 主题: {msg.topic}, 内容: {payload}")
# 调用注册的回调函数
for callback in self.message_callbacks:
callback(msg.topic, payload)
except Exception as e:
logger.error(f"处理消息时出错: {e}")
def _on_disconnect(self, client, userdata, rc):
"""断开连接回调"""
self.connected = False
logger.warning(f"与MQTT代理断开连接,错误码: {rc}")
def connect(self):
"""连接到MQTT代理"""
try:
self.client.connect(self.broker_host, self.broker_port, 60)
self.client.loop_start()
# 等待连接建立
timeout = 10
start_time = time.time()
while not self.connected and (time.time() - start_time) < timeout:
time.sleep(0.1)
if not self.connected:
raise ConnectionError("连接超时")
except Exception as e:
logger.error(f"连接MQTT代理失败: {e}")
raise
def disconnect(self):
"""断开MQTT连接"""
self.client.loop_stop()
self.client.disconnect()
logger.info("已断开MQTT连接")
def send_light_command(self, node_id: str, command: dict):
"""
发送单个灯光控制指令
:param node_id: 节点ID
:param command: 控制指令
"""
topic = f"building/lights/{node_id}/command"
payload = json.dumps(command)
result = self.client.publish(topic, payload, qos=1)
if result.rc != mqtt.MQTT_ERR_SUCCESS:
logger.error(f"发送指令失败: {node_id}")
def send_batch_commands(self, commands: List[dict], batch_size: int = 50):
"""
批量发送灯光控制指令
:param commands: 控制指令列表
:param batch_size: 每批发送的数量
"""
logger.info(f"开始发送 {len(commands)} 条灯光指令")
for i in range(0, len(commands), batch_size):
batch = commands[i:i + batch_size]
for cmd in batch:
node_id = cmd['node_id']
self.send_light_command(node_id, cmd)
# 小延迟避免网络拥塞
time.sleep(0.05)
if (i + batch_size) % 200 == 0:
logger.info(f"已发送 {i + batch_size}/{len(commands)} 条指令")
logger.info("所有指令发送完成")
def send_sync_command(self, commands: List[dict], sync_time: float = None):
"""
发送同步控制指令(所有灯光在指定时间同步变化)
:param commands: 控制指令列表
:param sync_time: 同步执行的时间戳(秒)
"""
if sync_time is None:
sync_time = time.time() + 2 # 默认2秒后执行
sync_payload = {
'sync_time': sync_time,
'commands': commands
}
topic = "building/lights/sync"
payload = json.dumps(sync_payload)
self.client.publish(topic, payload, qos=2)
logger.info(f"已发送同步指令,将在 {sync_time} 执行")
def set_all_lights(self, state: str, color: dict = None):
"""
设置所有灯光
:param state: 'on' 或 'off'
:param color: RGB颜色字典
"""
command = {
'state': state,
'color': color or {'r': 255, 'g': 255, 'b': 255},
'brightness': 100 if state == 'on' else 0
}
topic = "building/lights/all/command"
payload = json.dumps(command)
self.client.publish(topic, payload, qos=1)
logger.info(f"已设置所有灯光为: {state}")
def register_callback(self, callback: Callable):
"""
注册消息回调函数
:param callback: 回调函数,接收 (topic, payload) 参数
"""
self.message_callbacks.append(callback)
3.3 Web API服务
创建RESTful API和WebSocket服务:
python
# api_server.py
from flask import Flask, request, jsonify
from flask_socketio import SocketIO, emit
from flask_cors import CORS
import threading
import time
from graphics_renderer import BuildingLightRenderer
from mqtt_controller import BuildingLightController
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
CORS(app)
socketio = SocketIO(app, cors_allowed_origins="*")
# 全局对象
renderer = BuildingLightRenderer(width=20, height=30) # 假设20x30的灯光矩阵
mqtt_controller = BuildingLightController(
broker_host='localhost',
broker_port=1883
)
# 动画播放状态
animation_state = {
'playing': False,
'current_frame': 0,
'frames': []
}
@app.route('/api/health', methods=['GET'])
def health_check():
"""健康检查"""
return jsonify({
'status': 'ok',
'mqtt_connected': mqtt_controller.connected,
'renderer_size': f'{renderer.width}x{renderer.height}'
})
@app.route('/api/display/text', methods=['POST'])
def display_text():
"""显示文字"""
try:
data = request.json
text = data.get('text', 'Hello')
color = data.get('color', [255, 255, 255])
font_size = data.get('font_size', 10)
# 渲染文字
matrix = renderer.render_text(text, font_size, tuple(color))
# 转换为灯光指令
commands = renderer.matrix_to_light_commands(matrix)
# 发送到MQTT
mqtt_controller.send_batch_commands(commands)
return jsonify({
'success': True,
'message': f'已显示文字: {text}',
'commands_sent': len(commands)
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/display/pattern', methods=['POST'])
def display_pattern():
"""显示图案"""
try:
data = request.json
pattern_type = data.get('pattern', 'wave')
# 渲染图案
matrix = renderer.render_pattern(pattern_type)
# 转换为灯光指令
commands = renderer.matrix_to_light_commands(matrix)
# 发送到MQTT
mqtt_controller.send_batch_commands(commands)
return jsonify({
'success': True,
'message': f'已显示图案: {pattern_type}',
'commands_sent': len(commands)
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/display/image', methods=['POST'])
def display_image():
"""显示图片"""
try:
if 'file' not in request.files:
return jsonify({'success': False, 'error': '未找到图片文件'}), 400
file = request.files['file']
# 保存临时文件
temp_path = '/tmp/uploaded_image.png'
file.save(temp_path)
# 渲染图片
matrix = renderer.load_image(temp_path)
# 转换为灯光指令
commands = renderer.matrix_to_light_commands(matrix)
# 发送到MQTT
mqtt_controller.send_batch_commands(commands)
return jsonify({
'success': True,
'message': '已显示图片',
'commands_sent': len(commands)
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/animation/start', methods=['POST'])
def start_animation():
"""启动动画"""
try:
data = request.json
text = data.get('text', 'HELLO WORLD')
frame_count = data.get('frame_count', 30)
# 生成动画帧
frames = renderer.create_animation_frames(text, frame_count)
animation_state['frames'] = frames
animation_state['playing'] = True
animation_state['current_frame'] = 0
# 启动动画播放线程
thread = threading.Thread(target=play_animation)
thread.daemon = True
thread.start()
return jsonify({
'success': True,
'message': '动画已启动',
'frame_count': len(frames)
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/animation/stop', methods=['POST'])
def stop_animation():
"""停止动画"""
animation_state['playing'] = False
return jsonify({'success': True, 'message': '动画已停止'})
@app.route('/api/lights/all', methods=['POST'])
def control_all_lights():
"""控制所有灯光"""
try:
data = request.json
state = data.get('state', 'off')
color = data.get('color', {'r': 255, 'g': 255, 'b': 255})
mqtt_controller.set_all_lights(state, color)
return jsonify({
'success': True,
'message': f'所有灯光已设置为: {state}'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
def play_animation():
"""动画播放线程"""
fps = 10 # 每秒10帧
frame_delay = 1.0 / fps
while animation_state['playing'] and animation_state['frames']:
frame_idx = animation_state['current_frame']
frames = animation_state['frames']
if frame_idx >= len(frames):
frame_idx = 0
animation_state['current_frame'] = 0
# 渲染当前帧
matrix = frames[frame_idx]
commands = renderer.matrix_to_light_commands(matrix)
mqtt_controller.send_batch_commands(commands, batch_size=100)
# 通过WebSocket发送进度
socketio.emit('animation_progress', {
'current_frame': frame_idx,
'total_frames': len(frames)
})
animation_state['current_frame'] += 1
time.sleep(frame_delay)
@socketio.on('connect')
def handle_connect():
"""WebSocket连接"""
print('客户端已连接')
emit('connection_status', {'status': 'connected'})
@socketio.on('disconnect')
def handle_disconnect():
"""WebSocket断开"""
print('客户端已断开')
def initialize_system():
"""初始化系统"""
try:
mqtt_controller.connect()
print("MQTT控制器已连接")
except Exception as e:
print(f"初始化失败: {e}")
if __name__ == '__main__':
initialize_system()
socketio.run(app, host='0.0.0.0', port=5000, debug=True)
3.4 前端控制界面
创建简单的Web控制界面:
html
<!-- 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>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.content {
padding: 30px;
}
.control-panel {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.control-card {
background: #f8f9fa;
border-radius: 15px;
padding: 25px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.control-card h3 {
color: #667eea;
margin-bottom: 20px;
font-size: 1.3em;
}
.input-group {
margin-bottom: 15px;
}
.input-group label {
display: block;
margin-bottom: 8px;
color: #666;
font-weight: 500;
}
.input-group input,
.input-group select {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
transition: border-color 0.3s;
}
.input-group input:focus,
.input-group select:focus {
outline: none;
border-color: #667eea;
}
button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 30px;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
width: 100%;
font-weight: 600;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
button:active {
transform: translateY(0);
}
.canvas-container {
background: #000;
border-radius: 15px;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
#preview-canvas {
border: 2px solid #667eea;
border-radius: 8px;
image-rendering: pixelated;
}
.status-bar {
background: #f8f9fa;
padding: 15px;
border-radius: 10px;
margin-top: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.status-indicator {
display: flex;
align-items: center;
gap: 10px;
}
.status-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: #4CAF50;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.color-picker-group {
display: flex;
gap: 10px;
align-items: center;
}
.color-picker-group input[type="color"] {
width: 50px;
height: 40px;
border: none;
border-radius: 8px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🏢 大楼灯光控制系统</h1>
<p>Building Light Matrix Display Controller</p>
</div>
<div class="content">
<div class="control-panel">
<!-- 文字显示控制 -->
<div class="control-card">
<h3>📝 文字显示</h3>
<div class="input-group">
<label>输入文字</label>
<input type="text" id="text-input" placeholder="输入要显示的文字" value="HELLO">
</div>
<div class="input-group color-picker-group">
<label>文字颜色</label>
<input type="color" id="text-color" value="#00ffff">
</div>
<button onclick="displayText()">显示文字</button>
</div>
<!-- 图案显示控制 -->
<div class="control-card">
<h3>🎨 图案显示</h3>
<div class="input-group">
<label>选择图案</label>
<select id="pattern-select">
<option value="wave">波浪</option>
<option value="gradient">渐变</option>
<option value="checkerboard">棋盘</option>
</select>
</div>
<button onclick="displayPattern()">显示图案</button>
</div>
<!-- 动画控制 -->
<div class="control-card">
<h3>🎬 动画控制</h3>
<div class="input-group">
<label>动画文字</label>
<input type="text" id="animation-text" placeholder="滚动文字" value="WELCOME">
</div>
<div class="input-group">
<label>帧数</label>
<input type="number" id="frame-count" value="30" min="10" max="100">
</div>
<button onclick="startAnimation()">开始动画</button>
<button onclick="stopAnimation()" style="margin-top: 10px; background: #f44336;">停止动画</button>
</div>
<!-- 全局控制 -->
<div class="control-card">
<h3>🎛️ 全局控制</h3>
<button onclick="allLightsOn()">全部打开</button>
<button onclick="allLightsOff()" style="margin-top: 10px; background: #f44336;">全部关闭</button>
</div>
</div>
<!-- 预览画布 -->
<div class="canvas-container">
<canvas id="preview-canvas" width="200" height="300"></canvas>
</div>
<!-- 状态栏 -->
<div class="status-bar">
<div class="status-indicator">
<div class="status-dot"></div>
<span>系统运行中</span>
</div>
<div id="status-message">准备就绪</div>
</div>
</div>
</div>
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script>
const API_BASE = 'http://localhost:5000/api';
const socket = io('http://localhost:5000');
// 初始化画布
const canvas = document.getElementById('preview-canvas');
const ctx = canvas.getContext('2d');
// WebSocket事件
socket.on('connect', () => {
console.log('WebSocket已连接');
updateStatus('已连接到服务器');
});
socket.on('animation_progress', (data) => {
updateStatus(`动画播放中: ${data.current_frame}/${data.total_frames}`);
});
// 更新状态消息
function updateStatus(message) {
document.getElementById('status-message').textContent = message;
}
// 显示文字
async function displayText() {
const text = document.getElementById('text-input').value;
const colorHex = document.getElementById('text-color').value;
const color = hexToRgb(colorHex);
updateStatus('正在显示文字...');
try {
const response = await fetch(`${API_BASE}/display/text`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, color })
});
const result = await response.json();
if (result.success) {
updateStatus(`文字"${text}"已显示`);
drawTextPreview(text, color);
}
} catch (error) {
updateStatus('错误: ' + error.message);
}
}
// 显示图案
async function displayPattern() {
const pattern = document.getElementById('pattern-select').value;
updateStatus('正在显示图案...');
try {
const response = await fetch(`${API_BASE}/display/pattern`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pattern })
});
const result = await response.json();
if (result.success) {
updateStatus(`图案"${pattern}"已显示`);
drawPatternPreview(pattern);
}
} catch (error) {
updateStatus('错误: ' + error.message);
}
}
// 开始动画
async function startAnimation() {
const text = document.getElementById('animation-text').value;
const frameCount = parseInt(document.getElementById('frame-count').value);
updateStatus('正在启动动画...');
try {
const response = await fetch(`${API_BASE}/animation/start`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, frame_count: frameCount })
});
const result = await response.json();
if (result.success) {
updateStatus('动画已启动');
}
} catch (error) {
updateStatus('错误: ' + error.message);
}
}
// 停止动画
async function stopAnimation() {
try {
const response = await fetch(`${API_BASE}/animation/stop`, {
method: 'POST'
});
const result = await response.json();
if (result.success) {
updateStatus('动画已停止');
}
} catch (error) {
updateStatus('错误: ' + error.message);
}
}
// 全部打开
async function allLightsOn() {
updateStatus('打开所有灯光...');
try {
const response = await fetch(`${API_BASE}/lights/all`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ state: 'on' })
});
const result = await response.json();
if (result.success) {
updateStatus('所有灯光已打开');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
} catch (error) {
updateStatus('错误: ' + error.message);
}
}
// 全部关闭
async function allLightsOff() {
updateStatus('关闭所有灯光...');
try {
const response = await fetch(`${API_BASE}/lights/all`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ state: 'off' })
});
const result = await response.json();
if (result.success) {
updateStatus('所有灯光已关闭');
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
} catch (error) {
updateStatus('错误: ' + error.message);
}
}
// 预览画布绘制
function drawTextPreview(text, color) {
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
ctx.font = '40px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, canvas.width / 2, canvas.height / 2);
}
function drawPatternPreview(pattern) {
const imageData = ctx.createImageData(canvas.width, canvas.height);
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
const i = (y * canvas.width + x) * 4;
if (pattern === 'wave') {
const wave = Math.floor(127 * (1 + Math.sin(x / canvas.width * 4 * Math.PI)));
imageData.data[i] = wave;
imageData.data[i + 1] = wave;
imageData.data[i + 2] = 255;
} else if (pattern === 'gradient') {
imageData.data[i] = Math.floor(255 * x / canvas.width);
imageData.data[i + 1] = 0;
imageData.data[i + 2] = Math.floor(255 * y / canvas.height);
} else if (pattern === 'checkerboard') {
const blockSize = 20;
const value = ((Math.floor(x / blockSize) + Math.floor(y / blockSize)) % 2) * 255;
imageData.data[i] = value;
imageData.data[i + 1] = value;
imageData.data[i + 2] = value;
}
imageData.data[i + 3] = 255;
}
}
ctx.putImageData(imageData, 0, 0);
}
// 辅助函数:十六进制转RGB
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? [
parseInt(result[1], 16),
parseInt(result[2], 16),
parseInt(result[3], 16)
] : [255, 255, 255];
}
// 初始化
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
</script>
</body>
</html>
四、部署与实施步骤
4.1 硬件准备
-
智能灯具选型:
- 推荐:飞利浦Hue、Yeelight、小米智能灯泡
- 要求:支持WiFi/Zigbee,RGB调色,可编程控制
-
网络基础设施:
- 千兆以太网主干网络
- WiFi 6 AP覆盖(每层至少2个)
- Zigbee网关(可选)
-
控制服务器:
- CPU: 4核心以上
- RAM: 8GB以上
- 存储: 256GB SSD
- 操作系统: Ubuntu 20.04 LTS
4.2 软件部署
bash
# 1. 安装系统依赖
sudo apt update
sudo apt install -y python3-pip python3-venv mosquitto redis-server
# 2. 创建Python虚拟环境
python3 -m venv venv
source venv/bin/activate
# 3. 安装Python依赖
pip install flask flask-socketio flask-cors paho-mqtt pillow numpy
# 4. 启动MQTT代理
sudo systemctl start mosquitto
sudo systemctl enable mosquitto
# 5. 启动Redis
sudo systemctl start redis-server
sudo systemctl enable redis-server
# 6. 运行应用
python api_server.py
4.3 配置文件示例
yaml
# config.yaml
building:
name: "Demo Building"
dimensions:
width: 20 # 横向窗户数
height: 30 # 纵向窗户数
mqtt:
broker: "localhost"
port: 1883
username: "admin"
password: "password"
network:
api_host: "0.0.0.0"
api_port: 5000
websocket_enabled: true
performance:
batch_size: 50
frame_rate: 10
sync_tolerance_ms: 100
五、性能优化建议
5.1 网络优化
-
MQTT QoS策略:
- 状态更新使用QoS 0(快速但不保证)
- 关键指令使用QoS 1(保证送达)
- 同步指令使用QoS 2(仅一次送达)
-
批量处理:
- 将多个指令合并为批次发送
- 使用消息压缩减少带宽占用
5.2 同步机制
python
# 时间同步示例
import ntplib
from datetime import datetime
def sync_system_time():
"""与NTP服务器同步时间"""
client = ntplib.NTPClient()
try:
response = client.request('pool.ntp.org', version=3)
return response.tx_time
except:
return time.time()
5.3 故障恢复
- 实现断线重连机制
- 设备状态缓存与恢复
- 日志记录与监控告警
六、实际案例与效果
6.1 应用场景
- 节日庆典:显示节日主题图案和祝福语
- 品牌宣传:展示企业Logo和宣传标语
- 艺术装置:创意灯光艺术表演
- 互动游戏:结合手机App的互动游戏
6.2 成本估算
以30层、20个窗户宽的建筑为例(600个控制点):
项目 | 数量 | 单价 | 小计 |
---|---|---|---|
智能灯泡 | 600 | ¥80 | ¥48,000 |
网关/控制器 | 30 | ¥300 | ¥9,000 |
服务器 | 1 | ¥8,000 | ¥8,000 |
网络设备 | 1套 | ¥15,000 | ¥15,000 |
软件开发 | - | - | ¥50,000 |
总计 | ¥130,000 |
七、总结与展望
大楼灯光矩阵显示系统是物联网技术在建筑领域的创新应用,通过本文的技术方案,我们实现了:
✅ 完整的系统架构 :从硬件到软件的全栈方案
✅ 可扩展的设计 :支持不同规模建筑的灵活部署
✅ 丰富的功能 :文字、图案、动画的多样化展示
✅ 友好的接口:Web控制台和API接口方便集成
未来发展方向
- AI驱动:使用机器学习优化显示效果
- 实时互动:结合摄像头实现观众互动
- 声光同步:配合音乐实现声光表演
- 能耗优化:智能调度降低电力消耗
本文所有代码均为示例性质,实际部署时需根据具体硬件和需求进行调整。