Files
pip/libs/main/io_devices/piiodevice.cpp

637 lines
19 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
PIP - Platform Independent Primitives
Abstract input/output device
Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru
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, 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 Lesser General 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 "piiodevice.h"
#include "piconfig.h"
#include "piconnection.h"
#include "pipropertystorage.h"
//! \class PIIODevice piiodevice.h
//! \~\details
//! \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 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 declaration of class, i.e. at the last line of *.h 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(): PIObject() {
mode_ = ReadOnly;
_init();
setPath(PIString());
}
PIIODevice::PIIODevice(const PIString & path, PIIODevice::DeviceMode mode): PIObject() {
mode_ = mode;
_init();
setPath(path);
}
PIIODevice::~PIIODevice() {
stop();
}
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::setReopenEnabled(bool yes) {
setProperty("reopenEnabled", yes);
reopen_enabled = yes;
}
void PIIODevice::setReopenTimeout(int msecs) {
setProperty("reopenTimeout", msecs);
reopen_timeout = msecs;
}
//! \~\details
//! \~english
//! Set external static function of threaded read that will be executed
//! at every successful threaded read. Function should have format
//! "bool func(void * data, uchar * readed, int size)"
//! \~russian
//! Устанавливает внешний статический метод, который будет вызван
//! после каждого успешного потокового чтения. Метод должен быть
//! в формате "bool func(void * data, uchar * readed, int size)"
void PIIODevice::setThreadedReadSlot(ReadRetFunc func) {
func_read = func;
}
//! \~\details
//! \~english
//! Default size is 4096 bytes. If your device can read at single read
//! more than 4096 bytes you should use this function to adjust buffer size
//! \~russian
//! По умолчанию 4096 байт. Если устройство за одно чтение может читать
//! более 4096 байт, необходимо использовать этот метод для установки
//! нужного размера буфера
void PIIODevice::setThreadedReadBufferSize(int new_size) {
threaded_read_buffer_size = new_size;
threadedReadBufferSizeChanged();
}
bool PIIODevice::isThreadedRead() const {
return read_thread.isRunning();
}
void PIIODevice::startThreadedRead() {
if (!read_thread.isRunning()) {
buffer_tr.resize(threaded_read_buffer_size);
read_thread.start();
}
}
void PIIODevice::startThreadedRead(ReadRetFunc func) {
func_read = func;
startThreadedRead();
}
void PIIODevice::stopThreadedRead() {
if (!isThreadedRead()) return;
#ifdef MICRO_PIP
read_thread.stop();
#else
if (reading_now) {
read_thread.terminate();
reading_now = false;
} else {
read_thread.stop();
}
#endif
}
bool PIIODevice::waitThreadedReadFinished(int timeout_ms) {
return read_thread.waitForFinish(timeout_ms);
}
bool PIIODevice::isThreadedWrite() const {
return write_thread.isRunning();
}
void PIIODevice::startThreadedWrite() {
if (!write_thread.isRunning())
write_thread.startOnce();
}
void PIIODevice::stopThreadedWrite() {
if (!write_thread.isRunning()) return;
write_thread.stop();
}
bool PIIODevice::waitThreadedWriteFinished(int timeout_ms) {
return write_thread.waitForFinish(timeout_ms);
}
void PIIODevice::clearThreadedWriteQueue() {
write_thread.lock();
write_queue.clear();
write_thread.unlock();
}
void PIIODevice::start() {
startThreadedRead();
startThreadedWrite();
}
void PIIODevice::stop() {
stopThreadedRead();
stopThreadedWrite();
}
ssize_t PIIODevice::read(void * read_to, ssize_t max_size) {
ssize_t ret = readDevice(read_to, max_size);
return ret;
}
ssize_t PIIODevice::read(PIMemoryBlock mb) {
return read(mb.data(), mb.size());
}
PIByteArray PIIODevice::read(ssize_t max_size) {
if (max_size <= 0) return PIByteArray();
buffer_in.resize(max_size);
ssize_t ret = readDevice(buffer_in.data(), max_size);
if (ret < 0) return PIByteArray();
return buffer_in.resized(ret);
}
ssize_t PIIODevice::write(const void * data, ssize_t max_size) {
if (max_size <= 0) return 0;
return writeDevice(data, max_size);
}
void PIIODevice::_init() {
reading_now = false;
setOptions(0);
setReopenEnabled(true);
setReopenTimeout(1000);
#ifdef MICRO_PIP
threaded_read_buffer_size = 512;
#else
threaded_read_buffer_size = 4096;
#endif
read_thread .setName("__S__.PIIODevice.read_thread" );
write_thread.setName("__S__.PIIODevice.write_thread");
CONNECT(void, &write_thread, started, this, write_func);
CONNECTL(&read_thread, started, [this](){if (!isOpened()) open();});
read_thread.setSlot([this](void*){read_func();});
}
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);
}
piMinSleep();
}
}
PIIODevice * PIIODevice::newDeviceByPrefix(const char * prefix) {
if (!prefix) return nullptr;
auto fi = fabrics().value(prefix);
if (fi.fabricator)
return fi.fabricator();
return nullptr;
}
void PIIODevice::read_func() {
if (!isReadable()) {
read_thread.stop();
return;
}
if (!isOpened()) {
piMSleep(10);
bool ok = false;
if (reopen_enabled) {
if (reopen_tm.elapsed_m() >= reopen_timeout) {
reopen_tm.reset();
ok = open();
}
}
if (!ok) return;
}
ssize_t 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_);
threadedReadEvent(buffer_tr.data(), readed_);
}
PIByteArray PIIODevice::readForTime(double timeout_ms) {
PIByteArray str;
if (timeout_ms <= 0.) return str;
ssize_t ret;
uchar * td = new uchar[threaded_read_buffer_size];
tm.reset();
while (tm.elapsed_m() < timeout_ms) {
ret = read(td, threaded_read_buffer_size);
if (ret <= 0) piMinSleep();
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::open() {
buffer_tr.resize(threaded_read_buffer_size);
opened_ = openDevice();
if (opened_) opened();
return opened_;
}
bool PIIODevice::open(const PIString & _path) {
setPath(_path);
return open();
}
bool PIIODevice::open(DeviceMode _mode) {
mode_ = _mode;
return open();
}
bool PIIODevice::open(const PIString & _path, DeviceMode _mode) {
setPath(_path);
mode_ = _mode;
return open();
}
bool PIIODevice::close() {
opened_ = !closeDevice();
if (!opened_) closed();
return !opened_;
}
ssize_t PIIODevice::write(PIByteArray data) {
return writeDevice(data.data(), data.size_s());
}
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).toBool());
if (!ex) setReopenEnabled(em.getValue("reopenEnabled", isReopenEnabled()).toBool());
setReopenTimeout(ep->getValue("reopenTimeout", reopenTimeout(), &ex).toInt());
if (!ex) setReopenTimeout(em.getValue("reopenTimeout", reopenTimeout()).toInt());
setThreadedReadBufferSize(ep->getValue("threadedReadBufferSize", int(threaded_read_buffer_size), &ex).toInt());
if (!ex) setThreadedReadBufferSize(em.getValue("threadedReadBufferSize", int(threaded_read_buffer_size)).toInt());
} else {
setReopenEnabled(em.getValue("reopenEnabled", isReopenEnabled()).toBool());
setReopenTimeout(em.getValue("reopenTimeout", reopenTimeout()).toInt());
setThreadedReadBufferSize(em.getValue("threadedReadBufferSize", int(threaded_read_buffer_size)).toInt());
}
return configureDevice(&em, ep);
}
PIString PIIODevice::constructFullPath() const {
return fullPathPrefix().toString() + PIStringAscii("://") + 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);
}
PIVariantTypes::IODevice PIIODevice::constructVariant() const {
PIVariantTypes::IODevice ret;
ret.prefix = fullPathPrefix();
ret.mode = mode();
ret.options = options();
ret.set(constructVariantDevice());
return ret;
}
void PIIODevice::configureFromVariant(const PIVariantTypes::IODevice & d) {
setMode((DeviceMode)d.mode);
setOptions((DeviceOptions)d.options);
configureFromVariantDevice(d.get());
}
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 == PIStringAscii("r") || o == PIStringAscii("ro") || o == PIStringAscii("read") || o == PIStringAscii("readonly"))
dm |= ReadOnly;
if (o == PIStringAscii("w") || o == PIStringAscii("wo") || o == PIStringAscii("write") || o == PIStringAscii("writeonly"))
dm |= WriteOnly;
if (o == PIStringAscii("br") || o == PIStringAscii("blockr") || o == PIStringAscii("blockread") || o == PIStringAscii("blockingread"))
op |= BlockingRead;
if (o == PIStringAscii("bw") || o == PIStringAscii("blockw") || o == PIStringAscii("blockwrite") || o == PIStringAscii("blockingwrite"))
op |= BlockingWrite;
if (o == PIStringAscii("brw") || o == PIStringAscii("bwr") || o == PIStringAscii("blockrw") || o == PIStringAscii("blockwr") || o == PIStringAscii("blockreadrite") || o == PIStringAscii("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;
}
PIStringList PIIODevice::availablePrefixes() {
PIStringList ret;
for (const auto & i: fabrics())
ret << i.second.prefix.toString();
return ret;
}
PIStringList PIIODevice::availableClasses() {
PIStringList ret;
for (const auto & i: fabrics())
ret << i.second.classname.toString();
return ret;
}
void PIIODevice::registerDevice(PIConstChars prefix, PIConstChars classname, PIIODevice * (*fabric)()) {
if (prefix.isEmpty()) return;
//printf("registerDevice %s %d %d\n", prefix, p.isEmpty(), fabrics().size());
if (!fabrics().contains(prefix)) {
FabricInfo fi;
fi.prefix = prefix;
fi.classname = classname;
fi.fabricator = fabric;
fabrics()[prefix] = fi;
}
}
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 + ")";
}
//! \~\details
//! \~english
//! To function \a configureFromFullPath() "full_path" passed without \a fullPathPrefix() and "://".
//! See \ref PIIODevice_sec7
//! \~russian
//! В метод \a configureFromFullPath() "full_path" передается без \a fullPathPrefix() и "://".
//! См. \ref PIIODevice_sec7
PIIODevice * PIIODevice::createFromFullPath(const PIString & full_path) {
PIString prefix = full_path.left(full_path.find(":"));
PIIODevice * nd = newDeviceByPrefix(prefix.dataAscii());
if (!nd) return nullptr;
nd->configureFromFullPath(full_path.mid(prefix.length() + 3));
cacheFullPath(full_path, nd);
return nd;
}
PIIODevice * PIIODevice::createFromVariant(const PIVariantTypes::IODevice & d) {
PIIODevice * nd = newDeviceByPrefix(d.prefix.dataAscii());
if (!nd) return nullptr;
nd->configureFromVariant(d);
return nd;
}
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();
}
PIMap<PIConstChars, PIIODevice::FabricInfo> & PIIODevice::fabrics() {
static PIMap<PIConstChars, FabricInfo> ret;
return ret;
}
bool PIIODevice::threadedRead(const uchar *readed, ssize_t size) {
// piCout << "iodevice threaded read";
if (func_read) return func_read(readed, size, ret_data_);
return true;
}
PIPropertyStorage PIIODevice::constructVariantDevice() const {
PIPropertyStorage ret;
ret.addProperty("path", path());
return ret;
}
void PIIODevice::configureFromVariantDevice(const PIPropertyStorage & d) {
setPath(d.propertyValueByName("path").toString());
}
void PIIODevice::softStopThreadedRead() {
read_thread.stop();
}