
//OpenSCADA module DAQ.ModBus file: modbus_daq.cpp
/***************************************************************************
 *   Copyright (C) 2007-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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <stdint.h>

#include <ttypeparam.h>

#include "modbus_daq.h"

ModBus::TTpContr *ModBus::mod;

using namespace ModBus;

//******************************************************
//* TTpContr                                           *
//******************************************************
TTpContr::TTpContr( string name ) : TTypeDAQ(DAQ_ID), tPrmId(-1), tPrmLId(-1)
{
    mod = this;

    modInfoMainSet(DAQ_NAME, DAQ_TYPE, DAQ_MVER, DAQ_AUTHORS, DAQ_DESCR, DAQ_LICENSE, name);
}

TTpContr::~TTpContr( )
{

}

void TTpContr::postEnable( int flag )
{
    TTypeDAQ::postEnable(flag);

    //Controler's bd structure
    fldAdd(new TFld("PRM_BD",trS("Parameters table"),TFld::String,TFld::NoFlag,"30",""));		//????[v1.0] Remove
    fldAdd(new TFld("PRM_BD_L",trS("Logical parameters table"),TFld::String,TFld::NoFlag,"30",""));	//????[v1.0] Remove
    fldAdd(new TFld("SCHEDULE",trS("Acquisition schedule"),TFld::String,TFld::NoFlag,"100","1"));
    fldAdd(new TFld("PRIOR",trS("Priority of the acquisition task"),TFld::Integer,TFld::NoFlag,"2","0","-1;199"));
    fldAdd(new TFld("PROT",trS("ModBus protocol"),TFld::String,TFld::Selectable,"5","TCP","TCP;RTU;ASCII"));
    fldAdd(new TFld("ADDR",trS("Transport address"),TFld::String,TFld::NoFlag,"41",""));
    fldAdd(new TFld("NODE",trS("Destination node"),TFld::Integer,TFld::NoFlag,"20","1","0;255"));
    fldAdd(new TFld("FRAG_MERGE",trS("Merging of the data fragments"),TFld::Boolean,TFld::NoFlag,"1","0"));
    fldAdd(new TFld("WR_MULTI",trS("Using the multi-items writing functions (15,16)"),TFld::Boolean,TFld::NoFlag,"1","0"));
    fldAdd(new TFld("WR_ASYNCH",trS("Asynchronous write"),TFld::Boolean,TFld::NoFlag,"1","0"));
    fldAdd(new TFld("WR_OMIT_CYCL_RD",trS("Omit cycles for read back of written"),TFld::Integer,TFld::NoFlag,"2","0","0;9"));
    fldAdd(new TFld("TM_REQ",trS("Timeout of connection, milliseconds"),TFld::Integer,TFld::NoFlag,"5","0","0;10000"));
    fldAdd(new TFld("TM_REST",trS("Timeout of restore, seconds"),TFld::Integer,TFld::NoFlag,"4","30","1;3600"));
    fldAdd(new TFld("REQ_TRY",trS("Request tries"),TFld::Integer,TFld::NoFlag,"1","1","1;9"));
    fldAdd(new TFld("MAX_BLKSZ",trS("Maximum size of the request block, bytes"),TFld::Integer,TFld::NoFlag,"3","200","2;250"));

    //Parameter type bd structure
    // Standard parameter type by the simple attributes list
    tPrmId = tpParmAdd("Prm", "PRM_BD", _("Standard"), true);
    tpPrmAt(tPrmId).fldAdd(new TFld("ATTR_LS",trS("Attributes list"),TFld::String,TFld::FullText|/*TFld::TransltText|*/TCfg::NoVal,"100000",""));
    // Extended logical parameter type by the DAQ parameter template
    tPrmLId = tpParmAdd("PrmL", "PRM_BD_L", _("Logical"));
    tpPrmAt(tPrmLId).fldAdd(new TFld("TMPL",trS("Parameter template"),TFld::String,TCfg::NoVal,"50",""));
    //  Parameter template IO DB structure
    elPrmIO.fldAdd(new TFld("PRM_ID",trS("Parameter ID"),TFld::String,TCfg::Key,i2s(limObjID_SZ*6).c_str()));
    elPrmIO.fldAdd(new TFld("ID",trS("Identifier"),TFld::String,TCfg::Key,i2s(limObjID_SZ*1.5).c_str()));
    elPrmIO.fldAdd(new TFld("VALUE",trS("Value"),TFld::String,TFld::TransltText,"1000000"));
}

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), enRes(true),
	mPrior(cfg("PRIOR").getId()), mNode(cfg("NODE").getId()), blkMaxSz(cfg("MAX_BLKSZ").getId()),
	mSched(cfg("SCHEDULE")), mPrt(cfg("PROT")), mAddr(cfg("ADDR")),
	mMerge(cfg("FRAG_MERGE").getBd()), mMltWr(cfg("WR_MULTI").getBd()), mAsynchWr(cfg("WR_ASYNCH").getBd()),
	mOmitRdCyclForWr(cfg("WR_OMIT_CYCL_RD").getId()), reqTm(cfg("TM_REQ").getId()), restTm(cfg("TM_REST").getId()),
	connTry(cfg("REQ_TRY").getId()),
	mPer(1e9), prcSt(false), callSt(false), endrunReq(false), alSt(-1), tmDelay(0),
	numRReg(0), numRRegIn(0), numRCoil(0), numRCoilIn(0), numWReg(0), numWCoil(0), numErrCon(0), numErrResp(0), numErrPrtPrm(0)
{
    //????[v1.0] Remove
    cfg("PRM_BD").setS("ModBusPrm_"+id());
    cfg("PRM_BD_L").setS("ModBusPrmL_"+id());

    mPrt = "TCP";
}

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

void TMdContr::postDisable( int flag )
{
    try {
	if(flag&(NodeRemove|NodeRemoveOnlyStor))
	    TBDS::dataDelTbl(DB(flag&NodeRemoveOnlyStor)+"."+tbl(owner().tpPrmAt(mod->tPrmLId))+"_io",
				owner().nodePath()+tbl(owner().tpPrmAt(mod->tPrmLId))+"_io");
    } catch(TError &err) { mess_err(err.cat.c_str(),"%s",err.mess.c_str()); }

    TController::postDisable(flag);
}

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

    if(startStat() && !redntUse()) {
	if(!prcSt) val += TSYS::strMess(_("Task terminated! "));
	if(tmDelay > -1) {
	    int errCode = TError::Tr_Connect;
	    if(addr().empty())	{ val += TSYS::strMess(_("No transport. ")); errCode = TError::Tr_ErrTransport; }
	    else try {
		    AutoHD<TTransportOut> tr = SYS->transport().at().outAt(addr());
			//SYS->transport().at().at(TSYS::strParse(addr(),0,".")).at().outAt(TSYS::strParse(addr(),1,"."));
		    if(tr.at().addr().empty()) { val += TSYS::strMess(_("Empty transport. ")); errCode = TError::Tr_ErrTransport; }
		} catch(TError&) { val += TSYS::strMess(_("Error the transport. ")); errCode = TError::Tr_ErrTransport; }
	    if(errCode == TError::Tr_Connect)	val += TSYS::strMess(_("Error the connection. "));
	    val += " " + TSYS::strMess(_("Restoring in %.6g s. "), tmDelay);
	    val.replace(0, 1, i2s(errCode));
	}
	else {
	    if(callSt)	val += TSYS::strMess(_("Acquisition. "));
	    if(period())val += TSYS::strMess(_("Acquisition with the period %s. "), tm2s(1e-9*period()).c_str());
	    else val += TSYS::strMess(_("Next acquisition by the cron '%s'. "), atm2s(TSYS::cron(cron()),"%d-%m-%Y %R").c_str());
	    val += TSYS::strMess(_("Spent time %s[%s]. "),
			tm2s(SYS->taskUtilizTm(nodePath('.',true))).c_str(),
			tm2s(SYS->taskUtilizTm(nodePath('.',true),true)).c_str());
	    val += TSYS::strMess(_("Read %g(%g) registers, %g(%g) coils. "),
				    (double)numRReg, (double)numRRegIn, (double)numRCoil, (double)numRCoilIn);
	    val += TSYS::strMess(_("Wrote %g registers, %g coils. "), (double)numWReg, (double)numWCoil);
	    val += TSYS::strMess(_("Errors of connection %g, of response %g, of protocol parameters %g. "),
				(double)numErrCon, (double)numErrResp, (double)numErrPrtPrm);
	}
	if(mAsynchWr || asynchWrs.size()) val += TSYS::strMess(_("To write %d. "), asynchWrs.size());
    }

    return val;
}

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

void TMdContr::disable_( )
{
    // Asynchronous writings buffer clean up
    aWrRes.lock(); asynchWrs.clear(); aWrRes.unlock();

    //Clear acquisition data block
    reqRes.resRequestW(true);
    acqBlks.clear();
    acqBlksIn.clear();
    acqBlksCoil.clear();
    acqBlksCoilIn.clear();
    reqRes.resRelease();

    //Clear process parameters list
    enRes.lock(); pHd.clear(); enRes.unlock();
}

void TMdContr::load_( )
{
    if(SYS->cfgCtx() && toEnable() && !enableStat())	enable();
}

void TMdContr::start_( )
{
    if(prcSt) return;

    //Clear statistic
    numRReg = numRRegIn = numRCoil = numRCoilIn = numWReg = numWCoil = numErrCon = numErrResp = numErrPrtPrm = 0;
    tmDelay = 0;

    //Start the gathering data task
    SYS->taskCreate(nodePath('.',true), mPrior, TMdContr::Task, this);
}

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

    alarmSet(TSYS::strMess(_("Connection to the data source: %s."),_("STOP")), TMess::Info);
    alSt = -1;
}

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

    if(co.fld().name() == "SCHEDULE") {
	bool isCRON = TSYS::strParse(cron(),1," ").size();

	//Stopping to reset big time intervals or CRON
	if((!isCRON && 1e-9*period() > 60 && s2r(cron()) < 1e-9*period()) ||	//Setting lesser time to big
		(isCRON && 1e-9*period() > 60) ||	//Switching to CRON from big period
		!period())				//Reseting CRON
	    try { stop(); } catch(TError&) { }

	mPer = isCRON ? 0 : vmax(0,1e9*s2r(cron()));
    }
    else if(co.fld().name() == "PROT" && co.getS() != pc.getS()) {
	cfg("REQ_TRY").setView(co.getS()!="TCP");
	//if(startStat()) stop();
    }
    else if(co.fld().name() == "FRAG_MERGE" && enableStat() && co.getB() != pc.getB())
	disable();

    return true;
}

