
//OpenSCADA module DAQ.ICP_DAS file: da_87x.cpp
/***************************************************************************
 *   Copyright (C) 2012-2025 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 <tsys.h>

#include "da_87x.h"
#include "ICP_module.h"

using namespace ICP_DAS_DAQ;

//*************************************************
//* da_87x                                        *
//*************************************************
da_87x::da_87x( )
{
    devs["I-87xxx"]	= DevFeature( 0 );
    devs["I-87005"]	= DevFeature( 0x0008,	0,	0,	0x0101 );
    devs["I-87013"]	= DevFeature( 0x0004 );
    devs["I-87015"]	= DevFeature( 0x0007 );
    devs["I-87016"]	= DevFeature( 0x0002 );
    devs["I-87017"]	= DevFeature( 0x0008 );
    devs["I-87017DW"]	= DevFeature( 0x0010 );
    devs["I-87017ZW"]	= DevFeature( 0x0014 );
    devs["I-87017ZW"].aiTypes = string("7;8;9;10;11;12;13;26\n")+
	_("4mA to 20mA;-10V to 10V;-5V to 5V;-1V to 1V;-500mV to 500mV;-150mV to 150mV;-20mA to 20mA;0mA to 20mA");
    devs["I-87018"]	= DevFeature( 0x0108 );
    devs["I-87018ZW"]	= DevFeature( 0x010A );
    devs["I-87019RW"]	= DevFeature( 0x0108 );
    devs["I-87019RW"].aiTypes = string("0;1;2;3;4;5;6;8;9;10;11;12;13;14;15;16;17;18;19;20;21;22;23;24;25\n")+
	_("-15mV to +15mV;-50mV to +50mV;-100mV to +100mV;-500mV to +500mV;-1V to +1V;-2.5V to +2.5V;"
	  "-20mA to +20mA (with 125 ohms resistor);-10V to +10V;-5V to +5V;-1V to +1V;-500mV to +500mV;"
	  "-150mV to +150mV;-20mA to +20mA (with 125 ohms resistor);J Type;K Type;T Type;E Type;R Type;"
	  "S Type;B Type;N Type;C Type;L Type;M Type;L Type (DIN43710C Type)");
    devs["I-87019ZW"]	= DevFeature( 0x010A);
    devs["I-87022"]	= DevFeature( 0,	2 );
    devs["I-87024"]	= DevFeature( 0,	4 );
    devs["I-87026"]	= DevFeature( 0,	2 );
    devs["I-87026PW"]	= DevFeature( 0x0006,	2,	0x0101,	0x0101 );
    devs["I-87026PW"].aiTypes = string("7;8;9;10;11;12;13;26\n")+
	_("4mA to 20mA;-10V to 10V;-5V to 5V;-1V to 1V;-500mV to 500mV;-150mV to 150mV;-20mA to 20mA;0mA to 20mA");
    devs["I-87028"]	= DevFeature( 0,	8 );
    devs["I-87037"]	= DevFeature( 0,	0,	0,	0x0002 );
    devs["I-87040"]	= DevFeature( 0,	0,	0x0004,	0,	32 );
    devs["I-87041"]	= DevFeature( 0,	0,	0,	0x0004 );
    devs["I-87042"]	= DevFeature( 0,	0,	0x0002,	0x0002 );
    devs["I-87046"]	= DevFeature( 0,	0,	0x0002,	0,	16 );
    devs["I-87051"]	= DevFeature( 0,	0,	0x0002,	0,	16 );
    devs["I-87052"]	= DevFeature( 0,	0,	0x0001,	0,	8 );
    devs["I-87053"]	= DevFeature( 0,	0,	0x0002,	0,	16 );
    devs["I-87054"]	= DevFeature( 0,	0,	0x0001,	0x0001,	8 );
    devs["I-87055"]	= DevFeature( 0,	0,	0x0001,	0x0001,	8 );
    devs["I-87057"]	= DevFeature( 0,	0,	0,	0x0002 );
    devs["I-87058"]	= DevFeature( 0,	0,	0x0001,	0,	8 );
    devs["I-87059"]	= DevFeature( 0,	0,	0x0001,	0,	8 );
    devs["I-87061"]	= DevFeature( 0,	0,	0,	0x0002 );
    devs["I-87063"]	= DevFeature( 0,	0,	0x0002,	0x0002,	16 );
    devs["I-87064"]	= DevFeature( 0,	0,	0,	0x0001 );
    devs["I-87065"]	= DevFeature( 0,	0,	0,	0x0001 );
    devs["I-87066"]	= DevFeature( 0,	0,	0,	0x0001 );
    devs["I-87068"]	= DevFeature( 0,	0,	0,	0x0001 );
    devs["I-87069"]	= DevFeature( 0,	0,	0,	0x0001 );
}

da_87x::~da_87x( )
{

}

void da_87x::tpList( TMdPrm *p, vector<string> &tpl, vector<string> *ntpl )
{
    //Only for I-8700 and I-7000 serial bus
    if(p->owner().bus() < 0)	return;
    for(map<string, DevFeature>::iterator i_d = devs.begin(); i_d != devs.end(); i_d++) {
	tpl.push_back(i_d->first);
	if(ntpl) ntpl->push_back(i_d->first);
    }
}

void da_87x::enable( TMdPrm *p, vector<string> &als )
{
    string chnId, chnNm;

    if(!p->extPrms) p->extPrms = new tval();
    tval *ePrm = (tval*)p->extPrms;
    ePrm->dev = getDev(p, p->modTp.getS());

    /*chnId = "modInfo"; chnNm = _("Module information");
    p->pEl.fldAdd(new TFld(chnId.c_str(),chnNm.c_str(),TFld::String,TFld::NoWrite)); als.push_back(chnId);*/

    //AI processing
    if(ePrm->dev.AI) {
	if((ePrm->dev.AI>>8) == 1) {
	    chnId = "cvct"; chnNm = _("Cold-Junction Compensation(CJC) temperature");
	    p->pEl.fldAdd(new TFld(chnId.c_str(),chnNm.c_str(),TFld::Real,TFld::NoWrite)); als.push_back(chnId);
	}
	for(int iI = 0; iI < (ePrm->dev.AI&0xFF); iI++) {
	    chnId = TSYS::strMess("ai%d",iI); chnNm = TSYS::strMess(_("Input %d"),iI);
	    p->pEl.fldAdd(new TFld(chnId.c_str(),chnNm.c_str(),TFld::Real,TFld::NoWrite)); als.push_back(chnId);
	    //p->pEl.fldAdd(new TFld(TSYS::strMess("ha%d",iI).c_str(),TSYS::strMess(_("H/A %d"),iI).c_str(),TFld::Boolean,TVal::DirWrite));
	    //als.push_back(TSYS::strMess("ha%d",iI));
	    //p->pEl.fldAdd(new TFld(TSYS::strMess("la%d",iI).c_str(),TSYS::strMess(_("L/A %d"),iI).c_str(),TFld::Boolean,TVal::DirWrite));
	    //als.push_back(TSYS::strMess("la%d",iI));
	}
    }

    //AO processing
    for(unsigned i_a = 0; i_a < ePrm->dev.AO; i_a++) {
	chnId = TSYS::strMess("ao%d",i_a); chnNm = TSYS::strMess(_("Out %d"),i_a);
	p->pEl.fldAdd(new TFld(chnId.c_str(),chnNm.c_str(),TFld::Real,TVal::DirWrite)); als.push_back(chnId);
    }

    //DI and DO processing
    for(unsigned iCh = 0; iCh < ((ePrm->dev.DI&0xFF)+(ePrm->dev.DO&0xFF)); iCh++) {
	// Reverse configuration load
	p->dInOutRev[iCh] = s2i(p->modPrm("dIORev"+i2s(iCh)));

	// Attributes create
	if(iCh < (ePrm->dev.DI&0xFF))
	    for(int iI = 0; iI < 8; iI++) {
		chnId = TSYS::strMess("di%d_%d",iCh,iI); chnNm = TSYS::strMess(_("Digital input %d.%d"),iCh,iI);
		p->pEl.fldAdd(new TFld(chnId.c_str(),chnNm.c_str(),TFld::Boolean,TFld::NoWrite)); als.push_back(chnId);
	    }
	else
	    for(int iO = 0; iO < 8; iO++) {
		chnId = TSYS::strMess("do%d_%d",iCh-(ePrm->dev.DI&0xFF),iO);
		chnNm = TSYS::strMess(_("Digital out %d.%d"),iCh-(ePrm->dev.DI&0xFF),iO);
		p->pEl.fldAdd(new TFld(chnId.c_str(),chnNm.c_str(),TFld::Boolean,TVal::DirWrite)); als.push_back(chnId);
	    }
    }

    //Counters processing
    for(unsigned iC = 0; iC < ePrm->dev.CNTR; iC++) {
	chnId = TSYS::strMess("cntr%d",iC); chnNm = TSYS::strMess(_("Counter %d"),iC);
	p->pEl.fldAdd(new TFld(chnId.c_str(),chnNm.c_str(),TFld::Real,TFld::NoWrite)); als.push_back(chnId);
    }

    //Watchdog timeout read
    p->wTm = s2r(p->modPrm("wTm","0"));
}

