LoginSignup
2
3

More than 1 year has passed since last update.

gSOAP/linuxで同一LAN内のONVIFカメラをサーチするプログラムを作成

Last updated at Posted at 2021-08-07

目的

最新のgSOAPを使って、同一LAN内のONVIFカメラをサーチする
Raspberry Pi 4で実行できるプログラムをビルドする。

環境

  • 実行環境 Raspberry Pi 4   インストールとgSOAPツール実行時にインターネット接続状態必要
  • 開発環境 Raspberry Pi 4
  • ONVIF準拠カメラ TP-Link Tapo C100/A

開発ツールのインストール

各種開発ツール

$ sudo apt-get install cmake libxml2-dev uuid-dev libavformat-dev libavcodec-dev libavutil-dev libswresample-dev libswscale-dev libjson-c-dev flex bison libssl-dev dh-autoreconf

gSOAP

gSOAP ToolkitをSourceForgeからダウンロードしてインストールする。

2021/7/14 にダウンロードしたら↓のファイルだった。
gsoap_2.8.116.zip

Windows 10のPCでダウンロードし、TeratermでRaspberry Pi4にSSH接続し、TeraTerm メニュー→ファイル→SSH SCP でPCからコピーした。
他の方法でも もちろん可。
例として、ユーザーホームディレクトリの直下の gsoap-2.8 フォルダにインストールする。

$ unzip gsoap_2.8.116.zip
$ cd gsoap-2.8/
$ ./configure
$ make

makeに約12分かかった。

$ sudo make install

gSOAPが入ったかどうかの確認

GeniviaのInstalling gSOAP on Unix/Linuxには以下の記載があるが…

After successful configuration and completion of the build steps, the following two tools are produced:

gsoap/bin/wsdl2h translator of WSDL/XSD to services and XML data bindings (interface tool)
gsoap/bin/soapcpp2 code generator for services and XML data bindings (implementation tool)

ファイル wsdl2、stdsoapcpp2 は gsoap/bin/ にではなく、 gsoap/ に存在した。

プログラムの作成

プログラム用フォルダの作成

例として、ユーザーホームディレクトリの直下に onvif フォルダを作って、そこでプログラムをビルドする。

$ cd
$ mkdir onvif
$ cd onvif

typemap.datのカスタマイズ

typemap.datをgSOAPのフォルダから自分のビルド用フォルダにコピる。

cp ~/gsoap-2.8/gsoap/typemap.dat .

typemap.datの以下の2行のコメントを外す。

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

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

xsd__dateTime = #import "custom/struct_timeval.h" | xsd__dateTime

wsdl2hでonvif.hを生成

$ wsdl2h -O4 -P -x -o onvif.h \
 http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl \
 http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl \
 http://www.onvif.org/onvif/ver10/event/wsdl/event.wsdl \
 http://www.onvif.org/onvif/ver10/deviceio.wsdl \
 http://www.onvif.org/onvif/ver20/imaging/wsdl/imaging.wsdl \
 http://www.onvif.org/onvif/ver10/receiver.wsdl \
 http://www.onvif.org/onvif/ver10/search.wsdl \
 http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl

Geniviaのonvif exmapleのページから ほぼコピペしてるが、もう少し減らしても大丈夫かも。

onvif.hを手修正

#import "wsdd10.h"  // wsdd10 = <http://schemas.xmlsoap.org/ws/2005/04/discovery>

//#import "wsdd10.h"    // wsdd10 = <http://schemas.xmlsoap.org/ws/2005/04/discovery>
#import "wsdd5.h"
#import "wsse.h"

soapcpp2でsoap*.h/soap*.cpp等の作成

soapcpp2 -2 -C -I ~/gsoap-2.8/gsoap/import -I ~/gsoap-2.8/gsoap -j -x onvif.h

WS-Discoveryのclientに必要なwsdd*.h/cpp等の作成

soapcpp2 -a -x -L -pwsdd -I ~/gsoap-2.8/gsoap/import ~/gsoap-2.8/gsoap/import/wsdd5.h

wsaapi.cをcppとしてコピー(中身そのまま)

cp ~/gsoap-2.8/gsoap/plugin/wsaapi.c wsaapi.cpp

main.cpp を書く

※あくまでも例

