支持相机基础信息配置

This commit is contained in:
lin 2026-06-17 16:16:50 +08:00
parent 1fc8848cf9
commit 5a3b831717
22 changed files with 984 additions and 277 deletions

View File

@ -1,14 +1,16 @@
package com.genersoft.iot.vmp.gb28181.bean;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.dom4j.Element;
/**
* 基础配置
*/
@Data
@Schema(description = "基础配置")
public class BasicParam {
public class BasicParam implements DeviceConfigAware {
@Schema(description = "设备ID")
private String deviceId;
@ -46,4 +48,20 @@ public class BasicParam {
basicParam.setHeartBeatCount(heartBeatCount);
return basicParam;
}
@Override
public String configType() {
return "BasicParam";
}
@Override
public void fromXml(Element element) {
setName(XmlUtil.getText(element, "Name"));
setExpiration(XmlUtil.getText(element, "Expiration"));
setHeartBeatInterval(XmlUtil.getInteger(element, "HeartBeatInterval"));
setHeartBeatCount(XmlUtil.getInteger(element, "HeartBeatCount"));
setPositionCapability(XmlUtil.getInteger(element, "PositionCapability"));
setLongitude(XmlUtil.getDouble(element, "Longitude"));
setLatitude(XmlUtil.getDouble(element, "Latitude"));
}
}

View File

@ -0,0 +1,10 @@
package com.genersoft.iot.vmp.gb28181.bean;
import org.dom4j.Element;
public interface DeviceConfigAware {
String configType();
void fromXml(Element element);
}

View File

@ -0,0 +1,70 @@
package com.genersoft.iot.vmp.gb28181.bean;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.dom4j.Element;
@Data
@Schema(description = "SVAC解码配置")
public class SVACDecodeConfig implements DeviceConfigAware {
@Schema(description = "SVC参数")
private SVCParam svcParam;
@Schema(description = "监控专用信息参数")
private SurveillanceParam surveillanceParam;
@Override
public String configType() {
return "SVACDecodeConfig";
}
@Override
public void fromXml(Element element) {
Element svcEl = element.element("SVCParam");
if (svcEl != null) {
SVCParam s = new SVCParam();
s.fromXml(svcEl);
setSvcParam(s);
}
Element survEl = element.element("SurveillanceParam");
if (survEl != null) {
SurveillanceParam s = new SurveillanceParam();
s.fromXml(survEl);
setSurveillanceParam(s);
}
}
@Data
public static class SVCParam {
@Schema(description = "空域编码能力0:不支持1:1级增强2:2级增强3:3级增强")
private Integer SVCSpaceSupportMode;
@Schema(description = "时域编码能力0:不支持1:1级增强2:2级增强3:3级增强")
private Integer SVCTimeSupportMode;
public void fromXml(Element element) {
setSVCSpaceSupportMode(XmlUtil.getInteger(element, "SVCSpaceSupportMode"));
setSVCTimeSupportMode(XmlUtil.getInteger(element, "SVCTimeSupportMode"));
}
}
@Data
public static class SurveillanceParam {
@Schema(description = "绝对时间信息显示开关0:关闭1:打开")
private Integer TimeShowFlag;
@Schema(description = "监控事件信息显示开关0:关闭1:打开")
private Integer EventShowFlag;
@Schema(description = "报警信息显示开关0:关闭1:打开")
private Integer AlerShowtFlag;
public void fromXml(Element element) {
setTimeShowFlag(XmlUtil.getInteger(element, "TimeShowFlag"));
setEventShowFlag(XmlUtil.getInteger(element, "EventShowFlag"));
setAlerShowtFlag(XmlUtil.getInteger(element, "AlerShowtFlag"));
}
}
}

View File

@ -0,0 +1,167 @@
package com.genersoft.iot.vmp.gb28181.bean;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.dom4j.Element;
import java.util.ArrayList;
import java.util.List;
@Data
@Schema(description = "SVAC编码配置")
public class SVACEncodeConfig implements DeviceConfigAware {
@Schema(description = "感兴趣区域参数")
private ROIParam roiParam;
@Schema(description = "SVC参数")
private SVCParam svcParam;
@Schema(description = "监控专用信息参数")
private SurveillanceParam surveillanceParam;
@Schema(description = "音频参数")
private AudioParam audioParam;
@Override
public String configType() {
return "SVACEncodeConfig";
}
@Override
public void fromXml(Element element) {
Element roiEl = element.element("ROIParam");
if (roiEl != null) {
ROIParam r = new ROIParam();
r.fromXml(roiEl);
setRoiParam(r);
}
Element svcEl = element.element("SVCParam");
if (svcEl != null) {
SVCParam s = new SVCParam();
s.fromXml(svcEl);
setSvcParam(s);
}
Element survEl = element.element("SurveillanceParam");
if (survEl != null) {
SurveillanceParam s = new SurveillanceParam();
s.fromXml(survEl);
setSurveillanceParam(s);
}
Element audioEl = element.element("AudioParam");
if (audioEl != null) {
AudioParam a = new AudioParam();
a.fromXml(audioEl);
setAudioParam(a);
}
}
@Data
public static class ROIParam {
@Schema(description = "感兴趣区域开关0:关闭1:打开")
private Integer ROIFlag;
@Schema(description = "感兴趣区域数量取值范围0~16")
private Integer ROINumber;
@Schema(description = "感兴趣区域列表")
private List<ROIItem> Item;
@Schema(description = "背景区域编码质量等级0:一般1:较好2:好3:很好")
private Integer BackGroundQP;
@Schema(description = "背景跳过开关0:关闭1:打开")
private Integer BackGroundSkipFlag;
public void fromXml(Element element) {
setROIFlag(XmlUtil.getInteger(element, "ROIFlag"));
setROINumber(XmlUtil.getInteger(element, "ROINumber"));
List<Element> itemElements = element.elements("Item");
if (!itemElements.isEmpty()) {
List<ROIItem> list = new ArrayList<>();
for (Element e : itemElements) {
ROIItem item = new ROIItem();
item.fromXml(e);
list.add(item);
}
setItem(list);
}
setBackGroundQP(XmlUtil.getInteger(element, "BackGroundQP"));
setBackGroundSkipFlag(XmlUtil.getInteger(element, "BackGroundSkipFlag"));
}
}
@Data
public static class ROIItem {
@Schema(description = "感兴趣区域编号取值范围1~16")
private Integer ROISeq;
@Schema(description = "感兴趣区域左上角坐标取值范围0~19683")
private Integer TopLeft;
@Schema(description = "感兴趣区域右下角坐标取值范围0~19683")
private Integer BottomRight;
@Schema(description = "ROI区域编码质量等级0:一般1:较好2:好3:很好")
private Integer ROIQP;
public void fromXml(Element element) {
setROISeq(XmlUtil.getInteger(element, "ROISeq"));
setTopLeft(XmlUtil.getInteger(element, "TopLeft"));
setBottomRight(XmlUtil.getInteger(element, "BottomRight"));
setROIQP(XmlUtil.getInteger(element, "ROIQP"));
}
}
@Data
public static class SVCParam {
@Schema(description = "空域编码方式0:基本层1:1级增强2:2级增强3:3级增强")
private Integer SVCSpaceDomainMode;
@Schema(description = "时域编码方式0:基本层1:1级增强2:2级增强3:3级增强")
private Integer SVCTimeDomainMode;
@Schema(description = "空域编码能力0:不支持1:1级增强2:2级增强3:3级增强")
private Integer SVCSpaceSupportMode;
@Schema(description = "时域编码能力0:不支持1:1级增强2:2级增强3:3级增强")
private Integer SVCTimeSupportMode;
public void fromXml(Element element) {
setSVCSpaceDomainMode(XmlUtil.getInteger(element, "SVCSpaceDomainMode"));
setSVCTimeDomainMode(XmlUtil.getInteger(element, "SVCTimeDomainMode"));
setSVCSpaceSupportMode(XmlUtil.getInteger(element, "SVCSpaceSupportMode"));
setSVCTimeSupportMode(XmlUtil.getInteger(element, "SVCTimeSupportMode"));
}
}
@Data
public static class SurveillanceParam {
@Schema(description = "绝对时间信息开关0:关闭1:打开")
private Integer TimeFlag;
@Schema(description = "监控事件信息开关0:关闭1:打开")
private Integer EventFlag;
@Schema(description = "报警信息开关0:关闭1:打开")
private Integer AlertFlag;
public void fromXml(Element element) {
setTimeFlag(XmlUtil.getInteger(element, "TimeFlag"));
setEventFlag(XmlUtil.getInteger(element, "EventFlag"));
setAlertFlag(XmlUtil.getInteger(element, "AlertFlag"));
}
}
@Data
public static class AudioParam {
@Schema(description = "声音识别特征参数开关0:关闭1:打开")
private Integer AudioRecognitionFlag;
public void fromXml(Element element) {
setAudioRecognitionFlag(XmlUtil.getInteger(element, "AudioRecognitionFlag"));
}
}
}

View File

@ -0,0 +1,28 @@
package com.genersoft.iot.vmp.gb28181.bean;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.dom4j.Element;
@Data
@Schema(description = "视频参数范围")
public class VideoParamOpt implements DeviceConfigAware {
@Schema(description = "下载倍速范围,各可选参数以 '/' 分隔")
private String downloadSpeed;
@Schema(description = "摄像机支持的分辨率,多个分辨率值以 '/' 分隔")
private String resolution;
@Override
public String configType() {
return "VideoParamOpt";
}
@Override
public void fromXml(Element element) {
setDownloadSpeed(XmlUtil.getText(element, "DownloadSpeed"));
setResolution(XmlUtil.getText(element, "Resolution"));
}
}

View File

@ -1,15 +1,7 @@
/**
* 设备设置命令API接口
*
* @author lawrencehj
* @date 2021年2月2日
*/
package com.genersoft.iot.vmp.gb28181.controller;
import com.genersoft.iot.vmp.conf.security.JwtUtils;
import com.genersoft.iot.vmp.gb28181.bean.BasicParam;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@ -32,60 +24,106 @@ public class DeviceConfig {
@Autowired
private IDeviceService deviceService;
@GetMapping("/basicParam")
@Operation(summary = "基本配置设置命令", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "basicParam", description = "基础配置参数", required = true)
public DeferredResult<WVPResult<String>> homePositionApi(BasicParam basicParam) {
@GetMapping("/set/basicParam")
@Operation(summary = "设置基本配置", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "basicParam", description = "基础配置参数", required = true)
public DeferredResult<WVPResult<String>> homePositionApi(BasicParam basicParam) {
if (log.isDebugEnabled()) {
log.debug("基本配置设置命令API调用");
}
Assert.notNull(basicParam.getDeviceId(), "设备ID必须存在");
log.debug("基本配置设置命令API调用");
}
Assert.notNull(basicParam.getDeviceId(), "设备ID必须存在");
Device device = deviceService.getDeviceByDeviceId(basicParam.getDeviceId());
Assert.notNull(device, "设备不存在");
Device device = deviceService.getDeviceByDeviceId(basicParam.getDeviceId());
Assert.notNull(device, "设备不存在");
DeferredResult<WVPResult<String>> deferredResult = new DeferredResult<>();
deviceService.deviceBasicConfig(device, basicParam, (code, msg, data) -> {
deferredResult.setResult(new WVPResult<>(code, msg, data));
});
DeferredResult<WVPResult<String>> deferredResult = new DeferredResult<>();
deviceService.deviceBasicConfig(device, basicParam, (code, msg, data) -> {
deferredResult.setResult(new WVPResult<>(code, msg, data));
});
deferredResult.onTimeout(() -> {
log.warn("[设备配置] 超时, {}", device.getDeviceId());
deferredResult.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "超时"));
});
return deferredResult;
deferredResult.onTimeout(() -> {
log.warn("[设备配置] 超时, {}", device.getDeviceId());
deferredResult.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "超时"));
});
return deferredResult;
}
}
@Operation(summary = "查询基本参数配置", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
@Parameter(name = "channelId", description = "通道国标编号")
@GetMapping("/query/basicParam")
public DeferredResult<WVPResult<BasicParam>> queryBasicParam(String deviceId,
@RequestParam(required = false) String channelId) {
Device device = deviceService.getDeviceByDeviceId(deviceId);
Assert.notNull(device, "设备不存在");
DeferredResult<WVPResult<BasicParam>> deferredResult = new DeferredResult<>();
deviceService.deviceConfigQuery(device, channelId, BasicParam.class, (code, msg, data) -> {
data.setDeviceId(deviceId);
data.setChannelId(channelId);
deferredResult.setResult(new WVPResult<>(code, msg, data));
});
deferredResult.onTimeout(() -> {
log.warn("[获取设备配置] 超时, {}", device.getDeviceId());
deferredResult.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "超时"));
});
return deferredResult;
}
@Operation(summary = "设备配置查询", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
@Parameter(name = "channelId", description = "通道国标编号", required = true)
@Parameter(name = "configType", description = "配置类型, 可选值," +
"基本参数配置:BasicParam," +
"视频参数范围:VideoParamOpt, " +
"SVAC编码配置:SVACEncodeConfig, " +
"SVAC解码配置:SVACDecodeConfig。" +
"可同时查询多个配置类型,各类型以“/”分隔,")
@GetMapping("/query")
public DeferredResult<WVPResult<Object>> configDownloadApi(String deviceId,String configType,
@RequestParam(required = false) String channelId) {
if (log.isDebugEnabled()) {
log.debug("设备配置查询请求API调用");
}
Device device = deviceService.getDeviceByDeviceId(deviceId);
Assert.notNull(device, "设备不存在");
@Operation(summary = "查询视频参数范围", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
@Parameter(name = "channelId", description = "通道国标编号")
@GetMapping("/query/videoParamOpt")
public DeferredResult<WVPResult<VideoParamOpt>> queryVideoParamOpt(String deviceId,
@RequestParam(required = false) String channelId) {
Device device = deviceService.getDeviceByDeviceId(deviceId);
Assert.notNull(device, "设备不存在");
DeferredResult<WVPResult<VideoParamOpt>> deferredResult = new DeferredResult<>();
deviceService.deviceConfigQuery(device, channelId, VideoParamOpt.class, (code, msg, data) -> {
deferredResult.setResult(new WVPResult<>(code, msg, data));
});
deferredResult.onTimeout(() -> {
log.warn("[获取设备配置] 超时, {}", device.getDeviceId());
deferredResult.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "超时"));
});
return deferredResult;
}
DeferredResult<WVPResult<Object>> deferredResult = new DeferredResult<>();
@Operation(summary = "查询SVAC编码配置", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
@Parameter(name = "channelId", description = "通道国标编号")
@GetMapping("/query/svacEncodeConfig")
public DeferredResult<WVPResult<SVACEncodeConfig>> querySVACEncodeConfig(String deviceId,
@RequestParam(required = false) String channelId) {
Device device = deviceService.getDeviceByDeviceId(deviceId);
Assert.notNull(device, "设备不存在");
DeferredResult<WVPResult<SVACEncodeConfig>> deferredResult = new DeferredResult<>();
deviceService.deviceConfigQuery(device, channelId, SVACEncodeConfig.class, (code, msg, data) -> {
deferredResult.setResult(new WVPResult<>(code, msg, data));
});
deferredResult.onTimeout(() -> {
log.warn("[获取设备配置] 超时, {}", device.getDeviceId());
deferredResult.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "超时"));
});
return deferredResult;
}
deviceService.deviceConfigQuery(device, channelId, configType, (code, msg, data) -> {
deferredResult.setResult(new WVPResult<>(code, msg, data));
});
deferredResult.onTimeout(() -> {
log.warn("[获取设备配置] 超时, {}", device.getDeviceId());
deferredResult.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "超时"));
});
return deferredResult;
}
@Operation(summary = "查询SVAC解码配置", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
@Parameter(name = "channelId", description = "通道国标编号")
@GetMapping("/query/svacDecodeConfig")
public DeferredResult<WVPResult<SVACDecodeConfig>> querySVACDecodeConfig(String deviceId,
@RequestParam(required = false) String channelId) {
Device device = deviceService.getDeviceByDeviceId(deviceId);
Assert.notNull(device, "设备不存在");
DeferredResult<WVPResult<SVACDecodeConfig>> deferredResult = new DeferredResult<>();
deviceService.deviceConfigQuery(device, channelId, SVACDecodeConfig.class, (code, msg, data) -> {
deferredResult.setResult(new WVPResult<>(code, msg, data));
});
deferredResult.onTimeout(() -> {
log.warn("[获取设备配置] 超时, {}", device.getDeviceId());
deferredResult.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "超时"));
});
return deferredResult;
}
}

