ONVIF-gSOAP

130 阅读7分钟

ONVIF-gSOAP

ONVIF(开放网络视频接口论坛)标准使用Web Services技术,而gSOAP是一个用于开发Web Services服务的工具包,它能够将C/C++代码与SOAP/WSDL绑定,从而方便地实现ONVIF规范的各个服务.

通俗来讲就是:ONVIF是一种安防接口规范,gSoap工具将ONVIF的接口规范生成对应代码。

ONVIF

官网:Home - ONVIF Mandarin

Onvif接口文档: 网络接口规范 - ONVIF Mandarin
左边一列是解释文档、右边是接口文档(WSDL),右边的看不懂了再去看左边的。

总的接口文档 ONVIF Core Specification

测试工具下载(需要会员账号):onvif.org/member-tool…

image.png

gSOAP

官网源码下载(有商业版、开源版两个版本):Genivia - gSOAP C and C++ SOAP/XML Web Services Tools
开发者文档: www.genivia.com/dev.html
工具下载,编译指南:Genivia Product Downloads

步骤

  1. 获取ONVIF的WSDL文件。
  2. onvif接口转换为头文件:使用gSOAP的工具wsdl2h将WSDL文件转化为头文件(比如onvif.h)
  3. 根据头文件(接口)转换成对应的C/C++代码:使用gSOAP的工具soapcpp2,将头文件转换成C/C++代码。
  4. 在项目中使用生成的代码,并实现ONVIF客户端或服务器。

gSOAP编译

# sudo apt-get install flex bison
# apt install autoconf automake libtool
#将编译好的OpenSSL的include和lib拷贝到gsoap-2.8/
# ./configure --prefix=/user_code/build_env/gsoap-2.8/install/ --with-openssl=/user_code/build_env/gsoap-2.8/openssl
# make
# make install

得到gSoap代码生成工具

bin/soapcpp2
bin/wsdl2h
share/gsoap/WS/typemap.dat

确认gsoap是否支持ssl,存在libsoapssl.a, 说明已经支持ssl: image.png

下载WSDL

WSDL下载
在线地址是网络接口规范右侧的服务器定义URL

支持鉴权

方法一:在生成onvif.h文件里加上#import "wsse.h" 方法二:在typemap.dat结尾出添加 #import "wsse.h",这样就不用每次生成onvif.h头文件都要修改一次了

# C语言需要打开xsd__dateTime注释, C++ 不需要
# xsd__dateTime = #import "custom/struct_timeval.h" | xsd__dateTime

wstop__TopicSetType = $ _XML __mixed;

wsnt__FilterType = $ wsnt__TopicExpressionType;

_wsnt__NotificationMessageHolderType_Message = $ _tt__Message* tt__Message;
[
#import "wsse.h"  支持鉴权
#import "duration.h" 可选
#import "wsa.h" 可选
#import "ds.h" 可选
]