main.cpp
#include "soapDeviceBindingProxy.h"
#include "soapMediaBindingProxy.h"
#include "soapImagingBindingProxy.h"
#include "soapRemoteDiscoveryBindingProxy.h" 
#include "soapDeviceIOBindingProxy.h" 
#include "plugin/wsddapi.h"
#include "plugin/wsseapi.h"
#include "wsdd.nsmap"

#define USERNAME "onvif-camera account" //ONVIFカメラで設定したアカウント
#define PASSWORD "onvif-camera password" //ONVIFカメラで設定したパスワード

int main(int argc, char** argv)
{
    struct soap* serv = soap_new1(SOAP_IO_UDP);
    soap_register_plugin(serv, soap_wsse);
    soap_register_plugin(serv, soap_wsa);
   if (!soap_valid_socket(soap_bind(serv, NULL, 0, 1000)))
    {
        soap_print_fault(serv, stderr);
        exit(1);
    }

    serv->connect_flags = SO_BROADCAST;
    ip_mreq mcast;
    mcast.imr_multiaddr.s_addr = inet_addr("239.255.255.250");
    mcast.imr_interface.s_addr = htonl(INADDR_ANY);
    if (setsockopt(serv->master, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mcast, sizeof(mcast))<0) {
        std::cout << "group membership failed:" << strerror(errno) << std::endl;

    }
    const char* MessageID = soap_wsa_rand_uuid(serv);
    std::cout << "MessageID of Probe:" << MessageID << std::endl;

    int res = soap_wsdd_Probe(serv, SOAP_WSDD_ADHOC, SOAP_WSDD_TO_TS,
        "soap.udp://239.255.255.250:3702", MessageID, NULL, "dp0:NetworkVideoTransmitter", NULL, "");
    if (res != SOAP_OK)
    {
        soap_print_fault(serv, stderr);
        exit(1);
    }
    soap_wsdd_listen(serv, 10); //timeout 10sec
    soap_destroy(serv);
    soap_end(serv);
    soap_done(serv);
    return 0;
}
void wsdd_event_Hello(struct soap *soap, unsigned int InstanceId, const char *SequenceId, unsigned int MessageNumber, const char *MessageID, const char *RelatesTo, const char *EndpointReference, const char *Types, const char *Scopes, const char *MatchBy, const char *XAddrs, unsigned int MetadataVersion)
{ }

void wsdd_event_Bye(struct soap *soap, unsigned int InstanceId, const char *SequenceId, unsigned int MessageNumber, const char *MessageID, const char *RelatesTo, const char *EndpointReference, const char *Types, const char *Scopes, const char *MatchBy, const char *XAddrs, unsigned int *MetadataVersion)
{ }

soap_wsdd_mode wsdd_event_Probe(struct soap *soap, const char *MessageID, const char *ReplyTo, const char *Types, const char *Scopes, const char *MatchBy, struct wsdd__ProbeMatchesType *ProbeMatches)
{
    printf("wsdd_event_Probe()\n");
  return SOAP_WSDD_ADHOC;
}

// to report an error
void report_error(struct soap *soap)
{
  std::cerr << "Oops, something went wrong:" << std::endl;
  soap_stream_fault(soap, std::cerr);
  exit(EXIT_FAILURE);
}

void set_credentials(struct soap *soap)
{
    soap_wsse_delete_Security(soap);
    if (soap_wsse_add_Timestamp(soap, "Time", 10) || soap_wsse_add_UsernameTokenDigest(soap, "Auth", USERNAME, PASSWORD))
    {
        report_error(soap);
    }
}