View File

@ -171,7 +171,7 @@ public interface IDeviceService {
void deviceBasicConfig(Device device, BasicParam basicParam, ErrorCallback<String> callback);
void deviceConfigQuery(Device device, String channelId, String configType, ErrorCallback<Object> callback);
<T extends DeviceConfigAware> void deviceConfigQuery(Device device, String channelId, Class<T> configClass, ErrorCallback<T> callback);
void teleboot(Device device);

View File

@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.gb28181.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.CommonCallback;
import com.genersoft.iot.vmp.common.enums.ChannelDataType;
import com.genersoft.iot.vmp.conf.UserSetting;
@ -331,7 +332,7 @@ public class DeviceServiceImpl implements IDeviceService {
redisCatchStorage.updateDevice(device);
try {
commander.deviceInfoQuery(device, null);
commander.deviceConfigQuery(device, null, "BasicParam", null);
commander.deviceConfigQuery(device, null, BasicParam.class, null);
} catch (InvalidArgumentException | SipException | ParseException e) {
log.error("[命令发送失败] 查询设备信息: {}", e.getMessage());
}
@ -500,37 +501,37 @@ public class DeviceServiceImpl implements IDeviceService {
}
private void catalogSubscribeExpire(String deviceId, SipTransactionInfo transactionInfo) {
log.info("[目录订阅] 到期, 编号: {}", deviceId);
Device device = getDeviceByDeviceId(deviceId);
if (device == null) {
log.info("[目录订阅] 到期, 编号: {}, 设备不存在, 忽略", deviceId);
return;
}
if (device.isOnLine() && device.getSubscribeCycleForCatalog() > 0) {
log.info("[目录订阅] 到期, 编号: {}", deviceId);
addCatalogSubscribe(device, transactionInfo);
}
}
private void mobilPositionSubscribeExpire(String deviceId, SipTransactionInfo transactionInfo) {
log.info("[移动位置订阅] 到期, 编号: {}", deviceId);
Device device = getDeviceByDeviceId(deviceId);
if (device == null) {
log.info("[移动位置订阅] 到期, 编号: {}, 设备不存在, 忽略", deviceId);
return;
}
if (device.isOnLine() && device.getSubscribeCycleForMobilePosition() > 0) {
log.info("[移动位置订阅] 到期, 编号: {}", deviceId);
addMobilePositionSubscribe(device, transactionInfo);
}
}
private void alarmSubscribeExpire(String deviceId, SipTransactionInfo transactionInfo) {
log.info("[报警订阅] 到期, 编号: {}", deviceId);
Device device = getDeviceByDeviceId(deviceId);
if (device == null) {
log.info("[移报警订阅] 到期, 编号: {}, 设备不存在, 忽略", deviceId);
return;
}
if (device.isOnLine() && device.getSubscribeCycleForAlarm() > 0) {
log.info("[报警订阅] 到期, 编号: {}", deviceId);
addAlarmSubscribe(device, transactionInfo);
}
}
@ -1160,16 +1161,21 @@ public class DeviceServiceImpl implements IDeviceService {
}
@Override
public void deviceConfigQuery(Device device, String channelId, String configType, ErrorCallback<Object> callback) {
public <T extends DeviceConfigAware> void deviceConfigQuery(Device device, String channelId, Class<T> configClass, ErrorCallback<T> callback) {
if (!userSetting.getServerId().equals(device.getServerId())) {
WVPResult<String> result = redisRpcService.deviceConfigQuery(device.getServerId(), device, channelId, configType);
callback.run(result.getCode(), result.getMsg(), result.getData());
WVPResult<Object> result = redisRpcService.deviceConfigQuery(device.getServerId(), device, channelId, configClass.getName());
if (result.getData() instanceof JSONObject) {
T obj = ((JSONObject) result.getData()).toJavaObject(configClass);
callback.run(result.getCode(), result.getMsg(), obj);
} else {
callback.run(result.getCode(), result.getMsg(), null);
}
return;
}
try {
sipCommander.deviceConfigQuery(device, channelId, configType, callback);
sipCommander.deviceConfigQuery(device, channelId, configClass, callback);
} catch (InvalidArgumentException | SipException | ParseException e) {
log.error("[命令发送失败] 获取设备配置: {}", e.getMessage());
callback.run(ErrorCode.ERROR100.getCode(), "命令发送: " + e.getMessage(), null);

View File

@ -248,9 +248,9 @@ public interface ISIPCommander {
*
* @param device 视频设备
* @param channelId 通道编码可选
* @param configType 配置类型
* @param configClass 配置类型
*/
void deviceConfigQuery(Device device, String channelId, String configType, ErrorCallback<Object> callback) throws InvalidArgumentException, SipException, ParseException;
<T extends DeviceConfigAware> void deviceConfigQuery(Device device, String channelId, Class<T> configClass, ErrorCallback<T> callback) throws InvalidArgumentException, SipException, ParseException;
/**
* 查询设备预置位置

View File

@ -29,6 +29,7 @@ import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
@ -846,11 +847,13 @@ public class SIPCommander implements ISIPCommander {
cmdXml.append("<Control>\r\n");
cmdXml.append("<CmdType>" + cmdType + "</CmdType>\r\n");
cmdXml.append("<SN>" + sn + "</SN>\r\n");
String channelId = basicParam.getChannelId();
if (ObjectUtils.isEmpty(channelId)) {
channelId = device.getDeviceId();
}
cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
// String channelId = basicParam.getChannelId();
// if (ObjectUtils.isEmpty(channelId)) {
// channelId = device.getDeviceId();
// }
// 此处经过详细测试 对于这里使用通道ID还是设备ID海康两者都支持 大华必须是设备ID宇视都支持但是不回复消息直接自己就注销重启
// 所以这里取值 device.getDeviceId()
cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
cmdXml.append("<BasicParam>\r\n");
if (!ObjectUtils.isEmpty(basicParam.getName())) {
cmdXml.append("<Name>" + basicParam.getName() + "</Name>\r\n");
@ -870,7 +873,7 @@ public class SIPCommander implements ISIPCommander {
cmdXml.append("</Control>\r\n");
MessageEvent<String> messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 1000L, callback);
MessageEvent<String> messageEvent = MessageEvent.getInstance(cmdType, sn + "", device.getDeviceId(), 2000L, callback);
messageSubscribe.addSubscribe(messageEvent);
Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
@ -1073,12 +1076,12 @@ public class SIPCommander implements ISIPCommander {
/**
* 查询设备配置
*
* @param device 视频设备
* @param channelId 通道编码可选
* @param configType 配置类型
* @param device 视频设备
* @param channelId 通道编码可选
* @param configClass 配置类型
*/
@Override
public void deviceConfigQuery(Device device, String channelId, String configType, ErrorCallback<Object> callback) throws InvalidArgumentException, SipException, ParseException {
public <T extends DeviceConfigAware> void deviceConfigQuery(Device device, String channelId, Class<T> configClass, ErrorCallback<T> callback) throws InvalidArgumentException, SipException, ParseException {
String cmdType = "ConfigDownload";
int sn = (int) ((Math.random() * 9 + 1) * 100000);
@ -1093,10 +1096,42 @@ public class SIPCommander implements ISIPCommander {
} else {
cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
}
T sample;
try {
sample = configClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
log.error("[命令发送失败] 实例化配置类: {}", e.getMessage());
if (callback != null) {
callback.run(ErrorCode.ERROR100.getCode(), "实例化失败: " + e.getMessage(), null);
}
return;
}
String configType = sample.configType();
cmdXml.append("<ConfigType>" + configType + "</ConfigType>\r\n");
cmdXml.append("</Query>\r\n");
MessageEvent<Object> messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 1000L, callback);
ErrorCallback<Object> wrappedCallback = (code, msg, data) -> {
if (callback != null) {
if (code == ErrorCode.SUCCESS.getCode() && data instanceof Element) {
Element responseElement = (Element) data;
Element configElement = responseElement.element(configType);
if (configElement != null) {
try {
T result = configClass.getDeclaredConstructor().newInstance();
result.fromXml(configElement);
callback.run(code, msg, result);
return;
} catch (Exception e) {
log.error("[设备配置查询] 创建实例失败", e);
}
}
}
callback.run(code, msg, null);
}
};
MessageEvent<Object> messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 1000L, wrappedCallback);
messageSubscribe.addSubscribe(messageEvent);
Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));

View File

@ -31,6 +31,7 @@ import javax.sip.RequestEvent;
import javax.sip.SipException;
import javax.sip.header.AuthorizationHeader;
import javax.sip.header.ContactHeader;
import javax.sip.header.ExpiresHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
@ -75,177 +76,45 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
sipProcessorObserver.addRequestProcessor(method, this);
}
/**
* 收到注册请求 处理
*/
@Override
public void process(RequestEvent evt) {
try {
SIPRequest request = (SIPRequest) evt.getRequest();
Response response = null;
boolean passwordCorrect = false;
// 注册标志
boolean registerFlag = request.getExpires().getExpires() != 0;
// 注销成功
FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
AddressImpl address = (AddressImpl) fromHeader.getAddress();
SipUri uri = (SipUri) address.getURI();
String deviceId = uri.getUser();
if (userSetting.isDeviceIdStrict()) {
// 严格模式下非20位设备ID不予处理
GbCode decode = GbCode.decode(deviceId);
if (decode == null) {
// 注册失败
response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
Response response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
return;
}
}
// 调整逻辑如果为设置公共密码那么就必须要预设用户信息否则无法注册
ExpiresHeader expiresHeader = request.getExpires();
if (expiresHeader == null) {
Response response = getMessageFactory().createResponse(Response.BAD_REQUEST, request);
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
return;
}
boolean registerFlag = expiresHeader.getExpires() != 0;
Device device = deviceService.getDeviceByDeviceId(deviceId);
RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request,
userSetting.getSipUseSourceIpAsRemoteAddress());
String requestAddress = remoteAddressInfo.getIp() + ":" + remoteAddressInfo.getPort();
String title = registerFlag ? "[注册请求]" : "[注销请求]";
log.info("{} 设备:{}, 开始处理: {}", title, deviceId, requestAddress);
String password = null;
if (device != null) {
if (device.getSipTransactionInfo() != null &&
request.getCallIdHeader().getCallId().equals(device.getSipTransactionInfo().getCallId())) {
log.info("{} 设备:{}, 注册续订: {}", title, device.getDeviceId(), device.getDeviceId());
if (registerFlag) {
device.setExpires(request.getExpires().getExpires());
device.setIp(remoteAddressInfo.getIp());
device.setPort(remoteAddressInfo.getPort());
device.setHostAddress(IpPortUtil.concatenateIpAndPort(remoteAddressInfo.getIp(), String.valueOf(remoteAddressInfo.getPort())));
device.setLocalIp(request.getLocalAddress().getHostAddress());
Response registerOkResponse = getRegisterOkResponse(request);
// 判断TCP还是UDP
ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
String transport = reqViaHeader.getTransport();
device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), registerOkResponse);
device.setRegisterTimeStamp(System.currentTimeMillis());
deviceService.online(device);
} else {
deviceService.offline(device);
}
return;
}else {
// 正常注册, 用户信息未设置密码并且公共密码也未设置则关闭鉴权
if (!ObjectUtils.isEmpty(device.getPassword()) || !ObjectUtils.isEmpty(sipConfig.getPassword())) {
password = (!ObjectUtils.isEmpty(device.getPassword())) ? device.getPassword() : sipConfig.getPassword();
}
// 如果设置了一个无密码的设备那么这里就会自动跳动后续会直接注册成功
}
}else {
if (ObjectUtils.isEmpty(sipConfig.getPassword())) {
log.info("{} 设备:{}, 地址: {}, 公共密码已经禁用,请添加用户信息后注册", title, deviceId, requestAddress);
response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
return;
}else {
password = sipConfig.getPassword();
}
}
AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
if (authHead == null && !ObjectUtils.isEmpty(password)) {
log.info(title + " 设备:{}, 回复401: {}", deviceId, requestAddress);
response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain());
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
return;
}
// 校验密码是否正确
passwordCorrect = ObjectUtils.isEmpty(password) ||
new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, password);
if (!passwordCorrect) {
// 注册失败
response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
response.setReasonPhrase("wrong password");
log.info("{} 设备:{}, 密码/SIP服务器ID错误, 回复403: {}", title, deviceId, requestAddress);
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
return;
}
// 携带授权头并且密码正确
response = getMessageFactory().createResponse(Response.OK, request);
// 如果主动禁用了Date头则不添加
if (!userSetting.isDisableDateHeader()) {
// 添加 date头
SIPDateHeader dateHeader = new SIPDateHeader();
// 使用自己修改的
GbSipDate gbSipDate = new GbSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
dateHeader.setDate(gbSipDate);
response.addHeader(dateHeader);
}
if (request.getExpires() == null) {
response = getMessageFactory().createResponse(Response.BAD_REQUEST, request);
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
return;
}
// 添加 Contact头
response.addHeader(request.getHeader(ContactHeader.NAME));
// 添加 Expires头
response.addHeader(request.getExpires());
if (device == null) {
device = new Device();
device.setStreamMode("TCP-PASSIVE");
device.setCharset("GB2312");
device.setGeoCoordSys("WGS84");
device.setMediaServerId("auto");
device.setDeviceId(deviceId);
device.setOnLine(false);
} else {
if (ObjectUtils.isEmpty(device.getStreamMode())) {
device.setStreamMode("TCP-PASSIVE");
}
if (ObjectUtils.isEmpty(device.getCharset())) {
device.setCharset("GB2312");
}
if (ObjectUtils.isEmpty(device.getGeoCoordSys())) {
device.setGeoCoordSys("WGS84");
}
}
device.setServerId(userSetting.getServerId());
device.setIp(remoteAddressInfo.getIp());
device.setPort(remoteAddressInfo.getPort());
device.setHostAddress(IpPortUtil.concatenateIpAndPort(remoteAddressInfo.getIp(), String.valueOf(remoteAddressInfo.getPort())));
device.setLocalIp(request.getLocalAddress().getHostAddress());
if (request.getExpires().getExpires() == 0) {
// 注销成功
registerFlag = false;
} else {
// 注册成功
device.setExpires(request.getExpires().getExpires());
registerFlag = true;
// 判断 TCP/UDP
ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
String transport = reqViaHeader.getTransport();
device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
}
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
// 注册成功
device.setRegisterTimeStamp(System.currentTimeMillis());
// 保存到 redis
if (registerFlag) {
log.info("[注册成功] deviceId: {}->{}", deviceId, requestAddress);
SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse) response);
device.setSipTransactionInfo(sipTransactionInfo);
deviceService.online(device);
registerHandler(device, request, remoteAddressInfo, deviceId, requestAddress);
} else {
log.info("[注销成功] deviceId: {}->{}", deviceId, requestAddress);
deviceService.offline(device);
cancellationHandler(device, request, remoteAddressInfo, deviceId, requestAddress);
}
redisCatchStorage.updateDeviceRegisterTimeStamp(List.of(device));
} catch (SipException | NoSuchAlgorithmException | ParseException e) {
log.error("未处理的异常 ", e);
}
@ -273,4 +142,149 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
}
private void registerHandler(Device device, SIPRequest request, RemoteAddressInfo remoteAddressInfo,
String deviceId, String requestAddress) throws SipException, NoSuchAlgorithmException, ParseException {
if (device != null && device.getSipTransactionInfo() != null &&
request.getCallIdHeader().getCallId().equals(device.getSipTransactionInfo().getCallId())) {
log.info("[注册续订] 设备:{}", device.getDeviceId());
device.setExpires(request.getExpires().getExpires());
device.setIp(remoteAddressInfo.getIp());
device.setPort(remoteAddressInfo.getPort());
device.setHostAddress(IpPortUtil.concatenateIpAndPort(remoteAddressInfo.getIp(), String.valueOf(remoteAddressInfo.getPort())));
device.setLocalIp(request.getLocalAddress().getHostAddress());
ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
String transport = reqViaHeader.getTransport();
device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
Response okResponse = getRegisterOkResponse(request);
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), okResponse);
device.setRegisterTimeStamp(System.currentTimeMillis());
deviceService.online(device);
return;
}
if (device == null && ObjectUtils.isEmpty(sipConfig.getPassword())) {
log.info("[注册请求] 设备:{}, 地址: {}, 公共密码已经禁用,请添加用户信息后注册", deviceId, requestAddress);
Response response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
return;
}
String password = device != null && !ObjectUtils.isEmpty(device.getPassword()) ? device.getPassword() : sipConfig.getPassword();
AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
if (!ObjectUtils.isEmpty(password) && authHead == null) {
log.info("[注册请求] 设备:{}, 回复401: {}", deviceId, requestAddress);
Response response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain());
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
return;
}
if (!ObjectUtils.isEmpty(password) && !new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, password)) {
log.info("[注册请求] 设备:{}, 密码/SIP服务器ID错误, 回复403: {}", deviceId, requestAddress);
Response response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
response.setReasonPhrase("wrong password");
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
return;
}
Response response = getMessageFactory().createResponse(Response.OK, request);
if (!userSetting.isDisableDateHeader()) {
SIPDateHeader dateHeader = new SIPDateHeader();
GbSipDate gbSipDate = new GbSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
dateHeader.setDate(gbSipDate);
response.addHeader(dateHeader);
}
response.addHeader(request.getHeader(ContactHeader.NAME));
response.addHeader(request.getExpires());
if (device == null) {
device = new Device();
device.setStreamMode("TCP-PASSIVE");
device.setCharset("GB2312");
device.setGeoCoordSys("WGS84");
device.setMediaServerId("auto");
device.setDeviceId(deviceId);
device.setOnLine(false);
} else {
if (ObjectUtils.isEmpty(device.getStreamMode())) {
device.setStreamMode("TCP-PASSIVE");
}
if (ObjectUtils.isEmpty(device.getCharset())) {
device.setCharset("GB2312");
}
if (ObjectUtils.isEmpty(device.getGeoCoordSys())) {
device.setGeoCoordSys("WGS84");
}
}
device.setServerId(userSetting.getServerId());
device.setIp(remoteAddressInfo.getIp());
device.setPort(remoteAddressInfo.getPort());
device.setHostAddress(IpPortUtil.concatenateIpAndPort(remoteAddressInfo.getIp(), String.valueOf(remoteAddressInfo.getPort())));
device.setLocalIp(request.getLocalAddress().getHostAddress());
device.setExpires(request.getExpires().getExpires());
ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
String transport = reqViaHeader.getTransport();
device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
device.setRegisterTimeStamp(System.currentTimeMillis());
SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse) response);
device.setSipTransactionInfo(sipTransactionInfo);
deviceService.online(device);
redisCatchStorage.updateDeviceRegisterTimeStamp(List.of(device));
log.info("[注册成功] deviceId: {}->{}", deviceId, requestAddress);
}
private void cancellationHandler(Device device, SIPRequest request, RemoteAddressInfo remoteAddressInfo,
String deviceId, String requestAddress) throws SipException, NoSuchAlgorithmException, ParseException {
if (device != null && device.getSipTransactionInfo() != null &&
request.getCallIdHeader().getCallId().equals(device.getSipTransactionInfo().getCallId())) {
Response response = getRegisterOkResponse(request);
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
deviceService.offline(device);
log.info("[注销成功] deviceId: {}->{}", deviceId, requestAddress);
return;
}
if (device == null && ObjectUtils.isEmpty(sipConfig.getPassword())) {
log.info("[注销请求] 设备:{}, 地址: {}, 公共密码已经禁用,请添加用户信息后注销", deviceId, requestAddress);
Response response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
return;
}
String password = device != null && !ObjectUtils.isEmpty(device.getPassword()) ? device.getPassword() : sipConfig.getPassword();
AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
if (!ObjectUtils.isEmpty(password) && authHead == null) {
log.info("[注销请求] 设备:{}, 回复401: {}", deviceId, requestAddress);
Response response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain());
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
return;
}
if (!ObjectUtils.isEmpty(password) && !new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, password)) {
log.info("[注销请求] 设备:{}, 密码/SIP服务器ID错误, 回复403: {}", deviceId, requestAddress);
Response response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
response.setReasonPhrase("wrong password");
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
return;
}
Response response = getRegisterOkResponse(request);
sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response);
if (device != null) {
deviceService.offline(device);
redisCatchStorage.updateDeviceRegisterTimeStamp(List.of(device));
}
log.info("[注销成功] deviceId: {}->{}", deviceId, requestAddress);
}
}