void TMdContr::prmEn( TMdPrm *prm, bool val )
{
    unsigned iPrm;

    MtxAlloc res(enRes, true);
    for(iPrm = 0; iPrm < pHd.size(); iPrm++)
	if(&pHd[iPrm].at() == prm) break;

    if(val && iPrm >= pHd.size())	pHd.push_back(prm);
    if(!val && iPrm < pHd.size())	pHd.erase(pHd.begin()+iPrm);
}

string TMdContr::regVal( const string &addr )
{
    int off = 0;
    string atp = TSYS::strParse(addr, 0, ":", &off);
    if(atp.empty()) return "";
    string atp_m = TSYS::strParse(atp, 0, "_");
    string atp_sub = TSYS::strParse(atp, 1, "_");
    string ai = TSYS::strParse(addr, 0, ":", &off);
    if(ai.empty()) return "";
    int reg = strtol(ai.c_str(), NULL, 0);
    string aflg = TSYS::strParse(addr, 0, ":", &off);
    bool justWr = (aflg.find("w") != string::npos && aflg.find("r") == string::npos);
    if(!justWr) regVal(reg, atp_m, (aflg.find("s") != string::npos));
    if(atp[0] == 'R') {
	if(atp_sub == "b") {
	    atp += TSYS::strParse(ai, 1, ".");
	    ai = TSYS::strParse(ai, 0, ".");
	}
	else if(atp_sub == "i4" || atp_sub == "u4" || atp_sub == "f") {
	    int reg2 = TSYS::strParse(ai,1,",").empty() ? (reg+1) : strtol(TSYS::strParse(ai,1,",").c_str(), NULL, 0);
	    if(!justWr) regVal(reg2, atp_m);
	    ai = (aflg.find("~") == string::npos)
			? TSYS::strMess("%d,%d", reg, reg2)
			: TSYS::strMess("%d,%d", reg2, reg);
	}
	else if(atp_sub == "i8" || atp_sub == "d") {
	    int reg2 = TSYS::strParse(ai,1,",").empty() ? (reg+1) : strtol(TSYS::strParse(ai,1,",").c_str(),NULL,0);
	    int reg3 = TSYS::strParse(ai,2,",").empty() ? (reg2+1) : strtol(TSYS::strParse(ai,2,",").c_str(),NULL,0);
	    int reg4 = TSYS::strParse(ai,3,",").empty() ? (reg3+1) : strtol(TSYS::strParse(ai,3,",").c_str(),NULL,0);
	    if(!justWr) { regVal(reg2, atp_m); regVal(reg3, atp_m); regVal(reg4, atp_m); }
	    ai = (aflg.find("~") == string::npos)
			? TSYS::strMess("%d,%d,%d,%d", reg, reg2, reg3, reg4)
			: TSYS::strMess("%d,%d,%d,%d", reg4, reg3, reg2, reg);
	}
	else if(atp_sub[0] == 's') {
	    int rN = vmax(0,vmin(100,strtol(TSYS::strParse(ai,1,",").c_str(), NULL, 0)));
	    if(rN == 0) rN = 10;
	    if(!justWr) for(int iR = reg; iR < reg+rN; iR++) regVal(iR, atp_m);
	    ai = TSYS::strMess("%d,%d", reg, rN);
	}
    } else if(atp != "C" && atp != "CI") return "";

    return atp+":"+ai+":"+aflg;
}

void TMdContr::regVal( int reg, const string &dt, bool separate )
{
    if(reg < 0)	return;

    ResAlloc res(reqRes, true);

    //Register to acquisition block
    if(dt == "R" || dt == "RI") {
	vector< SDataRec > &workCnt = (dt == "RI") ? acqBlksIn : acqBlks;
	unsigned iB;
	for(iB = 0; iB < workCnt.size(); iB++) {
	    if((reg*2) < workCnt[iB].off) {
		if((mMerge || (reg*2+2) >= workCnt[iB].off) && (int)(workCnt[iB].val.size()+workCnt[iB].off-(reg*2)) < blkMaxSz)
		{
		    workCnt[iB].val.insert(0,workCnt[iB].off-reg*2,0);
		    workCnt[iB].off = reg*2;
		}
		else workCnt.insert(workCnt.begin()+iB,SDataRec(reg*2,2));
	    }
	    else if((reg*2+2) > (workCnt[iB].off+(int)workCnt[iB].val.size())) {
		if(!separate && (mMerge || reg*2 <= (workCnt[iB].off+(int)workCnt[iB].val.size())) && (reg*2+2-workCnt[iB].off) < blkMaxSz)
		{
		    workCnt[iB].val.append((reg*2+2)-(workCnt[iB].off+workCnt[iB].val.size()),0);
		    // Check for allow mergin to next block
		    if(!mMerge && iB+1 < workCnt.size() && (workCnt[iB].off+(int)workCnt[iB].val.size()) >= workCnt[iB+1].off)
		    {
			workCnt[iB].val.append(workCnt[iB+1].val,workCnt[iB].off+(int)workCnt[iB].val.size()-workCnt[iB+1].off,string::npos);
			workCnt.erase(workCnt.begin()+iB+1);
		    }
		}
		else continue;
	    }
	    break;
	}
	if(iB >= workCnt.size()) workCnt.insert(workCnt.begin()+iB,SDataRec(reg*2,2));
    }
    //Coils
    else if(dt == "C" || dt == "CI") {
	vector< SDataRec > &workCnt = (dt == "CI") ? acqBlksCoilIn : acqBlksCoil;
	unsigned iB;
	for(iB = 0; iB < workCnt.size(); iB++) {
	    if(reg < workCnt[iB].off) {
		if((mMerge || (reg+1) >= workCnt[iB].off) && (int)(workCnt[iB].val.size()+workCnt[iB].off-reg) < blkMaxSz*8)
		{
		    workCnt[iB].val.insert(0,workCnt[iB].off-reg,0);
		    workCnt[iB].off = reg;
		}
		else workCnt.insert(workCnt.begin()+iB,SDataRec(reg,1));
	    }
	    else if((reg+1) > (workCnt[iB].off+(int)workCnt[iB].val.size())) {
		if((mMerge || reg <= (workCnt[iB].off+(int)workCnt[iB].val.size())) && (reg+1-workCnt[iB].off) < blkMaxSz*8)
		{
		    workCnt[iB].val.append((reg+1)-(workCnt[iB].off+workCnt[iB].val.size()),0);
		    // Check for allow mergin to next block
		    if(!mMerge && iB+1 < workCnt.size() && (workCnt[iB].off+(int)workCnt[iB].val.size()) >= workCnt[iB+1].off)
		    {
			workCnt[iB].val.append(workCnt[iB+1].val,workCnt[iB].off+workCnt[iB].val.size()-workCnt[iB+1].off,string::npos);
			workCnt.erase(workCnt.begin()+iB+1);
		    }
		}
		else continue;
	    }
	    break;
	}
	if(iB >= workCnt.size()) workCnt.insert(workCnt.begin()+iB,SDataRec(reg,1));
    }
}

TVariant TMdContr::getVal( const string &addr, MtxString &w_err )
{
    if(tmDelay > 0) {
	if(w_err.getVal().empty()) w_err.setVal(_("10:Error of connection or no response."));
	return EVAL_REAL;
    }

    int off = 0;
    string tp = TSYS::strParse(addr, 0, ":", &off);
    string atp_sub = TSYS::strParse(tp, 1, "_");
    bool isInputs = (tp.size() >= 2 && tp[1] == 'I');
    string aids = TSYS::strParse(addr, 0, ":", &off);
    int aid = strtol(aids.c_str(),NULL,0);
    string mode = TSYS::strParse(addr, 0, ":", &off);

    if(tp.empty() || !(mode.find("r") != string::npos || mode.find("w") == string::npos)) return (int64_t)EVAL_INT;
    if(tp[0] == 'C') return getValC(aid, w_err, isInputs);
    if(tp[0] == 'R') {
	bool regLE = (mode.find("e") != string::npos);	//BE by default
	int64_t vl = getValR(aid, w_err, isInputs, regLE);
	atp_sub.resize(vmax(2,atp_sub.size()), 0);
	switch(atp_sub[0]) {
	    case 'b':	return char((vl==EVAL_INT)?EVAL_BOOL:((vl>>atoi(atp_sub.c_str()+1))&1));
	    case 'f': {	//Float (4)
		int64_t vl2 = getValR(strtol(TSYS::strParse(aids,1,",").c_str(),NULL,0), w_err, isInputs, regLE);
		if(vl == EVAL_INT || vl2 == EVAL_INT) return EVAL_REAL;
		union { uint16_t r[2]; float f; } wl;
		wl.r[0] = vl; wl.r[1] = vl2;
		return wl.f;
	    }
	    case 'd': {	//Double (8)
		int64_t vl2 = getValR(strtol(TSYS::strParse(aids,1,",").c_str(),NULL,0), w_err, isInputs, regLE);
		int64_t vl3 = getValR(strtol(TSYS::strParse(aids,2,",").c_str(),NULL,0), w_err, isInputs, regLE);
		int64_t vl4 = getValR(strtol(TSYS::strParse(aids,3,",").c_str(),NULL,0), w_err, isInputs, regLE);
		if(vl == EVAL_INT || vl2 == EVAL_INT || vl3 == EVAL_INT || vl4 == EVAL_INT) return EVAL_REAL;
		union { uint16_t r[4]; double d; } wl;
		wl.r[0] = vl; wl.r[1] = vl2; wl.r[2] = vl3; wl.r[3] = vl4;
		return wl.d;
	    }
	    case 'i':	//Integer
		switch(atp_sub[1]) {
		    case '2':	return int64_t((vl==EVAL_INT)?EVAL_INT:(int16_t)vl);
		    case '4': {
			int64_t vl2 = getValR(strtol(TSYS::strParse(aids,1,",").c_str(),NULL,0), w_err, isInputs, regLE);
			if(vl == EVAL_INT || vl2 == EVAL_INT) return (int64_t)EVAL_INT;
			union { uint16_t r[2]; int32_t i; } wl;
			wl.r[0] = vl; wl.r[1] = vl2;
			return wl.i;
		    }
		    case '8': {
			int64_t vl2 = getValR(strtol(TSYS::strParse(aids,1,",").c_str(),NULL,0), w_err, isInputs, regLE);
			int64_t vl3 = getValR(strtol(TSYS::strParse(aids,2,",").c_str(),NULL,0), w_err, isInputs, regLE);
			int64_t vl4 = getValR(strtol(TSYS::strParse(aids,3,",").c_str(),NULL,0), w_err, isInputs, regLE);
			if(vl == EVAL_INT || vl2 == EVAL_INT || vl3 == EVAL_INT || vl4 == EVAL_INT) return (int64_t)EVAL_INT;
			union { uint16_t r[4]; int64_t i; } wl;
			wl.r[0] = vl; wl.r[1] = vl2; wl.r[2] = vl3; wl.r[3] = vl4;
			return wl.i;
		    }
		}
		break;
	    case 'u':	//Unsigned integer
		switch(atp_sub[1]) {
		    case '2':	return int64_t((vl==EVAL_INT)?EVAL_INT:vl);
		    case '4': {
			int64_t vl2 = getValR(strtol(TSYS::strParse(aids,1,",").c_str(),NULL,0), w_err, isInputs, regLE);
			if(vl == EVAL_INT || vl2 == EVAL_INT) return (int64_t)EVAL_INT;
			union { uint16_t r[2]; uint32_t i; } wl;
			wl.r[0] = vl; wl.r[1] = vl2;
			return (int64_t)wl.i;
		    }
		}
		break;
	    case 's': {
		regLE = (mode.find("e") == string::npos);	//LE by default
		int rSz = strtol(TSYS::strParse(aids,1,",").c_str(), NULL, 0);
		string rez;
		for(int iR = aid; iR < (aid+rSz); iR++) {
		    vl = getValR(iR, w_err, isInputs, regLE);
		    if(vl == EVAL_INT) return EVAL_STR;
		    rez.append((char*)&vl, 2);
		}
		if(atp_sub.size() > 1) rez = Mess->codeConvIn(atp_sub.substr(1), rez);
		return rez;
	    }
	    default: return vl;
	}
    }
    return (int64_t)EVAL_INT;
}

