
//OpenSCADA file: tdaqs.cpp
/***************************************************************************
 *   Copyright (C) 2003-2026 by Roman Savochenko, <roman@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 "tsys.h"
#include "tmess.h"
#include "tcontroller.h"
#include "tmodule.h"
#include "tvalue.h"
#include "tdaqs.h"

using namespace OSCADA;

//*************************************************
//* TDAQS                                         *
//*************************************************
TDAQS::TDAQS( ) : TSubSYS(SDAQ_ID, true), mElErr("Error"), mRdRestDtTm(1)
{
    mTmpLib = grpAdd("tmplb_");

    //Templates lib db structure
    mElLib.fldAdd(new TFld("ID",trS("Identifier"),TFld::String,TCfg::Key,i2s(limObjID_SZ).c_str()));
    mElLib.fldAdd(new TFld("NAME",trS("Name"),TFld::String,TFld::TransltText,i2s(limObjNm_SZ).c_str()));
    mElLib.fldAdd(new TFld("DESCR",trS("Description"),TFld::String,TFld::FullText|TFld::TransltText,i2s(limObjDscr_SZ).c_str()));
    mElLib.fldAdd(new TFld("DB",trS("Data base"),TFld::String,TFld::NoFlag,"30"));	//????[v1.0] Remove

    //Template DB structure
    mElTmpl.fldAdd(new TFld("ID",trS("Identifier"),TFld::String,TCfg::Key,i2s(limObjID_SZ).c_str()));
    mElTmpl.fldAdd(new TFld("NAME",trS("Name"),TFld::String,TFld::TransltText,i2s(limObjNm_SZ).c_str()));
    mElTmpl.fldAdd(new TFld("DESCR",trS("Description"),TFld::String,TFld::FullText|TFld::TransltText,i2s(limObjDscr_SZ).c_str()));
    mElTmpl.fldAdd(new TFld("MAXCALCTM",trS("Maximum calculate time, seconds"),TFld::Integer,TFld::NoFlag,"4","10","0;3600"));
    mElTmpl.fldAdd(new TFld("PR_TR",trS("Completely translate the procedure"),TFld::Boolean,TFld::NoFlag,"1","0"));
    mElTmpl.fldAdd(new TFld("PROGRAM",trS("Procedure"),TFld::String,TFld::TransltText,"1000000"));
    mElTmpl.fldAdd(new TFld("TIMESTAMP",trS("Date of modification"),TFld::Integer,TFld::DateTimeDec));

    //Parameter template IO DB structure
    mElTmplIO.fldAdd(new TFld("TMPL_ID",trS("Template identifier"),TFld::String,TCfg::Key,i2s(limObjID_SZ).c_str()));
    mElTmplIO.fldAdd(new TFld("ID",trS("Identifier"),TFld::String,TCfg::Key,i2s(limObjID_SZ*1.5).c_str()));
    mElTmplIO.fldAdd(new TFld("NAME",trS("Name"),TFld::String,TFld::TransltText,i2s(limObjNm_SZ).c_str()));
    mElTmplIO.fldAdd(new TFld("TYPE",trS("Value type"),TFld::Integer,TFld::NoFlag,"1"));
    mElTmplIO.fldAdd(new TFld("FLAGS",trS("Flags"),TFld::Integer,TFld::NoFlag,"4"));
    mElTmplIO.fldAdd(new TFld("VALUE",trS("Value"),TFld::String,TFld::TransltText,"50"));
    mElTmplIO.fldAdd(new TFld("POS",trS("Real position"),TFld::Integer,TFld::NoFlag,"4"));

    //Error attributes
    mElErr.fldAdd(new TFld("err",trS("Error"),TFld::String,TFld::NoWrite|TVal::DirRead));

    if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TDAQS::~TDAQS( )
{
    nodeDelAll();

    if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), -1);
}

string TDAQS::objName( )		{ return TSubSYS::objName()+":TDAQS"; }

void TDAQS::rdActCntrList( vector<string> &ls, bool isRun )
{
    AutoHD<TController> cntr;
    ls.clear();
    vector<string> mls, cls;
    modList(mls);
    for(unsigned iM = 0; iM < mls.size(); iM++) {
	if(!at(mls[iM]).at().redntAllow()) continue;
	at(mls[iM]).at().list(cls);
	for(unsigned iC = 0; iC < cls.size(); iC++) {
	    cntr = at(mls[iM]).at().at(cls[iC]);
	    if(cntr.at().startStat() && (!isRun || !cntr.at().redntUse(TController::Any)))
		ls.push_back(cntr.at().workId());
	}
    }
}

void TDAQS::ctrListPrmAttr( XMLNode *opt, const string &ilPrm, bool toPrm, char sep, const string &pref, TCntrNode *ndBase )
{
    string cPath = "", cEl;
    vector<string> ls;
    string lPrm = ilPrm;
    if(pref.size() && lPrm.find(pref) == 0) lPrm = lPrm.substr(pref.size());

    if(sep || cPath.size()) opt->childAdd("el")->setText(pref+cPath);

    AutoHD<TCntrNode> DAQnd = this;
    const char *cGrp = "mod_";
    int cLv = 0;
    for(int cOff = 0; (sep && (cEl=TSYS::strSepParse(lPrm,0,sep,&cOff)).size()) ||
		      (!sep && (cEl=TSYS::pathLev(lPrm,0,true,&cOff)).size()); ++cLv)
    {
	if(cEl == ".") {
	    if(ndBase && cLv == 0 && lPrm[0] == '.') { DAQnd = ndBase; cPath += cEl; }
	    else cPath += "/"+cEl;
	}
	else if(cEl == "..") {
	    if(ndBase && cLv == 0 && lPrm.find("..") == 0) { DAQnd = ndBase; cPath += cEl; }
	    else cPath += "/"+cEl;
	    if(&DAQnd.at() != this) DAQnd = DAQnd.at().nodePrev();
	}
	else {
	    cPath += sep ? (cLv?sep+cEl:cEl) : "/"+cEl;
	    DAQnd = DAQnd.at().nodeAt(cGrp+cEl, 0, sep, 0, true);
	}

	if(DAQnd.freeStat()) break;
	opt->childAdd("el")->setText(pref+cPath);
	cGrp = dynamic_cast<TTypeDAQ*>(&DAQnd.at()) ? "cntr_" : "prm_";
    }
    if(sep && cLv) cPath += sep;
    if(!sep && ndBase) {
	if(cPath.size()) opt->childAdd("el")->setText(pref+cPath+"/..");
	else {
	    opt->childAdd("el")->setText(pref+".");
	    opt->childAdd("el")->setText(pref+"..");
	}
    }

    if(!DAQnd.freeStat()) {
	DAQnd.at().chldList(DAQnd.at().grpId(cGrp), ls, true);
	for(unsigned iL = 0; iL < ls.size(); iL++)
	    opt->childAdd("el")->setText(pref+cPath+(sep?ls[iL]:("/"+ls[iL])));

	//Get attributes
	if(!toPrm && strcmp(cGrp,"prm_") == 0) {
	    DAQnd.at().chldList(DAQnd.at().grpId("a_"), ls, true);
	    if(ls.size()) opt->childAdd("el")->setText(_("=== Attributes ==="));
	    for(unsigned iL = 0; iL < ls.size(); iL++)
		opt->childAdd("el")->setText(pref+cPath+(sep?ls[iL]:("/"+ls[iL])));
	}
    }
}

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

    map<string, bool>	itReg;

    //Loading the template libraries of the DAQ-parameter
    try {
	// Search and create new libraries
	TConfig cEl(&elLib());
	//cEl.cfgViewAll(false);
	vector<string> dbLs;

	// Search in DB
	TBDS::dbList(dbLs, TBDS::LsCheckSel|TBDS::LsInclGenFirst);
	for(unsigned iIt = 0; iIt < dbLs.size(); iIt++)
	    for(int libCnt = 0; TBDS::dataSeek(dbLs[iIt]+"."+tmplLibTable(),nodePath()+"tmplib",libCnt++,cEl,TBDS::UseCache); ) {
		string lId = cEl.cfg("ID").getS();
		if(!tmplLibPresent(lId)) tmplLibReg(new TPrmTmplLib(lId.c_str(),"",dbLs[iIt]));
		if(tmplLibAt(lId).at().DB() == dbLs[iIt]) tmplLibAt(lId).at().load(&cEl);
		tmplLibAt(lId).at().setDB(dbLs[iIt], true);
		itReg[lId] = true;
	    }

	//  Check for remove items removed from DB
	if(SYS->chkSelDB(SYS->selDB(),true)) {
	    tmplLibList(dbLs);
	    for(unsigned iIt = 0; iIt < dbLs.size(); iIt++)
		if(itReg.find(dbLs[iIt]) == itReg.end() && SYS->chkSelDB(tmplLibAt(dbLs[iIt]).at().DB()))
		    tmplLibUnreg(dbLs[iIt]);
        }
    } catch(TError &err) {
	mess_err(err.cat.c_str(), "%s", err.mess.c_str());
	mess_sys(TMess::Error, _("Error loading template libraries."));
    }

    //Loading the DAQ-controller objects
    try {
	AutoHD<TTypeDAQ> wmod;
	vector<string> modLs, dbLs;

	modList(modLs);
	for(unsigned iMd = 0; iMd < modLs.size(); iMd++) {
	    wmod = at(modLs[iMd]);
	    TConfig gCfg(&wmod.at());
	    //gCfg.cfgViewAll(false);
	    itReg.clear();

	    // Search in the storage and create new controller objects
	    TBDS::dbList(dbLs, TBDS::LsCheckSel|TBDS::LsInclGenFirst);
	    for(unsigned iIt = 0; iIt < dbLs.size(); iIt++)
		for(int fldCnt = 0; TBDS::dataSeek(dbLs[iIt]+"."+subId()+"_"+wmod.at().modId(),wmod.at().nodePath()+"DAQ",fldCnt++,gCfg,TBDS::UseCache); ) {
		    string mId = gCfg.cfg("ID").getS();
		    try {
			if(!wmod.at().present(mId)) wmod.at().add(mId, dbLs[iIt]);
			if(wmod.at().at(mId).at().DB() == dbLs[iIt]) wmod.at().at(mId).at().load(&gCfg);
			wmod.at().at(mId).at().setDB(dbLs[iIt], true);
			itReg[mId] = true;
		    } catch(TError &err) {
			mess_err(err.cat.c_str(), "%s", err.mess.c_str());
			mess_sys(TMess::Error, _("Error adding controller '%s'."), mId.c_str());
		    }
		}

	    //  Check for remove items removed from DB
	    wmod.at().list(dbLs);
	    for(unsigned iIt = 0; iIt < dbLs.size(); iIt++)
		if(itReg.find(dbLs[iIt]) == itReg.end() && SYS->chkSelDB(wmod.at().at(dbLs[iIt]).at().DB()))
		    wmod.at().del(dbLs[iIt]);
	}
    } catch(TError &err) { mess_err(err.cat.c_str(), "%s", err.mess.c_str()); }

    //Load parameters from the config-file and SYS DB
    setRdRestDtTm(s2r(TBDS::genPrmGet(nodePath()+"RdRestDtTm",r2s(rdRestDtTm()))));
}

void TDAQS::load__( )
{
    //Early starting for template libraries, after the whole loading
    vector<string> tmpl_lst;
    tmplLibList(tmpl_lst);
    for(unsigned iLb = 0; iLb < tmpl_lst.size(); iLb++)
	try { tmplLibAt(tmpl_lst[iLb]).at().start(true); }
	catch(TError &err) { }
}

void TDAQS::save_( )
{
    //Save parameters to SYS DB
    TBDS::genPrmSet(nodePath()+"RdRestDtTm", r2s(rdRestDtTm()));
}

TVariant TDAQS::objFuncCall( const string &iid, vector<TVariant> &prms, const string &user_lang )
{
    // TCntrNodeObj daqAt(string path, string sep = "", waitForAttr = true) - attaches to a DAQ node (controller object, parameter, attribute) in the ''path''
    //		or the separated string by the separator ''sep'', from the DAQ-subsystem. Check for an attribute in the path last element, at ''waitForAttr''.
    //  path - path to the DAQ-node
    //  sep - the symbol separator for separated string path
    //  waitForAttr - wait for an attribute or other
    if(iid == "daqAt" && prms.size() >= 1) {
	AutoHD<TCntrNode> nd = daqAt(prms[0].getS(), (prms.size()>=2 && prms[1].getS().size())?prms[1].getS()[0]:0, true, (prms.size()>=3)?prms[2].getB():true);
	return nd.freeStat() ? TVariant(false) : TVariant(new TCntrNodeObj(nd,user_lang));
    }
    // bool funcCall(string progLang, TVarObj args, string prog, string fixId = "", string err = "") -
    //    Call function text <prog> whith arguments <args> for program language <progLang>
    //    and with the fixed identifier <fixId> (automatic for this empty). Return "true" on a well call.
    //    For the fixed function recreate you need change the program or clean up <fixId> by the function original id.
    //  progLang - program procedure language or the stored function's address, after call with <store>;
    //  args - function arguments;
    //  prog - function text;
    //  fixId - two direction field of fixed identifier of the function;
    //          for the field empty the function id will be automatic and destroy at end,
    //          else the id will used on the function creation and replaced by an address to it.
    //  err   - to place here all errors
    if(iid == "funcCall" && prms.size() >= 3 && prms[1].type() == TVariant::Object) {
	string fixId = (prms.size() >= 4) ? prms[3].getS() : "";
	string faddr;
	AutoHD<TFunction> wFnc; if(fixId.size()) wFnc = SYS->nodeAt(fixId, 0, 0, 0, true);
	AutoHD<TVarObj> args = prms[1].getO();
	vector<string> als; args.at().propList(als);
	TVariant aVal;

	try {
	    //New call environment preparing or modifying presented
	    if(wFnc.freeStat() || prms[2].getS() != wFnc.at().prog()) {
		// Update present function
		if(!wFnc.freeStat()) { wFnc.at().setStart(false); wFnc.at().setProg(prms[2].getS()); wFnc.at().setStart(true); }
		else {
		    string langMod = TSYS::strParse(prms[0].getS(), 0, ".");
		    if(!modPresent(langMod)) return false;

		    //Prepare arguments structure
		    TFunction argStr(fixId.size()?"uf_"+TSYS::strEncode(fixId,TSYS::oscdID):"<auto>");
		    for(unsigned iA = 0; iA < als.size(); iA++) {
			aVal = args.at().propGet(als[iA]);
			IO::Type tp = IO::String;
			switch(aVal.type()) {
			    case TVariant::Boolean:	tp = IO::Boolean;	break;
			    case TVariant::Integer:	tp = IO::Integer;	break;
			    case TVariant::Real:	tp = IO::Real;		break;
			    case TVariant::String:	tp = IO::String;	break;
			    case TVariant::Object:	tp = IO::Object;	break;
			    default:	break;
			}
			argStr.ioAdd(new IO(als[iA].c_str(),als[iA].c_str(),tp,IO::Default));
		    }

		    //Get function id and compile.
		    faddr = at(langMod).at().compileFunc(TSYS::strParse(prms[0].getS(),1,"."), argStr, prms[2].getS());
		    wFnc = SYS->nodeAt(faddr);
		}
	    }
	    //Prepare and execute
	    TValFunc wCtx("UserFunc", &wFnc.at(), true, TSYS::strLine(user_lang,0));

	    // Load values
	    for(unsigned iA = 0; iA < als.size(); iA++)
		switch((aVal=args.at().propGet(als[iA])).type()) {
		    case TVariant::Boolean:	wCtx.setB(iA, aVal.getB());	break;
		    case TVariant::Integer:	wCtx.setI(iA, aVal.getI());	break;
		    case TVariant::Real:	wCtx.setR(iA, aVal.getR());	break;
		    case TVariant::String:	wCtx.setS(iA, aVal.getS());	break;
		    case TVariant::Object:	wCtx.setO(iA, aVal.getO());	break;
		    default:	break;
		}

	    // Call function
	    wCtx.calc();

	    // Place the call results and remove function object.
	    for(int iA = 0; iA < wCtx.ioSize(); iA++)
		switch(wCtx.ioType(iA)) {
		    case IO::Boolean:	args.at().propSet(als[iA], wCtx.getB(iA));	break;
		    case IO::Integer:	args.at().propSet(als[iA], wCtx.getI(iA));	break;
		    case IO::Real:	args.at().propSet(als[iA], wCtx.getR(iA));	break;
		    case IO::String:	args.at().propSet(als[iA], wCtx.getS(iA));	break;
		    case IO::Object:	args.at().propSet(als[iA], wCtx.getO(iA));	break;
		    default:	break;
		}

	    // Remove compiled function object
	    wCtx.setFunc(NULL);
		wFnc.free();
		if(faddr.size()) {
		    if(fixId.size()) { prms[3].setS(faddr); prms[3].setModify(); }
		    else SYS->nodeDel(faddr);
		}

	    return true;
	}
	catch(TError &err) {
	    if(prms.size() >= 5) { prms[4].setS(err.mess); prms[4].setModify(); }
	    mess_err(err.cat.c_str(), "%s", err.mess.c_str());
	}

	return false;
    }
    // string funcSnthHgl(string progLang) - Requesting the program language <progLang> syntax highlight rules.
    //  progLang - program procedure language;
    if(iid == "funcSnthHgl" && prms.size() >= 1) {
	string langMod = TSYS::strParse(prms[0].getS(), 0, ".");
	if(!modPresent(langMod)) return "";

	XMLNode hgl_req("SnthHgl");
	at(langMod).at().compileFuncSnthHgl(TSYS::strParse(prms[0].getS(),1,"."), hgl_req);

	return hgl_req.childSize() ? hgl_req.save() : string("");
    }

    return TCntrNode::objFuncCall(iid, prms, user_lang);
}

void TDAQS::unload( )
{
    TSubSYS::unload();

    mRdRes.lock(true);
    mRdRestDtTm = 1;
    mRdCntr.clear();
    mRdRes.unlock();
}

void TDAQS::subStart( )
{
    vector<string> mL, tmpl_lst;

    bool reply   = false;
    int  try_cnt = 0;

    do {
	//Start template's libraries
	tmplLibList(tmpl_lst);
	for(unsigned iLb = 0; iLb < tmpl_lst.size(); iLb++)
	    try { tmplLibAt(tmpl_lst[iLb]).at().start(true); }
	    catch(TError &err) {
		if(try_cnt) {
		    mess_err(err.cat.c_str(), "%s", err.mess.c_str());
		    mess_sys(TMess::Error, _("Error starting the templates library '%s'."), tmpl_lst[iLb].c_str());
		}
		reply = true;
	    }

	//Enable controllers
	modList(mL);
	for(unsigned iM = 0; iM < mL.size(); iM++) {
	    vector<string> cL;
	    at(mL[iM]).at().list(cL);
	    for(unsigned iC = 0; iC < cL.size(); iC++) {
		AutoHD<TController> cntr = at(mL[iM]).at().at(cL[iC]);
		if(/*!cntr.at().enableStat() &&*/ cntr.at().toEnable())
		    try{ cntr.at().enable(); }
		    catch(TError &err) {
			if(try_cnt) {
			    mess_err(err.cat.c_str(), "%s", err.mess.c_str());
			    mess_sys(TMess::Error, _("Error enabling the controller object '%s'."), (mL[iM]+"."+cL[iC]).c_str());
			}
			reply = true;
		    }
	    }
	}
	try_cnt++;
    } while(reply && try_cnt < 2);

    //Archive subsystem start
    if(!SYS->archive().at().subStartStat() || !SYS->stopSignal()) SYS->archive().at().subStart();

    //Controllers start
    TSubSYS::subStart();
}

