/*! \file piiodevice.h * \ingroup IO * \~\brief * \~english Abstract input/output device * \~russian Базовый класс утройств ввода/вывода */ /* 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 . */ #ifndef PIIODEVICE_H #define PIIODEVICE_H #include "piinit.h" #include "piqueue.h" #include "pitimer.h" /// TODO: написать документацию, тут ничего не понятно // function executed from threaded read, pass readedData, sizeOfData, ThreadedReadData typedef std::function ReadRetFunc; #ifdef DOXYGEN //! \relatesalso PIIODevice //! \brief //! \~english Enable device instances creation with \a PIIODevice::createFromFullPath() function. //! \~russian Включить создание экземпляров устройства с помощью метода \a PIIODevice::createFromFullPath(). //! \~\details //! \~english This macro may be placed in cpp or header file, but preferred place is header //! \~russian Этот макрос может быть расположен в cpp или заголовочном файле, но предпочтительнее распологать в заголовочном # define REGISTER_DEVICE(class) //! \relatesalso PIIODevice //! \brief //! \~english Use this macro instead of PIOBJECT when describe your own PIIODevice. //! \~russian Используйте этот макрос вместо PIOBJECT при объявлении своего PIIODevice. //! \~\param "prefix" //! \~english Unique device prefix in quotes, may be "" //! \~russian Уникальный префикс устройства в кавычках, может быть "" # define PIIODEVICE(class, "prefix") #else # define REGISTER_DEVICE(name) \ STATIC_INITIALIZER_BEGIN \ PIIODevice::registerDevice(name::fullPathPrefixS(), #name, []() -> PIIODevice * { return new name(); }); \ STATIC_INITIALIZER_END # define PIIODEVICE(name, prefix) \ PIOBJECT_SUBCLASS(name, PIIODevice) \ PIIODevice * copy() const override { \ return new name(); \ } \ \ public: \ PIConstChars fullPathPrefix() const override { \ return prefix; \ } \ static PIConstChars fullPathPrefixS() { \ return prefix; \ } \ \ private: #endif //! \ingroup IO //! \~\brief //! \~english Base class for input/output devices. //! \~russian Базовый класс утройств ввода/вывода. class PIP_EXPORT PIIODevice: public PIObject { PIOBJECT_SUBCLASS(PIIODevice, PIObject); friend void __DevicePool_threadReadDP(void * ddp); public: NO_COPY_CLASS(PIIODevice); //! \~english Constructs a empty %PIIODevice //! \~russian Создает пустой %PIIODevice explicit PIIODevice(); //! \~english Open modes for PIIODevice //! \~russian Режимы открытия для PIIODevice enum DeviceMode { ReadOnly /*! \~english Device can only read \~russian Устройство может только читать */ = 0x01, WriteOnly /*! \~english Device can only write \~russian Устройство может только писать */ = 0x02, ReadWrite /*! \~english Device can both read and write \~russian Устройство может читать и писать */ = 0x03 }; //! \~english Options for PIIODevice, works with some devices //! \~russian Опции для PIIODevice, работает для некоторых устройств enum DeviceOption { BlockingRead /*! \~english \a read() block until data is received, default off \~russian \a read() блокируется, пока данные не поступят, по умолчанию выключено */ = 0x01, BlockingWrite /*! \~english \a write() block until data is sent, default off \~russian \a write() блокируется, пока данные не запишутся, по умолчанию выключено */ = 0x02 }; //! \~english Characteristics of PIIODevice channel //! \~russian Характеристики канала PIIODevice enum DeviceInfoFlag { Sequential /*! \~english Continuous bytestream without packets \~russian Непрерывный поток байт, без пакетирования */ = 0x01, Reliable /*! \~english Channel without data errors or corruptions \~russian Канал без ошибок или повреждений данных */ = 0x02 }; struct FabricInfo { PIConstChars prefix; PIConstChars classname; PIIODevice * (*fabricator)() = nullptr; }; typedef PIFlags DeviceOptions; typedef PIFlags DeviceInfoFlags; //! \~english Constructs %PIIODevice with path "path" and open mode "mode" //! \~russian Создает %PIIODevice с путём "path" и режимом открытия "mode" explicit PIIODevice(const PIString & path, DeviceMode mode = ReadWrite); virtual ~PIIODevice(); //! \~english Returns current open mode of device //! \~russian Возвращает текущий режим открытия устройства DeviceMode mode() const { return mode_; } //! \~english Set open mode of device. Don`t reopen device //! \~russian Устанавливает режим открытия устройства. Не переоткрывает устройство void setMode(DeviceMode m) { mode_ = m; } //! \~english Returns current device options //! \~russian Возвращает текущие опции устройства DeviceOptions options() const { return options_; } //! \~english Returns current device option "o" state //! \~russian Возвращает текущее состояние опции "o" bool isOptionSet(DeviceOption o) const { return options_[o]; } //! \~english Set device options //! \~russian Устанавливает опции устройства void setOptions(DeviceOptions o); //! \~english Set device option "o" to "yes" and returns previous state //! \~russian Устанавливает опцию "o" устройства в "yes" и возвращает предыдущее состояние опции bool setOption(DeviceOption o, bool yes = true); //! \~english Returns device characteristic flags //! \~russian Возвращает характеристики канала DeviceInfoFlags infoFlags() const { return deviceInfoFlags(); } //! \~english Returns current path of device //! \~russian Возвращает текущий путь устройства PIString path() const { return property("path").toString(); } //! \~english Set path of device. Don`t reopen device //! \~russian Устанавливает путь устройства. Не переоткрывает устройство void setPath(const PIString & path) { setProperty("path", path); } //! \~english Returns if mode is ReadOnly or ReadWrite //! \~russian Возвращает равен ли режим открытия ReadOnly или ReadWrite bool isReadable() const { return (mode_ & ReadOnly); } //! \~english Returns if mode is WriteOnly or ReadWrite //! \~russian Возвращает равен ли режим открытия WriteOnly или ReadWrite bool isWriteable() const { return (mode_ & WriteOnly); } //! \~english Returns if device is successfully opened //! \~russian Возвращает успешно ли открыто устройство bool isOpened() const { return opened_; } //! \~english Returns if device is closed //! \~russian Возвращает закрыто ли устройство bool isClosed() const { return !opened_; } //! \~english Returns if device can read \b now //! \~russian Возвращает может ли устройство читать \b сейчас virtual bool canRead() const { return opened_ && (mode_ & ReadOnly); } //! \~english Returns if device can write \b now //! \~russian Возвращает может ли устройство писать \b сейчас virtual bool canWrite() const { return opened_ && (mode_ & WriteOnly); } //! \~english Set calling of \a open() enabled while threaded read on closed device //! \~russian Устанавливает возможность вызова \a open() при потоковом чтении на закрытом устройстве void setReopenEnabled(bool yes = true); //! \~english Set timeout in milliseconds between \a open() tryings if reopen is enabled //! \~russian Устанавливает задержку в миллисекундах между вызовами \a open() если переоткрытие активно void setReopenTimeout(int msecs); //! \~english Returns reopen enable //! \~russian Возвращает активно ли переоткрытие bool isReopenEnabled() const { return property("reopenEnabled").toBool(); } //! \~english Returns reopen timeout in milliseconds //! \~russian Возвращает задержку переоткрытия в миллисекундах int reopenTimeout() { return property("reopenTimeout").toInt(); } //! \~english Set threaded read callback //! \~russian Устанавливает callback потокового чтения void setThreadedReadSlot(ReadRetFunc func); //! \~english Set custom data that will be passed to threaded read callback //! \~russian Устанавливает произвольный указатель, который будет передан в callback потокового чтения void setThreadedReadData(void * d) { ret_data_ = d; } //! \~english Set size of threaded read buffer //! \~russian Устанавливает размер буфера потокового чтения void setThreadedReadBufferSize(int new_size); //! \~english Returns size of threaded read buffer //! \~russian Возвращает размер буфера потокового чтения int threadedReadBufferSize() const { return threaded_read_buffer_size; } //! \~english Returns content of threaded read buffer //! \~russian Возвращает содержимое буфера потокового чтения const uchar * threadedReadBuffer() const { return buffer_tr.data(); } //! \~english Returns custom data that will be passed to threaded read callback //! \~russian Возвращает произвольный указатель, который будет передан в callback потокового чтения void * threadedReadData() const { return ret_data_; } //! \~english Returns if threaded read is started //! \~russian Возвращает запущен ли поток чтения bool isThreadedRead() const; //! \~english Start threaded read //! \~russian Запускает потоковое чтение void startThreadedRead(); //! \~english Start threaded read and assign threaded read callback to "func" //! \~russian Запускает потоковое чтение и устанавливает callback потокового чтения в "func" void startThreadedRead(ReadRetFunc func); //! \~english Stop threaded read. //! \~russian Останавливает потоковое чтение. void stopThreadedRead(); //! \~english Wait for threaded read finish no longer than "timeout_ms" milliseconds. //! \~russian Ожидает завершения потокового чтения в течении не более "timeout_ms" миллисекунд. bool waitThreadedReadFinished(int timeout_ms = -1); //! \~english Returns if threaded write is started //! \~russian Возвращает запущен ли поток записи bool isThreadedWrite() const; //! \~english Start threaded write //! \~russian Запускает потоковую запись void startThreadedWrite(); //! \~english Stop threaded write. //! \~russian Останавливает потоковую запись. void stopThreadedWrite(); //! \~english Wait for threaded write finish no longer than "timeout_ms" milliseconds. //! \~russian Ожидает завершения потоковой записи в течении не более "timeout_ms" миллисекунд. bool waitThreadedWriteFinished(int timeout_ms = -1); //! \~english Clear threaded write task queue //! \~russian Очищает очередь потоковой записи void clearThreadedWriteQueue(); //! \~english Start both threaded read and threaded write //! \~russian Запускает потоковое чтение и запись void start(); //! \~english Stop both threaded read and threaded write. //! \~russian Останавливает потоковое чтение и запись. void stop(); //! \~english Stop both threaded read and threaded write and wait for finish. //! \~russian Останавливает потоковое чтение и запись и ожидает завершения. void stopAndWait(int timeout_ms = -1); //! \~english Interrupt blocking operation. //! \~russian Прерывает блокирующую операцию. virtual void interrupt() {} //! \~english Read from device maximum "max_size" bytes to "read_to" //! \~russian Читает из устройства не более "max_size" байт в "read_to" ssize_t read(void * read_to, ssize_t max_size); //! \~english Read from device to memory block "mb" //! \~russian Читает из устройства в блок памяти "mb" ssize_t read(PIMemoryBlock mb); //! \~english Read from device maximum "max_size" bytes and returns them as PIByteArray //! \~russian Читает из устройства не более "max_size" байт и возвращает данные как PIByteArray PIByteArray read(ssize_t max_size); //! \~english Returns the number of bytes that are available for reading. //! \~russian Возвращает количество байт доступных для чтения //! \~\details //! \~english This function is commonly used with sequential devices //! to determine the number of bytes to allocate in a buffer before reading. //! If function returns -1 it mean that number of bytes undefined. //! \~russian Эта функция как правило используется чтобы знать какой //! размер буфера нужен в памяти для чтения. //! Если функция возвращает -1 это значит что количество байт для чтения не известно. virtual ssize_t bytesAvailable() const { return -1; } //! \~english Write maximum "max_size" bytes of "data" to device //! \~russian Пишет в устройство не более "max_size" байт из "data" ssize_t write(const void * data, ssize_t max_size); //! \~english Read from device for "timeout_ms" milliseconds and return readed data as PIByteArray. //! Timeout should to be greater than 0 //! \~russian Читает из устройства в течении "timeout_ms" миллисекунд и возвращает данные как PIByteArray. //! Таймаут должен быть больше 0 PIByteArray readForTime(double timeout_ms); //! \~english Add task to threaded write queue and return task ID //! \~russian Добавляет данные в очередь на потоковую запись и возвращает ID задания ullong writeThreaded(const void * data, ssize_t max_size) { return writeThreaded(PIByteArray(data, uint(max_size))); } //! \~english Add task to threaded write queue and return task ID //! \~russian Добавляет данные в очередь на потоковую запись и возвращает ID задания ullong writeThreaded(const PIByteArray & data); //! \~english Configure device from section "section" of file "config_file", if "parent_section" parent section also will be read //! \~russian bool configure(const PIString & config_file, const PIString & section, bool parent_section = false); //! \~english Returns full unambiguous string prefix. \ref PIIODevice_sec7 //! \~russian Возвращает префикс устройства. \ref PIIODevice_sec7 virtual PIConstChars fullPathPrefix() const { return ""; } static PIConstChars fullPathPrefixS() { return ""; } //! \~english Returns full unambiguous string, describes this device, \a fullPathPrefix() + "://" + ... //! \~russian Возвращает строку полного описания для этого устройства, \a fullPathPrefix() + "://" + ... PIString constructFullPath() const; //! \~english Configure device with parameters of full unambiguous string //! \~russian Настраивает устройство из параметров строки полного описания void configureFromFullPath(const PIString & full_path); //! \~english Returns PIVariantTypes::IODevice, describes this device //! \~russian Возвращает PIVariantTypes::IODevice, описывающий это устройство PIVariantTypes::IODevice constructVariant() const; //! \~english Configure device from PIVariantTypes::IODevice //! \~russian Настраивает устройство из PIVariantTypes::IODevice void configureFromVariant(const PIVariantTypes::IODevice & d); //! \~english Try to create new device by prefix, configure it with \a configureFromFullPath() and returns it. //! \~russian Пытается создать новое устройство по префиксу, настраивает с помощью \a configureFromFullPath() и возвращает его static PIIODevice * createFromFullPath(const PIString & full_path); //! \~english Try to create new device by prefix, configure it with \a configureFromVariant() and returns it. //! \~russian Пытается создать новое устройство по префиксу, настраивает с помощью \a configureFromVariant() и возвращает его static PIIODevice * createFromVariant(const PIVariantTypes::IODevice & d); static PIString normalizeFullPath(const PIString & full_path); static void splitFullPath(PIString fpwm, PIString * full_path, DeviceMode * mode = 0, DeviceOptions * opts = 0); //! \~english Returns fullPath prefixes of all registered devices //! \~russian Возвращает префиксы всех зарегистрированных устройств static PIStringList availablePrefixes(); //! \~english Returns class names of all registered devices //! \~russian Возвращает имена классов всех зарегистрированных устройств static PIStringList availableClasses(); static void registerDevice(PIConstChars prefix, PIConstChars classname, PIIODevice * (*fabric)()); EVENT_HANDLER(bool, open); EVENT_HANDLER1(bool, open, const PIString &, _path); bool open(DeviceMode _mode); EVENT_HANDLER2(bool, open, const PIString &, _path, DeviceMode, _mode); EVENT_HANDLER(bool, close); EVENT_HANDLER1(ssize_t, write, PIByteArray, data); //! \~english Write memory block "mb" to device //! \~russian Пишет в устройство блок памяти "mb" ssize_t write(const PIMemoryBlock & mb) { return write(mb.data(), mb.size()); } EVENT_VHANDLER(void, flush) { ; } EVENT(opened); EVENT(closed); EVENT2(threadedReadEvent, const uchar *, readed, ssize_t, size); EVENT2(threadedWriteEvent, ullong, id, ssize_t, written_size); //! \handlers //! \{ //! \fn bool open() //! \~english Open device //! \~russian Открывает устройство //! \fn bool open(const PIString & path) //! \~english Open device with path "path" //! \~russian Открывает устройство с путём "path" //! \fn bool open(const DeviceMode & mode) //! \~english Open device with mode "mode" //! \~russian Открывает устройство с режимом открытия "mode" //! \fn bool open(const PIString & path, const DeviceMode & mode) //! \~english Open device with path "path" and mode "mode" //! \~russian Открывает устройство с путём "path" и режимом открытия "mode" //! \fn bool close() //! \~english Close device //! \~russian Закрывает устройство //! \fn ssize_t write(PIByteArray data) //! \~english Write "data" to device //! \~russian Пишет "data" в устройство //! \} //! \vhandlers //! \{ //! \fn void flush() //! \~english Immediate write all buffers //! \~russian Немедленно записать все буферизированные данные //! \} //! \events //! \{ //! \fn void opened() //! \~english Raise if succesfull open //! \~russian Вызывается при успешном открытии //! \fn void closed() //! \~english Raise if succesfull close //! \~russian Вызывается при успешном закрытии //! \fn void threadedReadEvent(const uchar * readed, ssize_t size) //! \~english Raise if read thread succesfull read some data //! \~russian Вызывается при успешном потоковом чтении данных //! \fn void threadedWriteEvent(ullong id, ssize_t written_size) //! \~english Raise if write thread successfull write some data of task with ID "id" //! \~russian Вызывается при успешной потоковой записи данных с ID задания "id" //! \} //! \ioparams //! \{ #ifdef DOXYGEN //! \~english setReopenEnabled, default "true" //! \~russian setReopenEnabled, по умолчанию "true" bool reopenEnabled; //! \~english setReopenTimeout in milliseconds, default 1000 //! \~russian setReopenTimeout в миллисекундах, по умолчанию 1000 int reopenTimeout; //! \~english setThreadedReadBufferSize in bytes, default 4096 //! \~russian setThreadedReadBufferSize в байтах, по умолчанию 4096 int threadedReadBufferSize; #endif //! \} protected: //! \~english Reimplement to configure device from entries "e_main" and "e_parent", cast arguments to \a PIConfig::Entry* //! \~russian virtual bool configureDevice(const void * e_main, const void * e_parent = 0) { return true; } //! \~english Reimplement to open device, return value will be set to "opened_" variable. //! Don't call this function in subclass, use \a open()! //! \~russian Переопределите для открытия устройства, возвращаемое значение будет установлено в //! переменную "opened_". Не используйте напрямую, только через \a open()! virtual bool openDevice() = 0; // use path_, type_, opened_, init_ variables //! \~english Reimplement to close device, inverse return value will be set to "opened_" variable //! \~russian Переопределите для закрытия устройства, обратное возвращаемое значение будет установлено в переменную "opened_" virtual bool closeDevice() { return true; } // use path_, type_, opened_, init_ variables //! \~english Reimplement this function to read from your device //! \~russian Переопределите для чтения данных из устройства virtual ssize_t readDevice(void * read_to, ssize_t max_size) { piCoutObj << "\"read\" is not implemented!"; return -2; } //! \~english Reimplement this function to write to your device //! \~russian Переопределите для записи данных в устройство virtual ssize_t writeDevice(const void * data, ssize_t max_size) { piCoutObj << "\"write\" is not implemented!"; return -2; } //! \~english Function executed when thread read some data, default implementation execute external callback "ret_func_" //! \~russian Метод вызывается после каждого успешного потокового чтения, по умолчанию вызывает callback "ret_func_" virtual bool threadedRead(const uchar * readed, ssize_t size); //! \~english Reimplement to construct full unambiguous string, describes this device. //! Default implementation returns \a path() //! \~russian Переопределите для создания строки полного описания устройства. //! По умолчанию возвращает \a path() virtual PIString constructFullPathDevice() const { return path(); } //! \~english Reimplement to configure your device with parameters of full unambiguous string. //! Default implementation call \a setPath() //! \~russian Переопределите для настройки устройства из строки полного описания. //! По умолчанию вызывает \a setPath() virtual void configureFromFullPathDevice(const PIString & full_path) { setPath(full_path); } //! \~english Reimplement to construct device properties. //! Default implementation return PIPropertyStorage with \"path\" entry //! \~russian Переопределите для создания свойств устройства. //! По умолчанию возвращает PIPropertyStorage со свойством \"path\" virtual PIPropertyStorage constructVariantDevice() const; //! \~english Reimplement to configure your device from PIPropertyStorage. Options and mode already applied. //! Default implementation apply \"path\" entry //! \~russian Переопределите для настройки устройства из PIPropertyStorage. Опции и режим уже применены. //! По умолчанию устанавливает свойство \"path\" virtual void configureFromVariantDevice(const PIPropertyStorage & d); //! \~english Reimplement to apply new device options //! \~russian Переопределите для применения новых опций устройства virtual void optionsChanged() { ; } //! \~english Reimplement to return correct \a DeviceInfoFlags. Default implementation returns 0 //! \~russian Переопределите для возврата правильных \a DeviceInfoFlags. По умолчанию возвращает 0 virtual DeviceInfoFlags deviceInfoFlags() const { return 0; } //! \~english Reimplement to apply new \a threadedReadBufferSize() //! \~russian Переопределите для применения нового \a threadedReadBufferSize() virtual void threadedReadBufferSizeChanged() { ; } static PIIODevice * newDeviceByPrefix(const char * prefix); bool isThreadedReadStopping() const { return read_thread.isStopping(); } DeviceMode mode_; DeviceOptions options_; ReadRetFunc func_read = nullptr; std::atomic_bool opened_; void * ret_data_ = nullptr; private: EVENT_HANDLER(void, read_func); EVENT_HANDLER(void, write_func); virtual PIIODevice * copy() const { return nullptr; } PIString fullPathOptions() const; void _init(); static void cacheFullPath(const PIString & full_path, const PIIODevice * d); static PIMap & fabrics(); PITimeMeasurer tm, reopen_tm; PIThread read_thread, write_thread; PIByteArray buffer_in, buffer_tr; PIQueue> write_queue; ullong tri = 0; uint threaded_read_buffer_size, reopen_timeout = 1000; bool reopen_enabled = true, destroying = false; static PIMutex nfp_mutex; static PIMap nfp_cache; }; #endif // PIIODEVICE_H