Files
pip/src_main/io/piiodevice.cpp

408 lines
14 KiB
C++
Executable File

/*
PIP - Platform Independent Primitives
Abstract input/output device
Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
*/
#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://<path>"
* * PIBinaryLog: "binlog://<logDir>[:<filePrefix>][:<defaultID>]"
* * PISerial: "ser://<device>:<speed(50|...|115200)>[:<dataBitsCount(6|7|8)>][:<parity(N|E|O)>][:<stopBits(1|2)>]"
* * PIEthernet: UDP "eth://UDP:<readIP>:<readPort>:<sendIP>:<sendPort>[:<multicast(mcast:<ip>)>]"
* * PIEthernet: TCP "eth://TCP:<IP>:<Port>"
* * PIUSB: "usb://<vid>:<pid>[:<deviceNumber>][:<readEndpointNumber>][:<writeEndpointNumber>]"
* \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<PIString, PIString> 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<PIByteArray, ullong> 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<PIByteArray, ullong>(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<const PIObject * > 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;
}