int64_t TMdContr::getValR( int addr, MtxString &err, bool in, bool isLE )
{
    int64_t rez = EVAL_INT;
    ResAlloc res(reqRes, false);
    vector<SDataRec>	&workCnt = in ? acqBlksIn : acqBlks;
    for(unsigned iB = 0; iB < workCnt.size(); iB++)
	if((addr*2) >= workCnt[iB].off && (addr*2+2) <= (workCnt[iB].off+(int)workCnt[iB].val.size())) {
	    string terr = workCnt[iB].err.getVal();
	    if(terr.empty()) {
		rez = TSYS::getUnalign16(workCnt[iB].val.data()+(addr*2-workCnt[iB].off));
		rez = isLE ? TSYS::i16_LE(rez) : TSYS::i16_BE(rez);
	    } else if(err.getVal().empty()) err.setVal(terr);
	    break;
	}
    return rez;
}

char TMdContr::getValC( int addr, MtxString &err, bool in )
{
    char rez = EVAL_BOOL;
    ResAlloc res(reqRes, false);
    vector<SDataRec>	&workCnt = in ? acqBlksCoilIn : acqBlksCoil;
    for(unsigned iB = 0; iB < workCnt.size(); iB++)
	if(addr >= workCnt[iB].off && (addr+1) <= (workCnt[iB].off+(int)workCnt[iB].val.size())) {
	    string terr = workCnt[iB].err.getVal();
	    if(terr.empty()) rez = workCnt[iB].val[addr-workCnt[iB].off];
	    else if(err.getVal().empty()) err.setVal(terr);
	    break;
	}
    return rez;
}

bool TMdContr::setVal( const TVariant &val, const string &addr, MtxString &w_err, bool isGeneric )
{
    int off = 0;
    string tp = TSYS::strParse(addr, 0, ":", &off);
    string atp_sub = TSYS::strParse(tp, 1, "_");
    string aids = TSYS::strParse(addr, 0, ":", &off);
    int aid = strtol(aids.c_str(), NULL, 0);
    string mode = TSYS::strParse(addr, 0, ":", &off);

    //Registering for the later common writing in the asynchronous mode and pass updating the just writed values
    if(isGeneric) {
	if(mOmitRdCyclForWr) { aWrRes.lock(); omitRdCycl[addr] = mOmitRdCyclForWr; aWrRes.unlock(); }
	if(mAsynchWr) {
	    aWrRes.lock(); asynchWrs[addr] = val.getS(); aWrRes.unlock();
	    return true;
	}
    }

    //For direct writing we need the good connection in any event
    if(tmDelay > 0) {
	if(w_err.getVal().empty()) w_err.setVal(_("10:Error of connection or no response."));
	return false;
    }

    if(tp.empty() || (tp.size() >= 2 && tp[1] == 'I')) return true;
    bool wrRez = false;
    if(tp[0] == 'C')	wrRez = setValC(val.getB(), aid, w_err);
    if(tp[0] == 'R') {
	bool regLE = (mode.find("e") != string::npos);	//BE by default
	atp_sub.resize(vmax(2,atp_sub.size()), 0);
	switch(atp_sub[0]) {
	    case 'b': {
		int64_t vl = getValR(aid, w_err, false, regLE);
		if(vl != EVAL_INT) wrRez = setValR(val.getB() ? (vl|(1<<atoi(atp_sub.c_str()+1))) : (vl & ~(1<<atoi(atp_sub.c_str()+1))), aid, w_err, regLE);
		else if(tmDelay == 0) wrRez = true;	//By no previous data present but need for connect try
		break;
	    }
	    case 'f': {
		union { uint16_t r[2]; float f; } wl;
		wl.f = val.getR();
		map<int, uint16_t> regs;
		regs[aid] = wl.r[0];
		regs[strtol(TSYS::strParse(aids,1,",").c_str(),NULL,0)] = wl.r[1];
		wrRez = setValRs(regs, w_err, regLE);
		break;
	    }
	    case 'd': {
		union { uint16_t r[4]; double d; } wl;
		wl.d = val.getR();
		map<int, uint16_t> regs;
		regs[aid] = wl.r[0];
		regs[strtol(TSYS::strParse(aids,1,",").c_str(),NULL,0)] = wl.r[1];
		regs[strtol(TSYS::strParse(aids,2,",").c_str(),NULL,0)] = wl.r[2];
		regs[strtol(TSYS::strParse(aids,3,",").c_str(),NULL,0)] = wl.r[3];
		wrRez = setValRs(regs, w_err, regLE);
		break;
	    }
	    case 'i':
	    case 'u':
		switch(atp_sub[1]) {
		    case '2':	wrRez = setValR(val.getI(), aid, w_err, regLE);	break;
		    case '4': {
			union { uint16_t r[2]; uint32_t i; } wl;
			wl.i = val.getI();
			map<int, uint16_t> regs;
			regs[aid] = wl.r[0];
			regs[strtol(TSYS::strSepParse(aids,1,',').c_str(),NULL,0)] = wl.r[1];
			wrRez = setValRs(regs, w_err, regLE);
			break;
		    }
		    case '8': {
			union { uint16_t r[4]; uint64_t i; } wl;
			wl.i = val.getI();
			map<int, uint16_t> regs;
			regs[aid] = wl.r[0];
			regs[strtol(TSYS::strSepParse(aids,1,',').c_str(),NULL,0)] = wl.r[1];
			regs[strtol(TSYS::strSepParse(aids,2,',').c_str(),NULL,0)] = wl.r[2];
			regs[strtol(TSYS::strSepParse(aids,3,',').c_str(),NULL,0)] = wl.r[3];
			wrRez = setValRs(regs, w_err, regLE);
			break;
		    }
		}
		break;
	    case 's': {
		regLE = (mode.find("e") == string::npos);	//LE by default
		string vl = val.getS();
		if(atp_sub.size() > 1) vl = Mess->codeConvOut(atp_sub.substr(1), vl);
		vl.resize(strtol(TSYS::strSepParse(aids,1,',').c_str(),NULL,0)*2);
		map<int, uint16_t> regs;
		for(int iR = aid; iR < (aid+(int)vl.size()/2); iR++)
		    regs[iR] = TSYS::getUnalign16(vl.data()+(iR-aid)*2);
		wrRez = setValRs(regs, w_err, regLE);
		break;
	    }
	    default: wrRez = setValR(val.getI(), aid, w_err, regLE);
	}
    }

    return wrRez;
}

bool TMdContr::setValR( uint16_t val, int addr, MtxString &err, bool isLE )
{
    val = isLE ? TSYS::i16_LE(val) : TSYS::i16_BE(val);

    //Encode request PDU (Protocol Data Units)
    string pdu, terr;
    if(!mMltWr) {
	pdu = (char)0x6;		//Function, preset single register
	pdu += (char)(addr>>8);		//Address MSB
	pdu += (char)addr;		//Address LSB
	pdu += (char)val;		//Data MSB
	pdu += (char)(val>>8);		//Data LSB
    }
    else {
	pdu = (char)0x10;		//Function, preset multiple registers
	pdu += (char)(addr>>8);		//Address MSB
	pdu += (char)addr;		//Address LSB
	pdu += (char)0x00;		//Quantity MSB
	pdu += (char)0x01;		//Quantity LSB
	pdu += (char)0x02;		//Byte Count
	pdu += (char)val;		//Data MSB
	pdu += (char)(val>>8);		//Data LSB
    }

    //Request to a remote server
    if((terr=modBusReq(pdu)).empty())	numWReg++;
    else {
	if(err.getVal().empty()) err.setVal(terr);
	return false;
    }

    //Set to the acquisition block
    ResAlloc res(reqRes, false);
    for(unsigned iB = 0; iB < acqBlks.size(); iB++)
	if((addr*2) >= acqBlks[iB].off && (addr*2+2) <= (acqBlks[iB].off+(int)acqBlks[iB].val.size())) {
	    acqBlks[iB].val[addr*2-acqBlks[iB].off]   = (char)val;	//Data MSB
	    acqBlks[iB].val[addr*2-acqBlks[iB].off+1] = (char)(val>>8);	//Data LSB
	    break;
	}

    return true;
}

