相机重连

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.version>3.5.5</mybatis-plus.version>
<mybatis-plus-generator.version>3.5.5</mybatis-plus-generator.version> <mybatis-plus-generator.version>3.5.5</mybatis-plus-generator.version>
</properties> </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> <dependencies>
<!-- Spring Boot Web --> <!-- Spring Boot Web -->
@ -106,11 +117,11 @@
<version>1.18.32</version> <version>1.18.32</version>
</dependency> </dependency>
<!-- Swagger/OpenAPI --> <!-- Swagger/OpenAPI (Spring Boot 3.x) -->
<dependency> <dependency>
<groupId>org.springdoc</groupId> <groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>1.6.15</version> <version>2.4.0</version>
</dependency> </dependency>
<!-- Spring AOP --> <!-- Spring AOP -->

@ -28,6 +28,9 @@ public class ImageCaptureService {
// 设备句柄映射表 (IP -> Handle) // 设备句柄映射表 (IP -> Handle)
private final Map<String, Handle> handleMap = new ConcurrentHashMap<>(); private final Map<String, Handle> handleMap = new ConcurrentHashMap<>();
// 设备列表(全局变量)
private ArrayList<MV_CC_DEVICE_INFO> stDeviceList = null;
// SDK初始化状态 // SDK初始化状态
private boolean isSdkInitialized = false; private boolean isSdkInitialized = false;
@ -39,7 +42,10 @@ public class ImageCaptureService {
@PostConstruct @PostConstruct
public void init() { public void init() {
try { try {
log.info("初始化海康相机SDK..."); log.info("========== 开始初始化海康相机 ==========");
// 1. 初始化SDK
log.info("步骤1: 初始化SDK...");
int nRet = MvCameraControl.MV_CC_Initialize(); int nRet = MvCameraControl.MV_CC_Initialize();
if (MV_OK != nRet) { if (MV_OK != nRet) {
log.error("初始化SDK失败errcode: [0x{}]", Integer.toHexString(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 Version: {}", MV_CC_GetSDKVersion());
log.info("SDK初始化成功"); 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()){ 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) { } catch (Exception e) {
log.error("初始化SDK异常", 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()); log.info("开始批量初始化 {} 个相机...", ipList.size());
// 枚举所有设备 // 枚举所有设备(使用全局变量)
ArrayList<MV_CC_DEVICE_INFO> stDeviceList = null;
try { try {
stDeviceList = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE); stDeviceList = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE);
if (stDeviceList == null || stDeviceList.isEmpty()) { if (stDeviceList == null || stDeviceList.isEmpty()) {
@ -168,6 +310,14 @@ public class ImageCaptureService {
return initCameras(ipList); return initCameras(ipList);
} }
/**
*
* @param ip IP
* @return
*/
private Handle initSingleCamera(String ip) {
return initSingleCamera(ip, stDeviceList);
}
/** /**
* *
* @param ip IP * @param ip IP
@ -236,55 +386,110 @@ public class ImageCaptureService {
} }
/** /**
* IP * IP
* @param ip IP * @param ip IP
* @param savePath * @param savePath
* @return * @return
*/ */
public boolean captureImage(String ip, String savePath) { public boolean captureImage(String ip, String savePath) {
if (!isSdkInitialized) { return captureImageWithRetry(ip, savePath, 3);
log.error("SDK未初始化"); }
/**
* 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; return false;
} }
// 获取相机句柄 // 获取相机句柄
Handle hCamera = handleMap.get(ip); Handle hCamera = handleMap.get(ip);
if (hCamera == null) { if (hCamera == null) {
log.error("相机 {} 未初始化请先调用init方法", ip); log.warn("相机 {} 句柄为空,尝试自动初始化...", ip);
return false; 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 { int attempt = 0;
// 获取PayloadSize int lastErrorCode = 0;
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;
}
// 获取一帧图像 while (attempt < maxRetries) {
MV_FRAME_OUT_INFO stImageInfo = new MV_FRAME_OUT_INFO(); attempt++;
byte[] pData = new byte[(int) stParam.curValue]; try {
nRet = MvCameraControl.MV_CC_GetOneFrameTimeout(hCamera, pData, stImageInfo, 3000); // 获取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)); MV_FRAME_OUT_INFO stImageInfo = new MV_FRAME_OUT_INFO();
return false; byte[] pData = new byte[(int) stParam.curValue];
} nRet = MvCameraControl.MV_CC_GetOneFrameTimeout(hCamera, pData, stImageInfo, 3000);
log.info("获取图像成功: 帧[{}] 宽[{}] 高[{}]", if (MV_OK != nRet) {
stImageInfo.frameNum, stImageInfo.width, stImageInfo.height); 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格式 log.info("获取图像成功: 帧[{}] 宽[{}] 高[{}]",
return saveImageAsJpeg(hCamera, pData, stImageInfo, savePath); stImageInfo.frameNum, stImageInfo.width, stImageInfo.height);
} catch (Exception e) { // 保存为JPEG格式
log.error("拍照异常", e); boolean saveResult = saveImageAsJpeg(hCamera, pData, stImageInfo, savePath);
return false; 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) { public List<String> captureBatch(String ip, String basePath, int count) {
List<String> successPaths = new ArrayList<>(); List<String> successPaths = new ArrayList<>();
if (!isSdkInitialized) { // 确保相机已初始化
log.error("SDK未初始化"); if (!ensureCameraInitialized(ip)) {
log.error("相机 {} 初始化失败,无法连拍", ip);
return successPaths; return successPaths;
} }

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

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

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

Loading…
Cancel
Save