void wsdd_event_ProbeMatches(struct soap *soap, unsigned int InstanceId, const char *SequenceId, unsigned int MessageNumber, const char *MessageID, const char *RelatesTo, struct wsdd__ProbeMatchesType *ProbeMatches)
{
    std::cout << "wsdd_event_ProbeMatches()\n";
    std::cout << "InstanceId=" << InstanceId << ", MessageNumber=" <<  MessageNumber << std::endl;
    if (SequenceId) printf("SequenceId[%s]\n", SequenceId);
    if (MessageID) printf("MessageID[%s]\n", MessageID);
    if (RelatesTo) printf("RelatesTo[%s]\n", RelatesTo);
    if (ProbeMatches->ProbeMatch->XAddrs)
    {
        std::cout << "ProbeMatches->XAddrs:" << ProbeMatches->ProbeMatch->XAddrs << std::endl;

        // create the proxies to access the ONVIF service API at HOSTNAME
        DeviceBindingProxy proxyDevice(soap);

        // get device info and print
        proxyDevice.soap_endpoint = ProbeMatches->ProbeMatch->XAddrs;
        _tds__GetDeviceInformation GetDeviceInformation;
        _tds__GetDeviceInformationResponse GetDeviceInformationResponse;
        set_credentials(soap);
        if (proxyDevice.GetDeviceInformation(&GetDeviceInformation, GetDeviceInformationResponse))
        {
            report_error(soap);
        }
        //check_response(soap);
        std::cout << "Manufacturer:    " << GetDeviceInformationResponse.Manufacturer << std::endl;
        std::cout << "Model:           " << GetDeviceInformationResponse.Model << std::endl;
        std::cout << "FirmwareVersion: " << GetDeviceInformationResponse.FirmwareVersion << std::endl;
        std::cout << "SerialNumber:    " << GetDeviceInformationResponse.SerialNumber << std::endl;
        std::cout << "HardwareId:      " << GetDeviceInformationResponse.HardwareId << std::endl;
    }
    if (ProbeMatches->ProbeMatch->Scopes->__item) printf("ProbeMatches->Scopes->__item[%s]\n", ProbeMatches->ProbeMatch->Scopes->__item);
    if (ProbeMatches->ProbeMatch->wsa5__EndpointReference.Address) printf("ProbeMatches->wsa5__EndpointReference.Address[%s]\n", ProbeMatches->ProbeMatch->wsa5__EndpointReference.Address);
    if (ProbeMatches->ProbeMatch->Types) printf("ProbeMatches->Types[%s]\n", ProbeMatches->ProbeMatch->Types);
    printf("ProbeMatches->MetadataVersion=%u\n", ProbeMatches->ProbeMatch->MetadataVersion);
}

soap_wsdd_mode wsdd_event_Resolve(struct soap *soap, const char *MessageID, const char *ReplyTo, const char *EndpointReference, struct wsdd__ResolveMatchType *match)
{
    printf("wsdd_event_Resolve()\n");
  return SOAP_WSDD_ADHOC;
}

void wsdd_event_ResolveMatches(struct soap *soap, unsigned int InstanceId, const char * SequenceId, unsigned int MessageNumber, const char *MessageID, const char *RelatesTo, struct wsdd__ResolveMatchType *match)
{
    printf("wsdd_event_ResolveMatches()\n");
}

int SOAP_ENV__Fault(struct soap *soap, char *faultcode, char *faultstring, char *faultactor, struct SOAP_ENV__Detail *detail, struct SOAP_ENV__Code *SOAP_ENV__Code, struct SOAP_ENV__Reason *SOAP_ENV__Reason, char *SOAP_ENV__Node, char *SOAP_ENV__Role, struct SOAP_ENV__Detail *SOAP_ENV__Detail)
{
  // populate the fault struct from the operation arguments to print it
  soap_fault(soap);
  // SOAP 1.1
  soap->fault->faultcode = faultcode;
  soap->fault->faultstring = faultstring;
  soap->fault->faultactor = faultactor;
  soap->fault->detail = detail;
  // SOAP 1.2
  soap->fault->SOAP_ENV__Code = SOAP_ENV__Code;
  soap->fault->SOAP_ENV__Reason = SOAP_ENV__Reason;
  soap->fault->SOAP_ENV__Node = SOAP_ENV__Node;
  soap->fault->SOAP_ENV__Role = SOAP_ENV__Role;
  soap->fault->SOAP_ENV__Detail = SOAP_ENV__Detail;
  // set error
  soap->error = SOAP_FAULT;
  // handle or display the fault here with soap_stream_fault(soap, std::cerr);
  // return HTTP 202 Accepted
  return soap_send_empty_response(soap, SOAP_OK);
}

Makefile を書く

※あくまでも例

# Makefile for onvifclient
OBJS = main.o soapC.o wsddClient.o soapDeviceBindingProxy.o soapwsddProxy.o soapDeviceIOBindingProxy.o soapImagingBindingProxy.o soapMediaBindingProxy.o stdsoap2.o dom.o smdevp.o mecevp.o duration.o struct_timeval.o wsddapi.o wsseapi.o wsaapi.o
CC = g++
INCLUDEDIR = -I./ -I ~/gsoap-2.8/gsoap
CFLAGS = $(INCLUDEDIR) -DWITH_OPENSSL -DWITH_DOM -DWITH_ZLIB

