Onvif

I spent a few days to study and implement Onvif of server side. Some important points were recorded as below:

準備:

 開發編譯皆在virtualbox ubuntu 64bit
   download gsoap
   安裝 openssl and libssl-dev: sudo apt-get install openssl libssl-dev
   安裝 bison and flex: sudo apt-get install flex bison
   ./configure
   make & make install
 兩種主要測試工具,安裝在windows:
  ONVIF Device TestTool v13.12 (not free,自行google)
  ONVIF Device Manager (free)

說明:

Onvif 主要是透過SOAP和XML進行server和client間溝通
這裡server和client和一般想像中的有點不同
server指的是設備端像是IPCAM之類,client指的是管理端一般是NVR軟體
gSOAP目的,透過gSOAP可以編譯出server或client的C或C++介面
利用gSOAP編譯出來的介面,其中的struct會對應規格書定義,
只要按照規格書定義對struct填入該填的值,client&server就可以快樂溝通了?!

實作:

http://www.onvif.org/Portals/0/documents/WhitePapers/ONVIF_WG-APG-Application_Programmer%27s_Guide.pdf
規格書定義參數 http://www.onvif.org/onvif/ver20/util/operationindex.html
gSOAP編譯步驟:

1
2
wsdl2h -cegxy -o onvif.h -t /home/jeff/gsoap-2.8/gsoap/typemap.dat http://docs.oasis-open.org/ws-dd/discovery/1.1/os/wsdd-discovery-1.1-wsdl-os.wsdl http://www.w3.org/2006/03/addressing/ws-addr.xsd http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl   
soapcpp2 -S -x -I /home/jeff/gsoap-2.8/gsoap/import -I /home/jeff/gsoap-2.8/gsoap/ onvif.h

以上,-c產生C語言格式,typemap.dat裡面定義XML namespace prefixes and type bindings,可以自行定義
然後,依據各個wsdl檔案定義去產生onvif.h
onvif.透過soapcpp2 產生出一些.c檔,-S只產生server端的定義檔

其中,
soapStub.h

1
2
3
4
5
6
7
8
9
10
11
12
/******************************************************************************\
* *
* Server-Side Operations *
* *
\******************************************************************************/

