//OpenSCADA module Protocol.ModBus file: modbus_prt.cpp /*************************************************************************** * Copyright (C) 2008-2024 by Roman Savochenko, * * * * 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 #include #include #include #include #include #include "modbus_daq.h" #include "modbus_prt.h" ModBus::TProt *ModBus::modPrt; using namespace ModBus; //************************************************* //* TProt * //************************************************* TProt::TProt( string name ) : TProtocol(PRT_ID), mPrtLen(0) { modPrt = this; modInfoMainSet(PRT_NAME, PRT_TYPE, PRT_MVER, PRT_AUTHORS, PRT_DESCR, PRT_LICENSE, name); mNode = grpAdd("n_"); //Node DB structure mNodeEl.fldAdd(new TFld("ID",trS("Identifier"),TFld::String,TCfg::Key|TFld::NoWrite,i2s(limObjID_SZ).c_str())); mNodeEl.fldAdd(new TFld("NAME",trS("Name"),TFld::String,TFld::TransltText,i2s(limObjNm_SZ).c_str())); mNodeEl.fldAdd(new TFld("DESCR",trS("Description"),TFld::String,TFld::FullText|TFld::TransltText,i2s(limObjDscr_SZ).c_str())); mNodeEl.fldAdd(new TFld("EN",trS("To enable"),TFld::Boolean,0,"1","0")); mNodeEl.fldAdd(new TFld("ADDR",trS("Address"),TFld::Integer,0,"3","1","1;247")); mNodeEl.fldAdd(new TFld("InTR",trS("Input transport"),TFld::String,0,i2s(limObjID_SZ).c_str(),"*")); mNodeEl.fldAdd(new TFld("PRT",trS("Protocol"),TFld::String,TFld::Selectable,"5","*","RTU;ASCII;TCP;*",trS("RTU;ASCII;TCP/IP;All"))); mNodeEl.fldAdd(new TFld("MODE",trS("Mode"),TFld::Integer,TFld::Selectable,"1","0", TSYS::strMess("%d;%d;%d",Node::MD_DATA,Node::MD_GT_ND,Node::MD_GT_NET),trS("Data;Gateway node;Gateway net"))); mNodeEl.fldAdd(new TFld("TIMESTAMP",trS("Date of modification"),TFld::Integer,TFld::DateTimeDec)); // For "Data" mode mNodeEl.fldAdd(new TFld("DT_PER",trS("Period of the data calculation, seconds"),TFld::Real,0,"5.3","1","0.001;99")); mNodeEl.fldAdd(new TFld("DT_PR_TR",trS("Completely translate the procedure"),TFld::Boolean,TFld::NoFlag,"1","0")); mNodeEl.fldAdd(new TFld("DT_PROG",trS("Procedure"),TFld::String,TFld::TransltText,"1000000")); // For "Gateway" mode mNodeEl.fldAdd(new TFld("TO_TR",trS("To output transport"),TFld::String,0,i2s(limObjID_SZ).c_str())); mNodeEl.fldAdd(new TFld("TO_PRT",trS("To protocol"),TFld::String,TFld::Selectable,"5","RTU","RTU;ASCII;TCP")); mNodeEl.fldAdd(new TFld("TO_ADDR",trS("To address"),TFld::Integer,0,"3","1","1;247")); //Node data IO DB structure mNodeIOEl.fldAdd(new TFld("NODE_ID",trS("Node ID"),TFld::String,TCfg::Key,i2s(limObjID_SZ).c_str())); mNodeIOEl.fldAdd(new TFld("ID",trS("Identifier"),TFld::String,TCfg::Key,i2s(limObjID_SZ).c_str())); mNodeIOEl.fldAdd(new TFld("NAME",trS("Name"),TFld::String,TFld::TransltText,i2s(limObjNm_SZ).c_str())); mNodeIOEl.fldAdd(new TFld("TYPE",trS("Value type"),TFld::Integer,TFld::NoFlag,"1")); mNodeIOEl.fldAdd(new TFld("FLAGS",trS("Flags"),TFld::Integer,TFld::NoFlag,"4")); mNodeIOEl.fldAdd(new TFld("VALUE",trS("Value"),TFld::String,TFld::TransltText,"100")); mNodeIOEl.fldAdd(new TFld("POS",trS("Real position"),TFld::Integer,TFld::NoFlag,"4")); } TProt::~TProt( ) { nodeDelAll(); } string TProt::nAdd( const string &iid, const string &db ) { return chldAdd(mNode, new Node(TSYS::strEncode(sTrm(iid),TSYS::oscdID),db,&nodeEl())); } void TProt::load_( ) { //Load parameters from command line //Load DB // Search and create new nodes try { TConfig gCfg(&nodeEl()); gCfg.cfg("DT_PROG").setExtVal(true); //gCfg.cfgViewAll(false); vector itLs; map itReg; // Search in DB TBDS::dbList(itLs, TBDS::LsCheckSel|TBDS::LsInclGenFirst); for(unsigned iDB = 0; iDB < itLs.size(); iDB++) for(int fldCnt = 0; TBDS::dataSeek(itLs[iDB]+"."+modId()+"_node",nodePath()+modId()+"_node",fldCnt++,gCfg,TBDS::UseCache); ) { string id = gCfg.cfg("ID").getS(); if(!nPresent(id)) nAdd(id, itLs[iDB]); if(nAt(id).at().DB() == itLs[iDB]) nAt(id).at().load(&gCfg); nAt(id).at().setDB(itLs[iDB], true); itReg[id] = true; } // Check for remove items removed from DB if(SYS->chkSelDB(SYS->selDB(),true)) { nList(itLs); for(unsigned iIt = 0; iIt < itLs.size(); iIt++) if(itReg.find(itLs[iIt]) == itReg.end() && SYS->chkSelDB(nAt(itLs[iIt]).at().DB())) nDel(itLs[iIt]); } } catch(TError &err) { mess_err(err.cat.c_str(),"%s",err.mess.c_str()); mess_err(nodePath().c_str(),_("Error of searching and a new node creation.")); } } void TProt::save_( ) { } void TProt::modStart( ) { vector ls; nList(ls); for(unsigned iN = 0; iN < ls.size(); iN++) if(nAt(ls[iN]).at().toEnable()) nAt(ls[iN]).at().setEnable(true); } void TProt::modStop( ) { vector ls; nList(ls); for(unsigned iN = 0; iN < ls.size(); iN++) nAt(ls[iN]).at().setEnable(false); } TProtocolIn *TProt::in_open( const string &name ) { return new TProtIn(name); } uint8_t TProt::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 TProt::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 TProt::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); } uint8_t TProt::LRC( const string &mbap ) { uint8_t ch = 0; for(unsigned i_b = 0; i_b < mbap.size(); ++i_b) ch += (uint8_t)mbap[i_b]; return -ch; } string TProt::DataToASCII( const string &in ) { uint8_t ch; string rez; for(unsigned i = 0; i < in.size(); i++) { ch = (in[i]&0xF0)>>4; rez += (ch + ((ch<=9)?'0':('A'-10))); ch = in[i]&0x0F; rez += (ch + ((ch<=9)?'0':('A'-10))); } return rez; } string TProt::ASCIIToData( const string &in ) { uint8_t ch1, ch2; string rez; for(unsigned i = 0; i < (in.size()&(~0x01)); i += 2) { ch1 = in[i]; if(ch1 >= '0' && ch1 <= '9') ch1 -= '0'; else if(ch1 >= 'A' && ch1 <= 'F') ch1 -= ('A'-10); else ch1 = 0; ch2 = ch1 << 4; ch1 = in[i+1]; if(ch1 >= '0' && ch1 <= '9') ch1 -= '0'; else if(ch1 >= 'A' && ch1 <= 'F') ch1 -= ('A'-10); else ch1 = 0; rez += ch2|ch1; } return rez; } void TProt::outMess( XMLNode &io, TTransportOut &tro ) { string mbap, err, rez; char buf[1000]; MtxAlloc resN(tro.reqRes(), true); string prt = io.name(); string sid = io.attr("id"); string debugCat = io.attr("debugCat"); int reqTm = s2i(io.attr("reqTm")); int node = s2i(io.attr("node")); int reqTry = vmin(10, vmax(1,s2i(io.attr("reqTry")))); string pdu = io.text(); try { if(!tro.startStat()) tro.start(); if(prt == "TCP") { // Modbus/TCP protocol process //Encode MBAP (Modbus Application Protocol) uint16_t tid = rand()%65536; mbap.reserve(pdu.size()+7); mbap.append((char*)&tid, 2); //Transaction ID mbap.append(2, (char)0); //Protocol ID mbap += (char)((pdu.size()+1)>>8); //PDU size MSB mbap += (char)(pdu.size()+1); //PDU size LSB mbap += (char)node; //Unit identifier mbap += pdu; //Send request int resp_len = tro.messIO(mbap.data(), mbap.size(), buf, sizeof(buf), reqTm); rez.assign(buf, resp_len); //Wait tail while(rez.size() && (rez.size() < 7 || rez.size() < (((unsigned short)(rez[4]<<8)|(unsigned char)rez[5])+6)) && rez.size() < MODBUS_FRM_LIM && (resp_len=tro.messIO(NULL,0,buf,sizeof(buf),reqTm))) rez.append(buf, resp_len); if(rez.empty()) err = _("No response"); else if(rez.size() < (((unsigned short)(rez[4]<<8)|(unsigned char)rez[5])+6)) err = _("Not full response"); else if(TSYS::getUnalign16(rez.data()) != tid) err = TSYS::strMess(_("The response Transaction ID %d is not suitable to the request one %d."), TSYS::getUnalign16(rez.data()), (int)tid); else if(rez.size() >= MODBUS_FRM_LIM) err = _("Error of the response: Too large."); else pdu = rez.substr(7); if(err.size()) err = i2s(TError::Tr_ErrResponse) + ":" + err; } else if(prt == "RTU") { // Modbus/RTU protocol process if(tro.isNetwork()) tro.setTimings("5:0.1", true); mbap.reserve(pdu.size()+3); mbap += (uint8_t)node; //Unit identifier mbap += pdu; uint16_t crc = CRC16(mbap); mbap += (crc>>8); mbap += crc; //Send request for(int iTr = 0, resp_len = 0; iTr < reqTry; iTr++) { try { resp_len = tro.messIO(mbap.data(), mbap.size(), buf, sizeof(buf), reqTm); rez.assign(buf, resp_len); //Wait tail while(resp_len && rez.size() < MODBUS_FRM_LIM && // Error specific fast termination the data waiting (rez.size() < 3 || !(((rez[1]&0x80) && rez.size() >= (2+3)) || // Function [1...4] specific fast termination the data waiting (rez[1] >= 1 && rez[1] <= 4 && rez.size() >= (((uint8_t)rez[2])+2+3)) || // Function [5,6,15,16] specific fast termination the data waiting ((rez[1] == 5 || rez[1] == 6 || rez[1] == 15 || rez[1] == 16) && rez.size() >= (5+3))) )) { try { resp_len = tro.messIO(NULL, 0, buf, sizeof(buf)); } catch(TError &err) { break; } rez.append(buf, resp_len); } } catch(TError &er) { //By possible the send request breakdown and no response if(err.empty()) err = i2s(TError::Tr_ErrDevice)+":"+_("Device error: ") + er.mess; else if(err.find(er.mess) == string::npos) err += "; " + er.mess; continue; } if(rez.size() < 2) { err = i2s(TError::Tr_ErrResponse)+":"+_("Error of the response: Too short."); continue; } if(rez.size() >= MODBUS_FRM_LIM) { err = i2s(TError::Tr_ErrResponse)+":"+_("Error of the response: Too large."); continue; } if(CRC16(rez.substr(0,rez.size()-2)) != (uint16_t)((rez[rez.size()-2]<<8)+(uint8_t)rez[rez.size()-1])) { err = i2s(TError::Tr_ErrResponse)+":"+_("Error of the response: CRC error."); continue; } pdu = rez.substr(1, rez.size()-3); err = ""; break; } } else if(prt == "ASCII") { // Modbus/ASCII protocol process mbap.reserve(pdu.size()+2); mbap += (uint8_t)node; //Unit identifier mbap += pdu; mbap += LRC(mbap); mbap = ":"+DataToASCII(mbap)+"\x0D\x0A"; //Send request for(int iTr = 0, resp_len = 0; iTr < reqTry; iTr++) { try { resp_len = tro.messIO(mbap.data(), mbap.size(), buf, sizeof(buf), reqTm); rez.assign(buf, resp_len); //Wait tail while(resp_len && (rez.size() < 3 || rez.substr(rez.size()-2,2) != "\x0D\x0A") && rez.size() < MODBUS_FRM_LIM) { try { resp_len = tro.messIO(NULL, 0, buf, sizeof(buf)); } catch(TError &err) { break; } rez.append(buf, resp_len); } } catch(TError &er) { //By possible the send request breakdown and no response if(err.empty()) err = i2s(TError::Tr_ErrDevice)+":"+_("Device error: ") + er.mess; else if(err.find(er.mess) != string::npos) err += "; " + er.mess; continue; } if(rez.size() >= MODBUS_FRM_LIM) { err = i2s(TError::Tr_ErrResponse)+":"+_("Error of the response: Too large."); continue; } if(rez.size() < 3 || rez[0] != ':' || rez.substr(rez.size()-2,2) != "\x0D\x0A") { err = i2s(TError::Tr_ErrResponse)+":"+_("Error of the response: Format error."); continue; } string rezEnc = ASCIIToData(rez.substr(1,rez.size()-3)); if(LRC(rezEnc.substr(0,rezEnc.size()-1)) != (uint8_t)rezEnc[rezEnc.size()-1]) { err = i2s(TError::Tr_ErrResponse)+":"+_("Error of the response: LRC error."); continue; } pdu = rezEnc.substr(1,rezEnc.size()-2); err = ""; break; } } else err = TSYS::strMess(_("Error of the protocol '%s'."),prt.c_str()); //Check response pdu if(err.empty()) { if(pdu.size() < 2) err = i2s(TError::Tr_ErrResponse)+":"+_("Error of the response"); if(pdu[0]&0x80) switch(pdu[1]) { case 0x1: err = TSYS::strMess(_("1:%02X:Function is not supported."),(unsigned char)(pdu[0]&(~0x80))); break; case 0x2: err = _("2:Request address is not allowed or the query range is too large."); break; case 0x3: err = _("3:Illegal value of the data in the request."); break; case 0x4: err = _("4:Server failure."); break; case 0x5: err = _("5:Request requires too long time for execute."); break; case 0x6: err = _("6:Server is busy."); break; case 0x7: err = _("7:Error in program function. Requested by the function 13 or 14."); break; case 0xA: case 0xB: err = _("10:Gateway problem."); break; default: err = TSYS::strMess(_("12:%02X:Unknown error."),(unsigned char)(pdu[1])); break; } } } catch(TError &er) { err = i2s(TError::Tr_ErrDevice)+":"+_("Device error: ") + er.mess; } io.setText(err.empty()?pdu:""); if(!err.empty()) io.setAttr("err", err); //Prepare log if(prtLen() || debugCat.size()) { string mess = _("REQ -> "); if(prt != "ASCII") mess += TSYS::strDecode(mbap, TSYS::Bin, " "); else if(mbap.size() > 2) mess += mbap.substr(0,mbap.size()-2); mess += "\n"; if(err.empty()) mess += _("RESP -> "); else mess += _("ERR -> ") + err + " -> "; if(prt != "ASCII") mess += TSYS::strDecode(rez, TSYS::Bin, " "); else if(rez.size() > 2) mess += rez.substr(0,rez.size()-2); if(prtLen()) pushPrtMess(atm2s(time(NULL))+" "+prt+": '"+sid+"' --> "+i2s(node)+"("+tro.workId()+")\n"+mess+"\n"); if(debugCat.size()) mess_debug_(debugCat.c_str(), mess.c_str()); } } void TProt::setPrtLen( int vl ) { MtxAlloc res(dataRes(), true); while((int)mPrt.size() > vl) mPrt.pop_back(); mPrtLen = vl; } void TProt::pushPrtMess( const string &vl ) { MtxAlloc res(dataRes(), true); if(!prtLen()) return; mPrt.push_front(vl); while((int)mPrt.size() > prtLen()) mPrt.pop_back(); } void TProt::cntrCmdProc( XMLNode *opt ) { //Get page info if(opt->name() == "info") { TProtocol::cntrCmdProc(opt); ctrMkNode("grp",opt,-1,"/br/n_",_("Node"),RWRWR_,"root",SPRT_ID,2,"idm",i2s(limObjNm_SZ).c_str(),"idSz",i2s(limObjID_SZ).c_str()); if(ctrMkNode("area",opt,0,"/node",_("Nodes"))) ctrMkNode("list",opt,-1,"/node/node",_("Nodes"),RWRWR_,"root",SPRT_ID,5, "tp","br","idm",i2s(limObjNm_SZ).c_str(),"s_com","add,del","br_pref","n_","idSz",i2s(limObjID_SZ).c_str()); if(ctrMkNode("area",opt,1,"/rep",_("Report"))) { ctrMkNode("fld",opt,-1,"/rep/repLen",_("Report length"),RWRWR_,"root",SPRT_ID,4,"tp","dec","min","0","max","10000", "help",_("Use zero for the report disabling")); if(prtLen()) ctrMkNode("fld",opt,-1,"/rep/rep",_("Report"),R_R_R_,"root",SPRT_ID,3,"tp","str","cols","90","rows","20"); } return; } //Process command to page string a_path = opt->attr("path"); if(a_path == "/br/n_" || a_path == "/node/node") { if(ctrChkNode(opt,"get",RWRWR_,"root",SPRT_ID,SEC_RD)) { vector lst; nList(lst); for(unsigned iF = 0; iF < lst.size(); iF++) opt->childAdd("el")->setAttr("id",lst[iF])->setText(trD(nAt(lst[iF]).at().name())); } if(ctrChkNode(opt,"add",RWRWR_,"root",SPRT_ID,SEC_WR)) { opt->setAttr("id", nAdd(opt->attr("id"))); nAt(opt->attr("id")).at().setName(opt->text()); } if(ctrChkNode(opt,"del",RWRWR_,"root",SPRT_ID,SEC_WR)) chldDel(mNode, opt->attr("id"), -1, NodeRemove); } else if(a_path == "/rep/repLen") { if(ctrChkNode(opt,"get",RWRWR_,"root",SPRT_ID,SEC_RD)) opt->setText(i2s(prtLen())); if(ctrChkNode(opt,"set",RWRWR_,"root",SPRT_ID,SEC_WR)) setPrtLen(s2i(opt->text())); } else if(a_path == "/rep/rep" && ctrChkNode(opt)) { MtxAlloc res(dataRes(), true); for(unsigned iP = 0; iP < mPrt.size(); iP++) opt->setText(opt->text() + mPrt[iP] + "\n"); } else TProtocol::cntrCmdProc(opt); } //************************************************* //* TProtIn * //************************************************* TProtIn::TProtIn( string name ) : TProtocolIn(name) { } TProtIn::~TProtIn( ) { } TProt &TProtIn::owner( ) const { return *(TProt*)nodePrev(); } bool TProtIn::mess( const string &ireqst, string &answer ) { //Check for protocol type unsigned char node = 0; string prt, pdu; string reqst = ireqst; bool isBuf = false; retry: bool isASCII = (reqst.size() > 3 && reqst[0] == ':' && reqst.substr(reqst.size()-2,2) == "\x0D\x0A"); bool isRTU = (reqst.size() > 3 && reqst.size() <= 256 && modPrt->CRC16(reqst.substr(0,reqst.size()-2)) == (uint16_t)((reqst[reqst.size()-2]<<8)+(uint8_t)reqst[reqst.size()-1])); bool isTCP = (reqst.size() > 7 && reqst.size() <= 260 && reqst.size() == (unsigned)((unsigned short)(reqst[4]<<8)|(unsigned char)reqst[5])+6); // ASCII check if(isASCII) { prt = "ASCII"; string req = modPrt->ASCIIToData(reqst.substr(1,reqst.size()-3)); if(modPrt->LRC(req.substr(0,req.size()-1)) != (uint8_t)req[req.size()-1]) return false; node = req[0]; pdu = req.substr(1, req.size()-2); } // RTU check else if((isRTU && !isTCP) || (isRTU && isTCP && srcTr().at().owner().modId() == "Serial")) { prt = "RTU"; node = reqst[0]; pdu = reqst.substr(1, reqst.size()-3); } // TCP check else if(isTCP) { prt = "TCP"; node = reqst[6]; pdu = reqst.substr(7); } else { if(!isBuf && req_buf.size()) { reqst = req_buf+reqst; isBuf = true; goto retry; } req_buf = reqst; if(req_buf.size() > 2048) req_buf = ""; return true; } req_buf = ""; vector nls; modPrt->nList(nls); unsigned iN; for(iN = 0; iN < nls.size(); iN++) if(modPrt->nAt(nls[iN]).at().req(srcTr().at().workId(),prt,node,pdu)) break; if(iN >= nls.size()) return false; answer = ""; if(prt == "TCP") { //Encode MBAP (Modbus Application Protocol) answer.reserve(pdu.size()+7); answer += reqst[0]; //Transaction ID MSB answer += reqst[1]; //Transaction ID LSB answer += reqst[2]; //Protocol ID MSB answer += reqst[3]; //Protocol ID LSB answer += (char)((pdu.size()+1)>>8); //PDU size MSB answer += (char)(pdu.size()+1); //PDU size LSB answer += (char)node; //Unit identifier answer += pdu; } else if(prt == "RTU") { answer.reserve(pdu.size()+3); answer += (uint8_t)node; //Unit identifier answer += pdu; uint16_t crc = modPrt->CRC16(answer); answer += crc>>8; answer += crc; } else if(prt == "ASCII") { answer.reserve(pdu.size()+2); answer += (uint8_t)node; //Unit identifier answer += pdu; answer += modPrt->LRC(answer); answer = ":"+modPrt->DataToASCII(answer)+"\x0D\x0A"; } if(owner().prtLen() && prt.size() && answer.size()) { string mess = atm2s(time(NULL))+" "+prt+": "+srcTr().at().workId()+ "("+TSYS::strLine(srcAddr(),0)+") --> "+i2s(node)+"\n"; mess += _("REQ -> "); if(prt != "ASCII") mess += TSYS::strDecode(reqst, TSYS::Bin, " "); else if(reqst.size() > 2) mess += reqst.substr(0, reqst.size()-2); mess += "\n"; mess +=_("RESP -> "); if(prt != "ASCII") mess += TSYS::strDecode(answer, TSYS::Bin, " "); else if(answer.size() > 2) mess += answer.substr(0, answer.size()-2); owner().pushPrtMess(mess+"\n"); } return false; } //************************************************* //* Node: ModBus input protocol node. * //************************************************* Node::Node( const string &iid, const string &idb, TElem *el ) : TFunction("ModBusNode_"+iid), TConfig(el), data(NULL), isDAQTmpl(false), mId(cfg("ID")), mName(cfg("NAME")), mDscr(cfg("DESCR")), mPer(cfg("DT_PER").getRd()), mAEn(cfg("EN").getBd()), mEn(false), mTimeStamp(cfg("TIMESTAMP").getId()), mDB(idb), prcSt(false), endRun(false), cntReq(0) { mId = iid; cfg("MODE").setI(0); } Node::~Node( ) { try{ setEnable(false); } catch(...) { } if(data) { delete data; data = NULL; } } TCntrNode &Node::operator=( const TCntrNode &node ) { Node *src_n = const_cast(dynamic_cast(&node)); if(!src_n) return *this; //Copy main parameters if(enableStat()) setEnable(false); exclCopy(*src_n, "ID;"); *(TFunction*)this = *(TFunction*)src_n; setDB(src_n->DB()); //Copy for current values and links (by the templates) if(src_n->mode() == MD_DATA && src_n->enableStat()) { setEnable(true); ResAlloc res(nRes, false); ResAlloc res1(src_n->nRes, false); for(int iIO = 0; iIO < src_n->data->func()->ioSize(); iIO++) if(src_n->data->func()->io(iIO)->flg()&TPrmTempl::CfgLink) data->lnkAddrSet(iIO, src_n->data->lnkAddr(iIO)); else data->set(iIO, src_n->data->get(iIO)); } return *this; } void Node::postEnable( int flag ) { //Create default IOs if(flag&TCntrNode::NodeConnect) { ioIns(new IO("f_frq",trS("Frequency of calculation of the function, Hz"),IO::Real,TPrmTempl::LockAttr,"1000",false), 0); ioIns(new IO("f_start",trS("Function start flag"),IO::Boolean,TPrmTempl::LockAttr,"0",false), 1); ioIns(new IO("f_stop",trS("Function stop flag"),IO::Boolean,TPrmTempl::LockAttr,"0",false), 2); } } void Node::postDisable( int flag ) { if(flag&(NodeRemove|NodeRemoveOnlyStor)) { TBDS::dataDel(fullDB(flag&NodeRemoveOnlyStor), owner().nodePath()+tbl(), *this, TBDS::UseAllKeys); TConfig cfg(&owner().nodeIOEl()); cfg.cfg("NODE_ID").setS(id(), true); TBDS::dataDel(fullDB(flag&NodeRemoveOnlyStor)+"_io", owner().nodePath()+tbl()+"_io", cfg); if(flag&NodeRemoveOnlyStor) { setStorage(mDB, "", true); return; } } } TProt &Node::owner( ) const { return *(TProt*)nodePrev(); } string Node::name( ) { string tNm = mName; return tNm.size() ? tNm : id(); } string Node::tbl( ) const { return owner().modId()+"_node"; } int Node::addr( ) const { return cfg("ADDR").getI(); } string Node::inTransport( ) { return cfg("InTR").getS(); } string Node::prt( ) { return cfg("PRT").getS(); } Node::NodeModes Node::mode( ) { return (Node::NodeModes)cfg("MODE").getI(); } string Node::progLang( ) { string mProg = cfg("DT_PROG").getS(); return mProg.substr(0, mProg.find("\n")); } string Node::prog( ) { string mProg = cfg("DT_PROG").getS(); size_t lngEnd = mProg.find("\n"); return mProg.substr(((lngEnd==string::npos)?0:lngEnd+1)); } void Node::setProgLang( const string &ilng ) { cfg("DT_PROG").setS(ilng+"\n"+prog()); modif(); } void Node::setProg( const string &iprg ) { cfg("DT_PROG").setS(progLang()+"\n"+iprg); modif(); } bool Node::cfgChange( TCfg &co, const TVariant &pc ) { if(co.name() == "DT_PR_TR") cfg("DT_PROG").setNoTransl(!progTr()); else if(co.name() == "DT_PROG") { string lfnc = TSYS::strParse(progLang(), 0, "."), wfnc = TSYS::strParse(progLang(), 1, "."); isDAQTmpl = SYS->daq().at().tmplLibPresent(lfnc) && SYS->daq().at().tmplLibAt(lfnc).at().present(wfnc); } else if(co.name() == "MODE") { setEnable(false); //Hide all specific cfg("ADDR").setView(false); cfg("DT_PER").setView(false); cfg("DT_PR_TR").setView(false); cfg("DT_PROG").setView(false); cfg("TO_TR").setView(false); cfg("TO_PRT").setView(false); cfg("TO_ADDR").setView(false); //Show selected switch(co.getI()) { case 0: cfg("ADDR").setView(true); cfg("DT_PER").setView(true); cfg("DT_PR_TR").setView(true); cfg("DT_PROG").setView(true);break; case 1: cfg("ADDR").setView(true); cfg("TO_TR").setView(true); cfg("TO_PRT").setView(true); cfg("TO_ADDR").setView(true); break; case 2: cfg("TO_TR").setView(true); cfg("TO_PRT").setView(true); break; } } modif(); return true; } void Node::regCR( int id, const SIO &val, const string &tp, bool wr ) { bool varSec = false; map::iterator it; if(tp == "C" || (varSec=(tp=="CI"))) { map &blk = varSec ? data->coilI : (wr?data->coilW:data->coilR); if((it=blk.find(id)) == blk.end()) blk[id] = val; else mess_warning(nodePath().c_str(), _("Coil(%s) %d already registered for IO#%d. IO#%d will be disabled for processing the coil %d!"), tp.c_str(), id, it->second.id, val.id, id); } else if(tp == "R" || (varSec=(tp=="RI"))) { map &blk = varSec ? data->regI : (wr?data->regW:data->regR); if((it=blk.find(id)) == blk.end()) blk[id] = val; else mess_warning(nodePath().c_str(), _("Register(%s) %d already registered for IO#%d. IO#%d will be disabled for processing the register %d!"), tp.c_str(), id, it->second.id, val.id, id); } else throw TError(nodePath().c_str(), _("Error of the ModBUS data type '%s'!"), tp.c_str()); } void Node::load_( TConfig *icfg ) { if(!SYS->chkSelDB(DB())) throw TError(); bool en_prev = enableStat(); if(en_prev) setEnable(false); //!!!! Reload only disabled if(icfg) *(TConfig*)this = *icfg; else { //cfgViewAll(true); cfg("DT_PROG").setExtVal(true); TBDS::dataGet(fullDB(), owner().nodePath()+tbl(), *this); //cfg("MODE").setI(cfg("MODE").getI()); } if(!progTr()) cfg("DT_PROG").setExtVal(false, true); loadIO(); if(en_prev && !enableStat()) setEnable(true); } void Node::loadIO( ) { if(mode() == MD_DATA) { ResAlloc res(nRes, false); //TFunction *f = data && data->func() ? data->func() : this; //Load IO vector u_pos; TConfig cfg(&owner().nodeIOEl()); cfg.cfg("NODE_ID").setS(id(), TCfg::ForceUse); cfg.cfg("VALUE").setExtVal(true); for(int ioCnt = 0; TBDS::dataSeek(fullDB()+"_io",owner().nodePath()+tbl()+"_io",ioCnt++,cfg,TBDS::UseCache); ) { string sid = cfg.cfg("ID").getS(); int iid = ioId(sid); if(!isDAQTmpl) { //Position storing int pos = cfg.cfg("POS").getI(); while((int)u_pos.size() <= pos) u_pos.push_back(""); u_pos[pos] = sid; int flgs = cfg.cfg("FLAGS").getI(); if(flgs&IsLink) { flgs |= TPrmTempl::CfgLink; flgs &= ~IsLink; } if(iid < 0) iid = ioIns(new IO(sid.c_str(),cfg.cfg("NAME").getS(),(IO::Type)cfg.cfg("TYPE").getI(),flgs|IO::Output,"",false), pos); else { io(iid)->setName(cfg.cfg("NAME").getS()); io(iid)->setType((IO::Type)cfg.cfg("TYPE").getI()); io(iid)->setFlg(flgs|IO::Output); } cfg.cfg("VALUE").setNoTransl(io(iid)->type()!=IO::String); if(io(iid)->flg()&TPrmTempl::CfgLink) io(iid)->setRez(cfg.cfg("VALUE").getS()); else io(iid)->setDef(cfg.cfg("VALUE").getS()); } if(data && iid >= 0) { if(io(iid)->flg()&TPrmTempl::CfgLink) data->lnkAddrSet(iid, cfg.cfg("VALUE").getS()); else data->setS(iid, cfg.cfg("VALUE").getS()); } } if(!isDAQTmpl) { //Clearing not saved IO for(int iIO = 0, iP = 0; iIO < ioSize(); iIO++) { if(io(iIO)->id() == "f_frq" || io(iIO)->id() == "f_start" || io(iIO)->id() == "f_stop") continue; for(iP = 0; iP < (int)u_pos.size() && u_pos[iP] != io(iIO)->id(); iP++) ; if(iP >= (int)u_pos.size()) { ioDel(iIO); iIO--; } } //Remove holes for(unsigned iP = 0; iP < u_pos.size(); ) if(u_pos[iP].empty()) u_pos.erase(u_pos.begin()+iP); else iP++; //Position fixing for(int iP = 0; iP < (int)u_pos.size(); iP++) { int iid = ioId(u_pos[iP]); if(iid != iP) try{ ioMove(iid,iP); } catch(...){ } } } } } void Node::save_( ) { mTimeStamp = SYS->sysTm(); TBDS::dataSet(fullDB(), owner().nodePath()+tbl(), *this); saveIO(); setDB(DB(), true); } void Node::saveIO( ) { if(mode() == MD_DATA) { ResAlloc res(nRes, false); TFunction *f = data && data->func() ? data->func() : this; //Save IO TConfig cfg(&owner().nodeIOEl()); cfg.cfg("NODE_ID").setS(id(), true); for(int iIO = 0; iIO < f->ioSize(); iIO++) { if(f->io(iIO)->flg()&TPrmTempl::LockAttr) continue; cfg.cfg("ID").setS(f->io(iIO)->id()); cfg.cfg("NAME").setS(f->io(iIO)->name()); cfg.cfg("TYPE").setI(f->io(iIO)->type()); cfg.cfg("FLAGS").setI(f->io(iIO)->flg()&TPrmTempl::CfgLink); cfg.cfg("POS").setI(iIO); cfg.cfg("VALUE").setNoTransl(f->io(iIO)->type() != IO::String || (f->io(iIO)->flg()&TPrmTempl::CfgLink)); cfg.cfg("VALUE").setView(true); if(f->io(iIO)->flg()&TPrmTempl::CfgLink) //f->io(iIO)->rez()); { data ? cfg.cfg("VALUE").setS(data->lnkAddr(iIO)) : cfg.cfg("VALUE").setView(false); } else if(data && data->func()) cfg.cfg("VALUE").setS(data->getS(iIO)); else cfg.cfg("VALUE").setS(f->io(iIO)->def()); TBDS::dataSet(fullDB()+"_io", owner().nodePath()+tbl()+"_io", cfg); } //Clearing IO cfg.cfgViewAll(false); for(int fldCnt = 0; TBDS::dataSeek(fullDB()+"_io",owner().nodePath()+tbl()+"_io",fldCnt++,cfg); ) { string sio = cfg.cfg("ID").getS(); if(f->ioId(sio) < 0) { if(!TBDS::dataDel(fullDB()+"_io",owner().nodePath()+tbl()+"_io",cfg,TBDS::UseAllKeys|TBDS::NoException)) break; fldCnt--; } } } } void Node::setEnable( bool vl ) { if(mEn == vl) return; cntReq = 0; ResAlloc res(nRes, true); //Enable node if(vl && mode() == MD_DATA) { // Data structure allocate if(!data) data = new SData(this, ("ModBusNode_"+id()).c_str()); else data->rReg = data->wReg = data->rCoil = data->wCoil = data->rCoilI = data->rRegI = 0; string lfnc = TSYS::strParse(progLang(), 0, "."), wfnc = TSYS::strParse(progLang(), 1, "."); isDAQTmpl = SYS->daq().at().tmplLibPresent(lfnc) && SYS->daq().at().tmplLibAt(lfnc).at().present(wfnc); // Try the DAQ template if(isDAQTmpl) data->setFunc(&SYS->daq().at().tmplLibAt(lfnc).at().at(wfnc).at().func().at()); // Compile the function else try { if(progLang().empty()) data->setFunc(this); else { string mWorkProg = SYS->daq().at().at(TSYS::strSepParse(progLang(),0,'.')).at().compileFunc(TSYS::strSepParse(progLang(),1,'.'), *this, prog()); data->setFunc(&((AutoHD)SYS->nodeAt(mWorkProg)).at()); } } catch(TError &err) { mess_err(nodePath().c_str(),_("Error compiling function on the language '%s': %s"),progLang().c_str(),err.mess.c_str()); throw; } // Links, registers and coils init data->addLinksAttrs(); for(int iIO = 0; iIO < data->func()->ioSize(); iIO++) { string ioId = data->func()->io(iIO)->id(); if(data->func()->io(iIO)->flg()&TPrmTempl::CfgLink) data->lnkAddrSet(iIO, data->func()->io(iIO)->rez()); string atp, ai, mode, atpM, atpSub, sTmp; int off = 0; if(ioId[0] == 'C' || ioId[0] == 'R') { // Unified mode if(TSYS::strParse(ioId,1,":").size()) { atp = TSYS::strParse(ioId, 0, ":", &off); ai = TSYS::strParse(ioId, 0, ":", &off); mode= TSYS::strParse(ioId, 0, ":", &off); } // Specific mode else { for(off = 0; off < (int)ioId.size() && !isdigit(ioId[off]); off++) ; atp = ioId.substr(0,off); ai = ioId.substr(off); for(off = (int)ioId.size()-1; off >= 0 && !isdigit(ioId[off]); off--) if(tolower(ioId[off]) == 'w') mode += "w"; else if(tolower(ioId[off]) == '~') mode += "~"; else if(tolower(ioId[off]) == 'e') mode += "e"; } } else continue; atpM = TSYS::strParse(atp, 0, "_"), atpSub = TSYS::strParse(atp, 1, "_"); bool isWr = (mode.find("w") != string::npos); bool isBackSeq = (mode.find("~") != string::npos); bool isEToggle = (mode.find("e") != string::npos); int reg = strtol(ai.c_str(), NULL, 0); if(atpM[0] == 'R') { if(atpSub == "i" || atpSub == "i4" || atpSub == "f") { int reg2 = (sTmp=TSYS::strParse(ai,1,",")).empty() ? (reg+1) : strtol(sTmp.c_str(),NULL,0); regCR(reg, SIO(iIO,atpSub[0],isBackSeq?1:0,isEToggle), atpM); regCR(reg2, SIO(iIO,atpSub[0],isBackSeq?0:1,isEToggle), atpM); if(isWr) { regCR(reg, SIO(iIO,atpSub[0],isBackSeq?1:0,isEToggle), atpM, true); regCR(reg2, SIO(iIO,atpSub[0],isBackSeq?0:1,isEToggle), atpM, true); } } else if(atpSub == "i8" || atpSub == "d") { int reg2 = (sTmp=TSYS::strParse(ai,1,",")).empty() ? (reg+1) : strtol(sTmp.c_str(),NULL,0), reg3 = (sTmp=TSYS::strParse(ai,2,",")).empty() ? (reg2+1) : strtol(sTmp.c_str(),NULL,0), reg4 = (sTmp=TSYS::strParse(ai,3,",")).empty() ? (reg3+1) : strtol(sTmp.c_str(),NULL,0); regCR(reg, SIO(iIO,atpSub[0],isBackSeq?3:0,isEToggle), atpM); regCR(reg2, SIO(iIO,atpSub[0],isBackSeq?2:1,isEToggle), atpM); regCR(reg3, SIO(iIO,atpSub[0],isBackSeq?1:2,isEToggle), atpM); regCR(reg4, SIO(iIO,atpSub[0],isBackSeq?0:3,isEToggle), atpM); if(isWr) { regCR(reg, SIO(iIO,atpSub[0],isBackSeq?3:0,isEToggle), atpM, true); regCR(reg2, SIO(iIO,atpSub[0],isBackSeq?2:1,isEToggle), atpM, true); regCR(reg3, SIO(iIO,atpSub[0],isBackSeq?1:2,isEToggle), atpM, true); regCR(reg4, SIO(iIO,atpSub[0],isBackSeq?0:3,isEToggle), atpM, true); } } else { regCR(reg, SIO(iIO,atpSub[0],0,isEToggle), atpM); if(isWr) regCR(reg, SIO(iIO,atpSub[0],0,isEToggle), atpM, true); if(atpSub == "s") { int N = (sTmp=TSYS::strParse(ai,1,",")).empty() ? 0 : vmin(100,strtol(sTmp.c_str(),NULL,0)); if(!N) N = 10; //Default length 10 registers and maximum 100 for(int iR = reg+1; iR < (reg+N); iR++) { regCR(iR, SIO(iIO,atpSub[0],iR-reg,isEToggle), atpM); if(isWr) regCR(iR, SIO(iIO,atpSub[0],iR-reg,isEToggle), atpM, true); } } } } if(atpM[0] == 'C') { regCR(reg, iIO, atpM); if(isWr) regCR(reg, iIO, atpM, true); } } data->chkLnkNeed = data->initLnks(); //Load IO //loadIO(); // Start task SYS->taskCreate(nodePath('.',true), 0, Task, this); } //Disable node if(!vl) { // Stop the calc data task if(prcSt) SYS->taskDestroy(nodePath('.',true), &endRun); // Data structure delete if(data) { delete data; data = NULL; } } mEn = vl; } string Node::getStatus( ) { string rez = _("Disabled. "); if(enableStat()) { rez = _("Enabled. "); switch(mode()) { case MD_DATA: rez += TSYS::strMess(_("Spent time %s[%s]. Requests %.4g. Read registers %.4g, coils %.4g, register inputs %.4g, coil inputs %.4g.\n" "Wrote registers %.4g, coils %.4g."), tm2s(SYS->taskUtilizTm(nodePath('.',true))).c_str(), tm2s(SYS->taskUtilizTm(nodePath('.',true),true)).c_str(), cntReq, data->rReg, data->rCoil, data->rRegI, data->rCoilI, data->wReg, data->wCoil); break; case MD_GT_ND: case MD_GT_NET: rez += TSYS::strMess(_("Requests %.4g."), cntReq); break; } } return rez; } bool Node::req( const string &itr, const string &iprt, unsigned char inode, string &pdu ) { ResAlloc res(nRes, false); //Check for allow request if(!enableStat() || pdu.empty() || !((inTransport() == "*" && mode() != MD_GT_NET) || inTransport() == itr) || !(addr() == inode || mode() == MD_GT_NET) || !(prt() == "*" || iprt == prt())) return false; cntReq++; //Data mode requests process if(mode() == MD_DATA) switch(pdu[0]) { case TProt::FC_MULT_COILS: case TProt::FC_MULT_COILS_IN: { //Read multiple coils int c_sz = 0; if(pdu.size() == 5) c_sz = (uint16_t)(pdu[3]<<8) | (uint8_t)pdu[4]; if(c_sz < 1 || c_sz > 2000) { pdu.assign(1, pdu[0]|0x80); pdu += 0x3; return true; } int c_addr = (uint16_t)(pdu[1]<<8) | (uint8_t)pdu[2]; pdu.assign(1, pdu[0]); pdu += (char)(c_sz/8+((c_sz%8)?1:0)); pdu += string(pdu[1], (char)0); bool isData = false; map &blk = (pdu[0]==0x01) ? data->coilR : data->coilI; map::iterator itc; for(int i_c = c_addr; i_c < (c_addr+c_sz); i_c++) { if((itc=blk.find(i_c)) != blk.end()) { if(data->getB(itc->second.id)) pdu[2+(i_c-c_addr)/8] |= (1<<((i_c-c_addr)%8)); isData = true; } } if(!isData) { pdu.assign(1, pdu[0]|0x80); pdu += 0x2; return true; } if(pdu[0] == 0x01) data->rCoil += c_sz; else data->rCoilI += c_sz; return true; } case TProt::FC_MULT_REGS: case TProt::FC_MULT_REGS_IN: { //Read multiple registers int r_sz = 0; if(pdu.size() == 5) r_sz = (uint16_t)(pdu[3]<<8) | (uint8_t)pdu[4]; if(r_sz < 1 || r_sz > 125) { pdu.assign(1, pdu[0]|0x80); pdu += 0x3; return true; } int r_addr = (uint16_t)(pdu[1]<<8) | (uint8_t)pdu[2]; pdu.assign(1, pdu[0]); pdu += (char)(r_sz*2); bool isData = false; map grpVals; map &blk = (pdu[0]==0x03) ? data->regR : data->regI; map::iterator itr; for(int iR = r_addr; iR < (r_addr+r_sz); iR++) { uint16_t val = 0; if((itr=blk.find(iR)) != blk.end()) { switch(itr->second.sTp) { case 'i': { union { uint16_t r[4]; int64_t i; } wl; map::iterator grpValIt = grpVals.find(itr->second.id); if(grpValIt != grpVals.end()) wl.i = grpValIt->second.getI(); else { wl.i = data->getI(itr->second.id); grpVals[itr->second.id] = wl.i; } val = wl.r[itr->second.pos]; val = itr->second.endian ? TSYS::i16_LE(val) : TSYS::i16_BE(val); break; } case 'f': { union { uint16_t r[2]; float f; } wl; map::iterator grpValIt = grpVals.find(itr->second.id); if(grpValIt != grpVals.end()) wl.f = grpValIt->second.getR(); else { wl.f = data->getR(itr->second.id); grpVals[itr->second.id] = wl.f; } val = wl.r[itr->second.pos]; val = itr->second.endian ? TSYS::i16_LE(val) : TSYS::i16_BE(val); break; } case 'd': { union { uint16_t r[4]; double d; } wl; map::iterator grpValIt = grpVals.find(itr->second.id); if(grpValIt != grpVals.end()) wl.d = grpValIt->second.getR(); else { wl.d = data->getR(itr->second.id); grpVals[itr->second.id] = wl.d; } val = wl.r[itr->second.pos]; val = itr->second.endian ? TSYS::i16_LE(val) : TSYS::i16_BE(val); break; } case 's': { string valIO; map::iterator grpValIt = grpVals.find(itr->second.id); if(grpValIt != grpVals.end()) valIO = grpValIt->second.getS(); else { valIO = data->getS(itr->second.id); grpVals[itr->second.id] = valIO; } valIO.resize(vmax(0,(itr->second.pos+1)*2), 0); val = TSYS::getUnalign16(valIO.data()+itr->second.pos*2); val = itr->second.endian ? TSYS::i16_BE(val) : TSYS::i16_LE(val); break; } default: val = data->getI(itr->second.id); val = itr->second.endian ? TSYS::i16_LE(val) : TSYS::i16_BE(val); break; } isData = true; } pdu += string((char*)&val, 2); //TSYS::strEncode(string((char*)&val,2), TSYS::Reverse); } if(!isData) { pdu.assign(1, pdu[0]|0x80); pdu += 0x2; return true; } if(pdu[0] == 0x03) data->rReg += r_sz; else data->rRegI += r_sz; return true; } case TProt::FC_SING_COILS_WR: { //Write single coil if(pdu.size() != 5) { pdu.assign(1, pdu[0]|0x80); pdu += 0x3; return true; } int c_addr = (uint16_t)(pdu[1]<<8) | (uint8_t)pdu[2]; map::iterator ic = data->coilW.find(c_addr); if(ic == data->coilW.end()) { pdu.assign(1, pdu[0]|0x80); pdu += 0x2; } else { data->setB(ic->second.id, (bool)pdu[3]); data->lnkOutput(ic->second.id, (bool)pdu[3]); } data->wCoil++; return true; } case TProt::FC_SING_REGS_WR: { //Write single register if(pdu.size() != 5) { pdu.assign(1, pdu[0]|0x80); pdu += 0x3; return true; } int r_addr = (uint16_t)(pdu[1]<<8) | (uint8_t)pdu[2]; map >::iterator il; map::iterator ir = data->regW.find(r_addr); if(ir == data->regW.end()) { pdu.assign(1, pdu[0]|0x80); pdu += 0x2; } else { uint16_t val = (uint16_t)(pdu[4]<<8) | (uint8_t)(pdu[3]); switch(ir->second.sTp) { case 'i': { union { uint16_t r[4]; int64_t i; } wl; wl.i = data->getI(ir->second.id); wl.r[ir->second.pos] = ir->second.endian ? TSYS::i16_LE(val) : TSYS::i16_BE(val); data->setI(ir->second.id, wl.i); data->lnkOutput(ir->second.id, wl.i); break; } case 'f': { union { uint16_t r[2]; float f; } wl; wl.f = data->getR(ir->second.id); wl.r[ir->second.pos] = ir->second.endian ? TSYS::i16_LE(val) : TSYS::i16_BE(val); data->setR(ir->second.id, wl.f); data->lnkOutput(ir->second.id, wl.f); break; } case 'd': { union { uint16_t r[4]; double d; } wl; wl.d = data->getR(ir->second.id); wl.r[ir->second.pos] = ir->second.endian ? TSYS::i16_LE(val) : TSYS::i16_BE(val); data->setR(ir->second.id, wl.d); data->lnkOutput(ir->second.id, wl.d); break; } case 's': { val = ir->second.endian ? TSYS::i16_BE(val) : TSYS::i16_LE(val); string valIO = data->getS(ir->second.id); valIO.resize(vmax((int)valIO.size(),(ir->second.pos+1)*2), 0); valIO.replace(ir->second.pos*2, 2, (char*)&val, 2); data->setS(ir->second.id, valIO); data->lnkOutput(ir->second.id, valIO); break; } default: val = ir->second.endian ? TSYS::i16_LE(val) : TSYS::i16_BE(val); data->setI(ir->second.id, val); data->lnkOutput(ir->second.id, val); break; } } data->wReg++; return true; } case TProt::FC_MULT_COILS_WR: { //Write multiple coils if(pdu.size() < 6) { pdu.assign(1, pdu[0]|0x80); pdu += 0x3; return true; } int c_aSt = (uint16_t)(pdu[1]<<8) | (uint8_t)pdu[2]; int c_aCnt = (uint16_t)(pdu[3]<<8) | (uint8_t)pdu[4]; int bCnt = (uint8_t)pdu[5]; if((int)pdu.size() != (6+bCnt) || bCnt < (c_aCnt/8)) { pdu.assign(1, pdu[0]|0x80); pdu += 0x3; return true; } bool noWrReg = false; for(int i_c = 0; i_c < c_aCnt; i_c++) { map::iterator ic = data->coilW.find(c_aSt+i_c); if(ic == data->coilW.end()) noWrReg = true; else { data->setB(ic->second.id, (bool)(1&(pdu[6+i_c/8]>>(i_c%8)))); data->lnkOutput(ic->second.id, data->getB(ic->second.id)); data->wCoil++; } } if(noWrReg) { pdu.assign(1, pdu[0]|0x80); pdu += 0x2; } else pdu.resize(5); return true; } case TProt::FC_MULT_REGS_WR: { //Write multiple register if(pdu.size() < 6) { pdu.assign(1, pdu[0]|0x80); pdu += 0x3; return true; } int r_aSt = (uint16_t)(pdu[1]<<8) | (uint8_t)pdu[2]; int r_aCnt = (uint16_t)(pdu[3]<<8) | (uint8_t)pdu[4]; int bCnt = (uint8_t)pdu[5]; if((int)pdu.size() != (6+bCnt) || bCnt < (r_aCnt*2) || r_aCnt > 123) { pdu.assign(1, pdu[0]|0x80); pdu += 0x3; return true; } ResAlloc res(data->func()->fRes(), true); bool noWrReg = false; map grpVals; map >::iterator il; for(int iR = 0; iR < r_aCnt; iR++) { map::iterator ir = data->regW.find(r_aSt+iR); if(ir == data->regW.end()) noWrReg = true; else { uint16_t val = (uint16_t)(pdu[6+iR*2+1]<<8) | (uint8_t)pdu[6+iR*2]; switch(ir->second.sTp) { case 'i': { union { uint16_t r[4]; int64_t i; } wl; map::iterator grpValIt = grpVals.find(ir->second.id); wl.i = (grpValIt != grpVals.end()) ? grpValIt->second.getI() : data->getI(ir->second.id); wl.r[ir->second.pos] = ir->second.endian ? TSYS::i16_LE(val) : TSYS::i16_BE(val); grpVals[ir->second.id] = wl.i; break; } case 'f': { union { uint16_t r[2]; float f; } wl; map::iterator grpValIt = grpVals.find(ir->second.id); wl.f = (grpValIt != grpVals.end()) ? grpValIt->second.getR() : data->getR(ir->second.id); wl.r[ir->second.pos] = ir->second.endian ? TSYS::i16_LE(val) : TSYS::i16_BE(val); grpVals[ir->second.id] = wl.f; break; } case 'd': { union { uint16_t r[4]; double d; } wl; map::iterator grpValIt = grpVals.find(ir->second.id); wl.d = (grpValIt != grpVals.end()) ? grpValIt->second.getR() : data->getR(ir->second.id); wl.r[ir->second.pos] = ir->second.endian ? TSYS::i16_LE(val) : TSYS::i16_BE(val); grpVals[ir->second.id] = wl.d; break; } case 's': { val = ir->second.endian ? TSYS::i16_BE(val) : TSYS::i16_LE(val); map::iterator grpValIt = grpVals.find(ir->second.id); string valIO = (grpValIt != grpVals.end()) ? grpValIt->second.getS() : data->getS(ir->second.id); valIO.resize(vmax((int)valIO.size(),(ir->second.pos+1)*2), 0); valIO.replace(ir->second.pos*2, 2, (char*)&val, 2); grpVals[ir->second.id] = valIO; break; } default: val = ir->second.endian ? TSYS::i16_LE(val) : TSYS::i16_BE(val); data->setI(ir->second.id, val); data->lnkOutput(ir->second.id, val); break; } } data->wReg++; } // Real write cached values for(map::iterator iG = grpVals.begin(); iG != grpVals.end(); ++iG) { data->set(iG->first, iG->second); data->lnkOutput(iG->first, iG->second); } if(noWrReg) { pdu.assign(1, pdu[0]|0x80); pdu += 0x2; } else pdu.resize(5); return true; } default: pdu.assign(1, pdu[0]|0x80); pdu += 0x1; return true; } //Gateway mode requests process else if(mode() == MD_GT_ND || mode() == MD_GT_NET) { try { AutoHD tr = SYS->transport().at().at(TSYS::strSepParse(cfg("TO_TR").getS(),0,'.')).at(). outAt(TSYS::strSepParse(cfg("TO_TR").getS(),1,'.')); if(!tr.at().startStat()) tr.at().start(); XMLNode req(cfg("TO_PRT").getS()); req.setAttr("id", id())-> setAttr("node", (mode()==MD_GT_NET)?i2s(inode):cfg("TO_ADDR").getS())-> setAttr("reqTry", "3")-> setText(pdu); tr.at().messProtIO(req, "ModBus"); if(!req.attr("err").empty()) { pdu.assign(1, pdu[0]|0x80); pdu += 0xA; } pdu = req.text(); } catch(TError &err) { pdu.assign(1, pdu[0]|0x80); pdu += 0xA; } return true; } return true; } void *Node::Task( void *ind ) { Node &nd = *(Node*)ind; nd.endRun = false; nd.prcSt = true; bool isStart = true; bool isStop = false; int ioFrq = nd.data->ioId("f_frq"); int ioStart = nd.data->ioId("f_start"); int ioStop = nd.data->ioId("f_stop"); for(unsigned int clc = 0; true; clc++) { if(SYS->daq().at().subStartStat()) { if(nd.data->chkLnkNeed) nd.data->chkLnkNeed = nd.data->initLnks(); //Setting special IO if(ioFrq >= 0) nd.data->setR(ioFrq, (float)1/nd.period()); if(ioStart >= 0) nd.data->setB(ioStart, isStart); if(ioStop >= 0) nd.data->setB(ioStop, isStop); try { //Get input links nd.data->inputLinks(); nd.data->setMdfChk(true); nd.data->calc(); //Put output links nd.data->outputLinks(); } catch(TError &err) { mess_err(err.cat.c_str(), "%s", err.mess.c_str()); mess_err(nd.nodePath().c_str(), _("Error calculation the node function.")); } } if(isStop) break; TSYS::taskSleep((int64_t)(1e9*nd.period())); if(nd.endRun) isStop = true; isStart = false; if(SYS->modifCalc()) nd.modif(); } nd.prcSt = false; return NULL; } void Node::cntrCmdProc( XMLNode *opt ) { string lfnc = TSYS::strSepParse(progLang(), 0, '.'), wfnc = TSYS::strSepParse(progLang(), 1, '.'); bool isDirFunc = lfnc.size() && wfnc.size() && !(SYS->daq().at().tmplLibPresent(lfnc) && SYS->daq().at().tmplLibAt(lfnc).at().present(wfnc)); //Get page info if(opt->name() == "info") { TCntrNode::cntrCmdProc(opt); ctrMkNode("oscada_cntr",opt,-1,"/",_("Node: ")+name(),RWRWR_,"root",SPRT_ID); if(ctrMkNode("area",opt,-1,"/nd",_("Node"))) { if(ctrMkNode("area",opt,-1,"/nd/st",_("State"))) { ctrMkNode("fld",opt,-1,"/nd/st/status",_("Status"),R_R_R_,"root",SPRT_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/nd/st/en_st",_("Enabled"),RWRWR_,"root",SPRT_ID,1,"tp","bool"); ctrMkNode("fld",opt,-1,"/nd/st/db",_("Storage"),RWRWR_,"root",SPRT_ID,4, "tp","str", "dest","select", "select","/db/list","help",TMess::labStor().c_str()); if(DB(true).size()) ctrMkNode("comm",opt,-1,"/nd/st/removeFromDB",TSYS::strMess(_("Remove from '%s'"), TMess::labStorFromCode(DB(true)).c_str()).c_str(),RWRW__,"root",SPRT_ID,1,"help",TMess::labStorRem(mDB).c_str()); ctrMkNode("fld",opt,-1,"/nd/st/timestamp",_("Date of modification"),R_R_R_,"root",SPRT_ID,1,"tp","time"); } if(ctrMkNode("area",opt,-1,"/nd/cfg",_("Configuration"))) { TConfig::cntrCmdMake(opt, "/nd/cfg", 0, "root", SPRT_ID, RWRWR_); ctrMkNode("fld",opt,-1,"/nd/cfg/MODE",EVAL_STR,enableStat()?R_R_R_:RWRWR_,"root",SDAQ_ID); // Append configuration properties XMLNode *xt = ctrId(opt->childGet(0),"/nd/cfg/InTR",true); if(xt) xt->setAttr("dest","sel_ed")->setAttr("select","/nd/cfg/ls_itr"); xt = ctrId(opt->childGet(0),"/nd/cfg/TO_TR",true); if(xt) xt->setAttr("dest","sel_ed")->setAttr("select","/nd/cfg/ls_otr"); ctrRemoveNode(opt, "/nd/cfg/DT_PROG"); ctrRemoveNode(opt, "/nd/cfg/DT_PR_TR"); ctrRemoveNode(opt, "/nd/cfg/TIMESTAMP"); /*xt = ctrId(opt->childGet(0),"/nd/cfg/DT_PROG",true); if(xt) xt->parent()->childDel(xt);*/ if(mode() == MD_DATA) ctrMkNode("fld",opt,-1,"/nd/cfg/progLang",_("DAQ template or direct procedure language"),(enableStat()?R_R_R_:RWRWR_),"root",SPRT_ID,3, "tp","str", "dest","select", "select","/plang/list"); } } ResAlloc res(nRes, false); if(mode() == MD_DATA && ctrMkNode("area",opt,-1,"/dt",_("Data"))) { if((isDirFunc || enableStat()) && ctrMkNode("table",opt,-1,"/dt/io",_("IO"),RWRWR_,"root",SPRT_ID,2,"s_com","add,ins,move,del","rows","15")) { ctrMkNode("list",opt,-1,"/dt/io/id",_("Identifier"),(enableStat()?R_R_R_:RWRWR_),"root",SPRT_ID,2, "tp","str", "help",_("For the \"Id\" field, a specific ModBus data record form is provided:\n" " \"R{N}[w~]\", \"RI{N}[w~]\" - specific register (and input) form, can be expanded by the suffixes:\n" " \"i\"-Int32, \"f\"-Float, \"d\"-Double, \"s\"-String;\n" " \"R:{N}[:w~]\", \"RI:{N}[:w~]\" - classic register (and input) form, can be expanded by the suffixes:\n" " \"i4\"-Int32, \"i8\"-Int64, \"f\"-Float, \"d\"-Double, \"s\"-String;\n" " \"C{N}[w]\", \"CI{N}[w]\", \"C:{N}[:w]\", \"CI:{N}[:w]\" - coil (and input).\n" "Where:\n" " {N} - ModBus data address of the device (dec, hex or octal) [0...65535];\n" " w~e - flags: write mode 'w',\n" " registers order inversion '~',\n" " register 'e'ndian toggle (to LE in generic and BE for strings).\n" "Examples:\n" " \"R0x300w\" - register access;\n" " \"C100w\" - coil access, allowed to write;\n" " \"R_f200\", \"R_f200~\" - get float from the registers 200 and 201, 201 and 200;\n" " \"R_i400,300\" - get int32 from the registers 300 and 400;\n" " \"R_s15,20\" - get string (registers block) from the register 15 and size 20;\n" " \"R_i8:0x10:w\" - get and set int64 into the registers [0x10-0x13];\n" " \"R_d:0x20,0x30\" - get double float point (8 byte) from the registers [0x20,0x30-0x32].")); ctrMkNode("list",opt,-1,"/dt/io/nm",_("Name"),(enableStat()?R_R_R_:RWRWR_),"root",SPRT_ID,1,"tp","str"); ctrMkNode("list",opt,-1,"/dt/io/tp",_("Type"),(enableStat()?R_R_R_:RWRWR_),"root",SPRT_ID,5,"tp","dec","idm","1","dest","select", "sel_id",TSYS::strMess("%d;%d;%d;%d;%d",IO::Real,IO::Integer,IO::Boolean,IO::String,IO::Object).c_str(), "sel_list",_("Real;Integer;Boolean;String;Object")); ctrMkNode("list",opt,-1,"/dt/io/lnk",_("Link"),(enableStat()?R_R_R_:RWRWR_),"root",SPRT_ID,1,"tp","bool"); ctrMkNode("list",opt,-1,"/dt/io/vl",_("Value"),RWRWR_,"root",SPRT_ID,1,"tp","str"); } if((enableStat() && !isDAQTmpl) || isDirFunc) { if(progTr()) ctrMkNode("fld",opt,-1,"/dt/progTr",_(cfg("DT_PR_TR").fld().descr()),RWRWR_,"root",SPRT_ID,1, "tp","bool"); ctrMkNode("fld",opt,-1,"/dt/prog",_(cfg("DT_PROG").fld().descr()),RWRWR_,"root",SPRT_ID,3, "tp","str", "rows","10", "SnthHgl","1"); } } if(data && data->func()) data->TPrmTempl::Impl::cntrCmdProc(opt, "/cfg"); return; } //Process command to page string a_path = opt->attr("path"); if(a_path == "/nd/st/status" && ctrChkNode(opt)) opt->setText(getStatus()); else if(a_path == "/nd/st/en_st") { if(ctrChkNode(opt,"get",RWRWR_,"root",SPRT_ID,SEC_RD)) opt->setText(enableStat()?"1":"0"); if(ctrChkNode(opt,"set",RWRWR_,"root",SPRT_ID,SEC_WR)) setEnable(s2i(opt->text())); } else if(a_path == "/nd/st/db") { if(ctrChkNode(opt,"get",RWRWR_,"root",SPRT_ID,SEC_RD)) opt->setText(DB()); if(ctrChkNode(opt,"set",RWRWR_,"root",SPRT_ID,SEC_WR)) setDB(opt->text()); } else if(a_path == "/nd/st/removeFromDB" && ctrChkNode(opt,"set",RWRW__,"root",SPRT_ID,SEC_WR)) postDisable(NodeRemoveOnlyStor); else if(a_path == "/nd/st/timestamp" && ctrChkNode(opt)) opt->setText(i2s(timeStamp())); else if(a_path == "/nd/cfg/ls_itr" && ctrChkNode(opt)) { if(mode() != MD_GT_NET) opt->childAdd("el")->setText("*"); vector sls; SYS->transport().at().inTrList(sls); for(unsigned iS = 0; iS < sls.size(); iS++) opt->childAdd("el")->setText(sls[iS]); } else if(a_path == "/nd/cfg/ls_otr" && ctrChkNode(opt)) { vector sls; SYS->transport().at().outTrList(sls); for(unsigned iS = 0; iS < sls.size(); iS++) opt->childAdd("el")->setText(sls[iS]); } else if(a_path == "/nd/cfg/progLang") { if(ctrChkNode(opt,"get",RWRWR_,"root",SPRT_ID,SEC_RD)) opt->setText(progLang()); if(ctrChkNode(opt,"set",RWRWR_,"root",SPRT_ID,SEC_WR)) setProgLang(opt->text()); } else if(a_path.find("/nd/cfg") == 0) TConfig::cntrCmdProc(opt, TSYS::pathLev(a_path,2), "root", SPRT_ID, RWRWR_); else if(a_path == "/plang/list") { vector lls, ls; //Templates SYS->daq().at().tmplLibList(lls); for(unsigned iL = 0; iL < lls.size(); iL++) { SYS->daq().at().tmplLibAt(lls[iL]).at().list(ls); for(unsigned iT = 0; iT < ls.size(); iT++) opt->childAdd("el")->setText(lls[iL]+"."+ls[iT]); } TCntrNode::cntrCmdProc(opt); } else if(a_path.find("/dt") == 0 || a_path.find("/cfg") == 0) { ResAlloc res(nRes, false); if(a_path == "/dt/io") { TFunction *f = data && data->func() ? data->func() : this; if(ctrChkNode(opt,"get",RWRWR_,"root",SPRT_ID,SEC_RD)) { XMLNode *nId = ctrMkNode("list",opt,-1,"/dt/io/id",""); XMLNode *nNm = ctrMkNode("list",opt,-1,"/dt/io/nm",""); XMLNode *nType = ctrMkNode("list",opt,-1,"/dt/io/tp",""); XMLNode *nLnk = ctrMkNode("list",opt,-1,"/dt/io/lnk",""); XMLNode *nVal = ctrMkNode("list",opt,-1,"/dt/io/vl",""); for(int id = 0; id < f->ioSize(); id++) { if(nId) nId->childAdd("el")->setText(f->io(id)->id()); if(nNm) nNm->childAdd("el")->setText(trD(f->io(id)->name())); if(nType) nType->childAdd("el")->setText(i2s(f->io(id)->type())); if(nLnk) nLnk->childAdd("el")->setText((f->io(id)->flg()&TPrmTempl::CfgLink)?"1":"0"); if(nVal) nVal->childAdd("el")->setText((data && data->func()) ? data->getS(id) : f->io(id)->def()); } } if(ctrChkNode(opt,"add",RWRWR_,"root",SPRT_ID,SEC_WR)) { if(enableStat()) throw TError(nodePath(), _("Turn off the node for this operation")); IO *ioPrev = f->ioSize() ? f->io(f->ioSize()-1) : NULL; if(ioPrev) f->ioAdd(new IO(TSYS::strLabEnum(ioPrev->id()).c_str(),TSYS::strLabEnum(ioPrev->name()).c_str(),ioPrev->type(),ioPrev->flg()&(~TPrmTempl::LockAttr))); else f->ioAdd(new IO("new",trDSet("",_("New IO")),IO::Integer,IO::Output)); modif(); } if(ctrChkNode(opt,"ins",RWRWR_,"root",SPRT_ID,SEC_WR)) { if(enableStat()) throw TError(nodePath(), _("Turn off the node for this operation")); int row = s2i(opt->attr("row")); IO *ioPrev = row ? f->io(row-1) : NULL; if(ioPrev) f->ioIns(new IO(TSYS::strLabEnum(ioPrev->id()).c_str(),TSYS::strLabEnum(ioPrev->name()).c_str(),ioPrev->type(),ioPrev->flg()&(~TPrmTempl::LockAttr)), row); else f->ioIns(new IO("new",trDSet("",_("New IO")),IO::Integer,IO::Output), row); modif(); } if(ctrChkNode(opt,"del",RWRWR_,"root",SPRT_ID,SEC_WR)) { if(enableStat()) throw TError(nodePath(), _("Turn off the node for this operation")); int row = s2i(opt->attr("row")); if(f->io(row)->flg()&TPrmTempl::LockAttr) throw TError(nodePath(), _("Deleting a locked attribute is not allowed.")); f->ioDel(row); modif(); } if(ctrChkNode(opt,"move",RWRWR_,"root",SPRT_ID,SEC_WR)) { if(enableStat()) throw TError(nodePath(), _("Turn off the node for this operation")); f->ioMove(s2i(opt->attr("row")), s2i(opt->attr("to"))); modif(); } if(ctrChkNode(opt,"set",RWRWR_,"root",SPRT_ID,SEC_WR)) { int row = s2i(opt->attr("row")); string col = opt->attr("col"); if(enableStat() && col != "vl") throw TError(nodePath(), _("Turn off the node for this operation")); if(f->io(row)->flg()&TPrmTempl::LockAttr) throw TError(nodePath(), _("Changing a locked attribute is not allowed.")); if((col == "id" || col == "nm") && !opt->text().size()) throw TError(nodePath(), _("Empty value is not allowed.")); if(col == "id") f->io(row)->setId(opt->text()); else if(col == "nm") f->io(row)->setName(trDSet(f->io(row)->name(),opt->text())); else if(col == "tp") f->io(row)->setType((IO::Type)s2i(opt->text())); else if(col == "lnk") { f->io(row)->setFlg(s2i(opt->text()) ? (f->io(row)->flg()|TPrmTempl::CfgLink) : (f->io(row)->flg() & ~TPrmTempl::CfgLink)); f->io(row)->setDef(""); } else if(col == "vl") { if(data && data->func()) { data->setS(row, opt->text()); data->lnkOutput(row, opt->text()); } else f->io(row)->setDef(opt->text()); } modif(); } } else if(a_path == "/dt/progTr") { if(ctrChkNode(opt,"get",RWRWR_,"root",SPRT_ID,SEC_RD)) opt->setText(i2s(progTr())); if(ctrChkNode(opt,"set",RWRWR_,"root",SPRT_ID,SEC_WR)) setProgTr(s2i(opt->text())); } else if(a_path == "/dt/prog") { if(ctrChkNode(opt,"get",RWRWR_,"root",SPRT_ID,SEC_RD)) opt->setText(prog()); if(ctrChkNode(opt,"set",RWRWR_,"root",SPRT_ID,SEC_WR)) setProg(opt->text()); if(ctrChkNode(opt,"SnthHgl",RWRWR_,"root",SDAQ_ID,SEC_RD)) try { SYS->daq().at().at(TSYS::strParse(progLang(),0,".")).at(). compileFuncSnthHgl(TSYS::strParse(progLang(),1,"."),*opt); } catch(...) { } } else if(data && data->func() && data->TPrmTempl::Impl::cntrCmdProc(opt,"/cfg")) ; } else TCntrNode::cntrCmdProc(opt); }