View File

@ -119,6 +119,7 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement
}else { // 由于上面已经判断都为null则直接返回所以这里device和parentPlatform必有一个不为null
messageHandler.handForPlatform(evt, parentPlatform, rootElement);
}
responseAck(request, Response.OK);
}else {
// 不支持的message
// 不存在则回复415

View File

@ -1,10 +1,8 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.Platform;
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
@ -31,9 +29,6 @@ public class ConfigDownloadResponseMessageHandler extends SIPRequestProcessorPar
@Autowired
private ResponseMessageHandler responseMessageHandler;
@Autowired
private DeferredResultHolder deferredResultHolder;
@Autowired
private IDeviceService deviceService;
@ -42,54 +37,29 @@ public class ConfigDownloadResponseMessageHandler extends SIPRequestProcessorPar
responseMessageHandler.addHandler(cmdType, this);
}
@Override
public void handForDevice(RequestEvent evt, Device device, Element element) {
try {
// 回复200 OK
responseAck((SIPRequest) evt.getRequest(), Response.OK);
} catch (SipException | InvalidArgumentException | ParseException e) {
log.error("[命令发送失败] 设备配置查询: {}", e.getMessage());
}
// 此处是对本平台发出DeviceControl指令的应答
JSONObject json = new JSONObject();
XmlUtil.node2Json(element, json);
if (log.isDebugEnabled()) {
log.debug(json.toJSONString());
}
JSONObject jsonObject = new JSONObject();
if (json.get("BasicParam") != null) {
jsonObject.put("BasicParam", json.getJSONObject("BasicParam"));
}
if (json.get("VideoParamOpt") != null) {
jsonObject.put("VideoParamOpt", json.getJSONObject("VideoParamOpt"));
}
if (json.get("SVACEncodeConfig") != null) {
jsonObject.put("SVACEncodeConfig", json.getJSONObject("SVACEncodeConfig"));
}
if (json.get("SVACDecodeConfig") != null) {
jsonObject.put("SVACDecodeConfig", json.getJSONObject("SVACDecodeConfig"));
}
responseMessageHandler.handMessageEvent(element, jsonObject);
responseMessageHandler.handMessageEvent(element, element);
JSONObject basicParam = json.getJSONObject("BasicParam");
Element basicParam = element.element("BasicParam");
if (basicParam != null) {
Integer heartBeatInterval = basicParam.getInteger("HeartBeatInterval");
Integer heartBeatCount = basicParam.getInteger("HeartBeatCount");
Integer positionCapability = basicParam.getInteger("PositionCapability");
Integer heartBeatInterval = XmlUtil.getInteger(basicParam, "HeartBeatInterval");
Integer heartBeatCount = XmlUtil.getInteger(basicParam, "HeartBeatCount");
Integer positionCapability = XmlUtil.getInteger(basicParam, "PositionCapability");
device.setHeartBeatInterval(heartBeatInterval);
device.setHeartBeatCount(heartBeatCount);
device.setPositionCapability(positionCapability);
deviceService.updateDeviceHeartInfo(device);
}
}
@Override
public void handForPlatform(RequestEvent evt, Platform parentPlatform, Element element) {
// 不会收到上级平台的心跳信息
}
}

