前言
朋友的公司是做GPS的,上周聯(lián)系到我要幫做個程序把他們平臺的車輛定位跟蹤數(shù)據(jù)和省里的平臺對接。看一下官方提供的三個文檔,洋洋灑灑共一百多頁,一大堆協(xié)議的定義甚是齊全,好在官方的文件中也帶有個封裝好通信功能的DLL和一個調(diào)用此接口的c++ DEMO程序,既然有現(xiàn)成的可用,那就不必去看他的協(xié)議了。
說實話,參加工作之后就基本沒用過c++,生疏了。特別是要用c++操作數(shù)據(jù)庫,對我來說比割幾刀還要痛苦。官方的API中已經(jīng)很詳盡,要做的就是從現(xiàn)有平臺的數(shù)據(jù)庫中獲取車輛定位信息,通過官方的API發(fā)送到省中心平臺。
本想用C#給官方API做個包裝,省得再去動用C++,可是看到此API中定義有幾個Struct,而且下行數(shù)據(jù)都是通過回調(diào)函數(shù)方式提供,google了一下,似乎C#對調(diào)用有回調(diào)函數(shù)的C DLL不是很順暢,于是放棄了,想到了Python。
一、Python之ctypes
ctypes是Python的一個外部庫,提供和C語言兼容的數(shù)據(jù)類型,可以很方便地調(diào)用C DLL中的函數(shù)。在Python2.5官方安裝包都帶有ctypes 1.1版。ctypes的官方文檔在這里。
ctypes的使用非常簡明,如調(diào)用cdecl方式的DLL只需這樣:
from ctypes import *; h=CDLL('msvcrt.dll') h.printf('a=%d,b=%d,a+b=%d',1,2,1+2);
以上代碼運行后輸出 a=1,b=2,a+b=3。
二、加載庫和普通函數(shù)的調(diào)用
官方API提供的庫中有幾個主要的函數(shù):
//初始化 int DCSPCLIENTDLL InitInterface( const char *pCenterIP, const unsigned short nUpLinkSvrPort,const unsigned short nDownLinkSvrPort ); //釋放資源 int DCSPCLIENTDLL FiniInterface( void ); //登錄 int DCSPCLIENTDLL Login( const unsigned int uiBranchPlatformID, const unsigned int nUserID, const char *pPassword ); //注銷 int DCSPCLIENTDLL Logout( const unsigned int uiBranchPlatformID, const unsigned int nUserID, const char *pPassword ); //發(fā)車輛實時定位數(shù)據(jù) int DCSPCLIENTDLL SendUPRealLocation( const char * const pDeviceId, const char cDeviceColor, const unsigned short nMsgCode, const _stBPDynamicData * const pStGpsData );
在Python中加載使用:
from ctypes import * #加載API庫 api = CDLL('DCSPClientDLL.dll'); #初始化函數(shù)的參數(shù)類型 api.InitInterface.argtypes=[c_char_p,c_ushort,c_ushort] api.Login.argtypes=[c_uint,c_uint,c_char_p] api.Logout.argtypes=[c_uint,c_uint,c_char_p] #初始化并登錄 api.InitInterface(u"中心服務(wù)器地址" , u'上行服務(wù)端端口' , u'下行客戶端端口') api.Login(platformID,userID,password); #.....其它操作 api.Logout(platformID,userID,password); #注銷
參數(shù)類型可以像上面的代碼一樣預(yù)先設(shè)定好,或者在調(diào)用函數(shù)時再把參數(shù)轉(zhuǎn)成相應(yīng)的c_***類型。ctypes的類型對應(yīng)如下:
如此,完成了簡單的第一步。
三、C語言中的Struct數(shù)據(jù)結(jié)構(gòu)
在發(fā)送實時定位數(shù)據(jù)的函數(shù)SendUPRealLocation中有一個參數(shù)是結(jié)構(gòu)體類型 _stBPDynamicData。python中沒有struct這種數(shù)據(jù)結(jié)構(gòu),ctypes很周全,對C的struct和union這二種數(shù)據(jù)類型都提供很好的支持。stBPDynamicData結(jié)構(gòu)的定義如下:
// 車輛動態(tài)數(shù)據(jù)結(jié)構(gòu)體 struct _stBPDynamicData { // 加密狀態(tài) unsigned char encrypt; // GPS 時間 _StructTime gpsTime; // 經(jīng)度 unsigned int longitude; // 緯度 unsigned int latitude; // GPS速度 unsigned short unGpsSpeed; // 行駛記錄儀速度 unsigned short unTachographSpeed; // 車輛當前總里程數(shù) unsigned int uiMileageTotal; // 角度 unsigned short angle; // 車輛狀態(tài) unsigned short state; // 報警狀態(tài) unsigned short alarm; };
在python中,需要定義一個與這兼容的類,繼承于ctypes.Structure,其中還用到一個_StructTime結(jié)構(gòu),這里一并貼出代碼:
class _StructTime(Structure): _fields_ =[('day',c_ubyte), ('month',c_ubyte), ('year',c_ushort), ('hour',c_ubyte), ('minute',c_ubyte), ('second',c_ubyte)]; def __str__(self): return '{0}-{1}-{2} {3}:{4}:{5}'.format(self.year,self.month,self.day,self.hour,self.minute,self.second); class _stBPDynamicData(Structure): _fields_ =[('encrypt',c_ubyte), ('gpsTime',_StructTime), ('longitude',c_uint), ('latitude',c_uint), ('unGpsSpeed',c_ushort), ('unTachographSpeed',c_ushort), ('uiMileageTotal',c_uint), ('angle',c_ushort), ('state',c_ushort), ('alarm',c_ushort)]; def __str__(self): return u'({0},{1}),{6},sp:{2},angle:{3},st:{4},al:{5}'.format(self.longitude, self.latitude,self.unGpsSpeed, self.angle ,self.state,self.alarm,self.gpsTime ); class gpsData(Structure): _fields_ =[('strDeviceID',c_char_p), ('cDeviceColor',c_char), ('nMsgCode',c_ushort), ('stBPD',_stBPDynamicData)]; def __str__(self): return u'{0},{1}'.format(self.strDeviceID,self.stBPD );
gpsData是我自己加的一個類,用于記錄每輛車的信息。
現(xiàn)在就可以使用SendUPRealLocation函數(shù)發(fā)送車輛實時數(shù)據(jù)了:
tm=_StructTime(); tm.year=2010;tm.month=4;tm.day=3;tm.hour=11;tm.minute=2;tm.second=11; bpd=_stBPDynamicData(); bpd.gpsTime=tm;bpd.longitude=1234567;bpd.latitude=246898;#...其它參數(shù) data=gpsData(); data.strDeviceID=u'桂Coo007';data.stBPD=bpd; #調(diào)用 API發(fā)送數(shù)據(jù) api.SendUPRealLocation( data.strDeviceID, data.cDeviceColor , data.nMsgCode, addressof( data.stBPD ) );
注意SendUPRealLocation第三個參數(shù)是_stBPDynamicData * 指針類型,所以要用ctypes.addressof()取參數(shù)的地址。
四、回調(diào)函數(shù)
寫到這里就忍不住嘮叨幾句,這個系統(tǒng)的協(xié)議設(shè)計的太有 “個性”了。這個系統(tǒng)的功能說起來也不復(fù)雜,就是要GPS運營商把指定的車輛位置信息發(fā)送到中心平臺,同時中心平臺可以向各GPS終端發(fā)送一些數(shù)據(jù)和指令,比如傳送文字信息到終端,或者要求終端拍張照片反饋到中心。
這個協(xié)議流程是這樣,運營商端主動連接到中心服務(wù)器,然后此連接只用于傳輸向中心平臺主動發(fā)送的數(shù)據(jù)。登錄成功了之后呢,中心平臺再向運營商的IP建立一個連接,用于中心下發(fā)的數(shù)據(jù)和指令。官方稱為“雙鏈路”。
于是,就要求運營商必須要有固定的公網(wǎng)IP(這個不是問題,據(jù)了解GPS運營商服務(wù)器都有固定IP),而且這個程序必須運行在有公網(wǎng)IP的電腦上或采用端口映射之類的方法。可是俺開發(fā)設(shè)計時是在大教育局域網(wǎng)中的,搞個端口映射都不可能更別談公網(wǎng)IP了。于是,在調(diào)試下行數(shù)據(jù)部分功能時就只能遠程到運營商服務(wù)器上去調(diào)試了。
回歸正題。
要使用回調(diào)函數(shù),需要先用 CFUNCTYPE 定義回調(diào)函數(shù)的類型,官方API中有十多個回調(diào)函數(shù)注冊,定義摘抄:
#define DCSPCLIENTDLL __declspec(dllexport) typedef void (*pDownTextInfoFv) ( const char *const pDeviceID, const char cDeviceColor, const char *const pInfo ); typedef void (*pDownCommunicateReqFv) ( const char *const pDeviceID, const char cDeviceColor, const char *const pCalledTel ); extern "C" { void DCSPCLIENTDLL RegDownTextInfoFunc( pDownTextInfoFv pFv ); void DCSPCLIENTDLL RegDownCommunicateReqFunc( pDownCommunicateReqFv pFv ); };
在python中,定義相應(yīng)的類型和回調(diào)處理函數(shù):
"""下發(fā)文字信息""" def downTextInfo(pDeviceID,cDeviceColor,pInfo): print(u'<-[下發(fā)文字]:{0},{1}'.format(str(pDeviceID),str(pInfo)) ); r=api.SendUpTextInfoAck(pDeviceID, cDeviceColor, True ); if r==0: print(u'->回復(fù)下發(fā)文字成功。'); else: print(u'->回復(fù)下發(fā)文字失敗。'); pDownTextInfoFv = CFUNCTYPE(c_void_p,c_char_p, c_char, c_char_p) #回調(diào)函數(shù)類型定義 pDownTextInfoHandle = pDownTextInfoFv(downTextInfo); api.RegDownTextInfoFunc(pDownTextInfoHandle); #注冊回調(diào)函數(shù)
其中SendUpCommunicateAck是回應(yīng)中心,告知已經(jīng)收到信息。二個參數(shù)類型和downTextInfo中的參數(shù)類型一到,所以可以不用初始化聲明此函數(shù)的參數(shù)定義。
其余的回調(diào)函數(shù)用相同的方法處理。
結(jié)尾
調(diào)試完API對接部分功能后,在想用哪個py庫操作數(shù)據(jù)庫比較方便呢,找了一下之后才想到為何不用ironPython而可以直接使用ado.net訪問數(shù)據(jù)庫,豈不是更爽。
于是把代碼搬到ironPython2.6中試試,讓我十分驚喜的是不用做任何個性代碼直接運行成功!ironPython 2.6中的ctypes和Python2.6的一樣都是1.1.0版。
PS:借用 1號園友的主題和CSS。
出處:無常筆記 http://wuchang.cnblogs.com