bool TMdContr::setValRs( const map<int,uint16_t> &regs, MtxString &err, bool isLE )
{
    int start = 0, prev = 0;
    uint16_t vreg = 0;
    string pdu, terr;

    //Writing by single register
    if(!mMltWr) {
	for(map<int,uint16_t>::const_iterator iR = regs.begin(); iR != regs.end(); iR++)
	    if(!setValR(iR->second,iR->first,err,isLE)) return false;
	return true;
    }

    //Writing by multiply registers
    for(map<int,uint16_t>::const_iterator iR = regs.begin(); true; iR++) {
	if(iR == regs.end() || (pdu.length() && (((iR->first-prev) > 1) || (prev-start) > 122))) {
	    if(pdu.empty()) break;
	    // Finish and send request
	    pdu[3] = (char)0x00;		//Quantity MSB
	    pdu[4] = (char)(prev-start+1);	//Quantity LSB
	    pdu[5] = (char)((prev-start+1)*2);	//Byte Count
	    // Request to remote server
	    if((terr=modBusReq(pdu)).empty())	numWReg += (prev-start+1);
	    else {
		if(err.getVal().empty()) err.setVal(terr);
		return false;
	    }

	    pdu = "";
	    if(iR == regs.end()) break;
	}

	vreg = isLE ? TSYS::i16_LE(iR->second) : TSYS::i16_BE(iR->second);

	//Start request prepare
	if(pdu.empty()) {
	    pdu = (char)0x10;			//Function, preset multiple registers
	    pdu += (char)(iR->first>>8);	//Address MSB
	    pdu += (char)iR->first;		//Address LSB
	    pdu += (char)0x00;			//Quantity MSB
	    pdu += (char)0x01;			//Quantity LSB
	    pdu += (char)0x02;			//Byte Count
	    start = iR->first;
	}
	pdu += (char)vreg;			//Data MSB
	pdu += (char)(vreg>>8);			//Data LSB
	prev = iR->first;

	//Set to the acquisition block
	ResAlloc res(reqRes, false);
	for(unsigned iB = 0; iB < acqBlks.size(); iB++)
	    if((iR->first*2) >= acqBlks[iB].off && (iR->first*2+2) <= (acqBlks[iB].off+(int)acqBlks[iB].val.size())) {
		acqBlks[iB].val[iR->first*2-acqBlks[iB].off]   = (char)vreg;		//Data MSB
		acqBlks[iB].val[iR->first*2-acqBlks[iB].off+1] = (char)(vreg>>8);	//Data LSB
		break;
	    }
    }

    return true;
}

bool TMdContr::setValC( char val, int addr, MtxString &err )
{
    //Encode request PDU (Protocol Data Units)
    string pdu, terr;
    if(!mMltWr) {
	pdu = (char)0x5;		//Function, preset single coil
	pdu += (char)(addr>>8);		//Address MSB
	pdu += (char)addr;		//Address LSB
	pdu += (char)(val?0xFF:0x00);	//Data MSB
	pdu += (char)0x00;		//Data LSB
    }
    else {
	pdu = (char)0xF;		//Function, preset multiple coils
	pdu += (char)(addr>>8);		//Address MSB
	pdu += (char)addr;		//Address LSB
	pdu += (char)0x00;		//Quantity MSB
	pdu += (char)0x01;		//Quantity LSB
	pdu += (char)0x01;		//Byte Count
	pdu += (char)(val?0x01:0x00);	//Data MSB
    }
    //Request to remote server
    if((terr=modBusReq(pdu)).empty())	numWCoil++;
    else {
	if(err.getVal().empty()) err.setVal(terr);
	return false;
    }
    //Set to acquisition block
    ResAlloc res(reqRes, false);
    for(unsigned iB = 0; iB < acqBlksCoil.size(); iB++)
	if(addr >= acqBlksCoil[iB].off && (addr+1) <= (acqBlksCoil[iB].off+(int)acqBlksCoil[iB].val.size())) {
	    acqBlksCoil[iB].val[addr-acqBlksCoil[iB].off] = val;
	    break;
	}

    return true;
}

string TMdContr::modBusReq( string &pdu )
{
    string rez;

    AutoHD<TTransportOut> tr = SYS->transport().at().outAt(addr());
		//SYS->transport().at().at(TSYS::strParse(addr(),0,".")).at().outAt(TSYS::strParse(addr(),1,"."));

    XMLNode req(mPrt);
    req.setAttr("id", id())->
	setAttr("reqTm", i2s(reqTm))->
	setAttr("node", i2s(mNode))->
	setAttr("reqTry", i2s(connTry))->
	setAttr("debugCat", (messLev()==TMess::Debug) ? nodePath() : string(""))->
	setText(pdu);

    try {
	tr.at().messProtIO(req, "ModBus");
	if(req.attr("err").size()) throw TError(nodePath(),req.attr("err"));
	pdu = req.text();
    }
    catch(TError &err) {
	if(s2i(err.mess) == TError::Tr_ErrDevice)	numErrCon++;
	else if(s2i(err.mess) == TError::Prt_ErrParams)	numErrPrtPrm++;
	else numErrResp++;
	if(messLev() >= TMess::Error) mess_err(nodePath().c_str(), "%s", err.mess.c_str());
	rez = err.mess;
    }

    return rez;
}

void TMdContr::redntDataUpdate( )
{
    TController::redntDataUpdate();

    //Cleaning the direct connection alarms
    if(tmDelay > 0) {
	alarmSet(TSYS::strMess(_("Connection to the data source: %s."),_("IN REDUNDANCY")), TMess::Info);
	tmDelay = 0;
    }
}

