在实际的项目中,虽然大多数场景下,FsuOS的默认配置可以达到比较满意的效果,但是有时候我们也遇到性能尴尬的情况,核心原因主要还是在嵌入式系统上PHP的执行效率不高,在通常的配置下,设备数量和信号都不算多的情况, 整体性能还可以接受。但在以下情况下,性能恶化较快,表现为内存快速耗尽,CPU特别繁忙导致响应特别慢:
我们注意到有不少友商是直接采用C语言进行设备协议驱动的研发和信号的映射,我们评估过此方案,C/CPP语言可以保证程序的执行效率,基本上没有其他语言可以达到同等性能,但是C语言开发的问题也很明显,可使用C/CPP语言进行研发的程序员较少, 研发坑比较多,细节问题较多,开发效率低速度慢,难以快速适配新问题,因此FsuOS采用的是混合结构,cpp编写的驱动负责采集和告警,保持最大的实时性。PHP做用户数据展示和B接口转换,和SC对接。
我们选择PHP也不是随便选的,考虑到目标FSU设备中位配置为CPU arm9 500Mhz, 内存128MB, 硬盘256MB; 低配置为CPU arm9 200MHz, 内存64MB, 硬盘64MB, 常用的web研发语言.net/c#,java,python,ruby,nodejs个个都是内存杀手,这点配置都不够他们启动的,而PHP的执行程序仅仅5MB就可以将php运行起来,其他小语言lua,tcl本身缺乏web的生态,做一些业务逻辑还可以,做web麻烦更多。
问题还是要解决,FsuOS提供了B接口性能增强模式(boost模式),目前支持电信和联通B接口,对电信和联通的GET_DATA,GET_AIDATA,GET_SUINFO等心跳,请求频繁的动作,由SMDDevice直接处理,不经过PHP,其他普通的B接口命令,继续由php响应。
激活boost模式的准备工作: 调整设备协议驱动,由于GET_DATA由SMDDevice响应,SMDevice就要知道怎么将设备实时数据转换为B接口数据。 方法为在DeviceIoControl函数,对ioControlCode加上801分支,此分支返回json格式的解析后的数据,同php的helper文件返回的一致。 大家可以参考下面代码,我们对中间重点进行解读:
boost::posix_time::ptime lastJsonTime_;
std::mutex jsonStrMutex;
std::string jsonStr;
int H3GTABatteryBalancer160::DeviceIoControl(int ioControlCode, const void* inBuffer, int inBufferSize, void* outBuffer, int outBufferSize, int& bytesReturned)
{
switch(ioControlCode) {
case 801:
{
//get fsuos json data
std::lock_guard<std::mutex> lLock(jsonStrMutex);
if(lastTime != lastJsonTime_)
{
lastJsonTime_ = lastTime;
Json::Value retJson;
for(int i=0;i<240;i++)
{
std::string vName = "电池" + boost::lexical_cast<std::string>(i+1) + "电压";
std::stringstream vSS;
vSS<<std::setprecision(3)<<((float)cData.battery_voltage[i])/1000;
retJson[vName] = vSS.str();
std::string rName = "电池" + boost::lexical_cast<std::string>(i+1) + "内阻";
std::stringstream rSS;
rSS<<std::setprecision(3)<<((float)cData.battery_resist[i])/1000;
retJson[rName] = rSS.str();
std::string tName = "电池" + boost::lexical_cast<std::string>(i+1) + "温度";
std::stringstream tSS;
int sign = ((cData.battery_temperature[i] >> 15) & 0x1) ? -1 : 1;
tSS<<std::setprecision(2)<<sign*((float)(cData.battery_temperature[i]&0x7FFF))/100;
retJson[tName] = tSS.str();
}
std::stringstream gSS;
gSS<<std::setprecision(3)<<((float)cData.group_voltage)/1000;
retJson["整组电压"] = gSS.str();
std::stringstream iSS;
iSS<<std::setprecision(3)<<((float)cData.current)/1000;
retJson["充放电电流"] = iSS.str();
std::stringstream t1SS;
t1SS<<std::setprecision(1)<<((float)cData.temperature1)/10;
retJson["环境温度1"] = t1SS.str();
std::stringstream t2SS;
t2SS<<std::setprecision(1)<<((float)cData.temperature2)/10;
retJson["环境温度2"] = t2SS.str();
std::stringstream uSS;
uSS<<(int)cData.update_time.year<<"-"<<(int)cData.update_time.month<<"-"<<(int)cData.update_time.day<<" "<<(int)cData.update_time.hour<<":"<<(int)cData.update_time.minute<<":"<<(int)cData.update_time.second;
retJson["更新时间"] = uSS.str();
Json::FastWriter writer;
jsonStr = writer.write(retJson);
}
if(outBufferSize >= jsonStr.size())
{
memcpy(outBuffer, jsonStr.data(), jsonStr.size());
bytesReturned = jsonStr.size();
return 0;
}else{
bytesReturned = jsonStr.size();
return -1;
}
return 0;
}
激活步骤:
b_mode=2 工作在电信B接口模式
b_mode_port=8080 B接口监听端口8080
b_mode_worker=5 B 接口支持的并发为5个
##
## Document root
##
server.document-root = server_root
#$SERVER["socket"] == ":8080" {
# server.document-root = server_root
#}
4 .在/opt/SMDDevice或者/FsuOS下做符号链接,SMDDevice启动时候可以读取到B接口映射配置文件
ln -s /opt/www/application/helpers/ini .
5. 重启lighttpd
6. 重启SMDDevice