From 6da72d6702c9f75381aa4bd905260f6bfcb5ec62 Mon Sep 17 00:00:00 2001 From: zc Date: Fri, 27 Mar 2026 11:36:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/common/enums/CommandTypeEnum.java | 62 ++++ wms-module-system/pom.xml | 7 + .../top/wms/admin/light/LightService.java | 268 ++++++++++++++++++ .../wms/admin/light/SerialPortHandler.java | 241 ++++++++++++++++ .../material/model/entity/MaterialInfoDO.java | 1 - .../material/service/MaterialInfoService.java | 8 + .../service/impl/MaterialInfoServiceImpl.java | 10 +- wms-webapi/pom.xml | 6 - .../controller/light/LightController.java | 150 ++++++++++ 9 files changed, 744 insertions(+), 9 deletions(-) create mode 100644 wms-common/src/main/java/top/wms/admin/common/enums/CommandTypeEnum.java create mode 100644 wms-module-system/src/main/java/top/wms/admin/light/LightService.java create mode 100644 wms-module-system/src/main/java/top/wms/admin/light/SerialPortHandler.java create mode 100644 wms-webapi/src/main/java/top/wms/admin/controller/light/LightController.java diff --git a/wms-common/src/main/java/top/wms/admin/common/enums/CommandTypeEnum.java b/wms-common/src/main/java/top/wms/admin/common/enums/CommandTypeEnum.java new file mode 100644 index 0000000..cdcf5d8 --- /dev/null +++ b/wms-common/src/main/java/top/wms/admin/common/enums/CommandTypeEnum.java @@ -0,0 +1,62 @@ +package top.wms.admin.common.enums; + + +/** + * DPA6024V-2T-1.0 数字控制器命令类型枚举 + * 定义控制器支持的指令字 + */ +public enum CommandTypeEnum { + + /** + * 打开对应通道 (指令字: 1) + */ + ON('1'), + + /** + * 关闭对应通道 (指令字: 2) + */ + OFF('2'), + + /** + * 设置对应通道亮度参数 (指令字: 3) + */ + SET_BRIGHTNESS('3'), + + /** + * 读出对应通道亮度参数 (指令字: 4) + */ + READ('4'); + + private final char commandCode; + + CommandTypeEnum(char commandCode) { + this.commandCode = commandCode; + } + + /** + * 获取指令字字符 + * @return 指令字 (如 '1', '2', '3', '4') + */ + public char getCommandCode() { + return commandCode; + } + + /** + * 根据指令字获取对应的命令类型 + * @param commandCode 指令字字符 + * @return 对应的CommandType,找不到返回null + */ + public static CommandTypeEnum fromCommandCode(char commandCode) { + for (CommandTypeEnum type : values()) { + if (type.commandCode == commandCode) { + return type; + } + } + return null; + } + + @Override + public String toString() { + return String.valueOf(commandCode); + } +} \ No newline at end of file diff --git a/wms-module-system/pom.xml b/wms-module-system/pom.xml index 0f5c9a6..2e58377 100644 --- a/wms-module-system/pom.xml +++ b/wms-module-system/pom.xml @@ -25,5 +25,12 @@ 1.3.2 provided + + + + com.fazecast + jSerialComm + 2.10.5 + diff --git a/wms-module-system/src/main/java/top/wms/admin/light/LightService.java b/wms-module-system/src/main/java/top/wms/admin/light/LightService.java new file mode 100644 index 0000000..184ea18 --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/light/LightService.java @@ -0,0 +1,268 @@ +package top.wms.admin.light; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import top.wms.admin.common.enums.CommandTypeEnum; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; + +/** + * DPA6024V-2T-1.0 数字控制器 Java 控制类 + * 支持通过RS232串口控制LED光源的亮度(0-255级) + */ +@Slf4j +@Component +public class LightService { + + private SerialPortHandler serialHandler; + + /** + * 连接控制器 + * 前端页面进入"称重管理"页面时调用 + */ + public boolean connect() { + try { + // 使用COM1串口 + String portName = "COM1"; + // 默认波特率9600 + int baudRate = 9600; + serialHandler = new SerialPortHandler(portName, baudRate); + boolean connected = serialHandler.open(); + if (connected) { + log.info("数字控制器连接成功"); + } else { + log.error("数字控制器连接失败"); + } + return connected; + } catch (Exception e) { + log.error("连接失败: {}", e.getMessage()); + return false; + } + } + + /** + * 断开连接 + * 前端页面离开"称重管理"页面时调用 + */ + public void disconnect() { + if (serialHandler != null) { + log.info("正在断开数字控制器连接"); + serialHandler.close(); + serialHandler = null; + log.info("数字控制器连接已断开"); + } + } + + /** + * 检查是否已连接 + * @return 已连接返回true,否则返回false + */ + public boolean isConnected() { + return serialHandler != null && serialHandler.isOpen(); + } + + /** + * 打开指定通道 + * @param channel 通道号 (1-2) + */ + public boolean turnOn(int channel) { + return sendCommand(CommandTypeEnum.ON, channel, 0); + } + + /** + * 关闭指定通道 + * @param channel 通道号 (1-2) + */ + public boolean turnOff(int channel) { + return sendCommand(CommandTypeEnum.OFF, channel, 0); + } + + /** + * 设置通道亮度 + * @param channel 通道号 (1-2) + * @param brightness 亮度等级 (0-255) + */ + public boolean setBrightness(int channel, int brightness) { + if (brightness < 0 || brightness > 255) { + log.error("亮度等级必须在0-255之间"); + return false; + } + log.info("设置通道 {} 亮度为: {}", channel, brightness); + return sendCommand(CommandTypeEnum.SET_BRIGHTNESS, channel, brightness); + } + + /** + * 读取通道亮度 + * @param channel 通道号 (1-2) + * @return 亮度等级 (0-255),失败返回-1 + */ + public int getBrightness(int channel) { + int brightness = sendReadCommand(channel); + if (brightness != -1) { + log.info("通道 {} 当前亮度: {}", channel, brightness); + } + return brightness; + } + + /** + * 发送命令到控制器 + */ + private boolean sendCommand(CommandTypeEnum type, int channel, int brightness) { + if (serialHandler == null || !serialHandler.isOpen()) { + log.error("控制器未连接"); + return false; + } + + byte[] command = buildCommand(type, channel, brightness); + + // 发送命令 + serialHandler.sendData(command); + + // 等待响应(对于ON/OFF/SET命令,返回'$'表示成功) + if (type != CommandTypeEnum.READ) { + String response = serialHandler.receiveResponse(100); + boolean success = response != null && response.contains("$"); + if (success) { + log.debug("命令执行成功"); + } else { + log.error("命令执行失败,响应: {}", response); + } + return success; + } + + return true; + } + + /** + * 发送读取命令并获取返回值 + */ + private int sendReadCommand(int channel) { + if (serialHandler == null || !serialHandler.isOpen()) { + log.error("控制器未连接"); + return -1; + } + + byte[] command = buildCommand(CommandTypeEnum.READ, channel, 0); + if (command == null) { + return -1; + } + + // 发送命令 + serialHandler.sendData(command); + + // 接收响应(返回格式同发送格式,包含亮度数据) + String response = serialHandler.receiveResponse(200); + if (response == null || !response.startsWith("$")) { + log.error("读取亮度失败,响应: {}", response); + return -1; + } + + // 解析返回的亮度值 + int brightness = parseBrightnessFromResponse(response); + if (brightness == -1) { + log.error("解析亮度值失败,响应: {}", response); + } + return brightness; + } + + /** + * 构建命令字节数组 + * 格式: 特征字(1) + 指令字(1) + 通道字(1) + 数据(3) + 异或校验字(2) + * 所有字节采用ASCII码 + * 数据部分:3个ASCII字符,表示亮度的十六进制值(高位在前) + */ + private byte[] buildCommand(CommandTypeEnum type, int channel, int brightness) { + // 特征字: $ (ASCII 36) + char featureChar = '$'; + + // 指令字 + char cmdChar = type.getCommandCode(); + + // 通道字 (1-2) + char channelChar = (char) (channel + '0'); + + // 数据: 3字节,亮度的十六进制表示,高位在前 + // 亮度值范围0-255,需要转换为3个十六进制ASCII字符 + String dataStr; + if (type == CommandTypeEnum.SET_BRIGHTNESS) { + // 将亮度值转换为十六进制字符串,并确保是3位 + dataStr = String.format("%03X", brightness); + } else { + // ON/OFF/READ命令的数据字段固定为 "000" + dataStr = "000"; + } + + // 构建命令字符串用于计算校验和 + String cmdStr = featureChar + String.valueOf(cmdChar) + channelChar + dataStr; + byte[] cmdBytes = cmdStr.getBytes(StandardCharsets.US_ASCII); + + // 计算异或校验和 + byte xorSum = 0; + for (int i = 0; i < cmdBytes.length; i++) { + xorSum ^= cmdBytes[i]; + } + + // 将校验和转换为2位十六进制ASCII码 + String xorStr = String.format("%02X", xorSum & 0xFF).toUpperCase(); + + // 最终命令字符串 + String fullCommand = cmdStr + xorStr; + + log.debug("构建命令: {} (亮度: {} -> 十六进制: {})", fullCommand, brightness, dataStr); + return fullCommand.getBytes(StandardCharsets.US_ASCII); + + } + + /** + * 从响应字符串解析亮度值 + * 响应格式: $ + 指令字 + 通道字 + 数据(3) + 校验字(2) + * 例如: "$43038E" 表示通道3亮度56 + */ + private int parseBrightnessFromResponse(String response) { + if (response == null || response.length() < 8) { + return -1; + } + + try { + // 提取数据部分(第4-6个字符,索引3-5) + String dataStr = response.substring(3, 6); + return Integer.parseInt(dataStr); + } catch (NumberFormatException e) { + log.error("解析亮度值失败: {}", e.getMessage()); + return -1; + } + } + + /** + * 便捷方法:设置所有通道亮度 + * @param brightness 亮度等级 (0-255) + */ + public void setAllBrightness(int brightness) { + log.info("设置所有通道亮度为: {}", brightness); + for (int channel = 1; channel <= 2; channel++) { + setBrightness(channel, brightness); + } + } + + /** + * 便捷方法:关闭所有通道 + */ + public void turnAllOff() { + log.info("关闭所有通道"); + for (int channel = 1; channel <= 2; channel++) { + turnOff(channel); + } + } + + /** + * 便捷方法:打开所有通道 + */ + public void turnAllOn() { + log.info("打开所有通道"); + for (int channel = 1; channel <= 2; channel++) { + turnOn(channel); + } + } + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/light/SerialPortHandler.java b/wms-module-system/src/main/java/top/wms/admin/light/SerialPortHandler.java new file mode 100644 index 0000000..aa0a29e --- /dev/null +++ b/wms-module-system/src/main/java/top/wms/admin/light/SerialPortHandler.java @@ -0,0 +1,241 @@ +package top.wms.admin.light; + + +import com.fazecast.jSerialComm.SerialPort; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +/** + * 串口通信处理器 + * 负责与DPA6024V-2T-1.0数字控制器进行串口通信 + */ +@Slf4j +public class SerialPortHandler { + + private InputStream inputStream; + private OutputStream outputStream; + private SerialPort serialPort; + + /** + * 串口名称 + */ + @Getter + private final String portName; + + /** + * 波特率 + */ + @Getter + private final int baudRate; + + /** + * 构造函数 + * @param portName 串口名称,如 "COM3" (Windows) 或 "/dev/ttyUSB0" (Linux) + * @param baudRate 波特率 + */ + public SerialPortHandler(String portName, int baudRate) { + this.portName = portName; + this.baudRate = baudRate; + } + + /** + * 打开串口连接 + * @return 打开成功返回true,失败返回false + */ + public boolean open() { + try { + // 获取所有可用的串口 + SerialPort[] ports = SerialPort.getCommPorts(); + + // 查找指定的串口 + for (SerialPort port : ports) { + if (port.getSystemPortName().equals(portName)) { + serialPort = port; + break; + } + } + + if (serialPort == null) { + log.error("未找到串口: {}", portName); + return false; + } + + // 配置串口参数 + serialPort.setBaudRate(baudRate); + serialPort.setNumDataBits(8); + serialPort.setNumStopBits(1); + serialPort.setParity(SerialPort.NO_PARITY); + serialPort.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED); + + // 打开串口 + boolean opened = serialPort.openPort(); + if (opened) { + // 设置读取超时时间(毫秒) + serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 1000, 0); + inputStream = serialPort.getInputStream(); + outputStream = serialPort.getOutputStream(); + log.info("串口 {} 打开成功,波特率: {}", portName, baudRate); + } + return opened; + + } catch (Exception e) { + log.error("打开串口失败: {}", e.getMessage()); + return false; + } + } + + /** + * 检查串口是否已打开 + * @return 已打开返回true,否则返回false + */ + public boolean isOpen() { + return serialPort != null && serialPort.isOpen(); + } + + /** + * 发送数据 + * @param data 要发送的字节数组 + */ + public void sendData(byte[] data) { + try { + if (outputStream == null) { + log.error("输出流未初始化"); + return; + } + outputStream.write(data); + outputStream.flush(); + log.debug("发送: {}", new String(data)); + } catch (IOException e) { + log.error("发送数据失败: {}", e.getMessage()); + } + } + + /** + * 发送字符串数据 + * @param data 要发送的字符串 + */ + public void sendData(String data) { + try { + sendData(data.getBytes(StandardCharsets.US_ASCII)); + } catch (Exception e) { + log.error("发送字符串数据失败: {}", e.getMessage()); + } + } + + /** + * 接收响应数据 + * @param timeoutMs 超时时间(毫秒) + * @return 接收到的字符串,超时或无数据返回null + */ + public String receiveResponse(int timeoutMs) { + try { + // 等待数据到达 + long startTime = System.currentTimeMillis(); + while (inputStream.available() == 0 && + (System.currentTimeMillis() - startTime) < timeoutMs) { + Thread.sleep(10); + } + + // 读取数据 + byte[] buffer = new byte[1024]; + int available = inputStream.available(); + if (available > 0) { + int len = inputStream.read(buffer, 0, Math.min(available, buffer.length)); + String response = new String(buffer, 0, len); + log.debug("接收: {}", response); + return response; + } + } catch (Exception e) { + log.error("接收响应失败: ", e); + } + return null; + } + + /** + * 接收指定长度的响应数据 + * @param length 期望接收的字节数 + * @param timeoutMs 超时时间(毫秒) + * @return 接收到的字符串,超时或无数据返回null + */ + public String receiveResponse(int length, int timeoutMs) { + try { + byte[] buffer = new byte[length]; + int bytesRead = 0; + long startTime = System.currentTimeMillis(); + + while (bytesRead < length && + (System.currentTimeMillis() - startTime) < timeoutMs) { + if (inputStream.available() > 0) { + int read = inputStream.read(buffer, bytesRead, length - bytesRead); + if (read > 0) { + bytesRead += read; + } + } + Thread.sleep(10); + } + + if (bytesRead > 0) { + String response = new String(buffer, 0, bytesRead); + log.debug("接收: {}", response); + return response; + } + } catch (Exception e) { + log.error("接收响应失败: {}", e.getMessage()); + } + return null; + } + + /** + * 清空输入缓冲区 + */ + public void clearInputBuffer() { + try { + if (inputStream != null && inputStream.available() > 0) { + byte[] buffer = new byte[inputStream.available()]; + inputStream.read(buffer); + log.debug("清空输入缓冲区: {}", new String(buffer)); + } + } catch (IOException e) { + log.error("清空输入缓冲区失败: {}", e.getMessage()); + } + } + + /** + * 清空输出缓冲区 + */ + public void clearOutputBuffer() { + try { + if (outputStream != null) { + outputStream.flush(); + } + } catch (IOException e) { + log.error("清空输出缓冲区失败: {}", e.getMessage()); + } + } + + /** + * 关闭串口连接 + */ + public void close() { + try { + if (inputStream != null) { + inputStream.close(); + } + if (outputStream != null) { + outputStream.close(); + } + if (serialPort != null && serialPort.isOpen()) { + serialPort.closePort(); + log.info("串口 {} 已关闭", portName); + } + } catch (IOException e) { + log.error("关闭串口失败: {}", e.getMessage()); + } + } + +} \ No newline at end of file diff --git a/wms-module-system/src/main/java/top/wms/admin/material/model/entity/MaterialInfoDO.java b/wms-module-system/src/main/java/top/wms/admin/material/model/entity/MaterialInfoDO.java index 0c03c84..677eb3a 100644 --- a/wms-module-system/src/main/java/top/wms/admin/material/model/entity/MaterialInfoDO.java +++ b/wms-module-system/src/main/java/top/wms/admin/material/model/entity/MaterialInfoDO.java @@ -4,7 +4,6 @@ import lombok.Data; import com.baomidou.mybatisplus.annotation.TableName; -import top.wms.admin.common.enums.LightLevelEnum; import top.wms.admin.common.model.entity.BaseDO; import java.io.Serial; diff --git a/wms-module-system/src/main/java/top/wms/admin/material/service/MaterialInfoService.java b/wms-module-system/src/main/java/top/wms/admin/material/service/MaterialInfoService.java index c037741..ad1d075 100644 --- a/wms-module-system/src/main/java/top/wms/admin/material/service/MaterialInfoService.java +++ b/wms-module-system/src/main/java/top/wms/admin/material/service/MaterialInfoService.java @@ -28,6 +28,14 @@ public interface MaterialInfoService extends BaseService { if (row.getLightLevelName() != null) { Integer valueByDescription = LightLevelEnum.getValueByDescription(row.getLightLevelName()); - if(null != valueByDescription){ + if (null != valueByDescription) { lightLevelMap.put(row.getLightLevelName(), valueByDescription); } } diff --git a/wms-webapi/pom.xml b/wms-webapi/pom.xml index e18fe7e..08c71ba 100644 --- a/wms-webapi/pom.xml +++ b/wms-webapi/pom.xml @@ -94,12 +94,6 @@ 1.12.780 - - com.fazecast - jSerialComm - 2.9.2 - - jakarta.websocket diff --git a/wms-webapi/src/main/java/top/wms/admin/controller/light/LightController.java b/wms-webapi/src/main/java/top/wms/admin/controller/light/LightController.java new file mode 100644 index 0000000..7bbb01a --- /dev/null +++ b/wms-webapi/src/main/java/top/wms/admin/controller/light/LightController.java @@ -0,0 +1,150 @@ +package top.wms.admin.controller.light; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson2.JSONObject; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import top.continew.starter.log.annotation.Log; +import top.continew.starter.web.model.R; +import top.wms.admin.light.LightService; +import top.wms.admin.material.service.MaterialInfoService; + +/** + * 灯光控制器 API + * 提供前端页面调用的接口 + */ +@Slf4j +@RestController +@RequestMapping("/api/light") +@Log(ignore = true) +@RequiredArgsConstructor +@Tag(name = "灯光控制器", description = "灯光控制器相关接口") +public class LightController { + + private final LightService lightService; + + private final MaterialInfoService materialInfoService; + + /** + * 连接控制器 + * 前端页面进入"称重管理"页面时调用 + */ + @PostMapping("/connect") + @Operation(summary = "连接控制器", description = "前端页面进入'称重管理'页面时调用") + public R connect() { + boolean connected = lightService.connect(); + if (connected) { + return R.ok(); + } else { + return R.fail("500","灯光连接失败"); + } + } + + /** + * 断开连接 + * 前端页面离开"称重管理"页面时调用 + */ + @PostMapping("/disconnect") + @Operation(summary = "断开连接", description = "前端页面离开'称重管理'页面时调用") + public R disconnect() { + lightService.disconnect(); + return R.ok(); + } + + /** + * 检查连接状态 + */ + @GetMapping("/status") + @Operation(summary = "检查连接状态", description = "检查控制器是否已连接") + public R status() { + boolean connected = lightService.isConnected(); + return R.ok(connected); + } + + /** + * 设置通道亮度 + * + */ + @PostMapping("/brightness") + @Operation(summary = "设置通道亮度", description = "设置指定通道的亮度") + public R setBrightness(@RequestBody JSONObject js) { + Long materialId = js.getLong("materialId"); + Integer brightness = materialInfoService.getBrightness(materialId); + if (brightness == null) { + return R.ok(); + } + boolean success = lightService.setBrightness(1, brightness); + if (success) { + return R.ok(); + } else { + return R.fail("500","设置灯光亮度失败,请检查连接状态"); + } + } + + /** + * 读取通道亮度 + * @param channel 通道号 (1-2) + */ + @GetMapping("/brightness") + @Operation(summary = "读取通道亮度", description = "读取指定通道的当前亮度") + public R getBrightness(@RequestParam int channel) { + int brightness = lightService.getBrightness(channel); + return R.ok(brightness); + } + + /** + * 打开指定通道 + * @param channel 通道号 (1-2) + */ + @PostMapping("/turn-on") + @Operation(summary = "打开通道", description = "打开指定通道") + public R turnOn(@RequestParam int channel) { + boolean success = lightService.turnOn(channel); + return R.ok(success); + } + + /** + * 关闭指定通道 + * @param channel 通道号 (1-2) + */ + @PostMapping("/turn-off") + @Operation(summary = "关闭通道", description = "关闭指定通道") + public R turnOff(@RequestParam int channel) { + boolean success = lightService.turnOff(channel); + return R.ok(success); + } + + /** + * 设置所有通道亮度 + * @param brightness 亮度等级 (0-255) + */ + @PostMapping("/all-brightness") + @Operation(summary = "设置所有通道亮度", description = "设置所有通道的亮度") + public R setAllBrightness(@RequestParam int brightness) { + lightService.setAllBrightness(brightness); + return R.ok(); + } + + /** + * 打开所有通道 + */ + @PostMapping("/all-turn-on") + @Operation(summary = "打开所有通道", description = "打开所有通道") + public R turnAllOn() { + lightService.turnAllOn(); + return R.ok(); + } + + /** + * 关闭所有通道 + */ + @PostMapping("/all-turn-off") + @Operation(summary = "关闭所有通道", description = "关闭所有通道") + public R turnAllOff() { + lightService.turnAllOff(); + return R.ok(); + } +} \ No newline at end of file