/* PIP - Platform Independent Primitives Abstract input/output device Copyright (C) 2018 Ivan Pelipenko peri4ko@yandex.ru 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, either version 3 of the License, or (at your option) any later version. 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, see . */ #include "piiodevice.h" #include "piconfig.h" #include "piconnection.h" /*! \class PIIODevice * \brief Base class for input/output classes * * \section PIIODevice_sec0 Synopsis * This class provide open/close logic, threaded read/write and virtual input/output * functions \a read() and \a write(). You should implement pure virtual * function \a openDevice() in your subclass. * * \section PIIODevice_sec1 Open and close * PIIODevice have boolean variable indicated open status. Returns of functions * \a openDevice() and \a closeDevice() change this variable. * * \section PIIODevice_sec2 Threaded read * PIIODevice based on PIThread, so it`s overload \a run() to exec \a read() * in background thread. If read is successful virtual function \a threadedRead() * is executed. Default implementation of this function execute external static * function set by \a setThreadedReadSlot() with data set by \a setThreadedReadData(). * Extrenal static function should have format \n * bool func_name(void * Threaded_read_data, uchar * readed_data, int readed_size)\n * Threaded read starts with function \a startThreadedRead(). * * \section PIIODevice_sec3 Threaded write * PIIODevice aggregate another PIThread to perform a threaded write by function * \a writeThreaded(). This function add task to internal queue and return * queue entry ID. You should start write thread by function \a startThreadedWrite. * On successful write event \a threadedWriteEvent is raised with two arguments - * task ID and written bytes count. * * \section PIIODevice_sec4 Internal buffer * PIIODevice have internal buffer for threaded read, and \a threadedRead() function * receive pointer to this buffer in first argument. You can adjust size of this buffer * by function \a setThreadedReadBufferSize() \n * Default size of this buffer is 4096 bytes. * * \section PIIODevice_sec5 Reopen * When threaded read is begin its call \a open() if device is closed. While threaded * read running PIIODevice check if device opened every read and if not call \a open() * every reopen timeout if reopen enabled. Reopen timeout is set by \a setReopenTimeout(), * reopen enable is set by \a setReopenEnabled(). * * \section PIIODevice_sec6 Configuration * This is virtual function \a configureDevice() which executes when \a configure() * executes. This function takes two arguments: "e_main" and "e_parent" as void*. There * are pointers to PIConfig::Entry entries of section "section" and their parent. If * there is no parent "e_parent" = 0. Function \a configure() set three parameters of * device: "reopenEnabled", "reopenTimeout" and "threadedReadBufferSize", then execute * function \a configureDevice(). * \n Each ancestor of %PIIODevice reimlements \a configureDevice() function to be able * to be confured from configuration file. This parameters described at section * "Configurable parameters" in the class reference. \n Usage example: * \snippet piiodevice.cpp configure * Implementation example: * \snippet piiodevice.cpp configureDevice * * \section PIIODevice_sec7 Creating devices by unambiguous string * There are some virtual functions to describe child class without its declaration. * \n \a fullPathPrefix() should returns unique prefix of device * \n \a constructFullPath() should returns full unambiguous string, contains prefix and all device parameters * \n \a configureFromFullPath() provide configuring device from full unambiguous string without prefix and "://" * \n Macro PIIODEVICE should be used instead of PIOBJECT * \n Macro REGISTER_DEVICE should be used after definition of class, i.e. at the last line of *.cpp file * \n \n If custom I/O device corresponds there rules, it can be returned by function \a createFromFullPath(). * \n Each PIP I/O device has custom unambiguous string description: * * PIFile: "file://" * * PIBinaryLog: "binlog://[:][:]" * * PISerial: "ser://:[:][:][:]" * * PIEthernet: UDP "eth://UDP::::[:)>]" * * PIEthernet: TCP "eth://TCP::" * * PIUSB: "usb://:[:][:][:]" * \n \n Examples: * * PIFile: "file://../text.txt" * * PIBinaryLog: "binlog://../logs/:mylog_:1" * * PISerial: "ser:///dev/ttyUSB0:9600:8:N:1", equivalent "ser:///dev/ttyUSB0:9600" * * PIEthernet: "eth://TCP:127.0.0.1:16666", "eth://UDP:192.168.0.5:16666:192.168.0.6:16667:mcast:234.0.2.1:mcast:234.0.2.2" * * PIUSB: "usb://0bb4:0c86:1:1:2" * \n \n * So, custom I/O device can be created with next call: * \code{cpp} * // creatring devices * PISerial * ser = (PISerial * )PIIODevice::createFromFullPath("ser://COM1:115200"); * PIEthernet * eth = (PIEthernet * )PIIODevice::createFromFullPath("eth://UDP:127.0.0.1:4001:127.0.0.1:4002"); * // examine devices * piCout << ser << ser->properties(); * piCout << eth << eth->properties(); * \endcode * * \section PIIODevice_ex0 Example * \snippet piiodevice.cpp 0 */ PIMutex PIIODevice::nfp_mutex; PIMap PIIODevice::nfp_cache; PIIODevice::PIIODevice(): PIThread() { mode_ = ReadOnly; _init(); setPath(PIString()); } /*! \brief Constructs a PIIODevice with path and mode * \param path path to device * \param type mode for open */ PIIODevice::PIIODevice(const PIString & path, PIIODevice::DeviceMode mode): PIThread() { mode_ = mode; _init(); setPath(path); } PIIODevice::~PIIODevice() { stop(); if (opened_) { closeDevice(); if (!opened_) closed(); } } void PIIODevice::setOptions(PIIODevice::DeviceOptions o) { options_ = o; optionsChanged(); } bool PIIODevice::setOption(PIIODevice::DeviceOption o, bool yes) { bool ret = isOptionSet(o); options_.setFlag(o, yes); optionsChanged(); return ret; } void PIIODevice::_init() { opened_ = init_ = thread_started_ = false; raise_threaded_read_ = true; ret_func_ = 0; ret_data_ = 0; tri = 0; setOptions(0); setReopenEnabled(true); setReopenTimeout(1000); setThreadedReadBufferSize(4096); timer.setName("__S__reopen_timer"); write_thread.setName("__S__write_thread"); CONNECT2(void, void * , int, &timer, tickEvent, this, check_start); CONNECT(void, &write_thread, started, this, write_func); } void PIIODevice::check_start(void * data, int delim) { //cout << "check " << tread_started_ << endl; if (open()) { thread_started_ = true; timer.stop(); } } void PIIODevice::write_func() { while (!write_thread.isStopping()) { while (!write_queue.isEmpty()) { if (write_thread.isStopping()) return; write_thread.lock(); PIPair item(write_queue.dequeue()); write_thread.unlock(); int ret = write(item.first); threadedWriteEvent(item.second, ret); } msleep(1); } } void PIIODevice::terminate() { timer.stop(); thread_started_ = false; if (!init_) return; if (isRunning()) { stop(); PIThread::terminate(); } } void PIIODevice::begin() { //cout << " begin\n"; if (threadedReadBufferSize() == 0) piCoutObj << "Warning: threadedReadBufferSize() == 0, read may be useless!"; thread_started_ = false; if (!opened_) { if (open()) { thread_started_ = true; //cout << " open && ok\n"; return; } } else { thread_started_ = true; //cout << " ok\n"; return; } //init(); if (!timer.isRunning() && isReopenEnabled()) timer.start(reopenTimeout()); } void PIIODevice::run() { if (!isReadable()) { //cout << "not readable\n"; PIThread::stop(); return; } if (!thread_started_) { piMSleep(5); //cout << "not started\n"; return; } readed_ = read(buffer_tr.data(), buffer_tr.size_s()); if (readed_ <= 0) { piMSleep(10); //cout << readed_ << ", " << errno << ", " << errorString() << endl; return; } //piCoutObj << "readed" << readed_;// << ", " << errno << ", " << errorString(); threadedRead(buffer_tr.data(), readed_); if (raise_threaded_read_) threadedReadEvent(buffer_tr.data(), readed_); } PIByteArray PIIODevice::readForTime(double timeout_ms) { PIByteArray str; if (timeout_ms <= 0.) return str; int ret; uchar * td = new uchar[threadedReadBufferSize()]; tm.reset(); while (tm.elapsed_m() < timeout_ms) { ret = read(td, threadedReadBufferSize()); if (ret <= 0) msleep(1); else str.append(td, ret); } delete[] td; return str; } ullong PIIODevice::writeThreaded(const PIByteArray & data) { write_thread.lock(); write_queue.enqueue(PIPair(data, tri)); ++tri; write_thread.unlock(); return tri - 1; } bool PIIODevice::configure(const PIString & config_file, const PIString & section, bool parent_section) { PIConfig conf(config_file, PIIODevice::ReadOnly); if (!conf.isOpened()) return false; bool ex = true; PIConfig::Entry em; if (section.isEmpty()) em = conf.rootEntry(); else em = conf.getValue(section, PIString(), &ex); if (!ex) return false; PIConfig::Entry * ep = 0; if (parent_section) ep = em.parent(); if (ep != 0) { setReopenEnabled(ep->getValue("reopenEnabled", isReopenEnabled(), &ex)); if (!ex) setReopenEnabled(em.getValue("reopenEnabled", isReopenEnabled())); setReopenTimeout(ep->getValue("reopenTimeout", reopenTimeout(), &ex)); if (!ex) setReopenTimeout(em.getValue("reopenTimeout", reopenTimeout())); setThreadedReadBufferSize(ep->getValue("threadedReadBufferSize", int(buffer_tr.size_s()), &ex)); if (!ex) setThreadedReadBufferSize(em.getValue("threadedReadBufferSize", int(buffer_tr.size_s()))); } else { setReopenEnabled(em.getValue("reopenEnabled", isReopenEnabled())); setReopenTimeout(em.getValue("reopenTimeout", reopenTimeout())); setThreadedReadBufferSize(em.getValue("threadedReadBufferSize", int(buffer_tr.size_s()))); } return configureDevice(&em, ep); } PIString PIIODevice::constructFullPath() const { return fullPathPrefix() + "://" + constructFullPathDevice() + fullPathOptions(); } void PIIODevice::configureFromFullPath(const PIString & full_path) { PIString fp; DeviceMode dm = ReadWrite; DeviceOptions op = 0; splitFullPath(full_path, &fp, &dm, &op); setMode(dm); setOptions(op); configureFromFullPathDevice(fp); } void PIIODevice::splitFullPath(PIString fpwm, PIString * full_path, DeviceMode * mode, DeviceOptions * opts) { int dm = 0; DeviceOptions op = 0; if (fpwm.find("(") > 0 && fpwm.find(")") > 0) { PIString dms(fpwm.right(fpwm.length() - fpwm.findLast("(")).takeRange("(", ")").trim().toLowerCase().removeAll(" ")); PIStringList opts(dms.split(",")); piForeachC (PIString & o, opts) { //piCout << dms; if (o == "r" || o == "ro" || o == "read" || o == "readonly") dm |= ReadOnly; if (o == "w" || o == "wo" || o == "write" || o == "writeonly") dm |= WriteOnly; if (o == "br" || o == "blockr" || o == "blockread" || o == "blockingread") op |= BlockingRead; if (o == "bw" || o == "blockw" || o == "blockwrite" || o == "blockingwrite") op |= BlockingWrite; if (o == "brw" || o == "bwr" || o == "blockrw" || o == "blockwr" || o == "blockreadrite" || o == "blockingreadwrite") op |= BlockingRead | BlockingWrite; } fpwm.cutRight(fpwm.length() - fpwm.findLast("(")).trim(); } if (dm == 0) dm = ReadWrite; if (full_path) *full_path = fpwm; if (mode) *mode = (DeviceMode)dm; if (opts) *opts = op; } PIString PIIODevice::fullPathOptions() const { if (mode_ == ReadWrite && options_ == 0) return PIString(); PIString ret(" ("); bool f = true; if (mode_ == ReadOnly) {if (!f) ret += ","; f = false; ret += "ro";} if (mode_ == WriteOnly) {if (!f) ret += ","; f = false; ret += "wo";} if (options_[BlockingRead]) {if (!f) ret += ","; f = false; ret += "br";} if (options_[BlockingWrite]) {if (!f) ret += ","; f = false; ret += "bw";} return ret + ")"; } PIIODevice * PIIODevice::createFromFullPath(const PIString & full_path) { PIString prefix = full_path.left(full_path.find(":")); if (prefix.isEmpty()) return 0; PIVector rd(PICollection::groupElements("__PIIODevices__")); piForeachC (PIObject * d, rd) { if (prefix == ((const PIIODevice * )d)->fullPathPrefix()) { PIIODevice * nd = ((const PIIODevice * )d)->copy(); if (nd) nd->configureFromFullPath(full_path.mid(prefix.length() + 3)); cacheFullPath(full_path, nd); return nd; } } return 0; } PIString PIIODevice::normalizeFullPath(const PIString & full_path) { nfp_mutex.lock(); PIString ret = nfp_cache.value(full_path); if (!ret.isEmpty()) { nfp_mutex.unlock(); return ret; } nfp_mutex.unlock(); PIIODevice * d = createFromFullPath(full_path); //piCout << "normalizeFullPath" << d; if (d == 0) return PIString(); ret = d->constructFullPath(); delete d; return ret; } void PIIODevice::cacheFullPath(const PIString & full_path, const PIIODevice * d) { PIMutexLocker nfp_ml(nfp_mutex); nfp_cache[full_path] = d->constructFullPath(); } bool PIIODevice::threadedRead(uchar *readed, int size) { // piCout << "iodevice threaded read"; if (ret_func_ != 0) return ret_func_(ret_data_, readed, size); return true; }