
//OpenSCADA system module DAQ.DCON file: DCON_client.cpp
/***************************************************************************
 *   Copyright (C) 2008-2011 by Almaz Karimov                              *
 *		   2008-2014 by Roman Savochenko, rom_as@oscada.org        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; version 2 of the License.               *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <pcre.h>
#include <pcrecpp.h>
#include <tsys.h>
#include <ttypeparam.h>
#include <map>
#include <cmath>
#include "owen.h"

//*************************************************
//* Modul info!                                   *
#define MOD_ID		"Owen"
#define MOD_NAME	_("Owen")
#define MOD_TYPE	SDAQ_ID
#define VER_TYPE	SDAQ_VER
#define MOD_VER		"0.0.1"
#define AUTHORS		_("Andrey Polevoy")
#define DESCRIPTION	_("Provides an implementation of Owen protocol.")
#define LICENSE		"GPL2"
//*************************************************

Owen::TTpContr *Owen::mod;  //Pointer for direct access to the module

extern "C"
{
#ifdef MOD_INCL
    TModule::SAt daq_DCON_module(int n_mod)
#else
    TModule::SAt module(int n_mod)
#endif
    {
	if(n_mod == 0)	return TModule::SAt(MOD_ID,MOD_TYPE,VER_TYPE);
	return TModule::SAt("");
    }

#ifdef MOD_INCL
    TModule *daq_Owen_attach(const TModule::SAt &AtMod, const string &source)
#else
    TModule *attach(const TModule::SAt &AtMod, const string &source)
#endif
    {
	if(AtMod == TModule::SAt(MOD_ID,MOD_TYPE,VER_TYPE)) return new Owen::TTpContr(source);
	return NULL;
    }
}

using namespace Owen;

//******************************************************
//* TTpContr                                           *
//******************************************************
TTpContr::TTpContr(string name) : TTypeDAQ(MOD_ID)
{
    mod = this;
    modInfoMainSet(MOD_NAME, MOD_TYPE, MOD_VER, AUTHORS, DESCRIPTION, LICENSE, name);
}

TTpContr::~TTpContr( )
{

}

void TTpContr::postEnable( int flag )
{
    TTypeDAQ::postEnable( flag );
    //Controler's bd structure
    fldAdd(new TFld("PRM_BD",_("Parameters table"),TFld::String,TFld::NoFlag,"30",""));
    fldAdd(new TFld("PERIOD",_("Gather data period (s)"),TFld::Integer,TFld::NoFlag,"6","0","0;100"));	//!!!! Remove at further
    fldAdd(new TFld("SCHEDULE",_("Acquisition schedule"),TFld::String,TFld::NoFlag,"100","1"));
    fldAdd(new TFld("PRIOR",_("Gather task priority"),TFld::Integer,TFld::NoFlag,"2","0","-1;99"));
    fldAdd(new TFld("ADDR",_("Transport"),TFld::String,TFld::NoFlag,"41",""));
    fldAdd(new TFld("REQ_TRY",_("Request tries"),TFld::Integer,TFld::NoFlag,"1","1","1;10"));
    //Parameter type bd structure
    int t_prm = tpParmAdd("std","PRM_BD",_("Standard"));
    tpPrmAt(t_prm).fldAdd(new TFld("MOD_ADDR",_("Module address"),TFld::Integer,TFld::NoFlag|TCfg::NoVal,"20","1","0;255"));
    tpPrmAt(t_prm).fldAdd(new TFld("ADDR_11BIT",_("Address 11bit"),TFld::Boolean,TFld::NoFlag|TCfg::NoVal,"1","0"));
    tpPrmAt(t_prm).fldAdd(new TFld("ATTR_LS",_("Attributes list"),TFld::String,TFld::FullText|TCfg::NoVal|TCfg::TransltText,"100000",""));
}

void TTpContr::load_( )
{
    //Load parameters from command line
}

void TTpContr::save_( )
{

}

TController *TTpContr::ContrAttach( const string &name, const string &daq_db ) { return new TMdContr(name,daq_db,this); }

//******************************************************
//* TMdContr                                           *
//******************************************************
TMdContr::TMdContr( string name_c, const string &daq_db, TElem *cfgelem ) :
    TController(name_c, daq_db, cfgelem),
    mAddr(cfg("ADDR")), mPerOld(cfg("PERIOD").getId()), mPrior(cfg("PRIOR").getId()), connTry(cfg("REQ_TRY").getId()),
    prc_st(false), call_st(false), endrun_req(false), mPer(1e9), tm_gath(0)
{
    cfg("PRM_BD").setS("OwenPrm_"+name_c);
}

TMdContr::~TMdContr( )
{
    if(startStat()) stop();
}

string TMdContr::getStatus( )
{
    string rez = TController::getStatus();

    if(startStat() && !redntUse()) {
	if(call_st)	rez += TSYS::strMess(_("Call now. "));
	if(period())	rez += TSYS::strMess(_("Call by period: %s. "),tm2s(1e-3*period()).c_str());
	else rez += TSYS::strMess(_("Call next by cron '%s'. "),tm2s(TSYS::cron(cron()),"%d-%m-%Y %R").c_str());
	rez += TSYS::strMess(_("Spent time: %s. "),tm2s(tm_gath).c_str());
    }
    return rez;
}


TParamContr *TMdContr::ParamAttach( const string &name, int type ) { return new TMdPrm(name, &owner().tpPrmAt(type)); }

void TMdContr::load_( )
{
    if(!SYS->chkSelDB(DB())) throw TError();
    TController::load_( );
    //Check for get old period method value
    if(mPerOld) { cfg("SCHEDULE").setS(i2s(mPerOld)); mPerOld = 0; }
}

void TMdContr::disable_( )
{

}

void TMdContr::start_( )
{
    if(prc_st)	return;
    //Schedule process
    mPer = TSYS::strSepParse(cron(),1,' ').empty() ? vmax(0,1e9*s2r(cron())) : 0;
    //Fix old address format
    if(addr().size() && TSYS::strParse(addr(),1,".").empty())	mAddr = "Serial."+addr();
    //Establish connection
    AutoHD<TTransportOut> tr = SYS->transport().at().at(TSYS::strSepParse(addr(),0,'.')).at().outAt(TSYS::strSepParse(addr(),1,'.'));
    try { tr.at().start(); }
    catch(TError err) { mess_err(err.cat.c_str(),"%s",err.mess.c_str()); }
    //Start the gathering data task
    SYS->taskCreate(nodePath('.',true), mPrior, TMdContr::Task, this);
}

void TMdContr::stop_( )
{
	//Stop the request and calc data task
    if(prc_st) SYS->taskDestroy(nodePath('.',true), &endrun_req);
}

bool TMdContr::cfgChange( TCfg &co, const TVariant &pc )	{ TController::cfgChange(co, pc); return true; }

void TMdContr::prmEn( const string &id, bool val )
{
	ResAlloc res( en_res, true );
	unsigned i_prm;
    for(i_prm = 0; i_prm < p_hd.size(); i_prm++){
		if(p_hd[i_prm].at().id() == id) break;
	}
	if(val && i_prm >= p_hd.size())	p_hd.push_back(at(id));
	if(!val && i_prm < p_hd.size())	p_hd.erase(p_hd.begin()+i_prm);
}

void *TMdContr::Task( void *icntr )
{
    string str, pdu, comm_txterr, temp;
    int n, m, i, dblk;
	vector<string> als, vls;
    TMdContr &cntr = *(TMdContr *)icntr;
	TElem prmEl;
	AutoHD<TVal> attr;
	cntr.endrun_req = false;
    cntr.prc_st = true;
    try {
	while(!cntr.endrun_req) {
	    if(!cntr.redntUse()) {
		cntr.call_st = true;
		int64_t t_cnt = TSYS::curTime();
		//Update controller's data
		ResAlloc res(cntr.en_res, false);
		for(unsigned i_p = 0; i_p < cntr.p_hd.size(); i_p++) {
			//Reset errors
		    comm_txterr = "0";
			cntr.p_hd[i_p].at().elem().fldList( als );
			//for ( i=0; i < als.size(); i++) mess_debug_(cntr.nodePath().c_str(), _("Task	als[%i] -> '%s'"), i, als[i].c_str() ); //'rEAd_0' --- 'rEAd_7'
			//cntr.p_hd[i_p].at().vlList( vls );
			//for ( i=0; i < vls.size(); i++) mess_debug_(cntr.nodePath().c_str(), _("Task	vls[%i] -> '%s'"), i, vls[i].c_str() ); //'SHIFR' 'NAME' 'DESCR' 'err' 'rEAd_0' --- 'rEAd_7'
			//Process the attributes
			for ( i=0; i < als.size(); i++) {	
				mess_debug_(cntr.nodePath().c_str(), _("\n"));
				if ( als.size() ) mess_debug_(cntr.nodePath().c_str(), _("Task	als[%i] -> '%s'"), i, als[i].c_str() );	//'rEAd_0' --- 'rEAd_7'
				if ( cntr.p_hd[i_p].at().end_prm )	break;
				if (cntr.p_hd[i_p].at().attr.find(als[i])==cntr.p_hd[i_p].at().attr.end()) continue;
				//Send request
				pdu = cntr.p_hd[i_p].at().attr.find(als[i]) -> second.readReq;
				comm_txterr=cntr.OwenReq( pdu );
				//mess_debug_(cntr.nodePath().c_str(), _("Task	resp addr:%i"), ((((int) pdu[0])&0xFF)<<3) | ((((int) pdu[1])&0xFF)>>5) );
				//mess_debug_(cntr.nodePath().c_str(), _("Task	req flag:%i"), ((((int) pdu[1])&0x10)>>4) );
				//mess_debug_(cntr.nodePath().c_str(), _("Task	data block:%i"), (((int) pdu[1])&0xF) );
				//mess_debug_(cntr.nodePath().c_str(), _("Task	hash name:%#x"), ((((int) pdu[2])&0xFF)<<8) | (((int) pdu[3])&0xFF) );
				//Data block in response
				dblk=((int) pdu[1])&0xF;
				cntr.p_hd[i_p].at().attr.find(als[i]) -> second.err = comm_txterr;
				cntr.p_hd[i_p].at().attr.find(als[i]) -> second.respReq = pdu;			//					12bytes of response data is 24letters of line inteface request
				//mess_debug_(cntr.nodePath().c_str(), _("Task	resp: -> '%s'"), pdu.c_str() );	//Task	resp: -> 'f� �B�|mi �'	//#GKIMONOK KIJSPRKSQLQV NPGM
				if ( cntr.p_hd[i_p].at().vlPresent( als[i] ) ){
					if ( (pdu.size()==(dblk+4)) && comm_txterr=="0" && dblk != 0 ){
/*							string data = pdu.substr( 4,pdu.size() );
							for ( int j=0; j< data.size(); j++ ){
								mess_debug_(cntr.nodePath().c_str(), _("Task	data[%i]:%#x"), j, ((int) data[j])&0xFF );
							}
*/						switch( cntr.p_hd[i_p].at().attr.find(als[i]) -> second.type ) {
/*TFld::Int32*/				case TFld::Integer	:break;
/*TFld::Double*/			case TFld::Real		:{
								switch( cntr.p_hd[i_p].at().attr.find(als[i]) -> second.ftype ) {
/*f*/								case 0	:{ cntr.p_hd[i_p].at().attr.find(als[i]) -> second.db.f0 = cntr.ieee2float( pdu.substr( 4,4) ); break;}
/*f_b*/								case 1	:{ cntr.p_hd[i_p].at().attr.find(als[i]) -> second.db.f0 = cntr.b2float( pdu.substr(4) ); break;}
/*f_bd*/							case 2	:break;
									default:break;
								}
							}
							case TFld::String	:break;
							case TFld::Int16	:break;
							case TFld::Int64	:{
								int c=0;
								string sd = pdu.substr(4,8);
								if ( dblk != sd.size() ) break;
								cntr.p_hd[i_p].at().attr.find(als[i]) -> second.db.lli = 0;
								for ( int j=sd.size()-1, c=0; j>-1; j--,c++ ) cntr.p_hd[i_p].at().attr.find(als[i]) -> second.db.c[c] = ((int) sd[j])&0xFF;
								//mess_debug_(cntr.nodePath().c_str(), _("Task	lli1:%#x"), cntr.p_hd[i_p].at().attr.find(als[i]) -> second.db.li1 );
								//mess_debug_(cntr.nodePath().c_str(), _("Task	lli0:%#x"), cntr.p_hd[i_p].at().attr.find(als[i]) -> second.db.li0 );
								break;
							}
						default:break;
						}
					}
					else{
						switch( cntr.p_hd[i_p].at().attr.find(als[i]) -> second.type ) {
/*TFld::Int32*/			case TFld::Integer	:break;
/*TFld::Double*/		case TFld::Real		:cntr.p_hd[i_p].at().attr.find(als[i]) -> second.db.f0 = EVAL_REAL; break;
						case TFld::String	:break;
						case TFld::Int16	:break;
						case TFld::Int64	:cntr.p_hd[i_p].at().attr.find(als[i]) -> second.db.lli = EVAL_INT; break;
						default:break;
						}
					}
				//mess_debug_(cntr.nodePath().c_str(), _("Task	als[%i] comm_err: -> '%s'"), i, comm_txterr.c_str() );
				cntr.p_hd[i_p].at().comm_err.setVal(comm_txterr);
				}
				else break;
			//cntr		->->->->->->->->->->->	TMdContr
			//cntr.p_hd[x].at()		->->->->->	TMdPrm @ TValue
			//cntr.p_hd[x].at().elem()	->->->	TElem
			//cntr.p_hd[x].at().fldAt()	->->->	TFld
			//cntr.p_hd[x].at().vlAt()	->->->	AutoHD<TVal>
			//cntr.p_hd[x].at().vlAt().at()->	TVal
			}
		}
		res.release();
		//Calc acquisition process time
		cntr.tm_gath = TSYS::curTime()-t_cnt;
		cntr.call_st = false;
	    }
	    //Calc next work time and sleep
	    TSYS::taskSleep((int64_t)cntr.period(), (cntr.period()?0:TSYS::cron(cntr.cron())));
	}
    }
    catch(TError err)	{ mess_err(err.cat.c_str(), err.mess.c_str()); }
    cntr.prc_st = false;
    return NULL;
}

