相机重连

swagger配置
前端修改
plc数据获取
联合利华-拍照
LAPTOP-S9HJSOEB\昊天 1 month ago
parent 530b9a2052
commit cedfc82075

@ -35,6 +35,17 @@
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<mybatis-plus-generator.version>3.5.5</mybatis-plus-generator.version>
</properties>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<!-- Spring Boot Web -->
@ -106,11 +117,11 @@
<version>1.18.32</version>
</dependency>
<!-- Swagger/OpenAPI -->
<!-- Swagger/OpenAPI (Spring Boot 3.x) -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.15</version>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.4.0</version>
</dependency>
<!-- Spring AOP -->

@ -28,6 +28,9 @@ public class ImageCaptureService {
// 设备句柄映射表 (IP -> Handle)
private final Map<String, Handle> handleMap = new ConcurrentHashMap<>();
// 设备列表(全局变量)
private ArrayList<MV_CC_DEVICE_INFO> stDeviceList = null;
// SDK初始化状态
private boolean isSdkInitialized = false;
@ -39,7 +42,10 @@ public class ImageCaptureService {
@PostConstruct
public void init() {
try {
log.info("初始化海康相机SDK...");
log.info("========== 开始初始化海康相机 ==========");
// 1. 初始化SDK
log.info("步骤1: 初始化SDK...");
int nRet = MvCameraControl.MV_CC_Initialize();
if (MV_OK != nRet) {
log.error("初始化SDK失败errcode: [0x{}]", Integer.toHexString(nRet));
@ -48,14 +54,151 @@ public class ImageCaptureService {
log.info("SDK Version: {}", MV_CC_GetSDKVersion());
log.info("SDK初始化成功");
}
// 2. 枚举设备
log.info("步骤2: 枚举设备...");
try {
stDeviceList = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE);
if (stDeviceList == null || stDeviceList.isEmpty()) {
log.warn("未找到任何设备");
} else {
log.info("枚举到 {} 个设备", stDeviceList.size());
// 打印所有设备信息
for (int i = 0; i < stDeviceList.size(); i++) {
MV_CC_DEVICE_INFO device = stDeviceList.get(i);
if (device.transportLayerType == MV_GIGE_DEVICE) {
log.info("设备[{}] - IP: {}, 型号: {}",
i, device.gigEInfo.currentIp, device.gigEInfo.modelName);
} else if (device.transportLayerType == MV_USB_DEVICE) {
log.info("设备[{}] - USB序列号: {}",
i, device.usb3VInfo.serialNumber);
}
}
}
} catch (CameraControlException e) {
log.error("枚举设备失败", e);
}
// 3. 初始化配置的相机
if (appConfig.getHikCamera() != null && !appConfig.getHikCamera().isEmpty()){
initCamerasFromConfig(appConfig.getHikCamera());
log.info("步骤3: 从配置初始化 {} 个相机...", appConfig.getHikCamera().size());
initCamerasFromConfig(appConfig.getHikCamera());
} else {
log.warn("配置中没有海康相机");
}
log.info("========== 海康相机初始化完成 ==========");
log.info("已初始化相机: {}", handleMap.keySet());
} catch (Exception e) {
log.error("初始化SDK异常", e);
}
}
/**
*
* @param ip IP
* @return
*/
public boolean ensureCameraInitialized(String ip) {
// 检查SDK是否初始化
if (!isSdkInitialized) {
log.warn("SDK未初始化正在重新初始化...");
try {
int nRet = MvCameraControl.MV_CC_Initialize();
if (MV_OK != nRet) {
log.error("SDK重新初始化失败errcode: [0x{}]", Integer.toHexString(nRet));
return false;
}
isSdkInitialized = true;
log.info("SDK重新初始化成功");
} catch (Exception e) {
log.error("SDK重新初始化异常", e);
return false;
}
}
// 检查相机句柄是否存在
if (!handleMap.containsKey(ip)) {
log.warn("相机 {} 未初始化,正在重新初始化...", ip);
// 枚举设备(如果有全局设备列表则直接使用)
if (stDeviceList == null || stDeviceList.isEmpty()) {
try {
stDeviceList = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE);
if (stDeviceList == null || stDeviceList.isEmpty()) {
log.error("未找到任何设备");
return false;
}
log.info("重新枚举到 {} 个设备", stDeviceList.size());
} catch (CameraControlException e) {
log.error("枚举设备失败", e);
return false;
}
}
// 初始化单个相机
Handle handle = initSingleCamera(ip, stDeviceList);
if (handle == null) {
log.error("相机 {} 重新初始化失败", ip);
return false;
}
log.info("相机 {} 重新初始化成功", ip);
}
return true;
}
/**
*
*
*/
public void initAllCameras() {
log.info("========== 主动初始化所有相机 ==========");
// 确保SDK已初始化
if (!isSdkInitialized) {
log.warn("SDK未初始化正在初始化...");
try {
int nRet = MvCameraControl.MV_CC_Initialize();
if (MV_OK != nRet) {
log.error("SDK初始化失败errcode: [0x{}]", Integer.toHexString(nRet));
return;
}
isSdkInitialized = true;
log.info("SDK初始化成功");
} catch (Exception e) {
log.error("SDK初始化异常", e);
return;
}
}
// 清空已初始化的相机,重新初始化
handleMap.clear();
// 从配置初始化
if (appConfig.getHikCamera() != null && !appConfig.getHikCamera().isEmpty()){
for (AppConfig.Camera camera : appConfig.getHikCamera()) {
String ip = camera.getIp();
log.info("初始化相机: {}", ip);
if (stDeviceList == null || stDeviceList.isEmpty()) {
try {
stDeviceList = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE);
} catch (CameraControlException e) {
log.error("枚举设备失败", e);
continue;
}
}
Handle handle = initSingleCamera(ip, stDeviceList);
if (handle != null) {
log.info("相机 {} 初始化成功", ip);
} else {
log.error("相机 {} 初始化失败", ip);
}
}
}
log.info("========== 相机初始化完成 ==========");
log.info("已初始化相机: {}", handleMap.keySet());
}
/**
*
*/
@ -118,8 +261,7 @@ public class ImageCaptureService {
log.info("开始批量初始化 {} 个相机...", ipList.size());
// 枚举所有设备
ArrayList<MV_CC_DEVICE_INFO> stDeviceList = null;
// 枚举所有设备(使用全局变量)
try {
stDeviceList = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE);
if (stDeviceList == null || stDeviceList.isEmpty()) {
@ -168,6 +310,14 @@ public class ImageCaptureService {
return initCameras(ipList);
}
/**
*
* @param ip IP
* @return
*/
private Handle initSingleCamera(String ip) {
return initSingleCamera(ip, stDeviceList);
}
/**
*
* @param ip IP
@ -236,55 +386,110 @@ public class ImageCaptureService {
}
/**
* IP
* IP
* @param ip IP
* @param savePath
* @return
*/
public boolean captureImage(String ip, String savePath) {
if (!isSdkInitialized) {
log.error("SDK未初始化");
return captureImageWithRetry(ip, savePath, 3);
}
/**
* IP
* @param ip IP
* @param savePath
* @param maxRetries
* @return
*/
public boolean captureImageWithRetry(String ip, String savePath, int maxRetries) {
// 确保相机已初始化(如果未初始化则立即初始化)
if (!ensureCameraInitialized(ip)) {
log.error("相机 {} 初始化失败,无法拍照", ip);
return false;
}
// 获取相机句柄
Handle hCamera = handleMap.get(ip);
if (hCamera == null) {
log.error("相机 {} 未初始化请先调用init方法", ip);
return false;
log.warn("相机 {} 句柄为空,尝试自动初始化...", ip);
if (!ensureCameraInitialized(ip)) {
log.error("相机 {} 自动初始化失败,无法拍照", ip);
return false;
}
hCamera = handleMap.get(ip);
if (hCamera == null) {
log.error("相机 {} 自动初始化后仍无法获取句柄", ip);
return false;
}
log.info("相机 {} 自动初始化成功", ip);
}
log.info("开始拍照,相机: {}, 保存路径: {}", ip, savePath);
log.info("开始拍照,相机: {}, 保存路径: {}, 最大重试: {} 次", ip, savePath, maxRetries);
try {
// 获取PayloadSize
MVCC_INTVALUE stParam = new MVCC_INTVALUE();
int nRet = MvCameraControl.MV_CC_GetIntValue(hCamera, "PayloadSize", stParam);
if (MV_OK != nRet) {
log.error("获取PayloadSize失败errcode: [0x{}]", Integer.toHexString(nRet));
return false;
}
int attempt = 0;
int lastErrorCode = 0;
// 获取一帧图像
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, 3000);
while (attempt < maxRetries) {
attempt++;
try {
// 获取PayloadSize
MVCC_INTVALUE stParam = new MVCC_INTVALUE();
int nRet = MvCameraControl.MV_CC_GetIntValue(hCamera, "PayloadSize", stParam);
if (MV_OK != nRet) {
log.error("获取PayloadSize失败errcode: [0x{}]", Integer.toHexString(nRet));
lastErrorCode = nRet;
continue;
}
if (MV_OK != nRet) {
log.error("获取图像失败errcode: [0x{}]", Integer.toHexString(nRet));
return false;
}
// 获取一帧图像
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, 3000);
log.info("获取图像成功: 帧[{}] 宽[{}] 高[{}]",
stImageInfo.frameNum, stImageInfo.width, stImageInfo.height);
if (MV_OK != nRet) {
log.warn("获取图像失败 (第{}次)errcode: [0x{}]", attempt, Integer.toHexString(nRet));
lastErrorCode = nRet;
if (attempt < maxRetries) {
log.info("等待200ms后重试...");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
continue;
}
// 保存为JPEG格式
return saveImageAsJpeg(hCamera, pData, stImageInfo, savePath);
log.info("获取图像成功: 帧[{}] 宽[{}] 高[{}]",
stImageInfo.frameNum, stImageInfo.width, stImageInfo.height);
} catch (Exception e) {
log.error("拍照异常", e);
return false;
// 保存为JPEG格式
boolean saveResult = saveImageAsJpeg(hCamera, pData, stImageInfo, savePath);
if (saveResult) {
log.info("拍照成功!共尝试 {} 次", attempt);
return true;
} else {
lastErrorCode = -1;
}
} catch (Exception e) {
log.error("拍照异常 (第{}次)", attempt, e);
lastErrorCode = -1;
if (attempt < maxRetries) {
try {
Thread.sleep(200);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
log.error("拍照失败,已重试 {} 次,最后错误码: [0x{}]", attempt, Integer.toHexString(lastErrorCode));
return false;
}
/**
@ -415,8 +620,9 @@ public class ImageCaptureService {
public List<String> captureBatch(String ip, String basePath, int count) {
List<String> successPaths = new ArrayList<>();
if (!isSdkInitialized) {
log.error("SDK未初始化");
// 确保相机已初始化
if (!ensureCameraInitialized(ip)) {
log.error("相机 {} 初始化失败,无法连拍", ip);
return successPaths;
}

@ -48,6 +48,8 @@ import java.util.*;
import java.util.List;
import java.util.concurrent.*;
import static java.lang.Thread.sleep;
/**
* S7 PLC
* PLC
@ -259,7 +261,7 @@ public class S7MultiPlcService {
// 检查是否是 S7 String 格式(有长度前缀)
if (length > 2 && offset + 2 < buffer.length) {
int maxLen = buffer[offset] & 0xFF;
int actualLen = buffer[offset + 1] & 0xFF;
int actualLen = buffer[offset] & 0xFF;
if (maxLen > 0 && actualLen > 0) {
int readLen = Math.min(actualLen, length - 2);
return new String(buffer, offset + 2, readLen, StandardCharsets.UTF_8).trim();
@ -302,10 +304,11 @@ public class S7MultiPlcService {
Math.max(config.getPalletOffset(), config.getBatchOffset()),
Math.max(config.getDateOffset(), config.getSnapOffset())
);
int maxDataTypeSize = getDataTypeSize(config.getPalletDataType());
int maxDataTypeSize = getDataTypeSize(config.getSnapDataType());
int readLength = maxOffset + maxDataTypeSize;
byte[] buffer = new byte[readLength];
int result = client.ReadArea(S7.S7AreaDB, config.getReadDataBlock(), 0, readLength, buffer);
if (result != 0) {
@ -328,29 +331,41 @@ public class S7MultiPlcService {
// 检查托盘号是否需要处理
String lastPallet = lastPalletCache.get(plcNumber);
boolean needProcess = false;
if (data.getSnapFlag() == 0){
if (lastPallet == null) {
// 首次启动,查数据库比对
CameraUrl lastRecord = cameraUrlService.getOne(new QueryWrapper<CameraUrl>()
.orderByDesc("create_time").last("limit 1"));
if (lastRecord != null && !data.getPalletNo().equals(lastRecord.getPallet())) {
// 标记拍照修改
writePhotoResult(plcNumber, (byte) 0);
}else {
if (data.getSnapFlag() == 1 && (lastPallet == null || !lastPallet.equals(data.getPalletNo()))) {
// 托盘号变化或首次启动
needProcess = true;
}else{
lastPalletCache.put(plcNumber, data.getPalletNo());
}
} else if (!lastPallet.equals(data.getPalletNo())) {
// 托盘号变化
needProcess = true;
}
// if (lastPallet == null) {
// // 首次启动,查数据库比对
// CameraUrl lastRecord = cameraUrlService.getOne(new QueryWrapper<CameraUrl>()
// .orderByDesc("create_time").last("limit 1"));
//
// if (data.getPalletNo()!= null && !data.getPalletNo().isEmpty()&& !data.getPalletNo().equals(lastRecord.getPallet()) && data.getSnapFlag() == 1) {
// needProcess = true;
// }else{
// lastPalletCache.put(plcNumber, data.getPalletNo());
// }
// } else
// }
if (needProcess) {
lastPalletCache.put(plcNumber, data.getPalletNo());
// 触发灯
if (config.getLightChannel() != null) {
lightService.pulse(config.getLightChannel(), lightConfig.getDuration(),
lightConfig.getRes(), lightConfig.getIp(), lightConfig.getPort());
}
log.info("PLC {} 托盘号变化: {} -> {}", plcNumber, lastPallet, data.getPalletNo());
lastPalletCache.put(plcNumber, data.getPalletNo());
sleep(500);
// 拍照
Map<String, String> photos = capturePhotos(config, data);
@ -361,7 +376,16 @@ public class S7MultiPlcService {
cameraUrl.setPallet(data.getPalletNo());
cameraUrl.setBatch(data.getBatchNo());
if (data.getDate() != null && !data.getDate().isEmpty()) {
cameraUrl.setDate(LocalDateTime.parse(data.getDate(), FULL_DATE_FORMATTER));
try {
// 截取前14位兼容异常格式
String dateStr = data.getDate().trim();
if (dateStr.length() >= 15) {
cameraUrl.setDate(LocalDateTime.parse(dateStr, FULL_DATE_FORMATTER));
}
} catch (Exception e) {
log.warn("日期解析失败: '{}', 使用当前时间", data.getDate());
cameraUrl.setDate(LocalDateTime.now());
}
}
cameraUrl.setId(UUID.randomUUID().toString());
cameraUrl.setCreateTime(LocalDateTime.now());
@ -422,6 +446,7 @@ public class S7MultiPlcService {
log.info("PLC {} 上相机拍照: {}, 结果: {}", config.getPlcNumber(), upPath, success);
}
}
sleep(1000);
// 下相机拍照
if (config.getCameraDown() != null && !config.getCameraDown().isEmpty()) {

@ -94,7 +94,6 @@ springdoc:
path: /v3/api-docs # API文档路径
swagger-ui:
enabled: true # 是否启用Swagger UI
path: /swagger-ui.html # Swagger UI路径
deleteFile:
# cron: "0/5 * * * * ?"
@ -120,4 +119,3 @@ yoloModelConfig:
# 置信度
confThreshold: 0.95
names: ['code']

@ -338,7 +338,6 @@
const tbody = document.getElementById('tableBody');
tbody.innerHTML = data.list.map(item => `
<tr>
<td>${item.id || '-'}</td>
<td>${item.pallet || '-'}</td>
<td>${item.batch || '-'}</td>
<td>${formatDateTime(item.date)}</td>

Loading…
Cancel
Save