void TDAQS::subStop( )
{
    vector<string> mL;

    //Stop
    modList(mL);
    for(unsigned iM = 0; iM < mL.size(); iM++) {
	vector<string> cL;
	at(mL[iM]).at().list(cL);
	for(unsigned iC = 0; iC < cL.size(); iC++) {
	    AutoHD<TController> cntr = at(mL[iM]).at().at(cL[iC]);
	    if(cntr.at().startStat())
		try{ cntr.at().stop(); }
		catch(TError &err) {
		    mess_err(err.cat.c_str(), "%s", err.mess.c_str());
		    mess_sys(TMess::Error, _("Error stopping the controller object '%s'."), (mL[iM]+"."+cL[iC]).c_str());
		}
	}
    }
    //Disable
    for(unsigned iM = 0; iM < mL.size(); iM++) {
	vector<string> cL;
	at(mL[iM]).at().list(cL);
	for(unsigned iC = 0; iC < cL.size(); iC++) {
	    AutoHD<TController> cntr = at(mL[iM]).at().at(cL[iC]);
	    if(cntr.at().enableStat())
		try{ cntr.at().disable(); }
		catch(TError &err) {
		    mess_err(err.cat.c_str(), "%s", err.mess.c_str());
		    mess_sys(TMess::Error, _("Error disabling the controller object '%s'."), (mL[iM]+"."+cL[iC]).c_str());
		}
	}
    }

    //Stop template's libraries
    tmplLibList(mL);
    for(unsigned iLb = 0; iLb < mL.size(); iLb++)
	tmplLibAt(mL[iLb]).at().start(false);

    TSubSYS::subStop();
}