void TMdContr::cntrCmdProc( XMLNode *opt )
{
    //Get page info
    if(opt->name() == "info") {
	TController::cntrCmdProc(opt);
	ctrMkNode("fld",opt,-1,"/cntr/cfg/ADDR",mAddr.fld().descr(),startStat()?R_R_R_:RWRWR_,"root",SDAQ_ID,3,
	    "tp","str","dest","select","select","/cntr/cfg/trLst");
	ctrRemoveNode(opt,"/cntr/cfg/PERIOD");
	ctrMkNode("fld",opt,-1,"/cntr/cfg/SCHEDULE",cfg("SCHEDULE").fld().descr(),startStat()?R_R_R_:RWRWR_,"root",SDAQ_ID,4,
	    "tp","str","dest","sel_ed","sel_list",TMess::labSecCRONsel(),"help",TMess::labSecCRON());
	ctrMkNode("fld",opt,-1,"/cntr/cfg/PRIOR",cfg("PRIOR").fld().descr(),startStat()?R_R_R_:RWRWR_,"root",SDAQ_ID,1,"help",TMess::labTaskPrior());
	return;
    }
    //Process command to page
    string a_path = opt->attr("path");
    if(a_path == "/cntr/cfg/trLst" && ctrChkNode(opt)) {
	vector<string> sls;
	SYS->transport().at().outTrList(sls);
	for(unsigned i_s = 0; i_s < sls.size(); i_s++)
	    opt->childAdd("el")->setText(sls[i_s]);
    }
    else TController::cntrCmdProc(opt);
}