/** Web service operation '__tds__GetServices' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 __tds__GetServices(struct soap*, struct _tds__GetServices *tds__GetServices, struct _tds__GetServicesResponse *tds__GetServicesResponse);
/** Web service operation '__tds__GetServiceCapabilities' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 __tds__GetServiceCapabilities(struct soap*, struct _tds__GetServiceCapabilities *tds__GetServiceCapabilities, struct _tds__GetServiceCapabilitiesResponse *tds__GetServiceCapabilitiesResponse);
/** Web service operation '__tds__GetDeviceInformation' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 __tds__GetDeviceInformation(struct soap*, struct _tds__GetDeviceInformation *tds__GetDeviceInformation, struct _tds__GetDeviceInformationResponse *tds__GetDeviceInformationResponse);
........

這些就是需要實作的部分,依據wsdl數量所產生,也許超過百個函式以上


ONVIF Device TestTool

ONVIF Device TestTool 是模擬client端的測試動作和一些Debug用的XML輸出


Device Service Address: http://192.168.56.101:80/onvif/device_service check
其對應的是devicemgmt.wsdl中的GetDeviceInformation
所以server端所要做的就是把對接口準備好(TCP,port可以自己定,但規格書要說用80比較好,避免防火牆)
此部分socket可自行實作或交由soap_bind完成

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <pthread.h>
#include <sys/msg.h>
#include "mySoapStub.h"

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int thread_ret=0, thread_no=0;
typedef struct {
pthread_t thread_tid;
long thread_count;
}tThread;
tThread tptr[2];
void tcpThread(void* data);

int _server(int argc, char **argv);

int main(int argc, char **argv){
_server(argc, argv);
return 1;
}

void tcpThread(void* data){
int m, s;
struct soap tcpSoap;
pthread_detach(pthread_self());
soap_init(&tcpSoap);
// soap_set_namespaces(&add_soap, namespaces);
m = soap_bind(&tcpSoap, NULL, 80, 100);
if (m < 0) {
soap_print_fault(&tcpSoap, stderr);
exit(-1);
}
while(1){
s = soap_accept(&tcpSoap);
if (s < 0) {
soap_print_fault(&tcpSoap, stderr);
exit(-1);
}
fprintf(stderr, "Socket connection successful: slave socket = %d\n", s);
soap_serve(&tcpSoap);
soap_end(&tcpSoap);
usleep(500000);
}
pthread_exit ("thread all done");
}

int _server(int argc, char **argv){
thread_ret=pthread_create( &tptr[thread_no].thread_tid, NULL, (void *) tcpThread, (void*)&thread_no );
if(thread_ret!=0){
fprintf (stderr, "Create pthread error~~~\n");
exit (1);
}
thread_no++;
while(1){}
usleep(500000);

}
pthread_mutex_destroy(&mutex);
return 1;
}

還有把對應函式準備好,也就是實作soapStub.h裡面的

1
SOAP_FMAC5 int SOAP_FMAC6 __tds__GetDeviceInformation(...)

mySoapStub.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** Web service operation '__tds__GetDeviceInformation' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 __tds__GetDeviceInformation(struct soap *pSoap, struct _tds__GetDeviceInformation *tds__GetDeviceInformation, struct _tds__GetDeviceInformationResponse *tds__GetDeviceInformationResponse){
printf("\n");
tds__GetDeviceInformationResponse->Manufacturer=(char*)soap_malloc(pSoap,100);
tds__GetDeviceInformationResponse->Model=(char*)soap_malloc(pSoap,100);
tds__GetDeviceInformationResponse->FirmwareVersion=(char*)soap_malloc(pSoap,100);
tds__GetDeviceInformationResponse->SerialNumber=(char*)soap_malloc(pSoap,100);
tds__GetDeviceInformationResponse->HardwareId=(char*)soap_malloc(pSoap,100);

strcpy(tds__GetDeviceInformationResponse->Manufacturer,"Jeff Yang");
strcpy(tds__GetDeviceInformationResponse->Model,"Jeff Yang XXXX");
strcpy(tds__GetDeviceInformationResponse->FirmwareVersion,"v1.0.0.01");
strcpy(tds__GetDeviceInformationResponse->SerialNumber,"88998998888888");
strcpy(tds__GetDeviceInformationResponse->HardwareId,"123456789999");
return SOAP_OK;
}
/** Web service operation '__tds__SetSystemDateAndTime' (returns SOAP_OK or error code) */
SOAP_FMAC5 int SOAP_FMAC6 __tds__SetSystemDateAndTime(struct soap *pSoap, struct _tds__SetSystemDateAndTime *tds__SetSystemDateAndTime, struct _tds__SetSystemDateAndTimeResponse *tds__SetSystemDateAndTimeResponse){ return SOAP_OK; }
....

其他未使用到的函式,也需要填個回傳值否則將編譯錯誤
編譯,

1
2
gcc  -c -o mySoapStub.o mySoapStub.c
gcc soapC.o soapServer.o stdsoap2.o main.o duration.o mySoapStub.o -lpthread -o ws-server

把server端程式run起來,當按check時TestTool就會收到回傳值


ONVIF Device TestTool


以上,都是在已知server的IP和port情況下進行, e.g. http://192.168.56.101:80/onvif/device_service


一個client可以同時監控多個server
所以要一次得知單一或多個server的IP資訊需要透過multicast的方式(Discover Devices)
經由傳送資料到 239.255.255.250 port 3702(ws-discovery)
若網路上存在server,會回應此訊息
此部分可以參考
http://albert-oma.blogspot.tw/2013/09/onvif-ws-discovery-implementation.html
http://albert-oma.blogspot.tw/2013/09/onvif-ws-discovery-spec.html (A Introduction in Chinese)

小結:

符合ONVIF標準的client端(NVR)與server端(Devices),其包含兩主要流程
1.自動偵測Devices (multicast)
2.設定和取得Device資訊 (tcp)
將兩流程皆整合在server以及client端,相互對應,就可以使兩者以ONVIF標準方式進行溝通