This commit is contained in:
zc
2026-03-27 11:36:06 +08:00
parent 402d04294c
commit 6da72d6702
9 changed files with 744 additions and 9 deletions

View File

@@ -25,5 +25,12 @@
<version>1.3.2</version>
<scope>provided</scope>
</dependency>
<!-- 串口通信依赖 -->
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>2.10.5</version>
</dependency>
</dependencies>
</project>

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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;

View File

@@ -28,6 +28,14 @@ public interface MaterialInfoService extends BaseService<MaterialInfoResp, Mater
*/
MaterialInfoResp getMaterialInfoByCode(String code);
/**
* 根据物料ID查询亮度
*
* @param materialId 物料ID
* @return 亮度
*/
Integer getBrightness(Long materialId);
/**
* 下载导入模板
*

View File

@@ -40,7 +40,6 @@ import top.continew.starter.web.util.FileUploadUtils;
import top.wms.admin.common.constant.CacheConstants;
import top.wms.admin.common.context.UserContextHolder;
import top.wms.admin.common.enums.LightLevelEnum;
import top.wms.admin.common.enums.LightLevelEnumConverter;
import top.wms.admin.common.util.SecureUtils;
import top.wms.admin.material.mapper.MaterialInfoMapper;
import top.wms.admin.material.model.entity.MaterialInfoDO;
@@ -128,6 +127,13 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
return materialInfoResp;
}
@Override
public Integer getBrightness(Long materialId) {
MaterialInfoDO materialInfoDO = baseMapper.selectById(materialId);
CheckUtils.throwIf(materialInfoDO == null, "物料信息不存在");
return materialInfoDO.getLightLevel();
}
@Override
public void downloadImportTemplate(HttpServletResponse response) throws IOException {
try {
@@ -229,7 +235,7 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
importMaterialList.forEach(row -> {
if (row.getLightLevelName() != null) {
Integer valueByDescription = LightLevelEnum.getValueByDescription(row.getLightLevelName());
if(null != valueByDescription){
if (null != valueByDescription) {
lightLevelMap.put(row.getLightLevelName(), valueByDescription);
}
}