string TMdContr::attrName( string attr )
{
		string name;
 		vector<string>	repl;
		repl.push_back("\\s");
		repl.push_back("#sp#");
		repl.push_back("_");
		repl.push_back("#ud#");
		repl.push_back("\\-");
		repl.push_back("#hp#");
		repl.push_back("/");
		repl.push_back("#sl#");
		repl.push_back("\\.");
		repl.push_back("#pr#");
		repl.push_back("#");
		repl.push_back("_");
		name=attr;
		for ( int i=0; i<repl.size(); i+=2 ){
			pcrecpp::RE re_rep( repl[i] );
			re_rep.GlobalReplace( repl[i+1], &name );
		}
		return name;
}

char TMdContr::char4hash( char ch, char next )
{
	string temp;
	char char_for_hash;
	ch=toupper(ch);
		if ( isdigit(ch) ) {char_for_hash = (int)ch - (int)'0'; goto label;}
		if ( isalpha(ch) ) {char_for_hash = (int)ch - (int)'A' + 10 ; goto label;}
		if ( ch=='-' ) {char_for_hash = 10+26; goto label;}
		if ( ch=='_' ) {char_for_hash = 10+26+1; goto label;}
		if ( ch=='/' ) {char_for_hash = 10+26+2; goto label;}
		if ( ch==' ' ) char_for_hash = 10+26+3;
	label:
		char_for_hash*=2;
		if( next=='.' ) char_for_hash+=1;
	return char_for_hash;
} 