AutoHD<TCntrNode> TDAQS::daqAt( const string &path, char sep, bool noex, bool waitForAttr, TCntrNode *ndBase ) const
{
    string cEl;
    AutoHD<TCntrNode> DAQnd = const_cast<TDAQS*>(this);
    const char *cGrp = "mod_";
    for(int cOff = 0, cLv = 0; (sep && (cEl=TSYS::strSepParse(path,0,sep,&cOff)).size()) ||
		   (!sep && (cEl=TSYS::pathLev(path,0,true,&cOff)).size()); ++cLv)
    {
	bool lastEl = (/*cLv > 2 &&*/ cOff >= (int)path.size());
	if(waitForAttr && lastEl) cGrp = "a_";

	AutoHD<TCntrNode> tNd;
	if(cEl == ".") {
	    if(ndBase && cLv == 0 && path[0] == '.') tNd = ndBase;
	    else tNd = DAQnd;
	}
	else if(cEl == "..") {
	    if(ndBase && cLv == 0 && path.starts_with("..")) DAQnd = ndBase;
	    if(&DAQnd.at() != this) tNd = DAQnd.at().nodePrev();
	    else tNd = DAQnd;
	}
	else tNd = DAQnd.at().nodeAt(cGrp+cEl, 0, sep, 0, true);

	if(tNd.freeStat() && !(strcmp(cGrp,"a_") != 0 && lastEl && !(tNd=DAQnd.at().nodeAt("a_"+cEl,0,sep,0,true)).freeStat())) {
	    if(noex) return AutoHD<TValue>();
	    else throw err_sys(_("Missing the DAQ node '%s'."), path.c_str());
	}
	cGrp = dynamic_cast<TTypeDAQ*>(&tNd.at()) ? "cntr_" : "prm_";
	DAQnd = tNd;
    }

    return DAQnd;
}

