<commit_before>/*
 * Copyright (c) 2015 <NAME>.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, version 2.1.
 *
 * 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
 * Lesser General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
// -------------------------------------------------------------------------
#include <cmath>
#include <sstream>
#include <Poco/Net/NetException.h>
#include "Exceptions.h"
#include "Extensions.h"
#include "MBSlave.h"
#include "modbus/ModbusRTUSlaveSlot.h"
#include "modbus/ModbusTCPServerSlot.h"
#include "modbus/MBLogSugar.h"
// -------------------------------------------------------------------------
namespace uniset
{
	// -----------------------------------------------------------------------------
	using namespace std;
	using namespace uniset::extensions;
	using namespace ModbusRTU;
	// -----------------------------------------------------------------------------
	MBSlave::MBSlave(uniset::ObjectId objId, uniset::ObjectId shmId, const std::shared_ptr<SharedMemory>& ic, const string& prefix ):
		UniSetObject(objId),
		initPause(3000),
		test_id(DefaultObjectId),
		askcount_id(DefaultObjectId),
		respond_id(DefaultObjectId),
		respond_invert(false),
		askCount(0),
		activated(false),
		cancelled(false),
		activateTimeout(500),
		pingOK(true),
		force(false),
		mbregFromID(false),
		prefix(prefix),
		prop_prefix("")
	{
		if( objId == DefaultObjectId )
			throw uniset::SystemError("(MBSlave): objId=-1?!! Use --" + prefix + "-name" );

		auto conf = uniset_conf();
		mutex_start.setName(myname + "_mutex_start");

		loga = make_shared<LogAgregator>(myname + "-loga");
		loga->add(ulog());
		loga->add(dlog());

		mblog = loga->create(myname);
		conf->initLogStream(mblog, prefix + "-log");

		//    xmlNode* cnode = conf->getNode(myname);

		string conf_name(conf->getArgParam("--" + prefix + "-confnode", myname));
		cnode = conf->getNode(conf_name);

		if( cnode == NULL )
			throw uniset::SystemError("(MBSlave): Not found conf-node for " + myname );

		shm = make_shared<SMInterface>(shmId, ui, objId, ic);

		UniXML::iterator it(cnode);

		logserv = make_shared<LogServer>(loga);
		logserv->init( prefix + "-logserver", cnode );

		if( findArgParam("--" + prefix + "-run-logserver", conf->getArgc(), conf->getArgv()) != -1 )
		{
			logserv_host = conf->getArg2Param("--" + prefix + "-logserver-host", it.getProp("logserverHost"), "localhost");
			logserv_port = conf->getArgPInt("--" + prefix + "-logserver-port", it.getProp("logserverPort"), getId());
		}

		if( ic )
			ic->logAgregator()->add(loga);

		// определяем фильтр
		s_field = conf->getArgParam("--" + prefix + "-filter-field");
		s_fvalue = conf->getArgParam("--" + prefix + "-filter-value");
		mbinfo << myname << "(init): read s_field='" << s_field
			   << "' s_fvalue='" << s_fvalue << "'" << endl;

		vmonit(s_field);
		vmonit(s_fvalue);

		// префикс для "свойств" - по умолчанию
		prop_prefix = "";

		// если задано поле для "фильтрации"
		// то в качестве префикса используем его
		if( !s_field.empty() )
			prop_prefix = s_field + "_";

		// если "принудительно" задан префикс
		// используем его.
		{
			string p("--" + prefix + "-set-prop-prefix");
			string v = conf->getArgParam(p, "");

			if( !v.empty() && v[0] != '-' )
				prop_prefix = v;
			// если параметр всё-таки указан, считаем, что это попытка задать "пустой" префикс
			else if( findArgParam(p, conf->getArgc(), conf->getArgv()) != -1 )
				prop_prefix = "";
		}

		vmonit(prop_prefix);

		mbinfo << myname << "(init): prop_prefix=" << prop_prefix << endl;

		force = conf->getArgInt("--" + prefix + "-force", it.getProp("force"));

		// int recv_timeout = conf->getArgParam("--" + prefix + "-recv-timeout",it.getProp("recv_timeout")));

		vmonit(force);

		default_mbaddr = conf->getArg2Param("--" + prefix + "-default-mbaddr", it.getProp("default_mbaddr"), "");
		default_mbfunc = conf->getArgPInt("--" + prefix + "-default-mbfunc", it.getProp("default_mbfunc"), 0);

		mbregFromID = conf->getArgInt("--" + prefix + "-reg-from-id", it.getProp("reg_from_id"));
		checkMBFunc = conf->getArgInt("--" + prefix + "-check-mbfunc", it.getProp("check_mbfunc"));
		noMBFuncOptimize = conf->getArgInt("--" + prefix + "-no-mbfunc-optimization", it.getProp("no_mbfunc_optimization"));
		mbinfo << myname << "(init): mbregFromID=" << mbregFromID
			   << " checkMBFunc=" << checkMBFunc
			   << " default_mbfunc=" << default_mbfunc
			   << " default_mbaddr=" << default_mbaddr
			   << " noMBFuncOptimize=" << noMBFuncOptimize
			   << endl;

		vmonit(default_mbaddr);
		vmonit(default_mbfunc);
		vmonit(checkMBFunc);
		vmonit(noMBFuncOptimize);

		respond_id = conf->getSensorID(conf->getArgParam("--" + prefix + "-respond-id", it.getProp("respond_id")));
		respond_invert = conf->getArgInt("--" + prefix + "-respond-invert", it.getProp("respond_invert"));

		timeout_t reply_tout = conf->getArgPInt("--" + prefix + "-reply-timeout", it.getProp("replyTimeout"), 3000);

		timeout_t aftersend_pause = conf->getArgInt("--" + prefix + "-aftersend-pause", it.getProp("afterSendPause"));

		mbtype = conf->getArgParam("--" + prefix + "-type", it.getProp("type"));

		if( mbtype == "RTU" )
		{
			// ---------- init RS ----------
			string dev    = conf->getArgParam("--" + prefix + "-dev", it.getProp("device"));

			if( dev.empty() )
				throw uniset::SystemError(myname + "(MBSlave): Unknown device...");

			string speed     = conf->getArgParam("--" + prefix + "-speed", it.getProp("speed"));

			if( speed.empty() )
				speed = "38400";

			bool use485F = conf->getArgInt("--" + prefix + "-use485F", it.getProp("use485F"));
			bool transmitCtl = conf->getArgInt("--" + prefix + "-transmit-ctl", it.getProp("transmitCtl"));

			auto rs = make_shared<ModbusRTUSlaveSlot>(dev, use485F, transmitCtl);
			rs->setSpeed(speed);
			rs->setRecvTimeout(2000);
			rs->setAfterSendPause(aftersend_pause);
			rs->setReplyTimeout(reply_tout);
			// rs->setLog(mblog);

			mbslot = std::static_pointer_cast<ModbusServerSlot>(rs);
			thr = make_shared< ThreadCreator<MBSlave> >(this, &MBSlave::execute_rtu);
			thr->setFinalAction(this, &MBSlave::finalThread);
			mbinfo << myname << "(init): type=RTU dev=" << dev << " speed=" << speed << endl;

			ostringstream n;
			n << prefix << "-exchangelog";
			auto l = loga->create(n.str());

			if( mblog->is_crit() )
				l->addLevel(Debug::CRIT);

			if( mblog->is_warn() )
				l->addLevel(Debug::WARN);

			rs->setLog(l);
			conf->initLogStream(l, prefix + "-exchangelog");
		}
		else if( mbtype == "TCP" )
		{
			string iaddr = conf->getArgParam("--" + prefix + "-inet-addr", it.getProp("iaddr"));

			if( iaddr.empty() )
				throw uniset::SystemError(myname + "(MBSlave): Unknown TCP server address. Use: --prefix-inet-addr [ XXX.XXX.XXX.XXX| hostname ]");

			int port = conf->getArgPInt("--" + prefix + "-inet-port", it.getProp("iport"), 502);

			mbinfo << myname << "(init): type=TCP inet=" << iaddr << " port=" << port << endl;

			tcpserver = make_shared<ModbusTCPServerSlot>(iaddr, port);
			tcpserver->setAfterSendPause(aftersend_pause);
			tcpserver->setReplyTimeout(reply_tout);

			mbslot = std::static_pointer_cast<ModbusServerSlot>(tcpserver);
			//thr = make_shared< ThreadCreator<MBSlave> >(this, &MBSlave::execute_tcp);
			//thr->setFinalAction(this, &MBSlave::finalThread);
			mbinfo << myname << "(init): init TCP connection ok. " << " inet=" << iaddr << " port=" << port << endl;

			ostringstream n;
			n << prefix << "-exchangelog";
			auto l = loga->create(n.str());

			if( mblog->is_crit() )
				l->addLevel(Debug::CRIT);

			if( mblog->is_warn() )
				l->addLevel(Debug::WARN);

			tcpserver->setLog(l);
			conf->initLogStream(l, prefix + "-exchangelog");

			int tmpval = conf->getArgInt("--" + prefix + "-update-stat-time", it.getProp("updateStatTime"));

			if( tmpval > 0 )
				updateStatTime = tmpval;

			vmonit(updateStatTime);

			tmpval = conf->getArgInt("--" + prefix + "-session-timeout", it.getProp("sessTimeout"));

			if( tmpval > 0 )
				sessTimeout = tmpval;

			vmonit(sessTimeout);

			tmpval = conf->getArgInt("--" + prefix + "-session-maxnum", it.getProp("sessMaxNum"));

			if( tmpval > 0 )
				sessMaxNum = tmpval;

			vmonit(sessMaxNum);

			sesscount_id = conf->getSensorID( conf->getArgParam("--" + prefix + "-session-count-id", it.getProp("sesscount")) );
		}
		else
			throw uniset::SystemError(myname + "(MBSlave): Unknown slave type. Use: --" + prefix + "-type [RTU|TCP]");

		mbslot->connectReadCoil( sigc::mem_fun(this, &MBSlave::readCoilStatus) );
		mbslot->connectReadInputStatus( sigc::mem_fun(this, &MBSlave::readInputStatus) );
		mbslot->connectReadOutput( sigc::mem_fun(this, &MBSlave::readOutputRegisters) );
		mbslot->connectReadInput( sigc::mem_fun(this, &MBSlave::readInputRegisters) );
		mbslot->connectForceSingleCoil( sigc::mem_fun(this, &MBSlave::forceSingleCoil) );
		mbslot->connectForceCoils( sigc::mem_fun(this, &MBSlave::forceMultipleCoils) );
		mbslot->connectWriteOutput( sigc::mem_fun(this, &MBSlave::writeOutputRegisters) );
		mbslot->connectWriteSingleOutput( sigc::mem_fun(this, &MBSlave::writeOutputSingleRegister) );
		mbslot->connectMEIRDI( sigc::mem_fun(this, &MBSlave::read4314) );

		if( findArgParam("--" + prefix + "-allow-setdatetime", conf->getArgc(), conf->getArgv()) != -1 )
			mbslot->connectSetDateTime( sigc::mem_fun(this, &MBSlave::setDateTime) );

		mbslot->connectDiagnostics( sigc::mem_fun(this, &MBSlave::diagnostics) );
		mbslot->connectFileTransfer( sigc::mem_fun(this, &MBSlave::fileTransfer) );

		//    mbslot->connectJournalCommand( sigc::mem_fun(this, &MBSlave::journalCommand) );
		//    mbslot->connectRemoteService( sigc::mem_fun(this, &MBSlave::remoteService) );
		// -------------------------------

		initPause = conf->getArgPInt("--" + prefix + "-initPause", it.getProp("initPause"), 3000);

		if( shm->isLocalwork() )
		{
			readConfiguration();
			mbinfo << myname << "(init): iomap size = " << iomap.size()
				   << " myaddr=" << ModbusServer::vaddr2str(vaddr) << endl;
		}
		else
		{
			ic->addReadItem( sigc::mem_fun(this, &MBSlave::readItem) );
			// при работе с SM через указатель принудительно включаем force
			force = true;
		}

		// ********** HEARTBEAT *************
		string heart = conf->getArgParam("--" + prefix + "-heartbeat-id", it.getProp("heartbeat_id"));

		if( !heart.empty() )
		{
			sidHeartBeat = conf->getSensorID(heart);

			if( sidHeartBeat == DefaultObjectId )
			{
				ostringstream err;
				err << myname << ": не найден идентификатор для датчика 'HeartBeat' " << heart;
				mbcrit << myname << "(init): " << err.str() << endl;
				throw SystemError(err.str());
			}

			int heartbeatTime = conf->getArgPInt("--" + prefix + "-heartbeat-time", it.getProp("heartbeatTime"), conf->getHeartBeatTime());

			if( heartbeatTime > 0 )
				ptHeartBeat.setTiming(heartbeatTime);
			else
				ptHeartBeat.setTiming(UniSetTimer::WaitUpTime);

			maxHeartBeat = conf->getArgPInt("--" + prefix + "-heartbeat-max", it.getProp("heartbeat_max"), 10);
			test_id = sidHeartBeat;
		}
		else
		{
			test_id = conf->getSensorID("TestMode_S");

			if( test_id == DefaultObjectId )
			{
				ostringstream err;
				err << myname << ": test_id unknown. 'TestMode_S' not found...";
				mbcrit << myname << "(init): " << err.str() << endl;
				throw SystemError(err.str());
			}
		}

		askcount_id = conf->getSensorID(conf->getArgParam("--" + prefix + "-askcount-id", it.getProp("askcount_id")));
		mbinfo << myname << ": init askcount_id=" << askcount_id << endl;
		mbinfo << myname << ": init test_id=" << test_id << endl;

		wait_msec = conf->getHeartBeatTime() - 100;

		if( wait_msec < 500 )
			wait_msec = 500;

		activateTimeout    = conf->getArgPInt("--" + prefix + "-activate-timeout", 20000);

		timeout_t msec = conf->getArgPInt("--" + prefix + "-timeout", it.getProp("timeout"), 3000);
		ptTimeout.setTiming(msec);

		mbinfo << myname << "(init): rs-timeout=" << msec << " msec" << endl;

		// build file list...
		xmlNode* fnode = 0;
		const std::shared_ptr<UniXML> xml = conf->getConfXML();

		if( xml )
			fnode = xml->extFindNode(cnode, 1, 1, "filelist");

		if( fnode )
		{
			UniXML::iterator fit(fnode);

			if( fit.goChildren() )
			{
				for( ; fit.getCurrent(); fit.goNext() )
				{
					std::string nm = fit.getProp("name");

					if( nm.empty() )
					{
						mbwarn << myname << "(build file list): ignore empty name... " << endl;
						continue;
					}

					int id = fit.getIntProp("id");

					if( id == 0 )
					{
						mbwarn << myname << "(build file list): FAILED ID for " << nm << "... ignore..." << endl;
						continue;
					}

					std::string dir(fit.getProp("directory"));

					if( !dir.empty() )
					{
						if( dir == "ConfDir" )
							nm = conf->getConfDir() + nm;
						else if( dir == "DataDir" )
							nm = conf->getDataDir() + nm;
						else
							nm = dir + nm;
					}

					mbinfo << myname << "(init):       add to filelist: "
						   << "id=" << id
						   << " file=" << nm
						   << endl;

					flist[id] = nm;
				}
			}
			else
				mbinfo << myname << "(init): <filelist> empty..." << endl;
		}
		else
			mbinfo << myname << "(init): <filelist> not found..." << endl;


		// Формирование "карты" ответов на запрос 0x2B(43)/0x0E(14)
		xmlNode* mnode = 0;

		if( xml )
			mnode = xml->extFindNode(cnode, 1, 1, "MEI");

		if( mnode )
		{
			// Считывается структура для формирования ответов на запрос 0x2B(43)/0x0E(14)
			//        <MEI>
			//               <device id="">
			//                  <objects id="">
			//                      <string id="" value=""/>
			//                      <string id="" value=""/>
			//                      <string id="" value=""/>
			//                        ...
			//                  </objects>
			//               </device>
			//               <device devID="">
			//                 ...
			//               </device>
			//      </MEI>
			UniXML::iterator dit(mnode);

			if( dit.goChildren() )
			{
				// Device ID list..
				for( ; dit.getCurrent(); dit.goNext() )
				{
					if( dit.getProp("id").empty() )
					{
						mbwarn << myname << "(init): read <MEI>. Unknown <device id=''>. Ignore.." << endl;
						continue;
					}

					int devID = dit.getIntProp("id");

					UniXML::iterator oit(dit);

					if( oit.goChildren() )
					{
						mbwarn << myname << "(init): MEI: read dev='" << devID << "'" << endl;
						MEIObjIDMap meiomap;

						// Object ID list..
						for( ; oit.getCurrent(); oit.goNext() )
						{
							if( dit.getProp("id").empty() )
							{
								mbwarn << myname
									   << "(init): read <MEI>. Unknown <object id='' (for device id='"
									   << devID << "'). Ignore.."
									   << endl;

								continue;
							}

							int objID = oit.getIntProp("id");
							UniXML::iterator sit(oit);

							if( sit.goChildren() )
							{
								mbinfo << myname << "(init): MEI: read obj='" << objID << "'" << endl;
								MEIValMap meivmap;

								// request (string) list..
								for( ; sit.getCurrent(); sit.goNext() )
								{
									int vid = objID;

									if( sit.getProp("id").empty() )
									{
										mbwarn << myname << "(init): MEI: dev='" << devID
											   << "' obj='" << objID << "'"
											   << ". Unknown id='' for value='" << sit.getProp("value") << "'"
											   << ". Set objID='" << objID << "'"
											   << endl;
									}
									else
										vid = sit.getIntProp("id");

									meivmap[vid] = sit.getProp("value");
								}

								if( !meivmap.empty() )
									meiomap[objID] = meivmap;
							}
						}

						if( !meiomap.empty() )
							meidev[devID] = meiomap;
					}
				}
			}

			if( !meidev.empty() )
				mbinfo << myname << "(init): <MEI> init ok." << endl;
		}
		else
			mbinfo << myname << "(init): <MEI> empty..." << endl;

	}
	// -----------------------------------------------------------------------------
	MBSlave::~MBSlave()
	{
		cancelled = true;

		if( tcpserver && tcpserver->isActive() )
			tcpserver->terminate();

		if( thr && thr->isRunning() )
		{
			thr->stop();
			//        thr->join();
		}
	}
	// -----------------------------------------------------------------------------
	void MBSlave::finalThread()
	{
		activated = false;
		cancelled = true;
	}
	// -----------------------------------------------------------------------------
	void MBSlave::waitSMReady()
	{
		// waiting for SM is ready...
		int tout = uniset_conf()->getArgInt("--" + prefix + "-sm-ready-timeout", "15000");
		timeout_t ready_timeout = 60000;

		if( tout > 0 )
			ready_timeout = tout;
		else if( tout < 0 )
			ready_timeout = UniSetTimer::WaitUpTime;

		if( !shm->waitSMready(ready_timeout, 50) )
		{
			ostringstream err;
			err << myname << "(waitSMReady): Не дождались готовности SharedMemory к работе в течение " << ready_timeout << " мсек";
			mbcrit << err.str() << endl;
			//        throw SystemError(err.str());
			raise(SIGTERM);
			terminate();
			//        abort();
		}
	}
	// -----------------------------------------------------------------------------
	void MBSlave::execute_rtu()
	{
		auto rscomm = dynamic_pointer_cast<ModbusRTUSlaveSlot>(mbslot);

		// ждём чтобы прошла инициализация
		// потому-что нужно чтобы наполнилась таблица адресов (vaddr)
		if( !shm->isLocalwork() )
		{
			std::unique_lock<std::mutex> locker(mutexStartNotify);

			while( !activated )
				startNotifyEvent.wait(locker);
		}

		if( vaddr.empty() )
		{
			mbcrit << "(execute_rtu): Unknown my modbus adresses!" << endl;
			raise(SIGTERM);
			return;
		}

		mbinfo << myname << "(execute_rtu): thread running.."
			   << " iomap size = " << iomap.size()
			   << " myaddr=" << ModbusServer::vaddr2str(vaddr)
			   << endl;

		while( !cancelled )
		{
			try
			{
				ModbusRTU::mbErrCode res = rscomm->receive( vaddr, wait_msec );

				if( res != ModbusRTU::erTimeOut )
					ptTimeout.reset();

				if( res != ModbusRTU::erNoError && res != ModbusRTU::erTimeOut )
					mbwarn << myname << "(execute_rtu): " << ModbusRTU::mbErr2Str(res) << endl;

				if( !activated )
					continue;

				updateStatistics();
				updateThresholds();
			}
			catch( std::exception& ex )
			{
				dcrit << myname << "(execute_rtu): " << ex.what() << endl;
			}
		}
	}
	// -------------------------------------------------------------------------
	void MBSlave::execute_tcp()
	{
		if( !tcpserver )
		{
			mbcrit << myname << "(execute_tcp): DYNAMIC CAST ERROR (mbslot --> ModbusTCPServerSlot)" << std::endl;
			raise(SIGTERM);
			return;
		}

		// ждём чтобы прошла инициализация
		// потому-что нужно чтобы наполнилась таблица адресов (vaddr)
		if( !shm->isLocalwork() )
		{
			std::unique_lock<std::mutex> locker(mutexStartNotify);

			while( !activated )
				startNotifyEvent.wait(locker);
		}

		for( auto && i : cmap )
			i.second.ptTimeout.reset();

		tcpserver->setMaxSessions( sessMaxNum );

		// Чтобы не создавать отдельный поток для tcpserver (или для обновления статистики)
		// воспользуемся таймером tcpserver-а..
		tcpserver->signal_timer().connect( sigc::mem_fun(this, &MBSlave::updateTCPStatistics) );
		tcpserver->setTimer(updateStatTime);

		// для обновления пороговых датчиков
		tcpserver->signal_post_receive().connect( sigc::mem_fun(this, &MBSlave::postReceiveEvent) );

		mbinfo << myname << "(execute_tcp): run tcpserver ("
			   << tcpserver->getInetAddress() << ":" << tcpserver->getInetPort()
			   << ")" << endl;
<commit_msg>(MBSlave): добавил завершение работы в случае если не задан ни один адрес устройства<commit_after>/*
 * Copyright (c) 2015 <NAME>.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, version 2.1.
 *
 * 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
 * Lesser General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
// -------------------------------------------------------------------------
#include <cmath>
#include <sstream>
#include <Poco/Net/NetException.h>
#include "Exceptions.h"
#include "Extensions.h"
#include "MBSlave.h"
#include "modbus/ModbusRTUSlaveSlot.h"
#include "modbus/ModbusTCPServerSlot.h"
#include "modbus/MBLogSugar.h"
// -------------------------------------------------------------------------
namespace uniset
{
	// -----------------------------------------------------------------------------
	using namespace std;
	using namespace uniset::extensions;
	using namespace ModbusRTU;
	// -----------------------------------------------------------------------------
	MBSlave::MBSlave(uniset::ObjectId objId, uniset::ObjectId shmId, const std::shared_ptr<SharedMemory>& ic, const string& prefix ):
		UniSetObject(objId),
		initPause(3000),
		test_id(DefaultObjectId),
		askcount_id(DefaultObjectId),
		respond_id(DefaultObjectId),
		respond_invert(false),
		askCount(0),
		activated(false),
		cancelled(false),
		activateTimeout(500),
		pingOK(true),
		force(false),
		mbregFromID(false),
		prefix(prefix),
		prop_prefix("")
	{
		if( objId == DefaultObjectId )
			throw uniset::SystemError("(MBSlave): objId=-1?!! Use --" + prefix + "-name" );

		auto conf = uniset_conf();
		mutex_start.setName(myname + "_mutex_start");

		loga = make_shared<LogAgregator>(myname + "-loga");
		loga->add(ulog());
		loga->add(dlog());

		mblog = loga->create(myname);
		conf->initLogStream(mblog, prefix + "-log");

		//    xmlNode* cnode = conf->getNode(myname);

		string conf_name(conf->getArgParam("--" + prefix + "-confnode", myname));
		cnode = conf->getNode(conf_name);

		if( cnode == NULL )
			throw uniset::SystemError("(MBSlave): Not found conf-node for " + myname );

		shm = make_shared<SMInterface>(shmId, ui, objId, ic);

		UniXML::iterator it(cnode);

		logserv = make_shared<LogServer>(loga);
		logserv->init( prefix + "-logserver", cnode );

		if( findArgParam("--" + prefix + "-run-logserver", conf->getArgc(), conf->getArgv()) != -1 )
		{
			logserv_host = conf->getArg2Param("--" + prefix + "-logserver-host", it.getProp("logserverHost"), "localhost");
			logserv_port = conf->getArgPInt("--" + prefix + "-logserver-port", it.getProp("logserverPort"), getId());
		}

		if( ic )
			ic->logAgregator()->add(loga);

		// определяем фильтр
		s_field = conf->getArgParam("--" + prefix + "-filter-field");
		s_fvalue = conf->getArgParam("--" + prefix + "-filter-value");
		mbinfo << myname << "(init): read s_field='" << s_field
			   << "' s_fvalue='" << s_fvalue << "'" << endl;

		vmonit(s_field);
		vmonit(s_fvalue);

		// префикс для "свойств" - по умолчанию
		prop_prefix = "";

		// если задано поле для "фильтрации"
		// то в качестве префикса используем его
		if( !s_field.empty() )
			prop_prefix = s_field + "_";

		// если "принудительно" задан префикс
		// используем его.
		{
			string p("--" + prefix + "-set-prop-prefix");
			string v = conf->getArgParam(p, "");

			if( !v.empty() && v[0] != '-' )
				prop_prefix = v;
			// если параметр всё-таки указан, считаем, что это попытка задать "пустой" префикс
			else if( findArgParam(p, conf->getArgc(), conf->getArgv()) != -1 )
				prop_prefix = "";
		}

		vmonit(prop_prefix);

		mbinfo << myname << "(init): prop_prefix=" << prop_prefix << endl;

		force = conf->getArgInt("--" + prefix + "-force", it.getProp("force"));

		// int recv_timeout = conf->getArgParam("--" + prefix + "-recv-timeout",it.getProp("recv_timeout")));

		vmonit(force);

		default_mbaddr = conf->getArg2Param("--" + prefix + "-default-mbaddr", it.getProp("default_mbaddr"), "");
		default_mbfunc = conf->getArgPInt("--" + prefix + "-default-mbfunc", it.getProp("default_mbfunc"), 0);

		mbregFromID = conf->getArgInt("--" + prefix + "-reg-from-id", it.getProp("reg_from_id"));
		checkMBFunc = conf->getArgInt("--" + prefix + "-check-mbfunc", it.getProp("check_mbfunc"));
		noMBFuncOptimize = conf->getArgInt("--" + prefix + "-no-mbfunc-optimization", it.getProp("no_mbfunc_optimization"));
		mbinfo << myname << "(init): mbregFromID=" << mbregFromID
			   << " checkMBFunc=" << checkMBFunc
			   << " default_mbfunc=" << default_mbfunc
			   << " default_mbaddr=" << default_mbaddr
			   << " noMBFuncOptimize=" << noMBFuncOptimize
			   << endl;

		vmonit(default_mbaddr);
		vmonit(default_mbfunc);
		vmonit(checkMBFunc);
		vmonit(noMBFuncOptimize);

		respond_id = conf->getSensorID(conf->getArgParam("--" + prefix + "-respond-id", it.getProp("respond_id")));
		respond_invert = conf->getArgInt("--" + prefix + "-respond-invert", it.getProp("respond_invert"));

		timeout_t reply_tout = conf->getArgPInt("--" + prefix + "-reply-timeout", it.getProp("replyTimeout"), 3000);

		timeout_t aftersend_pause = conf->getArgInt("--" + prefix + "-aftersend-pause", it.getProp("afterSendPause"));

		mbtype = conf->getArgParam("--" + prefix + "-type", it.getProp("type"));

		if( mbtype == "RTU" )
		{
			// ---------- init RS ----------
			string dev    = conf->getArgParam("--" + prefix + "-dev", it.getProp("device"));

			if( dev.empty() )
				throw uniset::SystemError(myname + "(MBSlave): Unknown device...");

			string speed     = conf->getArgParam("--" + prefix + "-speed", it.getProp("speed"));

			if( speed.empty() )
				speed = "38400";

			bool use485F = conf->getArgInt("--" + prefix + "-use485F", it.getProp("use485F"));
			bool transmitCtl = conf->getArgInt("--" + prefix + "-transmit-ctl", it.getProp("transmitCtl"));

			auto rs = make_shared<ModbusRTUSlaveSlot>(dev, use485F, transmitCtl);
			rs->setSpeed(speed);
			rs->setRecvTimeout(2000);
			rs->setAfterSendPause(aftersend_pause);
			rs->setReplyTimeout(reply_tout);
			// rs->setLog(mblog);

			mbslot = std::static_pointer_cast<ModbusServerSlot>(rs);
			thr = make_shared< ThreadCreator<MBSlave> >(this, &MBSlave::execute_rtu);
			thr->setFinalAction(this, &MBSlave::finalThread);
			mbinfo << myname << "(init): type=RTU dev=" << dev << " speed=" << speed << endl;

			ostringstream n;
			n << prefix << "-exchangelog";
			auto l = loga->create(n.str());

			if( mblog->is_crit() )
				l->addLevel(Debug::CRIT);

			if( mblog->is_warn() )
				l->addLevel(Debug::WARN);

			rs->setLog(l);
			conf->initLogStream(l, prefix + "-exchangelog");
		}
		else if( mbtype == "TCP" )
		{
			string iaddr = conf->getArgParam("--" + prefix + "-inet-addr", it.getProp("iaddr"));

			if( iaddr.empty() )
				throw uniset::SystemError(myname + "(MBSlave): Unknown TCP server address. Use: --prefix-inet-addr [ XXX.XXX.XXX.XXX| hostname ]");

			int port = conf->getArgPInt("--" + prefix + "-inet-port", it.getProp("iport"), 502);

			mbinfo << myname << "(init): type=TCP inet=" << iaddr << " port=" << port << endl;

			tcpserver = make_shared<ModbusTCPServerSlot>(iaddr, port);
			tcpserver->setAfterSendPause(aftersend_pause);
			tcpserver->setReplyTimeout(reply_tout);

			mbslot = std::static_pointer_cast<ModbusServerSlot>(tcpserver);
			//thr = make_shared< ThreadCreator<MBSlave> >(this, &MBSlave::execute_tcp);
			//thr->setFinalAction(this, &MBSlave::finalThread);
			mbinfo << myname << "(init): init TCP connection ok. " << " inet=" << iaddr << " port=" << port << endl;

			ostringstream n;
			n << prefix << "-exchangelog";
			auto l = loga->create(n.str());

			if( mblog->is_crit() )
				l->addLevel(Debug::CRIT);

			if( mblog->is_warn() )
				l->addLevel(Debug::WARN);

			tcpserver->setLog(l);
			conf->initLogStream(l, prefix + "-exchangelog");

			int tmpval = conf->getArgInt("--" + prefix + "-update-stat-time", it.getProp("updateStatTime"));

			if( tmpval > 0 )
				updateStatTime = tmpval;

			vmonit(updateStatTime);

			tmpval = conf->getArgInt("--" + prefix + "-session-timeout", it.getProp("sessTimeout"));

			if( tmpval > 0 )
				sessTimeout = tmpval;

			vmonit(sessTimeout);

			tmpval = conf->getArgInt("--" + prefix + "-session-maxnum", it.getProp("sessMaxNum"));

			if( tmpval > 0 )
				sessMaxNum = tmpval;

			vmonit(sessMaxNum);

			sesscount_id = conf->getSensorID( conf->getArgParam("--" + prefix + "-session-count-id", it.getProp("sesscount")) );
		}
		else
			throw uniset::SystemError(myname + "(MBSlave): Unknown slave type. Use: --" + prefix + "-type [RTU|TCP]");

		mbslot->connectReadCoil( sigc::mem_fun(this, &MBSlave::readCoilStatus) );
		mbslot->connectReadInputStatus( sigc::mem_fun(this, &MBSlave::readInputStatus) );
		mbslot->connectReadOutput( sigc::mem_fun(this, &MBSlave::readOutputRegisters) );
		mbslot->connectReadInput( sigc::mem_fun(this, &MBSlave::readInputRegisters) );
		mbslot->connectForceSingleCoil( sigc::mem_fun(this, &MBSlave::forceSingleCoil) );
		mbslot->connectForceCoils( sigc::mem_fun(this, &MBSlave::forceMultipleCoils) );
		mbslot->connectWriteOutput( sigc::mem_fun(this, &MBSlave::writeOutputRegisters) );
		mbslot->connectWriteSingleOutput( sigc::mem_fun(this, &MBSlave::writeOutputSingleRegister) );
		mbslot->connectMEIRDI( sigc::mem_fun(this, &MBSlave::read4314) );

		if( findArgParam("--" + prefix + "-allow-setdatetime", conf->getArgc(), conf->getArgv()) != -1 )
			mbslot->connectSetDateTime( sigc::mem_fun(this, &MBSlave::setDateTime) );

		mbslot->connectDiagnostics( sigc::mem_fun(this, &MBSlave::diagnostics) );
		mbslot->connectFileTransfer( sigc::mem_fun(this, &MBSlave::fileTransfer) );

		//    mbslot->connectJournalCommand( sigc::mem_fun(this, &MBSlave::journalCommand) );
		//    mbslot->connectRemoteService( sigc::mem_fun(this, &MBSlave::remoteService) );
		// -------------------------------

		initPause = conf->getArgPInt("--" + prefix + "-initPause", it.getProp("initPause"), 3000);

		if( shm->isLocalwork() )
		{
			readConfiguration();
			mbinfo << myname << "(init): iomap size = " << iomap.size()
				   << " myaddr=" << ModbusServer::vaddr2str(vaddr) << endl;
		}
		else
		{
			ic->addReadItem( sigc::mem_fun(this, &MBSlave::readItem) );
			// при работе с SM через указатель принудительно включаем force
			force = true;
		}

		// ********** HEARTBEAT *************
		string heart = conf->getArgParam("--" + prefix + "-heartbeat-id", it.getProp("heartbeat_id"));

		if( !heart.empty() )
		{
			sidHeartBeat = conf->getSensorID(heart);

			if( sidHeartBeat == DefaultObjectId )
			{
				ostringstream err;
				err << myname << ": не найден идентификатор для датчика 'HeartBeat' " << heart;
				mbcrit << myname << "(init): " << err.str() << endl;
				throw SystemError(err.str());
			}

			int heartbeatTime = conf->getArgPInt("--" + prefix + "-heartbeat-time", it.getProp("heartbeatTime"), conf->getHeartBeatTime());

			if( heartbeatTime > 0 )
				ptHeartBeat.setTiming(heartbeatTime);
			else
				ptHeartBeat.setTiming(UniSetTimer::WaitUpTime);

			maxHeartBeat = conf->getArgPInt("--" + prefix + "-heartbeat-max", it.getProp("heartbeat_max"), 10);
			test_id = sidHeartBeat;
		}
		else
		{
			test_id = conf->getSensorID("TestMode_S");

			if( test_id == DefaultObjectId )
			{
				ostringstream err;
				err << myname << ": test_id unknown. 'TestMode_S' not found...";
				mbcrit << myname << "(init): " << err.str() << endl;
				throw SystemError(err.str());
			}
		}

		askcount_id = conf->getSensorID(conf->getArgParam("--" + prefix + "-askcount-id", it.getProp("askcount_id")));
		mbinfo << myname << ": init askcount_id=" << askcount_id << endl;
		mbinfo << myname << ": init test_id=" << test_id << endl;

		wait_msec = conf->getHeartBeatTime() - 100;

		if( wait_msec < 500 )
			wait_msec = 500;

		activateTimeout    = conf->getArgPInt("--" + prefix + "-activate-timeout", 20000);

		timeout_t msec = conf->getArgPInt("--" + prefix + "-timeout", it.getProp("timeout"), 3000);
		ptTimeout.setTiming(msec);

		mbinfo << myname << "(init): rs-timeout=" << msec << " msec" << endl;

		// build file list...
		xmlNode* fnode = 0;
		const std::shared_ptr<UniXML> xml = conf->getConfXML();

		if( xml )
			fnode = xml->extFindNode(cnode, 1, 1, "filelist");

		if( fnode )
		{
			UniXML::iterator fit(fnode);

			if( fit.goChildren() )
			{
				for( ; fit.getCurrent(); fit.goNext() )
				{
					std::string nm = fit.getProp("name");

					if( nm.empty() )
					{
						mbwarn << myname << "(build file list): ignore empty name... " << endl;
						continue;
					}

					int id = fit.getIntProp("id");

					if( id == 0 )
					{
						mbwarn << myname << "(build file list): FAILED ID for " << nm << "... ignore..." << endl;
						continue;
					}

					std::string dir(fit.getProp("directory"));

					if( !dir.empty() )
					{
						if( dir == "ConfDir" )
							nm = conf->getConfDir() + nm;
						else if( dir == "DataDir" )
							nm = conf->getDataDir() + nm;
						else
							nm = dir + nm;
					}

					mbinfo << myname << "(init):       add to filelist: "
						   << "id=" << id
						   << " file=" << nm
						   << endl;

					flist[id] = nm;
				}
			}
			else
				mbinfo << myname << "(init): <filelist> empty..." << endl;
		}
		else
			mbinfo << myname << "(init): <filelist> not found..." << endl;


		// Формирование "карты" ответов на запрос 0x2B(43)/0x0E(14)
		xmlNode* mnode = 0;

		if( xml )
			mnode = xml->extFindNode(cnode, 1, 1, "MEI");

		if( mnode )
		{
			// Считывается структура для формирования ответов на запрос 0x2B(43)/0x0E(14)
			//        <MEI>
			//               <device id="">
			//                  <objects id="">
			//                      <string id="" value=""/>
			//                      <string id="" value=""/>
			//                      <string id="" value=""/>
			//                        ...
			//                  </objects>
			//               </device>
			//               <device devID="">
			//                 ...
			//               </device>
			//      </MEI>
			UniXML::iterator dit(mnode);

			if( dit.goChildren() )
			{
				// Device ID list..
				for( ; dit.getCurrent(); dit.goNext() )
				{
					if( dit.getProp("id").empty() )
					{
						mbwarn << myname << "(init): read <MEI>. Unknown <device id=''>. Ignore.." << endl;
						continue;
					}

					int devID = dit.getIntProp("id");

					UniXML::iterator oit(dit);

					if( oit.goChildren() )
					{
						mbwarn << myname << "(init): MEI: read dev='" << devID << "'" << endl;
						MEIObjIDMap meiomap;

						// Object ID list..
						for( ; oit.getCurrent(); oit.goNext() )
						{
							if( dit.getProp("id").empty() )
							{
								mbwarn << myname
									   << "(init): read <MEI>. Unknown <object id='' (for device id='"
									   << devID << "'). Ignore.."
									   << endl;

								continue;
							}

							int objID = oit.getIntProp("id");
							UniXML::iterator sit(oit);

							if( sit.goChildren() )
							{
								mbinfo << myname << "(init): MEI: read obj='" << objID << "'" << endl;
								MEIValMap meivmap;

								// request (string) list..
								for( ; sit.getCurrent(); sit.goNext() )
								{
									int vid = objID;

									if( sit.getProp("id").empty() )
									{
										mbwarn << myname << "(init): MEI: dev='" << devID
											   << "' obj='" << objID << "'"
											   << ". Unknown id='' for value='" << sit.getProp("value") << "'"
											   << ". Set objID='" << objID << "'"
											   << endl;
									}
									else
										vid = sit.getIntProp("id");

									meivmap[vid] = sit.getProp("value");
								}

								if( !meivmap.empty() )
									meiomap[objID] = meivmap;
							}
						}

						if( !meiomap.empty() )
							meidev[devID] = meiomap;
					}
				}
			}

			if( !meidev.empty() )
				mbinfo << myname << "(init): <MEI> init ok." << endl;
		}
		else
			mbinfo << myname << "(init): <MEI> empty..." << endl;

	}
	// -----------------------------------------------------------------------------
	MBSlave::~MBSlave()
	{
		cancelled = true;

		if( tcpserver && tcpserver->isActive() )
			tcpserver->terminate();

		if( thr && thr->isRunning() )
		{
			thr->stop();
			//        thr->join();
		}
	}
	// -----------------------------------------------------------------------------
	void MBSlave::finalThread()
	{
		activated = false;
		cancelled = true;
	}
	// -----------------------------------------------------------------------------
	void MBSlave::waitSMReady()
	{
		// waiting for SM is ready...
		int tout = uniset_conf()->getArgInt("--" + prefix + "-sm-ready-timeout", "15000");
		timeout_t ready_timeout = 60000;

		if( tout > 0 )
			ready_timeout = tout;
		else if( tout < 0 )
			ready_timeout = UniSetTimer::WaitUpTime;

		if( !shm->waitSMready(ready_timeout, 50) )
		{
			ostringstream err;
			err << myname << "(waitSMReady): Не дождались готовности SharedMemory к работе в течение " << ready_timeout << " мсек";
			mbcrit << err.str() << endl;
			//        throw SystemError(err.str());
			raise(SIGTERM);
			terminate();
			//        abort();
		}
	}
	// -----------------------------------------------------------------------------
	void MBSlave::execute_rtu()
	{
		auto rscomm = dynamic_pointer_cast<ModbusRTUSlaveSlot>(mbslot);

		// ждём чтобы прошла инициализация
		// потому-что нужно чтобы наполнилась таблица адресов (vaddr)
		if( !shm->isLocalwork() )
		{
			std::unique_lock<std::mutex> locker(mutexStartNotify);

			while( !activated )
				startNotifyEvent.wait(locker);
		}

		if( vaddr.empty() )
		{
			mbcrit << "(execute_rtu): Unknown my modbus adresses!" << endl;
			raise(SIGTERM);
			return;
		}

		mbinfo << myname << "(execute_rtu): thread running.."
			   << " iomap size = " << iomap.size()
			   << " myaddr=" << ModbusServer::vaddr2str(vaddr)
			   << endl;

		while( !cancelled )
		{
			try
			{
				ModbusRTU::mbErrCode res = rscomm->receive( vaddr, wait_msec );

				if( res != ModbusRTU::erTimeOut )
					ptTimeout.reset();

				if( res != ModbusRTU::erNoError && res != ModbusRTU::erTimeOut )
					mbwarn << myname << "(execute_rtu): " << ModbusRTU::mbErr2Str(res) << endl;

				if( !activated )
					continue;

				updateStatistics();
				updateThresholds();
			}
			catch( std::exception& ex )
			{
				dcrit << myname << "(execute_rtu): " << ex.what() << endl;
			}
		}
	}
	// -------------------------------------------------------------------------
	void MBSlave::execute_tcp()
	{
		if( !tcpserver )
		{
			mbcrit << myname << "(execute_tcp): DYNAMIC CAST ERROR (mbslot --> ModbusTCPServerSlot)" << std::endl;
			raise(SIGTERM);
			return;
		}

		// ждём чтобы прошла инициализация
		// потому-что нужно чтобы наполнилась таблица адресов (vaddr)
		if( !shm->isLocalwork() )
		{
			std::unique_lock<std::mutex> locker(mutexStartNotify);

			while( !activated )
				startNotifyEvent.wait(locker);
		}

		for( auto && i : cmap )
			i.second.ptTimeout.reset();

		tcpserver->setMaxSessions( sessMaxNum );

		if( vaddr.empty() )
		{
			mbcrit << "(execute_tcp): Unknown my modbus adresses!" << endl;
			raise(SIGTERM);
			return;
		}

		// Чтобы не создавать отдельный поток для tcpserver (или для обновления статистики)
		// воспользуемся таймером tcpserver-а..
		tcpserver->signal_timer().connect( sigc::mem_fun(this, &MBSlave::updateTCPStatistics) );
		tcpserver->setTimer(updateStatTime);

		// для обновления пороговых датчиков
		tcpserver->signal_post_receive().connect( sigc::mem_fun(this, &MBSlave::postReceiveEvent) );

		mbinfo << myname << "(execute_tcp): run tcpserver ("
			   << tcpserver->getInetAddress() << ":" << tcpserver->getInetPort()
			   << ")" << endl;

{'fraction_non_alphanumeric': 0.2130999321897682, 'fraction_numerical': 0.006128035763618555, 'mean_word_length': 2.682759896411395, 'pattern_counts': {'":': 10, '<': 767, '<?xml version=': 0, '>': 327, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 16}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4491794', 'n_tokens_mistral': 16270, 'n_tokens_neox': 13857, 'n_words': 2693}