Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd1ba55b26 | |||
|
|
687be5840e | ||
|
|
d9f808ecc1 | ||
| c1d84aaf81 | |||
|
|
dec15eb913 | ||
| 18e014d9cb | |||
| 2cb03b146a |
@@ -119,5 +119,10 @@
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.1.100.Final</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -155,5 +155,12 @@
|
||||
<artifactId>spring-test</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.1.100.Final</version> <!-- 使用较新稳定版本 -->
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -21,5 +21,11 @@
|
||||
<groupId>top.wms</groupId>
|
||||
<artifactId>wms-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>1.3.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -18,5 +18,12 @@
|
||||
<groupId>top.wms</groupId>
|
||||
<artifactId>wms-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>1.3.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -56,7 +56,6 @@ public interface MaterialInfoService extends BaseService<MaterialInfoResp, Mater
|
||||
* */
|
||||
void uploadMaterialPhotos(MultipartFile file);
|
||||
|
||||
|
||||
/*
|
||||
* 称重时照片抓取
|
||||
* */
|
||||
|
||||
@@ -413,12 +413,11 @@ public class MaterialInfoServiceImpl extends BaseServiceImpl<MaterialInfoMapper,
|
||||
.forEach(code -> log.warn("物料编码 [{}] 不存在,照片更新失败", code));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String catchPhoto(MultipartFile file){
|
||||
public String catchPhoto(MultipartFile file) {
|
||||
String photoStoragePath = "catch" + DateUtil.today() + "/";
|
||||
FileInfo fileInfo = fileService.upload(file, photoStoragePath, null, true, true);
|
||||
CheckUtils.throwIfNull(fileInfo,"照片上传失败");
|
||||
CheckUtils.throwIfNull(fileInfo, "照片上传失败");
|
||||
return fileInfo.getUrl();
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileInfo upload(MultipartFile file, String path, String storageCode, Boolean createMin, Boolean store) {
|
||||
public FileInfo upload(MultipartFile file, String path, String storageCode, Boolean createMin, Boolean store) {
|
||||
StorageDO storage;
|
||||
if (StrUtil.isBlank(storageCode)) {
|
||||
storage = storageService.getDefaultStorage();
|
||||
|
||||
@@ -44,13 +44,11 @@ public class WorkOrderReq implements Serializable {
|
||||
@NotEmpty(message = "称重列表不能为空")
|
||||
private List<WorkOrderInfoReq> workOrderInfos;
|
||||
|
||||
|
||||
/**
|
||||
* 手动填写的物料数量
|
||||
*/
|
||||
private String inputQuantity;
|
||||
|
||||
|
||||
/**
|
||||
* 电子秤重量
|
||||
*/
|
||||
|
||||
@@ -24,7 +24,7 @@ public interface WorkOrderService extends BaseService<WorkOrderResp, WorkOrderRe
|
||||
*/
|
||||
WorkOrderResp getDetail(Long id);
|
||||
|
||||
/**
|
||||
/**
|
||||
* 创建任务工单
|
||||
*
|
||||
* @param req 创建任务工单参数
|
||||
@@ -41,7 +41,8 @@ public interface WorkOrderService extends BaseService<WorkOrderResp, WorkOrderRe
|
||||
List<WorkOrderInfoResp> getWorkOrderInfos(Long id);
|
||||
|
||||
/**
|
||||
* 校验物料数量和重量是否匹配
|
||||
* 校验物料数量和重量是否匹配
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,6 @@ import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
@@ -45,12 +44,11 @@ import java.util.List;
|
||||
@RequiredArgsConstructor
|
||||
public class WorkOrderServiceImpl extends BaseServiceImpl<WorkOrderMapper, WorkOrderDO, WorkOrderResp, WorkOrderResp, WorkOrderQuery, WorkOrderReq> implements WorkOrderService {
|
||||
|
||||
private final WorkOrderInfoMapper workOrderInfoMapper;
|
||||
private final WorkOrderInfoMapper workOrderInfoMapper;
|
||||
|
||||
private final MaterialInfoMapper materialInfoMapper;
|
||||
|
||||
private final ConfigService configService;
|
||||
private final MaterialInfoMapper materialInfoMapper;
|
||||
|
||||
private final ConfigService configService;
|
||||
|
||||
@Override
|
||||
public PageResp<WorkOrderResp> page(WorkOrderQuery query, PageQuery pageQuery) {
|
||||
@@ -75,7 +73,8 @@ private final ConfigService configService;
|
||||
workOrderResp.setEncoding(materialInfoDO.getEncoding());
|
||||
}
|
||||
|
||||
List<WorkOrderInfoDO> workOrderInfos = workOrderInfoMapper.selectList(new QueryWrapper<WorkOrderInfoDO>().eq("work_order_id", id));
|
||||
List<WorkOrderInfoDO> workOrderInfos = workOrderInfoMapper.selectList(new QueryWrapper<WorkOrderInfoDO>()
|
||||
.eq("work_order_id", id));
|
||||
if (CollUtil.isNotEmpty(workOrderInfos)) {
|
||||
BigDecimal bigDecimal = new BigDecimal("0");
|
||||
for (WorkOrderInfoDO workOrderInfoDO : workOrderInfos) {
|
||||
@@ -92,13 +91,11 @@ private final ConfigService configService;
|
||||
return baseMapper.getDetail(id);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterDelete(List<Long> ids) {
|
||||
workOrderInfoMapper.delete(new QueryWrapper<WorkOrderInfoDO>().in("work_order_id", ids));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public WorkOrderResp addWorKerOrder(WorkOrderReq req) {
|
||||
if (CollUtil.isEmpty(req.getWorkOrderInfos())) {
|
||||
@@ -117,8 +114,8 @@ private final ConfigService configService;
|
||||
String timestamp = DateUtil.format(new Date(), "yyyyMMddHHmmss");
|
||||
String randomNum = String.format("%06d", (int)(Math.random() * 1000000));
|
||||
workOrder.setOrderNo(timestamp + randomNum);
|
||||
String title = DateUtil.format(new Date(), DatePattern.CHINESE_DATE_PATTERN) + "-"
|
||||
+ UserContextHolder.getUsername() + "-" + req.getMaterialName();
|
||||
String title = DateUtil.format(new Date(), DatePattern.CHINESE_DATE_PATTERN) + "-" + UserContextHolder
|
||||
.getUsername() + "-" + req.getMaterialName();
|
||||
workOrder.setTitle(title);
|
||||
workOrder.setMaterialId(req.getMaterialId());
|
||||
workOrder.setTotalWeight(totalWeight);
|
||||
@@ -144,19 +141,19 @@ private final ConfigService configService;
|
||||
|
||||
//计算标准重量
|
||||
MaterialInfoDO materialInfoDO = materialInfoMapper.selectById(req.getMaterialId());
|
||||
BigDecimal standardWeight = materialInfoDO.getUnitWeight().subtract(new BigDecimal(req.getInputQuantity()));
|
||||
BigDecimal standardWeight = materialInfoDO.getUnitWeight().subtract(new BigDecimal(req.getInputQuantity()));
|
||||
BigDecimal electronicWeight = new BigDecimal(req.getAhDeviceWeight());
|
||||
|
||||
|
||||
// 检查 electronicWeight 是否大于 standardWeight
|
||||
if (electronicWeight.compareTo(standardWeight) <= 0) {
|
||||
log.error("电子秤重量必须大于标准重量");
|
||||
return 500; // 电子秤重量必须大于标准重量
|
||||
}
|
||||
|
||||
|
||||
// 计算比值:(electronicWeight - standardWeight) / standardWeight
|
||||
BigDecimal weightDifference = electronicWeight.subtract(standardWeight);
|
||||
BigDecimal ratio = weightDifference.divide(standardWeight, 4, BigDecimal.ROUND_HALF_UP);
|
||||
|
||||
|
||||
// 检查比值是否超过 6%
|
||||
if (ratio.compareTo(weightFloat) > 0) {
|
||||
log.error("比值超过 6%,当前比值: {}", ratio);
|
||||
|
||||
@@ -63,6 +63,13 @@
|
||||
<artifactId>jakarta.websocket-api</artifactId>
|
||||
<version>2.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.hikvision</groupId>
|
||||
<artifactId>MvCameraControlWrapper</artifactId>
|
||||
<version>1.0</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/Library/MvCameraControlWrapper.jar</systemPath>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<finalName>${project.parent.name}</finalName>
|
||||
|
||||
@@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.x.file.storage.spring.EnableFileStorage;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
|
||||
@@ -29,6 +30,7 @@ import top.continew.starter.web.model.R;
|
||||
@RestController
|
||||
@SpringBootApplication
|
||||
@RequiredArgsConstructor
|
||||
@EnableScheduling
|
||||
public class WmsAdminApplication {
|
||||
|
||||
private final ProjectProperties projectProperties;
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
package top.wms.admin.config.webSocket;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
import top.wms.admin.controller.hkMVS.CameraWebSocketHandler;
|
||||
import top.wms.admin.controller.weighManage.ah.ScaleWebSocketHandler;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Autowired
|
||||
private CameraWebSocketHandler cameraWebSocketHandler;
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
// 注册WebSocket端点,允许所有跨域请求
|
||||
registry.addHandler(new ScaleWebSocketHandler(), "/ws/scale").setAllowedOrigins("*");
|
||||
// 注册海康相机1 WebSocket端点
|
||||
registry.addHandler(cameraWebSocketHandler, "/ws/camera").setAllowedOrigins("*");
|
||||
}
|
||||
}
|
||||
@@ -1,487 +0,0 @@
|
||||
package top.wms.admin.controller.hkMVS;
|
||||
|
||||
import MvCameraControlWrapper.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.socket.BinaryMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static MvCameraControlWrapper.MvCameraControlDefines.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class CameraService {
|
||||
|
||||
// 相机句柄
|
||||
private Handle hCamera = null;
|
||||
// 设备信息列表
|
||||
private ArrayList<MV_CC_DEVICE_INFO> stDeviceList = null;
|
||||
// WebSocket会话映射,用于存储活跃的WebSocket连接
|
||||
private final Map<String, WebSocketSession> webSocketSessions = new ConcurrentHashMap<>();
|
||||
// 相机是否正在运行
|
||||
private boolean isRunning = false;
|
||||
// 帧率控制相关
|
||||
private long lastFrameTime = 0;
|
||||
private final int targetFps = 15; // 目标帧率
|
||||
private final long frameInterval = 1000 / targetFps; // 帧间隔(毫秒)
|
||||
// 最后一次会话移除时间,用于判断是否需要保留相机资源
|
||||
private long lastSessionRemoveTime = 0;
|
||||
// 相机资源保留时间(毫秒)
|
||||
private final long cameraResourceRetentionTime = 5000; // 5秒
|
||||
|
||||
/**
|
||||
* 初始化相机
|
||||
* @return 初始化结果
|
||||
*/
|
||||
public synchronized boolean initializeCamera() {
|
||||
if (hCamera != null) {
|
||||
log.info("Camera already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
int nRet = MV_OK;
|
||||
|
||||
try {
|
||||
// 初始化SDK
|
||||
log.info("Initializing SDK...");
|
||||
nRet = MvCameraControl.MV_CC_Initialize();
|
||||
if (MV_OK != nRet) {
|
||||
log.error("Initialize SDK fail! nRet {}", Integer.toHexString(nRet));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 枚举设备
|
||||
log.info("Enumerating devices...");
|
||||
try {
|
||||
stDeviceList = MvCameraControl.MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_GIGE_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE);
|
||||
} catch (CameraControlException e) {
|
||||
log.error("Enumerate devices failed!", e);
|
||||
MvCameraControl.MV_CC_Finalize();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stDeviceList == null || stDeviceList.size() <= 0) {
|
||||
log.error("No devices found!");
|
||||
MvCameraControl.MV_CC_Finalize();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 打印设备信息
|
||||
log.info("Found {} device(s):", stDeviceList.size());
|
||||
int i = 0;
|
||||
for (MV_CC_DEVICE_INFO stDeviceInfo : stDeviceList) {
|
||||
if (null == stDeviceInfo) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 默认选择第一个设备
|
||||
log.info("Selecting first device...");
|
||||
try {
|
||||
hCamera = MvCameraControl.MV_CC_CreateHandle(stDeviceList.get(0));
|
||||
} catch (CameraControlException e) {
|
||||
log.error("Create handle failed!", e);
|
||||
MvCameraControl.MV_CC_Finalize();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 打开设备
|
||||
log.info("Opening device...");
|
||||
nRet = MvCameraControl.MV_CC_OpenDevice(hCamera);
|
||||
if (MV_OK != nRet) {
|
||||
log.error("Connect to camera failed, errcode: {}", Integer.toHexString(nRet));
|
||||
MvCameraControl.MV_CC_DestroyHandle(hCamera);
|
||||
hCamera = null;
|
||||
MvCameraControl.MV_CC_Finalize();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 关闭触发模式
|
||||
log.info("Turning off trigger mode...");
|
||||
nRet = MvCameraControl.MV_CC_SetEnumValueByString(hCamera, "TriggerMode", "Off");
|
||||
if (MV_OK != nRet) {
|
||||
log.error("SetTriggerMode failed, errcode: {}", Integer.toHexString(nRet));
|
||||
closeCamera();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置Bayer转换质量,用于彩色相机
|
||||
log.info("Setting Bayer convert quality...");
|
||||
nRet = MvCameraControl.MV_CC_SetBayerCvtQuality(hCamera, 1);
|
||||
if (MV_OK != nRet) {
|
||||
log.error("Set Bayer convert quality fail! nRet {}", Integer.toHexString(nRet));
|
||||
closeCamera();
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("Camera initialized successfully");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Initialize camera failed", e);
|
||||
closeCamera();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始视频流
|
||||
* @return 开始结果
|
||||
*/
|
||||
public synchronized boolean startStream() {
|
||||
if (hCamera == null) {
|
||||
log.error("Camera not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isRunning) {
|
||||
log.info("Stream already running");
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// 注册图像回调
|
||||
log.info("Registering image callback...");
|
||||
int nRet = MvCameraControl.MV_CC_RegisterImageCallBack(hCamera, new CameraImageCallBack() {
|
||||
@Override
|
||||
public int OnImageCallBack(byte[] bytes, MV_FRAME_OUT_INFO mv_frame_out_info) {
|
||||
processImage(bytes, mv_frame_out_info);
|
||||
// processBlackWhiteImage(bytes, mv_frame_out_info);
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
if (MV_OK != nRet) {
|
||||
log.error("Register image callback failed, errcode: {}", Integer.toHexString(nRet));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 开始采集
|
||||
log.info("Starting grabbing...");
|
||||
nRet = MvCameraControl.MV_CC_StartGrabbing(hCamera);
|
||||
if (MV_OK != nRet) {
|
||||
log.error("StartGrabbing failed, errcode: {}", Integer.toHexString(nRet));
|
||||
return false;
|
||||
}
|
||||
|
||||
isRunning = true;
|
||||
log.info("Stream started successfully");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Start stream failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止视频流
|
||||
*/
|
||||
public synchronized void stopStream() {
|
||||
if (hCamera == null || !isRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 停止采集
|
||||
log.info("Stopping grabbing...");
|
||||
int nRet = MvCameraControl.MV_CC_StopGrabbing(hCamera);
|
||||
if (MV_OK != nRet) {
|
||||
log.error("StopGrabbing failed, errcode: {}", Integer.toHexString(nRet));
|
||||
}
|
||||
|
||||
// close device
|
||||
nRet = MvCameraControl.MV_CC_CloseDevice(hCamera);
|
||||
if (MV_OK != nRet) {
|
||||
|
||||
log.error("CloseDevice failed, errcode: {}", Integer.toHexString(nRet));
|
||||
}
|
||||
|
||||
if (null != hCamera) {
|
||||
// Destroy handle
|
||||
nRet = MvCameraControl.MV_CC_DestroyHandle(hCamera);
|
||||
if (MV_OK != nRet) {
|
||||
log.error("DestroyHandle failed, errcode: {}", Integer.toHexString(nRet));
|
||||
}
|
||||
hCamera = null;
|
||||
}
|
||||
MvCameraControl.MV_CC_Finalize();
|
||||
|
||||
isRunning = false;
|
||||
log.info("Stream stopped");
|
||||
} catch (Exception e) {
|
||||
log.error("Stop stream failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭相机
|
||||
*/
|
||||
public synchronized void closeCamera() {
|
||||
if (hCamera != null) {
|
||||
try {
|
||||
// 停止采集
|
||||
log.info("Stopping grabbing...");
|
||||
MvCameraControl.MV_CC_StopGrabbing(hCamera);
|
||||
// 关闭设备
|
||||
log.info("Closing device...");
|
||||
MvCameraControl.MV_CC_CloseDevice(hCamera);
|
||||
// 销毁句柄
|
||||
log.info("Destroying handle...");
|
||||
MvCameraControl.MV_CC_DestroyHandle(hCamera);
|
||||
} catch (Exception e) {
|
||||
log.error("Close camera failed", e);
|
||||
} finally {
|
||||
hCamera = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 释放SDK
|
||||
try {
|
||||
log.info("Finalizing SDK...");
|
||||
MvCameraControl.MV_CC_Finalize();
|
||||
} catch (Exception e) {
|
||||
log.error("Finalize SDK failed", e);
|
||||
}
|
||||
|
||||
isRunning = false;
|
||||
log.info("Camera closed");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理图像数据
|
||||
*
|
||||
* @param bytes 原始图像数据
|
||||
* @param frameInfo 帧信息
|
||||
*/
|
||||
private void processImage(byte[] bytes, MV_FRAME_OUT_INFO frameInfo) {
|
||||
if (bytes == null || frameInfo == null || webSocketSessions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 帧率控制
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (currentTime - lastFrameTime < frameInterval) {
|
||||
return; // 跳过当前帧,控制帧率
|
||||
}
|
||||
lastFrameTime = currentTime;
|
||||
|
||||
int dataSizeForRGB = frameInfo.width * frameInfo.height * 3; // RGB每个像素3字节
|
||||
byte[] pDataForRGB = new byte[dataSizeForRGB];
|
||||
|
||||
// 初始化像素转换参数
|
||||
MV_CC_PIXEL_CONVERT_PARAM_EX stConvertParam = new MV_CC_PIXEL_CONVERT_PARAM_EX();
|
||||
|
||||
// 使用实际的width和height,避免ExtendHeight为0的问题
|
||||
stConvertParam.width = frameInfo.width;
|
||||
stConvertParam.height = frameInfo.height;
|
||||
stConvertParam.srcData = bytes;
|
||||
stConvertParam.srcDataLen = frameInfo.frameLen;
|
||||
stConvertParam.srcPixelType = frameInfo.pixelType;
|
||||
stConvertParam.dstPixelType = MvGvspPixelType.PixelType_Gvsp_RGB8_Packed;
|
||||
stConvertParam.dstBuffer = pDataForRGB;
|
||||
stConvertParam.dstBufferSize = dataSizeForRGB;
|
||||
|
||||
// 尝试进行像素格式转换
|
||||
int nRet = MvCameraControl.MV_CC_ConvertPixelTypeEx(hCamera, stConvertParam);
|
||||
if (MV_OK != nRet) {
|
||||
log.error("Convert PixelType fail, errCode: {}, srcPixelType: {}, width: {}, height: {}",
|
||||
Integer.toHexString(nRet),
|
||||
frameInfo.pixelType.getnValue(),
|
||||
frameInfo.width,
|
||||
frameInfo.height);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 将RGB数据转换为BufferedImage
|
||||
BufferedImage image = new BufferedImage(frameInfo.width, frameInfo.height, BufferedImage.TYPE_3BYTE_BGR);
|
||||
image.getRaster().setDataElements(0, 0, frameInfo.width, frameInfo.height, pDataForRGB);
|
||||
|
||||
// 压缩图像为JPEG格式
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, "jpeg", baos);
|
||||
byte[] compressedData = baos.toByteArray();
|
||||
baos.close();
|
||||
|
||||
log.debug("Original size: {} bytes, Converted size: {} bytes, Compressed size: {} bytes, Compression ratio: {:.2f}%",
|
||||
bytes.length, pDataForRGB.length, compressedData.length, (1 - (double) compressedData.length / bytes.length) * 100);
|
||||
|
||||
// 创建包含图像尺寸信息和压缩数据的消息
|
||||
// 消息格式: [width(4 bytes)][height(4 bytes)][image data]
|
||||
byte[] message = new byte[8 + compressedData.length];
|
||||
|
||||
// 写入宽度和高度(使用小端序)
|
||||
message[0] = (byte) (frameInfo.width & 0xFF);
|
||||
message[1] = (byte) ((frameInfo.width >> 8) & 0xFF);
|
||||
message[2] = (byte) ((frameInfo.width >> 16) & 0xFF);
|
||||
message[3] = (byte) ((frameInfo.width >> 24) & 0xFF);
|
||||
|
||||
message[4] = (byte) (frameInfo.height & 0xFF);
|
||||
message[5] = (byte) ((frameInfo.height >> 8) & 0xFF);
|
||||
message[6] = (byte) ((frameInfo.height >> 16) & 0xFF);
|
||||
message[7] = (byte) ((frameInfo.height >> 24) & 0xFF);
|
||||
|
||||
// 写入压缩后的图像数据
|
||||
System.arraycopy(compressedData, 0, message, 8, compressedData.length);
|
||||
|
||||
// 发送图像数据到所有连接的WebSocket客户端
|
||||
for (WebSocketSession session : webSocketSessions.values()) {
|
||||
if (session.isOpen()) {
|
||||
try {
|
||||
session.sendMessage(new BinaryMessage(message));
|
||||
} catch (Exception e) {
|
||||
log.error("Send message to WebSocket failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Process image failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理黑白相机图像数据
|
||||
* @param bytes 原始图像数据
|
||||
* @param frameInfo 帧信息
|
||||
*/
|
||||
private void processBlackWhiteImage(byte[] bytes, MV_FRAME_OUT_INFO frameInfo) {
|
||||
if (bytes == null || frameInfo == null || webSocketSessions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 帧率控制
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (currentTime - lastFrameTime < frameInterval) {
|
||||
return; // 跳过当前帧,控制帧率
|
||||
}
|
||||
lastFrameTime = currentTime;
|
||||
|
||||
// 对于黑白相机(MV-CE050-30GM),数据是单通道灰度格式
|
||||
// 将原始灰度数据转换为BufferedImage
|
||||
BufferedImage image = new BufferedImage(frameInfo.width, frameInfo.height, BufferedImage.TYPE_BYTE_GRAY);
|
||||
image.getRaster().setDataElements(0, 0, frameInfo.width, frameInfo.height, bytes);
|
||||
|
||||
// 压缩图像为JPEG格式
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, "jpeg", baos);
|
||||
byte[] compressedData = baos.toByteArray();
|
||||
baos.close();
|
||||
|
||||
log.debug("Original size: {} bytes, Compressed size: {} bytes, Compression ratio: {:.2f}%",
|
||||
bytes.length, compressedData.length, (1 - (double)compressedData.length / bytes.length) * 100);
|
||||
|
||||
// 创建包含图像尺寸信息和压缩数据的消息
|
||||
// 消息格式: [width(4 bytes)][height(4 bytes)][image data]
|
||||
byte[] message = new byte[8 + compressedData.length];
|
||||
|
||||
// 写入宽度和高度(使用小端序)
|
||||
message[0] = (byte) (frameInfo.width & 0xFF);
|
||||
message[1] = (byte) ((frameInfo.width >> 8) & 0xFF);
|
||||
message[2] = (byte) ((frameInfo.width >> 16) & 0xFF);
|
||||
message[3] = (byte) ((frameInfo.width >> 24) & 0xFF);
|
||||
|
||||
message[4] = (byte) (frameInfo.height & 0xFF);
|
||||
message[5] = (byte) ((frameInfo.height >> 8) & 0xFF);
|
||||
message[6] = (byte) ((frameInfo.height >> 16) & 0xFF);
|
||||
message[7] = (byte) ((frameInfo.height >> 24) & 0xFF);
|
||||
|
||||
// 写入压缩后的图像数据
|
||||
System.arraycopy(compressedData, 0, message, 8, compressedData.length);
|
||||
|
||||
// 发送图像数据到所有连接的WebSocket客户端
|
||||
for (WebSocketSession session : webSocketSessions.values()) {
|
||||
if (session.isOpen()) {
|
||||
try {
|
||||
session.sendMessage(new BinaryMessage(message));
|
||||
} catch (Exception e) {
|
||||
log.error("Send message to WebSocket failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Process image failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加WebSocket会话
|
||||
* @param session WebSocket会话
|
||||
*/
|
||||
public void addWebSocketSession(WebSocketSession session) {
|
||||
if (session != null) {
|
||||
webSocketSessions.put(session.getId(), session);
|
||||
log.info("WebSocket session added: {}", session.getId());
|
||||
|
||||
// 确保相机已初始化
|
||||
if (!initializeCamera()) {
|
||||
log.error("Failed to initialize camera for new WebSocket session");
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保视频流已开始
|
||||
if (!isRunning) {
|
||||
boolean started = startStream();
|
||||
if (!started) {
|
||||
log.error("Failed to start stream for new WebSocket session");
|
||||
}
|
||||
} else {
|
||||
log.info("Stream already running, no need to start again");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除WebSocket会话
|
||||
* @param session WebSocket会话
|
||||
*/
|
||||
public void removeWebSocketSession(WebSocketSession session) {
|
||||
if (session != null) {
|
||||
webSocketSessions.remove(session.getId());
|
||||
log.info("WebSocket session removed: {}", session.getId());
|
||||
|
||||
// 如果没有活跃的WebSocket会话,记录移除时间
|
||||
if (webSocketSessions.isEmpty()) {
|
||||
lastSessionRemoveTime = System.currentTimeMillis();
|
||||
// 启动一个线程,延迟停止视频流,以便页面刷新时能够快速恢复
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(cameraResourceRetentionTime);
|
||||
// 再次检查是否仍然没有活跃会话
|
||||
if (webSocketSessions.isEmpty()) {
|
||||
stopStream();
|
||||
log.info("No active sessions after retention period, stopped stream");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error("Interrupted while waiting to stop stream", e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前连接的WebSocket会话数量
|
||||
* @return 会话数量
|
||||
*/
|
||||
public int getWebSocketSessionCount() {
|
||||
return webSocketSessions.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查相机是否正在运行
|
||||
* @return 是否正在运行
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package top.wms.admin.controller.hkMVS;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.BinaryWebSocketHandler;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CameraWebSocketHandler extends BinaryWebSocketHandler {
|
||||
|
||||
@Resource
|
||||
private CameraService cameraService;
|
||||
|
||||
/**
|
||||
* 连接建立时触发
|
||||
*/
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||
log.info("WebSocket connection established: {}", session.getId());
|
||||
// 添加WebSocket会话到CameraService
|
||||
cameraService.addWebSocketSession(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接关闭时触发
|
||||
*/
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
|
||||
log.info("WebSocket connection closed: {}, status: {}", session.getId(), status);
|
||||
// 从CameraService移除WebSocket会话
|
||||
cameraService.removeWebSocketSession(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理二进制消息
|
||||
*/
|
||||
@Override
|
||||
protected void handleBinaryMessage(WebSocketSession session, org.springframework.web.socket.BinaryMessage message) throws Exception {
|
||||
// 可以在这里处理前端发送的二进制消息(如果需要)
|
||||
log.debug("Received binary message from WebSocket session: {}", session.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文本消息
|
||||
*/
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, org.springframework.web.socket.TextMessage message) {
|
||||
// 可以在这里处理前端发送的文本消息(如果需要)
|
||||
log.debug("Received text message from WebSocket session: {}, message: {}", session.getId(), message.getPayload());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理传输错误
|
||||
*/
|
||||
@Override
|
||||
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
|
||||
log.error("WebSocket transport error: {}", exception.getMessage(), exception);
|
||||
// 从CameraService移除WebSocket会话
|
||||
cameraService.removeWebSocketSession(session);
|
||||
}
|
||||
}
|
||||
@@ -1,370 +0,0 @@
|
||||
package top.wms.admin.controller.hkMVS; /***************************************************************************************************
|
||||
* @file ConvertPixelType.java
|
||||
* @breif Use functions provided in MvCameraControlWrapper.jar to convert pixel type。
|
||||
* @author zhanglei72
|
||||
* @date 2020/02/10
|
||||
*
|
||||
* @warning
|
||||
* @version V1.0.0 2020/02/10 create this file.
|
||||
* @since 2020/02/10
|
||||
**************************************************************************************************/
|
||||
|
||||
import MvCameraControlWrapper.CameraControlException;
|
||||
import MvCameraControlWrapper.MvCameraControl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Scanner;
|
||||
|
||||
import static MvCameraControlWrapper.MvCameraControlDefines.*;
|
||||
|
||||
public class ConvertPixelType
|
||||
{
|
||||
public static Scanner scanner;
|
||||
private static void printDeviceInfo(MV_CC_DEVICE_INFO stDeviceInfo)
|
||||
{
|
||||
if (null == stDeviceInfo) {
|
||||
System.out.println("stDeviceInfo is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((stDeviceInfo.transportLayerType == MV_GIGE_DEVICE) ||( stDeviceInfo.transportLayerType == MV_GENTL_GIGE_DEVICE))
|
||||
{
|
||||
System.out.println("\tCurrentIp: " + stDeviceInfo.gigEInfo.currentIp);
|
||||
System.out.println("\tModel: " + stDeviceInfo.gigEInfo.modelName);
|
||||
System.out.println("\tUserDefinedName: " + stDeviceInfo.gigEInfo.userDefinedName);
|
||||
} else if (stDeviceInfo.transportLayerType == MV_USB_DEVICE) {
|
||||
System.out.println("\tUserDefinedName: " + stDeviceInfo.usb3VInfo.userDefinedName);
|
||||
System.out.println("\tSerial Number: " + stDeviceInfo.usb3VInfo.serialNumber);
|
||||
System.out.println("\tDevice Number: " + stDeviceInfo.usb3VInfo.deviceNumber);
|
||||
} else if (stDeviceInfo.transportLayerType == MV_GENTL_CAMERALINK_DEVICE)
|
||||
{
|
||||
System.out.println("\tUserDefinedName: " + stDeviceInfo.cmlInfo.userDefinedName);
|
||||
System.out.println("\tSerial Number: " + stDeviceInfo.cmlInfo.serialNumber);
|
||||
System.out.println("\tDevice Number: " + stDeviceInfo.cmlInfo.DeviceID);
|
||||
}
|
||||
else if (stDeviceInfo.transportLayerType == MV_GENTL_CXP_DEVICE)
|
||||
{
|
||||
System.out.println("\tUserDefinedName: " + stDeviceInfo.cxpInfo.userDefinedName);
|
||||
System.out.println("\tSerial Number: " + stDeviceInfo.cxpInfo.serialNumber);
|
||||
System.out.println("\tDevice Number: " + stDeviceInfo.cxpInfo.DeviceID);
|
||||
}
|
||||
else if (stDeviceInfo.transportLayerType == MV_GENTL_XOF_DEVICE)
|
||||
{
|
||||
System.out.println("\tUserDefinedName: " + stDeviceInfo.xofInfo.userDefinedName);
|
||||
System.out.println("\tSerial Number: " + stDeviceInfo.xofInfo.serialNumber);
|
||||
System.out.println("\tDevice Number: " + stDeviceInfo.xofInfo.DeviceID);
|
||||
}else {
|
||||
System.err.print("Device is not supported! \n");
|
||||
}
|
||||
|
||||
System.out.println("\tAccessible: "
|
||||
+ MvCameraControl.MV_CC_IsDeviceAccessible(stDeviceInfo, MV_ACCESS_Exclusive));
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
private static void printFrameInfo(MV_FRAME_OUT_INFO stFrameInfo)
|
||||
{
|
||||
if (null == stFrameInfo)
|
||||
{
|
||||
System.err.println("stFrameInfo is null");
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder frameInfo = new StringBuilder("");
|
||||
frameInfo.append(("\tFrameNum[" + stFrameInfo.frameNum + "]"));
|
||||
frameInfo.append("\tWidth[" + stFrameInfo.width + "]");
|
||||
frameInfo.append("\tHeight[" + stFrameInfo.height + "]");
|
||||
frameInfo.append(String.format("\tPixelType[%#x]", stFrameInfo.pixelType.getnValue()));
|
||||
|
||||
System.out.println(frameInfo.toString());
|
||||
}
|
||||
|
||||
public static void saveDataToFile(byte[] dataToSave, int dataSize, String fileName)
|
||||
{
|
||||
OutputStream os = null;
|
||||
|
||||
try
|
||||
{
|
||||
if((null == dataToSave)||(dataSize <= 0))
|
||||
{
|
||||
System.out.println("saveDataToFile param error.");
|
||||
return;
|
||||
}
|
||||
// create diractory
|
||||
File tempFile = new File("dat");
|
||||
if (!tempFile.exists())
|
||||
{
|
||||
tempFile.mkdirs();
|
||||
}
|
||||
|
||||
os = new FileOutputStream(tempFile.getPath() + File.separator + fileName);
|
||||
if(null != os)
|
||||
{
|
||||
os.write(dataToSave, 0, dataSize);
|
||||
System.out.println("ConvertPixelType succeed.");
|
||||
}
|
||||
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// close file stream
|
||||
try
|
||||
{
|
||||
if(os != null)
|
||||
{
|
||||
os.close();
|
||||
}
|
||||
|
||||
} catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int chooseCamera(ArrayList<MV_CC_DEVICE_INFO> stDeviceList)
|
||||
{
|
||||
if (null == stDeviceList)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Choose a device to operate
|
||||
int camIndex = -1;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
camIndex = 0;
|
||||
if ((camIndex >= 0 && camIndex < stDeviceList.size()) || -1 == camIndex) {
|
||||
break;
|
||||
} else {
|
||||
System.out.println("Input error: " + camIndex + " Over Range:( 0 - " + (stDeviceList.size() - 1) + " )");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("Input not number.");
|
||||
camIndex = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (-1 == camIndex) {
|
||||
System.out.println("Input error.exit");
|
||||
return camIndex;
|
||||
}
|
||||
|
||||
if (0 <= camIndex && stDeviceList.size() > camIndex)
|
||||
{
|
||||
if ((MV_GIGE_DEVICE == stDeviceList.get(camIndex).transportLayerType)||(MV_GENTL_GIGE_DEVICE == stDeviceList.get(camIndex).transportLayerType))
|
||||
{
|
||||
System.out.println("Connect to camera[" + camIndex + "]: " + stDeviceList.get(camIndex).gigEInfo.userDefinedName);
|
||||
}
|
||||
else if (MV_USB_DEVICE == stDeviceList.get(camIndex).transportLayerType)
|
||||
{
|
||||
System.out.println("Connect to camera[" + camIndex + "]: " + stDeviceList.get(camIndex).usb3VInfo.userDefinedName);
|
||||
}
|
||||
else if (MV_GENTL_CAMERALINK_DEVICE == stDeviceList.get(camIndex).transportLayerType)
|
||||
{
|
||||
System.out.println("Connect to camera[" + camIndex + "]: " + stDeviceList.get(camIndex).cmlInfo.DeviceID);
|
||||
}
|
||||
else if (MV_GENTL_CXP_DEVICE == stDeviceList.get(camIndex).transportLayerType)
|
||||
{
|
||||
System.out.println("Connect to camera[" + camIndex + "]: " + stDeviceList.get(camIndex).cxpInfo.DeviceID);
|
||||
}
|
||||
else if (MV_GENTL_XOF_DEVICE == stDeviceList.get(camIndex).transportLayerType)
|
||||
{
|
||||
System.out.println("Connect to camera[" + camIndex + "]: " + stDeviceList.get(camIndex).xofInfo.DeviceID);
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("Device is not supported.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("Invalid index " + camIndex);
|
||||
camIndex = -1;
|
||||
}
|
||||
|
||||
return camIndex;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
int nRet = MV_OK;
|
||||
int camIndex = -1;
|
||||
Handle hCamera = null;
|
||||
scanner = new Scanner(System.in);
|
||||
ArrayList<MV_CC_DEVICE_INFO> stDeviceList = null;
|
||||
|
||||
do
|
||||
{
|
||||
System.out.println("SDK Version " + MvCameraControl.MV_CC_GetSDKVersion());
|
||||
|
||||
// Initialize SDK
|
||||
nRet = MvCameraControl.MV_CC_Initialize();
|
||||
if (MV_OK != nRet)
|
||||
{
|
||||
System.err.printf("Initialize SDK fail! nRet [0x%x]\n\n",nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
// Enumerate GigE and USB devices
|
||||
try
|
||||
{
|
||||
stDeviceList = MvCameraControl.MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_GIGE_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE);
|
||||
if (0 >= stDeviceList.size())
|
||||
{
|
||||
System.out.println("No devices found!");
|
||||
break;
|
||||
}
|
||||
int i = 0;
|
||||
for (MV_CC_DEVICE_INFO stDeviceInfo : stDeviceList)
|
||||
{
|
||||
System.out.println("[camera " + (i++) + "]");
|
||||
printDeviceInfo(stDeviceInfo);
|
||||
}
|
||||
}
|
||||
catch (CameraControlException e)
|
||||
{
|
||||
System.err.println("Enumrate devices failed!" + e.toString());
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
|
||||
// choose camera
|
||||
camIndex = chooseCamera(stDeviceList);
|
||||
if (camIndex == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Create device handle
|
||||
try
|
||||
{
|
||||
hCamera = MvCameraControl.MV_CC_CreateHandle(stDeviceList.get(camIndex));
|
||||
}
|
||||
catch (CameraControlException e)
|
||||
{
|
||||
System.err.println("Create handle failed!" + e.toString());
|
||||
e.printStackTrace();
|
||||
hCamera = null;
|
||||
break;
|
||||
}
|
||||
|
||||
// Open selected device
|
||||
nRet = MvCameraControl.MV_CC_OpenDevice(hCamera);
|
||||
if (MV_OK != nRet)
|
||||
{
|
||||
System.err.printf("Connect to camera failed, errcode: [%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
// make sure that Trigger mode is off
|
||||
nRet = MvCameraControl.MV_CC_SetEnumValueByString(hCamera, "TriggerMode", "Off");
|
||||
if (MV_OK != nRet)
|
||||
{
|
||||
System.err.printf("SetTriggerMode failed, errcode: [%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
// Get payload size
|
||||
MVCC_INTVALUE stParam = new MVCC_INTVALUE();
|
||||
nRet = MvCameraControl.MV_CC_GetIntValue(hCamera, "PayloadSize", stParam);
|
||||
if (MV_OK != nRet)
|
||||
{
|
||||
System.err.printf("Get PayloadSize fail, errcode: [%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
// Start grabbing images
|
||||
nRet = MvCameraControl.MV_CC_StartGrabbing(hCamera);
|
||||
if (MV_OK != nRet)
|
||||
{
|
||||
System.err.printf("Start Grabbing fail, errcode: [%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
// Get one frame
|
||||
MV_FRAME_OUT_INFO stImageInfo = new MV_FRAME_OUT_INFO();
|
||||
byte[] pData = new byte[(int)stParam.curValue];
|
||||
nRet = MvCameraControl.MV_CC_GetOneFrameTimeout(hCamera, pData, stImageInfo, 1000);
|
||||
if (MV_OK != nRet)
|
||||
{
|
||||
System.err.printf("GetOneFrameTimeout fail, errcode:[%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
System.out.println("GetOneFrame: ");
|
||||
printFrameInfo(stImageInfo);
|
||||
|
||||
// set interpolation algorithm type, 0-Fast 1-Equilibrium 2-Optimal 3-Optimal plus
|
||||
nRet = MvCameraControl.MV_CC_SetBayerCvtQuality(hCamera, 1);
|
||||
if (MV_OK != nRet)
|
||||
{
|
||||
System.err.printf("set Bayer convert quality fail! nRet [0x%x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
System.out.println("临时日志: frameInfo.width = " + stImageInfo.width + ", frameInfo.height = " + stImageInfo.height + ", frameInfo.ExtendWidth = " + stImageInfo.ExtendWidth + ", frameInfo.ExtendHeight = " + stImageInfo.ExtendHeight + ", frameInfo.frameLen = " + stImageInfo.frameLen + ", frameInfo.pixelType = " + stImageInfo.pixelType);
|
||||
int dataSizeForRGB = stImageInfo.width * stImageInfo.height * 3; // every RGB pixel takes 3 bytes
|
||||
byte[] pDataForRGB = new byte[dataSizeForRGB];
|
||||
|
||||
// Convert pixel type to RGB8_Packed
|
||||
MV_CC_PIXEL_CONVERT_PARAM_EX stConvertParam = new MV_CC_PIXEL_CONVERT_PARAM_EX();
|
||||
stConvertParam.width = stImageInfo.ExtendWidth; // image width
|
||||
stConvertParam.height = stImageInfo.ExtendHeight; // image height
|
||||
stConvertParam.srcData = pData; // input buffer
|
||||
stConvertParam.srcDataLen = stImageInfo.frameLen; // input buffer size
|
||||
stConvertParam.srcPixelType = stImageInfo.pixelType; // input pixel format
|
||||
stConvertParam.dstPixelType = MvGvspPixelType.PixelType_Gvsp_RGB8_Packed; // output pixel format
|
||||
stConvertParam.dstBuffer = pDataForRGB; // output buffer
|
||||
stConvertParam.dstBufferSize = dataSizeForRGB; // output buffer size
|
||||
|
||||
nRet = MvCameraControl.MV_CC_ConvertPixelTypeEx(hCamera, stConvertParam);
|
||||
if (MV_OK != nRet)
|
||||
{
|
||||
System.err.printf("Convert PixelType fail, errcode: [%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
// Save buffer content to file
|
||||
saveDataToFile(pDataForRGB, dataSizeForRGB, "AfterConvert_RGB.raw");
|
||||
|
||||
// Stop grabbing
|
||||
nRet = MvCameraControl.MV_CC_StopGrabbing(hCamera);
|
||||
if (MV_OK != nRet)
|
||||
{
|
||||
System.err.printf("StopGrabbing failed, errcode: [%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
// close device
|
||||
nRet = MvCameraControl.MV_CC_CloseDevice(hCamera);
|
||||
if (MV_OK != nRet)
|
||||
{
|
||||
System.err.printf("CloseDevice failed, errcode: [%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
} while (false);
|
||||
|
||||
if (null != hCamera)
|
||||
{
|
||||
// Destroy handle
|
||||
nRet = MvCameraControl.MV_CC_DestroyHandle(hCamera);
|
||||
if (MV_OK != nRet) {
|
||||
System.err.printf("DestroyHandle failed, errcode: [%#x]\n", nRet);
|
||||
}
|
||||
}
|
||||
MvCameraControl.MV_CC_Finalize();
|
||||
scanner.close();
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
package top.wms.admin.controller.hkMVS; /***************************************************************************************************
|
||||
* @file Grab_Callback.java
|
||||
* @breif Use functions provided in MvCameraControlWrapper.jar to grab images
|
||||
* @author zhanglei72
|
||||
* @date 2020/01/12
|
||||
*
|
||||
* @warning
|
||||
* @version V1.0.0 2020/01/12 Create this file
|
||||
* V1.0.1 2020/02/10 add parameter checking
|
||||
* @since 2020/02/10
|
||||
**************************************************************************************************/
|
||||
|
||||
import MvCameraControlWrapper.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Scanner;
|
||||
|
||||
import static MvCameraControlWrapper.MvCameraControlDefines.*;
|
||||
|
||||
public class Grab_Callback {
|
||||
public static Scanner scanner;
|
||||
|
||||
private static void printDeviceInfo(MV_CC_DEVICE_INFO stDeviceInfo) {
|
||||
if (null == stDeviceInfo) {
|
||||
System.out.println("stDeviceInfo is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((stDeviceInfo.transportLayerType == MV_GIGE_DEVICE) || (stDeviceInfo.transportLayerType == MV_GENTL_GIGE_DEVICE)) {
|
||||
System.out.println("\tCurrentIp: " + stDeviceInfo.gigEInfo.currentIp);
|
||||
System.out.println("\tModel: " + stDeviceInfo.gigEInfo.modelName);
|
||||
System.out.println("\tUserDefinedName: " + stDeviceInfo.gigEInfo.userDefinedName);
|
||||
} else if (stDeviceInfo.transportLayerType == MV_USB_DEVICE) {
|
||||
System.out.println("\tUserDefinedName: " + stDeviceInfo.usb3VInfo.userDefinedName);
|
||||
System.out.println("\tSerial Number: " + stDeviceInfo.usb3VInfo.serialNumber);
|
||||
System.out.println("\tDevice Number: " + stDeviceInfo.usb3VInfo.deviceNumber);
|
||||
} else if (stDeviceInfo.transportLayerType == MV_GENTL_CAMERALINK_DEVICE) {
|
||||
System.out.println("\tUserDefinedName: " + stDeviceInfo.cmlInfo.userDefinedName);
|
||||
System.out.println("\tSerial Number: " + stDeviceInfo.cmlInfo.serialNumber);
|
||||
System.out.println("\tDevice Number: " + stDeviceInfo.cmlInfo.DeviceID);
|
||||
} else if (stDeviceInfo.transportLayerType == MV_GENTL_CXP_DEVICE) {
|
||||
System.out.println("\tUserDefinedName: " + stDeviceInfo.cxpInfo.userDefinedName);
|
||||
System.out.println("\tSerial Number: " + stDeviceInfo.cxpInfo.serialNumber);
|
||||
System.out.println("\tDevice Number: " + stDeviceInfo.cxpInfo.DeviceID);
|
||||
} else if (stDeviceInfo.transportLayerType == MV_GENTL_XOF_DEVICE) {
|
||||
System.out.println("\tUserDefinedName: " + stDeviceInfo.xofInfo.userDefinedName);
|
||||
System.out.println("\tSerial Number: " + stDeviceInfo.xofInfo.serialNumber);
|
||||
System.out.println("\tDevice Number: " + stDeviceInfo.xofInfo.DeviceID);
|
||||
} else {
|
||||
System.err.print("Device is not supported! \n");
|
||||
}
|
||||
|
||||
System.out.println("\tAccessible: "
|
||||
+ MvCameraControl.MV_CC_IsDeviceAccessible(stDeviceInfo, MV_ACCESS_Exclusive));
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
private static void printFrameInfo(MV_FRAME_OUT_INFO stFrameInfo) {
|
||||
if (null == stFrameInfo) {
|
||||
System.err.println("stFrameInfo is null");
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder frameInfo = new StringBuilder("");
|
||||
frameInfo.append(("\tFrameNum[" + stFrameInfo.frameNum + "]"));
|
||||
frameInfo.append("\tWidth[" + stFrameInfo.width + "]");
|
||||
frameInfo.append("\tHeight[" + stFrameInfo.height + "]");
|
||||
frameInfo.append(String.format("\tPixelType[%#x]", stFrameInfo.pixelType.getnValue()));
|
||||
|
||||
System.out.println(frameInfo.toString());
|
||||
}
|
||||
|
||||
public static int chooseCamera(ArrayList<MV_CC_DEVICE_INFO> stDeviceList) {
|
||||
if (null == stDeviceList) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Choose a device to operate
|
||||
int camIndex = -1;
|
||||
|
||||
while (true) {
|
||||
System.out.print("Please input camera index:");
|
||||
if (scanner.hasNextInt()) {
|
||||
try {
|
||||
|
||||
camIndex = scanner.nextInt();
|
||||
if ((camIndex >= 0 && camIndex < stDeviceList.size()) || -1 == camIndex) {
|
||||
break;
|
||||
} else {
|
||||
System.out.println("Input error: " + camIndex + " Over Range:( 0 - " + (stDeviceList.size() - 1) + " )");
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
System.out.println("Input not number.");
|
||||
camIndex = -1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
camIndex = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (-1 == camIndex) {
|
||||
System.out.println("Input error.exit");
|
||||
return camIndex;
|
||||
}
|
||||
|
||||
if (0 <= camIndex && stDeviceList.size() > camIndex) {
|
||||
if ((MV_GIGE_DEVICE == stDeviceList.get(camIndex).transportLayerType) || (MV_GENTL_GIGE_DEVICE == stDeviceList.get(camIndex).transportLayerType)) {
|
||||
System.out.println("Connect to camera[" + camIndex + "]: " + stDeviceList.get(camIndex).gigEInfo.userDefinedName);
|
||||
} else if (MV_USB_DEVICE == stDeviceList.get(camIndex).transportLayerType) {
|
||||
System.out.println("Connect to camera[" + camIndex + "]: " + stDeviceList.get(camIndex).usb3VInfo.userDefinedName);
|
||||
} else if (MV_GENTL_CAMERALINK_DEVICE == stDeviceList.get(camIndex).transportLayerType) {
|
||||
System.out.println("Connect to camera[" + camIndex + "]: " + stDeviceList.get(camIndex).cmlInfo.DeviceID);
|
||||
} else if (MV_GENTL_CXP_DEVICE == stDeviceList.get(camIndex).transportLayerType) {
|
||||
System.out.println("Connect to camera[" + camIndex + "]: " + stDeviceList.get(camIndex).cxpInfo.DeviceID);
|
||||
} else if (MV_GENTL_XOF_DEVICE == stDeviceList.get(camIndex).transportLayerType) {
|
||||
System.out.println("Connect to camera[" + camIndex + "]: " + stDeviceList.get(camIndex).xofInfo.DeviceID);
|
||||
} else {
|
||||
System.out.println("Device is not supported.");
|
||||
}
|
||||
} else {
|
||||
System.out.println("Invalid index " + camIndex);
|
||||
camIndex = -1;
|
||||
}
|
||||
|
||||
return camIndex;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int nRet = MV_OK;
|
||||
int camIndex = -1;
|
||||
scanner = new Scanner(System.in);
|
||||
|
||||
Handle hCamera = null;
|
||||
ArrayList<MV_CC_DEVICE_INFO> stDeviceList = null;
|
||||
|
||||
do {
|
||||
System.out.println("SDK Version " + MvCameraControl.MV_CC_GetSDKVersion());
|
||||
|
||||
// Initialize SDK
|
||||
nRet = MvCameraControl.MV_CC_Initialize();
|
||||
if (MV_OK != nRet) {
|
||||
System.err.printf("Initialize SDK fail! nRet [0x%x]\n\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
// Enumerate devices
|
||||
try {
|
||||
stDeviceList = MvCameraControl.MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_GIGE_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE);
|
||||
} catch (CameraControlException e) {
|
||||
System.err.println("Enumrate devices failed!" + e.toString());
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
|
||||
if (0 >= stDeviceList.size()) {
|
||||
System.out.println("No devices found!");
|
||||
break;
|
||||
}
|
||||
int i = 0;
|
||||
for (MV_CC_DEVICE_INFO stDeviceInfo : stDeviceList) {
|
||||
if (null == stDeviceInfo) {
|
||||
continue;
|
||||
}
|
||||
System.out.println("[camera " + (i++) + "]");
|
||||
printDeviceInfo(stDeviceInfo);
|
||||
}
|
||||
|
||||
// choose camera
|
||||
camIndex = chooseCamera(stDeviceList);
|
||||
if (-1 == camIndex) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Create device handle
|
||||
try {
|
||||
hCamera = MvCameraControl.MV_CC_CreateHandle(stDeviceList.get(camIndex));
|
||||
} catch (CameraControlException e) {
|
||||
System.err.println("Create handle failed!" + e.toString());
|
||||
e.printStackTrace();
|
||||
hCamera = null;
|
||||
break;
|
||||
}
|
||||
|
||||
// Open selected device
|
||||
nRet = MvCameraControl.MV_CC_OpenDevice(hCamera);
|
||||
if (MV_OK != nRet) {
|
||||
System.err.printf("Connect to camera failed, errcode: [%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
// Register image callback
|
||||
nRet = MvCameraControl.MV_CC_RegisterImageCallBack(hCamera, new CameraImageCallBack() {
|
||||
@Override
|
||||
public int OnImageCallBack(byte[] bytes, MV_FRAME_OUT_INFO mv_frame_out_info) {
|
||||
printFrameInfo(mv_frame_out_info);
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
if (MV_OK != nRet) {
|
||||
System.err.printf("register image callback failed, errcode: [%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Turn off trigger mode
|
||||
nRet = MvCameraControl.MV_CC_SetEnumValueByString(hCamera, "TriggerMode", "Off");
|
||||
if (MV_OK != nRet) {
|
||||
System.err.printf("SetTriggerMode failed, errcode: [%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
nRet = MvCameraControl.MV_CC_StartGrabbing(hCamera);
|
||||
if (MV_OK != nRet) {
|
||||
System.err.printf("StartGrabbing failed, errcode: [%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
scanner.useDelimiter("");
|
||||
System.out.println("Press Enter to exit.");
|
||||
while (true) {
|
||||
String input = scanner.nextLine();
|
||||
if (scanner.hasNextLine()) {
|
||||
break;
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(1 * 10);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Stop grabbing
|
||||
nRet = MvCameraControl.MV_CC_StopGrabbing(hCamera);
|
||||
if (MV_OK != nRet) {
|
||||
System.err.printf("StopGrabbing failed, errcode: [%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
// close device
|
||||
nRet = MvCameraControl.MV_CC_CloseDevice(hCamera);
|
||||
if (MV_OK != nRet) {
|
||||
System.err.printf("CloseDevice failed, errcode: [%#x]\n", nRet);
|
||||
break;
|
||||
}
|
||||
|
||||
} while (false);
|
||||
|
||||
if (null != hCamera) {
|
||||
// Destroy handle
|
||||
nRet = MvCameraControl.MV_CC_DestroyHandle(hCamera);
|
||||
if (MV_OK != nRet) {
|
||||
System.err.printf("DestroyHandle failed, errcode: [%#x]\n", nRet);
|
||||
}
|
||||
}
|
||||
MvCameraControl.MV_CC_Finalize();
|
||||
scanner.close();
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public class MaterialInfoController extends BaseController<MaterialInfoService,
|
||||
@GetMapping("/code/{code}")
|
||||
public MaterialInfoDO getMaterialInfoByCode(@PathVariable String code) {
|
||||
MaterialInfoDO materialInfoDO = baseService.getMaterialInfoByCode(code);
|
||||
CheckUtils.throwIfEmpty(materialInfoDO,"未查询到相关物料信息");
|
||||
CheckUtils.throwIfEmpty(materialInfoDO, "未查询到相关物料信息");
|
||||
return materialInfoDO;
|
||||
}
|
||||
|
||||
@@ -82,10 +82,11 @@ public class MaterialInfoController extends BaseController<MaterialInfoService,
|
||||
baseService.uploadMaterialPhotos(zipFile);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "照片抓取", description = "照片抓取")
|
||||
@PostMapping("/import/catch")
|
||||
public String catchPhoto(@RequestParam("file")MultipartFile file) {
|
||||
CheckUtils.throwIfEmpty(file,"照片抓取失败,请重新抓取");
|
||||
public String catchPhoto(@RequestParam("file") MultipartFile file) {
|
||||
CheckUtils.throwIfEmpty(file, "照片抓取失败,请重新抓取");
|
||||
return baseService.catchPhoto(file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package top.wms.admin.controller.tcp.config;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import top.wms.admin.controller.tcp.handler.TcpServerHandler;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
import io.netty.handler.codec.string.StringEncoder;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class NettyTcpServer {
|
||||
|
||||
@Value("${tcp.server.port:9005}")
|
||||
private int port;
|
||||
|
||||
@Value("${tcp.server.boss-threads:1}")
|
||||
private int bossThreads;
|
||||
|
||||
@Value("${tcp.server.worker-threads:4}")
|
||||
private int workerThreads;
|
||||
|
||||
private EventLoopGroup bossGroup;
|
||||
private EventLoopGroup workerGroup;
|
||||
private Channel serverChannel;
|
||||
|
||||
@PostConstruct
|
||||
public void start() throws InterruptedException {
|
||||
log.info("正在启动TCP服务端,端口: {}", port);
|
||||
|
||||
bossGroup = new NioEventLoopGroup(bossThreads);
|
||||
workerGroup = new NioEventLoopGroup(workerThreads);
|
||||
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.option(ChannelOption.SO_BACKLOG, 128)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
// 解决TCP粘包问题
|
||||
pipeline.addLast(new LineBasedFrameDecoder(1024));
|
||||
|
||||
// 字符串编解码
|
||||
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
|
||||
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
|
||||
|
||||
// 关键修复:直接new,不要从Spring容器获取
|
||||
pipeline.addLast(new TcpServerHandler());
|
||||
|
||||
log.debug("新连接接入,处理器已添加");
|
||||
}
|
||||
});
|
||||
|
||||
ChannelFuture future = bootstrap.bind(port).sync();
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
log.info("TCP服务端启动成功,端口: {}", port);
|
||||
} else {
|
||||
log.error("TCP服务端启动失败", future.cause());
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void stop() {
|
||||
log.info("正在关闭TCP服务端...");
|
||||
try {
|
||||
if (serverChannel != null) {
|
||||
serverChannel.close().sync();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error("关闭服务端通道时发生错误", e);
|
||||
} finally {
|
||||
if (bossGroup != null) {
|
||||
bossGroup.shutdownGracefully();
|
||||
}
|
||||
if (workerGroup != null) {
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
log.info("TCP服务端已关闭");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package top.wms.admin.controller.tcp.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SimpleRequestMatcher {
|
||||
|
||||
// 用队列存储响应
|
||||
private final BlockingQueue<String> responseQueue = new LinkedBlockingQueue<>(1);
|
||||
|
||||
// 当前等待的请求标识
|
||||
private volatile boolean isWaiting = false;
|
||||
|
||||
/**
|
||||
* 等待响应
|
||||
*/
|
||||
public String waitForResponse(int timeoutSeconds) {
|
||||
isWaiting = true;
|
||||
try {
|
||||
log.info("等待响应, 超时={}秒", timeoutSeconds);
|
||||
// 从队列取响应,最多等待timeoutSeconds秒
|
||||
String response = responseQueue.poll(timeoutSeconds, TimeUnit.SECONDS);
|
||||
if (response != null) {
|
||||
log.info("收到响应: {}", response);
|
||||
return response;
|
||||
} else {
|
||||
log.error("等待响应超时");
|
||||
return "TIMEOUT";
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error("等待响应被中断: {}", e.getMessage());
|
||||
return "ERROR";
|
||||
} finally {
|
||||
isWaiting = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收响应
|
||||
*/
|
||||
public void handleResponse(String response) {
|
||||
try {
|
||||
// 如果有请求在等待,把响应放入队列
|
||||
if (isWaiting) {
|
||||
responseQueue.offer(response);
|
||||
log.info("响应接收成功: {}", response);
|
||||
} else {
|
||||
log.warn("没有正在等待的请求, 响应被丢弃: {}", response);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理响应失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空队列
|
||||
*/
|
||||
public void clear() {
|
||||
responseQueue.clear();
|
||||
isWaiting = false;
|
||||
log.info("请求匹配器已清空");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package top.wms.admin.controller.tcp.handler;
|
||||
|
||||
import top.wms.admin.controller.tcp.config.SimpleRequestMatcher;
|
||||
import top.wms.admin.controller.tcp.util.SpringContextUtil;
|
||||
import top.wms.admin.controller.tcp.manager.ChannelManager;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class TcpServerHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
private ChannelManager channelManager;
|
||||
private SimpleRequestMatcher requestMatcher;
|
||||
|
||||
private ChannelManager getChannelManager() {
|
||||
if (channelManager == null) {
|
||||
channelManager = SpringContextUtil.getBean(ChannelManager.class);
|
||||
}
|
||||
return channelManager;
|
||||
}
|
||||
|
||||
private SimpleRequestMatcher getRequestMatcher() {
|
||||
if (requestMatcher == null) {
|
||||
requestMatcher = SpringContextUtil.getBean(SimpleRequestMatcher.class);
|
||||
}
|
||||
return requestMatcher;
|
||||
}
|
||||
|
||||
private String getClientId(ChannelHandlerContext ctx) {
|
||||
return ctx.channel().remoteAddress().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
String clientId = getClientId(ctx);
|
||||
getChannelManager().addChannel(clientId, ctx.channel());
|
||||
log.info("✅ VM客户端连接成功: {}", clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
String clientId = getClientId(ctx);
|
||||
getChannelManager().removeChannel(clientId);
|
||||
log.info("❌ VM客户端断开连接: {}", clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
|
||||
String clientId = getClientId(ctx);
|
||||
log.info("📥 收到VM数据 [{}]: {}", clientId, msg);
|
||||
|
||||
try {
|
||||
// 直接把收到的消息交给匹配器(不解析,不匹配)
|
||||
String cleanMsg = msg.trim();
|
||||
getRequestMatcher().handleResponse(cleanMsg);
|
||||
} catch (Exception e) {
|
||||
log.error("处理消息失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
log.error("连接异常: {}", cause.getMessage());
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package top.wms.admin.controller.tcp.manager;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ChannelManager {
|
||||
|
||||
// 存储所有VM客户端的连接
|
||||
private final ConcurrentHashMap<String, Channel> channels = new ConcurrentHashMap<>();
|
||||
|
||||
// 添加连接
|
||||
public void addChannel(String clientId, Channel channel) {
|
||||
channels.put(clientId, channel);
|
||||
log.info("VM客户端 [{}] 已连接,当前在线客户端数: {}", clientId, channels.size());
|
||||
}
|
||||
|
||||
// 移除连接
|
||||
public void removeChannel(String clientId) {
|
||||
channels.remove(clientId);
|
||||
log.info("VM客户端 [{}] 已断开,当前在线客户端数: {}", clientId, channels.size());
|
||||
}
|
||||
|
||||
// 获取第一个连接的VM(如果有多个VM,可以根据需要修改)
|
||||
public Channel getFirstChannel() {
|
||||
return channels.values().stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
// 根据ID获取连接
|
||||
public Channel getChannel(String clientId) {
|
||||
return channels.get(clientId);
|
||||
}
|
||||
|
||||
// 检查是否有VM连接
|
||||
public boolean hasConnection() {
|
||||
return !channels.isEmpty();
|
||||
}
|
||||
|
||||
// 获取所有连接
|
||||
public ConcurrentHashMap<String, Channel> getAllChannels() {
|
||||
return channels;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package top.wms.admin.controller.tcp.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
// 指令请求
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CommandRequest {
|
||||
private String processId; // 要执行的流程ID,如 "PROCESS_1"
|
||||
private String param; // 参数,如 "10.5"
|
||||
private Integer priority; // 优先级(可选)
|
||||
|
||||
// 构建完整的指令字符串
|
||||
public String buildCommand() {
|
||||
// 格式:*PROCESS_1,10.5#
|
||||
return String.format("*%s,%s#", processId, param);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package top.wms.admin.controller.tcp.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
// VM返回的结果
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class VMResult {
|
||||
private String processId; // 流程ID
|
||||
private String status; // OK 或 NG
|
||||
private double value; // 测量值
|
||||
private long timestamp; // 时间戳
|
||||
|
||||
public VMResult(String processId, String status, double value) {
|
||||
this.processId = processId;
|
||||
this.status = status;
|
||||
this.value = value;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package top.wms.admin.controller.tcp.service;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import top.wms.admin.controller.tcp.manager.ChannelManager;
|
||||
import top.wms.admin.controller.tcp.model.VMResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class CommandService {
|
||||
|
||||
// 统计
|
||||
private final AtomicInteger successCount = new AtomicInteger(0);
|
||||
private final AtomicInteger failCount = new AtomicInteger(0);
|
||||
|
||||
// 存储最近100条结果
|
||||
private final ConcurrentLinkedQueue<VMResult> resultQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
// 处理VM返回的结果
|
||||
public void processResult(String processId, String status, double value) {
|
||||
VMResult result = new VMResult(processId, status, value);
|
||||
|
||||
// 更新统计
|
||||
if ("OK".equalsIgnoreCase(status)) {
|
||||
successCount.incrementAndGet();
|
||||
} else {
|
||||
failCount.incrementAndGet();
|
||||
}
|
||||
|
||||
// 存储结果
|
||||
resultQueue.offer(result);
|
||||
if (resultQueue.size() > 100) {
|
||||
resultQueue.poll(); // 只保留最近100条
|
||||
}
|
||||
|
||||
log.info("处理结果 - 流程: {}, 状态: {}, 数值: {}", processId, status, value);
|
||||
|
||||
// 这里可以添加你的业务逻辑,比如:
|
||||
// 1. 存入数据库
|
||||
// 2. 通过WebSocket推送给前端
|
||||
// 3. 触发其他业务操作
|
||||
}
|
||||
|
||||
// 获取统计信息
|
||||
public String getStatistics() {
|
||||
return String.format("成功: %d, 失败: %d, 总数: %d", successCount.get(), failCount.get(), successCount
|
||||
.get() + failCount.get());
|
||||
}
|
||||
|
||||
// 获取最新结果
|
||||
public VMResult getLatestResult() {
|
||||
return resultQueue.peek();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private ChannelManager channelManager;
|
||||
|
||||
@Scheduled(cron = "*/1 * * * * ?")
|
||||
public void sendAndWait() {
|
||||
// 1. 检查连接
|
||||
log.info("查询时间========");
|
||||
Channel channel = channelManager.getFirstChannel();
|
||||
channel.writeAndFlush("001");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package top.wms.admin.controller.tcp.util;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class SpringContextUtil implements ApplicationContextAware {
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
SpringContextUtil.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public static <T> T getBean(Class<T> requiredType) {
|
||||
return applicationContext.getBean(requiredType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package top.wms.admin.controller.vm;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.x.file.storage.core.FileInfo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import top.continew.starter.core.validation.CheckUtils;
|
||||
import top.wms.admin.controller.tcp.config.SimpleRequestMatcher;
|
||||
import top.wms.admin.controller.tcp.manager.ChannelManager;
|
||||
import top.wms.admin.system.service.FileService;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/vm")
|
||||
public class VmCommandController {
|
||||
|
||||
@Autowired
|
||||
private ChannelManager channelManager;
|
||||
|
||||
@Autowired
|
||||
private SimpleRequestMatcher requestMatcher;
|
||||
|
||||
@Autowired
|
||||
private FileService fileService;
|
||||
|
||||
@GetMapping("/send")
|
||||
public String sendAndWait(@RequestParam String msg) {
|
||||
// 1. 检查连接
|
||||
Channel channel = channelManager.getFirstChannel();
|
||||
if (channel == null) {
|
||||
return "ERROR: VM未连接";
|
||||
}
|
||||
String sendMsg = msg;
|
||||
channel.writeAndFlush(sendMsg);
|
||||
log.info("发送指令: {}", sendMsg);
|
||||
// 3. 等待响应
|
||||
String response = requestMatcher.waitForResponse(20);
|
||||
CheckUtils.throwIf("TIMEOUT".equals(response), "响应超时,请重试");
|
||||
if (StrUtil.equals(response, "success") || StrUtil.equals(response, "failed")) {
|
||||
return response;
|
||||
}
|
||||
if (StrUtil.equals(response, msg)) {
|
||||
response = "success";
|
||||
} else {
|
||||
response = "failed";
|
||||
}
|
||||
// 4. 返回结果
|
||||
return response; // 直接返回VM的响应
|
||||
}
|
||||
|
||||
// 基础路径
|
||||
private static final String BASE_PATH = "C:/Users/14725/Desktop/material";
|
||||
|
||||
// 固定照片名称
|
||||
private static final String PHOTO_NAME = "001.bmp";
|
||||
|
||||
/**
|
||||
* 获取最新的001.bmp照片
|
||||
*
|
||||
* @return 图片文件
|
||||
*/
|
||||
/*@GetMapping("/latest-photo")
|
||||
public ResponseEntity<byte[]> getLatestPhoto() {
|
||||
try {
|
||||
// 获取当前日期
|
||||
LocalDate now = LocalDate.now();
|
||||
String yearMonth = now.format(DateTimeFormatter.ofPattern("yyyyMM"));
|
||||
String day = now.format(DateTimeFormatter.ofPattern("dd"));
|
||||
|
||||
// 构建完整的文件路径
|
||||
// 格式: C:/Users/14725/Desktop/material/202603/20260312/001.bmp
|
||||
String filePath = String.format("%s/%s/%s%s/%s", BASE_PATH, yearMonth, yearMonth, day, PHOTO_NAME);
|
||||
|
||||
// 读取图片文件
|
||||
Path imagePath = Paths.get(filePath);
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!Files.exists(imagePath)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
// 读取文件字节
|
||||
byte[] imageBytes = Files.readAllBytes(imagePath);
|
||||
|
||||
// 设置响应头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.valueOf("image/bmp"));
|
||||
headers.setContentLength(imageBytes.length);
|
||||
|
||||
// 返回图片
|
||||
return new ResponseEntity<>(imageBytes, headers, HttpStatus.OK);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}*/
|
||||
|
||||
@GetMapping("/latest-photo")
|
||||
public String getLatestPhoto() {
|
||||
try {
|
||||
// 获取当前日期
|
||||
LocalDate now = LocalDate.now();
|
||||
String yearMonth = now.format(DateTimeFormatter.ofPattern("yyyyMM"));
|
||||
String day = now.format(DateTimeFormatter.ofPattern("dd"));
|
||||
|
||||
// 构建完整的本地文件路径
|
||||
// 格式: C:/Users/14725/Desktop/material/202603/20260312/001.bmp
|
||||
String filePath = String.format("%s/%s/%s%s/%s", BASE_PATH, yearMonth, yearMonth, day, PHOTO_NAME);
|
||||
|
||||
// 读取图片文件
|
||||
Path imagePath = Paths.get(filePath);
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!Files.exists(imagePath)) {
|
||||
return null; // 或者抛出异常,根据业务需求决定
|
||||
}
|
||||
|
||||
// 将文件转换为MultipartFile
|
||||
File file = imagePath.toFile();
|
||||
FileInputStream input = new FileInputStream(file);
|
||||
MultipartFile multipartFile = new MockMultipartFile(
|
||||
file.getName(), // 文件名
|
||||
file.getName(), // 原始文件名
|
||||
"image/bmp", // 内容类型
|
||||
input // 文件输入流
|
||||
);
|
||||
|
||||
// 构建MinIO存储路径
|
||||
String photoStoragePath = "catch" + DateUtil.today() + "/";
|
||||
|
||||
// 使用现有的fileService上传到MinIO
|
||||
FileInfo fileInfo = fileService.upload(multipartFile, photoStoragePath, null, true, true);
|
||||
|
||||
// 检查上传结果
|
||||
CheckUtils.throwIfNull(fileInfo, "照片上传失败");
|
||||
|
||||
// 关闭输入流
|
||||
input.close();
|
||||
return fileInfo.getUrl();
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("处理图片失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import java.util.regex.Pattern;
|
||||
@Slf4j
|
||||
public class AHDZCConnect {
|
||||
|
||||
private static final String PORT_NAME = "COM5";
|
||||
private static final String PORT_NAME = "COM12";
|
||||
private static final int BAUD_RATE = 9600;
|
||||
private static final int DATA_BITS = 8;
|
||||
private static final int STOP_BITS = 1;
|
||||
@@ -52,10 +52,9 @@ public class AHDZCConnect {
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 项目启动时初始化并启动服务
|
||||
if (false) {
|
||||
ScaleService();
|
||||
start();
|
||||
}
|
||||
ScaleService();
|
||||
start();
|
||||
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
@@ -324,4 +323,4 @@ public class AHDZCConnect {
|
||||
log.info("线程已完全停止");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ spring.datasource:
|
||||
# 请务必提前创建好名为 wms_admin 的数据库,如果使用其他数据库名请注意同步修改 DB_NAME 配置
|
||||
url: jdbc:p6spy:mysql://127.0.0.1:3306/wms?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
username: root
|
||||
password: root
|
||||
password: test123$
|
||||
# PostgreSQL 配置
|
||||
# url: jdbc:p6spy:mysql://192.168.2.30:${DB_PORT:3306}/continew?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
# username: ${DB_USER:root}
|
||||
@@ -48,12 +48,12 @@ spring.data:
|
||||
## Redis 配置(单机模式)
|
||||
redis:
|
||||
# 地址
|
||||
host: ${REDIS_HOST:192.168.2.30}
|
||||
# host: ${REDIS_HOST:127.0.0.1}
|
||||
# host: ${REDIS_HOST:192.168.2.30}
|
||||
host: ${REDIS_HOST:127.0.0.1}
|
||||
# 端口(默认 6379)
|
||||
port: ${REDIS_PORT:6379}
|
||||
# 密码(未设置密码时请注释掉)
|
||||
password: ${REDIS_PWD:redis2025}
|
||||
# password: ${REDIS_PWD:redis2025}
|
||||
# 数据库索引
|
||||
database: ${REDIS_DB:1}
|
||||
# 连接超时时间
|
||||
|
||||
Reference in New Issue
Block a user