//OpenSCADA module Transport.Serial file: mod_serial.cpp /*************************************************************************** * Copyright (C) 2009-2023 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 #include #include #include #include #include #include #include #include "mod_serial.h" #ifdef HAVE_LINUX_SERIAL_H # include #endif #ifdef HAVE_LINUX_I2C_DEV_H # include #endif #ifdef HAVE_LINUX_SPI_SPIDEV_H # include #endif #define DEF_TMS "6:320" #define DEF_TaskPrior 0 #define DEF_MdmTm 30 #define DEF_MdmLifeTime 40 #define DEF_MdmPreInit 0.5 #define DEF_MdmPostInit 1.0 #define DEF_MdmInitStr1 "ATZ" #define DEF_MdmInitStr2 "" #define DEF_MdmInitResp "OK" #define DEF_MdmRingReq "RING" #define DEF_MdmRingAnswer "ATA" #define DEF_MdmRingAnswerResp "CONNECT" #define DEF_MdmDialStr "ATDT" #define DEF_MdmCnctResp "CONNECT" #define DEF_MdmBusyResp "BUSY" #define DEF_MdmNoCarResp "NO CARRIER" #define DEF_MdmNoDialToneResp "NO DIALTONE" #define DEF_MdmExit "+++" #define DEF_MdmHangUp "+++ATH" #define DEF_MdmHangUpResp "OK" #define DEF_TMS_OUT "640:6" #define DEF_NoStopOnProceed false //************************************************ //* Modul info! * #define MOD_ID "Serial" #define MOD_NAME trS("Serial interfaces") #define MOD_TYPE STR_ID #define VER_TYPE STR_VER #define MOD_VER "2.7.3" #define AUTHORS trS("Roman Savochenko, Maxim Kochetkov (2016)") #define DESCRIPTION trS("Provides transport based on the serial interfaces.\ It is used for data exchanging via the serial interfaces of the type RS232, RS485, GSM and similar.") #define LICENSE "GPL2" //************************************************ Serial::TTr *Serial::mod; extern "C" { #ifdef MOD_INCL TModule::SAt tr_Serial_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 *tr_Serial_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 Serial::TTr(source); return NULL; } } using namespace Serial; //************************************************ //* TTr * //************************************************ TTr::TTr( string name ) : TTypeTransport(MOD_ID) { mod = this; modInfoMainSet(MOD_NAME, MOD_TYPE, MOD_VER, AUTHORS, DESCRIPTION, LICENSE, name); } TTr::~TTr( ) { try { modStop(); } catch(...) { } } AutoHD TTr::inAt( const string &name ) { return TTypeTransport::inAt(name); } AutoHD TTr::outAt( const string &name ) { return TTypeTransport::outAt(name); } void TTr::load_( ) { TTypeTransport::load_(); //Load parameters from command line } TTransportIn *TTr::In( const string &name, const string &idb ) { return new TTrIn(name,idb,&owner().inEl()); } TTransportOut *TTr::Out( const string &name, const string &idb ){ return new TTrOut(name,idb,&owner().outEl()); } string TTr::outAddrHelp( ) { return string(_("The serial transport has the address format \"{dev}[:{speed}[:{format}[:{opts}[:{modTel}]]]]\", where:\n" " dev - serial device address (/dev/ttyS0);\n" " speed - device speed (300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200,\n" " 230400, 460800, 500000, 576000 or 921600);\n" " format - asynchronous data format '{size}{parity}{stop}' (8N1, 7E1, 5O2);\n" " opts - different options, mostly for flow control, separated by ',':\n" " '[-]h' - hardware (CRTSCTS);\n" " '[-]s' - software (IXON|IXOFF);\n" " 'rts' - using of the RTS signal for transferring(false) and checking for echo, for raw RS-485;\n" " 'rts1' - using of the RTS signal for transferring(true) and checking for echo, for raw RS-485;\n" " 'rtsne' - using of the RTS signal for transferring(false) and without checking for echo, for raw RS-485;\n" " 'rts1ne' - using of the RTS signal for transferring(true) and without checking for echo, for raw RS-485;\n" " '[-]RS485' - using RS-485 mode, through TIOCSRS485.\n" " modTel - modem telephone, the field presence do switch transport to work with modem mode.")) + "\n\n|| " + outTimingsHelp(); } string TTr::outTimingsHelp( ) { return _("Connection timings in the format \"{conn}:{symbol}[-{NextReqMult}][:{KeepAliveTm}[:{rtsDelay1}:{rtsDelay2}]]\", where:\n" " conn - maximum time of waiting the connecting response, in milliseconds;\n" " symbol - maximum time of one symbol, used for the frame end detection, in milliseconds;\n" " NextReqMult - next request's multiplicator to the time, 4 by default;\n" " KeepAliveTm - keep alive timeout to reconnect the transport, in seconds;\n" " use the value < 0 for disconnecting the transport after missing response at each request;\n" " rtsDelay1 - delay between the transmitter activation with RTS signal and start up of the transmission, in milliseconds;\n" " rtsDelay2 - delay between the transmitting and disconnecting the transmitter with RTS signal, in milliseconds.\n" "Can be prioritatile specified in the address field as the second global argument, as such \"/dev/rfcomm0:9600||1000:40-20\"."); } bool TTr::devLock( const string &dn, bool check ) { MtxAlloc res(dataRes(), true); if(check) return mDevLock[dn]; if(mDevLock[dn]) return false; mDevLock[dn] = true; return true; } void TTr::devUnLock( const string &dn ) { MtxAlloc res(dataRes(), true); mDevLock[dn] = false; } void TTr::perSYSCall( unsigned int cnt ) { TTypeTransport::perSYSCall(cnt); //Check all output transports vector ls; outList(ls); for(unsigned iL = 0; iL < ls.size(); iL++) try{ outAt(ls[iL]).at().check(); } catch(TError &err) { } } void TTr::writeLine( int fd, const string &ln, bool noNewLn ) { string obuf = ln + (noNewLn?"":"\x0D\x0A"); for(unsigned wOff = 0, kz = 0; wOff != obuf.size(); wOff += kz) if((kz=write(fd,obuf.data()+wOff,obuf.size()-wOff)) <= 0) throw TError(mod->nodePath(), _("Error writing a line.")); mess_debug(mod->nodePath().c_str(), _("Sent to the modem %d: '%s'."), fd, ln.c_str()); } string TTr::expect( int fd, const string& expLst, int tm ) { char buf[255]; string rez, stmp; int rl, kz; time_t st_exp = time(NULL); while((time(NULL)-st_exp) < tm) { //rl = messIO(NULL,0,buf,sizeof(buf),0,true); fd_set rd_fd; struct timeval tv; tv.tv_sec = tm-(time(NULL)-st_exp); tv.tv_usec = 0; FD_ZERO(&rd_fd); FD_SET(fd,&rd_fd); kz = select(fd+1,&rd_fd,NULL,NULL,&tv); if(kz == 0) continue; else if(kz < 0) throw TError(mod->nodePath(), _("Error reading from the serial interfaces.")); else if(FD_ISSET(fd,&rd_fd)) { rl = read(fd, buf, sizeof(buf)); rez.append(buf,rl); for(int off = 0; (stmp=TSYS::strParse(expLst,0,"\n",&off)).size(); ) if(rez.find(stmp) != string::npos) { mess_debug(mod->nodePath().c_str(), _("Received from the modem %d: '%s'."), fd, stmp.c_str()); return stmp; } } } if(rez.empty()) mess_debug(mod->nodePath().c_str(), _("No response from the modem %d."), fd); else mess_debug(mod->nodePath().c_str(), _("No expected response, but receiving from the modem %d: '%s'."), fd, rez.c_str()); return ""; } //************************************************ //* TTrIn * //************************************************ TTrIn::TTrIn( string name, const string &idb, TElem *el ) : TTransportIn(name,idb,el), fd(-1), endrun(false), trIn(0), trOut(0), tmMax(0), prcTm(0), prcTmMax(0), mTaskPrior(DEF_TaskPrior), mMdmTm(DEF_MdmTm), mMdmPreInit(DEF_MdmPreInit), mMdmPostInit(DEF_MdmPostInit), mMdmInitStr1(DEF_MdmInitStr1), mMdmInitStr2(DEF_MdmInitStr2), mMdmInitResp(DEF_MdmInitResp), mMdmRingReq(DEF_MdmRingReq), mMdmRingAnswer(DEF_MdmRingAnswer), mMdmRingAnswerResp(DEF_MdmRingAnswerResp), mMdmMode(false), mMdmDataMode(false), mRTSfc(false), mRTSlvl(false), mRTSEcho(false) { setAddr("/dev/ttyS0:19200:8E2"); setTimings(DEF_TMS); } TTrIn::~TTrIn( ) { } void TTrIn::load_( ) { setTimings(prm("TMS",DEF_TMS)); setTaskPrior(prm("TaskPrior",DEF_TaskPrior)); setMdmTm(prm("MdmTm",DEF_MdmTm)); setMdmPreInit((double)prm("MdmPreInit",DEF_MdmPreInit)); setMdmPostInit((double)prm("MdmPostInit",DEF_MdmPostInit)); setMdmInitStr1(prm("MdmInitStr1",DEF_MdmInitStr1)); setMdmInitStr2(prm("MdmInitStr2",DEF_MdmInitStr2)); setMdmInitResp(prm("MdmInitResp",DEF_MdmInitResp)); setMdmRingReq(prm("MdmRingReq",DEF_MdmRingReq)); setMdmRingAnswer(prm("MdmRingAnswer",DEF_MdmRingAnswer)); setMdmRingAnswerResp(prm("MdmRingAnswerResp",DEF_MdmRingAnswerResp)); } void TTrIn::save_( ) { prm("TMS", timings(), true); prm("TaskPrior", taskPrior(), true); prm("MdmTm", mdmTm(), true); prm("MdmPreInit", mdmPreInit(), true); prm("MdmPostInit", mdmPostInit(), true); prm("MdmInitStr1", mdmInitStr1(), true); prm("MdmInitStr2", mdmInitStr2(), true); prm("MdmInitResp", mdmInitResp(), true); prm("MdmRingReq", mdmRingReq(), true); prm("MdmRingAnswer", mdmRingAnswer(), true); prm("MdmRingAnswerResp", mdmRingAnswerResp(), true); TTransportIn::save_(); } bool TTrIn::cfgChange( TCfg &co, const TVariant &pc ) { if(co.name() == "ADDR") { //Times adjust int speed = s2i(TSYS::strParse(co.getS(),1,":")); if(speed) { float symbMlt = TRegExp("^.+\\/ttyS\\d+$").test(TSYS::strParse(co.getS(),0,":")) ? 1 : 3; setTimings(r2s(symbMlt*11e4/(float)speed,2,'f')+":"+i2s((512*11*1000)/speed)+ "::"+TSYS::strParse(timings(),3,":")+":"+TSYS::strParse(timings(),4,":")); } } return TTransportIn::cfgChange(co, pc); } string TTrIn::getStatus( ) { string rez = TTransportIn::getStatus( ); if(startStat()) { rez += TSYS::strMess(_("Traffic in %s, out %s. Maximum char timeout %.4g ms. "), TSYS::cpct2str(trIn).c_str(),TSYS::cpct2str(trOut).c_str(),tmMax); if(mess_lev() == TMess::Debug) rez += TSYS::strMess(_("Processing time %s[%s]. "), tm2s(1e-6*prcTm).c_str(), tm2s(1e-6*prcTmMax).c_str()); } return rez; } void TTrIn::setTimings( const string &vl ) { float wCharTm = vmax(0.01, vmin(1e3,s2r(TSYS::strParse(vl,0,":")))); int wFrTm = vmax(1, vmin(10000,s2i(TSYS::strParse(vl,1,":")))); mTimings = TSYS::strMess("%g:%d", wCharTm, wFrTm); float wRtsDel1 = vmax(0, vmin(10000,s2r(TSYS::strParse(vl,3,":")))); float wRtsDel2 = vmax(0, vmin(10000,s2r(TSYS::strParse(vl,4,":")))); if(wRtsDel1 || wRtsDel2) mTimings += TSYS::strMess("::%g:%g", wRtsDel1, wRtsDel2); modif(); } void TTrIn::connect( ) { try { //Open and setup device // Serial port open mDevPort = TSYS::strParse(addr(), 0, ":"); // O_NONBLOCK is used for prevent function open() hang on several USB->RS485 converters fd = open(mDevPort.c_str(), O_RDWR|O_NOCTTY|O_NONBLOCK); if(fd < 0) throw TError(nodePath().c_str(), _("Error the serial port '%s' %s: %s."), mDevPort.c_str(), "open", strerror(errno)); // Set serial port parameters struct termios tio; if(tcgetattr(fd,&tio) < 0) throw TError(nodePath().c_str(), _("Error the serial port '%s' %s: %s."), mDevPort.c_str(), "tcgetattr", strerror(errno)); tio.c_iflag = 0; tio.c_oflag = 0; tio.c_cflag |= (CREAD|CLOCAL); tio.c_lflag = 0; tio.c_cc[VTIME] = 0; //< inter-character timer unused tio.c_cc[VMIN] = 0; //< blocking read until 0 character arrives // Set speed string speed = sTrm(TSYS::strParse(addr(),1,":")); if(!speed.empty()) { speed_t tspd = B9600; switch(s2i(speed)) { case 300: tspd = B300; break; case 600: tspd = B600; break; case 1200: tspd = B1200; break; case 2400: tspd = B2400; break; case 4800: tspd = B4800; break; case 9600: tspd = B9600; break; case 19200: tspd = B19200; break; case 38400: tspd = B38400; break; case 57600: tspd = B57600; break; case 115200: tspd = B115200; break; case 230400: tspd = B230400; break; case 460800: tspd = B460800; break; case 500000: tspd = B500000; break; case 576000: tspd = B576000; break; case 921600: tspd = B921600; break; default: throw TError(nodePath().c_str(),_("Error the speed '%s'."),speed.c_str()); } cfsetispeed(&tio, tspd); cfsetospeed(&tio, tspd); } // Set asynchronous data format string format = sTrm(TSYS::strParse(addr(),2,":")); if(!format.empty()) { if(format.size() != 3) throw TError(nodePath().c_str(),_("Error the asynchronous data format '%s'."),format.c_str()); // Set byte length int len = format[0]-'0'; if(len < 5 || len > 8) throw TError(nodePath().c_str(),_("Error char length %d."),len); tio.c_cflag &= ~CSIZE; switch(len) { case 5: tio.c_cflag |= CS5; break; case 6: tio.c_cflag |= CS6; break; case 7: tio.c_cflag |= CS7; break; case 8: tio.c_cflag |= CS8; break; } // Set parity char parity = tolower(format[1]); switch(parity) { case 'e': tio.c_cflag |= PARENB; tio.c_cflag &= ~PARODD;break; case 'o': tio.c_cflag |= PARENB; tio.c_cflag |= PARODD; break; case 'n': tio.c_cflag &= ~PARENB; break; default: throw TError(nodePath().c_str(),_("Error the parity checking mode '%c'."),parity); } // Set stop bits number int stopbt = format[2]-'0'; if(stopbt == 1) tio.c_cflag &= ~CSTOPB; else if(stopbt == 2) tio.c_cflag |= CSTOPB; else throw TError(nodePath().c_str(),_("Error the stop bits %d."),stopbt); } // Set options mRTSfc = mRTSlvl = mRTSEcho = false; string opts = sTrm(TSYS::strParse(addr(),3,":")), opt; for(int off = 0; (opt=TSYS::strParse(opts,0,",",&off)).size(); ) { if(strcasecmp(opt.c_str(),"h") == 0) tio.c_cflag |= CRTSCTS; else if(strcasecmp(opt.c_str(),"-h") == 0) tio.c_cflag &= ~CRTSCTS; if(strcasecmp(opt.c_str(),"s") == 0) tio.c_iflag |= (IXON|IXOFF|IXANY); else if(strcasecmp(opt.c_str(),"-s") == 0) tio.c_iflag &= ~(IXON|IXOFF|IXANY); if(strcasecmp(opt.c_str(),"rts") == 0) { mRTSfc = mRTSEcho = true; } else if(strcasecmp(opt.c_str(),"rts1") == 0){ mRTSfc = mRTSlvl = mRTSEcho = true; } else if(strcasecmp(opt.c_str(),"rts1ne") == 0) { mRTSfc = mRTSlvl = true; } else if(strcasecmp(opt.c_str(),"rtsne") == 0) { mRTSfc = true; } #ifdef SER_RS485_ENABLED #ifndef TIOCSRS485 #define TIOCSRS485 0x542f #endif // Standard RS-485 mode if(strcasecmp(opt.c_str(),"rs485") == 0 || strcasecmp(opt.c_str(),"-rs485") == 0) { serial_rs485 rs485conf; memset(&rs485conf, 0, sizeof(serial_rs485)); if(strcasecmp(opt.c_str(),"rs485") == 0) rs485conf.flags |= SER_RS485_ENABLED; ioctl(fd, TIOCSRS485, &rs485conf); } #endif } // Set port's data tcflush(fd, TCIOFLUSH); if(tcsetattr(fd,TCSANOW,&tio) < 0) throw TError(nodePath().c_str(), _("Error the serial port '%s' %s: %s."), mDevPort.c_str(), "tcsetattr", strerror(errno)); //Modem init mMdmMode = s2i(TSYS::strParse(addr(),4,":")); if(mMdmMode) { mMdmDataMode = false; // Send init 1 string if(!mdmInitStr1().empty()) { if(mdmPreInit() > 0) { TSYS::sysSleep(mdmPreInit()*0.5); TTr::writeLine(fd,""); TSYS::sysSleep(mdmPreInit()*0.5); } TTr::writeLine(fd,mdmInitStr1()); if(TTr::expect(fd,mdmInitResp(),mdmTm()).empty()) throw TError(nodePath().c_str(),_("No response to the initial request '%s'."),mdmInitStr1().c_str()); TSYS::sysSleep(mdmPostInit()); } // Send init 2 string if(!mdmInitStr2().empty()) { if(mdmPreInit() > 0) { TSYS::sysSleep(mdmPreInit()*0.5); TTr::writeLine(fd,""); TSYS::sysSleep(mdmPreInit()*0.5); } TTr::writeLine(fd,mdmInitStr2()); if(TTr::expect(fd,mdmInitResp(),mdmTm()).empty()) throw TError(nodePath().c_str(),_("No response to the initial request '%s'."),mdmInitStr2().c_str()); TSYS::sysSleep(mdmPostInit()); } } } catch(TError &err) { if(fd >= 0 && close(fd) != 0) mess_warning(nodePath().c_str(), _("Closing the file %d error '%s (%d)'!"), fd, strerror(errno), errno); fd = -1; throw; } } void TTrIn::start( ) { if(runSt) return; //Status clear trIn = trOut = prcTm = prcTmMax = 0; tmMax = 0; connect(); //Start listen task SYS->taskCreate(nodePath('.',true), taskPrior(), Task, this); TTransportIn::start(); if(logLen()) pushLogMess(_("Connected")); } void TTrIn::stop( ) { if(!runSt) return; if(mMdmMode && mMdmDataMode) mod->devUnLock(mDevPort); mMdmMode = mMdmDataMode = false; //Status clear trIn = trOut = 0; tmMax = 0; SYS->taskDestroy(nodePath('.',true), &endrun); if(fd >= 0 && close(fd) != 0) mess_warning(nodePath().c_str(), _("Closing the file %d error '%s (%d)'!"), fd, strerror(errno), errno); fd = -1; TTransportIn::stop(); if(logLen()) pushLogMess(_("Disconnected")); } void *TTrIn::Task( void *tr_in ) { TTrIn *tr = (TTrIn*)tr_in; tr->runSt = true; tr->endrun = false; vector< AutoHD > prot_in; struct timeval tv; ssize_t r_len = 0; string req, answ; char buf[1000]; fd_set rw_fd; int kz, sec; float wCharTm = s2r(TSYS::strParse(tr->timings(),0,":")); int wFrTm = 1000*s2i(TSYS::strParse(tr->timings(),1,":")); float wRtsDelay1 = 1e-3*s2r(TSYS::strParse(tr->timings(),3,":")); float wRtsDelay2 = 1e-3*s2r(TSYS::strParse(tr->timings(),4,":")); int64_t stFrTm = 0, tmW = 0, tmTmp1; fcntl(tr->fd, F_SETFL, 0); if(tr->mRTSfc) { ioctl(tr->fd, TIOCMGET, &sec); //Disable transfer for read allow if(!tr->mRTSlvl) sec |= TIOCM_RTS; else sec &= ~TIOCM_RTS; ioctl(tr->fd, TIOCMSET, &sec); } while(!tr->endrun) { // Char timeout while(true) { tv.tv_sec = 0; tv.tv_usec = (int)(1.5e3*wCharTm); FD_ZERO(&rw_fd); FD_SET(tr->fd, &rw_fd); if(select(tr->fd+1,&rw_fd,NULL,NULL,&tv) <= 0) { if(tr->endrun || !req.empty()) break; continue; } r_len = read(tr->fd, buf, sizeof(buf)); if(r_len <= 0) break; // Requests statistic tmTmp1 = TSYS::curTime(); if(req.empty()) stFrTm = tmW = tmTmp1; if(tmW) tr->tmMax = vmax(tr->tmMax,1e-3*(tmTmp1-tmW)); tmW = tmTmp1; req += string(buf, r_len); if((TSYS::curTime()-stFrTm) > wFrTm) break; } if(tr->endrun || req.empty()) { if(r_len == 0) { if(close(tr->fd) != 0) mess_warning(tr->nodePath().c_str(), _("Closing the file %d error '%s (%d)'!"), tr->fd, strerror(errno), errno); tr->fd = -1; if(tr->mMdmMode && tr->mMdmDataMode) { // Reconnect try after hung up by remote agent mod->devUnLock(tr->mDevPort); TSYS::sysSleep(10); try{ tr->connect(); } catch(TError &err) { break; } continue; } } break; } tr->trIn += req.size(); if(mess_lev() == TMess::Debug) mess_debug(tr->nodePath().c_str(), _("The serial interface received the message '%d'."), req.size()); if(tr->logLen()) tr->pushLogMess(_("< Received from\n"), req, -1); //Check for device lock and RING request from modem if(tr->mMdmMode && !tr->mMdmDataMode) { if(mod->devLock(tr->mDevPort,true)) continue; if(req.find(tr->mdmRingReq()) != string::npos) { if(!mod->devLock(tr->mDevPort)) continue; // Send ring answer TTr::writeLine(tr->fd,tr->mdmRingAnswer()); if(TTr::expect(tr->fd,tr->mdmRingAnswerResp(),tr->mdmTm()).empty()) continue; tr->mMdmDataMode = true; req = ""; continue; } } //Send message to the protocols string prts = tr->protocols(), prt, subPrt, tAnsw; for(int off = 0, iP = 0; (prt=TSYS::strParse(prts,0,";",&off)).size(); iP++, answ += tAnsw, tAnsw = "") { try { if(iP >= (int)prot_in.size() || prot_in[iP].freeStat()) { AutoHD proto = SYS->protocol().at().modAt(TSYS::strParse(prt,0,".")); subPrt = TSYS::strParse(prt, 1, "."); string n_pr = tr->id() + i2s(tr->fd) + (subPrt.size()?"#"+subPrt:""); if(!proto.at().openStat(n_pr)) proto.at().open(n_pr, tr, "\n"+i2s(tr->fd)); if(iP < (int)prot_in.size()) prot_in[iP] = proto.at().at(n_pr); else prot_in.push_back(proto.at().at(n_pr)); } int64_t stTm = 0; if(mess_lev() == TMess::Debug) stTm = SYS->curTime(); prot_in[iP].at().mess(req, tAnsw); if(mess_lev() == TMess::Debug && stTm) { tr->prcTm = SYS->curTime() - stTm; tr->prcTmMax = vmax(tr->prcTmMax, tr->prcTm); } } catch(TError &err) { mess_err(tr->nodePath().c_str(),"%s",err.mess.c_str() ); mess_err(tr->nodePath().c_str(),_("Error requesting the protocol.")); } } //Sending request if(answ.size()) { if(mess_lev() == TMess::Debug) mess_debug(tr->nodePath().c_str(), _("The serial interface replied by the message '%d'."), answ.size()); // Pure RS-485 flow control: Clear RTS for transfer allow if(tr->mRTSfc) { if(!tr->mRTSlvl) sec &= ~TIOCM_RTS; else sec |= TIOCM_RTS; ioctl(tr->fd, TIOCMSET, &sec); if(wRtsDelay1) TSYS::sysSleep(wRtsDelay1); } ssize_t wL = 1; unsigned wOff = 0; for( ; wOff != answ.size() && wL > 0; wOff += wL) { wL = write(tr->fd, answ.data()+wOff, answ.size()-wOff); if(wL == 0) { mess_err(tr->nodePath().c_str(), _("Write: the answer is zero byte.")); break; } else if(wL < 0) { if(errno == EAGAIN) { tv.tv_sec = wFrTm/1000000; tv.tv_usec = wFrTm%1000000; FD_ZERO(&rw_fd); FD_SET(tr->fd, &rw_fd); kz = select(tr->fd+1, NULL, &rw_fd, NULL, &tv); if(kz > 0 && FD_ISSET(tr->fd,&rw_fd)) { wL = 0; continue; } } mess_err(tr->nodePath().c_str(), _("Write: error '%s (%d)'."), strerror(errno), errno); break; } tr->trOut += vmax(0, wL); if(wL > 0 && tr->logLen()) tr->pushLogMess(_("> Transmitted to\n"), string(answ.data()+wOff,wL), 1); } // Hard read for wait request echo and transfer disable if(tr->mRTSfc) { if(tr->mRTSEcho && wOff == answ.size()) { char echoBuf[255]; int64_t mLstReqTm = TSYS::curTime(); for(unsigned r_off = 0; r_off < answ.size(); ) { int kz = read(tr->fd,echoBuf,vmin(answ.size()-r_off,sizeof(echoBuf))); if(kz == 0 || (kz == -1 && errno == EAGAIN)) { if((TSYS::curTime()-mLstReqTm) > wCharTm*answ.size()*1e3) break; sched_yield(); continue; } if(kz < 0 || memcmp(echoBuf,answ.data()+r_off,kz) != 0) break; r_off += kz; } } else tcdrain(tr->fd); if(wRtsDelay2) TSYS::sysSleep(wRtsDelay2); if(!tr->mRTSlvl) sec |= TIOCM_RTS; else sec &= ~TIOCM_RTS; ioctl(tr->fd, TIOCMSET, &sec); } answ = ""; } req = ""; } //Close protocol for(unsigned iP = 0; iP < prot_in.size(); iP++) { if(prot_in[iP].freeStat()) continue; try { string n_pr = prot_in[iP].at().name(); AutoHD proto = AutoHD(&prot_in[iP].at().owner()); prot_in[iP].free(); proto.at().close(n_pr); } catch(TError &err) { mess_err(tr->nodePath().c_str(), "%s", err.mess.c_str() ); mess_err(tr->nodePath().c_str(), _("Error closing the protocol.")); } } tr->runSt = false; return NULL; } void TTrIn::cntrCmdProc( XMLNode *opt ) { //Get page info if(opt->name() == "info") { TTransportIn::cntrCmdProc(opt); ctrMkNode("fld",opt,-1,"/prm/cfg/ADDR",EVAL_STR,startStat()?R_R_R_:RWRWR_,"root",STR_ID,3, "dest","sel_ed","select","/prm/cfg/devLS","help", _("The serial transport has the address format \"{dev}[:{speed}[:{format}[:{opts}[:{mdm}]]]]\", where:\n" " dev - serial device address (/dev/ttyS0);\n" " speed - device speed (300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200,\n" " 230400, 460800, 500000, 576000 or 921600);\n" " format - asynchronous data format '{size}{parity}{stop}' (8N1, 7E1, 5O2);\n" " opts - different options, mostly for flow control, separated by ',':\n" " '[-]h' - hardware (CRTSCTS);\n" " '[-]s' - software (IXON|IXOFF);\n" " 'rts' - using of the RTS signal for transferring(false) and checking for echo, for raw RS-485;\n" " 'rts1' - using of the RTS signal for transferring(true) and checking for echo, for raw RS-485;\n" " 'rtsne' - using of the RTS signal for transferring(false) and without checking for echo, for raw RS-485;\n" " 'rts1ne' - using of the RTS signal for transferring(true) and without checking for echo, for raw RS-485;\n" " '[-]RS485' - using RS-485 mode, through TIOCSRS485.\n" " mdm - modem mode, listen for 'RING'.")); ctrMkNode("fld",opt,-1,"/prm/cfg/PROT",EVAL_STR,startStat()?R_R_R_:RWRWR_,"root",STR_ID); int pos = 0; ctrMkNode("fld",opt,pos++,"/aprm/TMS",_("Timings"),startStat()?R_R_R_:RWRWR_,"root",STR_ID,2,"tp","str","help", _("Connection timings in the format \"{symbol}:{frm}[::{rtsDelay1}:{rtsDelay2}]\", where:\n" " symbol - maximum time of one symbol, used for the frame end detection and for timeout of the next request, in milliseconds;\n" " frm - maximum frame length, in milliseconds;\n" " rtsDelay1 - delay between the transmitter activation with RTS signal and start up of the transmission, in milliseconds;\n" " rtsDelay2 - delay between the transmitting and disconnecting the transmitter with RTS signal, in milliseconds.")); ctrMkNode("fld",opt,pos++,"/aprm/taskPrior",_("Priority"),startStat()?R_R_R_:RWRWR_,"root",STR_ID,2, "tp","dec", "help",TMess::labTaskPrior().c_str()); if(s2i(TSYS::strParse(addr(),4,":")) && ctrMkNode("area",opt,pos++,"/aprm/mod",_("Modem"),R_R_R_,"root",STR_ID)) { ctrMkNode("fld",opt,-1,"/aprm/mod/tm",_("Timeout, seconds"),RWRWR_,"root",STR_ID,1,"tp","dec"); ctrMkNode("fld",opt,-1,"/aprm/mod/preInitDl",_("Pre-initial delay, seconds"),RWRWR_,"root",STR_ID,1,"tp","real"); ctrMkNode("fld",opt,-1,"/aprm/mod/postInitDl",_("Post-initial delay, seconds"),RWRWR_,"root",STR_ID,1,"tp","real"); ctrMkNode("fld",opt,-1,"/aprm/mod/initStr1",_("Initialization string 1"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/initStr2",_("Initialization string 2"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/initResp",_("Initial response"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/ringReq",_("Ring request"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/ringAnswer",_("Ring answer"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/ringAnswerResp",_("Result of the ring answer"),RWRWR_,"root",STR_ID,1,"tp","str"); } return; } //Process command to page string a_path = opt->attr("path"); if(a_path == "/prm/cfg/devLS" && ctrChkNode(opt)) { int off = 0; TSYS::ctrListFS(opt, TSYS::strParse(addr(),0,":",&off), ";"); string suf = (off < (int)addr().size()) ? addr().substr(off) : ""; for(unsigned iT = 0; suf.size() && iT < opt->childSize(); iT++) if(opt->childGet(iT)->text().size()) opt->childGet(iT)->setText(opt->childGet(iT)->text()+":"+suf); } else if(a_path == "/aprm/TMS") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(timings()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setTimings(opt->text()); } else if(a_path == "/aprm/taskPrior") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(i2s(taskPrior())); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setTaskPrior(s2i(opt->text())); } else if(a_path == "/aprm/mod/tm") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(i2s(mdmTm())); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmTm(s2i(opt->text())); } else if(a_path == "/aprm/mod/preInitDl") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(r2s(mdmPreInit())); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmPreInit(s2r(opt->text())); } else if(a_path == "/aprm/mod/postInitDl") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(r2s(mdmPostInit())); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmPostInit(s2r(opt->text())); } else if(a_path == "/aprm/mod/initStr1") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmInitStr1()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmInitStr1(opt->text()); } else if(a_path == "/aprm/mod/initStr2") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmInitStr2()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmInitStr2(opt->text()); } else if(a_path == "/aprm/mod/initResp") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmInitResp()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmInitResp(opt->text()); } else if(a_path == "/aprm/mod/ringReq") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmRingReq()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmRingReq(opt->text()); } else if(a_path == "/aprm/mod/ringAnswer") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmRingAnswer()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmRingAnswer(opt->text()); } else if(a_path == "/aprm/mod/ringAnswerResp") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmRingAnswerResp()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmRingAnswerResp(opt->text()); } else TTransportIn::cntrCmdProc(opt); } //************************************************ //* TTrOut * //************************************************ TTrOut::TTrOut(string name, const string &idb, TElem *el) : TTransportOut(name,idb,el), mNotStopOnProceed(DEF_NoStopOnProceed), fd(-1), mKeepAliveLstTm(0), trIn(0), trOut(0), mMdmTm(DEF_MdmTm), mMdmLifeTime(DEF_MdmLifeTime), mMdmPreInit(DEF_MdmPreInit), mMdmPostInit(DEF_MdmPostInit), mMdmInitStr1(DEF_MdmInitStr1), mMdmInitStr2(DEF_MdmInitStr2), mMdmInitResp(DEF_MdmInitResp), mMdmDialStr(DEF_MdmDialStr), mMdmCnctResp(DEF_MdmCnctResp), mMdmBusyResp(DEF_MdmBusyResp), mMdmNoCarResp(DEF_MdmNoCarResp), mMdmNoDialToneResp(DEF_MdmNoDialToneResp), mMdmExit(DEF_MdmExit), mMdmHangUp(DEF_MdmHangUp), mMdmHangUpResp(DEF_MdmHangUpResp), mMdmMode(false), mMdmDataMode(false), mRTSfc(false), mRTSlvl(false), mRTSEcho(false), mI2C(false), mSPI(false) { setAddr("/dev/ttyS0:19200:8E2"); setTimings(DEF_TMS_OUT, true); } TTrOut::~TTrOut( ) { } void TTrOut::load_( ) { setTimings(prm("TMS",DEF_TMS_OUT)); setNotStopOnProceed((char)prm("NoStopOnProceed",DEF_NoStopOnProceed)); setMdmTm(prm("MdmTm",DEF_MdmTm)); setMdmLifeTime(prm("MdmLifeTime",DEF_MdmLifeTime)); setMdmPreInit((double)prm("MdmPreInit",DEF_MdmPreInit)); setMdmPostInit((double)prm("MdmPostInit",DEF_MdmPostInit)); setMdmInitStr1(prm("MdmInitStr1",DEF_MdmInitStr1)); setMdmInitStr2(prm("MdmInitStr2",DEF_MdmInitStr2)); setMdmInitResp(prm("MdmInitResp",DEF_MdmInitResp)); setMdmDialStr(prm("MdmDialStr",DEF_MdmDialStr)); setMdmCnctResp(prm("MdmCnctResp",DEF_MdmCnctResp)); setMdmBusyResp(prm("MdmBusyResp",DEF_MdmBusyResp)); setMdmNoCarResp(prm("MdmNoCarResp",DEF_MdmNoCarResp)); setMdmNoDialToneResp(prm("MdmNoDialToneResp",DEF_MdmNoDialToneResp)); setMdmExit(prm("MdmExit",DEF_MdmExit)); setMdmHangUp(prm("MdmHangUp",DEF_MdmHangUp)); setMdmHangUpResp(prm("MdmHangUpResp",DEF_MdmHangUpResp)); } void TTrOut::save_( ) { prm("TMS", timings(), true); prm("NoStopOnProceed", notStopOnProceed(), true); prm("MdmTm", mdmTm(), true); prm("MdmLifeTime", mdmLifeTime(), true); prm("MdmPreInit", mdmPreInit(), true); prm("MdmPostInit", mdmPostInit(), true); prm("MdmInitStr1", mdmInitStr1(), true); prm("MdmInitStr2", mdmInitStr2(), true); prm("MdmInitResp", mdmInitResp(), true); prm("MdmDialStr", mdmDialStr(), true); prm("MdmCnctResp", mdmCnctResp(), true); prm("MdmBusyResp", mdmBusyResp(), true); prm("MdmNoCarResp", mdmNoCarResp(), true); prm("MdmNoDialToneResp", mdmNoDialToneResp(), true); prm("MdmExit", mdmExit(), true); prm("MdmHangUp", mdmHangUp(), true); prm("MdmHangUpResp", mdmHangUpResp(), true); TTransportOut::save_(); } string TTrOut::getStatus( ) { string rez = TTransportOut::getStatus(); if(startStat()) { rez += TSYS::strMess(_("Traffic in %s, out %s. "),TSYS::cpct2str(trIn).c_str(),TSYS::cpct2str(trOut).c_str()); if(mess_lev() == TMess::Debug && (respTmMax || respSymbTmMax)) rez += TSYS::strMess(_("Response time %s[%s:%s/1.5]. "), tm2s(1e-6*respTm).c_str(), tm2s(1e-6*respTmMax).c_str(), tm2s(1e-6*respSymbTmMax).c_str()); } return rez; } bool TTrOut::cfgChange( TCfg &co, const TVariant &pc ) { if(co.name() == "ADDR") { //Times adjust int speed = s2i(TSYS::strParse(co.getS(),1,":")); if(TSYS::strParse(addr(),4,":").size()) { mDefTimeouts = true; setTimings("5000:1000", true); } else if(speed) { float symbMlt = TRegExp("^.+\\/ttyS\\d+$").test(TSYS::strParse(co.getS(),0,":")) ? 1 : 3; mDefTimeouts = true; setTimings(i2s((1024*11*1000)/speed)+":"+r2s(symbMlt*11e4/(float)speed,2,'f')+ ":"+TSYS::strParse(timings(),2,":")+":"+TSYS::strParse(timings(),3,":")+":"+TSYS::strParse(timings(),4,":"), true); } } return TTransportOut::cfgChange(co, pc); } void TTrOut::setTimings( const string &vl, bool isDef ) { if((isDef && !mDefTimeouts) || vl == mTimings) return; else if(!isDef) mDefTimeouts = false; int wReqTm = vmax(1, vmin(10000,s2i(TSYS::strParse(vl,0,":")))); string charTm = TSYS::strParse(vl, 1, ":"); float wCharTm = vmax(0.01, vmin(1e4,s2r(charTm))); string reqRetrMult = TSYS::strParse(charTm, 1, "-"); mTimings = reqRetrMult.size() ? TSYS::strMess("%d:%g-%g", wReqTm, wCharTm, vmax(0,vmin(100,s2r(reqRetrMult)))) : TSYS::strMess("%d:%g", wReqTm, wCharTm); float wKeepAliveTm = vmax(0, vmin(1e3,s2r(TSYS::strParse(vl,2,":")))); float wRtsDelay1 = vmax(0, vmin(10000,s2r(TSYS::strParse(vl,3,":")))); float wRtsDelay2 = vmax(0, vmin(10000,s2r(TSYS::strParse(vl,4,":")))); if(wKeepAliveTm || wRtsDelay1 || wRtsDelay2) mTimings += TSYS::strMess(":%g", wKeepAliveTm); if(wRtsDelay1 || wRtsDelay2) mTimings += TSYS::strMess(":%g:%g", wRtsDelay1, wRtsDelay2); respSymbTmMax = 0; if(!isDef) modif(); } void TTrOut::start( int tmCon ) { MtxAlloc res(reqRes(), true); if(runSt) return; //Statuses clear mMdmMode = mRTSfc = mRTSlvl = mRTSEcho = mI2C = mSPI = false; trIn = trOut = respTm = respTmMax = respSymbTmMax = 0; bool isLock = false; // Reading the global arguments string addr_ = TSYS::strParse(addr(), 0, "||"); string tVl; if((tVl=TSYS::strParse(addr(),1,"||")).size()) setTimings(tVl); try { //Open and setup device mDevPort = TSYS::strParse(addr_, 0, ":"); // Lock device for all serial transports if(!(isLock=mod->devLock(mDevPort))) throw TError(nodePath().c_str(),_("The device '%s' is using now."),mDevPort.c_str()); mess_debug(nodePath().c_str(), _("Connecting.")); // Serial port open // O_NONBLOCK is used for prevent function open() hang on several USB->RS485 converters fd = open(mDevPort.c_str(), O_RDWR|O_NOCTTY|O_NONBLOCK); if(fd < 0) throw TError(nodePath().c_str(), _("Error the serial port '%s' %s: %s."), mDevPort.c_str(), "open", strerror(errno)); // Set serial port parameters struct termios tio; uint8_t spiMode = 0, spiBits = 8; uint32_t spiSpeed = 500000; if(tcgetattr(fd,&tio) < 0) { string tErr = TSYS::strMess(_("Error the serial port '%s' %s: %s."), mDevPort.c_str(), "tcgetattr", strerror(errno)); #ifdef I2C_SLAVE // Try for I2C by set some slave device address if(ioctl(fd,I2C_SLAVE,0) >= 0) mI2C = true; else #endif #ifdef HAVE_LINUX_SPI_SPIDEV_H // Try for SPI ... if(ioctl(fd,SPI_IOC_WR_MODE,&spiMode) >= 0 && ioctl(fd,SPI_IOC_RD_MODE,&spiMode) >= 0 && ioctl(fd,SPI_IOC_WR_BITS_PER_WORD,&spiBits) >= 0 && ioctl(fd,SPI_IOC_RD_BITS_PER_WORD,&spiBits) >= 0 && ioctl(fd,SPI_IOC_WR_MAX_SPEED_HZ,&spiSpeed) >= 0 && ioctl(fd,SPI_IOC_RD_MAX_SPEED_HZ,&spiSpeed) >= 0) mSPI = true; else #endif throw TError(nodePath(), tErr); } if(!mI2C && !mSPI) { tio.c_iflag = 0; tio.c_oflag = 0; tio.c_cflag |= (CREAD|CLOCAL); tio.c_lflag = 0; tio.c_cc[VTIME] = 0; //< inter-character timer unused tio.c_cc[VMIN] = 0; //< blocking read until 0 character arrives // Set speed string speed = sTrm(TSYS::strParse(addr_, 1, ":")); if(!speed.empty()) { speed_t tspd = B9600; switch(s2i(speed)) { case 300: tspd = B300; break; case 600: tspd = B600; break; case 1200: tspd = B1200; break; case 2400: tspd = B2400; break; case 4800: tspd = B4800; break; case 9600: tspd = B9600; break; case 19200: tspd = B19200; break; case 38400: tspd = B38400; break; case 57600: tspd = B57600; break; case 115200:tspd = B115200; break; case 230400:tspd = B230400; break; case 460800:tspd = B460800; break; case 500000:tspd = B500000; break; case 576000:tspd = B576000; break; case 921600:tspd = B921600; break; default: throw TError(nodePath().c_str(),_("Error the speed '%s'."),speed.c_str()); } cfsetispeed(&tio, tspd); cfsetospeed(&tio, tspd); } // Set asynchronous data format string format = sTrm(TSYS::strParse(addr_,2,":")); if(!format.empty()) { if(format.size() != 3) throw TError(nodePath().c_str(),_("Error the asynchronous data format '%s'."),format.c_str()); // Set byte length int len = format[0]-'0'; if(len < 5 || len > 8) throw TError(nodePath().c_str(),_("Error char length %d."),len); tio.c_cflag &= ~CSIZE; switch(len) { case 5: tio.c_cflag |= CS5; break; case 6: tio.c_cflag |= CS6; break; case 7: tio.c_cflag |= CS7; break; case 8: tio.c_cflag |= CS8; break; } // Set parity char parity = tolower(format[1]); switch(parity) { case 'e': tio.c_cflag |= PARENB; tio.c_cflag &= ~PARODD;break; case 'o': tio.c_cflag |= PARENB; tio.c_cflag |= PARODD; break; case 'n': tio.c_cflag &= ~PARENB; break; default: throw TError(nodePath().c_str(),_("Error the parity checking mode '%c'."),parity); } // Set stop bits number int stopbt = format[2]-'0'; if(stopbt == 1) tio.c_cflag &= ~CSTOPB; else if(stopbt == 2) tio.c_cflag |= CSTOPB; else throw TError(nodePath().c_str(),_("Error the stop bits %d."),stopbt); } // Set options string opts = sTrm(TSYS::strParse(addr_,3,":")), opt; for(int off = 0; (opt=TSYS::strParse(opts,0,",",&off)).size(); ) { if(strcasecmp(opt.c_str(),"h") == 0) tio.c_cflag |= CRTSCTS; else if(strcasecmp(opt.c_str(),"-h") == 0) tio.c_cflag &= ~CRTSCTS; if(strcasecmp(opt.c_str(),"s") == 0) tio.c_iflag |= (IXON|IXOFF|IXANY); else if(strcasecmp(opt.c_str(),"-s") == 0) tio.c_iflag &= ~(IXON|IXOFF|IXANY); if(strcasecmp(opt.c_str(),"rts") == 0) { mRTSfc = mRTSEcho = true; } else if(strcasecmp(opt.c_str(),"rts1") == 0) { mRTSfc = mRTSlvl = mRTSEcho = true; } else if(strcasecmp(opt.c_str(),"rts1ne") == 0) { mRTSfc = mRTSlvl = true; } else if(strcasecmp(opt.c_str(),"rtsne") == 0) { mRTSfc = true; } #ifdef SER_RS485_ENABLED #ifndef TIOCSRS485 #define TIOCSRS485 0x542f #endif // Standard RS-485 mode if(strcasecmp(opt.c_str(),"rs485") == 0 || strcasecmp(opt.c_str(),"-rs485") == 0) { serial_rs485 rs485conf; memset(&rs485conf, 0, sizeof(serial_rs485)); if(strcasecmp(opt.c_str(),"rs485") == 0) rs485conf.flags |= SER_RS485_ENABLED; ioctl(fd, TIOCSRS485, &rs485conf); } #endif } // Set port's data tcflush(fd, TCIOFLUSH); if(tcsetattr(fd,TCSANOW,&tio) < 0) throw TError(nodePath().c_str(), _("Error the serial port '%s' %s: %s."), mDevPort.c_str(), "tcsetattr", strerror(errno)); //Modem connection establish string telNumb = sTrm(TSYS::strParse(addr_,4,":")); if(!telNumb.empty()) { // Resource to transfer function alloc runSt = true; mMdmMode = true; // Send init 1 string if(!mdmInitStr1().empty()) { if(mdmPreInit() > 0) { TSYS::sysSleep(mdmPreInit()*0.5); TTr::writeLine(fd,""); TSYS::sysSleep(mdmPreInit()*0.5); } TTr::writeLine(fd,mdmInitStr1()); if(TTr::expect(fd,mdmInitResp(),mdmTm()).empty()) throw TError(nodePath().c_str(), _("No response to the initial request '%s'."), mdmInitStr1().c_str()); TSYS::sysSleep(mdmPostInit()); } // Send init 2 string if(!mdmInitStr2().empty()) { if(mdmPreInit() > 0) { TSYS::sysSleep(mdmPreInit()*0.5); TTr::writeLine(fd,""); TSYS::sysSleep(mdmPreInit()*0.5); } TTr::writeLine(fd,mdmInitStr2()); if(TTr::expect(fd,mdmInitResp(),mdmTm()).empty()) throw TError(nodePath().c_str(), _("No response to the initial request '%s'."), mdmInitStr2().c_str()); TSYS::sysSleep(mdmPostInit()); } // Dial number and connection wait string rez; telNumb = mdmDialStr()+telNumb; TTr::writeLine(fd,telNumb); if((rez=TTr::expect(fd,mdmCnctResp()+"\n"+mdmBusyResp()+"\n"+mdmNoCarResp()+"\n"+mdmNoDialToneResp(),mdmTm())) != mdmCnctResp()) { if(rez == mdmBusyResp()) throw TError(nodePath(), _("Modem busy")); else if(rez == mdmNoCarResp()) throw TError(nodePath(), _("Modem no carrier")); else if(rez == mdmNoDialToneResp()) throw TError(nodePath(), _("Modem no dial tone")); else throw TError(nodePath(), _("Modem no connected")); } mLstReqTm = TSYS::curTime(); mMdmDataMode = true; } } } catch(TError &err) { //HangUp if(mMdmMode) TTr::writeLine(fd,mdmHangUp()); if(fd >= 0 && close(fd) != 0) mess_warning(nodePath().c_str(), _("Closing the file %d error '%s (%d)'!"), fd, strerror(errno), errno); fd = -1; if(isLock) mod->devUnLock(mDevPort); runSt = false; mMdmMode = false; throw; } mKeepAliveLstTm = TSYS::curTime(); runSt = true; TTransportOut::start(); if(logLen()) pushLogMess(_("Connected")); } void TTrOut::stop( ) { MtxAlloc res(reqRes(), true); if(!runSt) return; mess_debug(nodePath().c_str(), _("Disconnecting.")); if(mMdmDataMode) { TTr::writeLine(fd, mdmExit(), true); if(mdmPreInit() > 0) TSYS::sysSleep(mdmPreInit()); //HangUp TTr::writeLine(fd,mdmHangUp()); mMdmDataMode = false; } //Status clear trIn = trOut = 0; //Port close if(close(fd) != 0) mess_warning(nodePath().c_str(), _("Closing the file %d error '%s (%d)'!"), fd, strerror(errno), errno); fd = -1; //Unlock device mod->devUnLock(mDevPort); runSt = false; mMdmMode = false; TTransportOut::stop(); if(logLen()) pushLogMess(_("Disconnected")); } void TTrOut::check( ) { int reRs = 1; bool toStop = (mMdmMode && mMdmDataMode && (reRs=reqRes().tryLock()) == 0 && (TSYS::curTime()-mLstReqTm)/1000000 > mdmLifeTime()); if(reRs == 0) reqRes().unlock(); if(toStop) stop(); } int TTrOut::messIO( const char *oBuf, int oLen, char *iBuf, int iLen, int time ) { string err = _("Unknown error"); ssize_t blen = 0; int off = 0, kz, sec; fd_set rw_fd; struct timeval tv; bool notReq = (time < 0); time = abs(time); MtxAlloc res(reqRes(), true); if(!runSt) throw TError(nodePath(), _("Transport is not connected!")); int wReqTm = s2i(TSYS::strParse(timings(),0,":",&off)); wReqTm = time ? time : wReqTm; string tVl = TSYS::strParse(timings(), 0, ":", &off); float wCharTm = s2r(TSYS::strParse(tVl,0,"-")); //Milliseconds tVl = TSYS::strParse(tVl, 1, "-"); float reqRetrMult = tVl.size() ? s2r(tVl) : 4; float wKeepAliveTm = s2r(TSYS::strParse(timings(),0,":",&off)); float wRtsDelay1 = 1e-3*s2r(TSYS::strParse(timings(),0,":",&off)); float wRtsDelay2 = 1e-3*s2r(TSYS::strParse(timings(),0,":",&off)); if(wKeepAliveTm > 0 && !notStopOnProceed() && (TSYS::curTime()-mKeepAliveLstTm) > wKeepAliveTm*1000000) { mess_debug(nodePath().c_str(), _("Reconnect by KeepAliveTm %gs."), wKeepAliveTm); stop(); start(); mKeepAliveLstTm = TSYS::curTime(); } int64_t tmW = TSYS::curTime(); if(mRTSfc) ioctl(fd, TIOCMGET, &sec); int64_t stRespTm = 0; //Write request if(oBuf && oLen > 0) { // Wait for char based timeout if((tmW-mLstReqTm) < 1e3*(reqRetrMult*wCharTm)) TSYS::sysSleep(1e-6*(1e3*(reqRetrMult*wCharTm)-(tmW-mLstReqTm))); // Input buffer clean if(!notReq && !mI2C && !mSPI) { tcflush(fd, TCIOFLUSH); char tbuf[30]; while(read(fd,tbuf,sizeof(tbuf)) > 0) ; } // Pure RS-485 flow control: Clear RTS for transfer allow if(mRTSfc) { if(!mRTSlvl) sec &= ~TIOCM_RTS; else sec |= TIOCM_RTS; ioctl(fd, TIOCMSET, &sec); if(wRtsDelay1) TSYS::sysSleep(wRtsDelay1); } // Same writing int wOff = 0; #ifdef I2C_SLAVE if(mI2C && oLen) { ioctl(fd, I2C_SLAVE, oBuf[wOff]); wOff++; } #endif for( ; wOff != oLen; wOff += kz) if((kz=write(fd,oBuf+wOff,oLen-wOff)) <= 0) { if(kz == 0 || (kz < 0 && errno == EAGAIN)) { tv.tv_sec = wReqTm/1000; tv.tv_usec = 1000*(wReqTm%1000); FD_ZERO(&rw_fd); FD_SET(fd, &rw_fd); kz = select(fd+1, NULL, &rw_fd, NULL, &tv); if(kz > 0 && FD_ISSET(fd,&rw_fd)) { kz = 0; continue; } } err = (kz < 0) ? TSYS::strMess("%s (%d)",strerror(errno),errno) : _("No wrote data"); mLstReqTm = TSYS::curTime(); if(!notStopOnProceed()) stop(); if(mess_lev() == TMess::Debug) mess_debug(nodePath().c_str(), _("Error writing: %s"), err.c_str()); if(logLen()) pushLogMess(TSYS::strMess(_("Error transmitting: %s"), err.c_str())); throw TError(nodePath().c_str(), _("Error writing: %s"), err.c_str()); } else { trOut += kz; if(logLen()) pushLogMess(_("> Transmitted to\n"), string(oBuf+wOff,kz), 1); if(mess_lev() == TMess::Debug) stRespTm = SYS->curTime(); } if(mess_lev() == TMess::Debug) mess_debug(nodePath().c_str(), _("Wrote %s."), TSYS::cpct2str(oLen).c_str()); // Hard read for wait request echo and transfer disable if(mRTSfc) { if(mRTSEcho) { char echoBuf[255]; mLstReqTm = TSYS::curTime(); for(int r_off = 0; r_off < oLen; ) { kz = read(fd, echoBuf, vmin(oLen-r_off,(int)sizeof(echoBuf))); if(kz == 0 || (kz == -1 && errno == EAGAIN)) { if((TSYS::curTime()-mLstReqTm) > wCharTm*oLen*1e3) throw TError(nodePath(), _("Timeouted!")); sched_yield(); continue; } if(kz < 0 || memcmp(echoBuf,oBuf+r_off,kz) != 0) throw TError(nodePath(), _("Error reading the echo request.")); r_off += kz; } } else tcdrain(fd); if(wRtsDelay2) TSYS::sysSleep(wRtsDelay2); if(!mRTSlvl) sec |= TIOCM_RTS; else sec &= ~TIOCM_RTS; ioctl(fd, TIOCMSET, &sec); } } //Read reply if(iBuf != NULL && iLen > 0) { if(oBuf && oLen > 0) { tv.tv_sec = wReqTm/1000; tv.tv_usec = 1000*(wReqTm%1000); } else { tv.tv_sec = (int)(1.5e-3*wCharTm); tv.tv_usec = (int)(1.5e3*wCharTm)%1000000; } FD_ZERO(&rw_fd); FD_SET(fd, &rw_fd); kz = select(fd+1, &rw_fd, NULL, NULL, &tv); if(kz == 0) { mLstReqTm = TSYS::curTime(); if(!notReq) { if(mess_lev() == TMess::Debug) mess_debug(nodePath().c_str(), _("Reading timeouted.")); if(logLen()) pushLogMess(_("Reading timeouted")); if(wKeepAliveTm < 0 && oLen > 0 && !notStopOnProceed()) stop(); } throw TError(nodePath(), _("Reading timeouted.")); } else if(kz < 0) { err = TSYS::strMess("%s (%d)", strerror(errno), errno); mLstReqTm = TSYS::curTime(); if(!notStopOnProceed()) stop(); if(mess_lev() == TMess::Debug) mess_debug(nodePath().c_str(), _("Error reading (select): %s"), err.c_str()); if(logLen()) pushLogMess(TSYS::strMess(_("Error reading (select): %s"), err.c_str())); throw TError(nodePath().c_str(), _("Error reading (select): %s"), err.c_str()); } else if(FD_ISSET(fd,&rw_fd)) { //!! Reading in that way but some time read() return 0 after the select() pass. // * Force wait any data in the request mode or EAGAIN // * No wait any data in the not request mode but it can get the data later for(int iRtr = 0; (((blen=read(fd,iBuf,iLen)) == 0 && !notReq) || (blen < 0 && errno == EAGAIN)) && iRtr < time; ++iRtr) TSYS::sysSleep(1e-3); // * Force errors // * Retry if any data was wrote but no reply there in the request mode if(blen < 0 || (blen == 0 && oBuf && oLen > 0 && !notReq)) { err = (blen < 0) ? TSYS::strMess("%s (%d)", strerror(errno), errno) : _("No data"); mLstReqTm = TSYS::curTime(); if(!notStopOnProceed()) stop(); if(mess_lev() == TMess::Debug) mess_debug(nodePath().c_str(), _("Error reading: %s"), err.c_str()); if(logLen()) pushLogMess(TSYS::strMess(_("Error reading: %s"), err.c_str())); throw TError(nodePath().c_str(), _("Error reading: %s"), err.c_str()); } if(mess_lev() == TMess::Debug) mess_debug(nodePath().c_str(), _("Read %s."), TSYS::cpct2str(vmax(0,blen)).c_str()); if(blen > 0 && logLen()) pushLogMess(_("< Received from\n"), string(iBuf,blen), -1); if(blen > 0 && !notReq && mess_lev() == TMess::Debug) { if(!(oBuf && oLen > 0)) respSymbTmMax = fmax(respSymbTmMax, SYS->curTime()-tmW); /*{ if((stRespTm=SYS->curTime()-mLstReqTm)/1.5e3 < wCharTm) respSymbTmMax = fmax(respSymbTmMax, stRespTm); }*/ else if(stRespTm) { respTm = SYS->curTime() - stRespTm; respTmMax = vmax(respTmMax, respTm); } } trIn += vmax(0, blen); } } mLstReqTm = TSYS::curTime(); return vmax(0, blen); } TVariant TTrOut::objFuncCall( const string &id, vector &prms, const string &user_lang ) { // bool TS(bool rts = EVAL) - To Send control by set request and return Clear CTS state // rts - Request value RTS if(id == "TS") { MtxAlloc res(reqRes(), true); if(!runSt) return EVAL_BOOL; int tiocm; //Get TIOCM current status ioctl(fd, TIOCMGET, &tiocm); if(prms.size()) { //Set RTS if(!prms[0].isEVal() && !prms[0].isNull()) { if(prms[0].getB()) tiocm |= TIOCM_RTS; else tiocm &= ~TIOCM_RTS; ioctl(fd, TIOCMSET, &tiocm); } //Get current RTS else { prms[0].setB((bool)(tiocm&TIOCM_RTS)); prms[0].setModify(); } } return (bool)(tiocm&TIOCM_CTS); } // bool DR(bool dtr = EVAL) - Device ready to communicate control by set Terminal Ready and return Set Ready DSR state // dtr - Terminal ready value DTR if(id == "DR") { MtxAlloc res(reqRes(), true); if(!runSt) return EVAL_BOOL; int tiocm; //Get TIOCM current status ioctl(fd, TIOCMGET, &tiocm); if(prms.size()) { //Set DTR if(!prms[0].isEVal() && !prms[0].isNull()) { if(prms[0].getB()) tiocm |= TIOCM_DTR; else tiocm &= ~TIOCM_DTR; ioctl(fd, TIOCMSET, &tiocm); } //Get current DTR else { prms[0].setB((bool)(tiocm&TIOCM_DTR)); prms[0].setModify(); } } return (bool)(tiocm&TIOCM_DSR); } // bool DCD() - Data Carrier Detect control if(id == "DCD") { MtxAlloc res(reqRes(), true); if(!runSt) return EVAL_BOOL; int tiocm; //Get TIOCM current status ioctl(fd, TIOCMGET, &tiocm); return (bool)(tiocm&TIOCM_CD); } // bool RI() - Ring Indicator control if(id == "RI") { MtxAlloc res(reqRes(), true); if(!runSt) return EVAL_BOOL; int tiocm; //Get TIOCM current status ioctl(fd, TIOCMGET, &tiocm); return (bool)(tiocm&TIOCM_RI); } // int sendbreak(int duration = 0) if(id == "sendbreak") { MtxAlloc res(reqRes(), true); if(!runSt) return (int64_t)EVAL_INT; return tcsendbreak(fd, prms.size() ? prms[0].getI() : 0); } return TTransportOut::objFuncCall(id, prms, user_lang); } void TTrOut::cntrCmdProc( XMLNode *opt ) { //Get page info if(opt->name() == "info") { TTransportOut::cntrCmdProc(opt); if(opt->childSize() && ctrId(opt->childGet(0),"/prm/cfg/",true)) ctrMkNode("fld",opt,-1,"/prm/cfg/ADDR",EVAL_STR,RWRWR_,"root",STR_ID,3, "dest","sel_ed", "select","/prm/cfg/devLS", "help",owner().outAddrHelp().c_str()); if(opt->childSize() && ctrId(opt->childGet(0),"/aprm/",true)) { int pos = 0; ctrMkNode("fld",opt,pos++,"/aprm/TMS",_("Timings"),RWRWR_,"root",STR_ID,2,"tp","str","help",TTr::outTimingsHelp().c_str()); ctrMkNode("fld",opt,pos++,"/aprm/notStopOnProceed",_("Do not disconnect at processing"),RWRWR_,"root",STR_ID,2,"tp","bool", "help", _("Sometime opened device closing can be breakage, on ICP-DAS LP PLC for example, then you are alowed to prevent it by this option.")); if(TSYS::strParse(addr(),4,":").size() && ctrMkNode("area",opt,pos++,"/aprm/mod",_("Modem"),R_R_R_,"root",STR_ID)) { ctrMkNode("fld",opt,-1,"/aprm/mod/tm",_("Timeout, seconds"),RWRWR_,"root",STR_ID,1,"tp","dec"); ctrMkNode("fld",opt,-1,"/aprm/mod/lifeTm",_("Life time, seconds"),RWRWR_,"root",STR_ID,1,"tp","dec"); ctrMkNode("fld",opt,-1,"/aprm/mod/preInitDl",_("Pre-initial delay, seconds"),RWRWR_,"root",STR_ID,1,"tp","real"); ctrMkNode("fld",opt,-1,"/aprm/mod/postInitDl",_("Post-initial delay, seconds"),RWRWR_,"root",STR_ID,1,"tp","real"); ctrMkNode("fld",opt,-1,"/aprm/mod/initStr1",_("Initialization string 1"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/initStr2",_("Initialization string 2"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/initResp",_("Initial response"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/dialStr",_("Dial string"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/cnctResp",_("Connect response"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/busyResp",_("Busy response"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/noCarResp",_("No carrier response"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/noDialToneResp",_("No dial tone response"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/exit",_("Exit"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/hangUp",_("Hangup string"),RWRWR_,"root",STR_ID,1,"tp","str"); ctrMkNode("fld",opt,-1,"/aprm/mod/hangUpResp",_("Hangup response"),RWRWR_,"root",STR_ID,1,"tp","str"); } } return; } //Process command to page string a_path = opt->attr("path"); if(a_path == "/prm/cfg/devLS" && ctrChkNode(opt)) { int off = 0; TSYS::ctrListFS(opt, TSYS::strParse(addr(),0,":",&off), ";"); string suf = (off < (int)addr().size()) ? addr().substr(off) : ""; for(unsigned iT = 0; suf.size() && iT < opt->childSize(); iT++) if(opt->childGet(iT)->text().size()) opt->childGet(iT)->setText(opt->childGet(iT)->text()+":"+suf); } else if(a_path == "/aprm/TMS") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(timings()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setTimings(opt->text()); } else if(a_path == "/aprm/notStopOnProceed") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(i2s(notStopOnProceed())); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setNotStopOnProceed(s2i(opt->text())); } else if(a_path == "/aprm/mod/tm") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(i2s(mdmTm())); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmTm(s2i(opt->text())); } else if(a_path == "/aprm/mod/lifeTm") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(i2s(mdmLifeTime())); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmLifeTime(s2i(opt->text())); } else if(a_path == "/aprm/mod/preInitDl") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(r2s(mdmPreInit())); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmPreInit(s2r(opt->text())); } else if(a_path == "/aprm/mod/postInitDl") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(r2s(mdmPostInit())); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmPostInit(s2r(opt->text())); } else if(a_path == "/aprm/mod/initStr1") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmInitStr1()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmInitStr1(opt->text()); } else if(a_path == "/aprm/mod/initStr2") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmInitStr2()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmInitStr2(opt->text()); } else if(a_path == "/aprm/mod/initResp") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmInitResp()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmInitResp(opt->text()); } else if(a_path == "/aprm/mod/dialStr") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmDialStr()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmDialStr(opt->text()); } else if(a_path == "/aprm/mod/cnctResp") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmCnctResp()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmCnctResp(opt->text()); } else if(a_path == "/aprm/mod/busyResp") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmBusyResp()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmBusyResp(opt->text()); } else if(a_path == "/aprm/mod/noCarResp") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmNoCarResp()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmNoCarResp(opt->text()); } else if(a_path == "/aprm/mod/noDialToneResp") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmNoDialToneResp()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmNoDialToneResp(opt->text()); } else if(a_path == "/aprm/mod/exit") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmExit()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmExit(opt->text()); } else if(a_path == "/aprm/mod/hangUp") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmHangUp()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmHangUp(opt->text()); } else if(a_path == "/aprm/mod/hangUpResp") { if(ctrChkNode(opt,"get",RWRWR_,"root",STR_ID,SEC_RD)) opt->setText(mdmHangUpResp()); if(ctrChkNode(opt,"set",RWRWR_,"root",STR_ID,SEC_WR)) setMdmHangUpResp(opt->text()); } else TTransportOut::cntrCmdProc(opt); }