string TMdContr::netName( string name, string *err )
{
	  string nn;
	  //Getting first 4 symbols with/without of the periods after ones
	  pcrecpp::RE gf4s("(.\\.*.\\.*.\\.*.\\.*)");	
	  gf4s.PartialMatch( name, &nn );
	  //Find any illegal symbols
	  pcrecpp::RE figs( "[^A-Za-z0-9\\s_\\-/\\.]|\\.(?=\\.)" );	//найдет любой символ кроме букв, цифр, пробелов, подчеркиваний, дефисов, слэшей, точек 	  или ..
      if ( figs.PartialMatch( nn ) ) {
		 *err = "illegal symbol in name, don't make the attribute\n";
         return 0;
      }
	  for ( int i=0; i < nn.size(); i++ ){
		nn[i] = tolower( nn[i] );
	  }	  
	  return nn;
}

bool TMdContr::formPDU( int addr, string netName, bool addr11bit, string *pdu, string *err )
{
	  string attr_str, attr_name, attr_hash, temp, req;	
	  unsigned attr_code, crc ; 
	  attr_str=netName;
	  //Hashing of the name
	  for ( int i=0; i<attr_str.size(); i++ ){
	  	attr_hash += char4hash( attr_str[i], attr_str[i+1] );
	  	i += ( attr_str[i+1]=='.' ) ? 1 : 0;
	  }
	  attr_code = hashCRC( attr_hash,1 );
	  //MAKING OF REQUEST
	  if(addr < 0 || addr > 2047) { *err = "addr error:Device address out of range 0...2047"; return 0; }
	  req += temp.assign<int>( 1, (( addr11bit || addr>256 ) ? addr>>3 : addr ) ).at(0);
	  req += temp.assign<int>( 1, ((( addr11bit || addr>256 ) ? addr<<5 : 0 ) | 0x10) ).at(0);
	  req += temp.assign<int>( 1, (attr_code >> 8) ).at(0);
	  req += temp.assign<int>( 1, (attr_code & 255) ).at(0);
	  //Making of CRC
	  crc = hashCRC( req,0 );
	  req += temp.assign<int>( 1, (crc >> 8) ).at(0);
	  req += temp.assign<int>( 1, (crc & 255) ).at(0);
	  // Convert to ASCII
	  for( int i = 0; i < req.size(); i++ ){
	  	*pdu += temp.assign<int>( 1, (((((int) req[i])&0xFF)>>4)+71) ).at(0);
	  	*pdu += temp.assign<int>( 1, ((((int) req[i])&0x0F)+71) ).at(0);
	  }
	  pdu->insert(0,"#");
	  *pdu += "\r";
	  return 1;
}