onvifclient: $(OBJS)
    $(CC) $(OBJS) -lcrypto -lssl -lz -o $@

main.o: main.cpp
    $(CC) $(CFLAGS) -c -o $@ $<

#wsddC.o: wsddC.cpp
#   $(CC) $(CFLAGS) -c -o $@ $<

soapC.o: soapC.cpp
    $(CC) $(CFLAGS) -c -o $@ $<

wsddClient.o: wsddClient.cpp
    $(CC) $(CFLAGS) -c -o $@ $<

soapDeviceBindingProxy.o: soapDeviceBindingProxy.cpp
    $(CC) $(CFLAGS) -c -o $@ $<

soapwsddProxy.o: soapwsddProxy.cpp
    $(CC) $(CFLAGS) -c -o $@ $<

soapDeviceIOBindingProxy.o: soapDeviceIOBindingProxy.cpp
    $(CC) $(CFLAGS) -c -o $@ $<

soapImagingBindingProxy.o: soapImagingBindingProxy.cpp
    $(CC) $(CFLAGS) -c -o $@ $<

soapMediaBindingProxy.o: soapMediaBindingProxy.cpp
    $(CC) $(CFLAGS) -c -o $@ $<

stdsoap2.o: ~/gsoap-2.8/gsoap/stdsoap2.c
    $(CC) $(CFLAGS) -c -o $@ $<

dom.o: ~/gsoap-2.8/gsoap/dom.c
    $(CC) $(CFLAGS) -c -o $@ $<

smdevp.o: ~/gsoap-2.8/gsoap/plugin/smdevp.c
    $(CC) $(CFLAGS) -c -o $@ $<

mecevp.o: ~/gsoap-2.8/gsoap/plugin/mecevp.c
    $(CC) $(CFLAGS) -c -o $@ $<

duration.o: ~/gsoap-2.8/gsoap/custom/duration.c
    $(CC) $(CFLAGS) -c -o $@ $<

struct_timeval.o: ~/gsoap-2.8/gsoap/custom/struct_timeval.c
    $(CC) $(CFLAGS) -c -o $@ $<

wsddapi.o: ~/gsoap-2.8/gsoap/plugin/wsddapi.c
    $(CC) $(CFLAGS) -c -o $@ $<

wsseapi.o: ~/gsoap-2.8/gsoap/plugin/wsseapi.cpp
    $(CC) $(CFLAGS) -fpermissive -c -o $@ $<

wsaapi.o: wsaapi.cpp
    $(CC) $(CFLAGS) -I ~/gsoap-2.8/gsoap/plugin -DSOAP_H_FILE=wsddH.h -c -o $@ $<

ビルドする

$ make

実行する

$ ./onvifclient

ハマったこと

  • ツールやビルドの実行が速いから、と、途中から WSL2/ubuntu で実行していたら WSL2/ubuntu環境で実行すると、ブロードキャスト宛送信が出来なかった。(WSL2/ubuntuを適切に設定すればいいのかもしれないが調べてない)
  • 開発に使用しているWin10PCから、Resolveパケットが定期的に出ていて、自分の作成したプログラムから送信されていると勘違いしてしまったことも、解決を長引かせた。プログラムを追っていくと、Probeパケットが出ているはずなのに、なぜwiresharkで見るとResolveになるのかが見当つかなかった。
  • ブロードキャストアドレスを試しにONVIFカメラのIPアドレスに変更してみたら、Probeパケットが出て、ONVIFカメラからのProbeMatches応答がwireshark上で見れるようになった。が、今度は、そのProbeMatchesを作成プログラム上で受信できなかった。これも、WSL2で動かすのをやめ、ラズパイ4で動かせば、受信できるようになった。PCのlinuxの場合は、WSL2ではなくnative linux環境なら大丈夫だろうと予想。
  • GeniviaのONVIF exampleページの手順通りにコマンド実行していっても、リンクエラーがなかなか取れず、typemap.datを適切に修正するのが必要であると分かるまで、かなり遠回りした。(直接的にリンクエラーを解消する方向でトライし始めてしまった)

今後やりたい関連事項

  • WSL2/ubuntu上でラズパイ用のクロスコンパイル環境を構築した。簡単なプログラムだと動いたが、gSOAP込みのプログラムは まだ…。

参考URL

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3