打开gStudio之后,点击菜单栏中Help->Contents。先把这个诡异的编程语言看一遍吧。这里搬一些东西出来。
从第一副图片中,我们可以看出,从源文件到可执行文件的过程。
从第二幅图我们可以了解到GoDB是如何跨平台的。
编程语言的话:
GBasic is a variant of the Basic Language included in GoDB platform as the scripting language.
这种编程语言大小写不区分,真是编程界的一朵奇葩。
GBasic程序通常是有一个frm文件和与之名字相同的bas文件。比如说当HOME.frm被加载时,HOME.bas就会被执行。
#对象名
call
,比如说call subname
,而Functions的调用则不用。GBasic自带了很多封装好的函数,这些函数如何用,请看Help。
GStudio这个IDE像Visual Stduio一样,支持控件的事件触发(按钮按下,编辑框改变etc.)这些事件称之为控件的事件,还有另外一种事件叫做Global Event
。
具体查看Help中的GBasic Reference
->Global Event
。这里稍微介绍下
form_load
当frm文件加载首先执行的是这个Sub。form_notify
当收到服务器反馈时自动调用,此时在用GetMessage
获取消息内容..form_timer
,定时器触发时调用此Sub,通常使用settimer(1000)
来使能定时触发。
所以一般阅读GoDB工程代码一般先从每个bas文件的
form_load
开始看起。
Debug
Release
Debug/ Release
下拉菜单中选中Release
Build
中在Make Read-Only BDB
选中Win32_Release
中找到gIPNC-ro.bdb
gIPNC-ro.gz
我们做的是在原有基础上添加东西,修修补补,谁要是有精力就从头开始弄吧。
bas和frm和inc一一对应,bas为执行文件,inc为头文件,frm为form文件,相当于Visual Studio中的rc文件。
还是先分析流程吧。
首先当然是登录界面,那就是对应logon.bas和auth.bas,首次登录执行的auth.bas文件,而用户刷新过程中执行的logon.bas文件。
关于logon.bas
readprofile("PAGENAME",pageName$)
...
readprofile("IPNC",loginDet$)
...
首先读取变量名为IPNC和PAGENAME的profile,如果读取得到那么就将读取到的用户名和密码填充到用户名编辑框和登录编辑框(这就是记住密码功能),调用dwnldIniFile
函数进行Http请求,请求内容是ip地址/ini.htm
,这样如果认证通过,那么此时已经把所有关键字及其内容保存到~responseData$
全局环境变量上。获取失败则弹出用户或密码错误。读取profile错误,则加载auth.frm。
我们知道,TI IPNC除了用户验证登录之外还有区分用户权限,所以在成功调用dwnldIniFile
调用getUserAuthority
获取当前用户的权限,获取失败弹出错误信息,成功并且PAGENAME的值为LOGIN则加载auth.frm
,否则直接进入liveVideo.frm
温馨提示,仔细研究下writeprofile函数,你就知道如何做到真正的记住密码。
温馨提示,GetIPAddress()是进行调试的关键。
分析auth.bas不难发现,无非就是。先将读取到的IPNC
变量的值进行填充,然后这个文件主要的逻辑就是cmdSubmit_Click这个按钮按下的事件处理函数了。先进行输入验证,然后是登录验证,也是调用dwnldIniFile
进行认证getUserAuthority
。最后成功加载livevideo.frm,并写PAGENAME的profile。
** 几个比较关键的文件 **
这个工程里面有几个文件是比较重要(特殊的文件)的那就是leftmenu.bas
,functions.inc
,defines.inc
,common.inc
和keywords.inc
leftmenu.bas
是网页侧边栏的执行文件,添加,删除子菜单就修改这个文件。functions.inc
里面定义了众多函数,大部分是网页keyword获取和设置的函数。defines.inc
文件定义了一些关键数字,比如说侧栏菜单(主菜单,子菜单)多少个,比如关键字最多有多少个,有时候一些莫名其妙的错误就是由于这个文件上的数字没对上引起的。common.inc
定义了一些通用函数,比如编辑框可以输入哪些字符,不可以输入哪些字符(CheckKey),产生Request Header(generateauthHeader)。keywords.inc
,这个仅在functions.inc
中被包括。这个文件其实不太重要,作用仅仅是用于显示获取或设置关键字时的错误信息而已。logon.bas和auth.bas他们都包括了functions.inc
,defines.inc
,common.inc
。而其它的主页面除了这个三个文件之外,还包括leftmenu.bas,这样的效果就是将侧边栏和各个form的网页叠加起来。
GBasic是从头到尾顺序执行的,所以每个bas文件先执行Sub或者functions之外的语句之后,然后再执行form_load事件,由于先包括了leftmenu.bas,所以会先执行leftmenu.bas里面的内容,而每个frm页面都包括了leftmenu.frm文件。这样就加载了侧边栏。
leftmenu负责触发加载各个页面。
这里只说怎么加,不讨论怎么建页面。
打开leftmenu.bas
dims ~menuArray$(MAXMAINMENU+MAXSUBMENU+3)
dims menuOffImg$(MAXMAINMENU) = ("!camera_Off.bin","!users_Off.bin","!settings_Off.bin","!maintanance_off.bin","!support_off.bin")
dims ~menuOnImg$(MAXMAINMENU) = ("!camera_On.bin","!users_On.bin","!settings_On.bin","!maintanance_on.bin","!support_on.bin")
dims ~urlMainArray$(MAXMAINMENU) = ("!liveVideo.frm", "!addusers.frm","!videoImageSettings.frm","!maintenance.frm","!supportScreen.frm")
dims ~urlSubArray$(MAXSUBMENU) = ("!videoImageSettings.frm","!videoAnalyticsSetting.frm","!DMVAeventMonitor.frm","!display_settings.frm", "!audioSetting.frm","!setDateTime.frm", "!NetworkSettings.frm", "!alarmSettings.frm", "!class.frm","!storageSetting.frm")
dimi dmvaEnable
#imgselected.hidden = 1
loadMenuCaptions()
....
上面是我修改过的。其中比较关键的是几个宏定义的定义,如果你要修改,请算好侧边栏的个数,然后修改在defines.inc中的数值。bin文件是主菜单的对应的图片,关于图片后面才说,frm就是对应的页面啦,算好顺序...
ReadOnly
控件,然后NAME必须为rosubmenu
,value为空,背景那些参照已有的..你会发现有好几个ReadOnly
都叫做rosubmenu
,然后这个ide就自动就其他们的name改为类似数组的样子..rosubmenu[0]
,而程序里面也是如此引用。当然leftmenu大部分菜单鼠标点击时鼠标的触发事件,也就是加载相应的页面。当然还有什么图标排列,音视频开关等处理...
另外值得注意的是:子菜单中有可能有一两个页面是默认隐藏的,某种条件触发下才显示。比如Live Video页面那个Example的下拉框就是一个触发条件,另外如果用户权限不同,子菜单显示的个数也有所不同,这些逻辑都在leftmenu.bas中。
IPNC网页默认几个页面,大部分都是传统的表单页面,无非就是些CheckBox
,Edit
,CommonBox
之类的控件,加载页面的时候,就是获取服务器上对应的控件的值然后填充进去,保存页面的时候,则是发送http请求到web服务器进行保存然后重新获取更新。
一般来说一个控件对应一个关键字。
我们先从对应页面的form_load看起,然后看savepage,然后看各个控件触发事件..其它全局事件。
我们拿声音设置
页面为例,对应的文件是audiosetting.bas
和audiosetting.frm
文件。
Sub Form_Load
dimi retVal
retVal = loadIniValues()
if ~maxPropIndex = 0 then
msgbox("无法加载初始配置",0,"错误",3)
loadurl("!auth.frm")
endif
call displayControls(LabelName$,XPos,YPos,Wdh,height)
call loadInitialValues()
showSubMenu(0,1)
setfocus("rosubmenu[4]")
selectSubMenu()
setfocus("chkenableaudio")
call chkenableAudio_Click 'TR-19
getimagefile(sliderImage$,"!slider_but.jpg")
#lblsuccessmessage$ = "" 'TR-35
End Sub
retVal = loadIniValues()
这是个关键函数,这个函数里面请求http://ip/ini.htm
获取所有关键字的值,如果获取失败了,就提示错误。在loadIniValues函数中,有个字符串数组非常关键,每次添加新的关键字都必须修改这个字符串在后面添加新的关键字。keywords$
网页在functions.inc
中的loadIniValues()中解析得到这些值将这些属性
和对应的值
放分别放到字符串数组~iniProperties$和iniPropValues$中.
值得注意的是在放入~iniProperties$之前,在每个属性名之前加了字母"g".
每个页面的form_load都会在一开始调用这个函数
call loadInitialValues()
这个函数,也是非常典型,几乎每个页面都会做类似的处理(可能函数名字不同或者没用函数),作用是获取当前页面关键字的值然后填充到控件里面。
然后这里基本上都用到functions.inc中"get"开头的一些函数,这些函数参数不同,都是处理流程一致,都是分析~iniPropValues$
这个全局变量然后取出对应的关键字的值。
retVal = getAudioSetting(enableAudio,audioMode,inputVolume,_
encoding,sampleRate,bitRate, _
alarmLevel,outputVolume)
'functions.inc中定义
function getAudioSetting(byref dimi enableAudio, byref dimi audioMode, byref dimi inputVolume, _
byref dimi encoding, byref dimi sampleRate, byref dimi bitRate, _
byref dimi alarmLevel, byref dimi outputVolume)
'TR-26
dims varName$(8) = ("enableAudio","audioMode","inputVolume", _
"encoding","sampleRate","bitRate", _
"alarmLevel","outputVolume")
dims propName$(8) = ("gaudioenable","gaudiomode","gaudioinvolume", _
"gencoding","gsamplerate","gaudiobitrate", _
"galarmlevel","gaudiooutvolume")
dims tempVal$(8)
dimi i,retVal
retVal = getiniValues(propName$,tempVal$)
if retVal = 0 then
for i= 0 to ubound(tempVal$)
{varName$(i)} = strtoint(tempVal$(i))
next
endif
return retVal
End Function
所以当你添加新的关键字时,做法可以是在原有函数基础增加参数。也可以按照里面的这些函数的形式和逻辑重新定义一个新的函数...
下面介绍另外一个重要函数,那就是savepage
函数,记住不管你页面是否需要保存,都必须存在这个函数。正如前面一小节所说......
savepage
函数里面重要是使用functions.inc中"set"开头的一些函数。这些函数参数不尽相同,但是逻辑相同,都是通过发送http请求设置关键字的值。
savepage
函数一般还会做控件输入值有效的检测,错误成功信息显示等处理...
/*一般实在保存按钮的事件响应函数中调用savepage函数*/
Sub cmdsave_Click
if canReload = 1 then
savePage()
end if
End Sub
Sub savePage()
'Validate user input controls values
iff validatemodecontrols() = 0 then return
dimi retVal,i
' TR-26 set values to camera
retVal = setAudioSetting(#chkenableaudio,#drpaudiomode,atol(#sldaudioinput$),_
#drpencoding,#drpsamplerate,#drpbitrate,_
#sldalarmlevel,atol(#sldaudiooutput$))
saveSuccess = retVal
tempX = #lblsuccessmessage.x
'Based on reload flag wait for the camera to restart
if getReloadFlag() = 1 then 'TR-45
#lblsuccessmessage.style = 128
#lblsuccessmessage.x = #lblsuccessmessage.x + #lblsuccessmessage.w/3
canReload = 0
animateCount = 1
call animateLabel("lblsuccessmessage","参数更新中")
else // If Reload animation is not required
canReload = 1
end if
if canReload = 1 Then //Do the remaining actions after reload animation is done
call displaySaveStatus(saveSuccess)
end if
End Sub
function setAudioSetting(dimi enableAudio, dimi audioMode, dimi inputVolume, _
dimi encoding, dimi sampleRate, dimi bitRate, _
dimi alarmLevel, dimi outputVolume)
dimi ret
dims value$
dims responseData$
' TR-26
value$ = "audioenable="+enableAudio+"&audiomode="+audioMode+"&audioinvolume="+inputVolume+_
"&encoding="+encoding+"&samplerate="+sampleRate+"&audiobitrate="+bitRate+_
"&alarmlevel="+alarmLevel+"&audiooutvolume="+outputVolume
pprint value$
ret = setProperties(value$, responseData$)
if ret > 0 then
dims propName$(8) = ("gaudioenable","gaudiomode","gaudioinvolume", _
"gencoding","gsamplerate","gaudiobitrate", _
"galarmlevel","gaudiooutvolume")
dims propVal$(8)
propVal$(0) = enableAudio
propVal$(1) = audioMode
propVal$(2) = inputVolume
propVal$(3) = encoding
propVal$(4) = sampleRate
propVal$(5) = bitRate
propVal$(6) = alarmLevel
propVal$(7) = outputVolume
~errorKeywords$ = ""
~errorKeywords$ = chkRetStatusAndUpdate$(responseData$,propName$,propVal$)
if len(~errorKeywords$) > 0 then
return -10
else
return ret
endif
endif
return ret
End Function
跟踪到里面去,不难发现,实际上就是发送这样形式的Http请求:
vb.htm?keyword1=xx&keyword2=xxx
当增加新的关键字时,可以仿照这些set开头函数重新定义一个新的函数,也可以在原有基础上扩充。
这里介绍在GoDB端添加关键字的流程。我们忽略boa端代码的修改,所以修改完之后,既不能获取到这个关键字也无法设置成功。
现在举添加rtmp功能的例子..
页面如下,在网络/端口
设置页面添加一个checkbox和一个edit,这就对应了两个关键字,关键字名字分别为rtmp_en
和rtmp_url
(随意)
还是从from_load开始,还是从加载控件默认值的函数开始...
call ShowDefaultValues()
在这个Sub内:
dimi rtmp_en
Dims rtmp_url$
然后我们选择扩充已有的函数
retVal=getSNTPRTSPDetails(sntpServer$, multiCast,rtmp_en,rtmp_url$)
/*在functions.inc中定义*/
function getSNTPRTSPDetails(byref dims sntpServer$,byref dimi multiCast,byref dimi rtmp_en,byref dims rtmp_url$)
dims varName$(4) = ("sntpServer$","multiCast","rtmp_en","rtmp_url$")
dims propName$(4) = ("gsntpip","gmulticast","grtmp_en","grtmp_url")
dims tempVal$(4)
dimi i,ret
ret=getiniValues(propName$,tempVal$)
if ret>=0 then
for i= 0 to ubound(tempVal$)
if i=1 or i=2 then
{varName$(i)} = strtoint(tempVal$(i))
else
{varName$(i)} = tempVal$(i)
endif
next
endif
return ret
End Function
你对比下原始函数就知道我修改了哪里..
接着我们修改functions.inc中的loadIniValues
函数,将rtmp_en
和rtmp_url
添加到keywords$
字符串后面
/*....*/
keywords$ +=",aviformat,aviformatname,aviduration,avidurationname,reloadtime,qpinit1,qpinit2,qpinit3"_
",videocodeccombo,videocodeccomboname,streamname1,streamname2,streamname3"_
",localdisplayname,sdinsert,reloadflag,dmvaenable,minnamelen,maxnamelen,minpwdlen,maxpwdlen,maxaccount"_
",bkupfirmware,display_mode,ppt_sw_en,ppt_sw_sensitivity,ppt_sw_interval_time,channel_id,psname,piname,ch_id_name"_
",file_size,video_long,brightness2,contrast2,saturation2,sharpness2,brightness3,contrast3,saturation3,sharpness3,brightness4,contrast4,saturation4,sharpness4"_
",language,ftp_upload_option,ftp_upload_time,trackip,cloudip,serverenable,singlecodecres,rtmp_en,rtmp_url"
接着我们修改keywords.inc
文件,将这两个关键字添加到cameraKeywords$
关键字后面。"|"右面是错误提示信息,合理填写即可。
cameraKeywords$ += ",display_mode|Display mode,ppt_sw_en|PPT Switch Enable,ppt_sw_interval_time|PPT Switch interval Time,"_
",ppt_sw_sensitivity|PPT Switch sensitivity,channel_id|main display channel,language|language,trackip|track ip,cloudip|cloudip,"_
",serverenable|Upload to server,singlecodecres|resolution for single stream,rtmp_en|rtmp enable,rtmp_url|rtmp url"
接着,我们修改savepage
函数。
retVal = setSNTPRTSPDetails(#txtsntpserver$, #chkrtspmulticast,#chkrtmp,#txtrtmpurl$)
这里传入的参数是直接将对象的值传入..
function setSNTPRTSPDetails(dims sntpServer$,dimi multiCast,dimi rtmp_en,dims rtmp_url$)
dimi ret
dims SNTPRTSPDetails$
dims responseData$
SNTPRTSPDetails$="sntpip="+sntpServer$+"&multicast="+multiCast+"&rtmp_en="+rtmp_en+"&rtmp_url="+rtmp_url$
ret = setProperties(SNTPRTSPDetails$, responseData$)
if ret >= 0 then
dims propName$(4) = ("gsntpip","gmulticast","grtmp_en","grtmp_url")
dims propVal$(4)
propVal$(0) = sntpServer$
propVal$(1) = multiCast
propVal$(2) = rtmp_en
propVal$(3) = rtmp_url$
~errorKeywords$ = ""
~errorKeywords$ = chkRetStatusAndUpdate$(responseData$,propName$,propVal$)
if len(~errorKeywords$) > 0 then
return -10
else
return ret
endif
endif
return ret
End Function
这样着,关键字添加完毕了,按照前面文章说的步骤编译生成吧..
只讲怎么添加。
请自行用工具定位下面变量,函数的位置。
先从SysInfo结构体开始修改,这个结构体将保存所有网页表单,关键字的值。
我们在末尾添加
typedef struct SysInfo{
//....
__u8 rtmp_enable; ///< rtmp_enable
char rtmp_url[MAX_RTMP_URL_LEN+1];
}SysInfo;
网页需要出厂默认值,所以在修改system_default.h文件添加两个宏定义。
#define RTMP_EANABLE_DEFAULT 0 //RTMP使能,0为不使能...
#define RTMP_URL_DEFAULT "rtmp://192.168.1.120:1935/live/xxoo"
修改file_mng.c
修改全局静态结构体SysInfoDefault
,往末尾添加
static SysInfo SysInfoDefault =
{
//....
RTMP_EANABLE_DEFAULT,
RTMP_URL_DEFAULT
};
修改HttpOptionTable
结构体。
HTTP_OPTION HttpOptionTable [] =
{
//....
{ "rtmp_en" , set_rtmp_en , AUTHORITY_OPERATOR, FALSE, TRUE, NULL },
{ "rtmp_url" , set_rtmp_url , AUTHORITY_OPERATOR, FALSE, TRUE, NULL },
//...
}
接着在该表所在文件定义这两个函数,一个是整数,一个字符串,注意区别。
void set_rtmp_en(request *req, COMMAND_ARGUMENT *argm)
{
__u8 value = atoi(argm->value);
do {
ControlSystemData(SFIELD_SET_RTMP_EN, (void *)&value, sizeof(value));
req->buffer_end += sprintf(req_bufptr(req), OPTION_OK "%s\n", argm->name);
return;
} while (0);
req->buffer_end += sprintf(req_bufptr(req), OPTION_NG "%s\n", argm->name);
}
void set_rtmp_url(request *req, COMMAND_ARGUMENT *argm)
{
do {
if(ControlSystemData(SFIELD_SET_RTMP_URL, (void*)argm->value, strlen(argm->value)) < 0)
break;
req->buffer_end += sprintf(req_bufptr(req), OPTION_OK "%s\n", argm->name);
return;
} while (0);
req->buffer_end += sprintf(req_bufptr(req), OPTION_NG "%s\n", argm->name);
}
在sysctrl.h
定义SFIELD_SET_RTMP_EN和SFIELD_SET_RTMP_URL
在ControlSystemData
函数中添加
case SFIELD_SET_RTMP_URL:
ret = SetSysCommon(data, len,SYS_MSG_SET_RTMP_URL);
break;
case SFIELD_SET_RTMP_EN:
ret = SetSysCommon(data, len, SYS_MSG_SET_RTMP_EN);
break;
在Msg_Def.h
中定义SYS_MSG_SET_RTMP_URL
和SYS_MSG_SET_RTMP_EN
在ProcSysMsg
函数添加
case SYS_MSG_SET_RTMP_EN:
{
unsigned char value;
DBG("SYS_MSG_SET_RTMP_EN\n");
if(pMsg->length != sizeof(value))
break;
ShareMemRead(pMsg->offset, &value, pMsg->length);
if(SetRtmpEn(value) != 0){
printf("\nSystemServer:Fail at SYS_MSG_SET_MULTICAST\n");
break;
}
ret = 1;
break;
}
case SYS_MSG_SET_RTMP_URL:
{
if(pMsg->length > SYSTEM_SERVER_BUFFER_LENGTH){
printf("String length bigger then System Server Buffer\n");
break;
}
ShareMemRead(pMsg->offset, buffer, pMsg->length);
if(SetRtmpUrl(buffer, pMsg->length) != 0){
printf("Fail at SYS_MSG_SET_RTMP_URL\n");
break;
}
ret = 1;
break;
}
定义SetRtmpUrl
和SetRtmpEn
函数(这里在system_control.c中定义)
这两个函数的内容就非常自由了,不过和其它相同的地方就是就是需要将关键字保存到sysenv.cfg文件中。
所以分别各自定义一个函数用于保存关键字。
fSetRtmpEn
和fSetRtmpUrl
(file_msg_drv.c)中定义。
int fSetRtmpEn(unsigned char value)
{
SysInfo *pSysInfo = (SysInfo *)pShareMem;
if(pSysInfo == NULL)
return -1;
if (value == pSysInfo->rtmp_enable)
return 0;
memcpy(&pSysInfo->rtmp_enable, &value, sizeof(value));
return SetSysInfo(0);
}
int fSetRtmpUrl(void *buf, int len)
{
SysInfo *pSysInfo = (SysInfo *)pShareMem;
if(pSysInfo == NULL)
return -1;
if(sizeof(pSysInfo->rtmp_url) < len + 1)
return -1;
memcpy(&pSysInfo->rtmp_url, (char*)buf, len);
pSysInfo->rtmp_url[len] = '\0';
return SetSysInfo(0);
}
int SetRtmpEn(unsigned char value)
{
int ret = 0;
SysInfo *pSysInfo = GetSysInfo();
if(pSysInfo == NULL)
return -1;
if(value != pSysInfo->rtmp_enable)
{
ret = fSetRtmpEn(value);
if ( value == 1 )
rtmp_start();
else
rtmp_stop();
}
return ret;
}
int SetRtmpUrl(void * buf, int length)
{
int ret = 0;
signed char* serverip=(signed char*)buf;
SysInfo *pSysInfo = GetSysInfo();
if(pSysInfo == NULL)
return -1;
if(strncmp(serverip,pSysInfo->rtmp_url,length) != 0)
{
ret = fSetRtmpUrl(buf,length);
rtmp_stop();
if ( pSysInfo->rtmp_enable )
rtmp_start();
}
return ret;
}
修改HttpArgument
结构体,用于获取关键字的值
HTML_ARGUMENT HttpArgument [] =
{
//...
{ "rtmp_en" , para_rtmp_en , AUTHORITY_VIEWER , NULL },
{ "rtmp_url" , para_rtmp_url , AUTHORITY_VIEWER , NULL },
//...
};
int para_rtmp_en(char *data, char *arg)
{
SysInfo* pSysInfo = GetSysInfo();
if(pSysInfo == NULL)
return -1;
return sprintf(data, "%d", pSysInfo->rtmp_enable);
}
int para_rtmp_url(char *data, char *arg)
{
SysInfo* pSysInfo = GetSysInfo();
if(pSysInfo == NULL)
return -1;
return sprintf(data, "%s", pSysInfo->rtmp_url);
}
以上就成功的加入了关键字了,在其它线程里面,你只需要调用
SysInfo *pSysInfo = GetSysInfo();
//pSysInfo->rtmp_en
//pSysInfo->rtmp_url