diff --git a/libs/ffmpeg/text.txt b/libs/ffmpeg/text.txt new file mode 100644 index 0000000..61de0ed --- /dev/null +++ b/libs/ffmpeg/text.txt @@ -0,0 +1 @@ +测试 \ No newline at end of file diff --git a/web/src/main/java/com/zhehekeji/web/config/FusionDesignApplication.java b/web/src/main/java/com/zhehekeji/web/config/FusionDesignApplication.java index 93a5865..7aadb8e 100644 --- a/web/src/main/java/com/zhehekeji/web/config/FusionDesignApplication.java +++ b/web/src/main/java/com/zhehekeji/web/config/FusionDesignApplication.java @@ -20,8 +20,8 @@ public class FusionDesignApplication { // 使用RestTemplateBuilder来实例化RestTemplate对象,spring默认已经注入了RestTemplateBuilder实例 @Bean public RestTemplate restTemplate() { - return builder.setConnectTimeout(Duration.ofMinutes(10)) // 设置连接超时时间,单位毫秒 - .setReadTimeout(Duration.ofMinutes(20)) // 设置读取超时时间,单位秒 + return builder.setConnectTimeout(Duration.ofMinutes(10)) // 设置连接超时时间,单位分钟 + .setReadTimeout(Duration.ofMinutes(20)) // 设置读取超时时间,单位分钟 .build(); } } diff --git a/web/src/main/java/com/zhehekeji/web/controller/IndustrialCameraController.java b/web/src/main/java/com/zhehekeji/web/controller/IndustrialCameraController.java index fe23fe9..17e2eb1 100644 --- a/web/src/main/java/com/zhehekeji/web/controller/IndustrialCameraController.java +++ b/web/src/main/java/com/zhehekeji/web/controller/IndustrialCameraController.java @@ -65,8 +65,8 @@ public class IndustrialCameraController { @ApiOperation("拍照") @PostMapping("pic") - public Result> pic(){ - List list = new ArrayList<>(); + public Result> pic(){ + List list = new ArrayList<>(); LocalDate currentDate = LocalDate.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); @@ -80,9 +80,12 @@ public class IndustrialCameraController { // 提交异步任务 CompletableFuture future = CompletableFuture.runAsync(() -> { - hikSaveImage.saveImage(camera, fullPath, "sn"); + boolean flag = hikSaveImage.saveImage(camera, fullPath, "sn"); synchronized (list) { - list.add(path); + ReadOCR readOCR = new ReadOCR(); + readOCR.setCode(path); + readOCR.setStatus(flag ? 1 : 0); + list.add(readOCR); } }); @@ -166,31 +169,46 @@ public class IndustrialCameraController { public Result siteInventory(@RequestBody IndustrialCameraVO industrialCameraVo) { serialPortExample.openLight(1); //2d图片保存 - Result> listResult = pic(); + Result> listResult = pic(); boolean re = false; //sick识别 Integer count = 0; String code = ""; String pa = ""; + int hikFlag = 0; + + ReadOCR readOCR = new ReadOCR(); try { Thread.sleep(400); - ReadOCR readOCR = new ReadOCR(); readOCR = SickSocket.readOCR(configProperties.getCameraConfig().getSickIp(), configProperties.getCameraConfig().getSickPort()); - code = readOCR.getCode(); + log.info("sick识别结果:"+readOCR); + if(readOCR.getStatus()!=1){ + code = "0"; + }else { + code = readOCR.getCode(); + } //2d识别 - for (String path : listResult.getData()) { + for (ReadOCR readOCR1 : listResult.getData()) { + if (readOCR1.getStatus()!=1){ + + continue; + } + String path = readOCR1.getCode(); + re = FeatureMatchingExample.matchTemplate( InventoryService.readImagesInFolder(configProperties.getSavePath().getMediaPath() + "template/" + industrialCameraVo.getTypeMacth()), configProperties.getSavePath().getMediaPath() + path); + log.info("2d识别结果:"+re); if (re) { pa = path; + hikFlag= 1; break; } } if (pa.equals("")) { - pa = listResult.getData().get(0); + pa = listResult.getData().get(0).getCode(); } //3d pcd保存 LocalDate currentDate = LocalDate.now(); @@ -214,9 +232,27 @@ public class IndustrialCameraController { kuKou.setWmsCode(industrialCameraVo.getCode()); kuKou.setPath(configProperties.getSavePath().getNetPicPath() + pa + ".jpg"); kuKou.setCategoryName(industrialCameraVo.getTypeMacth()); - if (re && count == industrialCameraVo.getCount() && code.endsWith(industrialCameraVo.getCode())) { +// 现场盘点不再进行识别,flag代表是否出现问题 +// if (re && count.equals(industrialCameraVo.getCount()) && code.endsWith(industrialCameraVo.getCode())) { +// kuKou.setFlag(1); +// } else + if (count.equals(-2)||readOCR.getStatus() ==0 || listResult.getData().get(0).getStatus() ==0){ + kuKou.setCount(0); + + kuKou.setFlag(11); + + }else if (count.equals(-1)){ + kuKou.setCount(0); + + kuKou.setFlag(12); + + }else { +// +// kuKou.setCount(0); kuKou.setFlag(1); - } else kuKou.setFlag(0); + + } + kuKouService.save(kuKou); // serialPortExample.openLight(0); diff --git a/web/src/main/java/com/zhehekeji/web/lib/joyware/JoywareLoginModuleImpl.java b/web/src/main/java/com/zhehekeji/web/lib/joyware/JoywareLoginModuleImpl.java index 1a201db..3261ed1 100644 --- a/web/src/main/java/com/zhehekeji/web/lib/joyware/JoywareLoginModuleImpl.java +++ b/web/src/main/java/com/zhehekeji/web/lib/joyware/JoywareLoginModuleImpl.java @@ -12,6 +12,10 @@ import com.zhehekeji.web.lib.joyware.NetSDKLib.NET_IN_LOGIN_WITH_HIGHLEVEL_SECUR import com.zhehekeji.web.lib.joyware.NetSDKLib.NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY; import lombok.extern.slf4j.Slf4j; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Scanner; + /** * 登陆接口实现 * 主要有 :初始化、登陆、登出功能 @@ -19,8 +23,8 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class JoywareLoginModuleImpl implements CameraControlLoginModule { - public static NetSDKLib netsdk = NetSDKLib.NETSDK_INSTANCE; - public static NetSDKLib configsdk = NetSDKLib.CONFIG_INSTANCE; + public static NetSDKLib netsdk = NetSDKLib.NETSDK_INSTANCE; + public static NetSDKLib configsdk = NetSDKLib.CONFIG_INSTANCE; private static int MAX_RECONNET_TIME = 100000; @@ -31,14 +35,14 @@ public class JoywareLoginModuleImpl implements CameraControlLoginModule { private static CallBack.HaveReConnect haveReConnect = new CallBack.HaveReConnect(); - private static boolean bInit = false; + private static boolean bInit = false; private static boolean bLogopen = false; private static CaptureReceiveCB captureReceiveCB = new CaptureReceiveCB(); - public static CallBack.Mp4ReceiveCB mp4ReceiveCB= new CallBack.Mp4ReceiveCB(); + public static CallBack.Mp4ReceiveCB mp4ReceiveCB = new CallBack.Mp4ReceiveCB(); - public static Boolean connectStatus(LLong userId){ + public static Boolean connectStatus(LLong userId) { IntByReference retLen = new IntByReference(0); Pointer p = new Memory(Integer.SIZE); p.clear(Integer.SIZE); @@ -48,10 +52,10 @@ public class JoywareLoginModuleImpl implements CameraControlLoginModule { Integer.SIZE, retLen, 500)) { - log.error("joyware connectStatus error,errorCode:{}",netsdk.CLIENT_GetLastError()); + log.error("joyware connectStatus error,errorCode:{}", netsdk.CLIENT_GetLastError()); return false; } - int []a = new int[1]; + int[] a = new int[1]; p.read(0, a, 0, 1); return a[0] == 1; } @@ -65,7 +69,7 @@ public class JoywareLoginModuleImpl implements CameraControlLoginModule { */ public boolean init(NetSDKLib.fDisConnect disConnect, NetSDKLib.fHaveReConnect haveReConnect) { bInit = netsdk.CLIENT_Init(disConnect, null); - if(!bInit) { + if (!bInit) { System.out.println("Initialize SDK failed"); return false; } @@ -86,7 +90,7 @@ public class JoywareLoginModuleImpl implements CameraControlLoginModule { netParam.nGetConnInfoTime = 3000; // 设置子连接的超时时间 netsdk.CLIENT_SetNetworkParam(netParam); //todo - netsdk.CLIENT_SetSnapRevCallBack(captureReceiveCB,null); + netsdk.CLIENT_SetSnapRevCallBack(captureReceiveCB, null); Native.setCallbackThreadInitializer(mp4ReceiveCB, new CallbackThreadInitializer(false, false, "downloadbytime callback thread")); @@ -101,11 +105,11 @@ public class JoywareLoginModuleImpl implements CameraControlLoginModule { * \endif */ public static void cleanup() { - if(bLogopen) { + if (bLogopen) { netsdk.CLIENT_LogClose(); } - if(bInit) { + if (bInit) { netsdk.CLIENT_Cleanup(); } } @@ -120,30 +124,28 @@ public class JoywareLoginModuleImpl implements CameraControlLoginModule { public LLong login(String m_strIp, int m_nPort, String m_strUser, String m_strPassword) { //IntByReference nError = new IntByReference(0); //入参 - init(disConnectCallBack,haveReConnect); - NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY pstInParam=new NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY(); - pstInParam.nPort=m_nPort; - pstInParam.szIP=m_strIp.getBytes(); - pstInParam.szPassword=m_strPassword.getBytes(); - pstInParam.szUserName=m_strUser.getBytes(); + init(disConnectCallBack, haveReConnect); + NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY pstInParam = new NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY(); + pstInParam.nPort = m_nPort; + pstInParam.szIP = m_strIp.getBytes(); + pstInParam.szPassword = m_strPassword.getBytes(); + pstInParam.szUserName = m_strUser.getBytes(); //出参 - NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY pstOutParam=new NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY(); - pstOutParam.stuDeviceInfo=m_stDeviceInfo; + NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY pstOutParam = new NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY(); + pstOutParam.stuDeviceInfo = m_stDeviceInfo; //m_hLoginHandle = netsdk.CLIENT_LoginEx2(m_strIp, m_nPort, m_strUser, m_strPassword, 0, null, m_stDeviceInfo, nError); - LLong m_hLoginHandle=netsdk.CLIENT_LoginWithHighLevelSecurity(pstInParam, pstOutParam); + LLong m_hLoginHandle = netsdk.CLIENT_LoginWithHighLevelSecurity(pstInParam, pstOutParam); int tryTimes = 0; - while (m_hLoginHandle.longValue()==0 && tryTimes <= MAX_RECONNET_TIME){ - log.error("joyware login error,ip:{},port:{},errorCode:{}",m_strIp,m_nPort,netsdk.CLIENT_GetLastError()); - m_hLoginHandle=netsdk.CLIENT_LoginWithHighLevelSecurity(pstInParam, pstOutParam); - tryTimes ++; + while (m_hLoginHandle.longValue() == 0 && tryTimes <= MAX_RECONNET_TIME) { + log.error("joyware login error,ip:{},port:{},errorCode:{}", m_strIp, m_nPort, netsdk.CLIENT_GetLastError()); + m_hLoginHandle = netsdk.CLIENT_LoginWithHighLevelSecurity(pstInParam, pstOutParam); + tryTimes++; } - if(m_hLoginHandle.longValue()==0){ + if (m_hLoginHandle.longValue() == 0) { return null; } - log.info("joyware login success,loginId:{}",m_hLoginHandle.longValue()); + log.info("joyware login success,loginId:{}", m_hLoginHandle.longValue()); return m_hLoginHandle; } - - } diff --git a/web/src/main/java/com/zhehekeji/web/lib/joyware/callBackTest/JoywarePlaybackTest.java b/web/src/main/java/com/zhehekeji/web/lib/joyware/callBackTest/JoywarePlaybackTest.java new file mode 100644 index 0000000..d81be02 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/lib/joyware/callBackTest/JoywarePlaybackTest.java @@ -0,0 +1,455 @@ +package com.zhehekeji.web.lib.joyware.callBackTest; + +import com.sun.jna.Pointer; +import com.zhehekeji.web.lib.joyware.CallBack; +import com.zhehekeji.web.lib.joyware.JoywareLoginModuleImpl; +import com.zhehekeji.web.lib.joyware.NetSDKLib; +import com.zhehekeji.web.lib.joyware.NetSDKLib.LLong; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Scanner; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 大华回放测试类(精简版) + * 功能:登录、回放、暂停/恢复、FFmpeg推流 + */ +public class JoywarePlaybackTest { + + // 存储登录句柄:ip -> loginId + private static final ConcurrentHashMap loginIdMap = new ConcurrentHashMap<>(); + + // 存储回放会话:sessionId -> PlaybackSession + private static final ConcurrentHashMap playbackSessions = new ConcurrentHashMap<>(); + + /** + * 回放会话信息 + */ + public static class PlaybackSession { + public String sessionId; + public String ip; + public int channel; + public LocalDateTime startTime; + public LocalDateTime endTime; + public LLong loginId; + public LLong playbackId; + public boolean isPaused; + public LocalDateTime createTime; + + public enum PlaybackStatus { + PLAYING, PAUSED, STOPPED + } + + public PlaybackStatus getStatus() { + if (isPaused) return PlaybackStatus.PAUSED; + return PlaybackStatus.PLAYING; + } + } + + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + + System.out.println("========================================"); + System.out.println(" 大华回放测试工具(精简版)"); + System.out.println("========================================"); + System.out.println(); + + // 初始化SDK + System.out.println("正在初始化大华SDK..."); + JoywareLoginModuleImpl joywareLogin = new JoywareLoginModuleImpl(); + joywareLogin.init( + new CallBack.DisConnectCallBack(), + new CallBack.HaveReConnect() + ); + System.out.println("SDK初始化完成!"); + System.out.println(); + + while (true) { + printMenu(); + + System.out.print("请输入操作编号: "); + String choice = scanner.nextLine().trim(); + + switch (choice) { + case "1": + loginDevice(scanner, joywareLogin); + break; + case "2": + startPlayback(scanner); + break; + case "3": + pauseOrResume(scanner); + break; + case "4": + stopPlayback(scanner); + break; + case "5": + listAllSessions(); + break; + case "0": + cleanup(joywareLogin); + System.out.println("退出程序!"); + return; + default: + System.out.println("无效的编号,请重新输入!"); + break; + } + System.out.println(); + } + } + + /** + * 打印菜单 + */ + private static void printMenu() { + System.out.println("----------------------------------------"); + System.out.println("1. 登录设备"); + System.out.println("2. 开始回放"); + System.out.println("3. 暂停/恢复回放"); + System.out.println("4. 停止回放"); + System.out.println("5. 查看所有会话"); + System.out.println("0. 退出"); + System.out.println("----------------------------------------"); + } + + /** + * 登录设备 + */ + private static void loginDevice(Scanner scanner, JoywareLoginModuleImpl joywareLogin) { + System.out.println(); + System.out.println(">>> 登录设备"); + + System.out.print("设备IP: "); + String ip = scanner.nextLine().trim(); + + System.out.print("设备端口(默认37777): "); + String portStr = scanner.nextLine().trim(); + int port = portStr.isEmpty() ? 37777 : Integer.parseInt(portStr); + + System.out.print("用户名(默认admin): "); + String username = scanner.nextLine().trim(); + if (username.isEmpty()) username = "admin"; + + System.out.print("密码(默认admin123): "); + String password = scanner.nextLine().trim(); + if (password.isEmpty()) password = "admin123"; + + try { + LLong loginId = joywareLogin.login(ip, port, username, password); + + if (loginId != null && loginId.longValue() != 0) { + loginIdMap.put(ip, loginId); + System.out.println("登录成功!"); + System.out.println(" 设备IP: " + ip); + System.out.println(" 登录句柄: " + loginId.longValue()); + } else { + System.out.println("登录失败,请检查IP、端口、用户名密码!"); + } + } catch (Exception e) { + System.out.println("登录异常: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 开始回放 + */ + private static void startPlayback(Scanner scanner) { + System.out.println(); + System.out.println(">>> 开始回放"); + + System.out.print("设备IP: "); + String ip = scanner.nextLine().trim(); + + if (!loginIdMap.containsKey(ip)) { + System.out.println("设备未登录,请先登录!"); + return; + } + + LLong loginId = loginIdMap.get(ip); + + System.out.print("通道号(默认0): "); + String channelStr = scanner.nextLine().trim(); + int channel = channelStr.isEmpty() ? 0 : Integer.parseInt(channelStr); + + System.out.print("开始时间(格式:2024-01-01 00:00:00): "); + String startTimeStr = scanner.nextLine().trim(); + + System.out.print("结束时间(格式:2024-01-01 23:59:59): "); + String endTimeStr = scanner.nextLine().trim(); + + // 检查输入是否为空 + if (startTimeStr.isEmpty()) { + System.out.println("开始时间不能为空!"); + return; + } + if (endTimeStr.isEmpty()) { + System.out.println("结束时间不能为空!"); + return; + } + + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + LocalDateTime startTime = LocalDateTime.parse(startTimeStr, formatter); + LocalDateTime endTime = LocalDateTime.parse(endTimeStr, formatter); + + PlaybackSession session = performStartPlayback( + loginId, + ip, + channel, + startTime, + endTime + ); + + playbackSessions.put(session.sessionId, session); + + System.out.println("回放启动成功!"); + System.out.println(" 会话ID: " + session.sessionId); + System.out.println(" 播放句柄: " + session.playbackId.longValue()); + System.out.println(" 回放时间: " + startTime + " ~ " + endTime); + System.out.println(" 播放状态: " + session.getStatus()); + + System.out.println(); + System.out.println("H.264数据将通过回调推送到FFmpeg,请检查RTSP推流是否正常"); + + } catch (Exception e) { + System.out.println("启动回放失败: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 执行开始回放 + */ + private static PlaybackSession performStartPlayback(LLong loginId, String ip, int channel, + LocalDateTime startTime, LocalDateTime endTime) { + // 准备时间结构 + NetSDKLib.NET_TIME stStartTime = new NetSDKLib.NET_TIME(); + stStartTime.setTime( + startTime.getYear(), + startTime.getMonthValue(), + startTime.getDayOfMonth(), + startTime.getHour(), + startTime.getMinute(), + startTime.getSecond() + ); + + NetSDKLib.NET_TIME stStopTime = new NetSDKLib.NET_TIME(); + stStopTime.setTime( + endTime.getYear(), + endTime.getMonthValue(), + endTime.getDayOfMonth(), + endTime.getHour(), + endTime.getMinute(), + endTime.getSecond() + ); + + // 调用大华SDK开始回放 + // hWnd = null 表示无窗口 + // mp4ReceiveCB 是数据回调,接收H.264数据 + LLong playbackId = JoywareLoginModuleImpl.netsdk.CLIENT_PlayBackByTimeEx( + loginId, // 登录句柄 + channel, // 通道号 + stStartTime, // 开始时间 + stStopTime, // 结束时间 + null, // hWnd = null,无窗口 + null, // 下载进度回调 + null, // 用户数据 + JoywareLoginModuleImpl.mp4ReceiveCB, // 数据回调(关键!) + null // 用户数据 + ); + + if (playbackId == null || playbackId.longValue() == 0) { + throw new RuntimeException("开始回放失败,错误码: " + + JoywareLoginModuleImpl.netsdk.CLIENT_GetLastError()); + } + + // 创建会话 + PlaybackSession session = new PlaybackSession(); + session.sessionId = java.util.UUID.randomUUID().toString(); + session.loginId = loginId; + session.playbackId = playbackId; + session.ip = ip; + session.channel = channel; + session.startTime = startTime; + session.endTime = endTime; + session.isPaused = false; + session.createTime = LocalDateTime.now(); + + return session; + } + + /** + * 暂停或恢复回放 + */ + private static void pauseOrResume(Scanner scanner) { + System.out.println(); + System.out.println(">>> 暂停/恢复回放"); + + System.out.print("会话ID: "); + String sessionId = scanner.nextLine().trim(); + + PlaybackSession session = playbackSessions.get(sessionId); + if (session == null) { + System.out.println("会话不存在!"); + return; + } + + try { + if (session.isPaused) { + // 当前是暂停状态,执行恢复 + performResume(session); + System.out.println("回放已恢复"); + System.out.println(" 会话ID: " + sessionId); + System.out.println(" 播放状态: " + session.getStatus()); + } else { + // 当前是播放状态,执行暂停 + performPause(session); + System.out.println("回放已暂停"); + System.out.println(" 会话ID: " + sessionId); + System.out.println(" 播放状态: " + session.getStatus()); + } + + } catch (Exception e) { + System.out.println("操作失败: " + e.getMessage()); + } + } + + /** + * 执行暂停 + */ + private static void performPause(PlaybackSession session) { + // 调用大华SDK暂停回放 + boolean result = JoywareLoginModuleImpl.netsdk.CLIENT_PausePlayBack( + session.playbackId, + 1 // 1表示暂停 + ); + + if (!result) { + throw new RuntimeException("暂停失败,错误码: " + + JoywareLoginModuleImpl.netsdk.CLIENT_GetLastError()); + } + + session.isPaused = true; + } + + /** + * 执行恢复 + */ + private static void performResume(PlaybackSession session) { + // 调用大华SDK恢复回放 + boolean result = JoywareLoginModuleImpl.netsdk.CLIENT_PausePlayBack( + session.playbackId, + 0 // 0表示恢复 + ); + + if (!result) { + throw new RuntimeException("恢复失败,错误码: " + + JoywareLoginModuleImpl.netsdk.CLIENT_GetLastError()); + } + + session.isPaused = false; + } + + /** + * 停止回放 + */ + private static void stopPlayback(Scanner scanner) { + System.out.println(); + System.out.println(">>> 停止回放"); + + System.out.print("会话ID: "); + String sessionId = scanner.nextLine().trim(); + + PlaybackSession session = playbackSessions.remove(sessionId); + if (session == null) { + System.out.println("会话不存在!"); + return; + } + + try { + performStop(session); + + System.out.println("回放已停止"); + System.out.println(" 会话ID: " + sessionId); + + } catch (Exception e) { + System.out.println("停止失败: " + e.getMessage()); + } + } + + /** + * 执行停止 + */ + private static void performStop(PlaybackSession session) { + // 调用大华SDK停止回放 + boolean result = JoywareLoginModuleImpl.netsdk.CLIENT_StopPlayBack(session.playbackId); + + if (!result) { + throw new RuntimeException("停止失败,错误码: " + + JoywareLoginModuleImpl.netsdk.CLIENT_GetLastError()); + } + + session.isPaused = false; + } + + /** + * 查看所有会话 + */ + private static void listAllSessions() { + System.out.println(); + System.out.println(">>> 所有回放会话"); + + if (playbackSessions.isEmpty()) { + System.out.println("当前没有活动的回放会话"); + return; + } + + System.out.println("----------------------------------------"); + System.out.printf("%-15s %-15s %-10s %-10s%n", "会话ID", "设备IP", "通道", "状态"); + System.out.println("----------------------------------------"); + + for (PlaybackSession session : playbackSessions.values()) { + System.out.printf("%-15s %-15s %-10d %-10s%n", + session.sessionId.substring(0, Math.min(15, session.sessionId.length())), + session.ip, + session.channel, + session.getStatus()); + } + + System.out.println("----------------------------------------"); + System.out.println("总计: " + playbackSessions.size() + " 个会话"); + } + + /** + * 清理资源 + */ + private static void cleanup(JoywareLoginModuleImpl joywareLogin) { + System.out.println(); + System.out.println("正在清理资源..."); + + // 停止所有回放 + for (PlaybackSession session : playbackSessions.values()) { + performStop(session); + } + playbackSessions.clear(); + + // 登出所有设备 + for (String ip : loginIdMap.keySet()) { + LLong loginId = loginIdMap.get(ip); +// boolean result = joywareLogin.logout(loginId); +// if (result) { +// System.out.println("登出设备: " + ip); +// } else { +// System.out.println("登出失败: " + ip); +// } + } + loginIdMap.clear(); + + // 清理SDK + JoywareLoginModuleImpl.cleanup(); + + System.out.println("资源清理完成!"); + } +} diff --git a/web/src/main/java/com/zhehekeji/web/lib/joyware/callBackTest/Mp4ReceivePlayCB.java b/web/src/main/java/com/zhehekeji/web/lib/joyware/callBackTest/Mp4ReceivePlayCB.java new file mode 100644 index 0000000..d588c92 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/lib/joyware/callBackTest/Mp4ReceivePlayCB.java @@ -0,0 +1,69 @@ +package com.zhehekeji.web.lib.joyware.callBackTest; + +import com.sun.jna.Pointer; +import com.zhehekeji.web.lib.joyware.NetSDKLib; + +import javax.security.auth.callback.Callback; +import java.io.IOException; +import java.io.OutputStream; + +// 在 Mp4ReceiveCB.java 中添加FFmpeg推流逻辑 +public class Mp4ReceivePlayCB implements NetSDKLib.fTimeDownLoadPosCallBack { + + private Process ffmpegProcess; + private OutputStream ffmpegStdin; + + public void startFFmpegPush(String streamId) { + try { + // 启动FFmpeg推流到RTSP服务器 + ProcessBuilder pb = new ProcessBuilder( + "D:\\git\\duoji_old\\libs\\ffmpeg\\ffmpeg.exe", + "-re", // 实时读取 + "-i", "pipe:0", // 从stdin读取H.264数据 + "-c:v", "copy", // 不重新编码 + "-f", "rtsp", // 输出RTSP格式 + "rtsp://127.0.0.1:8554/playback/" + streamId + ); + + ffmpegProcess = pb.start(); + ffmpegStdin = ffmpegProcess.getOutputStream(); + + System.out.println("FFmpeg推流已启动: " + streamId); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void invoke(Pointer buffer, int bufferSize, Pointer user) { + // 接收H.264数据 + byte[] h264Data = buffer.getByteArray(0, bufferSize); + + // 写入FFmpeg的stdin + try { + if (ffmpegStdin != null) { + ffmpegStdin.write(h264Data); + ffmpegStdin.flush(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void stopFFmpegPush() { + if (ffmpegProcess != null) { + ffmpegProcess.destroy(); + } + if (ffmpegStdin != null) { + try { + ffmpegStdin.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @Override + public void invoke(NetSDKLib.LLong lPlayHandle, int dwTotalSize, int dwDownLoadSize, int index, NetSDKLib.NET_RECORDFILE_INFO.ByValue recordfileinfo, Pointer dwUser) { + + } +} diff --git a/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/HikSaveImage.java b/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/HikSaveImage.java index d537c25..e2c51bd 100644 --- a/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/HikSaveImage.java +++ b/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/HikSaveImage.java @@ -168,8 +168,6 @@ public class HikSaveImage implements CameraSaveUtil{ public boolean saveImage(String sn, String path,String type) { int nRet = MV_OK; - do - { System.out.println("SDK Version " + MvCameraControl.MV_CC_GetSDKVersion()); // Enuerate GigE and USB devices @@ -194,7 +192,7 @@ public class HikSaveImage implements CameraSaveUtil{ if (MV_OK != nRet) { System.err.printf("Get PayloadSize fail, errcode: [%#x]\n", nRet); - break; + return false; } @@ -211,7 +209,7 @@ public class HikSaveImage implements CameraSaveUtil{ if (MV_OK != nRet) { System.err.printf("GetOneFrameTimeout fail, errcode:[%#x]\n", nRet); - break; + return false; } System.out.println("GetOneFrame: "); @@ -235,16 +233,14 @@ public class HikSaveImage implements CameraSaveUtil{ if (MV_OK != nRet) { System.err.printf("SaveImage fail, errcode: [%#x]\n", nRet); - break; + return false; } // Save buffer content to file saveDataToFile(imageBuffer, imageLen, path); - } while (false); - - return false; + return true; } public void init(List sns,String type){ ArrayList stDeviceList = new ArrayList<>(); diff --git a/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/LxPointCloudSaveImage.java b/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/LxPointCloudSaveImage.java index 5ea9d94..bfe3437 100644 --- a/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/LxPointCloudSaveImage.java +++ b/web/src/main/java/com/zhehekeji/web/service/IndustrialCamera/LxPointCloudSaveImage.java @@ -1,7 +1,9 @@ package com.zhehekeji.web.service.IndustrialCamera; import com.sun.jna.Native; +import com.sun.jna.Pointer; import com.sun.jna.ptr.PointerByReference; +import io.swagger.models.auth.In; import lombok.extern.slf4j.Slf4j; import java.nio.file.Files; @@ -9,153 +11,539 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; @Slf4j public class LxPointCloudSaveImage { + private static final int MAX_RETRY_COUNT = 3; + private static final int RETRY_INTERVAL_MS = 500; + public static DcLibrary INSTANCER = (DcLibrary) Native.loadLibrary((System.getProperty("user.dir"))+"\\libs\\lx\\LxCameraApi.dll", DcLibrary.class); - static Map handleMap = new ConcurrentHashMap<>(); - public static PointerByReference getHandle(String sn,int type ,DcLibrary INSTANCE){ - if(handleMap.get(sn)!=null){ - return handleMap.get(sn); - }else { - // 创建 Pointer 的引用 - PointerByReference handleRef = new PointerByReference(); - - - // 创建 PointerByReference 的实例用于接收设备列表 - // PointerByReference devlistRef = new PointerByReference(); - // 创建 LxDeviceInfo 实例 - DcLibrary.LxDeviceInfo info = new DcLibrary.LxDeviceInfo(); - - //library.DcGetDeviceList( devlistRef,0); - - // 调用 DcOpenDevice 函数 - System.out.println(sn+" "+handleRef+" "+info); - int result = INSTANCE.DcOpenDevice(1, sn, handleRef, info); - int i = 0; - if(result ==0) { - handleMap.put(sn, handleRef); - }else { - for (int ii=0;i<50;i++ ) { - result = INSTANCE.DcOpenDevice(1, sn, handleRef, info); - System.out.println(i + "次尝试;"+"异常:"+result); - try { - Thread.sleep(500); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } + // 设备句柄映射 + static Map handleMap = new ConcurrentHashMap<>(); + + // 设备信息封装类 + static class DeviceInfo { + PointerByReference handleRef; + DcLibrary.LxDeviceInfo deviceInfo; + int type; + long lastUseTime; + boolean streamStarted; // 流是否已启动 + + DeviceInfo(PointerByReference handleRef, DcLibrary.LxDeviceInfo deviceInfo, int type) { + this.handleRef = handleRef; + this.deviceInfo = deviceInfo; + this.type = type; + this.lastUseTime = System.currentTimeMillis(); + this.streamStarted = false; + } + + void updateTime() { + this.lastUseTime = System.currentTimeMillis(); + } + } + + /** + * 获取设备句柄,如果不存在则初始化 + * @param sn 设备序列号 + * @param type 设备类型 + * @param INSTANCE DcLibrary实例 + * @return 设备句柄引用,失败返回null + */ + public static PointerByReference getHandle(String sn, int type, DcLibrary INSTANCE) { + DeviceInfo deviceInfo = handleMap.get(sn); + if (deviceInfo != null) { + deviceInfo.updateTime(); + return deviceInfo.handleRef; + } + + // 设备不存在,进行初始化 + return initDevice(sn, type, INSTANCE); + } + + /** + * 初始化设备连接(启动流并设置命令码) + * @param sn 设备序列号 + * @param type 设备类型 + * @param INSTANCE DcLibrary实例 + * @return 设备句柄引用,失败返回null + */ + public static PointerByReference initDevice(String sn, int type, DcLibrary INSTANCE) { + if (INSTANCE == null) { + INSTANCE = INSTANCER; + } + + // 检查是否已经存在连接 + DeviceInfo existingInfo = handleMap.get(sn); + if (existingInfo != null && existingInfo.handleRef != null && existingInfo.handleRef.getValue() != null) { + log.info("设备 {} 已连接,复用现有连接", sn); + existingInfo.updateTime(); + return existingInfo.handleRef; + } + + // 创建句柄引用 + PointerByReference handleRef = new PointerByReference(); + DcLibrary.LxDeviceInfo info = new DcLibrary.LxDeviceInfo(); + + log.info("开始初始化设备: SN={}, Type={}", sn, type); + + // 尝试打开设备 + int result = openDeviceWithRetry(INSTANCE, sn, handleRef, info, MAX_RETRY_COUNT); + + if (result == 0) { + // 成功打开设备 + DeviceInfo deviceInfo = new DeviceInfo(handleRef, info, type); + handleMap.put(sn, deviceInfo); + log.info("设备 {} 初始化成功, Handle: {}", sn, handleRef.getValue()); + logDeviceInfo(info); + + // 立即启动流 + result = INSTANCE.DcStartStream(handleRef.getValue()); + if (result == 0) { + deviceInfo.streamStarted = true; + log.info("设备 {} 流启动成功", sn); + } else { + log.warn("设备 {} 流启动失败,错误码: {}", sn, result); } - // 输出结果 - System.out.println("Result: " + result); - System.out.println("Handle: " + handleRef.getValue()); // 获取 DcHandle 的值 - System.out.println("Device Type: " + info.dev_type); - System.out.println("ID: " + new String(info.id).trim()); - System.out.println("IP: " + new String(info.ip).trim()); - System.out.println("SN: " + new String(info.sn).trim()); - System.out.println("MAC: " + new String(info.mac).trim()); - System.out.println("Firmware Version: " + new String(info.firmware_ver).trim()); - System.out.println("Algorithm Version: " + new String(info.algor_ver).trim()); - System.out.println("Name: " + new String(info.name).trim()); - System.out.println("Reserve: " + new String(info.reserve).trim()); - System.out.println("Reserve2: " + new String(info.reserve2).trim()); - System.out.println("Reserve3: " + new String(info.reserve3).trim()); - System.out.println("Reserve4: " + new String(info.reserve4).trim());//lxPointCloudApi.DcCloseDevice(handle); + // 设置命令码 + result = INSTANCE.DcSetCmd(handleRef.getValue(), 5001); + if (result == 0 || result == 25) { + log.info("设备 {} 命令码设置成功(5001)", sn); + } else { + log.warn("设备 {} 命令码设置失败,错误码: {}", sn, result); + } return handleRef; + } else { + log.error("设备 {} 初始化失败,错误码: {}", sn, result); + return null; } + } + + /** + * 带重试的设备打开方法 + * @param INSTANCE DcLibrary实例 + * @param sn 设备序列号 + * @param handleRef 句柄引用 + * @param info 设备信息 + * @param maxRetries 最大重试次数 + * @return 错误码,0表示成功 + */ + private static int openDeviceWithRetry(DcLibrary INSTANCE, String sn, + PointerByReference handleRef, DcLibrary.LxDeviceInfo info, int maxRetries) { + int result = INSTANCE.DcOpenDevice(1, sn, handleRef, info); + log.debug("设备 {} 首次打开结果: {}", sn, result); + if (result == 0) { + return result; + } + + // 首次失败,进行重试 + AtomicInteger retryCount = new AtomicInteger(1); + while (retryCount.get() <= maxRetries) { + try { + log.info("设备 {} 打开失败,第 {} 次重试,错误码: {}", sn, retryCount.get(), result); + Thread.sleep(RETRY_INTERVAL_MS); + result = INSTANCE.DcOpenDevice(1, sn, handleRef, info); + if (result == 0) { + log.info("设备 {} 第 {} 次重试成功", sn, retryCount.get()); + return result; + } + retryCount.incrementAndGet(); + } catch (InterruptedException e) { + log.error("设备 {} 重试被中断", sn, e); + Thread.currentThread().interrupt(); + return result; + } + } + + log.error("设备 {} 重试 {} 次后仍然失败", sn, maxRetries); + return result; } - public static PointerByReference init(String sn){ - - if(handleMap.get(sn)!=null){ - return handleMap.get(sn); - }else { - // 创建 Pointer 的引用 - PointerByReference handleRef = new PointerByReference(); - - - // 创建 PointerByReference 的实例用于接收设备列表 - // PointerByReference devlistRef = new PointerByReference(); - // 创建 LxDeviceInfo 实例 - DcLibrary.LxDeviceInfo info = new DcLibrary.LxDeviceInfo(); - - //library.DcGetDeviceList( devlistRef,0); - - // 调用 DcOpenDevice 函数 - System.out.println(sn+" "+handleRef+" "+info); - int result = INSTANCER.DcOpenDevice(1, sn, handleRef, info); - int i = 0; - if(result ==0) { - handleMap.put(sn, handleRef); - }else { - for (int ii=0;i<50;i++ ) { - result = INSTANCER.DcOpenDevice(1, sn, handleRef, info); - System.out.println(i + "次尝试;"+"异常:"+result); - try { - Thread.sleep(500); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + + /** + * 重新连接设备(重新设置句柄,并重新启动流和设置命令码) + * @param sn 设备序列号 + * @param type 设备类型 + * @return true表示重连成功,false表示失败 + */ + public static boolean reconnect(String sn, int type) { + log.info("开始重连设备(重新设置句柄): SN={}", sn); + + DeviceInfo existingInfo = handleMap.get(sn); + + // 获取现有的句柄引用 + PointerByReference handleRef; + if (existingInfo != null && existingInfo.handleRef != null) { + // 先停止现有的流 + if (existingInfo.streamStarted) { + Pointer oldHandle = existingInfo.handleRef.getValue(); + if (oldHandle != null) { + INSTANCER.DcStopStream(oldHandle); + log.debug("设备 {} 停止旧流", sn); } } - // 输出结果 - System.out.println("Result: " + result); - System.out.println("Handle: " + handleRef.getValue()); // 获取 DcHandle 的值 - System.out.println("Device Type: " + info.dev_type); - System.out.println("ID: " + new String(info.id).trim()); - System.out.println("IP: " + new String(info.ip).trim()); - System.out.println("SN: " + new String(info.sn).trim()); - System.out.println("MAC: " + new String(info.mac).trim()); - System.out.println("Firmware Version: " + new String(info.firmware_ver).trim()); - System.out.println("Algorithm Version: " + new String(info.algor_ver).trim()); - System.out.println("Name: " + new String(info.name).trim()); - System.out.println("Reserve: " + new String(info.reserve).trim()); - System.out.println("Reserve2: " + new String(info.reserve2).trim()); - System.out.println("Reserve3: " + new String(info.reserve3).trim()); - System.out.println("Reserve4: " + new String(info.reserve4).trim());//lxPointCloudApi.DcCloseDevice(handle); + handleRef = existingInfo.handleRef; + log.info("复用现有句柄引用: {}", handleRef); + } else { + // 创建新的句柄引用 + handleRef = new PointerByReference(); + log.info("创建新句柄引用: {}", handleRef); + } + DcLibrary.LxDeviceInfo info = new DcLibrary.LxDeviceInfo(); - return handleRef; + // 尝试重新打开设备,设置新的句柄 + int result = openDeviceWithRetry(INSTANCER, sn, handleRef, info, MAX_RETRY_COUNT); + + if (result == 0) { + // 更新设备信息 + DeviceInfo deviceInfo = new DeviceInfo(handleRef, info, type); + handleMap.put(sn, deviceInfo); + log.info("设备 {} 重连成功,新句柄: {}", sn, handleRef.getValue()); + logDeviceInfo(info); + + // 重新启动流 + result = INSTANCER.DcStartStream(handleRef.getValue()); + if (result == 0) { + deviceInfo.streamStarted = true; + log.info("设备 {} 重连后流启动成功", sn); + } else { + log.warn("设备 {} 重连后流启动失败,错误码: {}", sn, result); + } + + // 重新设置命令码 + result = INSTANCER.DcSetCmd(handleRef.getValue(), 5001); + if (result == 0 || result == 25) { + log.info("设备 {} 重连后命令码设置成功(5001)", sn); + } else { + log.warn("设备 {} 重连后命令码设置失败,错误码: {}", sn, result); + } + + return true; + } else { + log.error("设备 {} 重连失败,错误码: {}", sn, result); + return false; } + } + public static boolean reDevice(String sn) { + closeDevice(sn); + return reconnect(sn, 1); } - public static boolean saveImage(String sn, String path,int type) { - DcLibrary INSTANCE; - INSTANCE = INSTANCER; - int result = 0; - PointerByReference handleRef = getHandle(sn,type,INSTANCE); - result = INSTANCE.DcStartStream(handleRef.getValue()); - System.out.println("Result: " + result); - result = INSTANCE.DcSetCmd(handleRef.getValue(),5001); + /** + * 关闭指定设备连接(停止流后关闭设备) + * @param sn 设备序列号 + * @return true表示关闭成功,false表示失败 + */ + public static boolean closeDevice(String sn) { + DeviceInfo deviceInfo = handleMap.get(sn); + if (deviceInfo == null || deviceInfo.handleRef == null) { + log.warn("设备 {} 未找到连接,无需关闭", sn); + return true; + } + try { + Pointer handle = deviceInfo.handleRef.getValue(); + if (handle != null) { + // 先停止流 + if (deviceInfo.streamStarted) { + int stopResult = INSTANCER.DcStopStream(handle); + log.info("设备 {} 停止流结果: {}", sn, stopResult); + deviceInfo.streamStarted = false; + } - System.out.println("Result: " + result); - Path path1 = Paths.get(path); + // 关闭设备 + int closeResult = INSTANCER.DcCloseDevice(handle); + log.info("设备 {} 关闭结果: {}", sn, closeResult); + + // 从映射中移除 + handleMap.remove(sn); + + return closeResult == 0; + } + } catch (Exception e) { + log.error("设备 {} 关闭时发生异常", sn, e); + } + + handleMap.remove(sn); + return false; + } + + /** + * 关闭所有设备连接 + */ + public static void closeAllDevices() { + log.info("开始关闭所有设备连接"); + handleMap.keySet().forEach(sn -> { + closeDevice(sn); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + log.info("所有设备连接已关闭"); + } + + /** + * 检查设备连接是否有效 + * @param sn 设备序列号 + * @return true表示连接有效,false表示无效 + */ + public static boolean isDeviceConnected(String sn) { + DeviceInfo deviceInfo = handleMap.get(sn); + if (deviceInfo == null || deviceInfo.handleRef == null) { + return false; + } + return deviceInfo.handleRef.getValue() != null; + } + + /** + * 获取设备信息 + * @param sn 设备序列号 + * @return 设备信息,不存在返回null + */ + public static DcLibrary.LxDeviceInfo getDeviceInfo(String sn) { + DeviceInfo deviceInfo = handleMap.get(sn); + return deviceInfo != null ? deviceInfo.deviceInfo : null; + } + /** + * 记录设备信息 + * @param info 设备信息 + */ + private static void logDeviceInfo(DcLibrary.LxDeviceInfo info) { + log.info("设备详细信息:"); + log.info(" 设备类型: {}", info.dev_type); + log.info(" ID: {}", new String(info.id).trim()); + log.info(" IP: {}", new String(info.ip).trim()); + log.info(" SN: {}", new String(info.sn).trim()); + log.info(" MAC: {}", new String(info.mac).trim()); + log.info(" 固件版本: {}", new String(info.firmware_ver).trim()); + log.info(" 算法版本: {}", new String(info.algor_ver).trim()); + log.info(" 设备名称: {}", new String(info.name).trim()); + } + + /** + * 保存点云图像(流已在初始化时启动) + * @param sn 设备序列号 + * @param path 保存路径 + * @param type 设备类型 + * @return 0表示成功,负数表示失败 + */ + public static Integer saveImage(String sn, String path, int type) { + DcLibrary INSTANCE = INSTANCER; + + // 获取设备句柄 + PointerByReference handleRef = getHandle(sn, type, INSTANCE); + if (handleRef == null) { + log.error("设备 {} 获取句柄失败", sn); + return -3; + } + + // 检查流是否已启动 + DeviceInfo deviceInfo = handleMap.get(sn); + if (deviceInfo != null && !deviceInfo.streamStarted) { + log.warn("设备 {} 流未启动,尝试启动流", sn); + int result = INSTANCE.DcStartStream(handleRef.getValue()); + if (result == 0) { + deviceInfo.streamStarted = true; + log.info("设备 {} 流启动成功", sn); + + // 设置命令码 + result = INSTANCE.DcSetCmd(handleRef.getValue(), 5001); + if (result != 0 && result != 25) { + log.error("设备 {} 设置命令失败,错误码: {}", sn, result); + return -3; + } + } else { + log.error("设备 {} 流启动失败,错误码: {}", sn, result); + return -3; + } + } + + // 创建保存目录 + Path path1 = Paths.get(path); try { - // 如果路径不存在,则创建目录 Files.createDirectories(path1.getParent()); - }catch (Exception e){ - log.info("路径失败"); + } catch (Exception e) { + log.error("设备 {} 创建目录失败: {}", sn, e.getMessage()); + return -2; } - int i = INSTANCE.DcSaveXYZ(handleRef.getValue(), path); - log.info(sn+" 3dCamera get pcd :"+path +";rest:"+i); - System.out.println(sn+" 3dCamera get pcd :"+path +";rest:"+i); - result = INSTANCE.DcStopStream(handleRef.getValue()); - System.out.println("stop Stream Result: " + result); + // 保存点云数据 + int saveResult = INSTANCE.DcSaveXYZ(handleRef.getValue(), path); + log.info("设备 {} 3dCamera save pcd: {}, result: {}", sn, path, saveResult); - return true; + if (saveResult != 0 && saveResult != 25) { + log.error("设备 {} 保存点云失败,错误码: {}", sn, saveResult); + return -1; + } + + // 不再每次都停止流,流保持运行状态 + log.info("设备 {} 保存成功", sn); + return 0; + } + + /** + * 保存点云图像(带循环拍照功能,失败自动重连) + * @param sn 设备序列号 + * @param path 保存路径 + * @param type 设备类型 + * @param maxCount 最大拍照次数,默认10次 + * @return 0表示成功,-3表示获取句柄失败,-2表示创建目录失败,-1表示保存点云失败 + */ + public static Integer saveImageWithRetry(String sn, String path, int type, int maxCount) { + if (maxCount <= 0) { + maxCount = 5; + } + + log.info("设备 {} 开始循环拍照,最多 {} 次,保存路径: {}", sn, maxCount, path); + + int retryCount = 0; + Integer lastError = -3; + + while (retryCount < maxCount) { + try { + retryCount++; + log.info("设备 {} 第 {} 次拍照尝试", sn, retryCount); + + // 获取设备句柄 + PointerByReference handleRef = getHandle(sn, type, INSTANCER); + if (handleRef == null) { + log.warn("设备 {} 第 {} 次拍照获取句柄失败,尝试重新设置句柄", sn, retryCount); + lastError = -3; + + // 尝试重新设置句柄 + boolean reconnectSuccess = reDevice(sn); + if (reconnectSuccess) { + log.info("设备 {} 重新设置句柄成功,继续拍照", sn); + continue; + } else { + log.error("设备 {} 重新设置句柄失败", sn); + if (retryCount < maxCount) { + Thread.sleep(RETRY_INTERVAL_MS); + } + continue; + } + } + + // 检查流状态 + DeviceInfo deviceInfo = handleMap.get(sn); + if (deviceInfo == null || !deviceInfo.streamStarted) { + log.warn("设备 {} 流未启动,重新初始化", sn); + boolean reconnectSuccess = reDevice(sn); + if (reconnectSuccess) { + continue; + } + } + + // 创建保存目录 + Path path1 = Paths.get(path); + try { + Files.createDirectories(path1.getParent()); + } catch (Exception e) { + log.error("设备 {} 创建目录失败: {}", sn, e.getMessage()); + lastError = -2; + if (retryCount < maxCount) { + Thread.sleep(RETRY_INTERVAL_MS); + } + continue; + } + + int result = INSTANCER.DcSetCmd(handleRef.getValue(), 5001); + log.info("设备 {} 3dCamera set cmd: {}, result: {}", sn, 5001, result); + // 保存点云数据 + int saveResult = INSTANCER.DcSaveXYZ(handleRef.getValue(), path); + log.info("设备 {} 3dCamera save pcd: {}, result: {}", sn, path, saveResult); + + if (saveResult != 0 && saveResult != 25) { + log.warn("设备 {} 第 {} 次拍照保存点云失败,错误码: {},尝试重新设置句柄", sn, retryCount, saveResult); + lastError = -1; + + // 尝试重新设置句柄 + boolean reconnectSuccess = reconnect(sn, type); + if (reconnectSuccess) { + log.info("设备 {} 重新设置句柄成功,继续拍照", sn); + continue; + } else { + log.error("设备 {} 重新设置句柄失败", sn); + if (retryCount < maxCount) { + Thread.sleep(RETRY_INTERVAL_MS); + } + continue; + } + } + + // 拍照成功 + log.info("设备 {} 第 {} 次拍照成功", sn, retryCount); + return 0; + + } catch (InterruptedException e) { + log.error("设备 {} 拍照重试被中断", sn, e); + Thread.currentThread().interrupt(); + return lastError; + } catch (Exception e) { + log.error("设备 {} 第 {} 次拍照发生异常", sn, retryCount, e); + lastError = -3; + + try { + boolean reconnectSuccess = reconnect(sn, type); + if (reconnectSuccess) { + log.info("设备 {} 异常后重新设置句柄成功,继续拍照", sn); + continue; + } + } catch (Exception ex) { + log.error("设备 {} 异常后重连失败", sn, ex); + } + + if (retryCount < maxCount) { + try { + Thread.sleep(RETRY_INTERVAL_MS); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + return lastError; + } + } + } + } + + log.error("设备 {} 重试 {} 次后拍照失败,最后错误码: {}", sn, maxCount, lastError); + return lastError; + } + /** + * 保存点云图像(带循环拍照功能,默认最多10次) + * @param sn 设备序列号 + * @param path 保存路径 + * @param type 设备类型 + * @return 0表示成功,-3表示获取句柄失败,-2表示创建目录失败,-1表示保存点云失败 + */ + public static Integer saveImageWithRetry(String sn, String path, int type) { + return saveImageWithRetry(sn, path, type, 5); } public static void main(String[] args) { - saveImage("1","E:\\1-8-2-R.pcd",1); + // 测试原始单次保存 + // saveImage("1", "E:\\1-8-2-R.pcd", 1); + // saveImage("2", "E:\\12-R.pcd", 2); + + // 测试带循环拍照的保存(默认最多10次) + Integer result = saveImageWithRetry("1", "E:\\test-retry.pcd", 1); + log.info("最终结果: {} (0=成功, -3=句柄失败, -2=目录失败, -1=保存失败)", result); + + // 测试带循环拍照的保存(自定义最大次数) + // Integer result = saveImageWithRetry("1", "E:\\test-retry.pcd", 1, 5); + + // 测试重连 + // boolean reconnectResult = reconnect("1", 1); + // log.info("重连结果: {}", reconnectResult); - saveImage("2","E:\\12-R.pcd",2); + // 测试关闭所有设备 + // closeAllDevices(); } } diff --git a/web/src/main/java/com/zhehekeji/web/service/PlcService.java b/web/src/main/java/com/zhehekeji/web/service/PlcService.java index 6e7f62d..cc1c621 100644 --- a/web/src/main/java/com/zhehekeji/web/service/PlcService.java +++ b/web/src/main/java/com/zhehekeji/web/service/PlcService.java @@ -480,12 +480,13 @@ public class PlcService { HttpHeaders headers = new HttpHeaders(); headers.set("Content-Type", "application/json"); headers.set("User-Agent", "Mozilla/5.0"); + log.info("scTransmission:{}",scTransmission); // 创建 HttpEntity 对象 HttpEntity entity = new HttpEntity<>(scTransmission, headers); // Map map = configProperties.getTemplate().stream().collect(Collectors.toMap(k->k.getCode(), v->v)); - + log.info("发送站台盘点:"+scTransmission.toString()); // 发送 POST 请求 ResponseEntity> response = restTemplate.exchange( "http://" + configProperties.getSiteInventoryIp() + ":8099" + "/api/industrialCamera/siteInventory", @@ -493,6 +494,7 @@ public class PlcService { entity, new ParameterizedTypeReference>() {} ); + log.info("请求成功,响应:{}", response.getBody()); if (response.getBody()!=null && Objects.requireNonNull(response.getBody()).getData().getFlag()==1){ scTransmission.setWmsTrayCode(dataInfo.getTrayCode()); @@ -511,6 +513,7 @@ public class PlcService { }else scTransmission.setTrayCode(""); scTransmission.setCount(0); + scTransmission.setFlag(response.getBody().getData().getFlag()); kuKouService.setHttp(scTransmission,flag); return i; } diff --git a/web/src/main/java/com/zhehekeji/web/service/algorithm/InventoryService.java b/web/src/main/java/com/zhehekeji/web/service/algorithm/InventoryService.java index e70f1c5..766ad9a 100644 --- a/web/src/main/java/com/zhehekeji/web/service/algorithm/InventoryService.java +++ b/web/src/main/java/com/zhehekeji/web/service/algorithm/InventoryService.java @@ -26,6 +26,8 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import static com.zhehekeji.web.service.IndustrialCamera.LxPointCloudSaveImage.INSTANCER; + @Service @Slf4j public class InventoryService { @@ -69,7 +71,8 @@ public class InventoryService { public void init(){ if (configProperties.getCameraConfig()!=null && configProperties.getCameraConfig().getCamera3D()!=null && !configProperties.getCameraConfig().getCamera3D().equals("")) { - PointerByReference handleRef = LxPointCloudSaveImage.init(configProperties.getCameraConfig().getCamera3D()); + DcLibrary INSTANCE = INSTANCER; + PointerByReference handleRef = LxPointCloudSaveImage.initDevice(configProperties.getCameraConfig().getCamera3D(), 1, INSTANCE); } } public int match3D(String category,String path){ @@ -84,10 +87,20 @@ public class InventoryService { public int match3D(String category,String path,String configPath){ //拍照 List results = new ArrayList<>(); + if(!pingDevice(configProperties.getCameraConfig().getCamera3D())){ + return -1; + } + log.info("3D拍照pcd: "+path); for (int i = 0; i < 3; i++) { String currentPath = path.replace(".pcd", "_" + i + ".pcd"); // 添加索引以区分不同次拍照的文件路径 - LxPointCloudSaveImage.saveImage(configProperties.getCameraConfig().getCamera3D(), currentPath, 1); + int picResult = LxPointCloudSaveImage.saveImageWithRetry(configProperties.getCameraConfig().getCamera3D(), currentPath, 1); + if(picResult != 0 && picResult!=25){ +// 没有成功拍照 + log.error("3D拍照失败,相机取流未成功拍照:"+picResult); + return -2; + } + log.info("3D拍照成功 :"+picResult); Integer result = PointCloudProcessor.getBaijiuBox( currentPath, @@ -107,14 +120,11 @@ public class InventoryService { return results.get(2); } - - - - try { + try { Thread.sleep(300*(i+1)); // 间隔300ms - } catch (InterruptedException e) { + } catch (InterruptedException e) { log.error("线程休眠异常", e); - } + } } return results.get(results.size() - 1); @@ -150,8 +160,84 @@ public class InventoryService { return list; } - public static void main(String[] args) { + /** + * Ping设备IP测试连通性(过滤TTL传输过期等非正常连接情况) + * @param ip 设备IP地址 + * @return true表示可连通,false表示不可连通 + */ + private boolean pingDevice(String ip) { + try { + log.info("开始Ping设备IP: {}", ip); + + // 根据操作系统选择Ping命令 + boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win"); + String command; + int timeout = 2000; // 超时时间2秒 + + if (isWindows) { + command = String.format("ping -n 2 -w %d %s", timeout, ip); + } else { + command = String.format("ping -c 2 -W %d %s", timeout / 1000, ip); + } + + Process process = Runtime.getRuntime().exec(command); + + // 读取输出以检查是否有TTL过期等异常 + java.io.BufferedReader reader = new java.io.BufferedReader( + new java.io.InputStreamReader(process.getInputStream(), "GBK") + ); + java.io.BufferedReader errorReader = new java.io.BufferedReader( + new java.io.InputStreamReader(process.getErrorStream(), "GBK") + ); + + StringBuilder output = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append("\n"); + } + while ((line = errorReader.readLine()) != null) { + output.append(line).append("\n"); + } + + int exitCode = process.waitFor(); + + // 检查输出中的关键字 + String outputStr = output.toString().toLowerCase(); + log.debug("Ping输出: {}", outputStr); + + // 检查是否包含TTL过期、超时等异常信息 + boolean hasError = outputStr.contains("timed out") || + outputStr.contains("传输失败") || + outputStr.contains("请求超时") || + outputStr.contains("无法访问目标主机") || + outputStr.contains("destination host unreachable") || + outputStr.contains("100% loss"); + + // 检查是否有成功的回复(Windows: 字节数=32 时间 map = new HashMap<>(); - findPcdFiles(directory,map); - for (Integer count : map.keySet()) { - System.out.print(count +" "); - try { - Files.copy(Paths.get(map.get(count)), Paths.get("E:\\192.168.40.11.pcd"), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - throw new RuntimeException(e); - } - - System.out.println(1111); - int i = INSTANCER.cameraapi("E:\\192.168.40.11.pcd","E:\\2.json",3); - System.out.println(i); -// System.out.println(similarity); - } - - } -} diff --git a/web/src/main/resources/application-prod.yml b/web/src/main/resources/application-prod.yml index 332921d..bf48e37 100644 --- a/web/src/main/resources/application-prod.yml +++ b/web/src/main/resources/application-prod.yml @@ -73,7 +73,7 @@ savePath: # ------------服务端类型 0:TCP(罗伯泰克) 1:KSEC(JSON)(昆船) serverMode: 1 ksec: - port: 3000 + port: 3010 ip: 127.0.0.1 platformIp: 127.0.0.1 platformPort: 3000