diff --git a/src/main/java/com/genersoft/iot/vmp/common/enums/ChannelDataType.java b/src/main/java/com/genersoft/iot/vmp/common/enums/ChannelDataType.java index 5ca59025..c2d2a11e 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/enums/ChannelDataType.java +++ b/src/main/java/com/genersoft/iot/vmp/common/enums/ChannelDataType.java @@ -1,8 +1,5 @@ package com.genersoft.iot.vmp.common.enums; -import lombok.Getter; - - /** * 支持的通道数据类型 */ diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndCode.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndCode.java new file mode 100644 index 00000000..ecdb4efa --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndCode.java @@ -0,0 +1,184 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * 解析收到的前端控制指令 + */ +@Data +public class FrontEndCode { + + + public static String encode(IFrontEndControlCode frontEndControlCode){ + return frontEndControlCode.encode(); + } + + public static IFrontEndControlCode decode(@NotNull String cmdStr) { + if (cmdStr.length() != 16) { + return null; + } + String cmdCodeStr = cmdStr.substring(6, 8); + int cmdCode = Integer.parseInt(cmdCodeStr, 16); + if (cmdCode < 39) { + // PTZ指令 + FrontEndControlCodeForPTZ codeForPTZ = new FrontEndControlCodeForPTZ(); + int zoomOut = cmdCode >> 5 & 1; + if (zoomOut == 1) { + codeForPTZ.setZoom(0); + } + int zoomIn = cmdCode >> 4 & 1; + if (zoomIn == 1) { + codeForPTZ.setZoom(1); + } + int tiltUp = cmdCode >> 3 & 1; + if (tiltUp == 1) { + codeForPTZ.setTilt(0); + } + int tiltDown = cmdCode >> 2 & 1; + if (tiltDown == 1) { + codeForPTZ.setTilt(1); + } + int panLeft = cmdCode >> 1 & 1; + if (panLeft == 1) { + codeForPTZ.setPan(0); + } + int panRight = cmdCode & 1; + if (panRight == 1) { + codeForPTZ.setPan(1); + } + String param1Str = cmdStr.substring(8, 10); + codeForPTZ.setPanSpeed(Integer.parseInt(param1Str, 16)); + String param2Str = cmdStr.substring(10, 12); + codeForPTZ.setTiltSpeed(Integer.parseInt(param2Str, 16)); + String param3Str = cmdStr.substring(12, 13); + codeForPTZ.setZoomSpeed(Integer.parseInt(param3Str, 16)); + return codeForPTZ; + }else if (cmdCode < 74) { + // FI指令 + FrontEndControlCodeForFI codeForFI = new FrontEndControlCodeForFI(); + int irisOut = cmdCode >> 3 & 1; + if (irisOut == 1) { + codeForFI.setIris(0); + } + int irisIn = cmdCode >> 2 & 1; + if (irisIn == 1) { + codeForFI.setIris(1); + } + int focusNear = cmdCode >> 1 & 1; + if (focusNear == 1) { + codeForFI.setFocus(0); + } + int focusFar = cmdCode & 1; + if (focusFar == 1) { + codeForFI.setFocus(1); + } + + String param1Str = cmdStr.substring(8, 10); + codeForFI.setFocusSpeed(Integer.parseInt(param1Str, 16)); + String param2Str = cmdStr.substring(10, 12); + codeForFI.setIrisSpeed(Integer.parseInt(param2Str, 16)); + return codeForFI; + }else if (cmdCode < 131) { + // 预置位指令 + FrontEndControlCodeForPreset codeForPreset = new FrontEndControlCodeForPreset(); + switch (cmdCode) { + case 0x81: // 设置预置位 + codeForPreset.setCode(1); + break; + case 0x82: // 调用预置位 + codeForPreset.setCode(2); + break; + case 0x83: // 删除预置位 + codeForPreset.setCode(3); + break; + default: + return null; + } + // 预置位编号 + String param2Str = cmdStr.substring(10, 12); + codeForPreset.setPresetId(Integer.parseInt(param2Str, 16)); + return codeForPreset; + }else if (cmdCode < 136) { + // 巡航指令 + FrontEndControlCodeForTour codeForTour = new FrontEndControlCodeForTour(); + String param3Str = cmdStr.substring(12, 13); + switch (cmdCode) { + case 0x84: // 加入巡航点 + codeForTour.setCode(1); + break; + case 0x85: // 删除一个巡航点 + codeForTour.setCode(2); + break; + case 0x86: // 设置巡航速度 + codeForTour.setCode(3); + codeForTour.setTourSpeed(Integer.parseInt(param3Str, 16)); + break; + case 0x87: // 设置巡航停留时间 + codeForTour.setCode(4); + codeForTour.setTourTime(Integer.parseInt(param3Str, 16)); + break; + case 0x88: // 开始巡航 + codeForTour.setCode(5); + break; + default: + return null; + } + String param1Str = cmdStr.substring(8, 10); + codeForTour.setTourId(Integer.parseInt(param1Str, 16)); + String param2Str = cmdStr.substring(10, 12); + codeForTour.setPresetId(Integer.parseInt(param2Str, 16)); + return codeForTour; + }else if (cmdCode < 138) { + // 扫描指令 + FrontEndControlCodeForScan controlCodeForScan = new FrontEndControlCodeForScan(); + String param2Str = cmdStr.substring(10, 11); + int param2Code = Integer.parseInt(param2Str, 16); + switch (cmdCode) { + case 0x89: + switch (param2Code) { + case 0x00: // 开始自动扫描 + controlCodeForScan.setCode(1); + break; + case 0x01: // 设置自动扫描左边界 + controlCodeForScan.setCode(2); + break; + case 0x02: // 设置自动扫描右边界 + controlCodeForScan.setCode(3); + break; + } + break; + case 0x8A: // 删除一个巡航点 + controlCodeForScan.setCode(4); + String param3Str = cmdStr.substring(12, 13); + controlCodeForScan.setScanSpeed(Integer.parseInt(param3Str, 16)); + break; + default: + return null; + } + String param1Str = cmdStr.substring(8, 10); + controlCodeForScan.setScanId(Integer.parseInt(param1Str, 16)); + return controlCodeForScan; + }else if (cmdCode < 141) { + // 辅助开关 + FrontEndControlCodeForAuxiliary codeForAuxiliary = new FrontEndControlCodeForAuxiliary(); + switch (cmdCode) { + case 0x8C: // 开 + codeForAuxiliary.setCode(1); + break; + case 0x8D: // 关 + codeForAuxiliary.setCode(2); + break; + default: + return null; + } + // 预置位编号 + String param2Str = cmdStr.substring(10, 12); + codeForAuxiliary.setAuxiliaryId(Integer.parseInt(param2Str, 16)); + return codeForAuxiliary; + }else { + return null; + } + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForAuxiliary.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForAuxiliary.java new file mode 100644 index 00000000..df81d339 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForAuxiliary.java @@ -0,0 +1,34 @@ +package com.genersoft.iot.vmp.gb28181.bean; + + +import lombok.Getter; +import lombok.Setter; + +public class FrontEndControlCodeForAuxiliary implements IFrontEndControlCode { + + private final FrontEndControlType type = FrontEndControlType.AUXILIARY; + + @Override + public FrontEndControlType getType() { + return type; + } + + /** + * 辅助开关控制指令: 1为开, 2为关, 3为设置自动扫描右边界, 4为设置自动扫描速度 + */ + @Getter + @Setter + private Integer code; + + /** + * 辅助开关编号 + */ + @Getter + @Setter + private Integer auxiliaryId; + + @Override + public String encode() { + return ""; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForFI.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForFI.java new file mode 100644 index 00000000..89c9f8d6 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForFI.java @@ -0,0 +1,48 @@ +package com.genersoft.iot.vmp.gb28181.bean; + + +import lombok.Getter; +import lombok.Setter; + +public class FrontEndControlCodeForFI implements IFrontEndControlCode { + + private final FrontEndControlType type = FrontEndControlType.FI; + + @Override + public FrontEndControlType getType() { + return type; + } + + /** + * 光圈,0为缩小 1为放大 + */ + @Getter + @Setter + private Integer iris; + + /** + * 聚焦 0 近, 1远 + */ + @Getter + @Setter + private Integer focus; + + /** + * 聚焦速度 + */ + @Getter + @Setter + private Integer focusSpeed; + + /** + * 光圈速度 + */ + @Getter + @Setter + private Integer irisSpeed; + + @Override + public String encode() { + return ""; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForPTZ.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForPTZ.java new file mode 100644 index 00000000..37598605 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForPTZ.java @@ -0,0 +1,62 @@ +package com.genersoft.iot.vmp.gb28181.bean; + + +import lombok.Getter; +import lombok.Setter; + +public class FrontEndControlCodeForPTZ implements IFrontEndControlCode { + + private final FrontEndControlType type = FrontEndControlType.PTZ; + + @Override + public FrontEndControlType getType() { + return type; + } + + /** + * 镜头变倍,0为缩小 1为放大 + */ + @Getter + @Setter + private Integer zoom; + + /** + * 云台垂直方向控制 0 为上, 1为下 + */ + @Getter + @Setter + private Integer tilt; + + /** + * 云台水平方向控制 0 为左, 1为右 + */ + @Getter + @Setter + private Integer pan; + + /** + * 水平控制速度相对值 + */ + @Getter + @Setter + private Integer panSpeed; + + /** + * 垂直控制速度相对值 + */ + @Getter + @Setter + private Integer tiltSpeed; + + /** + * 变倍控制速度相对值 + */ + @Getter + @Setter + private Integer zoomSpeed; + + @Override + public String encode() { + return ""; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForPreset.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForPreset.java new file mode 100644 index 00000000..f959ea55 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForPreset.java @@ -0,0 +1,35 @@ +package com.genersoft.iot.vmp.gb28181.bean; + + +import lombok.Getter; +import lombok.Setter; + +public class FrontEndControlCodeForPreset implements IFrontEndControlCode { + + private final FrontEndControlType type = FrontEndControlType.PRESET; + + @Override + public FrontEndControlType getType() { + return type; + } + + /** + * 预置位指令: 1为设置预置位, 2为调用预置位, 3为删除预置位 + */ + @Getter + @Setter + private Integer code; + + /** + * 预置位编号 + */ + @Getter + @Setter + private Integer presetId; + + + @Override + public String encode() { + return ""; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForScan.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForScan.java new file mode 100644 index 00000000..3bb72443 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForScan.java @@ -0,0 +1,41 @@ +package com.genersoft.iot.vmp.gb28181.bean; + + +import lombok.Getter; +import lombok.Setter; + +public class FrontEndControlCodeForScan implements IFrontEndControlCode { + + private final FrontEndControlType type = FrontEndControlType.SCAN; + + @Override + public FrontEndControlType getType() { + return type; + } + + /** + * 预置位指令: 1为开始自动扫描, 2为设置自动扫描左边界, 3为设置自动扫描右边界, 4为设置自动扫描速度 + */ + @Getter + @Setter + private Integer code; + + /** + * 自动扫描速度 + */ + @Getter + @Setter + private Integer scanSpeed; + + /** + * 扫描组号 + */ + @Getter + @Setter + private Integer scanId; + + @Override + public String encode() { + return ""; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForTour.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForTour.java new file mode 100644 index 00000000..2e27dc31 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlCodeForTour.java @@ -0,0 +1,55 @@ +package com.genersoft.iot.vmp.gb28181.bean; + + +import lombok.Getter; +import lombok.Setter; + +public class FrontEndControlCodeForTour implements IFrontEndControlCode { + + private final FrontEndControlType type = FrontEndControlType.TOUR; + + @Override + public FrontEndControlType getType() { + return type; + } + + /** + * 巡航指令: 1为加入巡航点, 2为删除一个巡航点, 3为设置巡航速度, 4为设置巡航停留时间, 5为开始巡航 + */ + @Getter + @Setter + private Integer code; + + /** + * 巡航点 + */ + @Getter + @Setter + private Integer tourId; + + /** + * 巡航停留时间 + */ + @Getter + @Setter + private Integer tourTime; + + /** + * 巡航速度 + */ + @Getter + @Setter + private Integer tourSpeed; + + /** + * 预置位编号 + */ + @Getter + @Setter + private Integer presetId; + + @Override + public String encode() { + return ""; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlType.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlType.java new file mode 100644 index 00000000..b79fbed7 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/FrontEndControlType.java @@ -0,0 +1,6 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +public enum FrontEndControlType { + + PTZ,FI,PRESET,TOUR,SCAN,AUXILIARY +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/IFrontEndControlCode.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/IFrontEndControlCode.java new file mode 100644 index 00000000..8264e532 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/IFrontEndControlCode.java @@ -0,0 +1,7 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +public interface IFrontEndControlCode { + + FrontEndControlType getType(); + String encode(); +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/PtzController.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/PtzController.java index 233050b6..061a9db1 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/PtzController.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/PtzController.java @@ -42,7 +42,7 @@ public class PtzController { private IDeviceService deviceService; @Autowired - private IPTZService iptzService; + private IPTZService ptzService; @Autowired private DeferredResultHolder resultHolder; @@ -75,7 +75,7 @@ public class PtzController { Assert.notNull(device, "设备[" + deviceId + "]不存在"); - iptzService.frontEndCommand(device, channelId, cmdCode, parameter1, parameter2, combindCode2); + ptzService.frontEndCommand(device, channelId, cmdCode, parameter1, parameter2, combindCode2); } @Operation(summary = "云台控制", security = @SecurityRequirement(name = JwtUtils.HEADER)) diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IDeviceChannelService.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IDeviceChannelService.java index c4a0200e..e9f137aa 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IDeviceChannelService.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IDeviceChannelService.java @@ -1,13 +1,17 @@ package com.genersoft.iot.vmp.gb28181.service; +import com.genersoft.iot.vmp.common.enums.DeviceControlType; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelReduce; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo; import com.genersoft.iot.vmp.web.gb28181.dto.DeviceChannelExtend; import com.github.pagehelper.PageInfo; +import org.dom4j.Element; +import javax.validation.constraints.NotNull; import java.util.List; /** @@ -126,4 +130,5 @@ public interface IDeviceChannelService { List queryChaneIdListByDeviceDbIds(List deviceDbId); + void handlePtzCmd(@NotNull Integer dataDeviceId, @NotNull Integer gbId, Element rootElement, DeviceControlType type, ErrorCallback callback); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelControlService.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelControlService.java new file mode 100644 index 00000000..c552c5b4 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelControlService.java @@ -0,0 +1,16 @@ +package com.genersoft.iot.vmp.gb28181.service; + +import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; +import com.genersoft.iot.vmp.gb28181.bean.FrontEndControlCodeForPTZ; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; + +public interface IGbChannelControlService { + + + void ptz(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback); + void fi(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback); + void preset(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback); + void tour(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback); + void scan(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback); + void auxiliary(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback); +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceChannelServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceChannelServiceImpl.java index 25f7ffff..2fcd0ab0 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceChannelServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/DeviceChannelServiceImpl.java @@ -5,6 +5,7 @@ import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.enums.ChannelDataType; +import com.genersoft.iot.vmp.common.enums.DeviceControlType; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.*; @@ -18,7 +19,9 @@ import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IInviteStreamService; import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService; +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; @@ -27,6 +30,7 @@ import com.genersoft.iot.vmp.web.gb28181.dto.DeviceChannelExtend; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import lombok.extern.slf4j.Slf4j; +import org.dom4j.Element; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -34,8 +38,15 @@ import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; +import javax.sip.InvalidArgumentException; +import javax.sip.SipException; +import javax.sip.message.Response; +import javax.validation.constraints.NotNull; +import java.text.ParseException; import java.util.*; +import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; + /** * @author lin */ @@ -72,6 +83,10 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { private IPlatformChannelService platformChannelService; + @Autowired + private ISIPCommander cmder; + + @Override public int updateChannels(Device device, List channels) { List addChannels = new ArrayList<>(); @@ -362,6 +377,39 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { return channelMapper.queryChaneIdListByDeviceDbIds(deviceDbIds); } + @Override + public void handlePtzCmd(@NotNull Integer dataDeviceId, @NotNull Integer gbId, Element rootElement, DeviceControlType type, ErrorCallback callback) { + + // 根据通道ID,获取所属设备 + Device device = deviceMapper.query(dataDeviceId); + if (device == null) { + // 不存在则回复404 + log.warn("[INFO 消息] 通道所属设备不存在, 设备ID: {}", dataDeviceId); + callback.run(Response.NOT_FOUND, "device not found", null); + return; + } + + DeviceChannel deviceChannel = channelMapper.getOneForSource(gbId); + if (deviceChannel == null) { + log.warn("[deviceControl] 未找到设备原始通道, 设备: {}({}),通道编号:{}", device.getName(), + device.getDeviceId(), gbId); + callback.run(Response.NOT_FOUND, "channel not found", null); + return; + } + log.info("[deviceControl] 命令: {}, 设备: {}({}), 通道{}({}", type, device.getName(), device.getDeviceId(), + deviceChannel.getName(), deviceChannel.getDeviceId()); + String cmdString = getText(rootElement, type.getVal()); + try { + cmder.fronEndCmd(device, deviceChannel.getDeviceId(), cmdString, errorResult->{ + callback.run(errorResult.statusCode, errorResult.msg, null); + }, errorResult->{ + callback.run(errorResult.statusCode, errorResult.msg, null); + }); + } catch (InvalidArgumentException | SipException | ParseException e) { + log.error("[命令发送失败] 云台/前端: {}", e.getMessage()); + } + } + @Override public void updateChannelGPS(Device device, DeviceChannel deviceChannel, MobilePosition mobilePosition) { if (userSetting.getSavePositionHistory()) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelControlServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelControlServiceImpl.java new file mode 100644 index 00000000..4751c2ef --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelControlServiceImpl.java @@ -0,0 +1,43 @@ +package com.genersoft.iot.vmp.gb28181.service.impl; + +import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel; +import com.genersoft.iot.vmp.gb28181.bean.FrontEndControlCodeForPTZ; +import com.genersoft.iot.vmp.gb28181.service.IGbChannelControlService; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class GbChannelControlServiceImpl implements IGbChannelControlService { + + @Override + public void ptz(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback) { + log.info("[通用通道] 云台控制, 通道: {}", channel.getGbId()); + } + + @Override + public void preset(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback) { + log.info("[通用通道] 预置位, 通道: {}", channel.getGbId()); + } + + @Override + public void fi(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback) { + log.info("[通用通道] FI指令, 通道: {}", channel.getGbId()); + } + + @Override + public void tour(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback) { + + } + + @Override + public void scan(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback) { + + } + + @Override + public void auxiliary(CommonGBChannel channel, FrontEndControlCodeForPTZ frontEndControlCode, ErrorCallback callback) { + + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/SipInviteSessionManager.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/SipInviteSessionManager.java index 69dd332e..a4468d92 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/session/SipInviteSessionManager.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/SipInviteSessionManager.java @@ -61,7 +61,7 @@ public class SipInviteSessionManager { if (ssrcTransaction == null ) { return; } - redisTemplate.opsForHash().delete(VideoManagerConstants.SIP_INVITE_SESSION_STREAM + userSetting.getServerId(), stream); + redisTemplate.opsForHash().delete(VideoManagerConstants.SIP_INVITE_SESSION_STREAM + userSetting.getServerId(), app + stream); if (ssrcTransaction.getCallId() != null) { redisTemplate.opsForHash().delete(VideoManagerConstants.SIP_INVITE_SESSION_CALL_ID + userSetting.getServerId(), ssrcTransaction.getCallId()); } @@ -74,7 +74,7 @@ public class SipInviteSessionManager { } redisTemplate.opsForHash().delete(VideoManagerConstants.SIP_INVITE_SESSION_CALL_ID + userSetting.getServerId(), callId); if (ssrcTransaction.getStream() != null) { - redisTemplate.opsForHash().delete(VideoManagerConstants.SIP_INVITE_SESSION_STREAM + userSetting.getServerId(), ssrcTransaction.getStream()); + redisTemplate.opsForHash().delete(VideoManagerConstants.SIP_INVITE_SESSION_STREAM + userSetting.getServerId(), ssrcTransaction.getApp() + ssrcTransaction.getStream()); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java index eed06646..905239df 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java @@ -211,9 +211,6 @@ public class SIPCommander implements ISIPCommander { ptzXml.append("\r\n"); ptzXml.append("\r\n"); - - - SIPRequest request = (SIPRequest) headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java index fce5b5e8..c4a8f268 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java @@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.service.IDeviceChannelService; import com.genersoft.iot.vmp.gb28181.service.IDeviceService; +import com.genersoft.iot.vmp.gb28181.service.IGbChannelControlService; import com.genersoft.iot.vmp.gb28181.service.IGbChannelService; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; @@ -42,6 +43,9 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent @Autowired private IGbChannelService channelService; + @Autowired + private IGbChannelControlService channelControlService; + @Autowired private IDeviceService deviceService; @@ -135,49 +139,91 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent * 处理云台指令 */ private void handlePtzCmd(CommonGBChannel channel, Element rootElement, SIPRequest request, DeviceControlType type) { - if (channel.getDataType() != ChannelDataType.GB28181.value) { - // 只支持国标的云台控制 - log.warn("[INFO 消息] 只支持国标的云台控制, 通道ID: {}", channel.getGbId()); - try { - responseAck(request, Response.FORBIDDEN, ""); - } catch (SipException | InvalidArgumentException | ParseException e) { - log.error("[命令发送失败] 错误信息: {}", e.getMessage()); - } - return; - } - // 根据通道ID,获取所属设备 - Device device = deviceService.getDevice(channel.getDataDeviceId()); - if (device == null) { - // 不存在则回复404 - log.warn("[INFO 消息] 通道所属设备不存在, 通道ID: {}", channel.getGbId()); - try { - responseAck(request, Response.NOT_FOUND, "device not found"); - } catch (SipException | InvalidArgumentException | ParseException e) { - log.error("[命令发送失败] 错误信息: {}", e.getMessage()); - } - return; - } + if (channel.getDataType() == ChannelDataType.GB28181.value) { - DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId()); - if (deviceChannel == null) { - log.warn("[deviceControl] 未找到设备原始通道, 设备: {}({}),通道编号:{}", device.getName(), - device.getDeviceId(), channel.getGbId()); - try { - responseAck(request, Response.NOT_FOUND, "channel not found"); - } catch (SipException | InvalidArgumentException | ParseException e) { - log.error("[命令发送失败] 错误信息: {}", e.getMessage()); + deviceChannelService.handlePtzCmd(channel.getDataDeviceId(), channel.getGbId(), rootElement, type, ((code, msg, data) -> { + try { + responseAck(request, code, msg); + } catch (InvalidArgumentException | SipException | ParseException exception) { + log.error("[命令发送失败] 云台指令: {}", exception.getMessage()); + } + })); + }else { + // 解析云台控制参数 + String cmdString = getText(rootElement, type.getVal()); + IFrontEndControlCode frontEndControlCode = FrontEndCode.decode(cmdString); + if (frontEndControlCode == null) { + log.info("[INFO 消息] 不支持的控制方式"); + try { + responseAck(request, Response.FORBIDDEN, ""); + } catch (InvalidArgumentException | SipException | ParseException exception) { + log.error("[命令发送失败] 云台指令: {}", exception.getMessage()); + } + return; + } + switch (frontEndControlCode.getType()){ + case PTZ: + channelControlService.ptz(channel, (FrontEndControlCodeForPTZ)frontEndControlCode, ((code, msg, data) -> { + try { + responseAck(request, code, msg); + } catch (InvalidArgumentException | SipException | ParseException exception) { + log.error("[命令发送失败] 云台指令: {}", exception.getMessage()); + } + })); + break; + case FI: + channelControlService.fi(channel, (FrontEndControlCodeForPTZ)frontEndControlCode, ((code, msg, data) -> { + try { + responseAck(request, code, msg); + } catch (InvalidArgumentException | SipException | ParseException exception) { + log.error("[命令发送失败] 云台指令: {}", exception.getMessage()); + } + })); + break; + case PRESET: + channelControlService.preset(channel, (FrontEndControlCodeForPTZ)frontEndControlCode, ((code, msg, data) -> { + try { + responseAck(request, code, msg); + } catch (InvalidArgumentException | SipException | ParseException exception) { + log.error("[命令发送失败] 云台指令: {}", exception.getMessage()); + } + })); + break; + case TOUR: + channelControlService.tour(channel, (FrontEndControlCodeForPTZ)frontEndControlCode, ((code, msg, data) -> { + try { + responseAck(request, code, msg); + } catch (InvalidArgumentException | SipException | ParseException exception) { + log.error("[命令发送失败] 云台指令: {}", exception.getMessage()); + } + })); + break; + case SCAN: + channelControlService.scan(channel, (FrontEndControlCodeForPTZ)frontEndControlCode, ((code, msg, data) -> { + try { + responseAck(request, code, msg); + } catch (InvalidArgumentException | SipException | ParseException exception) { + log.error("[命令发送失败] 云台指令: {}", exception.getMessage()); + } + })); + break; + case AUXILIARY: + channelControlService.auxiliary(channel, (FrontEndControlCodeForPTZ)frontEndControlCode, ((code, msg, data) -> { + try { + responseAck(request, code, msg); + } catch (InvalidArgumentException | SipException | ParseException exception) { + log.error("[命令发送失败] 云台指令: {}", exception.getMessage()); + } + })); + break; + default: + log.info("[INFO 消息] 设备不支持的控制方式"); + try { + responseAck(request, Response.FORBIDDEN, ""); + } catch (InvalidArgumentException | SipException | ParseException exception) { + log.error("[命令发送失败] 云台指令: {}", exception.getMessage()); + } } - return; - } - log.info("[deviceControl] 命令: {}, 设备: {}({}), 通道{}({}", type, device.getName(), device.getDeviceId(), - deviceChannel.getName(), deviceChannel.getDeviceId()); - String cmdString = getText(rootElement, type.getVal()); - try { - cmder.fronEndCmd(device, deviceChannel.getDeviceId(), cmdString, - errorResult -> onError(request, errorResult), - okResult -> onOk(request, okResult)); - } catch (InvalidArgumentException | SipException | ParseException e) { - log.error("[命令发送失败] 云台/前端: {}", e.getMessage()); } } diff --git a/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyPlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyPlayServiceImpl.java index d31fca5e..f4ea79d0 100755 --- a/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyPlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/streamProxy/service/impl/StreamProxyPlayServiceImpl.java @@ -2,9 +2,14 @@ package com.genersoft.iot.vmp.streamProxy.service.impl; import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.conf.DynamicTask; +import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.media.event.hook.Hook; +import com.genersoft.iot.vmp.media.event.hook.HookSubscribe; +import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.service.bean.ErrorCallback; @@ -23,6 +28,7 @@ import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import javax.sip.message.Response; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; /** @@ -39,6 +45,15 @@ public class StreamProxyPlayServiceImpl implements IStreamProxyPlayService { @Autowired private IMediaServerService mediaServerService; + @Autowired + private HookSubscribe subscribe; + + @Autowired + private DynamicTask dynamicTask; + + @Autowired + private UserSetting userSetting; + private ConcurrentHashMap> callbackMap = new ConcurrentHashMap<>(); private ConcurrentHashMap streamInfoMap = new ConcurrentHashMap<>(); @@ -96,9 +111,25 @@ public class StreamProxyPlayServiceImpl implements IStreamProxyPlayService { if (record != null) { streamProxy.setEnableMp4(record); } + StreamInfo streamInfo = startProxy(streamProxy); - if (streamInfo != null && callback != null) { - callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); + if (callback != null) { + // 设置流超时的定时任务 + String timeOutTaskKey = UUID.randomUUID().toString(); + Hook rtpHook = Hook.getInstance(HookType.on_media_arrival, streamProxy.getApp(), streamProxy.getStream(), streamInfo.getMediaServer().getId()); + dynamicTask.startDelay(timeOutTaskKey, () -> { + // 收流超时 + subscribe.removeSubscribe(rtpHook); + callback.run(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), streamInfo); + }, userSetting.getPlayTimeout()); + + // 开启流到来的监听 + subscribe.addSubscribe(rtpHook, (hookData) -> { + dynamicTask.stop(timeOutTaskKey); + // hook响应 + callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); + subscribe.removeSubscribe(rtpHook); + }); } return streamInfo; } diff --git a/web_src/src/components/channelList.vue b/web_src/src/components/channelList.vue index 7ee4defd..02c7448e 100755 --- a/web_src/src/components/channelList.vue +++ b/web_src/src/components/channelList.vue @@ -1,164 +1,167 @@