void *TMdContr::Task( void *icntr )
{
    const TSYS::STask &tsk = TSYS::taskDescr();
    string pdu;
    TMdContr &cntr = *(TMdContr *)icntr;

    cntr.endrunReq = false;
    cntr.prcSt = true;

    bool isStart = true;
    bool isStop  = false;

    while(true) {
	try {
	    if(!cntr.redntUse()) {
	    if(cntr.tmDelay > 0) {
		cntr.callSt = false;
		//Get data from blocks to parameters or calc for logical type parameters
		MtxAlloc prmRes(cntr.enRes, true);
		for(unsigned iP = 0; iP < cntr.pHd.size(); iP++)
		    cntr.pHd[iP].at().upValStd(tsk.tm_callRT/1000);
		for(unsigned iP = 0; iP < cntr.pHd.size(); iP++)
		    cntr.pHd[iP].at().upValLog(isStart, isStop,
			(isStart||isStop) ? DAQ_APER_FRQ : (tsk.period()?(1/tsk.period()):1e9/vmax(1e9/DAQ_APER_FRQ,cntr.period())), tsk.tm_callRT/1000);
		prmRes.unlock();

		cntr.tmDelay = vmax(0, cntr.tmDelay-(cntr.period()?(1e-9*(float)cntr.period()):1));

		if(isStop) break;

		TSYS::taskSleep(cntr.period()?cntr.period():1e9);

		if(cntr.endrunReq) isStop = true;
		isStart = false;
		continue;
	    }

	    cntr.callSt = true;

	    //Write asynchronous writings queue
	    /*MtxAlloc resAsWr(cntr.aWrRes, true);
	    map<string,string> aWrs = cntr.asynchWrs;
	    cntr.asynchWrs.clear();
	    resAsWr.unlock();
	    MtxString asWrErr(cntr.dataRes());
	    for(map<string,string>::iterator iw = aWrs.begin(); !isStart && !isStop && iw != aWrs.end(); ++iw) {
		if(asWrErr.size() && cntr.asynchWrs.find(iw->first) == cntr.asynchWrs.end()) cntr.asynchWrs[iw->first] = iw->second;
		if(!asWrErr.size() && !cntr.setVal(iw->second,iw->first,asWrErr)) { cntr.setCntrDelay(asWrErr); resAsWr.lock(); }
	    }
	    resAsWr.unlock();*/

	    ResAlloc res(cntr.reqRes, false);

	    //if(cntr.tmDelay > 0) continue;
	    //Get coils
	    for(unsigned iB = 0; !isStart && !isStop && iB < cntr.acqBlksCoil.size(); iB++) {
		if(cntr.endrunReq) break;
		//if(cntr.redntUse()) { cntr.acqBlksCoil[iB].err.setVal(_("4:Server failure.")); continue; }
		// Encode request PDU (Protocol Data Units)
		pdu = (char)0x01;					//Function, read multiple coils
		pdu += (char)(cntr.acqBlksCoil[iB].off>>8);		//Address MSB
		pdu += (char)cntr.acqBlksCoil[iB].off;			//Address LSB
		pdu += (char)(cntr.acqBlksCoil[iB].val.size()>>8);	//Number of coils MSB
		pdu += (char)cntr.acqBlksCoil[iB].val.size();		//Number of coils LSB
		// Request to remote server
		cntr.acqBlksCoil[iB].err.setVal(cntr.modBusReq(pdu));
		if(cntr.acqBlksCoil[iB].err.getVal().empty()) {
		    if((cntr.acqBlksCoil[iB].val.size()/8+((cntr.acqBlksCoil[iB].val.size()%8)?1:0)) != (pdu.size()-2))
			cntr.acqBlksCoil[iB].err.setVal(_("15:Error in size of response PDU."));
		    else {
			for(unsigned iC = 0; iC < cntr.acqBlksCoil[iB].val.size(); iC++)
			    cntr.acqBlksCoil[iB].val[iC] = (bool)((pdu[2+iC/8]>>(iC%8))&0x01);
			cntr.numRCoil += cntr.acqBlksCoil[iB].val.size();
		    }
		}
		else if(s2i(cntr.acqBlksCoil[iB].err.getVal()) == TError::Tr_ErrDevice) {
		    cntr.setCntrDelay(cntr.acqBlksCoil[iB].err.getVal());
		    break;
		}
	    }
	    if(cntr.tmDelay > 0) continue;

	    //Get input's coils
	    for(unsigned iB = 0; !isStart && !isStop && iB < cntr.acqBlksCoilIn.size(); iB++) {
		if(cntr.endrunReq) break;
		//if(cntr.redntUse()) { cntr.acqBlksCoilIn[iB].err.setVal(_("4:Server failure.")); continue; }
		// Encode request PDU (Protocol Data Units)
		pdu = (char)0x02;					//Function, read multiple input's coils
		pdu += (char)(cntr.acqBlksCoilIn[iB].off>>8);		//Address MSB
		pdu += (char)cntr.acqBlksCoilIn[iB].off;		//Address LSB
		pdu += (char)(cntr.acqBlksCoilIn[iB].val.size()>>8);	//Number of coils MSB
		pdu += (char)cntr.acqBlksCoilIn[iB].val.size();		//Number of coils LSB
		// Request to remote server
		cntr.acqBlksCoilIn[iB].err.setVal(cntr.modBusReq(pdu));
		if(cntr.acqBlksCoilIn[iB].err.getVal().empty()) {
		    if((cntr.acqBlksCoilIn[iB].val.size()/8+((cntr.acqBlksCoilIn[iB].val.size()%8)?1:0)) != (pdu.size()-2))
			cntr.acqBlksCoilIn[iB].err.setVal(_("15:Error in size of response PDU."));
		    else {
			for(unsigned iC = 0; iC < cntr.acqBlksCoilIn[iB].val.size(); iC++)
			    cntr.acqBlksCoilIn[iB].val[iC] = (bool)((pdu[2+iC/8]>>(iC%8))&0x01);
			cntr.numRCoilIn += cntr.acqBlksCoilIn[iB].val.size();
		    }
		}
		else if(s2i(cntr.acqBlksCoilIn[iB].err.getVal()) == TError::Tr_ErrDevice) {
		    cntr.setCntrDelay(cntr.acqBlksCoilIn[iB].err.getVal());
		    break;
		}
	    }
	    if(cntr.tmDelay > 0) continue;

	    //Get registers
	    for(unsigned iB = 0; !isStart && !isStop && iB < cntr.acqBlks.size(); iB++) {
		if(cntr.endrunReq) break;
		//if(cntr.redntUse()) { cntr.acqBlks[iB].err.setVal(_("4:Server failure.")); continue; }
		// Encode request PDU (Protocol Data Units)
		pdu = (char)0x03;				//Function, read multiple registers
		pdu += (char)((cntr.acqBlks[iB].off/2)>>8);	//Address MSB
		pdu += (char)(cntr.acqBlks[iB].off/2);		//Address LSB
		pdu += (char)((cntr.acqBlks[iB].val.size()/2)>>8);	//Number of registers MSB
		pdu += (char)(cntr.acqBlks[iB].val.size()/2);	//Number of registers LSB
		// Request to remote server
		cntr.acqBlks[iB].err.setVal(cntr.modBusReq(pdu));
		if(cntr.acqBlks[iB].err.getVal().empty()) {
		    if(cntr.acqBlks[iB].val.size() != (pdu.size()-2))
			cntr.acqBlks[iB].err.setVal(_("15:Error in size of response PDU."));
		    else {
			cntr.acqBlks[iB].val.replace(0, cntr.acqBlks[iB].val.size(), pdu.data()+2, cntr.acqBlks[iB].val.size());
			cntr.numRReg += cntr.acqBlks[iB].val.size()/2;
		    }
		}
		else if(s2i(cntr.acqBlks[iB].err.getVal()) == TError::Tr_ErrDevice) {
		    cntr.setCntrDelay(cntr.acqBlks[iB].err.getVal());
		    break;
		}
	    }
	    if(cntr.tmDelay > 0)	continue;

	    //Get input registers
	    for(unsigned iB = 0; !isStart && !isStop && iB < cntr.acqBlksIn.size(); iB++) {
		if(cntr.endrunReq) break;
		//if(cntr.redntUse()) { cntr.acqBlksIn[iB].err.setVal(_("4:Server failure.")); continue; }
		// Encode request PDU (Protocol Data Units)
		pdu = (char)0x04;					//Function, read multiple input registers
		pdu += (char)((cntr.acqBlksIn[iB].off/2)>>8);		//Address MSB
		pdu += (char)(cntr.acqBlksIn[iB].off/2);		//Address LSB
		pdu += (char)((cntr.acqBlksIn[iB].val.size()/2)>>8);	//Number of registers MSB
		pdu += (char)(cntr.acqBlksIn[iB].val.size()/2);	//Number of registers LSB
		// Request to remote server
		cntr.acqBlksIn[iB].err.setVal(cntr.modBusReq(pdu));
		if(cntr.acqBlksIn[iB].err.getVal().empty()) {
		    if(cntr.acqBlksIn[iB].val.size() != (pdu.size()-2))
			cntr.acqBlksIn[iB].err.setVal(_("15:Error in size of response PDU."));
		    else {
			cntr.acqBlksIn[iB].val.replace(0, cntr.acqBlksIn[iB].val.size(), pdu.data()+2, cntr.acqBlksIn[iB].val.size());
			cntr.numRRegIn += cntr.acqBlksIn[iB].val.size()/2;
		    }
		}
		else if(s2i(cntr.acqBlksIn[iB].err.getVal()) == TError::Tr_ErrDevice) {
		    cntr.setCntrDelay(cntr.acqBlksIn[iB].err.getVal());
		    break;
		}
	    }
	    if(cntr.tmDelay > 0)	continue;
	    res.release();

	    //Get data from blocks to parameters or calc for logical type parameters
	    MtxAlloc prmRes(cntr.enRes, true);
	    for(unsigned iP = 0; iP < cntr.pHd.size(); iP++)
		cntr.pHd[iP].at().upValStd(tsk.tm_callRT/1000);
	    for(unsigned iP = 0; iP < cntr.pHd.size(); iP++)
		cntr.pHd[iP].at().upValLog(isStart, isStop,
		    (isStart||isStop) ? DAQ_APER_FRQ : (tsk.period()?(1/tsk.period()):1e9/vmax(1e9/DAQ_APER_FRQ,cntr.period())), tsk.tm_callRT/1000);
	    isStart = false;
	    prmRes.unlock();

	    //Writing the asynchronous writings' buffer
	    MtxAlloc resAsWr(cntr.aWrRes, true);
	    map<string,string> aWrs = cntr.asynchWrs;
	    cntr.asynchWrs.clear();
	    resAsWr.unlock();
	    if(cntr.mAsynchWr) {
		MtxString asWrErr(cntr.dataRes());
		for(map<string,string>::iterator iw = aWrs.begin(); !isStart && !isStop && iw != aWrs.end(); ++iw) {
		    if(asWrErr.size() && cntr.asynchWrs.find(iw->first) == cntr.asynchWrs.end()) cntr.asynchWrs[iw->first] = iw->second;
		    if(!asWrErr.size() && !cntr.setVal(iw->second,iw->first,asWrErr)) { cntr.setCntrDelay(asWrErr); resAsWr.lock(); }
		}
		resAsWr.unlock();
	    }

	    //Generic acquisition alarm generate
	    if(cntr.tmDelay <= 0 && !cntr.endrunReq) {
		if(cntr.alSt != 0) {
		    cntr.alSt = 0;
		    cntr.alarmSet(TSYS::strMess(_("Connection to the data source: %s."),_("OK")), TMess::Info);
		}
		cntr.tmDelay--;
	    }

	    cntr.callSt = false;

	    }	// !cntr.redntUse()

	} catch(TError &err) { cntr.setCntrDelay(TSYS::strMess(_("10:Generic error: %s."), err.mess.c_str())); /*mess_err(err.cat.c_str(), err.mess.c_str());*/ }

	if(isStop) break;

	TSYS::taskSleep(cntr.period(), cntr.period() ? "" : cntr.cron());

	if(cntr.endrunReq) isStop = true;
    }

    cntr.prcSt = cntr.runSt = false;

    return NULL;
}

void TMdContr::setCntrDelay( const string &err )
{
    if(alSt <= 0) alSt = 1;

    int alLev = -TMess::Crit;
    //Device configuration error is not an alarm but only warning
    if(endrunReq || addr().empty() || !SYS->transport().at().modPresent(TSYS::strParse(addr(),0,".")) ||
	    !SYS->transport().at().at(TSYS::strParse(addr(),0,".")).at().outPresent(TSYS::strParse(addr(),1,".")) ||
	    SYS->transport().at().at(TSYS::strParse(addr(),0,".")).at().outAt(TSYS::strParse(addr(),1,".")).at().addr().empty())
	alLev = TMess::Warning;

    alarmSet(TSYS::strMess(_("Connection to the data source: %s."),TRegExp(":","g").replace(err,"=").c_str()), alLev);

    tmDelay = restTm;
}

TVariant TMdContr::objFuncCall( const string &id, vector<TVariant> &prms, const string &user_lang )
{
    // string messIO(string pdu) - sending the PDU <pdu> through the controller transpot by ModBus protocol.
    //  pdu - PDU request/response
    if(id == "messIO" && prms.size() >= 1 && prms[0].type() == TVariant::String) {
	string req = prms[0].getS();
	string rez = modBusReq(req);
	prms[0].setS(req); prms[0].setModify();
	return rez;
    }
    //ElTp value( string addr, ElTp setVl = NULL ) - direct accessing a value-registers with registering for acquisition.
    //  addr - the value-registers address in the standard format
    //  setVl - setting value, NULL (not specified) only for getting
    if(id == "value" && prms.size() >= 1) {
	if(!startStat()) return EVAL_REAL;
	string addr = prms[0].getS();
	MtxString err(dataRes());
	TVariant vl = getVal(addr, err);
	if(vl.isEVal()) regVal(addr);
	if(prms.size() >= 2 && !prms[1].isNull())
	    setVal(vl=prms[1], addr, err, true);

	return vl;
    }

    return TController::objFuncCall(id, prms, user_lang);
}

bool TMdContr::inWr( const string &addr )
{
    aWrRes.lock();

    //At presence in the writing buffer
    bool rez = (asynchWrs.find(addr) != asynchWrs.end());

    //During the omitting cycles
    if(!rez && (rez=(omitRdCycl.find(addr)!=omitRdCycl.end())) && (--omitRdCycl[addr]) <= 0)
	omitRdCycl.erase(addr);

    aWrRes.unlock();

    return rez;
}