AutoHD<TValue> TDAQS::prmAt( const string &path, char sep, bool noex, TCntrNode *ndBase ) const
{
    AutoHD<TCntrNode> DAQnd = daqAt(path, sep, noex, false, ndBase);
    if(DAQnd.freeStat() || !dynamic_cast<TValue*>(&DAQnd.at())) {
	if(noex) return AutoHD<TValue>();
	else throw err_sys(_("The specified node is not the parameter '%s'."), path.c_str());
    }

    return DAQnd;
}

AutoHD<TVal> TDAQS::attrAt( const string &path, char sep, bool noex, TCntrNode *ndBase ) const
{
    AutoHD<TCntrNode> DAQnd = daqAt(path, sep, noex, true, ndBase);
    if(DAQnd.freeStat() || !dynamic_cast<TVal*>(&DAQnd.at())) {
	if(noex) return AutoHD<TVal>();
	else throw err_sys(_("The specified node is not the attribute '%s'."), path.c_str());
    }

    return DAQnd;
}

bool TDAQS::rdProcess( XMLNode *reqSt )
{
    if(reqSt) {
	string StId = reqSt->attr("StId");
	ResAlloc res(mRdRes, true);
	mRdCntr[StId].clear();
	for(unsigned iC = 0; iC < reqSt->childSize(); iC++)
	    if(reqSt->childGet(iC)->name() == "cntr")
		mRdCntr[StId][reqSt->childGet(iC)->attr("id")] = s2i(reqSt->childGet(iC)->attr("run"));
	return true;
    }

    //Planing controllers' run and process requests to remote run controllers
    map<string,TSYS::SStat> sts = SYS->rdSts();
    map<string,TSYS::SStat>::iterator sit;
    map<string,bool>::iterator cit;

    vector<string> cls;
    rdActCntrList(cls);
    for(unsigned iC = 0; iC < cls.size(); iC++) {
	AutoHD<TController> cntr = at(TSYS::strParse(cls[iC],0,".")).at().at(TSYS::strParse(cls[iC],1,"."));

	// Process remote run controllers, before the redundancy status change
	// !!!! Moved here from the end
	if(cntr.at().startStat() && cntr.at().redntUse()) cntr.at().redntDataUpdate();

	// Check contoller run plane
	if(cntr.at().redntMode() == TController::Off) cntr.at().setRedntUse(false);
	else {
	    ResAlloc res(mRdRes, false);
	    if(cntr.at().redntRun() == "<high>") {
		int wLev = SYS->rdStLevel();
		for(sit = sts.begin(); sit != sts.end(); ++sit)
		    if(sit->second.isLive && (cit=mRdCntr[sit->first].find(cntr.at().workId())) != mRdCntr[sit->first].end() && cit->second)
			wLev = vmax(wLev, sit->second.lev);
		cntr.at().setRedntUse(SYS->rdStLevel() < wLev);
	    }
	    else if(cntr.at().redntRun() == "<low>") {
		int wLev = SYS->rdStLevel();
		for(sit = sts.begin(); sit != sts.end(); sit++)
		    if(sit->second.isLive && (cit=mRdCntr[sit->first].find(cntr.at().workId())) != mRdCntr[sit->first].end() && cit->second)
			wLev = vmin(wLev, sit->second.lev);
		cntr.at().setRedntUse(SYS->rdStLevel() > wLev);
	    }
	    else if(cntr.at().redntRun() == "<optimal>") {
		vector<string> cls_lc;
		rdActCntrList(cls_lc, true);
		bool remPresent = false;
		for(sit = sts.begin(); sit != sts.end(); sit++)
		    if(sit->second.isLive && (cit=mRdCntr[sit->first].find(cntr.at().workId())) != mRdCntr[sit->first].end()) {
			if(!remPresent) remPresent = cit->second;
			int aCntr = 0;
			for(map<string,bool>::iterator scit = mRdCntr[sit->first].begin(); scit != mRdCntr[sit->first].end(); scit++)
			    if(scit->second) aCntr++;
			if(((int)cls_lc.size()-aCntr) >= 0 && cit->second) break;
		    }
		cntr.at().setRedntUse(sit != sts.end());
	    }
	    else {
		for(sit = sts.begin(); sit != sts.end(); sit++)
		    if(sit->second.isLive && (cit=mRdCntr[sit->first].find(cntr.at().workId())) != mRdCntr[sit->first].end() &&
			cit->second && cntr.at().redntRun() == sit->first)
			    break;
		cntr.at().setRedntUse(sit != sts.end());
	    }
	    res.release();
	}
	cntr.free();
    }

    return true;
}

