
//OpenSCADA module DAQ.SMH2Gi file: module.cpp
/***************************************************************************
 *   Copyright (C) 2012-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 <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>

#include <algorithm>

#include "shm.hpp"

#include <terror.h>
#include <tsys.h>
#include <tmess.h>
#include <ttypeparam.h>
#include <tdaqs.h>

#include "module.h"

//*************************************************
//* Modul info!                                   *
#define MOD_ID		"SMH2Gi"
#define MOD_NAME	trS("Segnetics SMH2Gi and SMH4")
#define MOD_TYPE	SDAQ_ID
#define VER_TYPE	SDAQ_VER
#define MOD_VER		"1.1.26"
#define AUTHORS		trS("Roman Savochenko")
#define DESCRIPTION	trS("Data acquisition and control by Segnetics SMH2Gi and SMH4 hardware interfaces and modules.")
#define LICENSE		"GPL2"
//*************************************************

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

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

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

using namespace SMH2Gi;

//*************************************************
//* SMH2Gi::TTpContr                              *
//*************************************************
TTpContr::TTpContr( string name ) : TTypeDAQ(MOD_ID), mMRCDirDevs(DEF_MRCDirDevs)
{
    mod = this;

    modInfoMainSet(MOD_NAME, MOD_TYPE, MOD_VER, AUTHORS, DESCRIPTION, LICENSE, name);
}

TTpContr::~TTpContr( )
{

}

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

    //Controler's bd structure
    fldAdd(new TFld("PRM_BD_SHM",trS("Shared memory parameters"),TFld::String,TFld::NoFlag,"30",""));	//????[v1.0] Remove
    fldAdd(new TFld("PRM_BD_MRC",trS("MR and MC bus parameters"),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("SHM_VARS",trS("Shared memory variables file"),TFld::String,TFld::NoFlag,"255","/projects/load_files.srv"));
    fldAdd(new TFld("MR_DEV",trS("MR bus device"),TFld::String,TFld::NoFlag,"50","/dev/mrext"));
    fldAdd(new TFld("MC_DEV",trS("MC bus device"),TFld::String,TFld::NoFlag,"50","/dev/mrint"));
    fldAdd(new TFld("MC_DEV_RGPIO",trS("MC device reset GPIO"),TFld::Integer,TFld::NoFlag,"3","94"));
    fldAdd(new TFld("REQ_TRY",trS("Request tries"),TFld::Integer,TFld::NoFlag,"1","1","1;9"));

    //Parameters' types add
    tpParmAdd(new SHMParam());
    tpParmAdd(new MRCParam());
}

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

void TTpContr::setMRCDirDevs( const string &vl )
{
    mMRCDirDevs = vl;
    modif();

    ResAlloc res(MRCdevsRes, true);

    //Clear previous
    MRCdevs.clear();

    //Scan directory for MR/MC devices *.ini files load
    DevMRCFeature devIni;
    string fn, f_data;
    dirent  *scan_rez = NULL,
	    *scan_dirent = (dirent*)malloc(offsetof(dirent,d_name) + NAME_MAX + 1);
    DIR *IdDir = opendir(mod->MRCDirDevs().c_str());
    while(IdDir && readdir_r(IdDir,scan_dirent,&scan_rez) == 0 && scan_rez) {
	fn = mod->MRCDirDevs()+"/"+scan_rez->d_name;
	if(scan_rez->d_type != DT_REG || fn.compare(fn.size()-4,4,".ini") != 0) continue;
	if(devIni.load(fn)) {
	    if(devIni.HardID) MRCdevs[devIni.HardID] = devIni;
	    else if(strcmp(scan_rez->d_name,"ain_tunes.ini") == 0) MRCdevs[-1] = devIni;
	}
    }
    if(IdDir) closedir(IdDir);
    free(scan_dirent);
}

void TTpContr::perSYSCall( unsigned int cnt )
{
    //Check for restart some conrollers need
    vector<string> cls, pls;
    list(cls);
    for(unsigned i_c = 0; i_c < cls.size(); i_c++) {
	AutoHD<TController> cntr = at(cls[i_c]);
	if(!cntr.at().startStat()) continue;
	cntr.at().list(pls);
	for(unsigned i_p = 0; i_p < pls.size(); i_p++) {
	    int errCode = cntr.at().at(pls[i_p]).at().vlAt("err").at().getI();
	    if(errCode == 21) {	//By MR/MC bus lose
		mess_warning(cntr.at().nodePath().c_str(), _("Re-enable by Reinit flag for MC/MR module."));
		cntr.at().disable();
		cntr.at().start();
		break;
	    }
	}
    }
}

void TTpContr::load_( )
{
    //Load parameters from config-file
    setMRCDirDevs(TBDS::genPrmGet(nodePath()+"MRCDirDevs",DEF_MRCDirDevs));
}

void TTpContr::save_( )
{
    //Save parameters to config-file
    TBDS::genPrmSet(nodePath()+"MRCDirDevs",MRCDirDevs());
}

void TTpContr::cntrCmdProc( XMLNode *opt )
{
    //Get page info
    if(opt->name() == "info") {
	TTypeDAQ::cntrCmdProc(opt);
	if(ctrMkNode3("area",opt,0,"/prm","SMH2Gi",SEC_RD))
	    ctrMkNode3("fld",opt,-1,"/prm/dirMRC",_("MR/MC devices *.ini files directry"),SEC_RD|SEC_WR,
		"tp","str", "dest","sel_ed", "select","/prm/dirMRCList");
	return;
    }
    //Process command to page
    string a_path = opt->attr("path");
    if(a_path == "/prm/dirMRC") {
	if(ctrChkNode2(opt,"get",SEC_RD)) opt->setText(MRCDirDevs());
	if(ctrChkNode2(opt,"set",SEC_WR)) setMRCDirDevs(opt->text());
    }
    else if(a_path == "/prm/dirMRCList" && ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) TSYS::ctrListFS(opt, MRCDirDevs());
    else TTypeDAQ::cntrCmdProc(opt);
}

//*************************************************
//* SMH2Gi::TMdContr                              *
//*************************************************
TMdContr::TMdContr( string name_c, const string &daq_db, ::TElem *cfgelem) :
    ::TController(name_c,daq_db,cfgelem), smv(NULL), m_prior(cfg("PRIOR").getId()), connTry(cfg("REQ_TRY").getId()),
    mPer(1e9), prcSt(false), callSt(false), endrunReq(false), tmGath(0)
{
    //????[v1.0] Remove
    cfg("PRM_BD_SHM").setS("SMH2GiPrmSHM_"+name_c);
    cfg("PRM_BD_MRC").setS("SMH2GiPrmMRC_"+name_c);
}

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

string TMdContr::getStatus( )
{
    string rez = TController::getStatus( );
    if(startStat() && !redntUse()) {
	if(callSt)	rez += TSYS::strMess(_("Acquisition. "));
	if(period())	rez += TSYS::strMess(_("Acquisition with the period %s. "), tm2s(1e-9*period()).c_str());
	else rez += TSYS::strMess(_("Next acquisition by the cron '%s'. "), atm2s(TSYS::cron(cron()),"%d-%m-%Y %R").c_str());
	rez += TSYS::strMess(_("Spent time %s."), tm2s(1e-6*tmGath).c_str());
    }
    return rez;
}

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

void TMdContr::enable_( )
{
    //Shared memory object init
    if(!smv) smv = new Shm(cfg("SHM_VARS").getS().c_str());

    //MC bus init by send reset to GPIO PC30
    if(cfg("MC_DEV_RGPIO").getI() >= 0) {
	int hd = open(("/sys/class/gpio/gpio"+cfg("MC_DEV_RGPIO").getS()+"/value").c_str(), O_WRONLY);
	if(hd < 0) throw TError(nodePath().c_str(), _("Error opening GPIO %s for MC reset."), cfg("MC_DEV_RGPIO").getS().c_str());
	lseek(hd, 0, SEEK_SET);
	if(write(hd,"1",1) != 1) mess_err(nodePath().c_str(), _("Write to gpio94 error!"));
	TSYS::sysSleep(50e-3);	//50ms

	lseek(hd, 0, SEEK_SET);
	if(write(hd,"0",1) != 1) mess_err(nodePath().c_str(), _("Write to gpio94 error!"));
	TSYS::sysSleep(200e-3);	//200ms

	if(close(hd) != 0)
	    mess_warning(nodePath().c_str(), _("Closing the file %d error '%s (%d)'!"), hd, strerror(errno), errno);
    }

    //MR bus init by send 250*0xCC symbols
    string req(250, 0xCC);
    modBusReq(req);
    TSYS::sysSleep(60e-3);
}

void TMdContr::disable_( )
{
    //Shared memory object free
    if(smv) { delete smv; smv = NULL; }
}

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

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

    //Set EVal
    ResAlloc res(en_res,false);
    for(unsigned i_p = 0; i_p < p_hd.size(); i_p++)
	p_hd[i_p].at().setEval();
}

void TMdContr::prmEn( const string &id, bool val )
{
    unsigned i_prm;

    ResAlloc res(en_res,true);
    for(i_prm = 0; i_prm < p_hd.size(); i_prm++)
	if(p_hd[i_prm].at().id() == id) break;

    if(val && i_prm >= p_hd.size())	p_hd.push_back(at(id));
    if(!val && i_prm < p_hd.size())	p_hd.erase(p_hd.begin()+i_prm);
}

string TMdContr::modBusReq( string &pdu, bool MC, bool broadCast )
{
    char buf[1024];
    string mbap, rez, err;

    AutoHD<TTransportOut> tro = SYS->transport().at().nodeAt(MC?"Serial.out_SMH2Gi_MC":"Serial.out_SMH2Gi_MR", 0, '.', 0, true);

    try {
	//Transport creation for MC and MR busses and connect
	if(tro.freeStat()) {
	    SYS->transport().at().at("Serial").at().outAdd(MC?"SMH2Gi_MC":"SMH2Gi_MR");
	    tro = SYS->transport().at().nodeAt(MC?"Serial.out_SMH2Gi_MC":"Serial.out_SMH2Gi_MR", 0, '.', 0, true);
	    // Typical parameters set
	    tro.at().setDscr(TSYS::strMess(_("Segnetics SMH2Gi automatic created transport for '%s' bus"),MC?"MC":"MR"));
	    tro.at().setAddr(MC?(cfg("MC_DEV").getS()+":230400:8N1"):(cfg("MR_DEV").getS()+":230400:8N2"));
	}

	MtxAlloc resN(tro.at().reqRes(), true);

	//Start stoped transport
	if(!tro.at().startStat()) tro.at().start();

	//Prepare request
	mbap.reserve(pdu.size()+2);
	mbap += pdu;
	uint16_t crc = CRC16(mbap);
	mbap += (crc>>8);
	mbap += crc;

	//Send request
	for(int iTr = 0; iTr < connTry; iTr++) {
	    if(messLev() == TMess::Debug)
		mess_debug_(nodePath().c_str(), _("ModBUS REQ -> '%s': %s"), tro.at().id().c_str(), TSYS::strDecode(mbap,TSYS::Bin," ").c_str());
	    int resp_len = tro.at().messIO(mbap.data(), mbap.size(), (broadCast?NULL:buf), sizeof(buf));
	    if(broadCast) { err = ""; break; }
	    rez.assign(buf, resp_len);
	    //Wait tail
	    while(resp_len) {
		try{ resp_len = tro.at().messIO(NULL, 0, buf, sizeof(buf)); } catch(TError &err) { break; }
		rez.append(buf, resp_len);
	    }

	    if(rez.size() < 2) { err = TSYS::strMess(_("13:Error respond: Too short: %s."), TSYS::strDecode(rez,TSYS::Bin," ").c_str()); continue; }
	    if(CRC16(rez.substr(0,rez.size()-2)) != (uint16_t)((rez[rez.size()-2]<<8)+(uint8_t)rez[rez.size()-1]))
	    { err = TSYS::strMess(_("13:Error respond: CRC error: %s."), TSYS::strDecode(rez,TSYS::Bin," ").c_str()); continue; }
	    if(messLev() == TMess::Debug)
		mess_debug_(nodePath().c_str(), _("ModBUS RESP -> '%s': %s"), tro.at().id().c_str(), TSYS::strDecode(rez,TSYS::Bin," ").c_str());
	    pdu = rez.substr(0, rez.size()-2);
	    err = "";
	    break;
	}
    } catch(TError &ierr) { err = "10:"+ierr.mess; }

    if(messLev() >= TMess::Error && err.size()) mess_err(nodePath().c_str(), "%s", err.c_str());
    if(messLev() == TMess::Debug && err.size())
	mess_debug_(nodePath().c_str(), _("ModBUS ERR -> '%s': %s"), (tro.freeStat()?"<ERR>":tro.at().id().c_str()), err.c_str());

    return err;
}

void *TMdContr::Task( void *icntr )
{
    TMdContr &cntr = *(TMdContr *)icntr;

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

    while(!cntr.endrunReq) {
	int64_t t_cnt = TSYS::curTime();

	//Update controller's data
	cntr.en_res.resRequestR( );
	cntr.callSt = true;

	// MR frame prepare
	if(cntr.MRWrFrm.size() != 8) {
	    cntr.MRWrFrm.clear();
	    for(int i_n = 0; i_n < 8; i_n++)
		cntr.MRWrFrm += (char)8;
	}

	// Call for get vals
	for(unsigned i_p = 0; i_p < cntr.p_hd.size() && !cntr.redntUse(); i_p++)
	    try { cntr.p_hd[i_p].at().getVals(); }
	    catch(TError &err) { mess_err(err.cat.c_str(),"%s",err.mess.c_str()); }

	// Send generic MR write frame
	if(cntr.MRWrFrm.size() != 8) {
	    string pdu;
	    // Swap registers
	    for(int i_p = 0; i_p < (int)cntr.MRWrFrm.size()-1; i_p += 2)
	    { char t_sw = cntr.MRWrFrm[i_p]; cntr.MRWrFrm[i_p] = cntr.MRWrFrm[i_p+1]; cntr.MRWrFrm[i_p+1] = t_sw; }

	    // Prepare header
	    pdu = (char)0xFE;				//BroadCast
	    pdu += (char)0x10;				//Function, preset multiple registers
	    pdu += (char)0;
	    pdu += (char)0;
	    pdu += (char)((cntr.MRWrFrm.size()/2)>>8);	//Registers quantity MSB
	    pdu += (char)(cntr.MRWrFrm.size()/2);	//Registers quantity LSB
	    pdu += (char)cntr.MRWrFrm.size();		//Byte Count
	    pdu += cntr.MRWrFrm;
	    cntr.modBusReq(pdu, false, true);
	}

	cntr.callSt = false;
	cntr.en_res.resRelease( );
	cntr.tmGath = TSYS::curTime()-t_cnt;

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

    cntr.prcSt = false;

    return NULL;
}

void TMdContr::cntrCmdProc( XMLNode *opt )
{
    //Get page info
    if(opt->name() == "info") {
	TController::cntrCmdProc(opt);
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/SCHEDULE",EVAL_STR,SEC_RD|(startStat()?0: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/SHM_VARS",EVAL_STR,SEC_RD|(enableStat()?0:SEC_WR), "dest","sel_ed", "select","/cntr/cfg/fileShmL");
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/MC_DEV",EVAL_STR,SEC_RD|(enableStat()?0:SEC_WR), "dest","sel_ed", "select","/cntr/cfg/devMCLs");
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/MR_DEV",EVAL_STR,SEC_RD|(enableStat()?0:SEC_WR), "dest","sel_ed", "select","/cntr/cfg/devMRLs");
	ctrMkNode3("fld",opt,-1,"/cntr/cfg/MC_DEV_RGPIO",EVAL_STR,SEC_RD|(enableStat()?0:SEC_WR), "help",_("Set <0 for disable the reset."));
	return;
    }

    //Process command to page
    string a_path = opt->attr("path");
    if(a_path == "/cntr/cfg/fileShmL" && ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) TSYS::ctrListFS(opt, cfg("SHM_VARS").getS(), "srv;");
    else if(a_path == "/cntr/cfg/devMCLs" && ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) TSYS::ctrListFS(opt, cfg("MC_DEV").getS(), "<chrdev>;");
    else if(a_path == "/cntr/cfg/devMRLs" && ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) TSYS::ctrListFS(opt, cfg("MR_DEV").getS(), "<chrdev>;");
    else TController::cntrCmdProc(opt);
}

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

    if(co.fld().name() == "SCHEDULE")
	mPer = TSYS::strSepParse(cron(),1,' ').empty() ? vmax(0,(int64_t)(1e9*s2r(cron()))) : 0;

    return true;
}

uint8_t TMdContr::CRCHi[] =
{
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};

uint8_t TMdContr::CRCLo[] =
{
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04,
    0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8,
    0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
    0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10,
    0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
    0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
    0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,
    0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0,
    0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
    0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
    0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C,
    0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
    0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54,
    0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,
    0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};

uint16_t TMdContr::CRC16( const string &mbap )
{
    uint8_t hi = 0xFF;
    uint8_t lo = 0xFF;
    uint16_t index;
    for(unsigned i_b = 0; i_b < mbap.size(); i_b++) {
	index = lo^(uint8_t)mbap[i_b];
	lo = hi^CRCHi[index];
	hi = CRCLo[index];
    }
    return hi|(lo<<8);
}

//*************************************************
//* SMH2Gi::TMdPrm                                *
//*************************************************
TMdPrm::TMdPrm( string name, TTypeParam *tp_prm ) :
    TParamContr(name,tp_prm), p_el("w_attr"), extPrms(NULL)
{

}

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

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

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

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

    als.clear();
    TParamContr::enable();

    //Check for delete DAQ parameter's attributes
    for(int i_p = 0; i_p < (int)p_el.fldSize(); i_p++) {
	unsigned i_l;
	for(i_l = 0; i_l < als.size(); i_l++)
	    if(p_el.fldAt(i_p).name() == als[i_l])
		break;
	if(i_l >= als.size())
	    try{ p_el.fldDel(i_p); i_p--; }
	    catch(TError &err){ mess_warning(err.cat.c_str(),err.mess.c_str()); }
    }
    als.clear();

    owner().prmEn(id(), true);
}

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

    owner().prmEn(id(), false);

    TParamContr::disable();

    setEval();
}

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(acq_err.getVal().empty())	val.setS("0",0,true);
	else				val.setS(acq_err.getVal(),0,true);
    }
}

void TMdPrm::getVals( )
{
    //Values gather process
    type().getVals(this);
}

void TMdPrm::setEval( )
{
    vector<string> ls;
    elem().fldList(ls);
    for(unsigned i_el = 0; i_el < ls.size(); i_el++)
	vlAt(ls[i_el]).at().setS(EVAL_STR, 0, true);
}

string TMdPrm::modPrm( const string &prm, const string &def )
{
    string rez;
    XMLNode prmNd;
    try {
	prmNd.load(cfg("MOD_PRMS").getS());
	string sobj = TSYS::strParse(prm,0,":"), sa = TSYS::strParse(prm,1,":");
	if(!sa.size())  return (rez=prmNd.attr(prm)).empty()?def:rez;
	//Internal node
	for(unsigned i_n = 0; i_n < prmNd.childSize(); i_n++)
	    if(prmNd.childGet(i_n)->name() == sobj)
		return (rez=prmNd.childGet(i_n)->attr(sa)).empty()?def:rez;
    } catch(...) { }

    return def;
}

void TMdPrm::setModPrm( const string &prm, const string &val )
{
    XMLNode prmNd("ModCfg");
    try { prmNd.load(cfg("MOD_PRMS").getS()); } catch(...) { }

    if(modPrm(prm) != val) modif();
    string sobj = TSYS::strParse(prm,0,":"), sa = TSYS::strParse(prm,1,":");
    if(!sa.size()) prmNd.setAttr(prm,val);
    //Internal node
    else {
	unsigned i_n;
	for(i_n = 0; i_n < prmNd.childSize(); i_n++)
	    if(prmNd.childGet(i_n)->name() == sobj)
	    { prmNd.childGet(i_n)->setAttr(sa,val); break; }
	if(i_n >= prmNd.childSize())
	    prmNd.childAdd(sobj)->setAttr(sa,val);
    }

    cfg("MOD_PRMS").setS(prmNd.save(XMLNode::BrAllPast));
}

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

    if(val.arch().freeStat()) return;
    val.arch().at().setSrcMode(TVArchive::SRC_DAQAttr);
    val.arch().at().setPeriod((int64_t)(owner().period()*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);
	ctrRemoveNode(opt,"/prm/cfg/MOD_PRMS");
	return;
    }
    //Process command to page
    //string a_path = opt->attr("path");
    TParamContr::cntrCmdProc(opt);
}

//****************************************************
//* SMH2Gi::SHMParam - shared memory parameter type  *
//****************************************************
SHMParam::SHMParam( ) : TTypeParam("PrmSHM", _("Shared memory parameters"), "PRM_BD_SHM")
{
    fldAdd(new TFld("VAR_LS",trS("Variables list"),TFld::String,TFld::FullText|TCfg::NoVal,"100000",""));
}

void SHMParam::enable( TParamContr *ip )
{
    TMdPrm *p = (TMdPrm *)ip;

    //Read, check and create attributes from variables list
    string sid, sel;
    for(int ioff = 0; (sel=TSYS::strParse(p->cfg("VAR_LS").getS(),0,"\n",&ioff)).size(); ) {
	sid = TSYS::strEncode(sel, TSYS::oscdID);
	int vtp = p->owner().smv->getType(sel.c_str());
	if(vtp < 0) continue;
	TFld::Type tp = (TFld::Type)-1;
	switch(vtp) {
	    case BOOL:	tp = TFld::Boolean;	break;
	    case SHORT:
	    case LONG:	tp = TFld::Integer;	break;
	    case FLOAT:	tp = TFld::Real;	break;
	}
	p->p_el.fldAdd(new TFld(sid.c_str(),sel.c_str(),tp,TVal::DirWrite,"","","","",i2s(vtp).c_str()));
	p->als.push_back(sid);
    }
}

void SHMParam::getVals( TParamContr *ip )
{
    TMdPrm *p = (TMdPrm *)ip;

    string tnm, tvar, vals;
    for(int i_p = 0; i_p < (int)p->p_el.fldSize(); i_p++) {
	tnm = p->p_el.fldAt(i_p).name();
	AutoHD<TVal> vl = p->vlAt(tnm);
	tvar = p->p_el.fldAt(i_p).descr();
	switch(s2i(p->p_el.fldAt(i_p).reserve())) {
	    case BOOL: {
		char rvl = p->owner().smv->getBool(tvar.c_str());
		vl.at().setB((rvl==-1)?EVAL_BOOL:rvl, 0, true);
		break;
	    }
	    case SHORT:	vl.at().setI(p->owner().smv->getShort(tvar.c_str()), 0, true);	break;
	    case LONG:	vl.at().setI(p->owner().smv->getLong(tvar.c_str()), 0, true);	break;
	    case FLOAT:	vl.at().setR(p->owner().smv->getFloat(tvar.c_str()), 0, true);	break;
	}
	if(ip->owner().messLev() == TMess::Debug) vals += tvar+"='"+vl.at().getS(0,true)+"'; ";
    }
    if(ip->owner().messLev() == TMess::Debug) mess_debug_(ip->nodePath().c_str(), _("SHM Get vals: %s"), vals.c_str());
}

void SHMParam::vlSet( TParamContr *ip, TVal &vo, const TVariant &vl, const TVariant &pvl )
{
    TMdPrm *p = (TMdPrm *)ip;

    if(!p->enableStat() || !p->owner().startStat())	{ vo.setS(EVAL_STR, 0, true); return; }
    if(vl.isEVal() || vl == pvl) return;
    int rez = -2;
    string tvar = vo.fld().descr();
    switch(s2i(vo.fld().reserve())) {
	case BOOL:	rez = p->owner().smv->setBool(tvar.c_str(),vl.getB());	break;
	case SHORT:	rez = p->owner().smv->setShort(tvar.c_str(),vl.getI());	break;
	case LONG:	rez = p->owner().smv->setLong(tvar.c_str(),vl.getI());	break;
	case FLOAT:	rez = p->owner().smv->setFloat(tvar.c_str(),vl.getR());	break;
    }
    if(rez < 0)	mess_err(ip->nodePath().c_str(), _("SHM Set value '%s' to '%s' error."), tvar.c_str(), vl.getS().c_str());
    if(ip->owner().messLev() == TMess::Debug)
	mess_debug_(ip->nodePath().c_str(), _("SHM Set val: %s='%s': %d"), tvar.c_str(), vl.getS().c_str(), rez);
}

bool SHMParam::cntrCmdProc( TParamContr *p, XMLNode *opt )
{
    //Get page info
    if(opt->name() == "info") {
	p->ctrMkNode3("fld",opt,-1,"/prm/cfg/VAR_LS",EVAL_STR,SEC_RD|(p->enableStat()?0:SEC_WR));
	return true;
    }
    //Process command to page
    //string a_path = opt->attr("path");
    return false;
}

//****************************************************
//* SMH2Gi::MRCParam - MR and MC bus parameters      *
//****************************************************
struct Inquired_t
{
    union {
	struct {				//IDs
	    unsigned short	hard:10;
	    unsigned short	soft:6;
	} ID;					//used on parameters request
	struct {				// MC alarm codes
	    unsigned short	Reserved1:3;
	    unsigned short	NoTunes:1;	// No tunes get
	    unsigned short	AOutOverload:1;	// Analog output overvoltage
	    unsigned short	Reserved2:11;
	} AlarmsMC;
	struct {				// MR alarm codes
	    unsigned short	Reserved1:3;
	    unsigned short	HighVoltage:1;	// High voltage
	    unsigned short	LowVoltage:1;	// Low voltage
	    unsigned short	AOutOverload:1;	// Analog output overvoltage
	    unsigned short	PSOverload:1;	// Internal power supply overvoltage
	    unsigned short	NoTunes:1;	// No tunes get
	    unsigned short	UnknownType:1;	// Не определен тип МР
	    unsigned short	Reserved2:7;
	} AlarmsMR;				//used on normal processing
    } proc;
    unsigned long	SerialNum:24;		// unique serial number
    unsigned long	Reinit:1;		// МР need reinit
    unsigned long	MCAinsValid:1;		// Values ain0...ain7 valid
    unsigned long	Reserved3:5;		// reserve
    unsigned long	AlarmsInID:1;		// Alarms in the struct Alarms
}__attribute__((packed));

MRCParam::MRCParam( ) : TTypeParam("PrmMRC", _("MR and MC bus parameters"), "PRM_BD_MRC")
{
    fldAdd(new TFld("MOD_TP",trS("Module type"),TFld::Integer,TCfg::NoVal,"10","0"));
    fldAdd(new TFld("MOD_SLOT",trS("Module slot/address"),TFld::Integer,TCfg::NoVal,"2","-1","-1;7"));
    fldAdd(new TFld("MOD_PRMS",trS("Module addition parameters"),TFld::String,TFld::FullText|TCfg::NoVal,"100000"));
}

void MRCParam::enable( TParamContr *ip )
{
    TMdPrm *p = (TMdPrm *)ip;
    if(!p->extPrms) p->extPrms = new tval();
    tval *ePrm = (tval*)p->extPrms;
    ResAlloc res(mod->MRCdevsRes, false);
    ePrm->dev = mod->MRCdevs[p->cfg("MOD_TP").getI()];
    res.release();
    try {
	if(!ePrm->dev.HardID)
	    throw TError(p->nodePath().c_str(),TSYS::strMess(_("No selected any module type or type '%d' error."),p->cfg("MOD_TP").getI()).c_str());

	int modSlot = p->cfg("MOD_SLOT").getI();
	string pdu, rezReq;

	//Prepare and send tune request
	sendTune(ip);

	//Value's attributes prepare
	ePrm->DI = ePrm->DO = ePrm->AO = ePrm->CNTR = 0;
	for(map<string, DevMRCFeature::SVal>::iterator itv = ePrm->dev.vars.begin(); itv != ePrm->dev.vars.end(); itv++) {
	    string p_id = TSYS::TSYS::strEncode(itv->first,TSYS::oscdID);
	    string p_name = itv->second.descr.size() ? itv->second.descr : itv->first;
	    p->p_el.fldAdd(new TFld(p_id.c_str(),p_name.c_str(),itv->second.tp,itv->second.wr?TFld::NoFlag:TFld::NoWrite));
	    p->als.push_back(p_id);
	    // Standard types count for MR acquisition unification
	    if(modSlot < 0) continue;
	    if(itv->first.compare(0,3,"aou") == 0) ePrm->AO = vmax(ePrm->AO, atoi(itv->first.c_str()+3)+1);
	    else if(itv->first.compare(0,3,"din") == 0) ePrm->DI = vmax(ePrm->DI, atoi(itv->first.c_str()+3)+1);
	    else if(itv->first.compare(0,3,"dou") == 0) ePrm->DO = vmax(ePrm->DO, atoi(itv->first.c_str()+3)+1);
	    else if(itv->first.compare(0,4,"cntr") == 0) ePrm->CNTR = vmax(ePrm->CNTR, atoi(itv->first.c_str()+4));
	}
	ePrm->diRev = s2i(p->modPrm("DIRev"));
	ePrm->doRev = s2i(p->modPrm("DORev"));

	//Request HardID, SoftID, SN
	// Calculate request block size
	int bSz = sizeof(Inquired_t);
	if(modSlot >= 0) bSz += ePrm->CNTR*4 + (ePrm->DI?2:0);
	pdu = (char)max(0,modSlot);	//Slave address
	pdu += (char)0x03;		//Function, request multiple registers
	pdu += (char)0x00;		//Start address, MSB
	pdu += (char)0x00;		//Start address, LSB
	pdu += (char)((bSz/2)>>8);	//Registers quantity MSB
	pdu += (char)(bSz/2);		//Registers quantity LSB

	rezReq = p->owner().modBusReq(pdu, (modSlot<0));
	if(rezReq.size() || pdu.size() < (3+sizeof(Inquired_t)))
	    throw TError(p->nodePath().c_str(),_("Parameters request error: %s."),rezReq.c_str());
	pdu.erase(0,3);
	//Swap registers
	for(int i_p = 0; i_p < (int)pdu.size()-1; i_p += 2)
	{ char t_sw = pdu[i_p]; pdu[i_p] = pdu[i_p+1]; pdu[i_p+1] = t_sw; }
	Inquired_t *resp = (Inquired_t*)pdu.data();
	//Compare with configured module type
	if(resp->proc.ID.hard != ePrm->dev.HardID)
	    throw TError(p->nodePath().c_str(),_("Configure device %d but set %d."),ePrm->dev.HardID,resp->proc.ID.hard);

	//Save serial number for next process
	ePrm->SoftID = resp->proc.ID.soft;
	ePrm->SN = resp->SerialNum;

    } catch(TError &err) {
	if(p->extPrms)	{ delete (tval*)p->extPrms; p->extPrms = NULL; }
	throw;
    }
}

void MRCParam::sendTune( TParamContr *ip )
{
    TMdPrm *p = (TMdPrm *)ip;
    tval *ePrm = (tval*)p->extPrms;
    int modSlot = p->cfg("MOD_SLOT").getI();
    string pdu, tune, rezReq;

    for(int iT = 0; true; iT++) {
	string tits = TSYS::strMess("tune%d",iT);
	map<string, string>::iterator tit = ePrm->dev.sects["common"].find(tits);
	if(tit == ePrm->dev.sects["common"].end()) break;
	int regVal = s2i(p->modPrm(tits,TSYS::strParse(tit->second,5,"#")));
	tune += (char)(regVal>>8);	//MSB
	tune += (char)regVal;		//LSB
    }
    pdu = (char)max(0,modSlot);		//Slave address
    pdu += (char)0x10;			//Function, preset multiple registers
    pdu += (char)0x00;			//Start address, MSB
    pdu += (char)0x00;			//Start address, LSB
    pdu += (char)((tune.size()/2)>>8);	//Registers quantity MSB
    pdu += (char)(tune.size()/2);	//Registers quantity LSB
    pdu += (char)tune.size();		//Byte Count
    pdu += tune;

    rezReq = p->owner().modBusReq(pdu, (modSlot<0));
    if(rezReq.size()) throw TError(p->nodePath().c_str(),_("Send tune request error: %s."),rezReq.c_str());
}

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

void MRCParam::getVals( TParamContr *ip )
{
    string pdu, pduReq, data, rezReq;
    TMdPrm *p = (TMdPrm *)ip;
    tval *ePrm = (tval*)p->extPrms;
    int modSlot = p->cfg("MOD_SLOT").getI();

    //ID and SN attributes set
    if(p->vlPresent("id")) p->vlAt("id").at().setI(ePrm->dev.HardID, 0, true);
    if(p->vlPresent("sn")) p->vlAt("sn").at().setI(ePrm->SN, 0, true);

    if(modSlot < 0) {	//MC process
	// Read inputs
	pduReq = (char)ePrm->SN;	//SN[0]
	pduReq += (char)0x03;		//Function, read multiple registers
	pduReq += (char)(ePrm->SN>>16);	//SN[2]
	pduReq += (char)(ePrm->SN>>8);	//SN[1]
	pduReq += (char)0x00;		//Registers quantity MSB
	pduReq += (char)0x20;		//Registers quantity LSB, MC structure size 32 registers

	rezReq = p->owner().modBusReq(pduReq, (modSlot<0));
	if(rezReq.size() || pduReq.size() < 35) {
	    pduReq.resize(35);
	    if(rezReq.empty())	rezReq = _("20:PDU short.");
	}
	//else if(pduReq[0]&0x80)	rezReq = _("21:Error respond.");

	pduReq.erase(0,3);
	//Swap registers
	for(int i_p = 0; i_p < (int)pduReq.size()-1; i_p += 2)
	{ char t_sw = pduReq[i_p]; pduReq[i_p] = pduReq[i_p+1]; pduReq[i_p+1] = t_sw; }

	Inquired_t *respHd = (Inquired_t*)pduReq.data();
	const char *off = pduReq.data() + sizeof(Inquired_t);
	if(respHd->Reinit)	rezReq = _("21:Position lost. Need reload MR/MC bus - re-enable the controller object.");

	// AI process
	off += sizeof(float);
	for(int i_a = 0; i_a < 8; i_a++) {
	    p->vlAt(TSYS::strMess("ain%d",i_a)).at().setR((rezReq.size() || !respHd->MCAinsValid)?EVAL_REAL:TSYS::getUnalignFloat(off), 0, true);
	    off += sizeof(float);
	}
	off += sizeof(float);
	// Counters process
	for(int i_c = 7; i_c <= 8; i_c++) {
	    p->vlAt(TSYS::strMess("cntr_din%d_",i_c)).at().setI(rezReq.size()?EVAL_INT:TSYS::getUnalign32(off), 0, true);
	    off += sizeof(uint32_t);
	}
	// Frequency process
	for(int i_c = 7; i_c <= 8; i_c++) {
	    p->vlAt(TSYS::strMess("freq_din%d_",i_c)).at().setI(rezReq.size()?EVAL_INT:TSYS::getUnalign32(off), 0, true);
	    off += sizeof(uint32_t);
	}
	// Digit inputs process
	int vl = TSYS::getUnalign16(off);
	for(int i_d = 0; i_d <= 10; i_d++) {
	    if(i_d == 10)	p->vlAt("cr_ack_din8_").at().setB(rezReq.size()?EVAL_BOOL:(vl>>i_d)&1, 0, true);
	    else if(i_d == 9)	p->vlAt("cr_ack_din7_").at().setB(rezReq.size()?EVAL_BOOL:(vl>>i_d)&1, 0, true);
	    else p->vlAt(TSYS::strMess("din%d",i_d)).at().setB(rezReq.size()?EVAL_BOOL:((vl^ePrm->diRev)>>i_d)&1, 0, true);
	}

	//Send outputs
	if(!rezReq.size()) {
	    data.clear();
	    // MR_broadcast_t.Offsets
	    for(int i_n = 0; i_n < 8; i_n++)
		data += (char)((i_n == vmax(0,modSlot))?8:(8+10));
	    //Swap registers
	    for(int i_p = 0; i_p < (int)data.size()-1; i_p += 2)
	    { char t_sw = data[i_p]; data[i_p] = data[i_p+1]; data[i_p+1] = t_sw; }

	    // MR_broadcast_t.Data
	    //?!?!  Digital outputs prepare and place
	    vl = 0;
	    for(int i_d = 11; i_d >= 0; i_d--) {
		vl = vl << 1;
		if(i_d == 11)		{ if(p->vlAt("crst_din8_").at().getB(0, true) == true) vl |= 1; }
		else if(i_d == 10)	{ if(p->vlAt("crst_din7_").at().getB(0, true) == true) vl |= 1; }
		else if((p->vlAt(TSYS::strMess("dou%d",i_d)).at().getB(0,true)^(ePrm->doRev>>i_d))&1) vl |= 1;
	    }
	    data += (char)(vl>>8); data += (char)vl;

	    //  Analog outputs place
	    for(int i_a = 0; i_a < 4; i_a++) {
		vl = p->vlAt(TSYS::strMess("aou%d",i_a)).at().getI(0, true);
		if(vl == EVAL_INT) vl = 0;
		data += (char)(vl>>8); data += (char)vl;
	    }

	    // Prepare broadcast header
	    pdu = (char)0xFE;		//BroadCast
	    pdu += (char)0x10;		//Function, preset multiple registers
	    pdu += (char)0;
	    pdu += (char)0;
	    pdu += (char)((data.size()/2)>>8);	//Registers quantity MSB
	    pdu += (char)(data.size()/2);	//Registers quantity LSB
	    pdu += (char)data.size();	//Byte Count
	    pdu += data;
	    rezReq = p->owner().modBusReq(pdu, (modSlot<0), true);
	}

	//Alarms process
	if(rezReq.empty() && respHd->AlarmsInID) {
	    if(respHd->proc.AlarmsMC.AOutOverload) rezReq += _("Analog output overload; ");
	    if(respHd->proc.AlarmsMC.NoTunes) rezReq += _("No tunes get; ");
	    if(rezReq.size()) rezReq = "21:"+rezReq;
	}
    }
    else {		//MR process
	//Read inputs
	int reStrSize = sizeof(Inquired_t) + (ePrm->DI?2:0) + ePrm->CNTR*4;
	pduReq = (char)ePrm->SN;		//SN[0]
	pduReq += (char)0x03;			//Function, read multiple registers
	pduReq += (char)(ePrm->SN>>16);		//SN[2]
	pduReq += (char)(ePrm->SN>>8);		//SN[1]
	pduReq += (char)((reStrSize/2)>>8);	//Registers quantity MSB
	pduReq += (char)(reStrSize/2);		//Registers quantity LSB, MC structure size 32 registers
	rezReq = p->owner().modBusReq(pduReq, (modSlot<0));
	if(rezReq.size() || (int)pduReq.size() < (3+reStrSize)) {
	    pduReq.resize(3+reStrSize);
	    if(rezReq.empty()) rezReq = _("20:PDU short.");
	}
	//else if(pduReq[0]&0x80)	rezReq = _("21:Error respond.");

	pduReq.erase(0,3);
	//Swap registers
	for(int i_p = 0; i_p < (int)pduReq.size()-1; i_p += 2)
	{ char t_sw = pduReq[i_p]; pduReq[i_p] = pduReq[i_p+1]; pduReq[i_p+1] = t_sw; }

	Inquired_t *respHd = (Inquired_t*)pduReq.data();
	const char *off = pduReq.data() + sizeof(Inquired_t);
	if(respHd->Reinit)	rezReq = _("21:Position lost. Need reload MR/MC bus - re-enable the controller object.");

	// Counters process
	for(int i_c = 1; i_c <= ePrm->CNTR; i_c++) {
	    p->vlAt(TSYS::strMess("cntr%d",i_c)).at().setI(rezReq.size()?EVAL_INT:TSYS::getUnalign32(off), 0, true);
	    off += sizeof(uint32_t);
	}
	// Digit inputs process
	int vl = ePrm->DI ? TSYS::getUnalign16(off) : 0;
	for(int i_d = 0; i_d < ePrm->DI; i_d++)
	    p->vlAt(TSYS::strMess("din%d",i_d)).at().setB(rezReq.size()?EVAL_BOOL:((vl^ePrm->diRev)>>i_d)&1, 0, true);

	//Send outputs
	if(!rezReq.size() && (ePrm->DO || ePrm->AO)) {
	    data.clear();
	    //?!?! Digital outputs prepare and place
	    if(ePrm->DO) {
		int vl = 0;
		for(int i_d = (ePrm->DO-1); i_d >= 0; i_d--) {
		    vl = vl << 1;
		    if((p->vlAt(TSYS::strMess("dou%d",i_d)).at().getB(0,true)^(ePrm->doRev>>i_d))&1) vl |= 1;
		}
		data += (char)vl; data += (char)(vl>>8);
	    }
	    // Analog outputs place
	    for(int i_a = 0; i_a < ePrm->AO; i_a++) {
		vl = p->vlAt(TSYS::strMess("aou%d",i_a)).at().getI(0, true);
		if(vl == EVAL_INT) vl = 0;
		data += (char)vl; data += (char)(vl>>8);
	    }
	    // Append to generic MR write frame
	    if(data.size() && p->owner().MRWrFrm[modSlot] == ((modSlot<7)?p->owner().MRWrFrm[modSlot+1]:(int)p->owner().MRWrFrm.size())) {
		if(p->owner().MRWrFrm[modSlot] >= (int)p->owner().MRWrFrm.size()) p->owner().MRWrFrm.append(data);
		else p->owner().MRWrFrm.insert(p->owner().MRWrFrm[modSlot], data);

		for(int i_n = modSlot+1; i_n < 8; i_n++)
		    p->owner().MRWrFrm[i_n] += data.size();
	    }
	}

	//Alarms process
	if(rezReq.empty() && respHd->AlarmsInID) {
	    if(respHd->proc.AlarmsMR.HighVoltage)	rezReq += _("High voltage; ");
	    if(respHd->proc.AlarmsMR.LowVoltage)	rezReq += _("Low voltage; ");
	    if(respHd->proc.AlarmsMR.AOutOverload)	rezReq += _("Analog output overload; ");
	    if(respHd->proc.AlarmsMR.PSOverload)	rezReq += _("Internal power supply overvoltage; ");
	    if(respHd->proc.AlarmsMR.UnknownType)	rezReq += _("Unknown MR module type; ");
	    if(rezReq.size()) rezReq = "21:"+rezReq;
	}
    }

    p->acq_err.setVal(rezReq);
}

void MRCParam::vlSet( TParamContr *ip, TVal &vo, const TVariant &vl, const TVariant &pvl )
{

}

bool MRCParam::cntrCmdProc( TParamContr *ip, XMLNode *opt )
{
    TMdPrm *p = (TMdPrm *)ip;
    ResAlloc res(mod->MRCdevsRes, false);
    DevMRCFeature dMRC = mod->MRCdevs[p->cfg("MOD_TP").getI()];

    //Get page info
    if(opt->name() == "info") {
	p->ctrMkNode3("fld",opt,-1,"/prm/cfg/MOD_TP",EVAL_STR,SEC_RD|(p->enableStat()?0:SEC_WR),
	    "dest","select", "select","/prm/cfg/modLst");
	p->ctrMkNode3("fld",opt,-1,"/prm/cfg/MOD_SLOT",EVAL_STR,SEC_RD|(p->enableStat()?0:SEC_WR),
	    "help",_("-1  - for MC;\n0-7 - for MR."));
	if(dMRC.HardID && p->ctrMkNode3("area",opt,-1,"/cfg",_("Configuration"),SEC_RD)) {
	    //Tune configuration
	    for(int iT = 0, iAIn = 0; true; iT++) {
		string tits = TSYS::strMess("tune%d",iT);
		map<string, string>::iterator tit = dMRC.sects["common"].find(tits);
		if(tit == dMRC.sects["common"].end()) break;
		string tp = TSYS::strParse(tit->second,1,"#"),
		    view = TSYS::strParse(tit->second,2,"#"),
		    nm = TSYS::strParse(tit->second,3,"#"),
		    help = TSYS::strParse(tit->second,4,"#"),
		    vMin = TSYS::strParse(tit->second,6,"#"),
		    vMax = TSYS::strParse(tit->second,7,"#");
		if(tp == "int") {
		    if(view == "ain") {
			string tpLs, tpVal;
			//Prepare AIN types list
			for(int i_aTp = 0; true; i_aTp++) {
			    tpVal = mod->MRCdevs[-1].sects["Types"][TSYS::strMess("Type%d",i_aTp)];
			    if(!tpVal.size()) break;
			    tpLs += tpVal+";";
			    if(mod->MRCdevs[-1].sects[tpVal][TSYS::strMess("3WIRE%d",iAIn)].size()) tpLs += tpVal+" (3WIRE);";
			    if(mod->MRCdevs[-1].sects[tpVal][TSYS::strMess("4WIRE%d",iAIn)].size()) tpLs += tpVal+" (4WIRE);";
			}
			p->ctrMkNode3("fld",opt,-1,("/cfg/"+tits+"-"+i2s(iAIn)).c_str(),TSYS::strMess("AI %d",iAIn).c_str(),
				SEC_RD|((p->enableStat() || view == "sys")?0:SEC_WR),
				"tp","str", "help",help.c_str(), "dest","select", "sel_list",tpLs.c_str());
			iAIn++;
			iT += 3;
		    }
		    else
			p->ctrMkNode3("fld",opt,-1,("/cfg/"+tits).c_str(),nm.c_str(),SEC_RD|((p->enableStat() || view == "sys")?0:SEC_WR),
			    "tp","dec", "min",vMin.c_str(), "max",vMax.c_str(),"help",help.c_str());
		}
		else if(tp == "intbit")
		    for(int iTb = 0; iTb < 16; iTb++) {
			string titbs = TSYS::strMess("tune%d-%d",iT,iTb);
			map<string, string>::iterator titb = dMRC.sects["common"].find(titbs);
			if(titb == dMRC.sects["common"].end()) break;
			string bnm = nm+": "+TSYS::strParse(titb->second,1,"#"),
			    bhelp = TSYS::strParse(titb->second,2,"#");
			p->ctrMkNode3("fld",opt,-1,("/cfg/"+titbs).c_str(),bnm.c_str(),SEC_RD|((p->enableStat() || view == "sys")?0:SEC_WR),
			    "tp","bool", "help",bhelp.c_str());
		    }
	    }
	    //Digital signals revers configuration
	    XMLNode *tReq = p->ctrMkNode3("area",opt,-1,"/cfg/digRev",_("Digital signals reverse"),SEC_RD);
	    for(map<string, DevMRCFeature::SVal>::iterator itv = dMRC.vars.begin(); tReq && itv != dMRC.vars.end(); itv++) {
		int dN = (itv->first.size()>3) ? atoi(itv->first.c_str()+3) : -1;
		if(itv->first.find("din") == 0)
		    p->ctrMkNode3("fld",opt,-1,TSYS::strMess("/cfg/digRev/DI%d",dN).c_str(),TSYS::strMess(_("DI %d"),dN).c_str(),
			SEC_RD|(p->enableStat()?0:SEC_WR), "tp","bool");
		else if(itv->first.find("dou") == 0)
		    p->ctrMkNode3("fld",opt,-1,TSYS::strMess("/cfg/digRev/DO%d",dN).c_str(),TSYS::strMess(_("DO %d"),dN).c_str(),
			SEC_RD|(p->enableStat()?0:SEC_WR), "tp","bool");
	    }
	    if(tReq && !tReq->childSize()) tReq->parent()->childDel(tReq);
	}
	return true;
    }
    //Process command to page
    string a_path = opt->attr("path");
    if(a_path == "/prm/cfg/modLst" && p->ctrChkNode3(opt,"get",R_R_R_,SEC_RD)) {
	opt->childAdd("el")->setAttr("id","0")->setText(_("<No select>"));
	for(map<int, DevMRCFeature>::iterator i_d = mod->MRCdevs.begin(); i_d != mod->MRCdevs.end(); i_d++) {
	    if(!i_d->second.HardID) continue;
	    opt->childAdd("el")->setAttr("id",i2s(i_d->second.HardID))->setText(i_d->second.name);
	}
    }
    else if(a_path.compare(0,9,"/cfg/tune") == 0) {
	int iT = -1, iTb = -1;
	sscanf(a_path.c_str(),"/cfg/tune%d-%d",&iT,&iTb);
	string tits = TSYS::strMess("tune%d",iT);
	map<string, string>::iterator tit = dMRC.sects["common"].find(tits);
	string tp = TSYS::strParse(tit->second,1,"#"),
	    view = TSYS::strParse(tit->second,2,"#");
	if(p->ctrChkNode2(opt,"get",SEC_RD)) {
	    if(view == "ain") opt->setText(p->modPrm(tits+"val",mod->MRCdevs[-1].sects["Types"]["Type0"]));
	    else {
		opt->setText(TSYS::strParse(tit->second,5,"#"));
		if(view != "sys") opt->setText(p->modPrm(tits,opt->text()));
		if(iTb >= 0) opt->setText(((s2i(opt->text())>>iTb)&1)?"1":"0");
	    }
	}
	if(p->ctrChkNode2(opt,"set",SEC_WR) && view != "sys") {
	    if(view == "ain") {
		p->setModPrm(tits+"val",opt->text());
		// Set tunes registers from config ain_tunes.ini
		char buf[31];
		int nWr = 2;
		sscanf(opt->text().c_str(),"%30s (%dWIRE)",buf,&nWr);
		string cfgStream = TSYS::strEncode(mod->MRCdevs[-1].sects[buf][TSYS::strMess("%dWIRE%d",nWr,iTb)], TSYS::Bin);
		if(cfgStream.size() >= 8)
		    for(int iAIr = 0; iAIr < 4; iAIr++)
			p->setModPrm("tune"+i2s(iT+iAIr),i2s((cfgStream[iAIr*2]<<8)|cfgStream[iAIr*2+1]));
	    }
	    else if(iTb < 0) p->setModPrm(tits, opt->text());
	    else {
		int cur_val = s2i(p->modPrm(tits, TSYS::strParse(tit->second,5,"#")));
		if(s2i(opt->text())) cur_val |= 1<<iTb; else cur_val &= ~(1<<iTb);
		p->setModPrm(tits, i2s(cur_val));
	    }
	}
    }
    else if(a_path.compare(0,14,"/cfg/digRev/DI") == 0) {
	int dN = atoi(a_path.c_str()+14);
	int dVl = s2i(p->modPrm("DIRev"));
	if(p->ctrChkNode2(opt,"get",SEC_RD)) opt->setText((dVl&(1<<dN))?"1":"0");
	if(p->ctrChkNode2(opt,"set",SEC_WR))
	    p->setModPrm("DIRev", i2s(s2i(opt->text()) ? (dVl|(1<<dN)) : (dVl & ~(1<<dN))));
    }
    else if(a_path.compare(0,14,"/cfg/digRev/DO") == 0) {
	int dN = atoi(a_path.c_str()+14);
	int dVl = s2i(p->modPrm("DORev"));
	if(p->ctrChkNode2(opt,"get",SEC_RD)) opt->setText((dVl&(1<<dN))?"1":"0");
	if(p->ctrChkNode2(opt,"set",SEC_WR))
	    p->setModPrm("DORev", i2s(s2i(opt->text()) ? (dVl|(1<<dN)) : (dVl & ~(1<<dN))));
    }
    else return false;

    return true;
}

//*************************************************
//* SMH2Gi::DevMRCFeature                         *
//*************************************************
DevMRCFeature::DevMRCFeature( const string &iniFile ) : HardID(0)
{
    load(iniFile);
}

bool DevMRCFeature::load( const string &iniFile )
{
    //Clear all
    HardID = 0;
    name = descr = "";
    sects.clear();
    vars.clear();

    //Parse file
    char buf[prmStrBuf_SZ];
    TRegExp re("^\\s*\\[([^\\]]+)|^\\s*([^\\s;]+)\\s*=\\s*([^\\r\\n]+)" /* "\\[([^\\]]+)\\]|(\\S+)\\s*=\\s*([^\n\r]+)" */);
    FILE *fp = fopen(iniFile.c_str(), "r");
    if(fp == NULL) return false;
    string iniGroup, iniGroupLC, propNm, propVal;
    map<string, SVal>::iterator cVar;
    while(fgets(buf,sizeof(buf),fp) != NULL) {
	TArrayObj *reRez = re.match(string(buf,sizeof(buf)));
	//Group change
	if(reRez->size() == 2) {
	    iniGroup = reRez->arGet(1).getS();
	    iniGroupLC.resize(iniGroup.length());
	    std::transform(iniGroup.begin(), iniGroup.end(), iniGroupLC.begin(), ::tolower);
	}
	//Property process
	else if(reRez->size() == 4 && iniGroup.size()) {
	    propNm = reRez->arGet(2).getS(); propVal = Mess->codeConvIn("cp1251",reRez->arGet(3).getS());
	    sects[iniGroup][propNm] = propVal;
	    if(iniGroupLC == "common") {
		if(propNm == "name") 		name = propVal;
		else if(propNm == "descr")	descr = propVal;
		else if(propNm == "ID")		HardID = s2i(propVal);
		else if(propNm.compare(0,3,"var") == 0) {
		    std::transform(propVal.begin(), propVal.end(), propVal.begin(), ::tolower);
		    vars[propVal] = SVal();
		}
	    }
	    // Variables parsing
	    else if((cVar=vars.find(iniGroupLC)) != vars.end()) {
		if(propNm == "descr" /*|| propNm == "dop_descr"*/)	cVar->second.descr = propVal;
		else if(propNm == "addr") cVar->second.addr = s2i(propVal);
		else if(propNm == "type") {
		    cVar->second.tp = TFld::Integer;
		    if(propVal == "real")	cVar->second.tp = TFld::Real;
		    else if(propVal == "bool")	cVar->second.tp = TFld::Boolean;
		}
		else if(propNm == "dir")	cVar->second.wr = (propVal=="w");
		else if(propNm == "visible")	cVar->second.visible = propVal;
	    }
	}
	delete reRez;
    }

    fclose(fp);

    return true;
}