void TMdContr::cntrCmdProc( XMLNode *opt )
{
    //Get page info
    if(opt->name() == "info") {
	TController::cntrCmdProc(opt);
	ctrMkNode3("fld",opt,-1,"/cntr/st/runSt",EVAL_STR,SEC_RD|SEC_WR,
	    "help",_("Manual restart of the enabled controller object causes the force reformation of the acquisition blocks.\n"
		    "Restart to apply the removed PLC links in run."));
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/PROT",EVAL_STR,SEC_RD|SEC_WR);
	string outHelp, tVl;
	if((tVl=TSYS::strParse(addr(),1,".")).size() && tVl.find(STR_IN_PREF) != 0 &&
		SYS->transport().at().modPresent((tVl=TSYS::strParse(addr(),0,"."))))
	    outHelp = SYS->transport().at().at(tVl).at().outAddrHelp();
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/ADDR",EVAL_STR,SEC_RD|SEC_WR,
	    "tp","str", "dest","sel_ed", "select","/cntr/cfg/trLst",
	    "help",(TMess::labStdOutTrs()+(outHelp.size()?"\n\n"+outHelp+"\n\n"+_("Default port of the ModuBus/TCP is 502!")+"\n":"")).c_str());
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/NODE",EVAL_STR,SEC_RD|SEC_WR);
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/MAX_BLKSZ",EVAL_STR,SEC_RD|(startStat()?0:SEC_WR));
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/SCHEDULE",EVAL_STR,SEC_RD|SEC_WR,
	    "tp","str", "dest","sel_ed", "sel_list",TMess::labSecCRONsel().c_str(), "help",TMess::labSecCRON().c_str());
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/PRIOR",EVAL_STR,SEC_RD|(startStat()?0:SEC_WR), "help",TMess::labTaskPrior().c_str());
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/FRAG_MERGE",EVAL_STR,SEC_RD|(startStat()?0:SEC_WR),
	    "help",_("Merge non-adjacent fragments of registers for request in a single block.\n"
		    "WARNING! Some devices do not support the passing query of wrong registers in one block."));
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/TM_REQ",EVAL_STR,SEC_RD|SEC_WR,
	    "help",_("Individual connection timeout for the device polled by this task.\n"
		    "For zero value, the total connection timeout is used from the used output transport."));
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/WR_OMIT_CYCL_RD",EVAL_STR,SEC_RD|SEC_WR,
	    "help",_("Can be useful for PLC which applying the changes not fast "
		     "and they are processed in some significant time depending on the PLC load.\n"
		     "So, the cycles value then specifies count of the omitting read cycles "
		     "before reading back the changed value, preventing the value twinkle."));
	return;
    }

    //Process command to page
    string a_path = opt->attr("path");
    if(a_path == "/cntr/st/runSt" && ctrChkNode2(opt,"set",SEC_WR) && s2i(opt->text()) && enableStat()) {
	// Asynchronous writings buffer clean up
	aWrRes.lock(); asynchWrs.clear(); aWrRes.unlock();

	// Data blocks clean up
	reqRes.resRequestW(true);
	acqBlks.clear();
	acqBlksIn.clear();
	acqBlksCoil.clear();
	acqBlksCoilIn.clear();
	reqRes.resRelease();

	// Reloading the parameters' data
	vector<string> pls;
	list(pls);

	for(unsigned iP = 0; iP < pls.size(); iP++)
	    if(at(pls[iP]).at().enableStat())
		at(pls[iP]).at().loadDATA(true);

	// Now same starting
	start();
    }
    else if(a_path == "/cntr/cfg/trLst" && ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) {
	int cLv = 0;
	string	cPath = "", cEl, pAddr = TSYS::strParse(addr(), 0, ":");
	vector<string> list;
	for(int cOff = 0; (cEl=TSYS::strParse(pAddr,0,".",&cOff)).size(); ++cLv) {
	    cPath += cLv ? "."+cEl : cEl;
	    opt->childAdd("el")->setText(cPath);
	}

	switch(cLv) {
	    case 0:
		SYS->transport().at().modList(list);
		for(unsigned iS = 0; iS < list.size(); iS++)
		    opt->childAdd("el")->setText(list[iS]);
		break;
	    case 1:
		if(!SYS->transport().at().modPresent(cEl=TSYS::strParse(pAddr,0,"."))) break;
		SYS->transport().at().at(cEl).at().outList(list);
		for(unsigned iS = 0; iS < list.size(); iS++)
		    opt->childAdd("el")->setText(cPath+"."+list[iS]);
		SYS->transport().at().at(cEl).at().inList(list);
		for(unsigned iS = 0; iS < list.size(); iS++)
		    opt->childAdd("el")->setText(cPath+"."+STR_IN_PREF+list[iS]);
		break;
	    case 2:
		if(TSYS::strParse(pAddr,1,".").find(STR_IN_PREF) == 0)
		    opt->childAdd("el")->setText(cPath+":RemConId");
		//else ?!?!
		break;
	}
    }
    else TController::cntrCmdProc(opt);
}

TMdContr::SDataRec::SDataRec( int ioff, int v_rez ) : off(ioff), err(mod->dataRes())
{
    val.assign(v_rez, 0);
    err.setVal(_("11:No value received."));
}

//******************************************************
//* TMdPrm                                             *
//******************************************************
TMdPrm::TMdPrm( string name, TTypeParam *tp_prm ) : TParamContr(name, tp_prm), acqErr(dataRes()), pEl("ModBus_attr"), lCtx(NULL)
{
    acqErr.setVal("");
    if(isLogic()) lCtx = new TLogCtx(this, name+"_ModBusPrm");
}

TMdPrm::~TMdPrm( )
{
    nodeDelAll();
    if(lCtx) delete lCtx;
}

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

void TMdPrm::postDisable( int flag )
{
    TParamContr::postDisable(flag);

    if(flag&NodeRemove && isLogic()) {
	string io_bd = owner().DB()+"."+owner().tbl(type())+"_io";
	TConfig cfg(&mod->prmIOE());
	cfg.cfg("PRM_ID").setS(ownerPath(true), true);
	TBDS::dataDel(io_bd, owner().owner().nodePath()+owner().tbl(type())+"_io", cfg);
    }
}

TCntrNode &TMdPrm::operator=( const TCntrNode &node )
{
    TParamContr::operator=(node);

    const TMdPrm *src_n = dynamic_cast<const TMdPrm*>(&node);
    if(!src_n || !src_n->enableStat() || !enableStat() || !isLogic() || !lCtx || !lCtx->func()) return *this;

    //IO values copy
    for(int iIO = 0; iIO < src_n->lCtx->ioSize(); iIO++)
	if(src_n->lCtx->ioFlg(iIO)&TPrmTempl::CfgLink)
	    lCtx->lnkAddrSet(iIO, src_n->lCtx->lnkAddr(iIO));
	else lCtx->setS(iIO, src_n->lCtx->getS(iIO));

    lCtx->chkLnkNeed = lCtx->initLnks();
    modif();

    return *this;
}

void TMdPrm::setType( const string &tpId )
{
    if(lCtx) { delete lCtx; lCtx = NULL; }

    TParamContr::setType(tpId);

    if(isLogic()) lCtx = new TLogCtx(this, name()+"_ModBusPrm");
}

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

bool TMdPrm::isStd( ) const		{ return (type().name == "Prm"); }

bool TMdPrm::isLogic( ) const		{ return (type().name == "PrmL"); }

void TMdPrm::enable( )
{
    if(enableStat()) return;

    TParamContr::enable();

    loadDATA();

    owner().prmEn(this, true);	//Put to process
}

void TMdPrm::loadDATA( bool incl )
{
    map<string, bool> als;

    //Parse ModBus attributes and convert to string list for standard type parameter
    if(isStd()) {
	string sel, atp, atp_sub, ai, aflg, aid, anm;
	string m_attrLs = cfg("ATTR_LS").getS();
	for(int ioff = 0; (sel=TSYS::strLine(m_attrLs,0,&ioff)).size() || ioff < (int)m_attrLs.size(); ) {
	    if(sel.empty() || sel[0] == '#') continue;
	    int elOff = 0;
	    if((atp=TSYS::strParse(sel,0,":",&elOff)).empty()) continue;
	    atp_sub = TSYS::strParse(atp, 1, "_");
	    if((ai=TSYS::strParse(sel,0,":",&elOff)).empty()) continue;
	    aflg = TSYS::strParse(sel, 0, ":", &elOff);
	    aid = TSYS::strEncode(sTrm(TSYS::strParse(sel,0,":",&elOff)),TSYS::oscdID);
	    if(aid.empty()) aid = TSYS::strEncode(sTrm(ai),TSYS::oscdID);
	    anm = sel.substr(elOff);
	    if(anm.empty()) anm = aid;

	    if(aid.empty() || (vlPresent(aid) && !pEl.fldPresent(aid)) || als.find(aid) != als.end())	continue;

	    TFld::Type tp = TFld::Integer;
	    if(atp[0] == 'C' || (atp_sub.size() && atp_sub[0] == 'b')) tp = TFld::Boolean;
	    else if(atp_sub == "f" || atp_sub == "d") tp = TFld::Real;
	    else if(atp_sub.size() && atp_sub[0] == 's') tp = TFld::String;

	    if(!pEl.fldPresent(aid) || pEl.fldAt(pEl.fldId(aid)).type() != tp) {
		if(pEl.fldPresent(aid)) pEl.fldDel(pEl.fldId(aid));
		pEl.fldAdd(new TFld(aid.c_str(),"",tp,TFld::NoFlag));
	    }
	    int el_id = pEl.fldId(aid);

	    unsigned flg = (aflg.find("r") != string::npos && aflg.find("w") != string::npos) ? TVal::DirWrite|TVal::DirRead :
			   ((aflg.find("w") != string::npos) ? TVal::DirWrite : TFld::NoWrite|TVal::DirRead);

	    if(atp.size() >= 2 && atp[1] == 'I') flg = (flg & (~TVal::DirWrite)) | TFld::NoWrite;
	    pEl.fldAt(el_id).setFlg(flg);
	    pEl.fldAt(el_id).setDescr(anm);
	    pEl.fldAt(el_id).setReserve(owner().regVal(sel));

	    als[aid] = true;
	}
    }
    //Template's function connect for logical type parameter
    else if(isLogic() && lCtx && lCtx->func())	lCtx->chkLnkNeed = lCtx->initLnks(true);
    else if(isLogic() && lCtx && !lCtx->func())
	try {
	    //unsigned fId = 0;
	    string m_tmpl = cfg("TMPL").getS();
	    if(!lCtx->func() && m_tmpl.size()) {
		lCtx->setFunc(&SYS->daq().at().tmplLibAt(TSYS::strParse(m_tmpl,0,".")).at().
						      at(TSYS::strParse(m_tmpl,1,".")).at().func().at());
		lCtx->setVfName(id()+"_ModBus_tmplprm");
	    }
	    // Init attrubutes
	    if(lCtx->func()) {
		lCtx->addLinksAttrs(&pEl);

		// Load IO at enabling
		loadVal();

		// Init links
		lCtx->chkLnkNeed = lCtx->initLnks(/*true*/);	//!!!! Do not reconnect but that can be done in loadVal() early

		// Init system attributes identifiers
		lCtx->idFreq  = lCtx->ioId("f_frq");
		lCtx->idStart = lCtx->ioId("f_start");
		lCtx->idStop  = lCtx->ioId("f_stop");
		lCtx->idErr   = lCtx->ioId("f_err");
		lCtx->idSh    = lCtx->ioId("SHIFR");
		lCtx->idNm    = lCtx->ioId("NAME");
		lCtx->idDscr  = lCtx->ioId("DESCR");
		lCtx->idThis  = lCtx->ioId("this");
		if(lCtx->idThis >= 0) lCtx->setO(lCtx->idThis, new TCntrNodeObj(AutoHD<TCntrNode>(this),"root"));

		// First call
		if(owner().startStat() && !owner().redntUse()) upValLog(true, false, DAQ_APER_FRQ);
	    }
	} catch(TError &err) { disable(); throw; }

    //Checking to delete the DAQ parameter attributes
    for(int iP = 0; isStd() && iP < (int)pEl.fldSize(); iP++)
	if(als.find(pEl.fldAt(iP).name()) == als.end())
	    try{ pEl.fldDel(iP); iP--; }
	    catch(TError &err) { mess_warning(err.cat.c_str(),err.mess.c_str()); }

    //Call the included paramers' data reload
    if(incl) {
	vector<string> prmLs;
	list(prmLs);
	for(unsigned iP = 0; iP < prmLs.size(); iP++)
	    at(prmLs[iP]).at().loadDATA(incl);
    }
}