string TDAQS::rdStRequest( const string &cntr, XMLNode &req, const string &prevSt, bool toRun )
{
    string lcPath = req.attr("path");
    bool prevPresent = false;
    string rez;
    map<string, TSYS::SStat>::iterator sit;
    map<string, bool>::iterator cit;

    ResAlloc res(mRdRes, false);
    map<string, map<string,bool> > sts = mRdCntr;
    res.unlock();

    for(map<string, map<string,bool> >::iterator sit = sts.begin(); sit != sts.end(); ++sit)
	if((cit=sit->second.find(cntr)) != sit->second.end() && (!toRun || cit->second)) {
	    if(prevSt.size() && !prevPresent) {
		if(sit->first == prevSt) prevPresent = true;
		continue;
	    }
	    if((rez=SYS->rdStRequest(req,sit->first)).size()) return rez;

	    res.lock(true);
	    mRdCntr.erase(sit->first);
	    res.unlock();
	}

    if(prevSt.empty()) at(TSYS::strParse(cntr,0,".")).at().at(TSYS::strParse(cntr,1,".")).at().setRedntUse(false);

    return "";
}

string TDAQS::optDescr( )
{
    return TSYS::strMess(_(
	"=================== Subsystem \"Data acquisition\" options ================\n"
	"------ Parameters of the section '%s' of the configuration file ------\n"
	"RdRestDtTm <hour>       Depth of recovery time of archive data from the reserve station, when startup, in hours.\n\n"
	),nodePath().c_str()) + TSubSYS::optDescr();
}