string TMdContr::OwenReq( string &pdu )
{
    ResAlloc res(req_res, true);
    char buf[1000];
    string rez, err, temp, respBin, resp;
	int b = 0, crc;
	bool fullB = false;
    try {
		AutoHD<TTransportOut> tr = SYS->transport().at().at(TSYS::strSepParse(addr(),0,'.')).at().outAt(TSYS::strSepParse(addr(),1,'.'));
		if(!tr.at().startStat()) tr.at().start();
		if(messLev() == TMess::Debug) mess_debug_(nodePath().c_str(), _("OwReq	REQ ->	'%s'"), pdu.c_str());
		ResAlloc resN(tr.at().nodeRes(), true);
		for(int i_tr = 0, resp_len = 0; i_tr < vmax(1,vmin(10,connTry)); i_tr++) {
			try {
				resp_len = tr.at().messIO(pdu.data(), pdu.size(), buf, sizeof(buf), 0, true);
				rez.assign(buf,resp_len);
				//Wait tail
				while(resp_len && rez[rez.size()-1] != '\r') {
					try{ resp_len = tr.at().messIO(NULL, 0, buf, sizeof(buf), 0, true); } catch(TError er){ break; }
					rez.append(buf, resp_len);
				}
			}
			catch(TError er) {	//By possible the send request breakdown and no response
				if(err.empty()) err = _("10:Transport error: ") + er.mess;
				else if(err.find(er.mess) == string::npos) err += "; " + er.mess;
				continue;
			}
			//Respond process
			if(rez.size() < 14 || rez[rez.size()-1] != '\r' || rez[0] != '#' || (rez.size() % 2) != 0 ) { err = _("13:Error respond: Not full, wrong or no a respond."); continue; }
			// Remove markers
			if(messLev() == TMess::Debug) mess_debug_(nodePath().c_str(), _("OwReq	RESP ->	'%s'"), rez.c_str());	//#GKIMONOKKIJSPRKSQLQVNPGM
			if ( rez.size()>5 ) rez = rez.substr(1,rez.size()-2);
			// Convert to binary
			for( int i = 0; i < rez.size(); i++, fullB = !fullB) {
				b += ((int) rez[i]) - 71;
				if ( fullB ) { respBin += temp.assign<int>( 1, b ).at(0); b = 0; } else b *= 16;
			}	
			rez = respBin;
			crc = hashCRC( rez.substr(0,rez.size()-2) , 0 );
			// Check for CRC
			if( crc != ( ((rez[rez.size()-2]&0xFF) << 8) + ((rez[rez.size()-1])&0xFF) ) ) {
				err = _("11:CRC error."); continue;
			}
			//if(messLev() == TMess::Debug) mess_debug_(nodePath().c_str(), _("OwReq	RESP -> '%s'"), pdu.c_str());
			err = "0";
			break;
		}
    }
    catch(TError er) { err = _("10:Transport error: ")+er.mess; }
	if ( pdu.size()>3 ) pdu = rez.substr(0,rez.size()-2);
	if(err != "0") {
		if(messLev() == TMess::Debug) mess_debug_(nodePath().c_str(), _("OwReq	ERR -> '%s': %s"), pdu.c_str(), err.c_str());
    }
	res.release();
    return err;
}

