//! \~\file piiodevice.h //! \~\ingroup IO //! \~\brief //! \~english Core abstraction for configurable input/output devices //! \~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 "pithread.h" //! \~english Callback used by \a setThreadedReadSlot(). //! \~russian Callback, используемый методом \a setThreadedReadSlot(). //! \~\details //! \~english Receives pointer to data read by the background thread, number of bytes and user data set by \a setThreadedReadData(). //! \~russian Принимает указатель на данные, прочитанные фоновым потоком, количество байт и пользовательские данные, заданные через \a //! setThreadedReadData(). 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. //! \~\details //! \~english "prefix" is a unique device prefix used in \a createFromFullPath(). //! \~russian "prefix" это уникальный префикс устройства, используемый в \a createFromFullPath(). # 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 an 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 Generic options supported by some devices. //! \~russian Общие опции, поддерживаемые некоторыми устройствами. 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 the device channel. //! \~russian Характеристики канала устройства. 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; }; //! \~english Bitmask of \a DeviceOption values. //! \~russian Битовая маска значений \a DeviceOption. typedef PIFlags DeviceOptions; //! \~english Bitmask of \a DeviceInfoFlag values. //! \~russian Битовая маска значений \a DeviceInfoFlag. 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); //! \~english Destroys the device base object. //! \~russian Уничтожает базовый объект устройства. virtual ~PIIODevice(); //! \~english Returns current open mode. //! \~russian Возвращает текущий режим открытия. DeviceMode mode() const { return mode_; } //! \~english Sets open mode without reopening the device. //! \~russian Устанавливает режим открытия без переоткрытия устройства. void setMode(DeviceMode m) { mode_ = m; } //! \~english Returns current device options. //! \~russian Возвращает текущие опции устройства. DeviceOptions options() const { return options_; } //! \~english Returns whether option "o" is enabled. //! \~russian Возвращает, включена ли опция "o". bool isOptionSet(DeviceOption o) const { return options_[o]; } //! \~english Replaces all current device options with "o". //! \~russian Полностью заменяет текущие опции устройства на "o". void setOptions(DeviceOptions o); //! \~english Sets option "o" to "yes" and returns its previous state. //! \~russian Устанавливает опцию "o" в состояние "yes" и возвращает её предыдущее состояние. bool setOption(DeviceOption o, bool yes = true); //! \~english Returns device channel characteristics. //! \~russian Возвращает характеристики канала устройства. DeviceInfoFlags infoFlags() const { return deviceInfoFlags(); } //! \~english Returns current device path. //! \~russian Возвращает текущий путь устройства. PIString path() const { return property("path").toString(); } //! \~english Sets device path without reopening the device. //! \~russian Устанавливает путь устройства без его переоткрытия. void setPath(const PIString & path) { setProperty("path", path); } //! \~english Returns whether the current mode allows reading. //! \~russian Возвращает, разрешает ли текущий режим чтение. bool isReadable() const { return (mode_ & ReadOnly); } //! \~english Returns whether the current mode allows writing. //! \~russian Возвращает, разрешает ли текущий режим запись. bool isWriteable() const { return (mode_ & WriteOnly); } //! \~english Returns whether the device is currently opened. //! \~russian Возвращает, открыто ли сейчас устройство. bool isOpened() const { return opened_; } //! \~english Returns whether the device is currently closed. //! \~russian Возвращает, закрыто ли сейчас устройство. bool isClosed() const { return !opened_; } //! \~english Returns whether reading is possible right now. //! \~russian Возвращает, возможно ли чтение прямо сейчас. virtual bool canRead() const { return opened_ && (mode_ & ReadOnly); } //! \~english Returns whether writing is possible right now. //! \~russian Возвращает, возможна ли запись прямо сейчас. virtual bool canWrite() const { return opened_ && (mode_ & WriteOnly); } //! \~english Enables or disables automatic reopen attempts during threaded read. //! \~russian Включает или выключает автоматические попытки переоткрытия при потоковом чтении. void setReopenEnabled(bool yes = true); //! \~english Sets delay between automatic reopen attempts. //! \~russian Устанавливает задержку между автоматическими попытками переоткрытия. void setReopenTimeout(PISystemTime timeout); //! \~english Returns whether automatic reopen is enabled. //! \~russian Возвращает, включено ли автоматическое переоткрытие. bool isReopenEnabled() const { return property("reopenEnabled").toBool(); } //! \~english Returns delay between automatic reopen attempts. //! \~russian Возвращает задержку между автоматическими попытками переоткрытия. PISystemTime reopenTimeout() { return property("reopenTimeout").toSystemTime(); } //! \~english Sets callback invoked after successful threaded reads. //! \~russian Устанавливает callback, вызываемый после успешного потокового чтения. void setThreadedReadSlot(ReadRetFunc func); //! \~english Sets custom user data passed to threaded read callback. //! \~russian Устанавливает пользовательские данные, передаваемые в callback потокового чтения. void setThreadedReadData(void * d) { ret_data_ = d; } //! \~english Sets background read buffer size in bytes. //! \~russian Устанавливает размер буфера фонового чтения в байтах. void setThreadedReadBufferSize(int new_size); //! \~english Returns background read buffer size in bytes. //! \~russian Возвращает размер буфера фонового чтения в байтах. int threadedReadBufferSize() const { return threaded_read_buffer_size; } //! \~english Returns pointer to the internal threaded-read buffer. //! \~russian Возвращает указатель на внутренний буфер потокового чтения. const uchar * threadedReadBuffer() const { return buffer_tr.data(); } //! \~english Returns custom data passed to threaded read callback. //! \~russian Возвращает пользовательские данные, передаваемые в callback потокового чтения. void * threadedReadData() const { return ret_data_; } //! \~english Returns whether threaded read is running. //! \~russian Возвращает, запущено ли потоковое чтение. bool isThreadedRead() const; //! \~english Returns whether threaded read is stopping. //! \~russian Возвращает, находится ли потоковое чтение в процессе остановки. bool isThreadedReadStopping() const { return read_thread.isStopping(); } //! \~english Starts threaded read. //! \~russian Запускает потоковое чтение. void startThreadedRead(); //! \~english Sets threaded read callback to "func" and starts threaded read. //! \~russian Устанавливает callback потокового чтения в "func" и запускает потоковое чтение. void startThreadedRead(ReadRetFunc func); //! \~english Requests threaded read stop. //! \~russian Запрашивает остановку потокового чтения. void stopThreadedRead(); //! \~english Terminate threaded read. //! \~russian Прерывает потоковое чтение. //! \~\warning //! \~english Try not to use! This method may cause memory corruption! //! \~russian Старайтесь не использовать! Этот метод может привести к повреждению памяти! void terminateThreadedRead(); //! \~english Waits until threaded read finishes or "timeout" expires. //! \~russian Ожидает завершения потокового чтения, но не дольше "timeout". bool waitThreadedReadFinished(PISystemTime timeout = {}); //! \~english Returns delay between unsuccessful threaded read attempts in milliseconds. //! \~russian Возвращает задержку между безуспешными попытками потокового чтения в миллисекундах. uint threadedReadTimeout() const { return threaded_read_timeout_ms; } //! \~english Sets delay between unsuccessful threaded read attempts in milliseconds. //! \~russian Устанавливает задержку между безуспешными попытками потокового чтения в миллисекундах. void setThreadedReadTimeout(uint ms) { threaded_read_timeout_ms = ms; } //! \~english Returns whether threaded write is running. //! \~russian Возвращает, запущена ли потоковая запись. bool isThreadedWrite() const; //! \~english Starts threaded write. //! \~russian Запускает потоковую запись. void startThreadedWrite(); //! \~english Requests threaded write stop. //! \~russian Запрашивает остановку потоковой записи. void stopThreadedWrite(); //! \~english Terminate threaded write. //! \~russian Прерывает потоковую запись. //! \~\warning //! \~english Try not to use! This method may cause memory corruption! //! \~russian Старайтесь не использовать! Этот метод может привести к повреждению памяти! void terminateThreadedWrite(); //! \~english Waits until threaded write finishes or "timeout" expires. //! \~russian Ожидает завершения потоковой записи, но не дольше "timeout". bool waitThreadedWriteFinished(PISystemTime timeout = {}); //! \~english Clears queued threaded-write tasks. //! \~russian Очищает очередь заданий потоковой записи. void clearThreadedWriteQueue(); //! \~english Starts both threaded read and threaded write. //! \~russian Запускает потоковое чтение и потоковую запись. void start(); //! \~english Requests stop for both threaded read and threaded write. //! \~russian Запрашивает остановку потокового чтения и потоковой записи. void stop(); //! \~english Stops both background threads and waits for completion. //! \~russian Останавливает оба фоновых потока и ожидает их завершения. void stopAndWait(PISystemTime timeout = {}); //! \~english Interrupts a blocking device operation. //! \~russian Прерывает блокирующую операцию устройства. virtual void interrupt() {} //! \~english Reads at most "max_size" bytes into "read_to". //! \~russian Читает в "read_to" не более "max_size" байт. ssize_t read(void * read_to, ssize_t max_size); //! \~english Reads data into memory block "mb". //! \~russian Читает данные в блок памяти "mb". ssize_t read(PIMemoryBlock mb); //! \~english Reads at most "max_size" bytes and returns them as \a PIByteArray. //! \~russian Читает не более "max_size" байт и возвращает их как \a 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 Writes at most "max_size" bytes from "data". //! \~russian Записывает из "data" не более "max_size" байт. ssize_t write(const void * data, ssize_t max_size); //! \~english Reads data for up to "timeout" and returns collected bytes. //! \~russian Читает данные в течение "timeout" и возвращает накопленные байты. PIByteArray readForTime(PISystemTime timeout); //! \~english Queues "data" for threaded write and returns task ID. //! \~russian Помещает "data" в очередь потоковой записи и возвращает ID задания. ullong writeThreaded(const void * data, ssize_t max_size) { return writeThreaded(PIByteArray(data, uint(max_size))); } //! \~english Queues byte array "data" for threaded write and returns task ID. //! \~russian Помещает массив байт "data" в очередь потоковой записи и возвращает ID задания. ullong writeThreaded(const PIByteArray & data); //! \~english Configures the device from section "section" of file "config_file". //! \~russian Настраивает устройство из секции "section" файла "config_file". //! \~\details //! \~english //! If "parent_section" is true, inherited parameters are also read from the parent section. //! \~russian //! Если "parent_section" равно true, то дополнительные параметры также читаются из родительской секции. bool configure(const PIString & config_file, const PIString & section, bool parent_section = false); //! \~english Returns device prefix used in full-path notation. \ref PIIODevice_sec7 //! \~russian Возвращает префикс устройства, используемый в полной строке пути. \ref PIIODevice_sec7 virtual PIConstChars fullPathPrefix() const { return ""; } static PIConstChars fullPathPrefixS() { return ""; } //! \~english Returns full-path representation of this device, \a fullPathPrefix() + "://" + ... //! \~russian Возвращает полную строку описания этого устройства, \a fullPathPrefix() + "://" + ... PIString constructFullPath() const; //! \~english Configures the device from full-path parameters. //! \~russian Настраивает устройство из параметров полной строки описания. void configureFromFullPath(const PIString & full_path); //! \~english Builds \a PIVariantTypes::IODevice description for this device. //! \~russian Создает описание \a PIVariantTypes::IODevice для этого устройства. PIVariantTypes::IODevice constructVariant() const; //! \~english Configures the device from \a PIVariantTypes::IODevice. //! \~russian Настраивает устройство из \a 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); //! \~english Returns normalized full-path representation for "full_path". //! \~russian Возвращает нормализованную полную строку пути для "full_path". static PIString normalizeFullPath(const PIString & full_path); //! \~english Splits full-path string into path, mode and options. //! \~russian Разбирает полную строку пути на путь, режим и опции. 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)()); //! \~english Writes memory block "mb" to the device. //! \~russian Записывает в устройство блок памяти "mb". ssize_t write(const PIMemoryBlock & mb) { return write(mb.data(), mb.size()); } //! \handlers //! \{ //! \fn bool open() //! \~english Opens the device with current path and mode. //! \~russian Открывает устройство с текущими путём и режимом. EVENT_HANDLER(bool, open); //! \fn bool open(const PIString & path) //! \~english Opens the device with path "path". //! \~russian Открывает устройство с путём "path". EVENT_HANDLER1(bool, open, const PIString &, _path); //! \fn bool open(DeviceMode mode) //! \~english Opens the device with mode "mode". //! \~russian Открывает устройство с режимом "mode". bool open(DeviceMode _mode); //! \fn bool open(const PIString & path, DeviceMode mode) //! \~english Opens the device with path "path" and mode "mode". //! \~russian Открывает устройство с путём "path" и режимом "mode". EVENT_HANDLER2(bool, open, const PIString &, _path, DeviceMode, _mode); //! \fn bool close() //! \~english Closes the device. //! \~russian Закрывает устройство. EVENT_HANDLER(bool, close); //! \fn ssize_t write(PIByteArray data) //! \~english Writes "data" to the device. //! \~russian Записывает "data" в устройство. EVENT_HANDLER1(ssize_t, write, PIByteArray, data); //! \} //! \vhandlers //! \{ //! \fn void flush() //! \~english Immediately flushes device buffers. //! \~russian Немедленно сбрасывает буферы устройства. EVENT_VHANDLER(void, flush) { ; } //! \} //! \events //! \{ //! \fn void opened() //! \~english Raised after successful opening. //! \~russian Вызывается после успешного открытия. EVENT(opened); //! \fn void closed() //! \~english Raised after successful closing. //! \~russian Вызывается после успешного закрытия. EVENT(closed); //! \fn void threadedReadEvent(const uchar * readed, ssize_t size) //! \~english Raised after threaded read receives some data. //! \~russian Вызывается после того, как потоковое чтение получило данные. EVENT2(threadedReadEvent, const uchar *, readed, ssize_t, size); //! \fn void threadedWriteEvent(ullong id, ssize_t written_size) //! \~english Raised after threaded write processes task with ID "id". //! \~russian Вызывается после того, как потоковая запись обработала задание с ID "id". EVENT2(threadedWriteEvent, ullong, id, ssize_t, written_size); //! \} //! \ioparams //! \{ #ifdef DOXYGEN //! \~english setReopenEnabled, default "true". //! \~russian setReopenEnabled, по умолчанию "true". bool reopenEnabled; //! \~english setReopenTimeout, default 1_s. //! \~russian setReopenTimeout, по умолчанию 1_s. int reopenTimeout; //! \~english setThreadedReadBufferSize in bytes, default 4096. //! \~russian setThreadedReadBufferSize в байтах, по умолчанию 4096. int threadedReadBufferSize; #endif //! \} protected: //! \~english Reimplement to configure the device from "e_main" and optional "e_parent" entries cast to \a PIConfig::Entry*. //! \~russian Переопределите для настройки устройства из записей "e_main" и необязательной "e_parent", приведённых к \a //! PIConfig::Entry*. 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 the device; inverse return value is stored into "opened_". //! \~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 << "\"readDevice\" 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 << "\"writeDevice\" is not implemented!"; return -2; } //! \~english Called after threaded read receives data; default implementation calls the external callback set by \a //! setThreadedReadSlot(). //! \~russian Вызывается после успешного потокового чтения; по умолчанию вызывает внешний callback, заданный через \a //! setThreadedReadSlot(). virtual bool threadedRead(const uchar * readed, ssize_t size); //! \~english Reimplement to build device-specific part of full-path string. Default implementation returns \a path(). //! \~russian Переопределите для построения device-specific части полной строки пути. По умолчанию возвращает \a path(). virtual PIString constructFullPathDevice() const { return path(); } //! \~english Reimplement to configure the device from device-specific full-path parameters. Default implementation calls \a setPath(). //! \~russian Переопределите для настройки устройства из device-specific параметров полной строки пути. По умолчанию вызывает \a //! setPath(). virtual void configureFromFullPathDevice(const PIString & full_path) { setPath(full_path); } //! \~english Reimplement to build device-specific variant properties. Default implementation returns \a PIPropertyStorage with "path". //! \~russian Переопределите для построения device-specific свойств варианта. По умолчанию возвращает \a PIPropertyStorage со свойством //! "path". virtual PIPropertyStorage constructVariantDevice() const; //! \~english Reimplement to configure the device from \a PIPropertyStorage. Mode and options are already applied. //! Default implementation applies "path". //! \~russian Переопределите для настройки устройства из \a PIPropertyStorage. Режим и опции уже применены. //! Реализация по умолчанию применяет "path". virtual void configureFromVariantDevice(const PIPropertyStorage & d); //! \~english Reimplement to react to changed device options. //! \~russian Переопределите для реакции на изменение опций устройства. virtual void optionsChanged() { ; } //! \~english Reimplement to report actual \a DeviceInfoFlags. Default implementation returns 0. //! \~russian Переопределите для возврата актуальных \a DeviceInfoFlags. По умолчанию возвращает 0. virtual DeviceInfoFlags deviceInfoFlags() const { return 0; } //! \~english Reimplement to react to new \a threadedReadBufferSize(). //! \~russian Переопределите для реакции на новое значение \a threadedReadBufferSize(). virtual void threadedReadBufferSizeChanged() { ; } static PIIODevice * newDeviceByPrefix(const char * prefix); DeviceMode mode_ = ReadOnly; 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; PISystemTime reopen_timeout; ullong tri = 0; uint threaded_read_buffer_size, threaded_read_timeout_ms = 10; bool reopen_enabled = true, destroying = false; static PIMutex nfp_mutex; static PIMap nfp_cache; }; #endif // PIIODEVICE_H