WSDL转换为头文件

    Usage: wsdl2h [-a] [-b] [-c|-c++|-c++11|-c++14|-c++17] [-D] [-d] [-e] [-F] [-f] [-g] [-h] [-I path] [-i] [-j] [-k] [-L] [-l] [-M] [-m] [-N name] [-n name] [-O1|-O2|-O3|-O4|-Ow2|-Ow3|-Ow4] [-P|-p] [-Q] [-q name] [-R] [-r proxyhost[:port[:uid:pwd]]] [-r:uid:pwd] [-Sname] [-s] [-T] [-t typemapfile] [-U] [-u] [-V] [-v] [-w] [-W] [-x] [-y] [-z#] [-_] [-o outfile.h] infile.wsdl infile.xsd http://www... ...

    #../wsdl2h -s -t ../../share/gsoap/WS/typemap.dat  -o onvif.h ../../specs-21.12/wsdl/ver10/device/wsdl/devicemgmt.wsdl ../../specs-21.12/wsdl/ver10/events/wsdl/event.wsdl ../../specs-21.12/wsdl/ver10/schema/onvif.xsd ../../specs-21.12/wsdl/ver10/schema/common.xsd ../../specs-21.12/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl ../../specs-21.12/wsdl/ver10/media/wsdl/media.wsdl ../../specs-21.12/wsdl/ver20/media/wsdl/media.wsdl ../../specs-21.12/wsdl/ver20/imaging/wsdl/imaging.wsdl ../../specs-21.12/wsdl/ver20/ptz/wsdl/ptz.wsdl ../../specs-21.12/wsdl/ver10/deviceio.wsdl ../../specs-21.12/wsdl/ver20/analytics/wsdl/analytics.wsdl ../../specs-21.12/wsdl/ver20/analytics/rules.xsd ../../specs-21.12/wsdl/ver10/schema/metadatastream.xsd ../../specs-21.12/wsdl/ver10/recording.wsdl ../../specs-21.12/wsdl/ver10/replay.wsdl ../../specs-21.12/wsdl/ver10/search.wsdl ../../specs-21.12/wsdl/ver10/receiver.wsdl ../../specs-21.12/wsdl/ver10/display.wsdl ../../specs-21.12/wsdl/remotediscovery.wsdl

    或者直接指定在线的地址
    ../wsdl2h -s -t ../../share/gsoap/WS/typemap.dat  -o onvif.h https://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl https://www.onvif.org/ver10/events/wsdl/event.wsdl https://www.onvif.org/ver10/schema/onvif.xsd https://www.onvif.org/ver10/schema/common.xsd https://www.onvif.org/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl https://www.onvif.org/ver10/media/wsdl/media.wsdl https://www.onvif.org/ver20/media/wsdl/media.wsdl https://www.onvif.org/ver20/imaging/wsdl/imaging.wsdl https://www.onvif.org/ver20/ptz/wsdl/ptz.wsdl https://www.onvif.org/ver10/deviceio.wsdl https://www.onvif.org/ver20/analytics/wsdl/analytics.wsdl https://www.onvif.org/ver20/analytics/rules.xsd https://www.onvif.org/ver10/schema/metadatastream.xsd https://www.onvif.org/ver10/recording.wsdl https://www.onvif.org/ver10/replay.wsdl https://www.onvif.org/ver10/search.wsdl https://www.onvif.org/ver10/receiver.wsdl https://www.onvif.org/ver10/display.wsdl https://www.onvif.org/ver10/actionengine.wsdl

    -s:不生成STL代码。
    -c:生成C代码,默认是c++

根据WSDL转换的头文件转换为可调用的cpp

Usage: soapcpp2 [-0|-1|-2] [-C|-S|-CS] [-A] [-a] [-b] [-c|-c++|-c++11|-c++14|-c++17] [-d path] [-Ec] [-Ed] [-Et] [-e] [-f N] [-g] [-h] [-i] [-I path:path:...] [-L] [-l] [-m] [-n] [-p name] [-Q name] [-q name] [-r] [-s] [-T] [-t] [-u] [-V] [-v] [-w] [-x] [-y] [-z#] [infile]
#../soapcpp2 -2 -x -j -L -I../../share/gsoap/ -I../../share/gsoap/custom/ -I../../share/gsoap/import/ ./onvif.h
-x : 不生成xml
-2 : 使用soap1.2版本
-c : 生成c语言代码
-j : 生成c++代码,类似智能指针管理内存,要去掉-c
-L :不生成soapClientLib.cpp soapServerLib.cpp文件

最终生成:

  • soapC.cpp: 数据类型的序列化/反序列化实现
  • soapXXXProxy.h: C++ 代理类的头文件(Onvif接口)
  • soapProxy.cpp: C++ 代理类的实现文件(代替了 soapClient.cpp
  • soapH.h: 主要头文件
  • soapStub.h: 存根/定义头文件
  • soapXXXServer.cpp: 服务器端实现(如果你有服务端代码)(代替了 soapServer.cpp
  • *.nsmap或 *.xml: XML 命名空间映射文件(里面的内容都是相同的,只要一个就好)

错误:

wsa5.h(280): *WARNING*: Duplicate declaration of 'SOAP_ENV__Fault' (already declared at line 268)
wsa5.h(290): **ERROR**: service operation name clash: struct/class 'SOAP_ENV__Fault' already declared at wsa.h:278

原因是import/wsa.h和import/wsa5.h的定义SOAP_ENV__Fault类型冲突,修改wsa5.h的SOAP_ENV__Fault为SOAP_ENV__Fault_2 然后清理make clean & make distclean,重新生成soapcpp2工具。

错误:

**  The gSOAP code generator for C and C++, soapcpp2 release 2.8.139
**  Copyright (C) 2000-2025 Genivia Inc. All Rights Reserved.
**  The soapcpp2 tool and its generated software are released under the GPL.
**  ----------------------------------------------------------------------------
**  A commercial use license is available from Genivia Inc., contact@genivia.com
**  ----------------------------------------------------------------------------

Critical error: Cannot open file "custom/struct_timeval.h" to import: No such file or directory
Hint: use option -I<path> (for example -Igsoap:gsoap/import:gsoap/custom:.)

需要指定gsoap目录,加上-I../../share/gsoap/

生成成功后,得到(xml报文模板,软件用不到,用于参考报文格式):

wsdd.nsmap 全局xml命名空间, 对应每个库的接口
DeviceBinding.nsmap
soapH.h  soap接口函数
soapC.cpp 公共函数
soapClient.cpp 业务逻辑函数
soapClientLib.cpp
soapServer.cpp 业务逻辑函数
soapServerLib.cpp
soapStub.h 业务逻辑函数

编译错误:

/bin/ld: CMakeFiles/OnvifTest.dir/gsoap_src/stdsoap2.c.o: in function `soap_init_REQUIRE_lib_v208139':
/user_code/QtCode/OnvifTest/gsoap_src/stdsoap2.c:12785: undefined reference to `namespaces'

namespaces定义在nsmap文件中,解决:

#include "wsdd.nsmap"

错误:

In file included from /user_code/QtCode/OnvifTest/gsoap_src/custom/duration.c:58:0:
/user_code/QtCode/OnvifTest/gsoap_src/custom/duration.c:61:81: error: expected ‘)’ before ‘*’ token
 SOAP_FMAC3 void SOAP_FMAC4 soap_default_xsd__duration(struct soap *soap, LONG64 *a)

放开typemap.dat注释:

xsd__duration = #import "custom/duration.h" | xsd__duration

C++测试程序

不使用编好的gsoap库,直接使用gsoap源文件,需要将用到的*.c文件全部改成*.cpp。

cmake_minimum_required(VERSION 3.16)

project(OnvifTest VERSION 0.1 LANGUAGES CXX C)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

#add_compile_definitions(WITH_OPENSSL)
#add_compile_definitions(WITH_DOM)
#add_compile_definitions(WITH_OPENSSL_DTLS)

add_definitions(
    -DWITH_OPENSSL          # 启用OpenSSL
    -DWITH_DOM              # 启用DOM
    -DWITH_OPENSSL_DTLS
    -DSOAP_DEBUG
)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)

set(C_SOURCE
    # 生成的c代码
    gsoap_generated/soapC.cpp
    gsoap_generated/soapwsddProxy.cpp
    gsoap_generated/soapDeviceBindingProxy.cpp

    # gSOAP核心
    gsoap_src/stdsoap2.cpp
    gsoap_src/dom.cpp

    # 日期时间序列化插件(关键!)
    gsoap_src/custom/struct_timeval.cpp
    gsoap_src/custom/duration.cpp


    # gSOAP插件(必须完整)
    gsoap_src/plugin/mecevp.cpp      # 消息加密(关键!解决 soap_mec_size)
    gsoap_src/plugin/smdevp.cpp      # 签名/加密
    gsoap_src/plugin/httpda.cpp
    gsoap_src/plugin/wsseapi.cpp     # WS-Security
    gsoap_src/plugin/wsaapi.cpp      # WS-Addressing
    gsoap_src/plugin/threads.cpp     # 多线程
)
# 设置为C语言源文件
#set_source_files_properties(${C_SOURCE} PROPERTIES LANGUAGE C)

set(PROJECT_SOURCES
    mainwindow.ui
    main.cpp
    mainwindow.h
    mainwindow.cpp
    gsoap_generated/soapStub.h
    gsoap_generated/soapH.h

    gsoap_src/stdsoap2.h
    gsoap_src/stdsoap2.cpp
    gsoap_src/import/wsa.h
    gsoap_src/plugin/smdevp.h
    gsoap_src/plugin/wsseapi.h
    gsoap_src/plugin/threads.h

    # 日期时间序列化插件(关键!)
    ${C_SOURCE}
)

if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    qt_add_executable(OnvifTest
        MANUAL_FINALIZATION
        #${HEADER_FILES}
        ${PROJECT_SOURCES}
    )
# Define target properties for Android with Qt 6 as:
#    set_property(TARGET OnvifTest APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
#                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
    if(ANDROID)
        add_library(OnvifTest SHARED
            #${HEADER_FILES}
            ${PROJECT_SOURCES}
        )
# Define properties for Android with Qt 5 after find_package() calls as:
#    set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
    else()
        add_executable(OnvifTest
            ${PROJECT_SOURCES}
        )
    endif()
endif()

target_include_directories(OnvifTest PRIVATE ${PROJECT_SOURCE_DIR}/gsoap_generated)
target_include_directories(OnvifTest PRIVATE ${PROJECT_SOURCE_DIR}/gsoap_generated)
target_include_directories(OnvifTest PRIVATE ${PROJECT_SOURCE_DIR}/gsoap_src)
target_include_directories(OnvifTest PRIVATE ${PROJECT_SOURCE_DIR}/gsoap_src/import)
target_include_directories(OnvifTest PRIVATE ${PROJECT_SOURCE_DIR}/gsoap_src/plugin)
target_include_directories(OnvifTest PRIVATE ${PROJECT_SOURCE_DIR}/gsoap_src/custom)
target_link_libraries(OnvifTest PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)


target_include_directories(OnvifTest PUBLIC ${PROJECT_SOURCE_DIR}/gsoap_src/openssl/include/)

#库链接
target_link_directories(OnvifTest PUBLIC ${PROJECT_SOURCE_DIR}/gsoap_src/openssl/lib)
target_link_libraries(OnvifTest PRIVATE ssl crypto dl)

# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
  set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.OnvifTest)
endif()
set_target_properties(OnvifTest PROPERTIES
    ${BUNDLE_ID_OPTION}
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

include(GNUInstallDirs)
install(TARGETS OnvifTest
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

if(QT_VERSION_MAJOR EQUAL 6)
    qt_finalize_executable(OnvifTest)
endif()

#include "mainwindow.h"

#include <QApplication>
#include "DeviceBinding.nsmap"
#include "soapStub.h"
#include "soapH.h"
#include "wsseapi.h"
#include "httpda.h"
#include "soapDeviceBindingProxy.h"
#include <iostream>

int main(int argc, char *argv[])
{

    struct soap *soap = soap_new1(SOAP_C_UTFSTRING | SOAP_XML_INDENT);
    soap_init(soap);
    soap_set_mode(soap, SOAP_C_UTFSTRING);	//设置为utf-8编码
    soap->accept_timeout = 20;
    soap->connect_timeout= 20;
    soap->transfer_timeout = 10;
    soap->recv_timeout = 20;
    soap->send_timeout = 20;

    soap_set_namespaces(soap,namespaces);
    soap->namespaces = namespaces;

    soap_register_plugin(soap, http_da);
    soap_register_plugin(soap, soap_wsse);

    if (SOAP_OK != soap_wsse_add_Timestamp(soap, "Time", 60)) // 60 seconds lifetime
    {
        return 0;
    }

    int result = 0;
    const char *device_url = NULL;

    // === 1. 初始化 SSL/TLS (HTTPS 核心) ===
    if (soap_ssl_client_context(soap,
                                SOAP_SSL_SKIP_HOST_CHECK,
                                NULL, NULL,
                                NULL, // 确保证书路径正确!
                                NULL, NULL
                                )) {
        fprintf(stderr, "[错误] SSL 初始化失败\n");
        return 0;
    }
    printf("[信息] SSL/TLS 就绪。\n");

    if (argc > 1) {
        device_url = argv[1];
    } else {
        // 使用你的设备真实地址替换下面这个
        device_url = "https://10.214.55.155:443/onvif/device_service";
        printf("提示: 使用默认地址,可通过命令行参数指定。\n");
    }

    soap_set_recv_logfile(soap,  "/user_code/QtCode/OnvifTest/drecv.log");
    soap_set_sent_logfile(soap,  "/user_code/QtCode/OnvifTest/dsent.log");

    printf("目标设备: %s\n", device_url);

    if (soap_wsse_add_UsernameTokenDigest(soap, NULL, "admin", "Hycs@23589")) {
        fprintf(stderr, "[错误] 无法添加WS-Security认证头\n");
        //soap_stream_fault(soap, stderr);
        return 0;
    }

    // === 3. 准备请求与响应结构 ===
    // 这些类型定义在 soapStub.h 中
    struct _tds__GetDeviceInformation req;
    struct _tds__GetDeviceInformationResponse resp;

    DeviceBindingProxy deviceProxy(soap);

    // === 4. 执行 HTTPS 调用 ===
    printf("正在发送 GetDeviceInformation 请求...\n");
    result = deviceProxy.GetDeviceInformation(
        device_url,         // 端点地址
        NULL,               // SOAP动作,可为空(使用默认)
        &req, resp
        );
    if(result == 401)
    {
        struct http_da_info info;
        http_da_save(soap, &info, soap->authrealm, "admin", "Hycs@23589");
        result = deviceProxy.GetDeviceInformation(
            device_url,         // 端点地址
            NULL,               // SOAP动作,可为空(使用默认)
            &req, resp
            );
        http_da_release(soap, &info);
    }

    // === 5. 处理结果 ===
    if (result == SOAP_OK) {
        printf("\n>>> HTTPS ONVIF 调用成功! <<<\n");
        printf("制造商: %s\n", resp.Manufacturer ? resp.Manufacturer : "(空)");
        printf("型号  : %s\n", resp.Model ? resp.Model : "(空)");
        printf("序列号: %s\n", resp.SerialNumber ? resp.SerialNumber : "(空)");
    } else {
        fprintf(stderr, "\n>>> 调用失败 (错误代码: %d) <<<\n", result);
        // 分析常见错误
        if (soap->error == 401) {
            fprintf(stderr, "错误 401: 需要身份验证。ONVIF 需要设置 WS-Security 用户名/密码。\n");
            // 如需认证,在此添加代码:
            // soap_wsse_add_UsernameTokenDigest(soap, NULL, "admin", "12345");
        } else if (soap->error) {
            fprintf(stderr, "SOAP 错误: ");
            //soap_stream_fault(soap, stderr);
        }
        // if (soap->ssl_verify_error) {
        //     fprintf(stderr, "SSL 证书验证错误 (代码: 0x%04X)\n", soap->ssl_verify_error);
        // }
    }

    // === 6. 清理 ===
    //soap_destroy(soap);
    //soap_end(soap);
    //soap_free(soap);


    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

C测试程序

cmake_minimum_required(VERSION 3.16)

project(OnvifTest VERSION 0.1 LANGUAGES CXX C)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

#add_compile_definitions(WITH_OPENSSL)
#add_compile_definitions(WITH_DOM)
#add_compile_definitions(WITH_OPENSSL_DTLS)

add_definitions(
    -DWITH_OPENSSL          # 启用OpenSSL
    -DWITH_DOM              # 启用DOM
    -DWITH_OPENSSL_DTLS
    -DSOAP_DEBUG
)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)

set(C_SOURCE
    # 生成的代码
    gsoap_generated/soapC.c
    gsoap_generated/soapClient.c

    # gSOAP核心
    #gsoap_src/stdsoap2.c
    gsoap_src/dom.c         # DOM支持(关键!解决 soap_dom_find)

    # 日期时间序列化插件(关键!)
    gsoap_src/custom/struct_timeval.c
    gsoap_src/custom/duration.c


    # gSOAP插件(必须完整)
    #gsoap_src/plugin/plugin.c
    gsoap_src/plugin/mecevp.c      # 消息加密(关键!解决 soap_mec_size)
    gsoap_src/plugin/smdevp.c      # 签名/加密
    gsoap_src/plugin/httpda.c
    gsoap_src/plugin/wsseapi.c     # WS-Security
    gsoap_src/plugin/wsaapi.c      # WS-Addressing
    gsoap_src/plugin/threads.c     # 多线程
)
# 设置为C语言源文件
set_source_files_properties(${C_SOURCE} PROPERTIES LANGUAGE C)

set(PROJECT_SOURCES
    mainwindow.ui
    main.cpp
    mainwindow.h
    mainwindow.cpp
    gsoap_generated/soapStub.h
    gsoap_generated/soapH.h

    gsoap_src/stdsoap2.h
    gsoap_src/stdsoap2.cpp
    gsoap_src/import/wsa.h
    gsoap_src/plugin/smdevp.h
    gsoap_src/plugin/wsseapi.h
    gsoap_src/plugin/threads.h

    # 日期时间序列化插件(关键!)
    ${C_SOURCE}
)

if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    qt_add_executable(OnvifTest
        MANUAL_FINALIZATION
        #${HEADER_FILES}
        ${PROJECT_SOURCES}
    )
# Define target properties for Android with Qt 6 as:
#    set_property(TARGET OnvifTest APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
#                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
    if(ANDROID)
        add_library(OnvifTest SHARED
            #${HEADER_FILES}
            ${PROJECT_SOURCES}
        )
# Define properties for Android with Qt 5 after find_package() calls as:
#    set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
    else()
        add_executable(OnvifTest
            ${PROJECT_SOURCES}
        )
    endif()
endif()

target_include_directories(OnvifTest PRIVATE ${PROJECT_SOURCE_DIR}/gsoap_generated)
target_include_directories(OnvifTest PRIVATE ${PROJECT_SOURCE_DIR}/gsoap_generated)
target_include_directories(OnvifTest PRIVATE ${PROJECT_SOURCE_DIR}/gsoap_src)
target_include_directories(OnvifTest PRIVATE ${PROJECT_SOURCE_DIR}/gsoap_src/import)
target_include_directories(OnvifTest PRIVATE ${PROJECT_SOURCE_DIR}/gsoap_src/plugin)
target_include_directories(OnvifTest PRIVATE ${PROJECT_SOURCE_DIR}/gsoap_src/custom)
target_link_libraries(OnvifTest PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

target_include_directories(OnvifTest PUBLIC ${PROJECT_SOURCE_DIR}/gsoap_src/openssl/include/)

#库链接
target_link_directories(OnvifTest PUBLIC ${PROJECT_SOURCE_DIR}/gsoap_src/openssl/lib)
target_link_libraries(OnvifTest PRIVATE ssl crypto dl)

# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
  set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.OnvifTest)
endif()
set_target_properties(OnvifTest PROPERTIES
    ${BUNDLE_ID_OPTION}
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

include(GNUInstallDirs)
install(TARGETS OnvifTest
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

if(QT_VERSION_MAJOR EQUAL 6)
    qt_finalize_executable(OnvifTest)
endif()

#include "mainwindow.h"

#include <QApplication>
#include "DeviceBinding.nsmap"
#include "soapStub.h"
#include "soapH.h"
#include "wsseapi.h"
#include "httpda.h"
#include <iostream>

int main(int argc, char *argv[])
{

    struct soap *soap = soap_new1(SOAP_C_UTFSTRING | SOAP_XML_INDENT);
    soap_init(soap);
    soap_set_mode(soap, SOAP_C_UTFSTRING);	//设置为utf-8编码
    soap->accept_timeout = 20;
    soap->connect_timeout= 20;
    soap->transfer_timeout = 10;
    soap->recv_timeout = 20;
    soap->send_timeout = 20;

    soap_set_namespaces(soap,namespaces);
    soap->namespaces = namespaces;

    soap_register_plugin(soap, http_da);
    soap_register_plugin(soap, soap_wsse);

    if (SOAP_OK != soap_wsse_add_Timestamp(soap, "Time", 60)) // 60 seconds lifetime
    {
        return 0;
    }

    int result = 0;
    const char *device_url = NULL;

    // === 1. 初始化 SSL/TLS (HTTPS 核心) ===
    if (soap_ssl_client_context(soap,
                                SOAP_SSL_SKIP_HOST_CHECK,
                                NULL, NULL,
                                NULL, // 确保证书路径正确!
                                NULL, NULL
                                )) {
        fprintf(stderr, "[错误] SSL 初始化失败\n");
        return 0;
    }
    printf("[信息] SSL/TLS 就绪。\n");

    if (argc > 1) {
        device_url = argv[1];
    } else {
        // 使用你的设备真实地址替换下面这个
        device_url = "https://10.214.55.155:443/onvif/device_service";
        printf("提示: 使用默认地址,可通过命令行参数指定。\n");
    }

    soap_set_recv_logfile(soap,  "/user_code/QtCode/OnvifTest/drecv.log");
    soap_set_sent_logfile(soap,  "/user_code/QtCode/OnvifTest/dsent.log");

    printf("目标设备: %s\n", device_url);

    if (soap_wsse_add_UsernameTokenDigest(soap, NULL, "admin", "Hycs@23589")) {
        fprintf(stderr, "[错误] 无法添加WS-Security认证头\n");
        //soap_stream_fault(soap, stderr);
        return 0;
    }

    // === 3. 准备请求与响应结构 ===
    // 这些类型定义在 soapStub.h 中
    struct _tds__GetDeviceInformation req;
    struct _tds__GetDeviceInformationResponse resp;
    // 初始化结构(重要!)
    //soap_default__tds__GetDeviceInformationResponse(soap, &resp);

    // === 4. 执行 HTTPS 调用 ===
    // 函数名 `soap_call_tds__GetDeviceInformation` 请根据第一步的搜索结果调整
    printf("正在发送 GetDeviceInformation 请求...\n");
    result = soap_call___tds__GetDeviceInformation(
        soap,
        device_url,         // 端点地址
        NULL,               // SOAP动作,可为空(使用默认)
        &req, resp
        );
    if(result == 401)
    {
        struct http_da_info info;
        http_da_save(soap, &info, soap->authrealm, "admin", "Hycs@23589");
        result = soap_call___tds__GetDeviceInformation(
            soap,
            device_url,         // 端点地址
            NULL,               // SOAP动作,可为空(使用默认)
            &req, resp
            );
        http_da_release(soap, &info);
    }

    // === 5. 处理结果 ===
    if (result == SOAP_OK) {
        printf("\n>>> HTTPS ONVIF 调用成功! <<<\n");
        printf("制造商: %s\n", resp.Manufacturer ? resp.Manufacturer : "(空)");
        printf("型号  : %s\n", resp.Model ? resp.Model : "(空)");
        printf("序列号: %s\n", resp.SerialNumber ? resp.SerialNumber : "(空)");
    } else {
        fprintf(stderr, "\n>>> 调用失败 (错误代码: %d) <<<\n", result);
        // 分析常见错误
        if (soap->error == 401) {
            fprintf(stderr, "错误 401: 需要身份验证。ONVIF 需要设置 WS-Security 用户名/密码。\n");
            // 如需认证,在此添加代码:
            // soap_wsse_add_UsernameTokenDigest(soap, NULL, "admin", "12345");
        } else if (soap->error) {
            fprintf(stderr, "SOAP 错误: ");
            //soap_stream_fault(soap, stderr);
        }
        // if (soap->ssl_verify_error) {
        //     fprintf(stderr, "SSL 证书验证错误 (代码: 0x%04X)\n", soap->ssl_verify_error);
        // }
    }

    // === 6. 清理 ===
    //soap_destroy(soap);
    //soap_end(soap);
    //soap_free(soap);


    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}