unsigned TMdContr::hashCRC(string req, bool hash)
{
	//when hash is true then function calculats the hash code of name 
	int CRC = 0;
	unsigned char nbit; 
	nbit = (hash) ? 7 : 8;
	for( int i = 0 ; i < req.size(); i++) {
		int b = (hash) ? ((((int) req[i])&0xFF) << 1) : (((int) req[i])&0xFF);
		for( int j = 0; j < nbit; j++, b<<=1){								//hash func
			CRC = (((b^(CRC>>8))&0x80)?(CRC<<1)^0x8F57:CRC<<1) & 0xFFFF;	//hash func
		}																	//hash func
	}																
	return CRC;
}

float TMdContr::ieee2float( string data )
{
	float Float, incr;
	int exp;
	long mant;
	// Считаем число
	exp = ( ((int) data[0]) & 0xFF ) << 1;
	exp += ( ((int) data[1]) & 0xFF ) >> 7;
	// Обрезка экспоненты до 8 бит
	exp = exp & 255;
	exp -= 127;
	mant = (( ((int) data[1]) & 0xFF ) | 128) << 8;
	mant += ( ((int) data[2]) & 0xFF );
	mant<<= 8;
	mant += ( ((int) data[3]) & 0xFF );
	// Обрабатываем мантиссу
	Float = 1; incr = 1;
	for ( int i = 22; i >= 0; i-- ){
		if (exp > 0){
			Float *= 2;
			if ( mant & (1<<i) ) Float +=1;
		}
		if (exp <= 0){
			incr /= 2;
			if ( mant & (1<<i) ) Float += incr;
		}
		exp -= 1;
	}
	// Знак
	if ( ( ((int) data[0]) & 0xFF ) >> 7)
		Float *= -1;
	//if(messLev() == TMess::Debug) mess_debug_(nodePath().c_str(), _("ieee2float %%f Float long mant -> '%f'"), Float );
	return Float;
}

float TMdContr::b2float( string data )
{
	float Float;
	int exp, sign;
	long mant;
	sign = ((int) data[0])>>7 & 0x01;
	exp = ((int) data[0])>>4 & 0x07;
	mant = ( ((int) data[0]) & 0xF ) << 8*(data.size()-1);
	for (int i=1; i<data.size(); i++){
		mant += ( ((int) data[i]) & 0xFF ) << 8*(data.size()-i-1);
	}
	Float = pow(-1,sign) * pow(10,(-1*exp)) * mant;
	return Float;
}

//******************************************************
//* TMdPrm                                             *
//******************************************************
TMdPrm::TMdPrm( string name, TTypeParam *tp_prm ) :
    TParamContr( name, tp_prm ), p_el("w_attr"), mod_addr(cfg("MOD_ADDR").getId()), addr_11bit(cfg("ADDR_11BIT").getBd())
{
    comm_err.setVal("0");
    //Default input/output data
}

TMdPrm::~TMdPrm( )
{
	nodeDelAll( );
}

void TMdPrm::postEnable( int flag )
{
    TParamContr::postEnable(flag);
    if(!vlElemPresent(&p_el))	vlElemAtt(&p_el);
}

TMdContr &TMdPrm::owner( )	{ return (TMdContr&)TParamContr::owner(); }

