diff --git a/yanzhu-modules/yanzhu-led/LED系统技术文档.md b/yanzhu-modules/yanzhu-led/LED系统技术文档.md new file mode 100644 index 00000000..410731fc --- /dev/null +++ b/yanzhu-modules/yanzhu-led/LED系统技术文档.md @@ -0,0 +1,489 @@ +# LED 屏管理系统技术文档 + +## 一、项目概述 + +本项目是基于 Onbon Bx06 系列 LED 控制器开发的 LED 屏管理系统,实现了 LED 屏的自动连接、内容绘制、定时刷新和状态监控等功能。 + +## 二、核心依赖 + +### Maven 依赖配置 +项目使用 Onbon Bx06 系列 SDK,主要依赖包括: + +```xml + + + bx06 + bx06 + 0.6.5-SNAPSHOT + + + + + bx06.message + bx06.message + 0.6.5-SNAPSHOT + + + + + uia-comm + uia-comm + 0.5.3-SNAPSHOT + + + +``` + +## 三、核心架构 + +### 3.1 主要服务类 + +| 类名 | 职责 | 文件路径 | +|------|------|----------| +| LedServerService | LED 服务器管理,启动/停止/监控 | service/LedServerService.java | +| LedDrawService | LED 内容绘制和屏幕连接管理 | service/LedDrawService.java | +| LedServerListener | LED 屏连接/断开事件监听 | listener/LedServerListener.java | +| LedScreenServiceImpl | LED 屏配置管理和数据服务 | service/impl/LedScreenServiceImpl.java | +| LedController | REST API 接口 | controller/LedController.java | +| LedConfig | 配置类,启用定时任务 | config/LedConfig.java | +| LedProperties | 配置属性管理 | config/LedProperties.java | + +### 3.2 工具类 + +| 类名 | 职责 | +|------|------| +| UniLedDrawer | LED 屏内容绘制工具(类型1) | +| Uni2LedDrawer | LED 屏内容绘制工具(类型2) | +| LedFileUtils | LED 文件操作工具 | +| LedContentUpdater | LED 内容更新工具 | + +## 四、Onbon 接口使用详解 + +### 4.1 环境初始化接口 + +#### Bx6GEnv.initial() +```java +// 初始化 Bx6G 环境 +Bx6GEnv.initial(ledProperties.getEnvTimeout()); +``` +- **功能**:初始化 Onbon Bx06 SDK 环境 +- **参数**:超时时间(毫秒) +- **使用位置**:LedServerService.initializeServer() + +### 4.2 服务器管理接口 + +#### Bx6GServer +```java +// 创建服务器实例 +Bx6GServer server = new Bx6GServer("LED_Server", PORT, new Bx6E()); + +// 启动服务器 +boolean started = server.start(); + +// 停止服务器 +server.stop(); + +// 添加监听器 +server.addListener(ledServerListener); +``` +- **功能**:创建和管理 LED 服务器 +- **参数**: + - 服务器名称 + - 监听端口(默认 3801) + - 控制器类型(Bx6E/Bx6M/Bx6Q) +- **使用位置**:LedServerService + +#### 控制器系列类 +```java +import onbon.bx06.series.Bx6E; +import onbon.bx06.series.Bx6M; +import onbon.bx06.series.Bx6Q; +``` +- **功能**:定义支持的控制器类型 +- **使用位置**:LedServerService.getServerByType() + +### 4.3 屏幕操作接口 + +#### Bx6GScreen +```java +// Ping 测试 +Bx6GScreen.Result result = screen.ping(); + +// 检查控制器状态 +screen.checkControllerStatus(); + +// 检查固件版本 +screen.checkFirmware(); + +// 读取控制器 ID +screen.readControllerId(); + +// 写入节目 +screen.writeProgram(programFile); + +// 获取屏幕配置 +Bx6GScreenProfile profile = screen.getProfile(); +``` +- **功能**:LED 屏幕操作接口 +- **使用位置**:LedDrawService, LedServerListener + +### 4.4 节目文件接口 + +#### ProgramBxFile +```java +// 创建节目文件 +ProgramBxFile programFile = new ProgramBxFile("P000", profile); + +// 添加区域 +programFile.addArea(area); +``` +- **功能**:创建和管理节目文件 +- **参数**: + - 节目名称 + - 屏幕配置文件(Bx6GScreenProfile) +- **使用位置**:LedDrawService.createProgramByControllerType() + +### 4.5 显示区域接口 + +#### TextCaptionBxArea(文本区域) +```java +// 创建文本区域 +TextCaptionBxArea area = new TextCaptionBxArea(x, y, width, height, profile); + +// 添加页面 +area.addPage(textPage); +area.addPage(imagePage); +``` +- **功能**:创建文本和图片显示区域 +- **参数**:x坐标, y坐标, 宽度, 高度, 屏幕配置 +- **使用位置**:LedDrawService.createBx6MProgram() + +#### DateTimeBxArea(日期时间区域) +```java +// 创建日期时间区域 +DateTimeBxArea timeBxArea = new DateTimeBxArea(0, height-16, width, 16, profile); + +// 设置样式 +timeBxArea.setMultiline(false); +timeBxArea.setDateStyle(DateStyle.YYYY_MM_DD_3); +timeBxArea.setTimeStyle(TimeStyle.HH_MM_SS_1); +timeBxArea.setWeekStyle(WeekStyle.CHINESE); +``` +- **功能**:创建日期时间显示区域 +- **样式选项**: + - DateStyle: YYYY_MM_DD_3 等多种日期格式 + - TimeStyle: HH_MM_SS_1 等多种时间格式 + - WeekStyle: CHINESE(中文)等星期格式 +- **使用位置**:LedDrawService.createBx6MProgram() + +### 4.6 页面接口 + +#### TextBxPage(文本页) +```java +// 创建文本页 +TextBxPage textPage = new TextBxPage(text, new Font("宋体", Font.PLAIN, 12)); + +// 设置颜色 +textPage.setForeground(Color.WHITE); +textPage.setBackground(Color.BLACK); +``` +- **功能**:创建文本显示页面 +- **参数**:文本内容, 字体 +- **使用位置**:LedDrawService.createBx6MProgram() + +#### ImageFileBxPage(图片页) +```java +// 创建图片页 +ImageFileBxPage imagePage = new ImageFileBxPage(imagePath); +``` +- **功能**:创建图片显示页面 +- **参数**:图片文件路径 +- **使用位置**:LedDrawService.createBx6MProgram() + +### 4.7 服务器监听接口 + +#### Bx6GServerListener +```java +public class LedServerListener implements Bx6GServerListener { + @Override + public void connected(String socketId, String netId, Bx6GScreen screen) { + // LED 屏连接成功处理 + } + + @Override + public void disconnected(String socketId, String netId, Bx6GScreen screen) { + // LED 屏断开连接处理 + } +} +``` +- **功能**:监听 LED 屏的连接和断开事件 +- **回调方法**: + - connected(): 屏幕连接时触发 + - disconnected(): 屏幕断开时触发 +- **使用位置**:LedServerListener + +### 4.8 屏幕配置接口 + +#### Bx6GScreenProfile +```java +// 从屏幕获取配置 +Bx6GScreenProfile profile = screen.getProfile(); + +// 使用配置创建区域 +TextCaptionBxArea area = new TextCaptionBxArea(x, y, width, height, profile); +``` +- **功能**:LED 屏幕配置文件 +- **使用位置**:LedDrawService.createProgramByControllerType() + +## 五、系统流程 + +### 5.1 服务器启动流程 + +``` +1. 应用启动 + ↓ +2. LedServerService.initializeServer() (@PostConstruct) + ↓ +3. 初始化 Bx6G 环境 + Bx6GEnv.initial(envTimeout) + ↓ +4. 创建 LED 服务器 + new Bx6GServer("LED_Server", PORT, new Bx6E()) + ↓ +5. 添加监听器 + server.addListener(ledServerListener) + ↓ +6. 启动服务器 + server.start() + ↓ +7. 服务器监听端口 3801,等待 LED 屏连接 +``` + +### 5.2 LED 屏连接流程 + +``` +1. LED 屏主动连接到服务器(端口 3801) + ↓ +2. Bx6GServerListener.connected() 触发 + ↓ +3. 清理旧连接(如果存在) + ledDrawService.removeConnectedScreen(netId) + ↓ +4. 更新数据库状态为在线 + ledScreen.setOnline(true) + ledScreenService.updateLedScreen(ledScreen) + ↓ +5. 添加到连接管理器 + ledDrawService.addConnectedScreen(netId, screen) + ↓ +6. 执行设备状态检查 + - screen.ping() + - screen.checkControllerStatus() + - screen.checkFirmware() + - screen.readControllerId() +``` + +### 5.3 LED 内容绘制流程 + +``` +1. 触发绘制请求 + ↓ +2. drawLedScreenContent(ledScreen) + ↓ +3. 从连接管理器获取屏幕对象 + Bx6GScreen screen = connectedScreens.get(deviceSn) + ↓ +4. 获取屏幕配置 + Bx6GScreenProfile profile = screen.getProfile() + ↓ +5. 创建节目文件 + ProgramBxFile programFile = new ProgramBxFile("P000", profile) + ↓ +6. 创建显示区域 + a) 头部文本区域 (0, 0, width, 16) + - 创建 TextCaptionBxArea + - 添加 TextBxPage(标题) + + b) 中间图片区域 (0, 17, width, height-32) + - 创建 TextCaptionBxArea + - 添加多个 ImageFileBxPage(图片列表) + + c) 底部时间区域 (0, height-16, width, 16) + - 创建 DateTimeBxArea + - 设置日期/时间/星期样式 + ↓ +7. 将区域添加到节目文件 + programFile.addArea(area) + ↓ +8. 发送节目到 LED 屏 + screen.writeProgram(programFile) +``` + +### 5.4 定时检测流程 + +#### 服务器状态检测(每30秒) +``` +@Scheduled(fixedRate = 30000) +checkServerStatus() + ↓ +检查服务器是否运行 + ↓ +如果未运行,尝试重启 + - server.start() + - 失败则重新创建服务器实例 +``` + +#### 设备在线状态检测(每30秒) +``` +@Scheduled(fixedRate = 30000) +checkConnectedDevicesStatus() + ↓ +遍历所有已连接设备 + ↓ +对每个设备执行 ping 测试 + screen.ping() + ↓ +如果 ping 失败 + - 从连接管理器移除 + - 更新数据库状态为离线 +``` + +### 5.5 LED 屏断开流程 + +``` +1. LED 屏断开连接 + ↓ +2. Bx6GServerListener.disconnected() 触发 + ↓ +3. 更新数据库状态为离线 + ledScreen.setOnline(false) + ledScreenService.updateLedScreen(ledScreen) + ↓ +4. 从连接管理器移除 + ledDrawService.removeConnectedScreen(netId) +``` + +## 六、配置说明 + +### 6.1 application.yml 配置 + +```yaml +led: + server-host: localhost + server-port: 3801 + refresh-interval: 30 # 刷新间隔(秒) + enabled: true + max-connection-retries: 3 + connection-timeout: 5000 # 连接超时(毫秒) + response-timeout: 10000 # 响应超时(毫秒) + env-timeout: 30000 # 环境初始化超时(毫秒) + save-path: /Users/haha/code/leddata/ # LED 图片保存路径 +``` + +### 6.2 屏幕布局说明 + +LED 屏内容分为三个区域: + +| 区域 | 位置 | 尺寸 | 内容 | +|------|------|------|------| +| 头部区域 | (0, 0) | width × 16 | 标题文本 | +| 中间区域 | (0, 17) | width × (height-32) | 图片列表 | +| 底部区域 | (0, height-16) | width × 16 | 日期时间 | + +## 七、数据库表结构 + +### SysLedscreen 表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| deviceSn | String | 设备序列号(Net ID) | +| title | String | LED 屏标题 | +| width | Integer | 屏幕宽度 | +| height | Integer | 屏幕高度 | +| projectId | Long | 项目 ID | +| workareaId | Long | 工区 ID | +| drawType | Integer | 绘制类型(1/2) | +| online | Boolean | 在线状态 | +| enabled | Long | 是否启用 | +| isDel | Long | 是否删除 | + +## 八、API 接口 + +### 8.1 获取服务器状态 +``` +GET /led/status?projectId={projectId} +``` +返回:服务器运行状态和已连接屏幕数量 + +### 8.2 获取所有 LED 屏信息 +``` +GET /led/screens?projectId={projectId} +``` +返回:LED 屏列表 + +### 8.3 获取特定 LED 屏信息 +``` +GET /led/screens/{netId} +``` +返回:指定 LED 屏信息 + +### 8.4 手动刷新 LED 屏 +``` +POST /led/screens/{netId}/refresh +``` +返回:刷新结果 + +### 8.5 更新 LED 屏配置 +``` +PUT /led/screens +Body: SysLedscreen +``` +返回:更新结果 + +## 九、注意事项 + +1. **端口占用**:确保端口 3801 未被其他程序占用 +2. **文件路径**:LED 图片保存路径需要有读写权限 +3. **网络连接**:LED 屏需要能够访问服务器 IP 和端口 +4. **定时任务**:确保 Spring Scheduling 已启用(@EnableScheduling) +5. **线程安全**:使用 ConcurrentHashMap 管理连接,确保线程安全 +6. **异常处理**:所有 Onbon 接口调用都应包含异常处理 +7. **资源释放**:应用停止时正确关闭服务器(@PreDestroy) + +## 十、常见问题 + +### 10.1 服务器启动失败 +- 检查端口 3801 是否被占用 +- 检查 Bx6G 环境初始化是否成功 +- 查看日志中的错误信息 + +### 10.2 LED 屏无法连接 +- 检查网络连接 +- 检查 LED 屏的 IP 配置 +- 检查防火墙设置 +- 确认 LED 屏的设备序列号正确 + +### 10.3 内容显示异常 +- 检查图片路径是否正确 +- 检查图片格式是否支持 +- 检查屏幕尺寸配置是否正确 +- 查看绘制日志 + +## 十一、技术栈 + +- **后端框架**:Spring Boot +- **LED SDK**:Onbon Bx06 0.6.5-SNAPSHOT +- **数据库**:MySQL(通过 MyBatis) +- **定时任务**:Spring Scheduling +- **日志**:SLF4J + Logback +- **工具库**:Hutool + +## 十二、版本信息 + +- SDK 版本:bx06-0.6.5-SNAPSHOT +- Java 版本:1.8 +- Spring Boot 版本:3.6.2 + +--- + +文档生成时间:2026-03-23 diff --git a/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/LedMainApplication.java b/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/LedMainApplication.java index a9f25fd7..4fb3d50a 100644 --- a/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/LedMainApplication.java +++ b/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/LedMainApplication.java @@ -183,16 +183,11 @@ public class LedMainApplication { if (!isActuallyOnline) { cancelScheduledTask(ledScreen.getDeviceSn()); } else { + // 设备上线,创建刷新任务 createRefreshTask(ledScreen); + logger.info("设备 {} 已上线,已创建刷新任务", ledScreen.getDeviceSn()); } } - - // 主动检测设备状态,如果设备已连接但ping失败,则认为设备离线 - if (ledDrawService.isConnected(ledScreen.getDeviceSn()) && !isActuallyOnline) { - // 从连接管理器移除屏幕 - ledDrawService.removeConnectedScreen(ledScreen.getDeviceSn()); - logger.info("从连接管理器移除离线设备: {}", ledScreen.getDeviceSn()); - } } } catch (Exception e) { logger.error("定期刷新LED屏状态时出错", e); diff --git a/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/config/LedProperties.java b/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/config/LedProperties.java index 60c16396..23e5c664 100644 --- a/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/config/LedProperties.java +++ b/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/config/LedProperties.java @@ -14,6 +14,7 @@ public class LedProperties { private int maxConnectionRetries = 3; private int connectionTimeout = 5000; // 连接超时时间,单位毫秒 private int responseTimeout = 10000; // 响应超时时间,单位毫秒 + private int envTimeout = 30000; // Bx6G环境初始化超时时间,单位毫秒 private String savePath = "/Users/haha/code/leddata/"; // LED图片保存路径 // Getters and Setters @@ -73,6 +74,14 @@ public class LedProperties { this.responseTimeout = responseTimeout; } + public int getEnvTimeout() { + return envTimeout; + } + + public void setEnvTimeout(int envTimeout) { + this.envTimeout = envTimeout; + } + public String getSavePath() { return savePath; } diff --git a/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/listener/LedServerListener.java b/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/listener/LedServerListener.java index 3c65e933..a4dc0d35 100644 --- a/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/listener/LedServerListener.java +++ b/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/listener/LedServerListener.java @@ -25,6 +25,12 @@ public class LedServerListener implements Bx6GServerListener { public void connected(String socketId, String netId, Bx6GScreen screen) { logger.info("LED屏连接成功: Socket ID={}, Net ID={}", socketId, netId); + // 先检查并清理可能存在的旧连接(设备重连时) + if (ledDrawService.isConnected(netId)) { + logger.info("检测到设备 {} 存在旧连接,先清理旧连接", netId); + ledDrawService.removeConnectedScreen(netId); + } + // 更新LED屏在线状态 SysLedscreen ledScreen = ledScreenService.getLedScreenByNetId(netId); if (ledScreen != null) { diff --git a/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/service/LedDrawService.java b/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/service/LedDrawService.java index 11b84a95..8e477a9d 100644 --- a/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/service/LedDrawService.java +++ b/yanzhu-modules/yanzhu-led/src/main/java/com/yanzhu/led/service/LedDrawService.java @@ -28,6 +28,9 @@ public class LedDrawService { @Autowired private ILedScreenService ledScreenService; + @Autowired + private com.yanzhu.led.config.LedProperties ledProperties; + private static final Logger logger = LoggerFactory.getLogger(LedDrawService.class); // 存储已连接的屏幕,便于后续操作 @@ -37,8 +40,8 @@ public class LedDrawService { * 初始化Bx6G环境 */ public void initializeBx6GEnv() throws Exception { - Bx6GEnv.initial(30000); // 30秒超时 - logger.info("Bx6G环境初始化成功"); + Bx6GEnv.initial(ledProperties.getEnvTimeout()); + logger.info("Bx6G环境初始化成功,超时时间: {}ms", ledProperties.getEnvTimeout()); } /** @@ -52,7 +55,6 @@ public class LedDrawService { logger.error("屏幕未连接: {}", ledScreen.getDeviceSn()); return false; } - // 根据控制器类型创建节目文件,使用屏幕的配置文件 ProgramBxFile programFile = createProgramByControllerType(ledScreen, screen); @@ -162,7 +164,23 @@ public class LedDrawService { * @return true表示已连接,false表示未连接 */ public boolean isConnected(String deviceSn) { - return connectedScreens.containsKey(deviceSn); + Bx6GScreen screen = connectedScreens.get(deviceSn); + if (screen == null) { + return false; + } + + try { + // 通过真实的 ping 验证设备连接状态 + Bx6GScreen.Result result = screen.ping(); + boolean isOnline = result != null && result.isOK(); + if (!isOnline) { + logger.warn("设备 {} ping 失败,认为已离线", deviceSn); + } + return isOnline; + } catch (Exception e) { + logger.warn("检测设备 {} 在线状态时出错: {}", deviceSn, e.getMessage()); + return false; + } } /** @@ -191,16 +209,40 @@ public class LedDrawService { // 设备离线,从连接管理器中移除 connectedScreens.remove(deviceSn); logger.info("已从连接管理器移除离线设备: {}", deviceSn); + + // 同步更新数据库状态 + updateDeviceOnlineStatus(deviceSn, false); } } catch (Exception e) { logger.warn("检测设备 {} 在线状态时出错: {}, 将从连接管理器中移除", deviceSn, e.getMessage()); // 发生异常,从连接管理器中移除设备 connectedScreens.remove(deviceSn); logger.info("已从连接管理器移除异常设备: {}", deviceSn); + + // 同步更新数据库状态 + updateDeviceOnlineStatus(deviceSn, false); } } } + /** + * 更新设备在线状态到数据库 + * @param deviceSn 设备序列号 + * @param isOnline 在线状态 + */ + private void updateDeviceOnlineStatus(String deviceSn, boolean isOnline) { + try { + SysLedscreen ledScreen = ledScreenService.getLedScreenByNetId(deviceSn); + if (ledScreen != null) { + ledScreen.setOnline(isOnline); + ledScreenService.updateLedScreen(ledScreen); + logger.info("设备 {} 数据库状态已更新为: {}", deviceSn, isOnline ? "在线" : "离线"); + } + } catch (Exception e) { + logger.error("更新设备 {} 在线状态到数据库时出错: {}", deviceSn, e.getMessage(), e); + } + } + /** * 获取所有已连接的设备SN列表 */