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: 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 * * * \******************************************************************************/ SOAP_FMAC5 int SOAP_FMAC6 __tds__GetServices(struct soap*, struct _tds__GetServices *tds__GetServices, struct _tds__GetServicesResponse *tds__GetServicesResponse); SOAP_FMAC5 int SOAP_FMAC6 __tds__GetServiceCapabilities(struct soap*, struct _tds__GetServiceCapabilities *tds__GetServiceCapabilities, struct _tds__GetServiceCapabilitiesResponse *tds__GetServiceCapabilitiesResponse); 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); 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
| 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; }
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標準方式進行溝通