void TMdPrm::enable()	{
    if(enableStat())	return;
    TParamContr::enable();
	vector<string> als;
	int rez;
	string pdu, err, nn;
    string ai, sel, atp, atp_m, atp_sub, aid, anm, aid0, anm0, awr, ch;
    string m_attrLs = cfg("ATTR_LS").getS();
    end_prm = false;
	if (attr.size()) attr.clear();
	for(int ioff = 0; (sel=TSYS::strSepParse(m_attrLs,0,'\n',&ioff)).size(); ) {
        if(sel[0] == '#') continue;
        atp = TSYS::strSepParse(sel,0,':');
        if(atp.empty()) atp = "i8";
        atp_m = TSYS::strSepParse(atp,0,'_');
        atp_sub = TSYS::strSepParse(atp,1,'_');
        ch  = TSYS::strSepParse(sel,1,':');
        ai  = TSYS::strSepParse(sel,2,':');
		nn = owner().netName( ai, &err );
        awr = TSYS::strSepParse(sel,3,':');
        aid0 = TSYS::strSepParse(sel,4,':');
		aid0 = (aid0.empty()) ? owner().attrName( ai ) : aid0;
		anm0 = TSYS::strSepParse(sel,5,':');
		anm0 = (anm0.empty()) ? aid0 : anm0;
/*
Тип ячейки (enum – TFld::Type):

    TFld::Boolean(0) — логический тип;
    TFld::Integer(1) — целочисленный тип;			i		i2
    TFld::Real(4) — вещественный тип;				f	
    TFld::String(5) — строковый тип;				c16		s16
    TFld::Object(6) — объектный тип;
    TFld::GenMask(0x0F) — маска основных типов;
    TFld::Int16(0x11) — целое знаковое 16 разрядов;	i		i2
    TFld::Int32(0x01) — целое знаковое 32 разрядов;	li		i4
    TFld::Int64(0x21) — целое знаковое 64 разрядов;	lli		i8
    TFld::Float(0x14) — float (4 байта);			f
    TFld::Double(0x04) — double (8 байт).			d
	
		    "  dt - ModBus data type (R-register[3,6(16)], C-coil[1,5(15)], RI-input register[4], CI-input coil[2]);\n"
		    "       R and RI can be expanded by suffixes:\n"
		    "         i2-Int16, i4-Int32, i8-Int64, u2-UInt16, u4-UInt32, f-Float, d-Double, b5-Bit5, s-String;\n"	
*/
		for (int i=0; i < s2i(ch); i++) {
			data = data0;
			if ( s2i(ch) != 1 ) { aid = aid0 + "_" + i2s(i); anm = anm0 + "_" + i2s(i); }
			else { aid = aid0; anm = anm0; }
			pdu.erase();
			rez = owner().formPDU( mod_addr + i, nn, addr_11bit, &pdu, &err );
			data.readReq = pdu;
			if(vlPresent(aid) && !p_el.fldPresent(aid))	continue;
			TFld::Type tp = TFld::Int64;
			if( atp_m == "i8" || atp_m == "I8" ) tp = TFld::Int64;
			else if( atp_m == "f" || atp_m == "F" ){
				tp = TFld::Real;
				data.ftype = (atp_sub == "b") ? 1 : (atp_sub == "bd") ? 2 : 0;
			}
			else if( atp_m == "s" || atp_m == "S" ) tp = TFld::String;
			data.type = tp;
			if(!p_el.fldPresent(aid) || p_el.fldAt(p_el.fldId(aid)).type() != tp) {
				if(p_el.fldPresent(aid)) p_el.fldDel(p_el.fldId(aid));
				p_el.fldAdd(new TFld(aid.c_str(),"",tp,TFld::NoFlag));
			}
			attr[aid] = data;
			int el_id = p_el.fldId(aid);
			unsigned flg = (awr=="rw") ? TVal::DirWrite|TVal::DirRead :
			   ((awr=="w") ? TVal::DirWrite :
				 TFld::NoWrite|TVal::DirRead);
			p_el.fldAt(el_id).setDescr(anm);
			p_el.fldAt(el_id).setFlg( flg );
		    //mess_debug_(owner().nodePath().c_str(), _("TMdPrm::enable() attr: '%s'"), (atp+":"+ai).c_str() );
			//p_el.fldAt(el_id).setReserve(atp+":"+ai);
			als.push_back(aid);
		}
    }
    //Check for delete DAQ parameter's attributes
    for(int i_p = 0; i_p < (int)p_el.fldSize(); i_p++) {
		unsigned i_l;
		for(i_l = 0; i_l < als.size(); i_l++)
			if(p_el.fldAt(i_p).name() == als[i_l])
		break;
		if(i_l >= als.size())
			try{ p_el.fldDel(i_p); i_p--; }
			catch(TError err){ mess_warning(err.cat.c_str(),err.mess.c_str()); }
    }
    owner().prmEn( id(), true );
}

void TMdPrm::disable()
{
	if(!enableStat())	return;
	end_prm = true;
    owner().prmEn(id(), false);
	TParamContr::disable();
    //Set EVAL to parameter attributes
    vector<string> ls;
    elem().fldList(ls);
    for(unsigned i_el = 0; i_el < ls.size(); i_el++){
		vlAt(ls[i_el]).at().setS(EVAL_STR, 0, true);
	}
}

void TMdPrm::vlGet( TVal &val )
{
	if(!enableStat() || !owner().startStat()) {
	if(val.name() == "err") {
	    if(!enableStat())			val.setS(_("1:Parameter is disabled."),0,true);
	    else if(!owner().startStat())	val.setS(_("2:Acquisition is stopped."),0,true);
	}
	else val.setS(EVAL_STR,0,true);
	return;
    }
	//mess_debug_(owner().nodePath().c_str(), _("TMdPrm::vlGet val.name(): '%s'"), val.name().c_str() ); //	'rEAd_0'
    if(owner().redntUse()) return;
    if(val.name() == "comm_err")		val.setS(comm_err.getVal(),0,true);
    else if(val.name() == "err") {
	if(comm_err.getVal() != "0")	val.setS(comm_err.getVal(),0,true);
	else val.setS("0",0,true);
    return;
	}
	if ( attr.find(val.name()) != attr.end() ) {
		switch( attr.find(val.name()) -> second.type ) {
			case TFld::Integer	:break;
			case TFld::Real		:val.setR( attr.find( val.name() ) -> second.db.f0 ,0,true); break;
			case TFld::String	:break;
			case TFld::Int16	:break;
			case TFld::Int64	:val.setI( attr.find( val.name() ) -> second.db.lli ,0,true); break;
			default:break;
		}
	}
}