void TMdPrm::disable( )
{
    if(!enableStat())  return;

    if(lCtx && lCtx->func()) {
	//Waiting the ordinal calculation finish
	for(int iTm = 0; lCtx->isCalc() && iTm < prmWait_TM/prmWait_DL; iTm++) SYS->sysSleep(prmWait_DL);
	//Termination the calculation
	while(lCtx->isCalc()) { SYS->taskSendSIGALRM(owner().nodePath('.',true)); SYS->sysSleep(prmWait_DL); }
    }

    owner().prmEn(this, false);	//Remove from process
    if(lCtx && lCtx->func() && owner().startStat() && !owner().redntUse()) upValLog(false, true, DAQ_APER_FRQ);

    TParamContr::disable();

    //Set EVAL to parameter attributes
    vector<string> ls;
    elem().fldList(ls);
    for(unsigned iEl = 0; iEl < ls.size(); iEl++)
	vlAt(ls[iEl]).at().setS(EVAL_STR, 0, true);

    //Template's function disconnect
    if(lCtx)	lCtx->cleanLnks(true);
}

void TMdPrm::load_( )
{
    if(SYS->cfgCtx() && toEnable() && !enableStat())	enable();
    loadVal();
}

void TMdPrm::loadVal( const string &attr )
{
    if(!enableStat() || !isLogic() || !lCtx || !lCtx->func()) return;

    //Load IO and init links
    TConfig cfg(&mod->prmIOE());
    cfg.cfg("PRM_ID").setS(ownerPath(true));
    cfg.cfg("VALUE").setExtVal(true);
    string io_bd = owner().DB()+"."+owner().tbl(type())+"_io";

    //IO values loading and links set
    for(int iIO = 0; attr.empty() && iIO < lCtx->ioSize(); iIO++) {
	IO *oIO = lCtx->func()->io(iIO);
	cfg.cfg("ID").setS(oIO->id());
	if(!TBDS::dataGet(io_bd,owner().owner().nodePath()+owner().tbl(type())+"_io",cfg,TBDS::NoException)) continue;
	if(oIO->flg()&TPrmTempl::CfgLink)
	    lCtx->lnkAddrSet(iIO, cfg.cfg("VALUE").getS(TCfg::ExtValOne));	//Force to no translation
	else if(oIO->type() != IO::String || !(oIO->flg()&IO::TransltText))
	    lCtx->setS(iIO, cfg.cfg("VALUE").getS(TCfg::ExtValOne));		//Force to no translation
	else lCtx->setS(iIO, cfg.cfg("VALUE").getS());
    }
    lCtx->chkLnkNeed = lCtx->initLnks();

    //Load the dynamic parameters
    vector<string> list;
    vlList(list);
    for(unsigned iL = 0; iL < list.size(); iL++) {
	if((attr.size() && list[iL] != attr) || !(vlAt(list[iL]).at().fld().flg()&TVal::Dynamic)) continue;
	cfg.cfg("ID").setS(list[iL]);
	if(!TBDS::dataGet(io_bd,owner().owner().nodePath()+owner().tbl(type())+"_io",cfg,TBDS::NoException)) continue;
	vlAt(list[iL]).at().setS(cfg.cfg("VALUE").getS(TCfg::ExtValOne), 0, true);
    }
}

void TMdPrm::save_( )
{
    TParamContr::save_();
    saveVal();
}

void TMdPrm::saveVal( )
{
    if(!enableStat() || !isLogic() || !lCtx || !lCtx->func()) return;

    TConfig cfg(&mod->prmIOE());
    cfg.cfg("PRM_ID").setS(ownerPath(true));
    string io_bd = owner().DB()+"."+owner().tbl(type())+"_io";

    //Save IOs and links from the template
    for(int iIO = 0; iIO < lCtx->func()->ioSize(); iIO++) {
	// Do not save dynamically updated and fixed ones
	if(iIO == lCtx->idFreq || iIO == lCtx->idStart || iIO == lCtx->idStop || iIO == lCtx->idErr ||
		iIO == lCtx->idSh || iIO == lCtx->idNm || iIO == lCtx->idDscr || iIO == lCtx->idThis)
	    continue;

	IO *oIO = lCtx->func()->io(iIO);
	cfg.cfg("ID").setS(oIO->id());
	cfg.cfg("VALUE").setNoTransl(!(oIO->type() == IO::String &&
		(oIO->flg()&IO::TransltText) && !(oIO->flg()&TPrmTempl::CfgLink)));
	if(oIO->flg()&TPrmTempl::CfgLink)
	    cfg.cfg("VALUE").setS(lCtx->lnkAddr(iIO));
	else cfg.cfg("VALUE").setS(lCtx->getS(iIO));
	TBDS::dataSet(io_bd, owner().owner().nodePath()+owner().tbl(type())+"_io", cfg);
    }

    //Save the dynamic parameters
    vector<string> list;
    vlList(list);
    cfg.cfg("VALUE").setNoTransl(true);
    for(unsigned iL = 0; iL < list.size(); iL++) {
	if(!(vlAt(list[iL]).at().fld().flg()&TVal::Dynamic)) continue;
	cfg.cfg("ID").setS(list[iL]);
	cfg.cfg("VALUE").setS(vlAt(list[iL]).at().getS());
	TBDS::dataSet(io_bd, owner().owner().nodePath()+owner().tbl(type())+"_io", cfg);
    }
}

void TMdPrm::upValStd( int64_t tm )
{
    if(!isStd()) return;

    MtxString w_err(dataRes());
    AutoHD<TVal> pVal;
    vector<string> ls;

    elem().fldList(ls);
    for(unsigned iEl = 0; iEl < ls.size(); iEl++) {
	pVal = vlAt(ls[iEl]);
	if(!(pVal.at().fld().flg()&TVal::DirRead) || (pVal.at().fld().flg()&TVal::Dynamic) || owner().inWr(pVal.at().fld().reserve()))
	    continue;
	pVal.at().set(owner().getVal(pVal.at().fld().reserve(),w_err), tm, true);
    }

    //Alarm set
    acqErr.setVal(w_err.getVal());
}

void TMdPrm::upValLog( bool first, bool last, double frq, int64_t tm )
{
    if(!isLogic() || !lCtx->func())	return;

    try {
	if(lCtx->chkLnkNeed && !first && !last)	lCtx->chkLnkNeed = lCtx->initLnks();

	//Set fixed system attributes
	if(lCtx->idFreq >= 0)	lCtx->setR(lCtx->idFreq, frq);
	if(lCtx->idStart >= 0)	lCtx->setB(lCtx->idStart, lCtx->isChangedProg(true) || first);
	if(lCtx->idStop >= 0)	lCtx->setB(lCtx->idStop, last);
	if(lCtx->idSh >= 0)	lCtx->setS(lCtx->idSh, id());
	if(lCtx->idNm >= 0)	lCtx->setS(lCtx->idNm, trD(name()));
	if(lCtx->idDscr >= 0)	lCtx->setS(lCtx->idDscr, trD(descr()));

	//Get input links
	lCtx->inputLinks();

	//Calc template
	lCtx->setMdfChk(true);
	lCtx->calc();
	if(SYS->modifCalc()) modif();

	//Put output links
	lCtx->outputLinks();

	//Put fixed system attributes
	if(lCtx->idNm >= 0 && lCtx->ioMdf(lCtx->idNm)) setName(lCtx->getS(lCtx->idNm));
	if(lCtx->idDscr >= 0 && lCtx->ioMdf(lCtx->idDscr)) setDescr(lCtx->getS(lCtx->idDscr));

	//Put values to the attributes and archives in the passive archiving mode
	lCtx->archAttrs(this, tm);
    } catch(TError &err) {
	mess_warning(err.cat.c_str(),"%s",err.mess.c_str());
	mess_warning(nodePath().c_str(),_("Error of the calculation template."));
    }

    acqErr.setVal("");	//But it is not used for the type
}

void TMdPrm::vlGet( TVal &val )
{
    if(!enableStat() || !owner().startStat()) {
	if(val.name() == "err") {
	    if(!enableStat())			val.setS(_("1:Parameter disabled."), 0, true);
	    else if(!owner().startStat())	val.setS(_("2:Acquisition stopped."), 0, true);
	} else val.setS(EVAL_STR, 0, true);
	return;
    }

    if(owner().redntUse()) return;

    if(val.name() == "err") {
	if(owner().tmDelay > -1) val.setS(owner().getStatus(), 0, true);
	else if(acqErr.getVal().size()) val.setS(acqErr.getVal(), 0, true);
	else if(lCtx && lCtx->idErr >= 0) val.setS(lCtx->getS(lCtx->idErr), 0, true);
	else val.setS("0", 0, true);
    }
}

