Browse Source

Merge pull request #741 from gaofuwang/wvp-28181-2.0

修复WVP作为下级平台接收上级平台DeviceControl、RecordInfo信令处理问题和Alarm信令上报上级平台问题
648540858 2 years ago
parent
commit
03ee15ece2
21 changed files with 831 additions and 121 deletions
  1. 77 0
      src/main/java/com/genersoft/iot/vmp/common/enums/DeviceControlType.java
  2. 13 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java
  3. 14 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarmMethod.java
  4. 143 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DragZoomRequest.java
  5. 94 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/HomePositionRequest.java
  6. 11 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java
  7. 30 7
      src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java
  8. 6 1
      src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java
  9. 53 49
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java
  10. 14 12
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  11. 11 8
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  12. 244 30
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java
  13. 4 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java
  14. 4 4
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java
  15. 17 0
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/MessageElement.java
  16. 78 1
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
  17. 1 1
      src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java
  18. 3 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformChannelMapper.java
  19. 1 1
      src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
  20. 9 0
      src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
  21. 4 4
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java

+ 77 - 0
src/main/java/com/genersoft/iot/vmp/common/enums/DeviceControlType.java

@@ -0,0 +1,77 @@
+package com.genersoft.iot.vmp.common.enums;
+
+import org.dom4j.Element;
+import org.springframework.util.ObjectUtils;
+
+
+/**
+ * @author gaofuwang
+ * @date 2023/01/18/ 10:09:00
+ * @since 1.0
+ */
+public enum DeviceControlType {
+
+    /**
+     * 云台控制
+     * 上下左右,预置位,扫描,辅助功能,巡航
+     */
+    PTZ("PTZCmd","云台控制"),
+    /**
+     * 远程启动
+     */
+    TELE_BOOT("TeleBoot","远程启动"),
+    /**
+     * 录像控制
+     */
+    RECORD("RecordCmd","录像控制"),
+    /**
+     * 布防撤防
+     */
+    GUARD("GuardCmd","布防撤防"),
+    /**
+     * 告警控制
+     */
+    ALARM("AlarmCmd","告警控制"),
+    /**
+     * 强制关键帧
+     */
+    I_FRAME("IFameCmd","强制关键帧"),
+    /**
+     * 拉框放大
+     */
+    DRAG_ZOOM_IN("DragZoomIn","拉框放大"),
+    /**
+     * 拉框缩小
+     */
+    DRAG_ZOOM_OUT("DragZoomOut","拉框缩小"),
+    /**
+     * 看守位
+     */
+    HOME_POSITION("HomePosition","看守位");
+
+    private final String val;
+
+    private final String desc;
+
+    DeviceControlType(String val, String desc) {
+        this.val = val;
+        this.desc = desc;
+    }
+
+    public String getVal() {
+        return val;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public static DeviceControlType typeOf(Element rootElement) {
+        for (DeviceControlType item : DeviceControlType.values()) {
+            if (!ObjectUtils.isEmpty(rootElement.element(item.val)) || !ObjectUtils.isEmpty(rootElement.elements(item.val))) {
+                return item;
+            }
+        }
+        return null;
+    }
+}

+ 13 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/bean/AlarmChannelMessage.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
+
 /**
  * 通过redis分发报警消息
  */
@@ -8,12 +9,14 @@ public class AlarmChannelMessage {
      * 国标编号
      */
     private String gbId;
-
     /**
      * 报警编号
      */
     private int alarmSn;
-
+    /**
+     * 告警类型
+     */
+    private int alarmType;
 
     /**
      * 报警描述
@@ -36,6 +39,14 @@ public class AlarmChannelMessage {
         this.alarmSn = alarmSn;
     }
 
+    public int getAlarmType() {
+        return alarmType;
+    }
+
+    public void setAlarmType(int alarmType) {
+        this.alarmType = alarmType;
+    }
+
     public String getAlarmDescription() {
         return alarmDescription;
     }

+ 14 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarmMethod.java

@@ -37,4 +37,18 @@ public enum DeviceAlarmMethod {
     public int getVal() {
         return val;
     }
+
+    /**
+     * 查询是否匹配类型
+     * @param code
+     * @return
+     */
+    public static DeviceAlarmMethod typeOf(int code) {
+        for (DeviceAlarmMethod item : DeviceAlarmMethod.values()) {
+            if (code==item.getVal()) {
+                return item;
+            }
+        }
+        return null;
+    }
 }

+ 143 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DragZoomRequest.java

@@ -0,0 +1,143 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+import com.genersoft.iot.vmp.gb28181.utils.MessageElement;
+
+/**
+ * 设备信息查询响应
+ *
+ * @author Y.G
+ * @version 1.0
+ * @date 2022/6/28 14:55
+ */
+public class DragZoomRequest {
+    /**
+     * 序列号
+     */
+    @MessageElement("SN")
+    private String sn;
+
+    @MessageElement("DeviceID")
+    private String deviceId;
+
+    @MessageElement(value = "DragZoomIn")
+    private DragZoom dragZoomIn;
+
+    @MessageElement(value = "DragZoomOut")
+    private DragZoom dragZoomOut;
+
+    /**
+     * 基本参数
+     */
+    public static class DragZoom {
+        /**
+         * 播放窗口长度像素值
+         */
+        @MessageElement("Length")
+        protected Integer length;
+        /**
+         * 播放窗口宽度像素值
+         */
+        @MessageElement("Width")
+        protected Integer width;
+        /**
+         * 拉框中心的横轴坐标像素值
+         */
+        @MessageElement("MidPointX")
+        protected Integer midPointX;
+        /**
+         * 拉框中心的纵轴坐标像素值
+         */
+        @MessageElement("MidPointY")
+        protected Integer midPointY;
+        /**
+         * 拉框长度像素值
+         */
+        @MessageElement("LengthX")
+        protected Integer lengthX;
+        /**
+         * 拉框宽度像素值
+         */
+        @MessageElement("LengthY")
+        protected Integer lengthY;
+
+        public Integer getLength() {
+            return length;
+        }
+
+        public void setLength(Integer length) {
+            this.length = length;
+        }
+
+        public Integer getWidth() {
+            return width;
+        }
+
+        public void setWidth(Integer width) {
+            this.width = width;
+        }
+
+        public Integer getMidPointX() {
+            return midPointX;
+        }
+
+        public void setMidPointX(Integer midPointX) {
+            this.midPointX = midPointX;
+        }
+
+        public Integer getMidPointY() {
+            return midPointY;
+        }
+
+        public void setMidPointY(Integer midPointY) {
+            this.midPointY = midPointY;
+        }
+
+        public Integer getLengthX() {
+            return lengthX;
+        }
+
+        public void setLengthX(Integer lengthX) {
+            this.lengthX = lengthX;
+        }
+
+        public Integer getLengthY() {
+            return lengthY;
+        }
+
+        public void setLengthY(Integer lengthY) {
+            this.lengthY = lengthY;
+        }
+    }
+
+    public String getSn() {
+        return sn;
+    }
+
+    public void setSn(String sn) {
+        this.sn = sn;
+    }
+
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public DragZoom getDragZoomIn() {
+        return dragZoomIn;
+    }
+
+    public void setDragZoomIn(DragZoom dragZoomIn) {
+        this.dragZoomIn = dragZoomIn;
+    }
+
+    public DragZoom getDragZoomOut() {
+        return dragZoomOut;
+    }
+
+    public void setDragZoomOut(DragZoom dragZoomOut) {
+        this.dragZoomOut = dragZoomOut;
+    }
+}

+ 94 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/HomePositionRequest.java

@@ -0,0 +1,94 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+import com.genersoft.iot.vmp.gb28181.utils.MessageElement;
+
+/**
+ * 设备信息查询响应
+ *
+ * @author Y.G
+ * @version 1.0
+ * @date 2022/6/28 14:55
+ */
+public class HomePositionRequest {
+    /**
+     * 序列号
+     */
+    @MessageElement("SN")
+    private String sn;
+
+    @MessageElement("DeviceID")
+    private String deviceId;
+
+    @MessageElement(value = "HomePosition")
+    private HomePosition homePosition;
+
+
+    /**
+     * 基本参数
+     */
+    public static class HomePosition {
+        /**
+         * 播放窗口长度像素值
+         */
+        @MessageElement("Enabled")
+        protected String enabled;
+        /**
+         * 播放窗口宽度像素值
+         */
+        @MessageElement("ResetTime")
+        protected String resetTime;
+        /**
+         * 拉框中心的横轴坐标像素值
+         */
+        @MessageElement("PresetIndex")
+        protected String presetIndex;
+
+        public String getEnabled() {
+            return enabled;
+        }
+
+        public void setEnabled(String enabled) {
+            this.enabled = enabled;
+        }
+
+        public String getResetTime() {
+            return resetTime;
+        }
+
+        public void setResetTime(String resetTime) {
+            this.resetTime = resetTime;
+        }
+
+        public String getPresetIndex() {
+            return presetIndex;
+        }
+
+        public void setPresetIndex(String presetIndex) {
+            this.presetIndex = presetIndex;
+        }
+    }
+
+    public String getSn() {
+        return sn;
+    }
+
+    public void setSn(String sn) {
+        this.sn = sn;
+    }
+
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public HomePosition getHomePosition() {
+        return homePosition;
+    }
+
+    public void setHomePosition(HomePosition homePosition) {
+        this.homePosition = homePosition;
+    }
+}

+ 11 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
+
 import java.time.Instant;
 import java.util.List;
 
@@ -20,6 +21,8 @@ public class RecordInfo {
 	
 	private int sumNum;
 
+	private int count;
+
 	private Instant lastTime;
 	
 	private List<RecordItem> recordList;
@@ -79,4 +82,12 @@ public class RecordInfo {
 	public void setLastTime(Instant lastTime) {
 		this.lastTime = lastTime;
 	}
+
+	public int getCount() {
+		return count;
+	}
+
+	public void setCount(int count) {
+		this.count = count;
+	}
 }

+ 30 - 7
src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java

@@ -1,8 +1,10 @@
 package com.genersoft.iot.vmp.gb28181.event.record;
 
 import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
+import com.genersoft.iot.vmp.utils.redis.RedisUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationListener;
 import org.springframework.stereotype.Component;
 
@@ -20,25 +22,46 @@ public class RecordEndEventListener implements ApplicationListener<RecordEndEven
 
     private final static Logger logger = LoggerFactory.getLogger(RecordEndEventListener.class);
 
+    private Map<String, RecordEndEventHandler> handlerMap = new ConcurrentHashMap<>();
     public interface RecordEndEventHandler{
         void  handler(RecordInfo recordInfo);
     }
 
-    private Map<String, RecordEndEventHandler> handlerMap = new ConcurrentHashMap<>();
-
     @Override
     public void onApplicationEvent(RecordEndEvent event) {
-        logger.info("录像查询完成事件触发,deviceId:{}, channelId: {}, 录像数量{}条", event.getRecordInfo().getDeviceId(),
-                event.getRecordInfo().getChannelId(), event.getRecordInfo().getSumNum() );
+        String deviceId = event.getRecordInfo().getDeviceId();
+        String channelId = event.getRecordInfo().getChannelId();
+        int count = event.getRecordInfo().getCount();
+        int sumNum = event.getRecordInfo().getSumNum();
+        logger.info("录像查询完成事件触发,deviceId:{}, channelId: {}, 录像数量{}/{}条", event.getRecordInfo().getDeviceId(),
+                event.getRecordInfo().getChannelId(), count,sumNum);
         if (handlerMap.size() > 0) {
-            for (RecordEndEventHandler recordEndEventHandler : handlerMap.values()) {
-                recordEndEventHandler.handler(event.getRecordInfo());
+            RecordEndEventHandler handler = handlerMap.get(deviceId + channelId);
+            if (handler !=null){
+                handler.handler(event.getRecordInfo());
+                if (count ==sumNum){
+                    handlerMap.remove(deviceId + channelId);
+                }
             }
         }
-        handlerMap.clear();
     }
 
+    /**
+     * 添加
+     * @param device
+     * @param channelId
+     * @param recordEndEventHandler
+     */
     public void addEndEventHandler(String device, String channelId, RecordEndEventHandler recordEndEventHandler) {
         handlerMap.put(device + channelId, recordEndEventHandler);
     }
+    /**
+     * 添加
+     * @param device
+     * @param channelId
+     */
+    public void delEndEventHandler(String device, String channelId) {
+        handlerMap.remove(device + channelId);
+    }
+
 }

+ 6 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.session;
 
 import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.event.record.RecordEndEventListener;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -23,14 +24,17 @@ public class RecordDataCatch {
 
     @Autowired
     private DeferredResultHolder deferredResultHolder;
+    @Autowired
+    private RecordEndEventListener recordEndEventListener;
 
 
-    public int put(String deviceId, String sn, int sumNum, List<RecordItem> recordItems) {
+    public int put(String deviceId,String channelId, String sn, int sumNum, List<RecordItem> recordItems) {
         String key = deviceId + sn;
         RecordInfo recordInfo = data.get(key);
         if (recordInfo == null) {
             recordInfo = new RecordInfo();
             recordInfo.setDeviceId(deviceId);
+            recordInfo.setChannelId(channelId);
             recordInfo.setSn(sn.trim());
             recordInfo.setSumNum(sumNum);
             recordInfo.setRecordList(Collections.synchronizedList(new ArrayList<>()));
@@ -67,6 +71,7 @@ public class RecordDataCatch {
                 msg.setKey(msgKey);
                 msg.setData(recordInfo);
                 deferredResultHolder.invokeAllResult(msg);
+                recordEndEventListener.delEndEventHandler(recordInfo.getDeviceId(),recordInfo.getChannelId());
                 data.remove(key);
             }
         }

+ 53 - 49
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java

@@ -47,61 +47,65 @@ public class SIPSender {
     }
 
     public void transmitRequest(String ip, Message message, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, ParseException {
-        ViaHeader viaHeader = (ViaHeader)message.getHeader(ViaHeader.NAME);
-        String transport = "UDP";
-        if (viaHeader == null) {
-            logger.warn("[消息头缺失]: ViaHeader, 使用默认的UDP方式处理数据");
-        }else {
-            transport = viaHeader.getTransport();
-        }
-        if (message.getHeader(UserAgentHeader.NAME) == null) {
-            try {
-                message.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
-            } catch (ParseException e) {
-                logger.error("添加UserAgentHeader失败", e);
+        try {
+            ViaHeader viaHeader = (ViaHeader)message.getHeader(ViaHeader.NAME);
+            String transport = "UDP";
+            if (viaHeader == null) {
+                logger.warn("[消息头缺失]: ViaHeader, 使用默认的UDP方式处理数据");
+            }else {
+                transport = viaHeader.getTransport();
+            }
+            if (message.getHeader(UserAgentHeader.NAME) == null) {
+                try {
+                    message.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+                } catch (ParseException e) {
+                    logger.error("添加UserAgentHeader失败", e);
+                }
             }
-        }
 
-        CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME);
-        // 添加错误订阅
-        if (errorEvent != null) {
-            sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
-                errorEvent.response(eventResult);
-                sipSubscribe.removeErrorSubscribe(eventResult.callId);
-                sipSubscribe.removeOkSubscribe(eventResult.callId);
-            }));
-        }
-        // 添加订阅
-        if (okEvent != null) {
-            sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> {
-                okEvent.response(eventResult);
-                sipSubscribe.removeOkSubscribe(eventResult.callId);
-                sipSubscribe.removeErrorSubscribe(eventResult.callId);
-            });
-        }
-        if ("TCP".equals(transport)) {
-            SipProviderImpl tcpSipProvider = sipLayer.getTcpSipProvider(ip);
-            if (tcpSipProvider == null) {
-                logger.error("[发送信息失败] 未找到tcp://{}的监听信息", ip);
-                return;
+            CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME);
+            // 添加错误订阅
+            if (errorEvent != null) {
+                sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
+                    errorEvent.response(eventResult);
+                    sipSubscribe.removeErrorSubscribe(eventResult.callId);
+                    sipSubscribe.removeOkSubscribe(eventResult.callId);
+                }));
             }
-            if (message instanceof Request) {
-                tcpSipProvider.sendRequest((Request)message);
-            }else if (message instanceof Response) {
-                tcpSipProvider.sendResponse((Response)message);
+            // 添加订阅
+            if (okEvent != null) {
+                sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> {
+                    okEvent.response(eventResult);
+                    sipSubscribe.removeOkSubscribe(eventResult.callId);
+                    sipSubscribe.removeErrorSubscribe(eventResult.callId);
+                });
             }
+            if ("TCP".equals(transport)) {
+                SipProviderImpl tcpSipProvider = sipLayer.getTcpSipProvider(ip);
+                if (tcpSipProvider == null) {
+                    logger.error("[发送信息失败] 未找到tcp://{}的监听信息", ip);
+                    return;
+                }
+                if (message instanceof Request) {
+                    tcpSipProvider.sendRequest((Request)message);
+                }else if (message instanceof Response) {
+                    tcpSipProvider.sendResponse((Response)message);
+                }
 
-        } else if ("UDP".equals(transport)) {
-            SipProviderImpl sipProvider = sipLayer.getUdpSipProvider(ip);
-            if (sipProvider == null) {
-                logger.error("[发送信息失败] 未找到udp://{}的监听信息", ip);
-                return;
-            }
-            if (message instanceof Request) {
-                sipProvider.sendRequest((Request)message);
-            }else if (message instanceof Response) {
-                sipProvider.sendResponse((Response)message);
+            } else if ("UDP".equals(transport)) {
+                SipProviderImpl sipProvider = sipLayer.getUdpSipProvider(ip);
+                if (sipProvider == null) {
+                    logger.error("[发送信息失败] 未找到udp://{}的监听信息", ip);
+                    return;
+                }
+                if (message instanceof Request) {
+                    sipProvider.sendRequest((Request)message);
+                }else if (message instanceof Response) {
+                    sipProvider.sendResponse((Response)message);
+                }
             }
+        } finally {
+            logger.info("[SEND]:SUCCESS:{}", message);
         }
     }
 

+ 14 - 12
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -183,7 +183,7 @@ public interface ISIPCommander {
 	 * @param channelId  	预览通道
 	 * @param recordCmdStr	录像命令:Record / StopRecord
 	 */
-	void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 远程启动控制命令
@@ -197,7 +197,7 @@ public interface ISIPCommander {
 	 * 
 	 * @param device  	视频设备
 	 */
-	void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 报警复位命令
@@ -206,7 +206,7 @@ public interface ISIPCommander {
 	 * @param alarmMethod	报警方式(可选)
 	 * @param alarmType		报警类型(可选)
 	 */
-	void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 	
 	/**
 	 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
@@ -215,17 +215,19 @@ public interface ISIPCommander {
 	 * @param channelId  预览通道
 	 */
 	void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException;
-	
+
 	/**
 	 * 看守位控制命令
-	 * 
-	 * @param device		视频设备
-	 * @param enabled		看守位使能:1 = 开启,0 = 关闭
-	 * @param resetTime		自动归位时间间隔,开启看守位时使用,单位:秒(s)
-	 * @param presetIndex	调用预置位编号,开启看守位时使用,取值范围0~255
-	 */
-	void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-	
+	 *
+	 * @param device      视频设备
+	 * @param channelId      通道id,非通道则是设备本身
+	 * @param frontCmd     上级平台的指令,如果存在则直接下发
+	 * @param enabled     看守位使能:1 = 开启,0 = 关闭
+	 * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
+	 * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
+	 */
+	void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
+
 	/**
 	 * 设备配置命令
 	 * 

+ 11 - 8
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java

@@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.DependsOn;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.ResponseEvent;
@@ -663,7 +664,7 @@ public class SIPCommander implements ISIPCommander {
      * @param recordCmdStr 录像命令:Record / StopRecord
      */
     @Override
-    public void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+    public void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -681,7 +682,7 @@ public class SIPCommander implements ISIPCommander {
         
 
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
     }
 
     /**
@@ -715,7 +716,7 @@ public class SIPCommander implements ISIPCommander {
      * @param guardCmdStr "SetGuard"/"ResetGuard"
      */
     @Override
-    public void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+    public void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
@@ -728,7 +729,7 @@ public class SIPCommander implements ISIPCommander {
         cmdXml.append("</Control>\r\n");
 
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
     }
 
     /**
@@ -737,7 +738,7 @@ public class SIPCommander implements ISIPCommander {
      * @param device 视频设备
      */
     @Override
-    public void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+    public void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
@@ -764,7 +765,7 @@ public class SIPCommander implements ISIPCommander {
         
 
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
     }
 
     /**
@@ -800,12 +801,14 @@ public class SIPCommander implements ISIPCommander {
      * 看守位控制命令
      *
      * @param device      视频设备
+     * @param channelId      通道id,非通道则是设备本身
+     * @param frontCmd     上级平台的指令,如果存在则直接下发
      * @param enabled     看守位使能:1 = 开启,0 = 关闭
      * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
      * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
      */
     @Override
-    public void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+    public void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
@@ -840,7 +843,7 @@ public class SIPCommander implements ISIPCommander {
         
 
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
     }
 
     /**

+ 244 - 30
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java

@@ -1,8 +1,11 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.control.cmd;
 
-import com.genersoft.iot.vmp.VManageBootstrap;
+import com.genersoft.iot.vmp.common.enums.DeviceControlType;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DragZoomRequest;
+import com.genersoft.iot.vmp.gb28181.bean.HomePositionRequest;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
+import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
@@ -19,17 +22,14 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import javax.sip.*;
 import javax.sip.address.SipURI;
-import javax.sip.header.HeaderAddress;
-import javax.sip.header.ToHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
-import java.util.Iterator;
+import java.util.List;
 
-import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
+import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.*;
 
 @Component
 public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
@@ -81,7 +81,7 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
                 } catch (InvalidArgumentException | ParseException | SipException e) {
                     logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage());
                 }
-                taskExecutor.execute(()->{
+                taskExecutor.execute(() -> {
                     // 远程启动
 //                    try {
 //                        Thread.sleep(3000);
@@ -101,13 +101,12 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
 //                        logger.error("[任务执行失败] 服务重启: {}", e.getMessage());
 //                    }
                 });
-            } else {
-                // 远程启动指定设备
             }
         }
-        // 云台/前端控制命令
-        if (!ObjectUtils.isEmpty(getText(rootElement,"PTZCmd")) && !parentPlatform.getServerGBId().equals(targetGBId)) {
-            String cmdString = getText(rootElement,"PTZCmd");
+        DeviceControlType deviceControlType = DeviceControlType.typeOf(rootElement);
+        logger.info("[接受deviceControl命令] 命令: {}", deviceControlType);
+        if (!ObjectUtils.isEmpty(deviceControlType) && !parentPlatform.getServerGBId().equals(targetGBId)) {
+            //判断是否存在该通道
             Device deviceForPlatform = storager.queryVideoDeviceByPlatformIdAndChannelId(parentPlatform.getServerGBId(), channelId);
             if (deviceForPlatform == null) {
                 try {
@@ -117,25 +116,240 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
                 }
                 return;
             }
-            try {
-                cmder.fronEndCmd(deviceForPlatform, channelId, cmdString, eventResult -> {
-                    // 失败的回复
-                    try {
-                        responseAck(request, eventResult.statusCode, eventResult.msg);
-                    } catch (SipException | InvalidArgumentException | ParseException e) {
-                        logger.error("[命令发送失败] 云台/前端回复: {}", e.getMessage());
-                    }
-                }, eventResult -> {
-                    // 成功的回复
-                    try {
-                        responseAck(request, eventResult.statusCode);
-                    } catch (SipException | InvalidArgumentException | ParseException e) {
-                        logger.error("[命令发送失败] 云台/前端回复: {}", e.getMessage());
-                    }
-                });
-            } catch (InvalidArgumentException | SipException | ParseException e) {
-                logger.error("[命令发送失败] 云台/前端: {}", e.getMessage());
+            switch (deviceControlType) {
+                case PTZ:
+                    handlePtzCmd(deviceForPlatform, channelId, rootElement, request, DeviceControlType.PTZ);
+                    break;
+                case ALARM:
+                    handleAlarmCmd(deviceForPlatform, rootElement, request);
+                    break;
+                case GUARD:
+                    handleGuardCmd(deviceForPlatform, rootElement, request, DeviceControlType.GUARD);
+                    break;
+                case RECORD:
+                    handleRecordCmd(deviceForPlatform, channelId, rootElement, request, DeviceControlType.RECORD);
+                    break;
+                case I_FRAME:
+                    handleIFameCmd(deviceForPlatform, request, channelId);
+                    break;
+                case TELE_BOOT:
+                    handleTeleBootCmd(deviceForPlatform, request);
+                    break;
+                case DRAG_ZOOM_IN:
+                    handleDragZoom(deviceForPlatform, channelId, rootElement, request, DeviceControlType.DRAG_ZOOM_IN);
+                    break;
+                case DRAG_ZOOM_OUT:
+                    handleDragZoom(deviceForPlatform, channelId, rootElement, request, DeviceControlType.DRAG_ZOOM_OUT);
+                    break;
+                case HOME_POSITION:
+                    handleHomePositionCmd(deviceForPlatform, channelId, rootElement, request, DeviceControlType.HOME_POSITION);
+                    break;
+                default:
+                    break;
             }
         }
     }
+
+    /**
+     * 处理云台指令
+     *
+     * @param device      设备
+     * @param channelId   通道id
+     * @param rootElement
+     * @param request
+     */
+    private void handlePtzCmd(Device device, String channelId, Element rootElement, SIPRequest request, DeviceControlType type) {
+        String cmdString = getText(rootElement, type.getVal());
+        try {
+            cmder.fronEndCmd(device, channelId, cmdString,
+                    errorResult -> onError(request, errorResult),
+                    okResult -> onOk(request, okResult));
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 云台/前端: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 处理强制关键帧
+     *
+     * @param device    设备
+     * @param channelId 通道id
+     */
+    private void handleIFameCmd(Device device, SIPRequest request, String channelId) {
+        try {
+            cmder.iFrameCmd(device, channelId);
+            responseAck(request, Response.OK);
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 强制关键帧: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 处理重启命令
+     *
+     * @param device 设备信息
+     */
+    private void handleTeleBootCmd(Device device, SIPRequest request) {
+        try {
+            cmder.teleBootCmd(device);
+            responseAck(request, Response.OK);
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 重启: {}", e.getMessage());
+        }
+
+    }
+
+    /**
+     * 处理拉框控制***
+     *
+     * @param device      设备信息
+     * @param channelId   通道id
+     * @param rootElement 根节点
+     * @param type        消息类型
+     */
+    private void handleDragZoom(Device device, String channelId, Element rootElement, SIPRequest request, DeviceControlType type) {
+        try {
+            DragZoomRequest dragZoomRequest = loadElement(rootElement, DragZoomRequest.class);
+            DragZoomRequest.DragZoom dragZoom = dragZoomRequest.getDragZoomIn();
+            if (dragZoom == null) {
+                dragZoom = dragZoomRequest.getDragZoomOut();
+            }
+            StringBuffer cmdXml = new StringBuffer(200);
+            cmdXml.append("<" + type.getVal() + ">\r\n");
+            cmdXml.append("<Length>" + dragZoom.getLength() + "</Length>\r\n");
+            cmdXml.append("<Width>" + dragZoom.getWidth() + "</Width>\r\n");
+            cmdXml.append("<MidPointX>" + dragZoom.getMidPointX() + "</MidPointX>\r\n");
+            cmdXml.append("<MidPointY>" + dragZoom.getMidPointY() + "</MidPointY>\r\n");
+            cmdXml.append("<LengthX>" + dragZoom.getLengthX() + "</LengthX>\r\n");
+            cmdXml.append("<LengthY>" + dragZoom.getLengthY() + "</LengthY>\r\n");
+            cmdXml.append("</" + type.getVal() + ">\r\n");
+            cmder.dragZoomCmd(device, channelId, cmdXml.toString());
+            responseAck(request, Response.OK);
+        } catch (Exception e) {
+            logger.error("[命令发送失败] 拉框控制: {}", e.getMessage());
+        }
+
+    }
+
+    /**
+     * 处理看守位命令***
+     *
+     * @param device      设备信息
+     * @param channelId   通道id
+     * @param rootElement 根节点
+     * @param request     请求信息
+     * @param type        消息类型
+     */
+    private void handleHomePositionCmd(Device device, String channelId, Element rootElement, SIPRequest request, DeviceControlType type) {
+        try {
+            HomePositionRequest homePosition = loadElement(rootElement, HomePositionRequest.class);
+            //获取整个消息主体,我们只需要修改请求头即可
+            HomePositionRequest.HomePosition info = homePosition.getHomePosition();
+            cmder.homePositionCmd(device, channelId, info.getEnabled(), info.getResetTime(), info.getPresetIndex(),
+                    errorResult -> onError(request, errorResult),
+                    okResult -> onOk(request, okResult));
+        } catch (Exception e) {
+            logger.error("[命令发送失败] 看守位设置: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 处理告警消息***
+     *
+     * @param device      设备信息
+     * @param rootElement 根节点
+     * @param request     请求信息
+     */
+    private void handleAlarmCmd(Device device, Element rootElement, SIPRequest request) {
+        //告警方法
+        String alarmMethod = "";
+        //告警类型
+        String alarmType = "";
+        List<Element> info = rootElement.elements("Info");
+        if (info != null) {
+            for (Element element : info) {
+                alarmMethod = getText(element, "AlarmMethod");
+                alarmType = getText(element, "AlarmType");
+            }
+        }
+        try {
+            cmder.alarmCmd(device, alarmMethod, alarmType,
+                    errorResult -> onError(request, errorResult),
+                    okResult -> onOk(request, okResult));
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 告警消息: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 处理录像控制
+     *
+     * @param device      设备信息
+     * @param channelId   通道id
+     * @param rootElement 根节点
+     * @param request     请求信息
+     * @param type        消息类型
+     */
+    private void handleRecordCmd(Device device, String channelId, Element rootElement, SIPRequest request, DeviceControlType type) {
+        //获取整个消息主体,我们只需要修改请求头即可
+        String cmdString = getText(rootElement, type.getVal());
+        try {
+            cmder.recordCmd(device, channelId, cmdString,
+                    errorResult -> onError(request, errorResult),
+                    okResult -> onOk(request, okResult));
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 录像控制: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 处理报警布防/撤防命令
+     *
+     * @param device      设备信息
+     * @param rootElement 根节点
+     * @param request     请求信息
+     * @param type        消息类型
+     */
+    private void handleGuardCmd(Device device, Element rootElement, SIPRequest request, DeviceControlType type) {
+        //获取整个消息主体,我们只需要修改请求头即可
+        String cmdString = getText(rootElement, type.getVal());
+        try {
+            cmder.guardCmd(device, cmdString,
+                    errorResult -> onError(request, errorResult),
+                    okResult -> onOk(request, okResult));
+        } catch (InvalidArgumentException | SipException | ParseException e) {
+            logger.error("[命令发送失败] 布防/撤防命令: {}", e.getMessage());
+        }
+    }
+
+
+    /**
+     * 错误响应处理
+     *
+     * @param request     请求
+     * @param eventResult 响应结构
+     */
+    private void onError(SIPRequest request, SipSubscribe.EventResult eventResult) {
+        // 失败的回复
+        try {
+            responseAck(request, eventResult.statusCode, eventResult.msg);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 回复: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 成功响应处理
+     *
+     * @param request     请求
+     * @param eventResult 响应结构
+     */
+    private void onOk(SIPRequest request, SipSubscribe.EventResult eventResult) {
+        // 成功的回复
+        try {
+            responseAck(request, eventResult.statusCode);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 回复: {}", e.getMessage());
+        }
+    }
 }

+ 4 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java

@@ -181,11 +181,13 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
                             }
                         }
                         logger.info("[收到报警通知]内容:{}", JSON.toJSONString(deviceAlarm));
-                        if ("7".equals(deviceAlarm.getAlarmMethod()) ) {
+                        if (DeviceAlarmMethod.typeOf(Integer.parseInt(deviceAlarm.getAlarmMethod())) !=null) {
                             // 发送给平台的报警信息。 发送redis通知
+                            logger.info("[发送给平台的报警信息]内容:{}", JSONObject.toJSONString(deviceAlarm));
                             AlarmChannelMessage alarmChannelMessage = new AlarmChannelMessage();
                             alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
                             alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
+                            alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType()));
                             alarmChannelMessage.setGbId(channelId);
                             redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
                             continue;
@@ -264,6 +266,7 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
             alarmChannelMessage.setAlarmSn(Integer.parseInt(deviceAlarm.getAlarmMethod()));
             alarmChannelMessage.setAlarmDescription(deviceAlarm.getAlarmDescription());
             alarmChannelMessage.setGbId(channelId);
+            alarmChannelMessage.setAlarmType(Integer.parseInt(deviceAlarm.getAlarmType()));
             redisCatchStorage.sendAlarmMsg(alarmChannelMessage);
             return;
         }

+ 4 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java

@@ -102,8 +102,9 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
                         Element recordListElement = rootElementForCharset.element("RecordList");
                         if (recordListElement == null || sumNum == 0) {
                             logger.info("无录像数据");
+                            int count = recordDataCatch.put(take.getDevice().getDeviceId(),channelId, sn, sumNum, new ArrayList<>());
+                            recordInfo.setCount(count);
                             eventPublisher.recordEndEventPush(recordInfo);
-                            recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, new ArrayList<>());
                             releaseRequest(take.getDevice().getDeviceId(), sn);
                         } else {
                             Iterator<Element> recordListIterator = recordListElement.elementIterator();
@@ -137,12 +138,11 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
                                     recordList.add(record);
                                 }
                                 recordInfo.setRecordList(recordList);
+                                int count = recordDataCatch.put(take.getDevice().getDeviceId(),channelId, sn, sumNum, recordList);recordInfo.setCount(count);
+                                logger.info("[国标录像], {}->{}: {}/{}", take.getDevice().getDeviceId(), sn, count, sumNum);
                                 // 发送消息,如果是上级查询此录像,则会通过这里通知给上级
                                 eventPublisher.recordEndEventPush(recordInfo);
-                                int count = recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, recordList);
-                                logger.info("[国标录像], {}->{}: {}/{}", take.getDevice().getDeviceId(), sn, count, sumNum);
                             }
-
                             if (recordDataCatch.isComplete(take.getDevice().getDeviceId(), sn)){
                                 releaseRequest(take.getDevice().getDeviceId(), sn);
                             }

+ 17 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/utils/MessageElement.java

@@ -0,0 +1,17 @@
+package com.genersoft.iot.vmp.gb28181.utils;
+
+import java.lang.annotation.*;
+
+/**
+ * @author gaofuwang
+ * @version 1.0
+ * @date 2022/6/28 14:58
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface MessageElement {
+    String value();
+
+    String subVal() default "";
+}

+ 78 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java

@@ -1,5 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.utils;
 
+import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
@@ -15,12 +16,16 @@ import org.dom4j.io.SAXReader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
+import org.springframework.util.ReflectionUtils;
 
 import javax.sip.RequestEvent;
 import javax.sip.message.Request;
 import java.io.ByteArrayInputStream;
 import java.io.StringReader;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 import java.util.*;
 
 /**
@@ -411,4 +416,76 @@ public class XmlUtil {
         }
         return deviceChannel;
     }
+
+    /**
+     * 新增方法支持内部嵌套
+     *
+     * @param element xmlElement
+     * @param clazz 结果类
+     * @param <T> 泛型
+     * @return 结果对象
+     * @throws NoSuchMethodException
+     * @throws InvocationTargetException
+     * @throws InstantiationException
+     * @throws IllegalAccessException
+     */
+    public static <T> T loadElement(Element element, Class<T> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
+        Field[] fields = clazz.getDeclaredFields();
+        T t = clazz.getDeclaredConstructor().newInstance();
+        for (Field field : fields) {
+            ReflectionUtils.makeAccessible(field);
+            MessageElement annotation = field.getAnnotation(MessageElement.class);
+            if (annotation == null) {
+                continue;
+            }
+            String value = annotation.value();
+            String subVal = annotation.subVal();
+            Element element1 = element.element(value);
+            if (element1 == null) {
+                continue;
+            }
+            if ("".equals(subVal)) {
+                // 无下级数据
+                Object fieldVal = element1.isTextOnly() ? element1.getText() : loadElement(element1, field.getType());
+                Object o = simpleTypeDeal(field.getType(), fieldVal);
+                ReflectionUtils.setField(field, t,  o);
+            } else {
+                // 存在下级数据
+                ArrayList<Object> list = new ArrayList<>();
+                Type genericType = field.getGenericType();
+                if (!(genericType instanceof ParameterizedType)) {
+                    continue;
+                }
+                Class<?> aClass = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
+                for (Element element2 : element1.elements(subVal)) {
+                    list.add(loadElement(element2, aClass));
+                }
+                ReflectionUtils.setField(field, t, list);
+            }
+        }
+        return t;
+    }
+
+    /**
+     * 简单类型处理
+     *
+     * @param tClass
+     * @param val
+     * @return
+     */
+    private static Object simpleTypeDeal(Class<?> tClass, Object val) {
+        if (tClass.equals(String.class)) {
+            return val.toString();
+        }
+        if (tClass.equals(Integer.class)) {
+            return Integer.valueOf(val.toString());
+        }
+        if (tClass.equals(Double.class)) {
+            return Double.valueOf(val.toString());
+        }
+        if (tClass.equals(Long.class)) {
+            return Long.valueOf(val.toString());
+        }
+        return val;
+    }
 }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java

@@ -67,9 +67,9 @@ public class RedisAlarmMsgListener implements MessageListener {
                         deviceAlarm.setChannelId(gbId);
                         deviceAlarm.setAlarmDescription(alarmChannelMessage.getAlarmDescription());
                         deviceAlarm.setAlarmMethod("" + alarmChannelMessage.getAlarmSn());
+                        deviceAlarm.setAlarmType("" + alarmChannelMessage.getAlarmType());
                         deviceAlarm.setAlarmPriority("1");
                         deviceAlarm.setAlarmTime(DateUtil.getNowForISO8601());
-                        deviceAlarm.setAlarmType("1");
                         deviceAlarm.setLongitude(0);
                         deviceAlarm.setLatitude(0);
 

+ 3 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformChannelMapper.java

@@ -114,4 +114,7 @@ public interface PlatformChannelMapper {
             "         left join device d on dc.deviceId = d.deviceId\n" +
             "where dc.channelId = #{channelId} and pgc.platformId=#{platformId}")
     List<Device> queryDeviceInfoByPlatformIdAndChannelId(String platformId, String channelId);
+
+    @Select("SELECT pgc.platformId FROM platform_gb_channel pgc left join device_channel dc on dc.id = pgc.deviceChannelId WHERE dc.channelId='${channelId}'")
+    List<String> queryParentPlatformByChannelId(String channelId);
 }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java

@@ -830,7 +830,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
 
     @Override
     public void sendAlarmMsg(AlarmChannelMessage msg) {
-        String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM;
+        String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM_RECEIVE;
         logger.info("[redis发送通知] 报警{}: {}", key, JSON.toJSON(msg));
         RedisUtil.convertAndSend(key, (JSONObject)JSON.toJSON(msg));
     }

+ 9 - 0
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java

@@ -133,6 +133,15 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 					if (allChannelMap.containsKey(deviceChannel.getChannelId())) {
 						deviceChannel.setStreamId(allChannelMap.get(deviceChannel.getChannelId()).getStreamId());
 						deviceChannel.setHasAudio(allChannelMap.get(deviceChannel.getChannelId()).isHasAudio());
+						if (allChannelMap.get(deviceChannel.getChannelId()).getStatus() !=deviceChannel.getStatus()){
+							List<String> strings = platformChannelMapper.queryParentPlatformByChannelId(deviceChannel.getChannelId());
+							if (!CollectionUtils.isEmpty(strings)){
+								strings.forEach(platformId->{
+									eventPublisher.catalogEventPublish(platformId, deviceChannel, deviceChannel.getStatus()==1?CatalogEvent.ON:CatalogEvent.OFF);
+								});
+							}
+
+						}
 					}
 					channels.add(deviceChannel);
 					if (!ObjectUtils.isEmpty(deviceChannel.getParentId())) {

+ 4 - 4
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java

@@ -110,7 +110,7 @@ public class DeviceControl {
 				msg.setKey(key);
 				msg.setData(String.format("开始/停止录像操作失败,错误码: %s, %s", event.statusCode, event.msg));
 				resultHolder.invokeAllResult(msg);
-			});
+			},null);
 		} catch (InvalidArgumentException | SipException | ParseException e) {
 			logger.error("[命令发送失败] 开始/停止录像: {}", e.getMessage());
 			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
@@ -143,7 +143,7 @@ public class DeviceControl {
 				msg.setKey(key);
 				msg.setData(String.format("布防/撤防操作失败,错误码: %s, %s", event.statusCode, event.msg));
 				resultHolder.invokeResult(msg);
-			});
+			},null);
 		} catch (InvalidArgumentException | SipException | ParseException e) {
 			logger.error("[命令发送失败] 布防/撤防操作: {}", e.getMessage());
 			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage());
@@ -192,7 +192,7 @@ public class DeviceControl {
 				msg.setKey(key);
 				msg.setData(String.format("报警复位操作失败,错误码: %s, %s", event.statusCode, event.msg));
 				resultHolder.invokeResult(msg);
-			});
+			},null);
 		} catch (InvalidArgumentException | SipException | ParseException e) {
 			logger.error("[命令发送失败] 报警复位: {}", e.getMessage());
 			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
@@ -274,7 +274,7 @@ public class DeviceControl {
 				msg.setKey(key);
 				msg.setData(String.format("看守位控制操作失败,错误码: %s, %s", event.statusCode, event.msg));
 				resultHolder.invokeResult(msg);
-			});
+			},null);
 		} catch (InvalidArgumentException | SipException | ParseException e) {
 			logger.error("[命令发送失败] 看守位控制: {}", e.getMessage());
 			throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());