void da_87x::disable( TMdPrm *p )
{
    if(p->extPrms) {
	delete (tval *)p->extPrms;
	p->extPrms = NULL;
    }
}

void da_87x::getVal( TMdPrm *p )
{
    tval *ePrm = (tval*)p->extPrms;
    string rez = "1", tstr;
    bool CRC = s2i(p->modPrm("CRC","0"));

    //Get module info ($AAM, $AAF)
    /*if((tstr=p->vlAt("modInfo").at().getS(0, true)).empty() || tstr == EVAL_STR)
    {
	if(!rez.empty()) rez = p->owner().serReq(TSYS::strMess("~%02XM",(p->owner().bus()==0)?0:p->modAddr), p->modSlot, CRC);
	if(!rez.empty() && rez.size() > 3 && rez[0] == '!') tstr = rez.substr(3);
	if(!rez.empty()) rez = p->owner().serReq(TSYS::strMess("~%02XF",(p->owner().bus()==0)?0:p->modAddr), p->modSlot, CRC);
	if(!rez.empty() && rez.size() > 3) tstr += "("+rez.substr(3)+")";
	p->vlAt("modInfo").at().setS(rez.empty() ? EVAL_STR : tstr.c_str(), 0, true);
    }*/

    //Host watchdog processing (~AA3ETT)
    if((ePrm->dev.AO || (ePrm->dev.DO && (ePrm->dev.DO>>8)==0)) && p->wTm >= 0.1 && !rez.empty())
	rez = p->owner().serReq(TSYS::strMess("~%02X31%02X",(int)((p->owner().bus()==0)?0:p->modAddr),(int)(10*p->wTm)), p->modSlot, CRC);

    //AO back processing ($AA8N)
    for(unsigned iV = 0; iV < ePrm->dev.AO; iV++) {
	if(!rez.empty()) rez = p->owner().serReq(TSYS::strMess("$%02X8%d",(int)((p->owner().bus()==0)?0:p->modAddr),iV), p->modSlot, CRC);
	p->vlAt(TSYS::strMess("ao%d",iV)).at().setR((rez.size() != 10 || rez[0]!='!') ? EVAL_REAL : s2r(rez.data()+3), 0, true);
    }

    //AI processing
    if(ePrm->dev.AI) {
	// #AA
	if(!rez.empty()) rez = p->owner().serReq(TSYS::strMess("#%02X",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
	for(unsigned i_a = 0; i_a < (ePrm->dev.AI&0xFF); i_a++)
	    p->vlAt(TSYS::strMess("ai%d",i_a)).at().setR(((1+(i_a+1)*7) > rez.size() || rez[0] != '>') ? EVAL_REAL : s2r(rez.data()+1+7*i_a), 0, true);

	// $AA3
	if((ePrm->dev.AI>>8) == 1) {
	    if(!rez.empty()) rez = p->owner().serReq(TSYS::strMess("$%02X3",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
	    p->vlAt("cvct").at().setR((rez.size() != 8 || rez[0] != '>') ? EVAL_REAL : s2r(rez.data()+1), 0, true);
	}
    }

    //DI and DO back processing processing
    if(ePrm->dev.DI || ePrm->dev.DO) {
	unsigned DOsz = ePrm->dev.DO&0xFF, DIsz = ePrm->dev.DI&0xFF;
	bool isDOErr = false, isDIErr = false;
	uint32_t diVal;

	if(!rez.empty() && (ePrm->dev.DI>>8) == 0) {		//@AA
	    rez = p->owner().serReq(TSYS::strMess("@%02X",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
	    isDOErr = ((1+DOsz*2) > rez.size() || rez[0] != '>');
	    ePrm->doVal = isDOErr ? 0 : strtoul(rez.substr(1,DOsz*2).c_str(),NULL,16);
	    isDIErr = ((1+(DOsz+DIsz)*2) > rez.size() || rez[0] != '>');
	    diVal = isDIErr ? 0 : strtoul(rez.substr(1+DOsz*2,DIsz*2).c_str(),NULL,16);
	}
	else if(!rez.empty() && (ePrm->dev.DI>>8) == 1) {	//@AADI
	    rez = p->owner().serReq(TSYS::strMess("@%02XDI",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
	    isDOErr = ((3+DOsz*2) > rez.size() || rez[0] != '!');
	    ePrm->doVal = isDOErr ? 0 : strtoul(rez.substr(3,DOsz*2).c_str(),NULL,16);
	    isDIErr = ((3+(DOsz+DIsz)*2) > rez.size() || rez[0] != '!');
	    diVal = isDIErr ? 0 : strtoul(rez.substr(3+DOsz*2,DIsz*2).c_str(),NULL,16);
	}
	else rez = "";

	// DO
	for(unsigned iCh = 0; iCh < DOsz; iCh++)
	    for(unsigned i_d = 0; i_d < 8; i_d++)
		p->vlAt(TSYS::strMess("do%d_%d",iCh,i_d)).at().setB(isDOErr ? EVAL_BOOL :
		    (((ePrm->doVal>>(iCh*8))^p->dInOutRev[(ePrm->dev.DI&0xFF)+iCh])>>i_d)&1, 0, true);

	// DI
	for(unsigned iCh = 0; iCh < DIsz; iCh++)
	    for(unsigned i_d = 0; i_d < 8; i_d++)
		p->vlAt(TSYS::strMess("di%d_%d",iCh,i_d)).at().setB(isDIErr ? EVAL_BOOL :
		    (((diVal>>(iCh*8))^p->dInOutRev[iCh])>>i_d)&1, 0, true);
    }

    //Counters processing (#AAN)
    for(unsigned iC = 0; iC < ePrm->dev.CNTR; iC++) {
	if(!rez.empty()) rez = p->owner().serReq(TSYS::strMess("#%02X%d",(int)((p->owner().bus()==0)?0:p->modAddr),iC), p->modSlot, CRC);
	p->vlAt(TSYS::strMess("cntr%d",iC)).at().setR((rez.size() != 8 || rez[0] != '!') ? EVAL_INT : atoi(rez.data()+3), 0, true);
    }

    p->acqErr.setVal(rez.empty()?_("10:Request to module error."):"");
}

void da_87x::vlSet( TMdPrm *p, TVal &vo, const TVariant &vl, const TVariant &pvl )
{
    string rez;
    tval *ePrm = (tval*)p->extPrms;

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

    bool CRC = s2i(p->modPrm("CRC","0"));

    /*
    if(p->modTp.getS() == "I-87019")
    {
	bool ha = (vo.name().find("ha") == 0);
	bool la = (vo.name().find("la") == 0);
	if(!(ha||la)) return;

	//> Create previous value
	int hvl = 0, lvl = 0;
	for(int iV = 7; iV >= 0; iV--)
	{
	    hvl = hvl << 1;
	    lvl = lvl << 1;
	    if(p->vlAt(TSYS::strMess("ha%d",iV)).at().getB() == true)	hvl |= 1;
	    if(p->vlAt(TSYS::strMess("la%d",iV)).at().getB() == true)	lvl |= 1;
	}

	rez = p->owner().serReq(TSYS::strMess("@%02XL%02X%02X",(p->owner().bus()==0)?0:p->modAddr,lvl,hvl), p->modSlot, CRC);
	p->acqErr.setVal(rez.empty()?_("10:Request to module error."):"");
    }*/

    //AO processing, #AAN(Data)
    if(vo.name().find("ao") == 0 && ePrm->dev.AO) {
	string cmd = TSYS::strMess("#%02X%d%+07.3f",(int)((p->owner().bus()==0)?0:p->modAddr),atoi(vo.name().c_str()+2),vl.getR());

	repAO:
	rez = p->owner().serReq(cmd, p->modSlot, CRC);
	// Set watchdog flag is process
	if(!rez.empty() && rez[0] == '!') {
	    p->owner().serReq(TSYS::strMess("~%02X1",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
	    goto repAO;
	}
	vo.setR((rez.empty() || rez[0] != '>') ? EVAL_REAL : vl.getR(), 0, true);
	p->acqErr.setVal(rez.empty()?_("10:Request to module error."):"");
    }

    //DO processing
    if(vo.name().find("do") == 0 && ePrm->dev.DO) {
	uint32_t tvl = ePrm->doVal;
	int iCh = 0, iP = 0;
	if(sscanf(vo.name().c_str(),"do%d_%d",&iCh,&iP) != 2) return;
	if((int)vl.getB()^((p->dInOutRev[(ePrm->dev.DI&0xFF)+iCh]>>iP)&1))	tvl |= 1<<((iCh*8)+iP);
	else tvl &= ~(1<<((iCh*8)+iP));
	/*for(int iCh = (ePrm->dev.DO&0xFF)-1; iCh >= 0; iCh--)
	{
	    for(int iO = 7; iO >= 0; iO--)
	    {
		tvl = tvl << 1;
		if(p->vlAt(TSYS::strMess("do%d_%d",iCh,iO)).at().getB(0, true)) tvl |= 1;
	    }
	    tvl ^= p->dInOutRev[(ePrm->dev.DI&0xFF)+iCh];
	}*/

	string cmd;
	char rezOK = '>';
	if((ePrm->dev.DO>>8) == 0) {
	    cmd = TSYS::strMess(TSYS::strMess("@%%02X%%0%dX",(ePrm->dev.DO&0xFF)*2).c_str(),(int)((p->owner().bus()==0)?0:p->modAddr),tvl);
	    repDO:
	    rez = p->owner().serReq(cmd, p->modSlot, CRC);
	    // Set watchdog flag is process
	    if(!rez.empty() && rez[0] == '!') {
		p->owner().serReq(TSYS::strMess("~%02X1",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
		goto repDO;
	    }
	}
	else if((ePrm->dev.DO>>8) == 1) {
	    rezOK = '!';
	    cmd = TSYS::strMess(TSYS::strMess("@%%02XDO%%0%dX",(ePrm->dev.DO&0xFF)*2).c_str(),(int)((p->owner().bus()==0)?0:p->modAddr),tvl);
	    rez = p->owner().serReq(cmd, p->modSlot, CRC);
	}
	if(rez.size() && rez[0] == rezOK) ePrm->doVal = tvl;
	else vo.setB(EVAL_BOOL, 0, true);
	p->acqErr.setVal(rez.empty()?_("10:Request to module error."):((rez[0]!=rezOK)?_("11:Respond from module error."):""));
    }
}

da_87x::DevFeature da_87x::getDev( TMdPrm *p, const string &nm )
{
    int cntrSet = 0;
    DevFeature wdev = devs[nm];
    if(nm == "I-87xxx") {
	wdev.AI = (s2i(p->modPrm("modAItp","0"))<<8) | s2i(p->modPrm("modAI","0"));
	wdev.AO = s2i(p->modPrm("modAO","0"));
	wdev.DI = (s2i(p->modPrm("modDItp","0"))<<8) | s2i(p->modPrm("modDI","0"));
	wdev.DO = (s2i(p->modPrm("modDOtp","0"))<<8) | s2i(p->modPrm("modDO","0"));
	wdev.CNTR = s2i(p->modPrm("modCNTR","0"));
    }
    else {
	if(wdev.AO && (cntrSet=s2i(p->modPrm("modAO","-1"))) >= 0)	wdev.AO = vmin(cntrSet, wdev.AO);
	if(wdev.CNTR && (cntrSet=s2i(p->modPrm("modCNTR","-1"))) >= 0)	wdev.CNTR = vmin(cntrSet, wdev.CNTR);
    }

    return wdev;
}

bool da_87x::cntrCmdProc( TMdPrm *p, XMLNode *opt )
{
    string rez;
    DevFeature devOrig = devs[p->modTp.getS()];
    DevFeature dev = getDev(p, p->modTp.getS());
    tval *ePrm = (tval*)p->extPrms;
    bool CRC = s2i(p->modPrm("CRC","0"));

    if(opt->name() == "info") {
	p->ctrMkNode3("fld",opt,-1,"/prm/cfg/modCRC",_("CRC control"),SEC_RD|SEC_WR, "tp","bool");
	//Generic "I-87xxx" and AI CNTR channels processing limit set configuration
	if(p->modTp.getS() == "I-87xxx") {
	    p->ctrMkNode3("fld",opt,-1,"/prm/cfg/modAI",_("AI number"),SEC_RD|(p->enableStat()?0:SEC_WR),
		"tp","dec", "min","-1", "max","32");
	    if(!devOrig.AI) p->ctrMkNode3("fld",opt,-1,"/prm/cfg/modAItp","",SEC_RD|(p->enableStat()?0:SEC_WR),
		"tp","dec", "dest","select", "sel_id","0;1", "sel_list","#AA;$AA3,#AA");
	}
	if(p->modTp.getS() == "I-87xxx" || devOrig.AO)
	    p->ctrMkNode3("fld",opt,-1,"/prm/cfg/modAO",_("AO number, #AAN(Data)"),SEC_RD|(p->enableStat()?0:SEC_WR),
		"tp","dec", "min","-1", "max",devOrig.AO?i2s(devOrig.AO).c_str():"10");
	if(p->modTp.getS() == "I-87xxx") {
	    p->ctrMkNode3("fld",opt,-1,"/prm/cfg/modDI",_("DI number (ports x8)"),SEC_RD|(p->enableStat()?0:SEC_WR),
		"tp","dec", "min","-1", "max","4");
	    p->ctrMkNode3("fld",opt,-1,"/prm/cfg/modDItp","",SEC_RD|(p->enableStat()?0:SEC_WR),
		"tp","dec", "dest","select", "sel_id","0;1", "sel_list","@AA;@AADI");
	}
	if(p->modTp.getS() == "I-87xxx") {
	    p->ctrMkNode3("fld",opt,-1,"/prm/cfg/modDO",_("DO number (ports x8)"),SEC_RD|(p->enableStat()?0:SEC_WR),
		"tp","dec", "min","-1", "max","4");
	    p->ctrMkNode3("fld",opt,-1,"/prm/cfg/modDOtp","",SEC_RD|(p->enableStat()?0:SEC_WR),
		"tp","dec", "dest","select", "sel_id","0;1", "sel_list","@AA(Data);@AADODD");
	}
	if(p->modTp.getS() == "I-87xxx" || devOrig.CNTR)
	    p->ctrMkNode3("fld",opt,-1,"/prm/cfg/modCNTR",_("Counters number, #AAN"),SEC_RD|(p->enableStat()?0:SEC_WR),
		"tp","dec", "min","-1", "max",devOrig.CNTR?i2s(devOrig.CNTR).c_str():"32");

	//AI processing
	if(ePrm && ePrm->dev.AI && p->owner().startStat() && p->ctrMkNode3("area",opt,-1,"/cfg",_("Configuration"),SEC_RD))
	    for(int iV = 0; iV < (ePrm->dev.AI&0xFF); iV++) {
		XMLNode *tnd = p->ctrMkNode3("fld",opt,-1,TSYS::strMess("/cfg/inTp%d",iV).c_str(),TSYS::strMess(_("Input %d type"),iV).c_str(),SEC_RD|SEC_WR, "tp","dec");
		if(ePrm->dev.aiTypes.size()) tnd->setAttr("dest","select")->
		    setAttr("sel_id","-1;"+TSYS::strParse(ePrm->dev.aiTypes,0,"\n"))->
		    setAttr("sel_list",_("Error;")+TSYS::strParse(ePrm->dev.aiTypes,1,"\n"));
	    }
	//AO and watchdog processing
	if(ePrm && ePrm->dev.AO && p->ctrMkNode3("area",opt,-1,"/cfg",_("Configuration"),SEC_RD)) {
	    p->ctrMkNode3("fld",opt,-1,"/cfg/wTm",_("Host watchdog timeout, seconds"),SEC_RD|SEC_WR, "tp","real", "min","0", "max","25.5");
	    if(p->owner().startStat() && p->ctrMkNode3("area",opt,-1,"/cfg/mod",_("Module"),SEC_RD)) {
		p->ctrMkNode3("fld",opt,-1,"/cfg/mod/wSt",_("Host watchdog status"),SEC_RD, "tp","str");
		p->ctrMkNode3("fld",opt,-1,"/cfg/mod/vPon",_("Power on values"),SEC_RD, "tp","str");
		p->ctrMkNode3("comm",opt,-1,"/cfg/mod/vPonSet",_("Set power on values from current"),RWRW__);
		p->ctrMkNode3("fld",opt,-1,"/cfg/mod/vSf",_("Safe values"),SEC_RD, "tp","str");
		p->ctrMkNode3("comm",opt,-1,"/cfg/mod/vSfSet",_("Set safe values from current"),RWRW__);
	    }
	}
	//DI and DO processing
	if((dev.DI || dev.DO) && p->ctrMkNode3("area",opt,-1,"/cfg",_("Configuration"),SEC_RD)) {
	    for(unsigned iCh = 0; iCh < ((dev.DI&0xFF)+(dev.DO&0xFF)); iCh++)
		for(unsigned iN = 0; iN < 8; iN++)
		    p->ctrMkNode3("fld",opt,-1,TSYS::strMess("/cfg/nRevs%d_%d",iCh,iN).c_str(), (iN==0) ?
			((iCh < (dev.DI&0xFF)) ? TSYS::strMess(_("DI %d reverse"),iCh).c_str() :
						 TSYS::strMess(_("DO %d reverse"),iCh-(dev.DI&0xFF)).c_str()) : "",
			SEC_RD|(p->enableStat()?0:SEC_WR), "tp","bool");
	}
	//DO watchdog processing
	if(ePrm && ePrm->dev.DO && (ePrm->dev.DO>>8) == 0 && p->ctrMkNode3("area",opt,-1,"/cfg",_("Configuration"),SEC_RD)) {
	    p->ctrMkNode3("fld",opt,-1,"/cfg/wTm",_("Host watchdog timeout, seconds"),SEC_RD|SEC_WR, "tp","real", "min","0", "max","25.5");
	    if(p->owner().startStat() && p->ctrMkNode3("area",opt,-1,"/cfg/mod",_("Module"),SEC_RD)) {
		p->ctrMkNode3("fld",opt,-1,"/cfg/mod/wSt",_("Host watchdog status"),SEC_RD, "tp","str");
		p->ctrMkNode3("fld",opt,-1,"/cfg/mod/vPon",_("Power on values"),SEC_RD, "tp","str");
		p->ctrMkNode3("comm",opt,-1,"/cfg/mod/vPonSet",_("Set power on values from current"),RWRW__);
		p->ctrMkNode3("fld",opt,-1,"/cfg/mod/vSf",_("Safe values"),SEC_RD, "tp","str");
		p->ctrMkNode3("comm",opt,-1,"/cfg/mod/vSfSet",_("Set safe values from current"),RWRW__);
	    }
	}
	return true;
    }

    //Process command to page
    string a_path = opt->attr("path");
    bool isAI = ePrm && ePrm->dev.AI,
	 isAO = ePrm && ePrm->dev.AO,
	 isD = dev.DI || dev.DO,
	 isDO = isD && ePrm && ePrm->dev.DO && (ePrm->dev.DO>>8) == 0;
    // Generic "I-87xxx" and AI CNTR channels processing limit set configuration
    if(a_path == "/prm/cfg/modCRC") {
	if(p->ctrChkNode2(opt,"get",SEC_RD)) opt->setText(p->modPrm("CRC","0"));
	if(p->ctrChkNode2(opt,"set",SEC_WR)) p->setModPrm("CRC",opt->text());
    }
    else if(a_path.find("/prm/cfg/mod") == 0) {
	if(p->ctrChkNode2(opt,"get",SEC_RD))
	    opt->setText(p->modPrm(a_path.substr(9),(p->modTp.getS()=="I-87xxx")?"0":"-1"));
	if(p->ctrChkNode2(opt,"set",SEC_WR)) p->setModPrm(a_path.substr(9),opt->text());
    }
    // AI processing
    else if(isAI && p->owner().startStat() && a_path.find("/cfg/inTp") == 0) {
	if(p->ctrChkNode2(opt,"get",SEC_RD)) {	//$AA8Ci
	    rez = p->owner().serReq(TSYS::strMess("$%02X8C%01X",(int)((p->owner().bus()==0)?0:p->modAddr),s2i(a_path.substr(9))), p->modSlot, CRC);
	    opt->setText((rez.size()!=8||rez[0]!='!') ? "-1" : i2s(strtol(rez.data()+6,NULL,16)));
	}
	if(p->ctrChkNode2(opt,"set",SEC_WR))	//$AA7CiRrr
	    p->owner().serReq(TSYS::strMess("$%02X7C%dR%02X",(int)((p->owner().bus()==0)?0:p->modAddr),s2i(a_path.substr(9)),s2i(opt->text())), p->modSlot, CRC);
    }
    // AO and watchdog processing
    else if(isAO && a_path == "/cfg/wTm") {
	if(p->ctrChkNode2(opt,"get",SEC_RD)) opt->setText(r2s(p->wTm));
	if(p->ctrChkNode2(opt,"set",SEC_WR)) p->setModPrm("wTm",r2s(p->wTm = s2r(opt->text())));
    }
    else if(isAO && p->owner().startStat() && a_path == "/cfg/mod/wSt" && p->ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) {
	string wSt;

	//  ~AA0
	rez = p->owner().serReq(TSYS::strMess("~%02X0",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
	if(rez.size() == 5 && rez[0] == '!') {
	    wSt += (bool)strtol(rez.data()+3,NULL,16) ? _("Set. ") : _("Clear. ");

	    //  ~AA2
	    rez = p->owner().serReq(TSYS::strMess("~%02X2",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
	    if(rez.size() == 6 && rez[0] == '!') {
		wSt += (bool)strtol(string(rez.data()+3,1).c_str(),NULL,16) ? _("Enabled, ") : _("Disabled, ");
		wSt += r2s(0.1*strtol(rez.data()+4,NULL,16))+_(" s.");
	    }
	}
	opt->setText(wSt);
    }
    else if(isAO && p->owner().startStat() && a_path == "/cfg/mod/vPon" && p->ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) {
	string cnt;
	for(unsigned iC = 0; iC < dev.AO; iC++) {
	    //  $AA7N
	    rez = p->owner().serReq(TSYS::strMess("$%02X7%d",(int)((p->owner().bus()==0)?0:p->modAddr),iC), p->modSlot, CRC);
	    if(rez.size() != 10 || rez[0] != '!') { cnt = _("Error"); break; }
	    cnt = cnt + (rez.data()+3) + " ";
	}
	opt->setText(cnt);
    }
    else if(isAO && p->owner().startStat() && a_path == "/cfg/mod/vPonSet" && p->ctrChkNode3(opt,"set",RWRW__,SEC_WR))
	for(unsigned iC = 0; iC < dev.AO; iC++)	// $AA4N
	    p->owner().serReq(TSYS::strMess("$%02X4%d",(int)((p->owner().bus()==0)?0:p->modAddr),iC), p->modSlot, CRC);
    else if(isAO && p->owner().startStat() && a_path == "/cfg/mod/vSf" && p->ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) {
	string cnt;
	for(unsigned iC = 0; iC < dev.AO; iC++) {
	    //  ~AA4N
	    rez = p->owner().serReq(TSYS::strMess("~%02X4%d",(int)((p->owner().bus()==0)?0:p->modAddr),iC), p->modSlot, CRC);
	    if(rez.size() != 10 || rez[0] != '!') { cnt = _("Error"); break; }
	    cnt = cnt + (rez.data()+3) + " ";
	}
	opt->setText(cnt);
    }
    else if(isAO && p->owner().startStat() && a_path == "/cfg/mod/vSfSet" && p->ctrChkNode3(opt,"set",RWRW__,SEC_WR))
	for(unsigned iC = 0; iC < dev.AO; iC++)	// ~AA5N
	    p->owner().serReq(TSYS::strMess("~%02X5%d",(int)((p->owner().bus()==0)?0:p->modAddr),iC), p->modSlot, CRC);
    // DI and DO reverse processing
    else if(isD && a_path.find("/cfg/nRevs") == 0) {
	int iCh = 0, iN = 0;
	sscanf(a_path.c_str(), "/cfg/nRevs%d_%d", &iCh, &iN);
	int chVl = s2i(p->modPrm("dIORev"+i2s(iCh)));
	if(p->ctrChkNode2(opt,"get",SEC_RD)) opt->setText((chVl&(1<<iN))?"1":"0");
	if(p->ctrChkNode2(opt,"set",SEC_WR))
	    p->setModPrm("dIORev"+i2s(iCh), i2s(s2i(opt->text()) ? (chVl|(1<<iN)) : (chVl & ~(1<<iN))));
    }
    //  DO and watchdog processing
    else if(isDO && a_path == "/cfg/wTm") {
	if(p->ctrChkNode2(opt,"get",SEC_RD)) opt->setText(r2s(p->wTm));
	if(p->ctrChkNode2(opt,"set",SEC_WR)) p->setModPrm("wTm",r2s(p->wTm = s2r(opt->text())));
    }
    else if(isDO && p->owner().startStat() && a_path == "/cfg/mod/wSt" && p->ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) {
	string wSt;

	//   ~AA0
	rez = p->owner().serReq(TSYS::strMess("~%02X0",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
	if(rez.size() == 5 && rez[0] == '!') {
	    wSt += (bool)strtol(rez.data()+3,NULL,16) ? _("Set. ") : _("Clear. ");

	    //   ~AA2
	    rez = p->owner().serReq(TSYS::strMess("~%02X2",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
	    if(rez.size() == 6 && rez[0]=='!') {
		wSt += (bool)strtol(string(rez.data()+3,1).c_str(),NULL,16) ? _("Enabled, ") : _("Disabled, ");
		wSt += r2s(0.1*strtol(rez.data()+4,NULL,16))+_(" s.");
	    }
	}
	opt->setText(wSt);
    }
    else if(isDO && p->owner().startStat() && a_path == "/cfg/mod/vPon" && p->ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) {
	//   ~AA4P
	rez = p->owner().serReq(TSYS::strMess("~%02X4P",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
	if(rez.size() < 7 || rez[0] != '!') opt->setText(_("Error"));
	else {
	    string cnt;
	    uint32_t vl = strtol(rez.data()+3,NULL,16);
	    for(unsigned iO = 0; iO < (dev.DO&0xFF)*8; iO++) cnt += ((vl>>iO)&0x01)?"1 ":"0 ";
	    opt->setText(cnt);
	}
    }
    else if(isDO && p->owner().startStat() && a_path == "/cfg/mod/vPonSet" && p->ctrChkNode3(opt,"set",RWRW__,SEC_WR))
	//   ~AA5P
	p->owner().serReq(TSYS::strMess("~%02X5P",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
    else if(isDO && p->owner().startStat() && a_path == "/cfg/mod/vSf" && p->ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) {
	//   ~AA4S
	rez = p->owner().serReq(TSYS::strMess("~%02X4S",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
	if(rez.size() < 7 || rez[0] != '!') opt->setText(_("Error"));
	else {
	    string cnt;
	    int vl = strtol(rez.data()+3,NULL,16);
	    for(unsigned iO = 0; iO < (dev.DO&0xFF)*8; iO++) cnt += ((vl>>iO)&0x01)?"1 ":"0 ";
	    opt->setText(cnt);
	}
    }
    else if(isDO && p->owner().startStat() && a_path == "/cfg/mod/vSfSet" && p->ctrChkNode3(opt,"set",RWRW__,SEC_WR))
	//   ~AA5S
	p->owner().serReq(TSYS::strMess("~%02X5S",(int)((p->owner().bus()==0)?0:p->modAddr)), p->modSlot, CRC);
    else return false;

    return true;
}