void TDAQS::cntrCmdProc( XMLNode *opt )
{
    string a_path = opt->attr("path");
    //Service commands process
    // Redundant service requests
    if(a_path == "/serv/redundant") {
	if(ctrChkNode(opt,"st",RWRWR_,"root",SDAQ_ID,SEC_RD)) {	//State
	    opt->setAttr("inProc", "1");
	    opt->setAttr("StLevel", i2s(SYS->rdStLevel()));
	    map<string, bool> cntrLs;
	    vector<string> cls;
	    rdActCntrList(cls);
	    for(unsigned iL = 0; iL < cls.size(); iL++) cntrLs[cls[iL]] = false;
	    rdActCntrList(cls,true);
	    for(unsigned iL = 0; iL < cls.size(); iL++) cntrLs[cls[iL]] = true;
	    for(map<string,bool>::iterator cit = cntrLs.begin(); cit != cntrLs.end(); cit++)
		opt->childAdd("cntr")->setAttr("id",cit->first)->setAttr("run",cit->second?"1":"0");
	    return;
	}
    }
    // Parameter and/or attributes list-browse
    else if(a_path == "/serv/PrmAttr" && ctrChkNode(opt,"list",RWRWR_,"root",SDAQ_ID,SEC_RD)) {
	ctrListPrmAttr(opt, opt->attr("base"), s2i(opt->attr("toPrm")), opt->attr("sep")[0], opt->attr("pref"));
	return;
    }

    //Get page info
    if(opt->name() == "info") {
	TSubSYS::cntrCmdProc(opt);
	ctrMkNode("oscada_cntr",opt,-1,"/",EVAL_STR,R_R_R_,"root","root",1,"doc",(subId()+"|Documents/"+subId()).c_str());
	ctrMkNode("grp",opt,-1,"/br/tmplb_",_("Templates library"),RWRWR_,"root",SDAQ_ID,2,"idm",i2s(limObjNm_SZ).c_str(),"idSz",i2s(limObjID_SZ).c_str());
	if(SYS->rdEnable() && ctrMkNode("area",opt,0,"/redund",_("Redundancy"))) {
	    ctrMkNode("fld",opt,-1,"/redund/restDtTm",_("Depth time of restoring data at the starting, hours"),RWRWR_,"root",SDAQ_ID,1, "tp","real");
	    if(ctrMkNode("table",opt,-1,"/redund/cntr",_("Controllers"),RWRWR_,"root",SDAQ_ID,1,"key","id")) {
		ctrMkNode("list",opt,-1,"/redund/cntr/id",_("Controller"),R_R_R_,"root",SDAQ_ID,1,"tp","str");
		ctrMkNode("list",opt,-1,"/redund/cntr/nm",_("Name"),R_R_R_,"root",SDAQ_ID,1,"tp","str");
		ctrMkNode("list",opt,-1,"/redund/cntr/start",_("Run."),RWRWR_,"root",SDAQ_ID,1,"tp","bool");
		ctrMkNode("list",opt,-1,"/redund/cntr/rdndt",_("Redund."),RWRWR_,"root",SDAQ_ID,4, "tp","dec", "dest","select",
		    "sel_id",(i2s(TController::Off)+";"+i2s(TController::Asymmetric)+";"+i2s(TController::OnlyAlarms)).c_str(),
		    "sel_list",_("Off;Asymmetric;Only alarms"));
		ctrMkNode("list",opt,-1,"/redund/cntr/prefRun",_("Pref. to run"),RWRWR_,"root",SDAQ_ID,4,"tp","str",
		    "idm","1","dest","select","select","/redund/lsMode");
		ctrMkNode("list",opt,-1,"/redund/cntr/remoted",_("Remote"),R_R_R_,"root",SDAQ_ID,1,"tp","bool");
	    }
	}
	if(ctrMkNode("area",opt,1,"/tpllibs",_("Template libraries")))
	    ctrMkNode("list",opt,-1,"/tpllibs/lb",_("Template libraries"),RWRWR_,"root",SDAQ_ID,5,
		"tp","br","idm",i2s(limObjNm_SZ).c_str(),"s_com","add,del","br_pref","tmplb_","idSz",i2s(limObjID_SZ).c_str());
	return;
    }

    //Process command to page
    if(a_path == "/br/tmplb_" || a_path == "/tpllibs/lb") {
	if(ctrChkNode(opt,"get",RWRWR_,"root",SDAQ_ID,SEC_RD)) {
	    vector<string> lst;
	    tmplLibList(lst);
	    for(unsigned iA = 0; iA < lst.size(); iA++)
		opt->childAdd("el")->setAttr("id",lst[iA])->setText(trD(tmplLibAt(lst[iA]).at().name()));
	}
	if(ctrChkNode(opt,"add",RWRWR_,"root",SDAQ_ID,SEC_WR))
	    tmplLibReg(new TPrmTmplLib(TSYS::strEncode(opt->attr("id"),TSYS::oscdID).c_str(),opt->text().c_str(),DB_GEN));
	if(ctrChkNode(opt,"del",RWRWR_,"root",SDAQ_ID,SEC_WR))
	    tmplLibUnreg(opt->attr("id"),1);
    }
    else if(a_path == "/redund/restDtTm") {
	if(ctrChkNode(opt,"get",RWRWR_,"root",SDAQ_ID,SEC_RD))	opt->setText(r2s(rdRestDtTm()));
	if(ctrChkNode(opt,"set",RWRWR_,"root",SDAQ_ID,SEC_WR))	setRdRestDtTm(s2r(opt->text()));
    }
    else if(a_path == "/redund/cntr") {
	if(ctrChkNode(opt,"get",RWRWR_,"root",SDAQ_ID,SEC_RD)) {
	    XMLNode *nId	= ctrMkNode("list",opt,-1,"/redund/cntr/id","",R_R_R_,"root",SDAQ_ID);
	    XMLNode *nNm	= ctrMkNode("list",opt,-1,"/redund/cntr/nm","",R_R_R_,"root",SDAQ_ID);
	    XMLNode *nStart	= ctrMkNode("list",opt,-1,"/redund/cntr/start","",RWRWR_,"root",SDAQ_ID);
	    XMLNode *nRdndt	= ctrMkNode("list",opt,-1,"/redund/cntr/rdndt","",RWRWR_,"root",SDAQ_ID);
	    XMLNode *nPrefRun	= ctrMkNode("list",opt,-1,"/redund/cntr/prefRun","",RWRWR_,"root",SDAQ_ID);
	    XMLNode *nRem	= ctrMkNode("list",opt,-1,"/redund/cntr/remoted","",R_R_R_,"root",SDAQ_ID);

	    vector<string> mls, cls;
	    modList(mls);
	    AutoHD<TController> cntr;
	    for(unsigned iM = 0; iM < mls.size(); iM++) {
		if(!at(mls[iM]).at().redntAllow()) continue;
		at(mls[iM]).at().list(cls);
		for(unsigned iC = 0; iC < cls.size(); iC++) {
		    cntr = at(mls[iM]).at().at(cls[iC]);
		    if(nId)		nId->childAdd("el")->setText(mls[iM]+"."+cls[iC]);
		    if(nNm)		nNm->childAdd("el")->setText(trD(cntr.at().name()));
		    if(nStart)		nStart->childAdd("el")->setText(cntr.at().startStat()?"1":"0");
		    if(nRdndt)		nRdndt->childAdd("el")->setText(i2s(cntr.at().redntMode()));
		    if(nPrefRun)	nPrefRun->childAdd("el")->setText(cntr.at().redntRun());
		    if(nRem)		nRem->childAdd("el")->setText(cntr.at().redntUse(TController::Any)?"1":"0");
		}
	    }
	}
	if(ctrChkNode(opt,"set",RWRWR_,"root",SDAQ_ID,SEC_WR)) {
	    string col = opt->attr("col");
	    AutoHD<TController> cntr  = at(TSYS::strSepParse(opt->attr("key_id"),0,'.')).at().
					at(TSYS::strSepParse(opt->attr("key_id"),1,'.'));
	    if(col == "start")		s2i(opt->text()) ? cntr.at().start() : cntr.at().stop();
	    else if(col == "rdndt")	cntr.at().setRedntMode((TController::Redundant)(s2i(opt->text())));
	    else if(col == "prefRun")	cntr.at().setRedntRun(opt->text());
	}
    }
    else if(a_path == "/redund/lsMode" && ctrChkNode(opt)) {
	opt->childAdd("el")->setAttr("id","<high>")->setText(_("<High level>"));
	opt->childAdd("el")->setAttr("id","<low>")->setText(_("<Low level>"));
	opt->childAdd("el")->setAttr("id","<optimal>")->setText(_("<Optimal>"));
	vector<string> sls;
	SYS->rdStList(sls);
	for(unsigned iS = 0; iS < sls.size(); iS++)
	    opt->childAdd("el")->setAttr("id",sls[iS])->setText(SYS->transport().at().extHostGet("*",sls[iS]).name);
    }
    else TSubSYS::cntrCmdProc(opt);
}
