/*! \file piiodevice.h * \brief Abstract input/output device */ /* 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 "picollection.h" #include "pitimer.h" #include "piqueue.h" // function executed from threaded read, pass ThreadedReadData, readedData, sizeOfData typedef bool (*ReadRetFunc)(void * , uchar * , int ); #ifdef DOXYGEN //! \relatesalso PIIODevice \brief Use this macro to enable automatic creation instances of your class with \a createFromFullPath() function # define REGISTER_DEVICE(class) //! \relatesalso PIIODevice \brief Use this macro instead of PIOBJECT when describe your own PIIODevice # define PIIODEVICE(class) #else # define REGISTER_DEVICE(name) ADD_NEW_TO_COLLECTION_WITH_NAME(__PIIODevices__, name, __S__collection_##name##__) # define PIIODEVICE(name) PIOBJECT_SUBCLASS(name, PIIODevice) PIIODevice * copy() const {return new name();} #endif class PIP_EXPORT PIIODevice: public PIThread { PIOBJECT_SUBCLASS(PIIODevice, PIThread) friend void __DevicePool_threadReadDP(void * ddp); public: NO_COPY_CLASS(PIIODevice) //! Constructs a empty PIIODevice explicit PIIODevice(); //! \brief Open modes for PIIODevice enum DeviceMode { ReadOnly /*! Device can only read */ = 0x01, WriteOnly /*! Device can only write */ = 0x02, ReadWrite /*! Device can both read and write */ = 0x03 }; //! \brief Options for PIIODevice, works with some devices enum DeviceOption { BlockingRead /*! \a read block until data is received, default off */ = 0x01, BlockingWrite /*! \a write block until data is sent, default off */ = 0x02 }; //! \brief Characteristics of PIIODevice subclass enum DeviceInfoFlag { Sequential /*! Continuous bytestream without datagrams */ = 0x01, Reliable /*! Channel without data errors / corruptions */ = 0x02 }; typedef PIFlags DeviceOptions; typedef PIFlags DeviceInfoFlags; explicit PIIODevice(const PIString & path, DeviceMode mode = ReadWrite); virtual ~PIIODevice(); //! Current open mode of device DeviceMode mode() const {return mode_;} //! Set open mode of device void setMode(DeviceMode m) {mode_ = m;} //! Current device options DeviceOptions options() const {return options_;} //! Current device option "o" state bool isOptionSet(DeviceOption o) const {return options_[o];} //! Set device options void setOptions(DeviceOptions o); //! Set device option "o" to "yes" and return previous state bool setOption(DeviceOption o, bool yes = true); //! Returns device characteristic flags DeviceInfoFlags infoFlags() const {return deviceInfoFlags();} //! Current path of device PIString path() const {return property(PIStringAscii("path")).toString();} //! Set path of device void setPath(const PIString & path) {setProperty(PIStringAscii("path"), path);} //! Return \b true if mode is ReadOnly or ReadWrite bool isReadable() const {return (mode_ & ReadOnly);} //! Return \b true if mode is WriteOnly or ReadWrite bool isWriteable() const {return (mode_ & WriteOnly);} //! Return \b true if device is successfully opened bool isOpened() const {return opened_;} //! Return \b true if device is closed bool isClosed() const {return !opened_;} //! Return \b true if device can read \b now virtual bool canRead() const {return opened_ && (mode_ & ReadOnly);} //! Return \b true if device can write \b now virtual bool canWrite() const {return opened_ && (mode_ & WriteOnly);} //! Set execution of \a open enabled while threaded read on closed device void setReopenEnabled(bool yes = true) {setProperty(PIStringAscii("reopenEnabled"), yes);} //! Set timeout in milliseconds between \a open tryings if reopen is enabled void setReopenTimeout(int msecs) {setProperty(PIStringAscii("reopenTimeout"), msecs);} //! Return reopen enable bool isReopenEnabled() const {return property(PIStringAscii("reopenEnabled")).toBool();} //! Return reopen timeout int reopenTimeout() {return property(PIStringAscii("reopenTimeout")).toInt();} /** \brief Set "threaded read slot" * \details 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)" */ void setThreadedReadSlot(ReadRetFunc func) {ret_func_ = func;} //! Set custom data that will be passed to "threaded read slot" void setThreadedReadData(void * d) {ret_data_ = d;} /** \brief Set size of threaded read buffer * \details 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 */ void setThreadedReadBufferSize(int new_size) {threaded_read_buffer_size = new_size; threadedReadBufferSizeChanged();} //! Return size of threaded read buffer int threadedReadBufferSize() const {return threaded_read_buffer_size;} //! Return content of threaded read buffer const uchar * threadedReadBuffer() const {return buffer_tr.data();} //! Return custom data that will be passed to "threaded read slot" void * threadedReadData() const {return ret_data_;} //! Return \b true if threaded read is started bool isThreadedRead() const {return isRunning();} //! Start threaded read void startThreadedRead() {if (!isRunning()) PIThread::start();} //! Start threaded read and assign "threaded read slot" to "func" void startThreadedRead(ReadRetFunc func) {ret_func_ = func; startThreadedRead();} //! Stop threaded read. Hard stop terminate thread, otherwise wait fo 10 seconds void stopThreadedRead(bool hard = true); //! Return \b true if threaded write is started bool isThreadedWrite() const {return write_thread.isRunning();} //! Start threaded write void startThreadedWrite() {if (!write_thread.isRunning()) write_thread.startOnce();} //! Stop threaded write. Hard stop terminate thread, otherwise wait fo 10 seconds void stopThreadedWrite(bool hard = true); //! Clear threaded write task queue void clearThreadedWriteQueue(); //! Start both threaded read and threaded write void start(); //! Stop both threaded read and threaded write. Hard stop terminate threads, otherwise wait fo 10 seconds void stop(bool hard = true); //! Read from device maximum "max_size" bytes to "read_to" int read(void * read_to, int max_size) {return readDevice(read_to, max_size);} //! Read from device maximum "max_size" bytes and return them as PIByteArray PIByteArray read(int max_size); //! Write maximum "max_size" bytes of "data" to device int write(const void * data, int max_size) {return writeDevice(data, max_size);} //! Read from device for "timeout_ms" milliseconds and return readed data as PIByteArray. Timeout should to be greater than 0 PIByteArray readForTime(double timeout_ms); //! Add task to threaded write queue and return task ID ullong writeThreaded(const void * data, int max_size) {return writeThreaded(PIByteArray(data, uint(max_size)));} //! Add task to threaded write queue and return task ID ullong writeThreaded(const PIByteArray & data); //! Configure device from section "section" of file "config_file", if "parent_section" parent section also will be read bool configure(const PIString & config_file, const PIString & section, bool parent_section = false); //! Reimplement to construct full unambiguous string prefix. \ref PIIODevice_sec7 virtual PIString fullPathPrefix() const {return PIString();} //! Returns full unambiguous string, describes this device, \a fullPathPrefix() + "://" PIString constructFullPath() const; //! Configure device with parameters of full unambiguous string void configureFromFullPath(const PIString & full_path); //! Returns PIVariantTypes::IODevice, describes this device PIVariantTypes::IODevice constructVariant() const; //! Configure device from PIVariantTypes::IODevice void configureFromVariant(const PIVariantTypes::IODevice & d); //! \brief Try to determine suitable device, create new one, configure it with \a configureFromFullPath() and returns it. //! \details To function \a configureFromFullPath() "full_path" passed without \a fullPathPrefix() + "://". //! See \ref PIIODevice_sec7 static PIIODevice * createFromFullPath(const PIString & full_path); //! \brief Try to determine suitable device, create new one, configure it with \a configureFromVariant() and returns it. //! \details To function \a configureFromFullPath() "full_path" passed without \a fullPathPrefix() + "://". //! See \ref PIIODevice_sec7 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); //! Returns fullPath prefixes of all registered devices static PIStringList availablePrefixes(); 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(int, write, PIByteArray, data); EVENT_VHANDLER(void, flush) {;} EVENT(opened) EVENT(closed) EVENT2(threadedReadEvent, uchar * , readed, int, size) EVENT2(threadedWriteEvent, ullong, id, int, written_size) //! \handlers //! \{ //! \fn bool open() //! \brief Open device //! \fn bool open(const PIString & path) //! \brief Open device with path "path" //! \fn bool open(const DeviceMode & mode) //! \brief Open device with mode "mode" //! \fn bool open(const PIString & path, const DeviceMode & mode) //! \brief Open device with path "path" and mode "mode" //! \fn bool close() //! \brief Close device //! \fn int write(PIByteArray data) //! \brief Write "data" to device //! \} //! \vhandlers //! \{ //! \fn void flush() //! \brief Immediate write all buffers //! \} //! \events //! \{ //! \fn void opened() //! \brief Raise if succesfull open //! \fn void closed() //! \brief Raise if succesfull close //! \fn void threadedReadEvent(uchar * readed, int size) //! \brief Raise if read thread succesfull read some data //! \fn void threadedWriteEvent(ullong id, int written_size) //! \brief Raise if write thread successfull write some data of task with ID "id" //! \} //! \ioparams //! \{ #ifdef DOXYGEN //! \brief setReopenEnabled, default "true" bool reopenEnabled; //! \brief setReopenTimeout in ms, default 1000 int reopenTimeout; //! \brief setThreadedReadBufferSize in bytes, default 4096 int threadedReadBufferSize; #endif //! \} protected: //! Function executed before first \a openDevice() or from constructor virtual bool init() {return true;} //! Reimplement to configure device from entries "e_main" and "e_parent", cast arguments to \a PIConfig::Entry* virtual bool configureDevice(const void * e_main, const void * e_parent = 0) {return true;} //! Reimplement to open device, return value will be set to "opened_" variable; don't call this function in subclass, use open() virtual bool openDevice() = 0; // use path_, type_, opened_, init_ variables //! Reimplement to close device, inverse return value will be set to "opened_" variable virtual bool closeDevice() {return true;} // use path_, type_, opened_, init_ variables //! Reimplement this function to read from your device virtual int readDevice(void * read_to, int max_size) {piCoutObj << "\"read\" is not implemented!"; return -2;} //! Reimplement this function to write to your device virtual int writeDevice(const void * data, int max_size) {piCoutObj << "\"write\" is not implemented!"; return -2;} //! Function executed when thread read some data, default implementation execute external slot "ret_func_" virtual bool threadedRead(uchar * readed, int size); //! Reimplement to construct full unambiguous string, describes this device. Default implementation returns \a path() virtual PIString constructFullPathDevice() const {return path();} //! Reimplement to configure your device with parameters of full unambiguous string. Default implementation does nothing virtual void configureFromFullPathDevice(const PIString & full_path) {setPath(full_path);} //! Reimplement to construct device properties. //! Default implementation return PIPropertyStorage with \"path\" entry virtual PIPropertyStorage constructVariantDevice() const; //! Reimplement to configure your device from PIPropertyStorage. Options and mode already applied. //! Default implementation apply \"path\" entry virtual void configureFromVariantDevice(const PIPropertyStorage & d); //! Reimplement to apply new device options virtual void optionsChanged() {;} //! Reimplement to return correct \a DeviceInfoFlags. Default implementation returns 0 virtual DeviceInfoFlags deviceInfoFlags() const {return 0;} //! Reimplement to apply new \a threadedReadBufferSize() virtual void threadedReadBufferSizeChanged() {;} //! Invoked after hard read thread stop virtual void threadedReadTerminated() {;} //! Invoked after hard write thread stop virtual void threadedWriteTerminated() {;} static PIIODevice * newDeviceByPrefix(const PIString & prefix); void terminate(); DeviceMode mode_; DeviceOptions options_; ReadRetFunc ret_func_; bool opened_; void * ret_data_; private: EVENT_HANDLER2(void, check_start, void * , data, int, delim); EVENT_HANDLER(void, write_func); virtual PIIODevice * copy() const {return 0;} PIString fullPathOptions() const; void _init(); void begin(); void run(); void end() {terminate();} static void cacheFullPath(const PIString & full_path, const PIIODevice * d); PITimer timer; PITimeMeasurer tm; PIThread write_thread; PIByteArray buffer_in, buffer_tr; PIQueue > write_queue; ullong tri; int readed_; uint threaded_read_buffer_size; bool init_, thread_started_, raise_threaded_read_; static PIMutex nfp_mutex; static PIMap nfp_cache; }; #endif // PIIODEVICE_H