You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Onvif WS-BaseNotification事件通知解析与反序列化问题咨询

解决Onvif WS-BaseNotification通知的反序列化与主题映射问题

我之前也踩过Onvif事件处理的坑,WS-BaseNotification的通用框架和Onvif的自定义事件扩展确实容易让人混淆,咱们一步步拆解解决你的问题:

1. 先理清核心逻辑:WS-BaseNotification是壳,Onvif事件是内容

WS-BaseNotification定义了通用的通知结构(就是你拿到的NotificationMessageHolderType),而Onvif在这个框架上做了定制:

  • Topic元素是事件的"分类标签",用来区分不同类型的事件(比如你的tns1:AccessPoint/State/Enabled就是接入点状态变化事件)
  • Message里的SourceKeyData才是Onvif规范定义的事件核心内容,对应你提到的"谁产生的"、"何时发生"、"发生了什么"

你已经成功把通知反序列化成了org.onvif.ver10.schema.Message,这一步是对的,接下来就是把这个通用的Message对象,根据Topic映射到你自己的事件实现类(比如AccessPointState)。

2. 修正你的反序列化与映射代码

你不需要手动调用JAXB去反序列化Node,因为NotificationMessageHolderTypegetMessage()方法已经返回了现成的org.onvif.ver10.schema.Message对象。你要做的是根据Topic的值,把Message里的SourceData提取出来,填充到自定义事件类中。

给你调整后的代码示例:

PullMessagesResponse pullMessagesResponse = pullPointSubscription.pullMessages(pullMessages);
List<NotificationMessageHolderType> notificationMessages = pullMessagesResponse.getNotificationMessage();

for (NotificationMessageHolderType msgHolder : notificationMessages) {
    // 1. 获取事件主题,判断事件类型
    TopicExpressionType topic = msgHolder.getTopic();
    String topicValue = topic.getValue(); // 拿到 "tns1:AccessPoint/State/Enabled"
    
    // 2. 获取Onvif标准的Message对象
    org.onvif.ver10.schema.Message onvifMessage = msgHolder.getMessage();
    
    // 3. 根据主题映射到自定义事件类
    if ("tns1:AccessPoint/State/Enabled".equals(topicValue)) {
        AccessPointState accessPointEvent = new AccessPointState();
        
        // 填充Source中的设备标识信息
        if (onvifMessage.getSource() != null) {
            for (Item item : onvifMessage.getSource().getSimpleItemOrElementItem()) {
                if (item instanceof SimpleItem) {
                    SimpleItem simpleItem = (SimpleItem) item;
                    switch (simpleItem.getName()) {
                        case "AccessPointToken":
                            accessPointEvent.setAccessPointToken(simpleItem.getValue());
                            break;
                        case "Device Source":
                            accessPointEvent.setDeviceSource(simpleItem.getValue());
                            break;
                    }
                }
            }
        }
        
        // 填充Data中的事件详情
        if (onvifMessage.getData() != null) {
            for (Item item : onvifMessage.getData().getSimpleItemOrElementItem()) {
                if (item instanceof SimpleItem) {
                    SimpleItem simpleItem = (SimpleItem) item;
                    if ("State".equals(simpleItem.getName())) {
                        // 这里根据实际类型转换,比如State是int或boolean
                        accessPointEvent.setState(Integer.parseInt(simpleItem.getValue()));
                    }
                }
            }
        }
        
        // 处理你的自定义事件
        handleAccessPointEvent(accessPointEvent);
    }
    // 其他主题的事件处理逻辑可以继续添加...
}

3. 利用GetEventProperties实现动态映射(进阶方案)

你提到的GetEventPropertiesResponse是关键!这个接口会返回设备实际支持的所有主题,以及每个主题对应的元数据(比如Data项的名称、类型、可选性)。用它可以实现更灵活的事件映射,不用硬编码主题值:

步骤说明:

  1. 获取设备的事件属性
GetEventPropertiesResponse eventProperties = onvifDevice.getEventPort().getEventProperties();
  1. 解析主题元数据
    遍历eventProperties.getTopicSet().getTopic()eventProperties.getTopicComplexType(),可以拿到每个主题的路径、对应的Data结构(比如哪些SimpleItem是必须的,类型是什么)。
  2. 动态生成事件对象或构建映射关系
    • 如果是固定设备,可以提前根据这些元数据编写对应的事件类(比如AccessPointState
    • 如果要适配多设备,可以用反射(比如Apache Commons BeanUtils)或者动态代理,在运行时根据元数据创建对象并填充属性,避免硬编码每个主题的处理逻辑。

4. 关于Schema的补充说明

你找不到的主题结构描述,其实是设备厂商基于Onvif标准扩展的内容,不在通用的Onvif WSDL里。Onvif的核心规范里只定义了事件的通用结构,而设备特定的主题和数据结构,必须通过GetEventProperties接口获取——这也是Onvif规范要求设备必须实现的接口。

内容的提问来源于stack exchange,提问作者Manticore

火山引擎 最新活动