View File

@ -55,7 +55,7 @@ public interface IRedisRpcService {
WVPResult<String> deviceBasicConfig(String serverId, Device device, BasicParam basicParam);
WVPResult<String> deviceConfigQuery(String serverId, Device device, String channelId, String configType);
WVPResult<Object> deviceConfigQuery(String serverId, Device device, String channelId, String configType);
void teleboot(String serverId, Device device);

View File

@ -9,6 +9,7 @@ import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse;
import com.genersoft.iot.vmp.gb28181.bean.BasicParam;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceConfigAware;
import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
import com.genersoft.iot.vmp.gb28181.service.IDeviceService;
import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcController;
@ -121,7 +122,7 @@ public class RedisRpcDeviceController extends RpcController {
JSONObject paramJson = JSONObject.parseObject(request.getParam().toString());
String deviceId = paramJson.getString("deviceId");
String channelId = paramJson.getString("channelId");
String configType = paramJson.getString("configType");
String className = paramJson.getString("configType");
Device device = deviceService.getDeviceByDeviceId(deviceId);
@ -131,12 +132,24 @@ public class RedisRpcDeviceController extends RpcController {
response.setBody("param error");
return response;
}
deviceService.deviceConfigQuery(device, channelId, configType, (code, msg, data) -> {
response.setStatusCode(code);
response.setBody(new WVPResult<>(code, msg, data));
// 手动发送结果
sendResponse(response);
});
try {
Class<?> rawClass = Class.forName(className);
if (!DeviceConfigAware.class.isAssignableFrom(rawClass)) {
response.setStatusCode(ErrorCode.ERROR400.getCode());
response.setBody("invalid config type: " + className);
return response;
}
Class<? extends DeviceConfigAware> configClass = rawClass.asSubclass(DeviceConfigAware.class);
deviceService.deviceConfigQuery(device, channelId, configClass, (code, msg, data) -> {
response.setStatusCode(code);
response.setBody(new WVPResult<>(code, msg, data));
sendResponse(response);
});
} catch (ClassNotFoundException e) {
response.setStatusCode(ErrorCode.ERROR400.getCode());
response.setBody("unknown config type: " + className);
return response;
}
return null;
}

View File

@ -351,9 +351,9 @@ public class RedisRpcServiceImpl implements IRedisRpcService {
}
@Override
public WVPResult<String> deviceConfigQuery(String serverId, Device device, String channelId, String configType) {
public WVPResult<Object> deviceConfigQuery(String serverId, Device device, String channelId, String configType) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("device", device.getDeviceId());
jsonObject.put("deviceId", device.getDeviceId());
jsonObject.put("channelId", channelId);
jsonObject.put("configType", configType);
RedisRpcRequest request = buildRequest("device/deviceConfigQuery", jsonObject);

View File

@ -113,10 +113,19 @@ export function subscribeForAlarm(params) {
})
}
export function queryBasicParam(deviceId) {
export function queryBasicParam(params) {
return request({
method: 'get',
url: `/api/device/config/query/${deviceId}/BasicParam`
url: '/api/device/config/query/basicParam',
params: params
})
}
export function setBasicParam(params) {
return request({
method: 'get',
url: '/api/device/config/set/basicParam',
params
})
}

View File

@ -14,6 +14,7 @@ import {
queryDeviceTree,
queryHasStreamChannels,
resetGuard,
setBasicParam,
setGuard,
subscribeCatalog, subscribeForAlarm,
subscribeMobilePosition,
@ -124,9 +125,19 @@ const actions = {
})
})
},
queryBasicParam({ commit }, deviceId) {
queryBasicParam({ commit }, params) {
return new Promise((resolve, reject) => {
queryBasicParam(deviceId).then(response => {
queryBasicParam(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
setBasicParam({ commit }, params) {
return new Promise((resolve, reject) => {
setBasicParam(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {

View File

@ -0,0 +1,132 @@
<template>
<div id="dhBasicPropertyPage">
<div class="basic-property-body">
<div class="config-section">
<el-form ref="formRef" :model="form" label-width="140px">
<el-form-item label="设备名称" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="注册过期时间(秒)" prop="expiration">
<el-input-number v-model="form.expiration" :min="60" :max="86400" />
</el-form-item>
<el-form-item label="心跳间隔(秒)" prop="heartBeatInterval">
<el-input-number v-model="form.heartBeatInterval" :min="5" :max="3600" />
</el-form-item>
<el-form-item label="心跳超时次数" prop="heartBeatCount">
<el-input-number v-model="form.heartBeatCount" :min="1" :max="60" />
</el-form-item>
<el-form-item label="定位功能" prop="positionCapability">
<el-select v-model="form.positionCapability">
<el-option :value="0" label="不支持" />
<el-option :value="1" label="GPS定位" />
<el-option :value="2" label="北斗定位" />
</el-select>
</el-form-item>
<el-form-item label="经度" v-if="form.longitude !== null">
<span class="readonly-field">{{ form.longitude }}</span>
</el-form-item>
<el-form-item label="纬度" v-if="form.latitude !== null">
<span class="readonly-field">{{ form.latitude }}</span>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
<el-button :loading="loading" @click="handleRefresh">刷新</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'BasicPropertyPage',
props: {
deviceId: { type: String, default: null },
channelDeviceId: { type: String, default: null }
},
data() {
return {
loading: false,
saving: false,
form: {
deviceId: this.deviceId,
channelId: this.channelDeviceId,
name: '',
expiration: 3600,
heartBeatInterval: 60,
heartBeatCount: 3,
positionCapability: 0,
longitude: null,
latitude: null
}
}
},
mounted() {
this.loadConfig()
},
methods: {
loadConfig() {
this.loading = true
this.$store.dispatch('device/queryBasicParam', {
deviceId: this.deviceId,
channelId: this.channelDeviceId
})
.then(data => {
this.form = { ...this.form, ...data }
}).finally(() => {
this.loading = false
})
},
handleSave() {
this.saving = true
this.$store.dispatch('device/setBasicParam', this.form)
.then(() => {
this.$message({ showClose: true, message: '保存成功', type: 'success' })
})
.catch((error) => {
this.$message({ showClose: true, message: error, type: 'error' })
})
.finally(() => {
this.saving = false
})
},
handleRefresh() {
this.loadConfig()
}
}
}
</script>
<style scoped>
#dhBasicPropertyPage {
height: 100%;
display: flex;
flex-direction: column;
}
.basic-property-body {
display: flex;
gap: 16px;
min-height: 30vw;
flex: 1;
padding-top: 16px;
}
.config-section {
flex: 1;
min-width: 0;
max-width: 500px;
}
.config-section .el-input,
.config-section .el-input-number,
.config-section .el-select {
width: 100%;
}
.readonly-field {
color: #606266;
line-height: 32px;
}
</style>

View File

@ -0,0 +1,93 @@
<template>
<div id="dhCameraConfigPage">
<el-page-header content="相机配置" @back="$emit('close')" />
<div class="camera-config-body">
<div class="config-sidebar">
<el-menu :default-active="activeTab" @select="handleMenuSelect">
<el-menu-item index="base">
<i class="iconfont icon-wxbzhuye" style="margin-right: 10px" />
<span>基础属性</span>
</el-menu-item>
<el-menu-item index="image">
<i class="iconfont icon-shishiyulan" style="margin-right: 10px" />
<span>图像属性</span>
</el-menu-item>
<el-menu-item index="encode">
<i class="iconfont icon-shipin" style="margin-right: 10px" />
<span>编码设置</span>
</el-menu-item>
<el-menu-item index="alarm">
<i class="iconfont icon-baojing" style="margin-right: 10px" />
<span>报警配置</span>
</el-menu-item>
</el-menu>
</div>
<div class="config-content">
<basicPropertyConfig v-if="activeTab === 'base'" :device-id="deviceId" :channel-device-id="channelDeviceId" />
<imagePropertyConfig v-if="activeTab === 'image'" :device-id="deviceId" :channel-device-id="channelDeviceId" />
<div v-if="activeTab === 'osd'" class="placeholder-tab">OSD配置开发中</div>
<div v-if="activeTab === 'encode'" class="placeholder-tab">编码设置开发中</div>
<div v-if="activeTab === 'alarm'" class="placeholder-tab">报警配置开发中</div>
</div>
</div>
</div>
</template>
<script>
import basicPropertyConfig from './basicPropertyConfig.vue'
import imagePropertyConfig from './imagePropertyConfig.vue'
export default {
name: 'CameraConfigPage',
components: { basicPropertyConfig, imagePropertyConfig },
props: {
deviceId: { type: String, default: null },
channelDeviceId: { type: String, default: null }
},
data() {
return {
activeTab: 'base'
}
},
methods: {
handleMenuSelect(index) {
this.activeTab = index
}
}
}
</script>
<style scoped>
#dhCameraConfigPage {
height: 100%;
display: flex;
flex-direction: column;
}
.camera-config-body {
flex: 1;
display: flex;
overflow: hidden;
padding-top: 16px;
}
.config-sidebar {
width: 160px;
flex: none;
border-right: 1px solid #e6e6e6;
}
.config-sidebar .el-menu {
border-right: none;
}
.config-content {
flex: 1;
overflow: auto;
padding: 0 16px;
}
.placeholder-tab {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
color: #909399;
font-size: 16px;
}
</style>

View File

@ -0,0 +1,84 @@
<template>
<div id="dhImagePropertyPage">
<div class="image-property-body">
<div class="player-section">
<playerPtzPanel :device-id="deviceId" :channel-device-id="channelDeviceId" />
</div>
<div class="config-section">
<el-tabs v-model="activeTab" tab-position="left" class="config-tabs">
<el-tab-pane label="OSD配置" name="osd">
<div class="placeholder-tab">OSD配置开发中</div>
</el-tab-pane>
<el-tab-pane label="视角" name="control">
<div class="placeholder-tab">视角开发中</div>
</el-tab-pane>
<el-tab-pane label="画面遮挡" name="pictureMask">
<div class="placeholder-tab">画面遮挡开发中</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</template>
<script>
import playerPtzPanel from '../common/playerPtzPanel.vue'
export default {
name: 'ImagePropertyPage',
components: { playerPtzPanel },
props: {
deviceId: { type: String, default: null },
channelDeviceId: { type: String, default: null }
},
data() {
return {
activeTab: 'osd'
}
}
}
</script>
<style scoped>
#dhImagePropertyPage {
height: 100%;
display: flex;
flex-direction: column;
}
.image-property-body {
display: flex;
gap: 16px;
min-height: 30vw;
flex: 1;
padding-top: 16px;
}
.player-section {
width: 520px;
flex: none;
display: flex;
flex-direction: column;
}
.config-section {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.config-tabs {
flex: 1;
min-height: 0;
}
.placeholder-tab {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
color: #909399;
font-size: 16px;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div id="channelList" style="height: calc(100vh - 124px);">
<div v-if="!editId && !ptzConfigChannelDeviceId" style="height: 100%">
<div v-if="!editId && !ptzConfigChannelDeviceId && !cameraConfigDeviceId" style="height: 100%">
<el-form :inline="true" size="mini">
<el-form-item style="margin-right: 2rem">
<el-page-header content="通道列表" @back="showDevice" />
@ -193,6 +193,8 @@
云台配置</el-dropdown-item>
<el-dropdown-item command="audioTalk" :disabled="device == null || device.online === 0">
语音对讲</el-dropdown-item>
<el-dropdown-item command="cameraConfig" :disabled="device == null || device.online === 0" divided>
相机配置</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
@ -214,6 +216,7 @@
<devicePlayer ref="devicePlayer" />
<channel-edit v-if="editId" :id="editId" :close-edit="closeEdit" />
<ptzConfig v-if="ptzConfigChannelDeviceId" :device-id="ptzConfigDeviceId" :channel-device-id="ptzConfigChannelDeviceId" @close="closePtzConfig" />
<cameraConfig v-if="cameraConfigDeviceId" :device-id="deviceId" :channel-device-id="cameraConfigDeviceId" @close="cameraConfigDeviceId = null" />
<audioTalk ref="audioTalk" />
</div>
@ -224,6 +227,7 @@ import devicePlayer from '../dialog/devicePlayer.vue'
import audioTalk from '../dialog/audioTalk.vue'
import Edit from './edit.vue'
import ptzConfig from '@/views/device/channel/ptzConfig.vue'
import cameraConfig from './cameraConfig.vue'
export default {
name: 'ChannelList',
@ -231,7 +235,8 @@ export default {
devicePlayer,
audioTalk,
ChannelEdit: Edit,
ptzConfig
ptzConfig,
cameraConfig
},
props: {
defaultPage: {
@ -270,6 +275,7 @@ export default {
editId: null,
ptzConfigDeviceId: null,
ptzConfigChannelDeviceId: null,
cameraConfigDeviceId: null,
loadSnap: {},
ptzTypes: {
0: '未知',
@ -402,6 +408,8 @@ export default {
this.ptzConfigChannelDeviceId = itemData.deviceId
} else if (command === 'audioTalk') {
this.$refs.audioTalk.openDialog(this.deviceId, itemData.deviceId)
} else if (command === 'cameraConfig') {
this.cameraConfigDeviceId = itemData.deviceId
}
},
queryRecords: function(itemData) {