void TMdPrm::vlSet( TVal &vo, const TVariant &vl, const TVariant &pvl )
{
    if(!enableStat() || !owner().startStat())	{ vo.setS(EVAL_STR, 0, true); return; }

    if(vl.isEVal() || vl == pvl) return;

    //Send to active reserve station
    bool isRdnt = vlSetRednt(vo, vl, pvl);

    //Direct write
    bool wrRez = false;
    // Standard type request
    if(isStd() && !isRdnt)
	wrRez = owner().setVal(vl, vo.fld().reserve(), acqErr, true);
    // Logical type request
    else if(isLogic()) {
	int id_lnk = lCtx->lnkId(vo.name());
	if(id_lnk >= 0 && !lCtx->lnkActive(id_lnk)) id_lnk = -1;
	if(id_lnk < 0) { lCtx->set(lCtx->ioId(vo.name()), vl); wrRez = true; }
	else if(!isRdnt) wrRez = lCtx->lnkOutput(id_lnk, vl);
    }
    if(!wrRez && !isRdnt) vo.setS(EVAL_STR, 0, true);
}

void TMdPrm::vlArchMake( TVal &val )
{
    TParamContr::vlArchMake(val);

    if(val.arch().freeStat()) return;
    val.arch().at().setSrcMode(TVArchive::SRC_DAQAttr);
    val.arch().at().setPeriod(owner().period() ? owner().period()/1000 : 1000000);
    val.arch().at().setHardGrid(true);
    val.arch().at().setHighResTm(true);
}

void TMdPrm::cntrCmdProc( XMLNode *opt )
{
    //Get page info
    if(opt->name() == "info") {
	TParamContr::cntrCmdProc(opt);
	if(isStd())
	    ctrMkNode3("fld",opt,-1,"/prm/cfg/ATTR_LS",EVAL_STR,SEC_RD|((owner().startStat()&&enableStat())?0:SEC_WR),
		"rows","8", "SnthHgl","1",
		"help",_("Attributes configuration list. List must be written by lines in the form \"{dt}:{numb}[:{flg}[:{id}[:{name}]]]\".\n"
		    "Where:\n"
		    "  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 the suffixes:\n"
		    "         i2-Int16, i4-Int32, i8-Int64, u2-UInt16, u4-UInt32, f-Float, d-Double, b5-Bit5, b-Bit in address, s[CHARSET]-String;\n"
		    "       Start from the symbol '#' for the commented line;\n"
		    "  numb - ModBus data address of the device (dec, hex or octal) [0...65535];\n"
		    "  flg - flags: read/write mode (r-read, w-write),\n"
		    "               strict requesting mode (not combining) 's',\n"
		    "               registers order inversion '~',\n"
		    "               register 'e'ndian toggle (to LE in generic and BE for strings);\n"
		    "  id - identifier of the created attribute;\n"
		    "  name - name of the created attribute.\n"
		    "Examples:\n"
		    "  \"R:0x300:rw:var:Variable\" - register access;\n"
		    "  \"C:100:rw:var1:Variable 1\" - coil access;\n"
		    "  \"R_f:200:r:float:Float\", \"R_f:200:r~:float:Float\" - get float from the registers 200 and 201, 201 and 200;\n"
		    "  \"R_i4:400,300:r:int32:Int32\" - get int32 from the registers 400 and 300;\n"
		    "  \"R_b10:25:r:rBit:Reg bit\", \"R_b:25.10:r:rBit:Reg bit\" - get the bit 10 from the register 25;\n"
		    "  \"R_s:15,20:r:str:Reg blk\" - get string (registers block) from the register 15 and the size 20."));
	if(isLogic()) {
	    ctrMkNode3("fld",opt,-1,"/prm/cfg/TMPL",EVAL_STR,RWRW__, "tp","str", "dest","select", "select","/prm/tmplList");
	    if(enableStat() && lCtx->func())	lCtx->cntrCmdProc(opt);
	}
	return;
    }
    //Process command to page
    string a_path = opt->attr("path");
    if(isStd() && a_path == "/prm/cfg/ATTR_LS" && ctrChkNode2(opt,"SnthHgl",SEC_RD)) {
	opt->childAdd("rule")->setAttr("expr","^#[^\n]*")->setAttr("color","gray")->setAttr("font_italic","1");
	opt->childAdd("rule")->setAttr("expr","^(CI?|RI?_b1[0-5]|RI?_b[0-9]?|RI?_i[248]?|RI?_u[24]?|RI?_[fd]|RI?_s[^:]*|RI?)")->setAttr("color","darkorange");
	XMLNode *g0 = opt->childAdd("rule")->setAttr("expr","(?<=:).*");
	    g0->childAdd("rule")->setAttr("expr","^(0?[xX]?[0-9a-fA-F]*)(\\.1[0-5]|\\.[0-9]|,(0?[xX]?[0-9a-fA-F]*),?(0?[xX]?[0-9a-fA-F]*),?(0?[xX]?[0-9a-fA-F]*)|)")->setAttr("color","blue");
	    XMLNode *g1 = g0->childAdd("rule")->setAttr("expr","(?<=:).*");
		g1->childAdd("rule")->setAttr("expr","[rws~e]*(?<!:)")->setAttr("color","red");
		XMLNode *g2 = g1->childAdd("rule")->setAttr("expr","(?<=:).*");
		    g2->childAdd("rule")->setAttr("expr","[^:]*")->setAttr("font_weight","1");
		    g2->childAdd("rule")->setAttr("expr","(?<=:).*")->setAttr("font_italic","1");

	//opt->childAdd("rule")->setAttr("expr",":[rws~]*:")->setAttr("color","red");
	//opt->childAdd("rule")->setAttr("expr",":(0[xX][0-9a-fA-F]*|[0-9]*),?(0[xX][0-9a-fA-F]*|[0-9]*),?(0[xX][0-9a-fA-F]*|[0-9]*),?(0[xX][0-9a-fA-F]*|[0-9]*)")->setAttr("color","blue");
	//opt->childAdd("rule")->setAttr("expr","\\:")->setAttr("color","blue");
    }
    else if(isLogic() && a_path == "/prm/cfg/TMPL" && ctrChkNode3(opt,"set",RWRW__,SEC_WR)) {
	cfg("TMPL").setS(opt->text());
	disable();
	modif();
    }
    else if(isLogic() && enableStat() && lCtx->func() && lCtx->cntrCmdProc(opt)) {
	if(owner().period() && a_path != "/cfg/attr_only") opt->setAttr("updTm", r2s(1.5e-9*owner().period()));
    }
    else {
	TParamContr::cntrCmdProc(opt);
	if(a_path.starts_with("/val/") && owner().period())
	    opt->setAttr("updTm", r2s(1.5e-9*owner().period()));
    }
}

//***************************************************
//* Logical type parameter's context                *
TMdPrm::TLogCtx::TLogCtx( TCntrNode *iobj, const string &name ) : TPrmTempl::Impl(iobj, name.c_str()),
    chkLnkNeed(false), idFreq(-1), idStart(-1), idStop(-1), idErr(-1), idSh(-1), idNm(-1), idDscr(-1), idThis(-1)
{

}

/*void TMdPrm::TLogCtx::lnkAdd( int num, const SLnk &l )
{
    TPrmTempl::Impl::lnkAdd(num, l);

    MtxAlloc res(lnkRes, true);
    map<int,SLnk>::iterator it = lnks.find(num);
    if(it != lnks.end()) lnkAddrSet(num, func()->io(num)->def());
}*/

bool TMdPrm::TLogCtx::lnkInit( int num, bool toRecnt )
{
    //Common link forms
    if(!TPrmTempl::Impl::lnkInit(num,toRecnt))	return false;

    MtxAlloc res(lnkRes, true);
    map<int,SLnk>::iterator it = lnks.find(num);
    if(it == lnks.end() || it->second.addrSpec.size())	return false;

    return (it->second.addrSpec=((TMdPrm*)obj)->owner().regVal(it->second.addr)).size();
}

bool TMdPrm::TLogCtx::lnkActive( int num )
{
    MtxAlloc res(lnkRes, true);
    map<int,SLnk>::iterator it = lnks.find(num);
    if(it != lnks.end() && it->second.addrSpec.size())	return true;
    return TPrmTempl::Impl::lnkActive(num);
}

TVariant TMdPrm::TLogCtx::lnkInput( int num )
{
    MtxAlloc res(lnkRes, true);
    map<int,SLnk>::iterator it = lnks.find(num);
    if(it == lnks.end()) return EVAL_REAL;
    string addrSpec = it->second.addrSpec;
    res.unlock();

    return addrSpec.size() ?
	((TMdPrm*)obj)->owner().getVal(addrSpec,((TMdPrm*)obj)->acqErr) :
	TPrmTempl::Impl::lnkInput(num);
}

bool TMdPrm::TLogCtx::lnkOutput( int num, const TVariant &vl )
{
    MtxAlloc res(lnkRes, true);
    map<int,SLnk>::iterator it = lnks.find(num);
    if(it == lnks.end() || !(ioFlg(num)&(IO::Output|IO::Return))) return false;
    string addrSpec = it->second.addrSpec;
    res.unlock();

    if(addrSpec.size())
	((TMdPrm*)obj)->owner().setVal(vl, addrSpec, ((TMdPrm*)obj)->acqErr, true);
    else return TPrmTempl::Impl::lnkOutput(num, vl);

    return true;
}

void TMdPrm::TLogCtx::cleanLnks( bool andFunc )
{
    TPrmTempl::Impl::cleanLnks(andFunc);
    idFreq = idStart = idStop = idErr = idSh = idNm = idDscr = idThis = -1;
}

string TMdPrm::TLogCtx::lnkHelp( )
{
    return _("Special address format:\n"
	"ModBus address writes in the form \"{dt}:{numb}[:{flg}]\", where:\n"
	"  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 the suffixes:\n"
	"         i2-Int16, i4-Int32, i8-Int64, u2-UInt16, u4-UInt32, f-Float, d-Double, b5-Bit5, b-Bit in address, s[CHARSET]-String;\n"
	"  numb - ModBus data address of the device (dec, hex or octal) [0...65535];\n"
	"  flg - flags: read/write mode (r-read; w-write),\n"
	"               registers order inversion '~',\n"
	"               register 'e'ndian toggle (to LE in generic and BE for strings).\n"
	"Examples:\n"
	"  \"R:0x300:rw\" - register access;\n"
	"  \"C:100:rw\" - coil access;\n"
	"  \"R_f:200:r\", \"R_f:200:r~\" - get float from the registers 200 and 201, 201 and 200;\n"
	"  \"R_i4:400,300:r\" - get int32 from the registers 400 and 300;\n"
	"  \"R_b10:25:r\", \"R_b:25.10:r\" - get the bit 10 from the register 25;\n"
	"  \"R_s:15,20:r\" - get string (registers block) from the register 15 and the size 20.\n\n"
	"Common address format:\n") + TPrmTempl::Impl::lnkHelp();
}