void TMdPrm::vlSet( TVal &vo, const TVariant &vl, const TVariant &pvl )
{
    if(!enableStat() || !owner().startStat())	{ vo.setI(EVAL_INT, 0, true); return; }
    //Send to active reserve station
    if(owner().redntUse()) {
	if(vl == pvl) return;
	XMLNode req("set");
	req.setAttr("path",nodePath(0,true)+"/%2fserv%2fattr")->childAdd("el")->setAttr("id",vo.name())->setText(vl.getS());
	SYS->daq().at().rdStRequest(owner().workId(),req);
	return;
    }
    //Direct write
	if ( attr.find(vo.name()) != attr.end() ) {
		switch( attr.find(vo.name()) -> second.type ) {
			case TFld::Integer	:break;
			case TFld::Real		:attr.find( vo.name() ) -> second.db.f0 = vl.getR(); break;
			case TFld::String	:break;
			case TFld::Int16	:break;
			case TFld::Int64	:attr.find( vo.name() ) -> second.db.lli = vl.getI(); break;
			default:break;
		}
	}
}

void TMdPrm::vlArchMake( TVal &val )
{
    TParamContr::vlArchMake(val);
    if(val.arch().freeStat()) return;
    val.arch().at().setSrcMode(TVArchive::PassiveAttr);
    val.arch().at().setPeriod(owner().period() ? (int64_t)owner().period()/1000 : 1000000);
    val.arch().at().setHardGrid(true);
    val.arch().at().setHighResTm(true);
}

bool TMdPrm::cfgChange( TCfg &co, const TVariant &pc )
{
    TParamContr::cfgChange(co, pc);
    if(enableStat() && (co.fld().name() == "MOD_ADDR" || co.fld().name() == "ADDR_11BIT" || co.fld().name() == "ATTR_LS"))	disable();
    return true;
}

void TMdPrm::cntrCmdProc( XMLNode *opt )
{
	//mess_debug_(owner().nodePath().c_str(), _("TMdPrm::cntrCmdProc(opt) opt->attr('path'):%s"), opt->attr("path").c_str());
	//mess_debug_(owner().nodePath().c_str(), _("TMdPrm::cntrCmdProc(opt) opt->name():%s"), opt->name().c_str());
    //Get page info
    if(opt->name() == "info") {
		TParamContr::cntrCmdProc(opt);
/*
	    ctrMkNode("fld",opt,-1,"/prm/cfg/ATTR_LS",EVAL_STR,(owner().startStat()&&enableStat())?R_R_R_:RWRWR_,"root",SDAQ_ID,6,
		"rows","8","SnthHgl","1",
		"help",_("Attributes configuration list. List must be written by lines in format: \"{dt}:{ch}:{netnm}:{rw}:{id}:{name}\".\n"
		    "Where:\n"
		    "       Start from symbol '#' for comment line;\n"
		    "  dt - Owen data type (R-register[3,6(16)], C-coil[1,5(15)], RI-input register[4], CI-input coil[2]);\n"
		    "         i2-Int16, i4-Int32, i8-Int64, u2-UInt16, u4-UInt32, f-Float, d-Double, b5-Bit5, s-String;\n"
		    "  ch - quantity of chanels;\n"
			"  netnm - Owen parameter's net name;\n"
		    "  rw - read/write mode (r-read; w-write; rw-readwrite);\n"
		    "  id - created attribute identifier, if empty default name {netnm}_{ch} suffix will be added  ;\n"
		    "  name - created attribute name.\n"
		    "Examples:\n"
		    "  \"R:8:rEAd:r::\" - TRM138 8 chanels temperatures;\n"));
*/
		return;
    }
	    //Process command to page
    string a_path = opt->attr("path");
    if(a_path == "/prm/cfg/ATTR_LS" && ctrChkNode(opt,"SnthHgl",RWRWR_,"root",SDAQ_ID,SEC_RD)) {
	opt->childAdd("rule")->setAttr("expr","^#[^\n]*")->setAttr("color","gray")->setAttr("font_italic","1");
	opt->childAdd("rule")->setAttr("expr",":(r|w|rw):")->setAttr("color","red");
	opt->childAdd("rule")->setAttr("expr",":(0[xX][0-9a-fA-F]*|[0-9]*),?(0[xX][0-9a-fA-F]*|[0-9]*)")->setAttr("color","blue");
	opt->childAdd("rule")->setAttr("expr","^(C|CI|F|f|I|I?_[iubfds]\\d*)")->setAttr("color","darkorange");
	opt->childAdd("rule")->setAttr("expr","\\:")->setAttr("color","blue");
    }
    else TParamContr::cntrCmdProc(opt);

    //Process command to page
//	TParamContr::cntrCmdProc(opt);
}