diff --git a/Qt-application_for_UDP_broadcast/.gitignore b/Applications/Qt-application_for_UDP_broadcast/.gitignore similarity index 100% rename from Qt-application_for_UDP_broadcast/.gitignore rename to Applications/Qt-application_for_UDP_broadcast/.gitignore diff --git a/Qt-application_for_UDP_broadcast/Device_finder.pro b/Applications/Qt-application_for_UDP_broadcast/Device_finder.pro similarity index 100% rename from Qt-application_for_UDP_broadcast/Device_finder.pro rename to Applications/Qt-application_for_UDP_broadcast/Device_finder.pro diff --git a/Qt-application_for_UDP_broadcast/LICENSE b/Applications/Qt-application_for_UDP_broadcast/LICENSE similarity index 100% rename from Qt-application_for_UDP_broadcast/LICENSE rename to Applications/Qt-application_for_UDP_broadcast/LICENSE diff --git a/Qt-application_for_UDP_broadcast/README.md b/Applications/Qt-application_for_UDP_broadcast/README.md similarity index 100% rename from Qt-application_for_UDP_broadcast/README.md rename to Applications/Qt-application_for_UDP_broadcast/README.md diff --git a/Qt-application_for_UDP_broadcast/about.cpp b/Applications/Qt-application_for_UDP_broadcast/about.cpp similarity index 100% rename from Qt-application_for_UDP_broadcast/about.cpp rename to Applications/Qt-application_for_UDP_broadcast/about.cpp diff --git a/Qt-application_for_UDP_broadcast/about.h b/Applications/Qt-application_for_UDP_broadcast/about.h similarity index 100% rename from Qt-application_for_UDP_broadcast/about.h rename to Applications/Qt-application_for_UDP_broadcast/about.h diff --git a/Qt-application_for_UDP_broadcast/about.ui b/Applications/Qt-application_for_UDP_broadcast/about.ui similarity index 100% rename from Qt-application_for_UDP_broadcast/about.ui rename to Applications/Qt-application_for_UDP_broadcast/about.ui diff --git a/Qt-application_for_UDP_broadcast/doc/img/screenshot.png b/Applications/Qt-application_for_UDP_broadcast/doc/img/screenshot.png similarity index 100% rename from Qt-application_for_UDP_broadcast/doc/img/screenshot.png rename to Applications/Qt-application_for_UDP_broadcast/doc/img/screenshot.png diff --git a/Qt-application_for_UDP_broadcast/main.cpp b/Applications/Qt-application_for_UDP_broadcast/main.cpp similarity index 100% rename from Qt-application_for_UDP_broadcast/main.cpp rename to Applications/Qt-application_for_UDP_broadcast/main.cpp diff --git a/Qt-application_for_UDP_broadcast/mainwindow.cpp b/Applications/Qt-application_for_UDP_broadcast/mainwindow.cpp similarity index 100% rename from Qt-application_for_UDP_broadcast/mainwindow.cpp rename to Applications/Qt-application_for_UDP_broadcast/mainwindow.cpp diff --git a/Qt-application_for_UDP_broadcast/mainwindow.h b/Applications/Qt-application_for_UDP_broadcast/mainwindow.h similarity index 100% rename from Qt-application_for_UDP_broadcast/mainwindow.h rename to Applications/Qt-application_for_UDP_broadcast/mainwindow.h diff --git a/Qt-application_for_UDP_broadcast/mainwindow.ui b/Applications/Qt-application_for_UDP_broadcast/mainwindow.ui similarity index 100% rename from Qt-application_for_UDP_broadcast/mainwindow.ui rename to Applications/Qt-application_for_UDP_broadcast/mainwindow.ui diff --git a/Qt-application_for_UDP_broadcast/res.qrc b/Applications/Qt-application_for_UDP_broadcast/res.qrc similarity index 100% rename from Qt-application_for_UDP_broadcast/res.qrc rename to Applications/Qt-application_for_UDP_broadcast/res.qrc diff --git a/Qt-application_for_UDP_broadcast/res/copy-icon.png b/Applications/Qt-application_for_UDP_broadcast/res/copy-icon.png similarity index 100% rename from Qt-application_for_UDP_broadcast/res/copy-icon.png rename to Applications/Qt-application_for_UDP_broadcast/res/copy-icon.png diff --git a/Qt-application_for_UDP_broadcast/res/icon.png b/Applications/Qt-application_for_UDP_broadcast/res/icon.png similarity index 100% rename from Qt-application_for_UDP_broadcast/res/icon.png rename to Applications/Qt-application_for_UDP_broadcast/res/icon.png diff --git a/Qt-application_for_UDP_broadcast/res/oui.txt b/Applications/Qt-application_for_UDP_broadcast/res/oui.txt similarity index 100% rename from Qt-application_for_UDP_broadcast/res/oui.txt rename to Applications/Qt-application_for_UDP_broadcast/res/oui.txt diff --git a/Qt-application_for_UDP_broadcast/res/refresh-th.png b/Applications/Qt-application_for_UDP_broadcast/res/refresh-th.png similarity index 100% rename from Qt-application_for_UDP_broadcast/res/refresh-th.png rename to Applications/Qt-application_for_UDP_broadcast/res/refresh-th.png diff --git a/Qt-application_for_UDP_broadcast/res/rpi.png b/Applications/Qt-application_for_UDP_broadcast/res/rpi.png similarity index 100% rename from Qt-application_for_UDP_broadcast/res/rpi.png rename to Applications/Qt-application_for_UDP_broadcast/res/rpi.png diff --git a/Qt-application_for_UDP_broadcast/res/stm32.png b/Applications/Qt-application_for_UDP_broadcast/res/stm32.png similarity index 100% rename from Qt-application_for_UDP_broadcast/res/stm32.png rename to Applications/Qt-application_for_UDP_broadcast/res/stm32.png diff --git a/Qt-application_for_UDP_broadcast/res/unknown.png b/Applications/Qt-application_for_UDP_broadcast/res/unknown.png similarity index 100% rename from Qt-application_for_UDP_broadcast/res/unknown.png rename to Applications/Qt-application_for_UDP_broadcast/res/unknown.png diff --git a/Applications/qModMaster/.hgtags b/Applications/qModMaster/.hgtags new file mode 100644 index 0000000..ca0dbd9 --- /dev/null +++ b/Applications/qModMaster/.hgtags @@ -0,0 +1,27 @@ +d6d0b41b126ccba8b09fc59e48912745cfff75be 0.3b +2381684f3d5aba19a8341f5066e8926836e75bd4 0.3.1 +777230899f8d2d6293ce086e5d8121d5633587a7 0.3.2 +823d2854d09feaeffa8b33bd26820c92b1c9338c 0.3.3 +6b8ac66401954f5095d78a8b69737b1d29fb3058 0.3.4 +05290da92610d4e7b0ca3408b797f60970fa5196 0.3.5 +bc07afceca77bac00b39f4524c323cf0762b0adf 0.3.6 +91b2f29aabda0f8a447e68701e3ec5290c975763 0.3.7 +e29486700476b695e2c06efe547047b50b7d1310 0.3.8 +cff1be19d097303526a5d08e482748af329b038a 0.3.9 +cb03b5cbb8acb10f3eb448ab5ce77cdf7bea2846 0.4.0 +193b3543cff470f065e492921634466938c31087 0.4.1 +e2f735cb82c448dc01b6930326ef207cc634334f 0.4.2 +0d5dd919458705c1452c6729fb4a81213ebc59ef 0.4.2-TC1 +daa66ad96e999b69a7891d6b1e88f283449eb658 0.4.3 +89da4336a9421e32f7c067e0079df5a9bc174d93 0.4.5 +c3e35e2887e2406e5fa94663630bae92c57ce067 0.4.6 +38d3b294a86356a65fc62e2ea9e3d3e15078a51b 0.4.7 +86fbb4b96b1162d9f21c2a1fd521064ec9e10165 0.4.8 +38c7b5b820416bbb23c43623f75e743b15145ed3 0.4.9 +ef1fad56f0138adb3d72b1f4f607272cc2517150 0.5.0 +ed94674ee7bebfa6352922baf3cb4876a7c062b7 0.5.1-2 +44c7c2c84b61140d7965f1dd8dfb5276eb475c44 0.5.2 +e4488a09e77cee64179065cb4a3ee2ba315485ad 0.5.2-1 +febfe11c411e47f2311123b3382be598be529371 0.5.2-2 +6aba19d1ca6db89fe0f06039b05e2511bd57af4c 0.5.2-3 +c928384f1cd41a912f8238f076e691a382f95e5f 0.5.3-beta diff --git a/Applications/qModMaster/3rdparty/QsLog/QsLog.cpp b/Applications/qModMaster/3rdparty/QsLog/QsLog.cpp new file mode 100644 index 0000000..8ba5650 --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/QsLog.cpp @@ -0,0 +1,193 @@ +// Copyright (c) 2013, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "QsLog.h" +#include "QsLogDest.h" +#ifdef QS_LOG_SEPARATE_THREAD +#include +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include + +namespace QsLogging +{ +typedef QVector DestinationList; + +static const char TraceString[] = "TRACE"; +static const char DebugString[] = "DEBUG"; +static const char InfoString[] = "INFO"; +static const char WarnString[] = "WARN"; +static const char ErrorString[] = "ERROR"; +static const char FatalString[] = "FATAL"; + +// not using Qt::ISODate because we need the milliseconds too +static const QString fmtDateTime("yyyy-MM-ddThh:mm:ss.zzz"); + +static const char* LevelToText(Level theLevel) +{ + switch (theLevel) { + case TraceLevel: + return TraceString; + case DebugLevel: + return DebugString; + case InfoLevel: + return InfoString; + case WarnLevel: + return WarnString; + case ErrorLevel: + return ErrorString; + case FatalLevel: + return FatalString; + case OffLevel: + return ""; + default: { + assert(!"bad log level"); + return InfoString; + } + } +} + +#ifdef QS_LOG_SEPARATE_THREAD +class LogWriterRunnable : public QRunnable +{ +public: + LogWriterRunnable(const QString &message, Level level) + : mMessage(message) + , mLevel(level) {} + + virtual void run() + { + Logger::instance().write(mMessage, mLevel); + } + +private: + QString mMessage; + Level mLevel; +}; +#endif + +class LoggerImpl +{ +public: + LoggerImpl() : + level(InfoLevel) + { + // assume at least file + console + destList.reserve(2); +#ifdef QS_LOG_SEPARATE_THREAD + threadPool.setMaxThreadCount(1); + threadPool.setExpiryTimeout(-1); +#endif + } +#ifdef QS_LOG_SEPARATE_THREAD + QThreadPool threadPool; +#else + QMutex logMutex; +#endif + Level level; + DestinationList destList; +}; + +Logger::Logger() : + d(new LoggerImpl) +{ +} + +Logger::~Logger() +{ + delete d; +} + +void Logger::addDestination(DestinationPtr destination) +{ + assert(destination.data()); + d->destList.push_back(destination); +} + +void Logger::setLoggingLevel(Level newLevel) +{ + d->level = newLevel; +} + +Level Logger::loggingLevel() const +{ + return d->level; +} + +//! creates the complete log message and passes it to the logger +void Logger::Helper::writeToLog() +{ + const char* const levelName = LevelToText(level); + const QString completeMessage(QString("%1 %2 %3") + .arg(levelName, 5) + .arg(QDateTime::currentDateTime().toString(fmtDateTime)) + .arg(buffer) + ); + + Logger::instance().enqueueWrite(completeMessage, level); +} + +Logger::Helper::~Helper() +{ + try { + writeToLog(); + } + catch(std::exception&) { + // you shouldn't throw exceptions from a sink + assert(!"exception in logger helper destructor"); + throw; + } +} + +//! directs the message to the task queue or writes it directly +void Logger::enqueueWrite(const QString& message, Level level) +{ +#ifdef QS_LOG_SEPARATE_THREAD + LogWriterRunnable *r = new LogWriterRunnable(message, level); + d->threadPool.start(r); +#else + QMutexLocker lock(&d->logMutex); + write(message, level); +#endif +} + +//! Sends the message to all the destinations. The level for this message is passed in case +//! it's useful for processing in the destination. +void Logger::write(const QString& message, Level level) +{ + for (DestinationList::iterator it = d->destList.begin(), + endIt = d->destList.end();it != endIt;++it) { + (*it)->write(message, level); + } +} + +} // end namespace diff --git a/Applications/qModMaster/3rdparty/QsLog/QsLog.h b/Applications/qModMaster/3rdparty/QsLog/QsLog.h new file mode 100644 index 0000000..9bfc871 --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/QsLog.h @@ -0,0 +1,138 @@ +// Copyright (c) 2013, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef QSLOG_H +#define QSLOG_H + +#include "QsLogLevel.h" +#include "QsLogDest.h" +#include +#include + +#define QS_LOG_VERSION "2.0b1" + +namespace QsLogging +{ +class Destination; +class LoggerImpl; // d pointer + +class Logger +{ +public: + static Logger& instance() + { + static Logger staticLog; + return staticLog; + } + + //! Adds a log message destination. Don't add null destinations. + void addDestination(DestinationPtr destination); + //! Logging at a level < 'newLevel' will be ignored + void setLoggingLevel(Level newLevel); + //! The default level is INFO + Level loggingLevel() const; + + //! The helper forwards the streaming to QDebug and builds the final + //! log message. + class Helper + { + public: + explicit Helper(Level logLevel) : + level(logLevel), + qtDebug(&buffer) {} + ~Helper(); + QDebug& stream(){ return qtDebug; } + + private: + void writeToLog(); + + Level level; + QString buffer; + QDebug qtDebug; + }; + +private: + Logger(); + Logger(const Logger&); + Logger& operator=(const Logger&); + ~Logger(); + + void enqueueWrite(const QString& message, Level level); + void write(const QString& message, Level level); + + LoggerImpl* d; + + friend class LogWriterRunnable; +}; + +} // end namespace + +//! Logging macros: define QS_LOG_LINE_NUMBERS to get the file and line number +//! in the log output. +#ifndef QS_LOG_LINE_NUMBERS +#define QLOG_TRACE() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::TraceLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::TraceLevel).stream() +#define QLOG_DEBUG() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::DebugLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::DebugLevel).stream() +#define QLOG_INFO() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::InfoLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::InfoLevel).stream() +#define QLOG_WARN() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::WarnLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::WarnLevel).stream() +#define QLOG_ERROR() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::ErrorLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::ErrorLevel).stream() +#define QLOG_FATAL() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::FatalLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::FatalLevel).stream() +#else +#define QLOG_TRACE() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::TraceLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::TraceLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_DEBUG() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::DebugLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::DebugLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_INFO() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::InfoLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::InfoLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_WARN() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::WarnLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::WarnLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_ERROR() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::ErrorLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::ErrorLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_FATAL() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::FatalLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::FatalLevel).stream() << __FILE__ << '@' << __LINE__ +#endif + +#ifdef QS_LOG_DISABLE +#include "QsLogDisableForThisFile.h" +#endif + +#endif // QSLOG_H diff --git a/Applications/qModMaster/3rdparty/QsLog/QsLog.pri b/Applications/qModMaster/3rdparty/QsLog/QsLog.pri new file mode 100644 index 0000000..a664f6c --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/QsLog.pri @@ -0,0 +1,19 @@ +INCLUDEPATH += $$PWD +#DEFINES += QS_LOG_LINE_NUMBERS # automatically writes the file and line for each log message +#DEFINES += QS_LOG_DISABLE # logging code is replaced with a no-op +#DEFINES += QS_LOG_SEPARATE_THREAD # messages are queued and written from a separate thread +SOURCES += $$PWD/QsLogDest.cpp \ + $$PWD/QsLog.cpp \ + $$PWD/QsLogDestConsole.cpp \ + $$PWD/QsLogDestFile.cpp + +HEADERS += $$PWD/QSLogDest.h \ + $$PWD/QsLog.h \ + $$PWD/QsLogDestConsole.h \ + $$PWD/QsLogLevel.h \ + $$PWD/QsLogDestFile.h \ + $$PWD/QsLogDisableForThisFile.h + +OTHER_FILES += \ + $$PWD/QsLogChanges.txt \ + $$PWD/QsLogReadme.txt diff --git a/Applications/qModMaster/3rdparty/QsLog/QsLogChanges.txt b/Applications/qModMaster/3rdparty/QsLog/QsLogChanges.txt new file mode 100644 index 0000000..bd8e60f --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/QsLogChanges.txt @@ -0,0 +1,12 @@ +QsLog version 2.0b1 + +Changes: +* destination pointers use shared pointer semantics +* the file destination supports log rotation. As a consequence, log files are encoded as UTF-8 and +the log appends rather than truncating on every startup when rotation is enabled. +* added the posibility of disabling logging either at run time or compile time. +* added the possibility of using a separate thread for writing to the log destinations. + +Fixes: +* renamed the main.cpp example to avoid QtCreator confusion +* offer a way to check if the destination creation has failed (for e.g files) diff --git a/Applications/qModMaster/3rdparty/QsLog/QsLogDest.cpp b/Applications/qModMaster/3rdparty/QsLog/QsLogDest.cpp new file mode 100644 index 0000000..00c7906 --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/QsLogDest.cpp @@ -0,0 +1,54 @@ +// Copyright (c) 2013, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "QsLogDest.h" +#include "QsLogDestConsole.h" +#include "QsLogDestFile.h" +#include + +namespace QsLogging +{ + +//! destination factory +DestinationPtr DestinationFactory::MakeFileDestination(const QString& filePath, bool enableRotation, + qint64 sizeInBytesToRotateAfter, int oldLogsToKeep) +{ + if (enableRotation) { + QScopedPointer logRotation(new SizeRotationStrategy); + logRotation->setMaximumSizeInBytes(sizeInBytesToRotateAfter); + logRotation->setBackupCount(oldLogsToKeep); + + return DestinationPtr(new FileDestination(filePath, RotationStrategyPtr(logRotation.take()))); + } + + return DestinationPtr(new FileDestination(filePath, RotationStrategyPtr(new NullRotationStrategy))); +} + +DestinationPtr DestinationFactory::MakeDebugOutputDestination() +{ + return DestinationPtr(new DebugOutputDestination); +} + +} // end namespace diff --git a/Applications/qModMaster/3rdparty/QsLog/QsLogDest.h b/Applications/qModMaster/3rdparty/QsLog/QsLogDest.h new file mode 100644 index 0000000..8e09173 --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/QsLogDest.h @@ -0,0 +1,57 @@ +// Copyright (c) 2013, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef QSLOGDEST_H +#define QSLOGDEST_H + +#include "QsLogLevel.h" +#include +#include +class QString; + +namespace QsLogging +{ + +class Destination +{ +public: + virtual ~Destination(){} + virtual void write(const QString& message, Level level) = 0; + virtual bool isValid() = 0; // returns whether the destination was created correctly +}; +typedef QSharedPointer DestinationPtr; + +//! Creates logging destinations/sinks. The caller will have ownership of +//! the newly created destinations. +class DestinationFactory +{ +public: + static DestinationPtr MakeFileDestination(const QString& filePath, bool enableRotation = false, qint64 sizeInBytesToRotateAfter = 0, int oldLogsToKeep = 0); + static DestinationPtr MakeDebugOutputDestination(); +}; + +} // end namespace + +#endif // QSLOGDEST_H diff --git a/Applications/qModMaster/3rdparty/QsLog/QsLogDestConsole.cpp b/Applications/qModMaster/3rdparty/QsLog/QsLogDestConsole.cpp new file mode 100644 index 0000000..842c970 --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/QsLogDestConsole.cpp @@ -0,0 +1,75 @@ +// Copyright (c) 2013, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "QsLogDestConsole.h" +#include +#include + +#if defined(Q_OS_WIN) +#define WIN32_LEAN_AND_MEAN +#include +void QsDebugOutput::output( const QString& message ) +{ + OutputDebugStringW(reinterpret_cast(message.utf16())); + OutputDebugStringW(L"\n"); +} +#elif defined(Q_OS_SYMBIAN) +#include +void QsDebugOutput::output( const QString& message ) +{ + const int maxPrintSize = 256; + if (message.size() <= maxPrintSize) { + TPtrC16 symbianMessage(reinterpret_cast(message.utf16())); + RDebug::RawPrint(symbianMessage); + } else { + QString slicedMessage = message; + while (!slicedMessage.isEmpty()) { + const int sliceSize = qMin(maxPrintSize, slicedMessage.size()); + const QString slice = slicedMessage.left(sliceSize); + slicedMessage.remove(0, sliceSize); + + TPtrC16 symbianSlice(reinterpret_cast(slice.utf16())); + RDebug::RawPrint(symbianSlice); + } + } +} +#elif defined(Q_OS_UNIX) +#include +void QsDebugOutput::output( const QString& message ) +{ + fprintf(stderr, "%s\n", qPrintable(message)); + fflush(stderr); +} +#endif + +void QsLogging::DebugOutputDestination::write(const QString& message, Level) +{ + QsDebugOutput::output(message); +} + +bool QsLogging::DebugOutputDestination::isValid() +{ + return true; +} diff --git a/Applications/qModMaster/3rdparty/QsLog/QsLogDestConsole.h b/Applications/qModMaster/3rdparty/QsLog/QsLogDestConsole.h new file mode 100644 index 0000000..f80f490 --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/QsLogDestConsole.h @@ -0,0 +1,52 @@ +// Copyright (c) 2013, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef QSLOGDESTCONSOLE_H +#define QSLOGDESTCONSOLE_H + +#include "QsLogDest.h" + +class QString; + +class QsDebugOutput +{ +public: + static void output(const QString& a_message); +}; + +namespace QsLogging +{ + +// debugger sink +class DebugOutputDestination : public Destination +{ +public: + virtual void write(const QString& message, Level level); + virtual bool isValid(); +}; + +} + +#endif // QSLOGDESTCONSOLE_H diff --git a/Applications/qModMaster/3rdparty/QsLog/QsLogDestFile.cpp b/Applications/qModMaster/3rdparty/QsLog/QsLogDestFile.cpp new file mode 100644 index 0000000..19b1aab --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/QsLogDestFile.cpp @@ -0,0 +1,155 @@ +// Copyright (c) 2013, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "QsLogDestFile.h" +#include +#include +#include +#include + +const int QsLogging::SizeRotationStrategy::MaxBackupCount = 10; + +QsLogging::RotationStrategy::~RotationStrategy() +{ +} + +QsLogging::SizeRotationStrategy::SizeRotationStrategy() + : mCurrentSizeInBytes(0) + , mMaxSizeInBytes(0) + , mBackupsCount(0) +{ +} + +void QsLogging::SizeRotationStrategy::setInitialInfo(const QFile &file) +{ + mFileName = file.fileName(); + mCurrentSizeInBytes = file.size(); +} + +void QsLogging::SizeRotationStrategy::includeMessageInCalculation(const QString &message) +{ + mCurrentSizeInBytes += message.toUtf8().size(); +} + +bool QsLogging::SizeRotationStrategy::shouldRotate() +{ + return mCurrentSizeInBytes > mMaxSizeInBytes; +} + +// Algorithm assumes backups will be named filename.X, where 1 <= X <= mBackupsCount. +// All X's will be shifted up. +void QsLogging::SizeRotationStrategy::rotate() +{ + if (!mBackupsCount) { + if (!QFile::remove(mFileName)) + std::cerr << "QsLog: backup delete failed " << qPrintable(mFileName); + return; + } + + // 1. find the last existing backup than can be shifted up + const QString logNamePattern = mFileName + QString::fromUtf8(".%1"); + int lastExistingBackupIndex = 0; + for (int i = 1;i <= mBackupsCount;++i) { + const QString backupFileName = logNamePattern.arg(i); + if (QFile::exists(backupFileName)) + lastExistingBackupIndex = qMin(i, mBackupsCount - 1); + else + break; + } + + // 2. shift up + for (int i = lastExistingBackupIndex;i >= 1;--i) { + const QString oldName = logNamePattern.arg(i); + const QString newName = logNamePattern.arg(i + 1); + QFile::remove(newName); + const bool renamed = QFile::rename(oldName, newName); + if (!renamed) { + std::cerr << "QsLog: could not rename backup " << qPrintable(oldName) + << " to " << qPrintable(newName); + } + } + + // 3. rename current log file + const QString newName = logNamePattern.arg(1); + if (QFile::exists(newName)) + QFile::remove(newName); + if (!QFile::rename(mFileName, newName)) { + std::cerr << "QsLog: could not rename log " << qPrintable(mFileName) + << " to " << qPrintable(newName); + } +} + +QIODevice::OpenMode QsLogging::SizeRotationStrategy::recommendedOpenModeFlag() +{ + return QIODevice::Append; +} + +void QsLogging::SizeRotationStrategy::setMaximumSizeInBytes(qint64 size) +{ + Q_ASSERT(size >= 0); + mMaxSizeInBytes = size; +} + +void QsLogging::SizeRotationStrategy::setBackupCount(int backups) +{ + Q_ASSERT(backups >= 0); + mBackupsCount = qMin(backups, SizeRotationStrategy::MaxBackupCount); +} + + +QsLogging::FileDestination::FileDestination(const QString& filePath, RotationStrategyPtr rotationStrategy) + : mRotationStrategy(rotationStrategy) +{ + mFile.setFileName(filePath); + if (!mFile.open(QFile::WriteOnly | QFile::Text | mRotationStrategy->recommendedOpenModeFlag())) + std::cerr << "QsLog: could not open log file " << qPrintable(filePath); + mOutputStream.setDevice(&mFile); + /*mOutputStream.setCodec(QTextCodec::codecForName("UTF-8"));*/ + + mRotationStrategy->setInitialInfo(mFile); +} + +void QsLogging::FileDestination::write(const QString& message, Level) +{ + mRotationStrategy->includeMessageInCalculation(message); + if (mRotationStrategy->shouldRotate()) { + mOutputStream.setDevice(NULL); + mFile.close(); + mRotationStrategy->rotate(); + if (!mFile.open(QFile::WriteOnly | QFile::Text | mRotationStrategy->recommendedOpenModeFlag())) + std::cerr << "QsLog: could not reopen log file " << qPrintable(mFile.fileName()); + mRotationStrategy->setInitialInfo(mFile); + mOutputStream.setDevice(&mFile); + } + + mOutputStream << message << Qt::endl; + mOutputStream.flush(); +} + +bool QsLogging::FileDestination::isValid() +{ + return mFile.isOpen(); +} + diff --git a/Applications/qModMaster/3rdparty/QsLog/QsLogDestFile.h b/Applications/qModMaster/3rdparty/QsLog/QsLogDestFile.h new file mode 100644 index 0000000..ee7b523 --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/QsLogDestFile.h @@ -0,0 +1,101 @@ +// Copyright (c) 2013, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef QSLOGDESTFILE_H +#define QSLOGDESTFILE_H + +#include "QsLogDest.h" +#include +#include +#include +#include + +namespace QsLogging +{ +class RotationStrategy +{ +public: + virtual ~RotationStrategy(); + + virtual void setInitialInfo(const QFile &file) = 0; + virtual void includeMessageInCalculation(const QString &message) = 0; + virtual bool shouldRotate() = 0; + virtual void rotate() = 0; + virtual QIODevice::OpenMode recommendedOpenModeFlag() = 0; +}; + +// Never rotates file, overwrites existing file. +class NullRotationStrategy : public RotationStrategy +{ +public: + virtual void setInitialInfo(const QFile &) {} + virtual void includeMessageInCalculation(const QString &) {} + virtual bool shouldRotate() { return false; } + virtual void rotate() {} + virtual QIODevice::OpenMode recommendedOpenModeFlag() { return QIODevice::Truncate; } +}; + +// Rotates after a size is reached, keeps a number of <= 10 backups, appends to existing file. +class SizeRotationStrategy : public RotationStrategy +{ +public: + SizeRotationStrategy(); + static const int MaxBackupCount; + + virtual void setInitialInfo(const QFile &file); + virtual void includeMessageInCalculation(const QString &message); + virtual bool shouldRotate(); + virtual void rotate(); + virtual QIODevice::OpenMode recommendedOpenModeFlag(); + + void setMaximumSizeInBytes(qint64 size); + void setBackupCount(int backups); + +private: + QString mFileName; + qint64 mCurrentSizeInBytes; + qint64 mMaxSizeInBytes; + int mBackupsCount; +}; + +typedef QSharedPointer RotationStrategyPtr; + +// file message sink +class FileDestination : public Destination +{ +public: + FileDestination(const QString& filePath, RotationStrategyPtr rotationStrategy); + virtual void write(const QString& message, Level level); + virtual bool isValid(); + +private: + QFile mFile; + QTextStream mOutputStream; + QSharedPointer mRotationStrategy; +}; + +} + +#endif // QSLOGDESTFILE_H diff --git a/Applications/qModMaster/3rdparty/QsLog/QsLogDisableForThisFile.h b/Applications/qModMaster/3rdparty/QsLog/QsLogDisableForThisFile.h new file mode 100644 index 0000000..c70af10 --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/QsLogDisableForThisFile.h @@ -0,0 +1,22 @@ +#ifndef QSLOGDISABLEFORTHISFILE_H +#define QSLOGDISABLEFORTHISFILE_H + +#include +// When included AFTER QsLog.h, this file will disable logging in that C++ file. When included +// before, it will lead to compiler warnings or errors about macro redefinitions. + +#undef QLOG_TRACE +#undef QLOG_DEBUG +#undef QLOG_INFO +#undef QLOG_WARN +#undef QLOG_ERROR +#undef QLOG_FATAL + +#define QLOG_TRACE() if (1) {} else qDebug() +#define QLOG_DEBUG() if (1) {} else qDebug() +#define QLOG_INFO() if (1) {} else qDebug() +#define QLOG_WARN() if (1) {} else qDebug() +#define QLOG_ERROR() if (1) {} else qDebug() +#define QLOG_FATAL() if (1) {} else qDebug() + +#endif // QSLOGDISABLEFORTHISFILE_H diff --git a/Applications/qModMaster/3rdparty/QsLog/QsLogLevel.h b/Applications/qModMaster/3rdparty/QsLog/QsLogLevel.h new file mode 100644 index 0000000..6298473 --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/QsLogLevel.h @@ -0,0 +1,45 @@ +// Copyright (c) 2013, Razvan Petru +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// * The name of the contributors may not be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef QSLOGLEVEL_H +#define QSLOGLEVEL_H + +namespace QsLogging +{ + +enum Level +{ + TraceLevel = 0, + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + FatalLevel, + OffLevel +}; + +} + +#endif // QSLOGLEVEL_H diff --git a/Applications/qModMaster/3rdparty/QsLog/QsLogReadme.txt b/Applications/qModMaster/3rdparty/QsLog/QsLogReadme.txt new file mode 100644 index 0000000..4cddcca --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/QsLogReadme.txt @@ -0,0 +1,34 @@ +QsLog - the simple Qt logger + +QsLog is an easy to use logger that is based on Qt's QDebug class. +Features + + Six logging levels (from trace to fatal) + Logging level threshold configurable at runtime. + Minimum overhead when logging is turned off. + Supports multiple destinations, comes with file and debug destinations. + Thread-safe + Supports logging of common Qt types out of the box. + Small dependency: just drop it in your project directly. + +Usage + + Include QsLog.h. Include QsLogDest.h only where you create/add destinations. + Get the instance of the logger by calling QsLogging::Logger::instance(); + Optionally set the logging level. Info is default. + Create as many destinations as you want by using the QsLogging::DestinationFactory. + Add the destinations to the logger instance by calling addDestination. + +Disabling logging + +Sometimes it's necessary to turn off logging. This can be done in several ways: + + globally, at compile time, by enabling the QS_LOG_DISABLE macro in the supplied .pri file. + globally, at run time, by setting the log level to "OffLevel". + per file, at compile time, by including QsLogDisableForThisFile.h in the target file. + +Thread safety + +The Qt docs say: A thread-safe function can be called simultaneously from multiple threads, even when the invocations use shared data, because all references to the shared data are serialized. A reentrant function can also be called simultaneously from multiple threads, but only if each invocation uses its own data. + +Since sending the log message to the destinations is protected by a mutex, the logging macros are thread-safe provided that the log has been initialized - i.e: instance() has been called. The instance function and the setup functions (e.g: setLoggingLevel, addDestination) are NOT thread-safe and are NOT reentrant. diff --git a/Applications/qModMaster/3rdparty/QsLog/examplemain.cpp b/Applications/qModMaster/3rdparty/QsLog/examplemain.cpp new file mode 100644 index 0000000..0633b76 --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/examplemain.cpp @@ -0,0 +1,40 @@ +#include "QsLog.h" +#include "QsLogDest.h" +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + // init the logging mechanism + QsLogging::Logger& logger = QsLogging::Logger::instance(); + logger.setLoggingLevel(QsLogging::TraceLevel); + const QString sLogPath(QDir(a.applicationDirPath()).filePath("log.txt")); + + QsLogging::DestinationPtr fileDestination( + QsLogging::DestinationFactory::MakeFileDestination(sLogPath, true, 512, 2) ); + QsLogging::DestinationPtr debugDestination( + QsLogging::DestinationFactory::MakeDebugOutputDestination() ); + logger.addDestination(debugDestination); + logger.addDestination(fileDestination); + + QLOG_INFO() << "Program started"; + QLOG_INFO() << "Built with Qt" << QT_VERSION_STR << "running on" << qVersion(); + + QLOG_TRACE() << "Here's a" << QString::fromUtf8("trace") << "message"; + QLOG_DEBUG() << "Here's a" << static_cast(QsLogging::DebugLevel) << "message"; + QLOG_WARN() << "Uh-oh!"; + qDebug() << "This message won't be picked up by the logger"; + QLOG_ERROR() << "An error has occurred"; + qWarning() << "Neither will this one"; + QLOG_FATAL() << "Fatal error!"; + + logger.setLoggingLevel(QsLogging::OffLevel); + for (int i = 0;i < 10000000;++i) { + QLOG_ERROR() << QString::fromUtf8("logging is turned off"); + } + + return 0; +} diff --git a/Applications/qModMaster/3rdparty/QsLog/log_example.pro b/Applications/qModMaster/3rdparty/QsLog/log_example.pro new file mode 100644 index 0000000..087a3f6 --- /dev/null +++ b/Applications/qModMaster/3rdparty/QsLog/log_example.pro @@ -0,0 +1,11 @@ +# ------------------------------------------------- +# Project created by QtCreator 2010-03-20T12:17:43 +# ------------------------------------------------- +QT -= gui +TARGET = log_example +CONFIG += console +CONFIG -= app_bundle +TEMPLATE = app +SOURCES += \ + examplemain.cpp +include(QsLog.pri) diff --git a/Applications/qModMaster/3rdparty/libmodbus/config.h b/Applications/qModMaster/3rdparty/libmodbus/config.h new file mode 100644 index 0000000..d5e9269 --- /dev/null +++ b/Applications/qModMaster/3rdparty/libmodbus/config.h @@ -0,0 +1,171 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define to 1 if you have the header file. */ +#define HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have the declaration of `TIOCSRS485', and to 0 if you + don't. */ +#define HAVE_DECL_TIOCSRS485 0 + +/* Define to 1 if you have the declaration of `TIOCM_RTS', and to 0 if you + don't. */ +#define HAVE_DECL_TIOCM_RTS 0 + +/* Define to 1 if you have the declaration of `__CYGWIN__', and to 0 if you + don't. */ +#define HAVE_DECL___CYGWIN__ 0 + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if you have the `fork' function. */ +#define HAVE_FORK 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#define HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `inet_ntoa' function. */ +#define HAVE_INET_NTOA 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LIMITS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_SERIAL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `memset' function. */ +#define HAVE_MEMSET 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETDB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_TCP_H 1 + +/* Define to 1 if you have the `select' function. */ +#define HAVE_SELECT 1 + +/* Define to 1 if you have the `socket' function. */ +#define HAVE_SOCKET 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `strerror' function. */ +#define HAVE_STRERROR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +/* #undef HAVE_STRLCPY */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `vfork' function. */ +#define HAVE_VFORK 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_VFORK_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_WINSOCK2_H */ + +/* Define to 1 if `fork' works. */ +#define HAVE_WORKING_FORK 1 + +/* Define to 1 if `vfork' works. */ +#define HAVE_WORKING_VFORK 1 + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Name of package */ +#define PACKAGE "libmodbus" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "https://github.com/stephane/libmodbus/issues" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "libmodbus" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "libmodbus 3.1.0-1" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "libmodbus" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "3.1.0-1" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define to 1 if you can safely include both and . */ +#define TIME_WITH_SYS_TIME 1 + +/* Version number of package */ +#define VERSION "3.1.0-1" + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `int' if does not define. */ +/* #undef pid_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + +/* Define as `fork' if `vfork' does not work. */ +/* #undef vfork */ diff --git a/Applications/qModMaster/3rdparty/libmodbus/modbus-data.c b/Applications/qModMaster/3rdparty/libmodbus/modbus-data.c new file mode 100644 index 0000000..902b8c6 --- /dev/null +++ b/Applications/qModMaster/3rdparty/libmodbus/modbus-data.c @@ -0,0 +1,229 @@ +/* + * Copyright © 2010-2014 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include + +#ifndef _MSC_VER +# include +#else +# include "stdint.h" +#endif + +#include +#include + +#if defined(_WIN32) +# include +#else +# include +#endif + +#include + +#include "modbus.h" + +#if defined(HAVE_BYTESWAP_H) +# include +#endif + +#if defined(__APPLE__) +# include +# define bswap_16 OSSwapInt16 +# define bswap_32 OSSwapInt32 +# define bswap_64 OSSwapInt64 +#endif + +#if defined(__GNUC__) +# define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__ * 10) +# if GCC_VERSION >= 430 +// Since GCC >= 4.30, GCC provides __builtin_bswapXX() alternatives so we switch to them +# undef bswap_32 +# define bswap_32 __builtin_bswap32 +# endif +#endif + +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +# define bswap_32 _byteswap_ulong +# define bswap_16 _byteswap_ushort +#endif + +#if !defined(__CYGWIN__) && !defined(bswap_16) +# warning "Fallback on C functions for bswap_16" +static inline uint16_t bswap_16(uint16_t x) +{ + return (x >> 8) | (x << 8); +} +#endif + +#if !defined(bswap_32) +# warning "Fallback on C functions for bswap_32" +static inline uint32_t bswap_32(uint32_t x) +{ + return (bswap_16(x & 0xffff) << 16) | (bswap_16(x >> 16)); +} +#endif + +/* Sets many bits from a single byte value (all 8 bits of the byte value are + set) */ +void modbus_set_bits_from_byte(uint8_t *dest, int idx, const uint8_t value) +{ + int i; + + for (i=0; i < 8; i++) { + dest[idx+i] = (value & (1 << i)) ? 1 : 0; + } +} + +/* Sets many bits from a table of bytes (only the bits between idx and + idx + nb_bits are set) */ +void modbus_set_bits_from_bytes(uint8_t *dest, int idx, unsigned int nb_bits, + const uint8_t *tab_byte) +{ + unsigned int i; + int shift = 0; + + for (i = idx; i < idx + nb_bits; i++) { + dest[i] = tab_byte[(i - idx) / 8] & (1 << shift) ? 1 : 0; + /* gcc doesn't like: shift = (++shift) % 8; */ + shift++; + shift %= 8; + } +} + +/* Gets the byte value from many bits. + To obtain a full byte, set nb_bits to 8. */ +uint8_t modbus_get_byte_from_bits(const uint8_t *src, int idx, + unsigned int nb_bits) +{ + unsigned int i; + uint8_t value = 0; + + if (nb_bits > 8) { + /* Assert is ignored if NDEBUG is set */ + assert(nb_bits < 8); + nb_bits = 8; + } + + for (i=0; i < nb_bits; i++) { + value |= (src[idx+i] << i); + } + + return value; +} + +/* Get a float from 4 bytes (Modbus) without any conversion (ABCD) */ +float modbus_get_float_abcd(const uint16_t *src) +{ + float f; + uint32_t i; + + i = ntohl(((uint32_t)src[0] << 16) + src[1]); + memcpy(&f, &i, sizeof(float)); + + return f; +} + +/* Get a float from 4 bytes (Modbus) in inversed format (DCBA) */ +float modbus_get_float_dcba(const uint16_t *src) +{ + float f; + uint32_t i; + + i = ntohl(bswap_32((((uint32_t)src[0]) << 16) + src[1])); + memcpy(&f, &i, sizeof(float)); + + return f; +} + +/* Get a float from 4 bytes (Modbus) with swapped bytes (BADC) */ +float modbus_get_float_badc(const uint16_t *src) +{ + float f; + uint32_t i; + + i = ntohl((uint32_t)(bswap_16(src[0]) << 16) + bswap_16(src[1])); + memcpy(&f, &i, sizeof(float)); + + return f; +} + +/* Get a float from 4 bytes (Modbus) with swapped words (CDAB) */ +float modbus_get_float_cdab(const uint16_t *src) +{ + float f; + uint32_t i; + + i = ntohl((((uint32_t)src[1]) << 16) + src[0]); + memcpy(&f, &i, sizeof(float)); + + return f; +} + +/* DEPRECATED - Get a float from 4 bytes in sort of Modbus format */ +float modbus_get_float(const uint16_t *src) +{ + float f; + uint32_t i; + + i = (((uint32_t)src[1]) << 16) + src[0]; + memcpy(&f, &i, sizeof(float)); + + return f; +} + +/* Set a float to 4 bytes for Modbus w/o any conversion (ABCD) */ +void modbus_set_float_abcd(float f, uint16_t *dest) +{ + uint32_t i; + + memcpy(&i, &f, sizeof(uint32_t)); + i = htonl(i); + dest[0] = (uint16_t)(i >> 16); + dest[1] = (uint16_t)i; +} + +/* Set a float to 4 bytes for Modbus with byte and word swap conversion (DCBA) */ +void modbus_set_float_dcba(float f, uint16_t *dest) +{ + uint32_t i; + + memcpy(&i, &f, sizeof(uint32_t)); + i = bswap_32(htonl(i)); + dest[0] = (uint16_t)(i >> 16); + dest[1] = (uint16_t)i; +} + +/* Set a float to 4 bytes for Modbus with byte swap conversion (BADC) */ +void modbus_set_float_badc(float f, uint16_t *dest) +{ + uint32_t i; + + memcpy(&i, &f, sizeof(uint32_t)); + i = htonl(i); + dest[0] = (uint16_t)bswap_16(i >> 16); + dest[1] = (uint16_t)bswap_16(i & 0xFFFF); +} + +/* Set a float to 4 bytes for Modbus with word swap conversion (CDAB) */ +void modbus_set_float_cdab(float f, uint16_t *dest) +{ + uint32_t i; + + memcpy(&i, &f, sizeof(uint32_t)); + i = htonl(i); + dest[0] = (uint16_t)i; + dest[1] = (uint16_t)(i >> 16); +} + +/* DEPRECATED - Set a float to 4 bytes in a sort of Modbus format! */ +void modbus_set_float(float f, uint16_t *dest) +{ + uint32_t i; + + memcpy(&i, &f, sizeof(uint32_t)); + dest[0] = (uint16_t)i; + dest[1] = (uint16_t)(i >> 16); +} diff --git a/Applications/qModMaster/3rdparty/libmodbus/modbus-private.h b/Applications/qModMaster/3rdparty/libmodbus/modbus-private.h new file mode 100644 index 0000000..c5af0f9 --- /dev/null +++ b/Applications/qModMaster/3rdparty/libmodbus/modbus-private.h @@ -0,0 +1,115 @@ +/* + * Copyright © 2010-2012 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_PRIVATE_H +#define MODBUS_PRIVATE_H + +#ifndef _MSC_VER +# include +# include +#else +# include "stdint.h" +# include +typedef int ssize_t; +#endif +#include +#include + +#include "modbus.h" + +MODBUS_BEGIN_DECLS + +/* It's not really the minimal length (the real one is report slave ID + * in RTU (4 bytes)) but it's a convenient size to use in RTU or TCP + * communications to read many values or write a single one. + * Maximum between : + * - HEADER_LENGTH_TCP (7) + function (1) + address (2) + number (2) + * - HEADER_LENGTH_RTU (1) + function (1) + address (2) + number (2) + CRC (2) + */ +#define _MIN_REQ_LENGTH 12 + +#define _REPORT_SLAVE_ID 180 + +#define _MODBUS_EXCEPTION_RSP_LENGTH 5 + +/* Timeouts in microsecond (0.5 s) */ +#define _RESPONSE_TIMEOUT 500000 +#define _BYTE_TIMEOUT 500000 + +typedef enum { + _MODBUS_BACKEND_TYPE_RTU=0, + _MODBUS_BACKEND_TYPE_TCP +} modbus_backend_type_t; + +/* + * ---------- Request Indication ---------- + * | Client | ---------------------->| Server | + * ---------- Confirmation Response ---------- + */ +typedef enum { + /* Request message on the server side */ + MSG_INDICATION, + /* Request message on the client side */ + MSG_CONFIRMATION +} msg_type_t; + +/* This structure reduces the number of params in functions and so + * optimizes the speed of execution (~ 37%). */ +typedef struct _sft { + int slave; + int function; + int t_id; +} sft_t; + +typedef struct _modbus_backend { + unsigned int backend_type; + unsigned int header_length; + unsigned int checksum_length; + unsigned int max_adu_length; + int (*set_slave) (modbus_t *ctx, int slave); + int (*build_request_basis) (modbus_t *ctx, int function, int addr, + int nb, uint8_t *req); + int (*build_response_basis) (sft_t *sft, uint8_t *rsp); + int (*prepare_response_tid) (const uint8_t *req, int *req_length); + int (*send_msg_pre) (uint8_t *req, int req_length); + ssize_t (*send) (modbus_t *ctx, const uint8_t *req, int req_length); + int (*receive) (modbus_t *ctx, uint8_t *req); + ssize_t (*recv) (modbus_t *ctx, uint8_t *rsp, int rsp_length); + int (*check_integrity) (modbus_t *ctx, uint8_t *msg, + const int msg_length); + int (*pre_check_confirmation) (modbus_t *ctx, const uint8_t *req, + const uint8_t *rsp, int rsp_length); + int (*connect) (modbus_t *ctx); + void (*close) (modbus_t *ctx); + int (*flush) (modbus_t *ctx); + int (*select) (modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length); + void (*free) (modbus_t *ctx); +} modbus_backend_t; + +struct _modbus { + /* Slave address */ + int slave; + /* Socket or file descriptor */ + int s; + int debug; + int error_recovery; + struct timeval response_timeout; + struct timeval byte_timeout; + const modbus_backend_t *backend; + void *backend_data; +}; + +void _modbus_init_common(modbus_t *ctx); +void _error_print(modbus_t *ctx, const char *context); +int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type); + +#ifndef HAVE_STRLCPY +size_t strlcpy(char *dest, const char *src, size_t dest_size); +#endif + +MODBUS_END_DECLS + +#endif /* MODBUS_PRIVATE_H */ diff --git a/Applications/qModMaster/3rdparty/libmodbus/modbus-rtu-private.h b/Applications/qModMaster/3rdparty/libmodbus/modbus-rtu-private.h new file mode 100644 index 0000000..c7c8fe8 --- /dev/null +++ b/Applications/qModMaster/3rdparty/libmodbus/modbus-rtu-private.h @@ -0,0 +1,77 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_RTU_PRIVATE_H +#define MODBUS_RTU_PRIVATE_H + +#ifndef _MSC_VER +#include +#else +#include "stdint.h" +#endif + +#if defined(_WIN32) +#include +#else +#include +#endif + +#define _MODBUS_RTU_HEADER_LENGTH 1 +#define _MODBUS_RTU_PRESET_REQ_LENGTH 6 +#define _MODBUS_RTU_PRESET_RSP_LENGTH 2 + +#define _MODBUS_RTU_CHECKSUM_LENGTH 2 + +#if defined(_WIN32) +#if !defined(ENOTSUP) +#define ENOTSUP WSAEOPNOTSUPP +#endif + +/* WIN32: struct containing serial handle and a receive buffer */ +#define PY_BUF_SIZE 512 +struct win32_ser { + /* File handle */ + HANDLE fd; + /* Receive buffer */ + uint8_t buf[PY_BUF_SIZE]; + /* Received chars */ + DWORD n_bytes; +}; +#endif /* _WIN32 */ + +typedef struct _modbus_rtu { + /* Device: "/dev/ttyS0", "/dev/ttyUSB0" or "/dev/tty.USA19*" on Mac OS X. */ + char *device; + /* Bauds: 9600, 19200, 57600, 115200, etc */ + int baud; + /* Data bit */ + uint8_t data_bit; + /* Stop bit */ + uint8_t stop_bit; + /* Parity: 'N', 'O', 'E' */ + char parity; +#if defined(_WIN32) + struct win32_ser w_ser; + DCB old_dcb; +#else + /* Save old termios settings */ + struct termios old_tios; +#endif +#if HAVE_DECL_TIOCSRS485 + int serial_mode; +#endif +//***Not part of libmodbus - added for QModMaster***// +#if defined(_WIN32) || HAVE_DECL_TIOCM_RTS + int rts; + int rts_delay; + int onebyte_time; + void (*set_rts) (modbus_t *ctx, int on); +#endif + /* To handle many slaves on the same link */ + int confirmation_to_ignore; +} modbus_rtu_t; + +#endif /* MODBUS_RTU_PRIVATE_H */ diff --git a/Applications/qModMaster/3rdparty/libmodbus/modbus-rtu.c b/Applications/qModMaster/3rdparty/libmodbus/modbus-rtu.c new file mode 100644 index 0000000..4805bb7 --- /dev/null +++ b/Applications/qModMaster/3rdparty/libmodbus/modbus-rtu.c @@ -0,0 +1,1285 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include +#include +#include +#include +#include +#ifndef _MSC_VER +#include +#endif +#include + +#include "modbus-private.h" + +#include "modbus-rtu.h" +#include "modbus-rtu-private.h" + +#if HAVE_DECL_TIOCSRS485 || HAVE_DECL_TIOCM_RTS +#include +#endif + +#if HAVE_DECL_TIOCSRS485 +#include +#endif + +/* Table of CRC values for high-order byte */ +static const uint8_t table_crc_hi[] = { + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 +}; + +/* Table of CRC values for low-order byte */ +static const uint8_t table_crc_lo[] = { + 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, + 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, + 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, + 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, + 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, + 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, + 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, + 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, + 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, + 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, + 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, + 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, + 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, + 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, + 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, + 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, + 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, + 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, + 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, + 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, + 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, + 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, + 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, + 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, + 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, + 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 +}; + +/* Define the slave ID of the remote device to talk in master mode or set the + * internal slave ID in slave mode */ +static int _modbus_set_slave(modbus_t *ctx, int slave) +{ + /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */ + //***changed for QModMaster : <= 247***// + if (slave >= 0 && slave <= 255) { + ctx->slave = slave; + } else { + errno = EINVAL; + return -1; + } + + return 0; +} + +/* Builds a RTU request header */ +static int _modbus_rtu_build_request_basis(modbus_t *ctx, int function, + int addr, int nb, + uint8_t *req) +{ + assert(ctx->slave != -1); + req[0] = ctx->slave; + req[1] = function; + req[2] = addr >> 8; + req[3] = addr & 0x00ff; + req[4] = nb >> 8; + req[5] = nb & 0x00ff; + + return _MODBUS_RTU_PRESET_REQ_LENGTH; +} + +/* Builds a RTU response header */ +static int _modbus_rtu_build_response_basis(sft_t *sft, uint8_t *rsp) +{ + /* In this case, the slave is certainly valid because a check is already + * done in _modbus_rtu_listen */ + rsp[0] = sft->slave; + rsp[1] = sft->function; + + return _MODBUS_RTU_PRESET_RSP_LENGTH; +} + +static uint16_t crc16(uint8_t *buffer, uint16_t buffer_length) +{ + uint8_t crc_hi = 0xFF; /* high CRC byte initialized */ + uint8_t crc_lo = 0xFF; /* low CRC byte initialized */ + unsigned int i; /* will index into CRC lookup */ + + /* pass through message buffer */ + while (buffer_length--) { + i = crc_hi ^ *buffer++; /* calculate the CRC */ + crc_hi = crc_lo ^ table_crc_hi[i]; + crc_lo = table_crc_lo[i]; + } + + return (crc_hi << 8 | crc_lo); +} + +static int _modbus_rtu_prepare_response_tid(const uint8_t *req, int *req_length) +{ + (*req_length) -= _MODBUS_RTU_CHECKSUM_LENGTH; + /* No TID */ + return 0; +} + +static int _modbus_rtu_send_msg_pre(uint8_t *req, int req_length) +{ + uint16_t crc = crc16(req, req_length); + req[req_length++] = crc >> 8; + req[req_length++] = crc & 0x00FF; + + return req_length; +} + +#if defined(_WIN32) + +/* This simple implementation is sort of a substitute of the select() call, + * working this way: the win32_ser_select() call tries to read some data from + * the serial port, setting the timeout as the select() call would. Data read is + * stored into the receive buffer, that is then consumed by the win32_ser_read() + * call. So win32_ser_select() does both the event waiting and the reading, + * while win32_ser_read() only consumes the receive buffer. + */ + +static void win32_ser_init(struct win32_ser *ws) +{ + /* Clear everything */ + memset(ws, 0x00, sizeof(struct win32_ser)); + + /* Set file handle to invalid */ + ws->fd = INVALID_HANDLE_VALUE; +} + +/* FIXME Try to remove length_to_read -> max_len argument, only used by win32 */ +static int win32_ser_select(struct win32_ser *ws, int max_len, + const struct timeval *tv) +{ + COMMTIMEOUTS comm_to; + unsigned int msec = 0; + + /* Check if some data still in the buffer to be consumed */ + if (ws->n_bytes > 0) { + return 1; + } + + /* Setup timeouts like select() would do. + FIXME Please someone on Windows can look at this? + Does it possible to use WaitCommEvent? + When tv is NULL, MAXDWORD isn't infinite! + */ + if (tv == NULL) { + msec = MAXDWORD; + } else { + msec = tv->tv_sec * 1000 + tv->tv_usec / 1000; + if (msec < 1) + msec = 1; + } + + comm_to.ReadIntervalTimeout = msec; + comm_to.ReadTotalTimeoutMultiplier = 0; + comm_to.ReadTotalTimeoutConstant = msec; + comm_to.WriteTotalTimeoutMultiplier = 0; + comm_to.WriteTotalTimeoutConstant = 1000; + SetCommTimeouts(ws->fd, &comm_to); + + /* Read some bytes */ + if ((max_len > PY_BUF_SIZE) || (max_len < 0)) { + max_len = PY_BUF_SIZE; + } + + if (ReadFile(ws->fd, &ws->buf, max_len, &ws->n_bytes, NULL)) { + /* Check if some bytes available */ + if (ws->n_bytes > 0) { + /* Some bytes read */ + return 1; + } else { + /* Just timed out */ + return 0; + } + } else { + /* Some kind of error */ + return -1; + } +} + +static int win32_ser_read(struct win32_ser *ws, uint8_t *p_msg, + unsigned int max_len) +{ + unsigned int n = ws->n_bytes; + + if (max_len < n) { + n = max_len; + } + + if (n > 0) { + memcpy(p_msg, ws->buf, n); + } + + ws->n_bytes -= n; + + return n; +} +#endif + +#if HAVE_DECL_TIOCM_RTS +static void _modbus_rtu_ioctl_rts(modbus_t *ctx, int on) +{ + int fd = ctx->s; + int flags; + + ioctl(fd, TIOCMGET, &flags); + if (on) { + flags |= TIOCM_RTS; + } else { + flags &= ~TIOCM_RTS; + } + ioctl(fd, TIOCMSET, &flags); +} +#endif + +static ssize_t _modbus_rtu_send(modbus_t *ctx, const uint8_t *req, int req_length) +{ +#if defined(_WIN32) + modbus_rtu_t *ctx_rtu = ctx->backend_data; + DWORD n_bytes = 0; + return (WriteFile(ctx_rtu->w_ser.fd, req, req_length, &n_bytes, NULL)) ? (ssize_t)n_bytes : -1; +#else +#if HAVE_DECL_TIOCM_RTS + modbus_rtu_t *ctx_rtu = ctx->backend_data; + if (ctx_rtu->rts != MODBUS_RTU_RTS_NONE) { + ssize_t size; + + if (ctx->debug) { + fprintf(stderr, "Sending request using RTS signal\n"); + } + + ctx_rtu->set_rts(ctx, ctx_rtu->rts == MODBUS_RTU_RTS_UP); + usleep(ctx_rtu->rts_delay); + + size = write(ctx->s, req, req_length); + + usleep(ctx_rtu->onebyte_time * req_length + ctx_rtu->rts_delay); + ctx_rtu->set_rts(ctx, ctx_rtu->rts != MODBUS_RTU_RTS_UP); + + return size; + } else { +#endif + return write(ctx->s, req, req_length); +#if HAVE_DECL_TIOCM_RTS + } +#endif +#endif +} + +static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req) +{ + int rc; + modbus_rtu_t *ctx_rtu = ctx->backend_data; + + if (ctx_rtu->confirmation_to_ignore) { + _modbus_receive_msg(ctx, req, MSG_CONFIRMATION); + /* Ignore errors and reset the flag */ + ctx_rtu->confirmation_to_ignore = FALSE; + rc = 0; + if (ctx->debug) { + printf("Confirmation to ignore\n"); + } + } else { + rc = _modbus_receive_msg(ctx, req, MSG_INDICATION); + if (rc == 0) { + /* The next expected message is a confirmation to ignore */ + ctx_rtu->confirmation_to_ignore = TRUE; + } + } + return rc; +} + +static ssize_t _modbus_rtu_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length) +{ +#if defined(_WIN32) + return win32_ser_read(&((modbus_rtu_t *)ctx->backend_data)->w_ser, rsp, rsp_length); +#else + return read(ctx->s, rsp, rsp_length); +#endif +} + +static int _modbus_rtu_flush(modbus_t *); + +static int _modbus_rtu_pre_check_confirmation(modbus_t *ctx, const uint8_t *req, + const uint8_t *rsp, int rsp_length) +{ + /* Check responding slave is the slave we requested (except for broacast + * request) */ + if (req[0] != rsp[0] && req[0] != MODBUS_BROADCAST_ADDRESS) { + if (ctx->debug) { + fprintf(stderr, + "The responding slave %d isn't the requested slave %d\n", + rsp[0], req[0]); + } + errno = EMBBADSLAVE; + return -1; + } else { + return 0; + } +} + +/* The check_crc16 function shall return 0 is the message is ignored and the + message length if the CRC is valid. Otherwise it shall return -1 and set + errno to EMBADCRC. */ +static int _modbus_rtu_check_integrity(modbus_t *ctx, uint8_t *msg, + const int msg_length) +{ + uint16_t crc_calculated; + uint16_t crc_received; + int slave = msg[0]; + + /* Filter on the Modbus unit identifier (slave) in RTU mode to avoid useless + * CRC computing. */ + if (slave != ctx->slave && slave != MODBUS_BROADCAST_ADDRESS) { + if (ctx->debug) { + printf("Request for slave %d ignored (not %d)\n", slave, ctx->slave); + } + /* Following call to check_confirmation handles this error */ + return 0; + } + + crc_calculated = crc16(msg, msg_length - 2); + crc_received = (msg[msg_length - 2] << 8) | msg[msg_length - 1]; + + /* Check CRC of msg */ + if (crc_calculated == crc_received) { + return msg_length; + } else { + if (ctx->debug) { + fprintf(stderr, "ERROR CRC received 0x%0X != CRC calculated 0x%0X\n", + crc_received, crc_calculated); + } + + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { + _modbus_rtu_flush(ctx); + } + errno = EMBBADCRC; + return -1; + } +} + +/* Sets up a serial port for RTU communications */ +static int _modbus_rtu_connect(modbus_t *ctx) +{ +#if defined(_WIN32) + DCB dcb; +#else + struct termios tios; + speed_t speed; + int flags; +#endif + modbus_rtu_t *ctx_rtu = ctx->backend_data; + + if (ctx->debug) { + printf("Opening %s at %d bauds (%c, %d, %d)\n", + ctx_rtu->device, ctx_rtu->baud, ctx_rtu->parity, + ctx_rtu->data_bit, ctx_rtu->stop_bit); + } + +#if defined(_WIN32) + /* Some references here: + * http://msdn.microsoft.com/en-us/library/aa450602.aspx + */ + win32_ser_init(&ctx_rtu->w_ser); + + /* ctx_rtu->device should contain a string like "COMxx:" xx being a decimal + * number */ + ctx_rtu->w_ser.fd = CreateFileA(ctx_rtu->device, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL); + + /* Error checking */ + if (ctx_rtu->w_ser.fd == INVALID_HANDLE_VALUE) { + if (ctx->debug) { + fprintf(stderr, "ERROR Can't open the device %s (LastError %d)\n", + ctx_rtu->device, (int)GetLastError()); + } + return -1; + } + + /* Save params */ + ctx_rtu->old_dcb.DCBlength = sizeof(DCB); + if (!GetCommState(ctx_rtu->w_ser.fd, &ctx_rtu->old_dcb)) { + if (ctx->debug) { + fprintf(stderr, "ERROR Error getting configuration (LastError %d)\n", + (int)GetLastError()); + } + CloseHandle(ctx_rtu->w_ser.fd); + ctx_rtu->w_ser.fd = INVALID_HANDLE_VALUE; + return -1; + } + + /* Build new configuration (starting from current settings) */ + dcb = ctx_rtu->old_dcb; + + /* Speed setting */ + switch (ctx_rtu->baud) { + case 110: + dcb.BaudRate = CBR_110; + break; + case 300: + dcb.BaudRate = CBR_300; + break; + case 600: + dcb.BaudRate = CBR_600; + break; + case 1200: + dcb.BaudRate = CBR_1200; + break; + case 2400: + dcb.BaudRate = CBR_2400; + break; + case 4800: + dcb.BaudRate = CBR_4800; + break; + case 9600: + dcb.BaudRate = CBR_9600; + break; + case 14400: + dcb.BaudRate = CBR_14400; + break; + case 19200: + dcb.BaudRate = CBR_19200; + break; + case 38400: + dcb.BaudRate = CBR_38400; + break; + case 57600: + dcb.BaudRate = CBR_57600; + break; + case 115200: + dcb.BaudRate = CBR_115200; + break; + case 230400: + /* CBR_230400 - not defined */ + dcb.BaudRate = 230400; + break; + case 250000: + dcb.BaudRate = 250000; + break; + case 460800: + dcb.BaudRate = 460800; + break; + case 500000: + dcb.BaudRate = 500000; + break; + case 921600: + dcb.BaudRate = 921600; + break; + case 1000000: + dcb.BaudRate = 1000000; + break; + default: + dcb.BaudRate = CBR_9600; + if (ctx->debug) { + fprintf(stderr, "WARNING Unknown baud rate %d for %s (B9600 used)\n", + ctx_rtu->baud, ctx_rtu->device); + } + } + + /* Data bits */ + switch (ctx_rtu->data_bit) { + case 5: + dcb.ByteSize = 5; + break; + case 6: + dcb.ByteSize = 6; + break; + case 7: + dcb.ByteSize = 7; + break; + case 8: + default: + dcb.ByteSize = 8; + break; + } + + /* Stop bits */ + if (ctx_rtu->stop_bit == 1) + dcb.StopBits = ONESTOPBIT; + else /* 2 */ + dcb.StopBits = TWOSTOPBITS; + + /* Parity */ + if (ctx_rtu->parity == 'N') { + dcb.Parity = NOPARITY; + dcb.fParity = FALSE; + } else if (ctx_rtu->parity == 'E') { + dcb.Parity = EVENPARITY; + dcb.fParity = TRUE; + } else { + /* odd */ + dcb.Parity = ODDPARITY; + dcb.fParity = TRUE; + } + + /* Hardware handshaking left as default settings retrieved */ + + /* No software handshaking */ + dcb.fTXContinueOnXoff = TRUE; + dcb.fOutX = FALSE; + dcb.fInX = FALSE; + + /* Binary mode (it's the only supported on Windows anyway) */ + dcb.fBinary = TRUE; + + /* Don't want errors to be blocking */ + dcb.fAbortOnError = FALSE; + + //***Not part of libmodbus - added for QModMaster***// + dcb.fRtsControl = ctx_rtu->rts; + + /* Setup port */ + if (!SetCommState(ctx_rtu->w_ser.fd, &dcb)) { + if (ctx->debug) { + fprintf(stderr, "ERROR Error setting new configuration (LastError %d)\n", + (int)GetLastError()); + } + CloseHandle(ctx_rtu->w_ser.fd); + ctx_rtu->w_ser.fd = INVALID_HANDLE_VALUE; + return -1; + } +#else + /* The O_NOCTTY flag tells UNIX that this program doesn't want + to be the "controlling terminal" for that port. If you + don't specify this then any input (such as keyboard abort + signals and so forth) will affect your process + + Timeouts are ignored in canonical input mode or when the + NDELAY option is set on the file via open or fcntl */ + flags = O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL; +#ifdef O_CLOEXEC + flags |= O_CLOEXEC; +#endif + + ctx->s = open(ctx_rtu->device, flags); + if (ctx->s == -1) { + if (ctx->debug) { + fprintf(stderr, "ERROR Can't open the device %s (%s)\n", + ctx_rtu->device, strerror(errno)); + } + return -1; + } + + /* Save */ + tcgetattr(ctx->s, &ctx_rtu->old_tios); + + memset(&tios, 0, sizeof(struct termios)); + + /* C_ISPEED Input baud (new interface) + C_OSPEED Output baud (new interface) + */ + switch (ctx_rtu->baud) { + case 110: + speed = B110; + break; + case 300: + speed = B300; + break; + case 600: + speed = B600; + break; + case 1200: + speed = B1200; + break; + case 2400: + speed = B2400; + break; + case 4800: + speed = B4800; + break; + case 9600: + speed = B9600; + break; + case 19200: + speed = B19200; + break; + case 38400: + speed = B38400; + break; +#ifdef B57600 + case 57600: + speed = B57600; + break; +#endif +#ifdef B115200 + case 115200: + speed = B115200; + break; +#endif +#ifdef B230400 + case 230400: + speed = B230400; + break; +#endif +#ifdef B460800 + case 460800: + speed = B460800; + break; +#endif +#ifdef B500000 + case 500000: + speed = B500000; + break; +#endif +#ifdef B576000 + case 576000: + speed = B576000; + break; +#endif +#ifdef B921600 + case 921600: + speed = B921600; + break; +#endif +#ifdef B1000000 + case 1000000: + speed = B1000000; + break; +#endif +#ifdef B1152000 + case 1152000: + speed = B1152000; + break; +#endif +#ifdef B1500000 + case 1500000: + speed = B1500000; + break; +#endif +#ifdef B2500000 + case 2500000: + speed = B2500000; + break; +#endif +#ifdef B3000000 + case 3000000: + speed = B3000000; + break; +#endif +#ifdef B3500000 + case 3500000: + speed = B3500000; + break; +#endif +#ifdef B4000000 + case 4000000: + speed = B4000000; + break; +#endif + default: + speed = B9600; + if (ctx->debug) { + fprintf(stderr, + "WARNING Unknown baud rate %d for %s (B9600 used)\n", + ctx_rtu->baud, ctx_rtu->device); + } + } + + /* Set the baud rate */ + if ((cfsetispeed(&tios, speed) < 0) || + (cfsetospeed(&tios, speed) < 0)) { + close(ctx->s); + ctx->s = -1; + return -1; + } + + /* C_CFLAG Control options + CLOCAL Local line - do not change "owner" of port + CREAD Enable receiver + */ + tios.c_cflag |= (CREAD | CLOCAL); + /* CSIZE, HUPCL, CRTSCTS (hardware flow control) */ + + /* Set data bits (5, 6, 7, 8 bits) + CSIZE Bit mask for data bits + */ + tios.c_cflag &= ~CSIZE; + switch (ctx_rtu->data_bit) { + case 5: + tios.c_cflag |= CS5; + break; + case 6: + tios.c_cflag |= CS6; + break; + case 7: + tios.c_cflag |= CS7; + break; + case 8: + default: + tios.c_cflag |= CS8; + break; + } + + /* Stop bit (1 or 2) */ + if (ctx_rtu->stop_bit == 1) + tios.c_cflag &=~ CSTOPB; + else /* 2 */ + tios.c_cflag |= CSTOPB; + + /* PARENB Enable parity bit + PARODD Use odd parity instead of even */ + if (ctx_rtu->parity == 'N') { + /* None */ + tios.c_cflag &=~ PARENB; + } else if (ctx_rtu->parity == 'E') { + /* Even */ + tios.c_cflag |= PARENB; + tios.c_cflag &=~ PARODD; + } else { + /* Odd */ + tios.c_cflag |= PARENB; + tios.c_cflag |= PARODD; + } + + /* Read the man page of termios if you need more information. */ + + /* This field isn't used on POSIX systems + tios.c_line = 0; + */ + + /* C_LFLAG Line options + + ISIG Enable SIGINTR, SIGSUSP, SIGDSUSP, and SIGQUIT signals + ICANON Enable canonical input (else raw) + XCASE Map uppercase \lowercase (obsolete) + ECHO Enable echoing of input characters + ECHOE Echo erase character as BS-SP-BS + ECHOK Echo NL after kill character + ECHONL Echo NL + NOFLSH Disable flushing of input buffers after + interrupt or quit characters + IEXTEN Enable extended functions + ECHOCTL Echo control characters as ^char and delete as ~? + ECHOPRT Echo erased character as character erased + ECHOKE BS-SP-BS entire line on line kill + FLUSHO Output being flushed + PENDIN Retype pending input at next read or input char + TOSTOP Send SIGTTOU for background output + + Canonical input is line-oriented. Input characters are put + into a buffer which can be edited interactively by the user + until a CR (carriage return) or LF (line feed) character is + received. + + Raw input is unprocessed. Input characters are passed + through exactly as they are received, when they are + received. Generally you'll deselect the ICANON, ECHO, + ECHOE, and ISIG options when using raw input + */ + + /* Raw input */ + tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + + /* C_IFLAG Input options + + Constant Description + INPCK Enable parity check + IGNPAR Ignore parity errors + PARMRK Mark parity errors + ISTRIP Strip parity bits + IXON Enable software flow control (outgoing) + IXOFF Enable software flow control (incoming) + IXANY Allow any character to start flow again + IGNBRK Ignore break condition + BRKINT Send a SIGINT when a break condition is detected + INLCR Map NL to CR + IGNCR Ignore CR + ICRNL Map CR to NL + IUCLC Map uppercase to lowercase + IMAXBEL Echo BEL on input line too long + */ + if (ctx_rtu->parity == 'N') { + /* None */ + tios.c_iflag &= ~INPCK; + } else { + tios.c_iflag |= INPCK; + } + + /* Software flow control is disabled */ + tios.c_iflag &= ~(IXON | IXOFF | IXANY); + + /* C_OFLAG Output options + OPOST Postprocess output (not set = raw output) + ONLCR Map NL to CR-NL + + ONCLR ant others needs OPOST to be enabled + */ + + /* Raw ouput */ + tios.c_oflag &=~ OPOST; + + /* C_CC Control characters + VMIN Minimum number of characters to read + VTIME Time to wait for data (tenths of seconds) + + UNIX serial interface drivers provide the ability to + specify character and packet timeouts. Two elements of the + c_cc array are used for timeouts: VMIN and VTIME. Timeouts + are ignored in canonical input mode or when the NDELAY + option is set on the file via open or fcntl. + + VMIN specifies the minimum number of characters to read. If + it is set to 0, then the VTIME value specifies the time to + wait for every character read. Note that this does not mean + that a read call for N bytes will wait for N characters to + come in. Rather, the timeout will apply to the first + character and the read call will return the number of + characters immediately available (up to the number you + request). + + If VMIN is non-zero, VTIME specifies the time to wait for + the first character read. If a character is read within the + time given, any read will block (wait) until all VMIN + characters are read. That is, once the first character is + read, the serial interface driver expects to receive an + entire packet of characters (VMIN bytes total). If no + character is read within the time allowed, then the call to + read returns 0. This method allows you to tell the serial + driver you need exactly N bytes and any read call will + return 0 or N bytes. However, the timeout only applies to + the first character read, so if for some reason the driver + misses one character inside the N byte packet then the read + call could block forever waiting for additional input + characters. + + VTIME specifies the amount of time to wait for incoming + characters in tenths of seconds. If VTIME is set to 0 (the + default), reads will block (wait) indefinitely unless the + NDELAY option is set on the port with open or fcntl. + */ + /* Unused because we use open with the NDELAY option */ + tios.c_cc[VMIN] = 0; + tios.c_cc[VTIME] = 0; + + if (tcsetattr(ctx->s, TCSANOW, &tios) < 0) { + close(ctx->s); + ctx->s = -1; + return -1; + } +#endif + + return 0; +} + +int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCSRS485 + modbus_rtu_t *ctx_rtu = ctx->backend_data; + struct serial_rs485 rs485conf; + memset(&rs485conf, 0x0, sizeof(struct serial_rs485)); + + if (mode == MODBUS_RTU_RS485) { + rs485conf.flags = SER_RS485_ENABLED; + if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) { + return -1; + } + + ctx_rtu->serial_mode = MODBUS_RTU_RS485; + return 0; + } else if (mode == MODBUS_RTU_RS232) { + /* Turn off RS485 mode only if required */ + if (ctx_rtu->serial_mode == MODBUS_RTU_RS485) { + /* The ioctl call is avoided because it can fail on some RS232 ports */ + if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) { + return -1; + } + } + ctx_rtu->serial_mode = MODBUS_RTU_RS232; + return 0; + } +#else + if (ctx->debug) { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } + + /* Wrong backend and invalid mode specified */ + errno = EINVAL; + return -1; +} + +int modbus_rtu_get_serial_mode(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCSRS485 + modbus_rtu_t *ctx_rtu = ctx->backend_data; + return ctx_rtu->serial_mode; +#else + if (ctx->debug) { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } else { + errno = EINVAL; + return -1; + } +} + +int modbus_rtu_get_rts(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCM_RTS + modbus_rtu_t *ctx_rtu = ctx->backend_data; + return ctx_rtu->rts; +#else + if (ctx->debug) { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } else { + errno = EINVAL; + return -1; + } +} + +int modbus_rtu_set_rts(modbus_t *ctx, int mode) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCM_RTS + modbus_rtu_t *ctx_rtu = ctx->backend_data; + + if (mode == MODBUS_RTU_RTS_NONE || mode == MODBUS_RTU_RTS_UP || + mode == MODBUS_RTU_RTS_DOWN) { + ctx_rtu->rts = mode; + + /* Set the RTS bit in order to not reserve the RS485 bus */ + ctx_rtu->set_rts(ctx, ctx_rtu->rts != MODBUS_RTU_RTS_UP); + + return 0; + } else { + errno = EINVAL; + return -1; + } +#else + if (ctx->debug) { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } + /* Wrong backend or invalid mode specified */ + errno = EINVAL; + return -1; +} + +int modbus_rtu_set_custom_rts(modbus_t *ctx, void (*set_rts) (modbus_t *ctx, int on)) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCM_RTS + modbus_rtu_t *ctx_rtu = ctx->backend_data; + ctx_rtu->set_rts = set_rts; + return 0; +#else + if (ctx->debug) { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } else { + errno = EINVAL; + return -1; + } +} + +int modbus_rtu_get_rts_delay(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCM_RTS + modbus_rtu_t *ctx_rtu; + ctx_rtu = (modbus_rtu_t *)ctx->backend_data; + return ctx_rtu->rts_delay; +#else + if (ctx->debug) { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } else { + errno = EINVAL; + return -1; + } +} + +int modbus_rtu_set_rts_delay(modbus_t *ctx, int us) +{ + if (ctx == NULL || us < 0) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCM_RTS + modbus_rtu_t *ctx_rtu; + ctx_rtu = (modbus_rtu_t *)ctx->backend_data; + ctx_rtu->rts_delay = us; + return 0; +#else + if (ctx->debug) { + fprintf(stderr, "This function isn't supported on your platform\n"); + } + errno = ENOTSUP; + return -1; +#endif + } else { + errno = EINVAL; + return -1; + } +} + +static void _modbus_rtu_close(modbus_t *ctx) +{ + /* Restore line settings and close file descriptor in RTU mode */ + modbus_rtu_t *ctx_rtu = ctx->backend_data; + +#if defined(_WIN32) + /* Revert settings */ + if (!SetCommState(ctx_rtu->w_ser.fd, &ctx_rtu->old_dcb) && ctx->debug) { + fprintf(stderr, "ERROR Couldn't revert to configuration (LastError %d)\n", + (int)GetLastError()); + } + + if (!CloseHandle(ctx_rtu->w_ser.fd) && ctx->debug) { + fprintf(stderr, "ERROR Error while closing handle (LastError %d)\n", + (int)GetLastError()); + } +#else + if (ctx->s != -1) { + tcsetattr(ctx->s, TCSANOW, &ctx_rtu->old_tios); + close(ctx->s); + ctx->s = -1; + } +#endif +} + +static int _modbus_rtu_flush(modbus_t *ctx) +{ +#if defined(_WIN32) + modbus_rtu_t *ctx_rtu = ctx->backend_data; + ctx_rtu->w_ser.n_bytes = 0; + return (PurgeComm(ctx_rtu->w_ser.fd, PURGE_RXCLEAR) == FALSE); +#else + return tcflush(ctx->s, TCIOFLUSH); +#endif +} + +static int _modbus_rtu_select(modbus_t *ctx, fd_set *rset, + struct timeval *tv, int length_to_read) +{ + int s_rc; +#if defined(_WIN32) + s_rc = win32_ser_select(&((modbus_rtu_t *)ctx->backend_data)->w_ser, + length_to_read, tv); + if (s_rc == 0) { + errno = ETIMEDOUT; + return -1; + } + + if (s_rc < 0) { + return -1; + } +#else + while ((s_rc = select(ctx->s+1, rset, NULL, NULL, tv)) == -1) { + if (errno == EINTR) { + if (ctx->debug) { + fprintf(stderr, "A non blocked signal was caught\n"); + } + /* Necessary after an error */ + FD_ZERO(rset); + FD_SET(ctx->s, rset); + } else { + return -1; + } + } + + if (s_rc == 0) { + /* Timeout */ + errno = ETIMEDOUT; + return -1; + } +#endif + + return s_rc; +} + +static void _modbus_rtu_free(modbus_t *ctx) { + free(((modbus_rtu_t*)ctx->backend_data)->device); + free(ctx->backend_data); + free(ctx); +} + +const modbus_backend_t _modbus_rtu_backend = { + _MODBUS_BACKEND_TYPE_RTU, + _MODBUS_RTU_HEADER_LENGTH, + _MODBUS_RTU_CHECKSUM_LENGTH, + MODBUS_RTU_MAX_ADU_LENGTH, + _modbus_set_slave, + _modbus_rtu_build_request_basis, + _modbus_rtu_build_response_basis, + _modbus_rtu_prepare_response_tid, + _modbus_rtu_send_msg_pre, + _modbus_rtu_send, + _modbus_rtu_receive, + _modbus_rtu_recv, + _modbus_rtu_check_integrity, + _modbus_rtu_pre_check_confirmation, + _modbus_rtu_connect, + _modbus_rtu_close, + _modbus_rtu_flush, + _modbus_rtu_select, + _modbus_rtu_free +}; + +//***Not part of libmodbus - rts param added for QModMaster***// +modbus_t* modbus_new_rtu(const char *device, + int baud, char parity, int data_bit, + int stop_bit, int rts) +{ + modbus_t *ctx; + modbus_rtu_t *ctx_rtu; + + /* Check device argument */ + if (device == NULL || *device == 0) { + fprintf(stderr, "The device string is empty\n"); + errno = EINVAL; + return NULL; + } + + /* Check baud argument */ + if (baud == 0) { + fprintf(stderr, "The baud rate value must not be zero\n"); + errno = EINVAL; + return NULL; + } + + ctx = (modbus_t *)malloc(sizeof(modbus_t)); + _modbus_init_common(ctx); + ctx->backend = &_modbus_rtu_backend; + ctx->backend_data = (modbus_rtu_t *)malloc(sizeof(modbus_rtu_t)); + ctx_rtu = (modbus_rtu_t *)ctx->backend_data; + ctx_rtu->device = NULL; + + /* Device name and \0 */ + ctx_rtu->device = (char *)malloc((strlen(device) + 1) * sizeof(char)); + strcpy(ctx_rtu->device, device); + + ctx_rtu->baud = baud; + if (parity == 'N' || parity == 'E' || parity == 'O') { + ctx_rtu->parity = parity; + } else { + modbus_free(ctx); + errno = EINVAL; + return NULL; + } + ctx_rtu->data_bit = data_bit; + ctx_rtu->stop_bit = stop_bit; + +#if HAVE_DECL_TIOCSRS485 + /* The RS232 mode has been set by default */ + ctx_rtu->serial_mode = MODBUS_RTU_RS232; +#endif + +#if HAVE_DECL_TIOCM_RTS + /* The RTS use has been set by default */ + ctx_rtu->rts = MODBUS_RTU_RTS_NONE; + + /* Calculate estimated time in micro second to send one byte */ + ctx_rtu->onebyte_time = 1000000 * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud; + + /* The internal function is used by default to set RTS */ + ctx_rtu->set_rts = _modbus_rtu_ioctl_rts; + + /* The delay before and after transmission when toggling the RTS pin */ + ctx_rtu->rts_delay = ctx_rtu->onebyte_time; +#endif + +//***Not part of libmodbus - added for QModMaster***// +#if defined(_WIN32) + ctx_rtu->rts = rts; +#endif + + ctx_rtu->confirmation_to_ignore = FALSE; + + return ctx; +} diff --git a/Applications/qModMaster/3rdparty/libmodbus/modbus-rtu.h b/Applications/qModMaster/3rdparty/libmodbus/modbus-rtu.h new file mode 100644 index 0000000..e2c9a6c --- /dev/null +++ b/Applications/qModMaster/3rdparty/libmodbus/modbus-rtu.h @@ -0,0 +1,43 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_RTU_H +#define MODBUS_RTU_H + +#include "modbus.h" + +MODBUS_BEGIN_DECLS + +/* Modbus_Application_Protocol_V1_1b.pdf Chapter 4 Section 1 Page 5 + * RS232 / RS485 ADU = 253 bytes + slave (1 byte) + CRC (2 bytes) = 256 bytes + */ +#define MODBUS_RTU_MAX_ADU_LENGTH 256 + +//***Not part of libmodbus - rts param added for QModMaster***// +MODBUS_API modbus_t* modbus_new_rtu(const char *device, int baud, char parity, + int data_bit, int stop_bit, int rts); + +#define MODBUS_RTU_RS232 0 +#define MODBUS_RTU_RS485 1 + +MODBUS_API int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode); +MODBUS_API int modbus_rtu_get_serial_mode(modbus_t *ctx); + +#define MODBUS_RTU_RTS_NONE 0 +#define MODBUS_RTU_RTS_UP 1 +#define MODBUS_RTU_RTS_DOWN 2 + +MODBUS_API int modbus_rtu_set_rts(modbus_t *ctx, int mode); +MODBUS_API int modbus_rtu_get_rts(modbus_t *ctx); + +MODBUS_API int modbus_rtu_set_custom_rts(modbus_t *ctx, void (*set_rts) (modbus_t *ctx, int on)); + +MODBUS_API int modbus_rtu_set_rts_delay(modbus_t *ctx, int us); +MODBUS_API int modbus_rtu_get_rts_delay(modbus_t *ctx); + +MODBUS_END_DECLS + +#endif /* MODBUS_RTU_H */ diff --git a/Applications/qModMaster/3rdparty/libmodbus/modbus-tcp-private.h b/Applications/qModMaster/3rdparty/libmodbus/modbus-tcp-private.h new file mode 100644 index 0000000..55edf26 --- /dev/null +++ b/Applications/qModMaster/3rdparty/libmodbus/modbus-tcp-private.h @@ -0,0 +1,44 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_TCP_PRIVATE_H +#define MODBUS_TCP_PRIVATE_H + +#define _MODBUS_TCP_HEADER_LENGTH 7 +#define _MODBUS_TCP_PRESET_REQ_LENGTH 12 +#define _MODBUS_TCP_PRESET_RSP_LENGTH 8 + +#define _MODBUS_TCP_CHECKSUM_LENGTH 0 + +/* In both structures, the transaction ID must be placed on first position + to have a quick access not dependant of the TCP backend */ +typedef struct _modbus_tcp { + /* Extract from MODBUS Messaging on TCP/IP Implementation Guide V1.0b + (page 23/46): + The transaction identifier is used to associate the future response + with the request. This identifier is unique on each TCP connection. */ + uint16_t t_id; + /* TCP port */ + int port; + /* IP address */ + char ip[16]; +} modbus_tcp_t; + +#define _MODBUS_TCP_PI_NODE_LENGTH 1025 +#define _MODBUS_TCP_PI_SERVICE_LENGTH 32 + +typedef struct _modbus_tcp_pi { + /* Transaction ID */ + uint16_t t_id; + /* TCP port */ + int port; + /* Node */ + char node[_MODBUS_TCP_PI_NODE_LENGTH]; + /* Service */ + char service[_MODBUS_TCP_PI_SERVICE_LENGTH]; +} modbus_tcp_pi_t; + +#endif /* MODBUS_TCP_PRIVATE_H */ diff --git a/Applications/qModMaster/3rdparty/libmodbus/modbus-tcp.c b/Applications/qModMaster/3rdparty/libmodbus/modbus-tcp.c new file mode 100644 index 0000000..6528981 --- /dev/null +++ b/Applications/qModMaster/3rdparty/libmodbus/modbus-tcp.c @@ -0,0 +1,902 @@ +/* + * Copyright © 2001-2013 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include +#include +#include +#include +#ifndef _MSC_VER +#include +#endif +#include +#include + +#if defined(_WIN32) +# define OS_WIN32 +/* ws2_32.dll has getaddrinfo and freeaddrinfo on Windows XP and later. + * minwg32 headers check WINVER before allowing the use of these */ +# ifndef WINVER +# define WINVER 0x0501 +# endif +/* Already set in modbus-tcp.h but it seems order matters in VS2005 */ +# include +# include +# define SHUT_RDWR 2 +# define close closesocket +#else +# include +# include + +#if defined(__OpenBSD__) || (defined(__FreeBSD__) && __FreeBSD__ < 5) +# define OS_BSD +# include +#endif + +# include +# include +# include +# include +# include +#endif + +#if !defined(MSG_NOSIGNAL) +#define MSG_NOSIGNAL 0 +#endif + +#if defined(_AIX) && !defined(MSG_DONTWAIT) +#define MSG_DONTWAIT MSG_NONBLOCK +#endif + +#include "modbus-private.h" + +#include "modbus-tcp.h" +#include "modbus-tcp-private.h" + +#ifdef OS_WIN32 +static int _modbus_tcp_init_win32(void) +{ + /* Initialise Windows Socket API */ + WSADATA wsaData; + + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + fprintf(stderr, "WSAStartup() returned error code %d\n", + (unsigned int)GetLastError()); + errno = EIO; + return -1; + } + return 0; +} +#endif + +static int _modbus_set_slave(modbus_t *ctx, int slave) +{ + /* Broadcast address is 0 (MODBUS_BROADCAST_ADDRESS) */ + //***changed for QModMaster : <= 247***// + if (slave >= 0 && slave <= 255) { + ctx->slave = slave; + } else if (slave == MODBUS_TCP_SLAVE) { + /* The special value MODBUS_TCP_SLAVE (0xFF) can be used in TCP mode to + * restore the default value. */ + ctx->slave = slave; + } else { + errno = EINVAL; + return -1; + } + + return 0; +} + +/* Builds a TCP request header */ +static int _modbus_tcp_build_request_basis(modbus_t *ctx, int function, + int addr, int nb, + uint8_t *req) +{ + modbus_tcp_t *ctx_tcp = ctx->backend_data; + + /* Increase transaction ID */ + if (ctx_tcp->t_id < UINT16_MAX) + ctx_tcp->t_id++; + else + ctx_tcp->t_id = 0; + req[0] = ctx_tcp->t_id >> 8; + req[1] = ctx_tcp->t_id & 0x00ff; + + /* Protocol Modbus */ + req[2] = 0; + req[3] = 0; + + /* Length will be defined later by set_req_length_tcp at offsets 4 + and 5 */ + + req[6] = ctx->slave; + req[7] = function; + req[8] = addr >> 8; + req[9] = addr & 0x00ff; + req[10] = nb >> 8; + req[11] = nb & 0x00ff; + + return _MODBUS_TCP_PRESET_REQ_LENGTH; +} + +/* Builds a TCP response header */ +static int _modbus_tcp_build_response_basis(sft_t *sft, uint8_t *rsp) +{ + /* Extract from MODBUS Messaging on TCP/IP Implementation + Guide V1.0b (page 23/46): + The transaction identifier is used to associate the future + response with the request. */ + rsp[0] = sft->t_id >> 8; + rsp[1] = sft->t_id & 0x00ff; + + /* Protocol Modbus */ + rsp[2] = 0; + rsp[3] = 0; + + /* Length will be set later by send_msg (4 and 5) */ + + /* The slave ID is copied from the indication */ + rsp[6] = sft->slave; + rsp[7] = sft->function; + + return _MODBUS_TCP_PRESET_RSP_LENGTH; +} + + +static int _modbus_tcp_prepare_response_tid(const uint8_t *req, int *req_length) +{ + return (req[0] << 8) + req[1]; +} + +static int _modbus_tcp_send_msg_pre(uint8_t *req, int req_length) +{ + /* Substract the header length to the message length */ + int mbap_length = req_length - 6; + + req[4] = mbap_length >> 8; + req[5] = mbap_length & 0x00FF; + + return req_length; +} + +static ssize_t _modbus_tcp_send(modbus_t *ctx, const uint8_t *req, int req_length) +{ + /* MSG_NOSIGNAL + Requests not to send SIGPIPE on errors on stream oriented + sockets when the other end breaks the connection. The EPIPE + error is still returned. */ + return send(ctx->s, (const char *)req, req_length, MSG_NOSIGNAL); +} + +static int _modbus_tcp_receive(modbus_t *ctx, uint8_t *req) { + return _modbus_receive_msg(ctx, req, MSG_INDICATION); +} + +static ssize_t _modbus_tcp_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length) { + return recv(ctx->s, (char *)rsp, rsp_length, 0); +} + +static int _modbus_tcp_check_integrity(modbus_t *ctx, uint8_t *msg, const int msg_length) +{ + return msg_length; +} + +static int _modbus_tcp_pre_check_confirmation(modbus_t *ctx, const uint8_t *req, + const uint8_t *rsp, int rsp_length) +{ + /* Check transaction ID */ + if (req[0] != rsp[0] || req[1] != rsp[1]) { + if (ctx->debug) { + fprintf(stderr, "Invalid transaction ID received 0x%X (not 0x%X)\n", + (rsp[0] << 8) + rsp[1], (req[0] << 8) + req[1]); + } + errno = EMBBADDATA; + return -1; + } + + /* Check protocol ID */ + if (rsp[2] != 0x0 && rsp[3] != 0x0) { + if (ctx->debug) { + fprintf(stderr, "Invalid protocol ID received 0x%X (not 0x0)\n", + (rsp[2] << 8) + rsp[3]); + } + errno = EMBBADDATA; + return -1; + } + + return 0; +} + +static int _modbus_tcp_set_ipv4_options(int s) +{ + int rc; + int option; + + /* Set the TCP no delay flag */ + /* SOL_TCP = IPPROTO_TCP */ + option = 1; + rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, + (const void *)&option, sizeof(int)); + if (rc == -1) { + return -1; + } + + /* If the OS does not offer SOCK_NONBLOCK, fall back to setting FIONBIO to + * make sockets non-blocking */ + /* Do not care about the return value, this is optional */ +#if !defined(SOCK_NONBLOCK) && defined(FIONBIO) +#ifdef OS_WIN32 + { + /* Setting FIONBIO expects an unsigned long according to MSDN */ + u_long loption = 1; + ioctlsocket(s, FIONBIO, &loption); + } +#else + option = 1; + ioctl(s, FIONBIO, &option); +#endif +#endif + +#ifndef OS_WIN32 + /** + * Cygwin defines IPTOS_LOWDELAY but can't handle that flag so it's + * necessary to workaround that problem. + **/ + /* Set the IP low delay option */ + option = IPTOS_LOWDELAY; + rc = setsockopt(s, IPPROTO_IP, IP_TOS, + (const void *)&option, sizeof(int)); + if (rc == -1) { + return -1; + } +#endif + + return 0; +} + +static int _connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen, + const struct timeval *ro_tv) +{ + int rc = connect(sockfd, addr, addrlen); + +#ifdef OS_WIN32 + int wsaError = 0; + if (rc == -1) { + wsaError = WSAGetLastError(); + } + + if (wsaError == WSAEWOULDBLOCK || wsaError == WSAEINPROGRESS) { +#else + if (rc == -1 && errno == EINPROGRESS) { +#endif + fd_set wset; + int optval; + socklen_t optlen = sizeof(optval); + struct timeval tv = *ro_tv; + + /* Wait to be available in writing */ + FD_ZERO(&wset); + FD_SET(sockfd, &wset); + rc = select(sockfd + 1, NULL, &wset, NULL, &tv); + if (rc <= 0) { + /* Timeout or fail */ + return -1; + } + + /* The connection is established if SO_ERROR and optval are set to 0 */ + rc = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&optval, &optlen); + if (rc == 0 && optval == 0) { + return 0; + } else { + errno = ECONNREFUSED; + return -1; + } + } + return rc; +} + +/* Establishes a modbus TCP connection with a Modbus server. */ +static int _modbus_tcp_connect(modbus_t *ctx) +{ + int rc; + /* Specialized version of sockaddr for Internet socket address (same size) */ + struct sockaddr_in addr; + modbus_tcp_t *ctx_tcp = ctx->backend_data; + int flags = SOCK_STREAM; + +#ifdef OS_WIN32 + if (_modbus_tcp_init_win32() == -1) { + return -1; + } +#endif + +#ifdef SOCK_CLOEXEC + flags |= SOCK_CLOEXEC; +#endif + +#ifdef SOCK_NONBLOCK + flags |= SOCK_NONBLOCK; +#endif + + ctx->s = socket(PF_INET, flags, 0); + if (ctx->s == -1) { + return -1; + } + + rc = _modbus_tcp_set_ipv4_options(ctx->s); + if (rc == -1) { + close(ctx->s); + ctx->s = -1; + return -1; + } + + if (ctx->debug) { + printf("Connecting to %s:%d\n", ctx_tcp->ip, ctx_tcp->port); + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(ctx_tcp->port); + addr.sin_addr.s_addr = inet_addr(ctx_tcp->ip); + rc = _connect(ctx->s, (struct sockaddr *)&addr, sizeof(addr), &ctx->response_timeout); + if (rc == -1) { + close(ctx->s); + ctx->s = -1; + return -1; + } + + return 0; +} + +/* Establishes a modbus TCP PI connection with a Modbus server. */ +static int _modbus_tcp_pi_connect(modbus_t *ctx) +{ + int rc; + struct addrinfo *ai_list; + struct addrinfo *ai_ptr; + struct addrinfo ai_hints; + modbus_tcp_pi_t *ctx_tcp_pi = ctx->backend_data; + +#ifdef OS_WIN32 + if (_modbus_tcp_init_win32() == -1) { + return -1; + } +#endif + + memset(&ai_hints, 0, sizeof(ai_hints)); +#ifdef AI_ADDRCONFIG + ai_hints.ai_flags |= AI_ADDRCONFIG; +#endif + ai_hints.ai_family = AF_UNSPEC; + ai_hints.ai_socktype = SOCK_STREAM; + ai_hints.ai_addr = NULL; + ai_hints.ai_canonname = NULL; + ai_hints.ai_next = NULL; + + ai_list = NULL; + rc = getaddrinfo(ctx_tcp_pi->node, ctx_tcp_pi->service, + &ai_hints, &ai_list); + if (rc != 0) { + if (ctx->debug) { + fprintf(stderr, "Error returned by getaddrinfo: %s\n", gai_strerror(rc)); + } + errno = ECONNREFUSED; + return -1; + } + + for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) { + int flags = ai_ptr->ai_socktype; + int s; + +#ifdef SOCK_CLOEXEC + flags |= SOCK_CLOEXEC; +#endif + +#ifdef SOCK_NONBLOCK + flags |= SOCK_NONBLOCK; +#endif + + s = socket(ai_ptr->ai_family, flags, ai_ptr->ai_protocol); + if (s < 0) + continue; + + if (ai_ptr->ai_family == AF_INET) + _modbus_tcp_set_ipv4_options(s); + + if (ctx->debug) { + printf("Connecting to [%s]:%s\n", ctx_tcp_pi->node, ctx_tcp_pi->service); + } + + rc = _connect(s, ai_ptr->ai_addr, ai_ptr->ai_addrlen, &ctx->response_timeout); + if (rc == -1) { + close(s); + continue; + } + + ctx->s = s; + break; + } + + freeaddrinfo(ai_list); + + if (ctx->s < 0) { + return -1; + } + + return 0; +} + +/* Closes the network connection and socket in TCP mode */ +static void _modbus_tcp_close(modbus_t *ctx) +{ + if (ctx->s != -1) { + shutdown(ctx->s, SHUT_RDWR); + close(ctx->s); + ctx->s = -1; + } +} + +static int _modbus_tcp_flush(modbus_t *ctx) +{ + int rc; + int rc_sum = 0; + + do { + /* Extract the garbage from the socket */ + char devnull[MODBUS_TCP_MAX_ADU_LENGTH]; +#ifndef OS_WIN32 + rc = recv(ctx->s, devnull, MODBUS_TCP_MAX_ADU_LENGTH, MSG_DONTWAIT); +#else + /* On Win32, it's a bit more complicated to not wait */ + fd_set rset; + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 0; + FD_ZERO(&rset); + FD_SET(ctx->s, &rset); + rc = select(ctx->s+1, &rset, NULL, NULL, &tv); + if (rc == -1) { + return -1; + } + + if (rc == 1) { + /* There is data to flush */ + rc = recv(ctx->s, devnull, MODBUS_TCP_MAX_ADU_LENGTH, 0); + } +#endif + if (rc > 0) { + rc_sum += rc; + } + } while (rc == MODBUS_TCP_MAX_ADU_LENGTH); + + return rc_sum; +} + +/* Listens for any request from one or many modbus masters in TCP */ +int modbus_tcp_listen(modbus_t *ctx, int nb_connection) +{ + int new_s; + int enable; + struct sockaddr_in addr; + modbus_tcp_t *ctx_tcp; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + ctx_tcp = ctx->backend_data; + +#ifdef OS_WIN32 + if (_modbus_tcp_init_win32() == -1) { + return -1; + } +#endif + + new_s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (new_s == -1) { + return -1; + } + + enable = 1; + if (setsockopt(new_s, SOL_SOCKET, SO_REUSEADDR, + (char *)&enable, sizeof(enable)) == -1) { + close(new_s); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + /* If the modbus port is < to 1024, we need the setuid root. */ + addr.sin_port = htons(ctx_tcp->port); + if (ctx_tcp->ip[0] == '0') { + /* Listen any addresses */ + addr.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + /* Listen only specified IP address */ + addr.sin_addr.s_addr = inet_addr(ctx_tcp->ip); + } + if (bind(new_s, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + close(new_s); + return -1; + } + + if (listen(new_s, nb_connection) == -1) { + close(new_s); + return -1; + } + + return new_s; +} + +int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection) +{ + int rc; + struct addrinfo *ai_list; + struct addrinfo *ai_ptr; + struct addrinfo ai_hints; + const char *node; + const char *service; + int new_s; + modbus_tcp_pi_t *ctx_tcp_pi; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + ctx_tcp_pi = ctx->backend_data; + +#ifdef OS_WIN32 + if (_modbus_tcp_init_win32() == -1) { + return -1; + } +#endif + + if (ctx_tcp_pi->node[0] == 0) { + node = NULL; /* == any */ + } else { + node = ctx_tcp_pi->node; + } + + if (ctx_tcp_pi->service[0] == 0) { + service = "502"; + } else { + service = ctx_tcp_pi->service; + } + + memset(&ai_hints, 0, sizeof (ai_hints)); + /* If node is not NULL, than the AI_PASSIVE flag is ignored. */ + ai_hints.ai_flags |= AI_PASSIVE; +#ifdef AI_ADDRCONFIG + ai_hints.ai_flags |= AI_ADDRCONFIG; +#endif + ai_hints.ai_family = AF_UNSPEC; + ai_hints.ai_socktype = SOCK_STREAM; + ai_hints.ai_addr = NULL; + ai_hints.ai_canonname = NULL; + ai_hints.ai_next = NULL; + + ai_list = NULL; + rc = getaddrinfo(node, service, &ai_hints, &ai_list); + if (rc != 0) { + if (ctx->debug) { + fprintf(stderr, "Error returned by getaddrinfo: %s\n", gai_strerror(rc)); + } + errno = ECONNREFUSED; + return -1; + } + + new_s = -1; + for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) { + int s; + + s = socket(ai_ptr->ai_family, ai_ptr->ai_socktype, + ai_ptr->ai_protocol); + if (s < 0) { + if (ctx->debug) { + perror("socket"); + } + continue; + } else { + int enable = 1; + rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (void *)&enable, sizeof (enable)); + if (rc != 0) { + close(s); + if (ctx->debug) { + perror("setsockopt"); + } + continue; + } + } + + rc = bind(s, ai_ptr->ai_addr, ai_ptr->ai_addrlen); + if (rc != 0) { + close(s); + if (ctx->debug) { + perror("bind"); + } + continue; + } + + rc = listen(s, nb_connection); + if (rc != 0) { + close(s); + if (ctx->debug) { + perror("listen"); + } + continue; + } + + new_s = s; + break; + } + freeaddrinfo(ai_list); + + if (new_s < 0) { + return -1; + } + + return new_s; +} + +int modbus_tcp_accept(modbus_t *ctx, int *s) +{ + struct sockaddr_in addr; + socklen_t addrlen; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + addrlen = sizeof(addr); +#ifdef HAVE_ACCEPT4 + /* Inherit socket flags and use accept4 call */ + ctx->s = accept4(*s, (struct sockaddr *)&addr, &addrlen, SOCK_CLOEXEC); +#else + ctx->s = accept(*s, (struct sockaddr *)&addr, &addrlen); +#endif + + if (ctx->s == -1) { + close(*s); + *s = -1; + return -1; + } + + if (ctx->debug) { + printf("The client connection from %s is accepted\n", + inet_ntoa(addr.sin_addr)); + } + + return ctx->s; +} + +int modbus_tcp_pi_accept(modbus_t *ctx, int *s) +{ + struct sockaddr_storage addr; + socklen_t addrlen; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + addrlen = sizeof(addr); +#ifdef HAVE_ACCEPT4 + /* Inherit socket flags and use accept4 call */ + ctx->s = accept4(*s, (struct sockaddr *)&addr, &addrlen, SOCK_CLOEXEC); +#else + ctx->s = accept(*s, (struct sockaddr *)&addr, &addrlen); +#endif + if (ctx->s == -1) { + close(*s); + *s = -1; + } + + if (ctx->debug) { + printf("The client connection is accepted.\n"); + } + + return ctx->s; +} + +static int _modbus_tcp_select(modbus_t *ctx, fd_set *rset, struct timeval *tv, int length_to_read) +{ + int s_rc; + while ((s_rc = select(ctx->s+1, rset, NULL, NULL, tv)) == -1) { + if (errno == EINTR) { + if (ctx->debug) { + fprintf(stderr, "A non blocked signal was caught\n"); + } + /* Necessary after an error */ + FD_ZERO(rset); + FD_SET(ctx->s, rset); + } else { + return -1; + } + } + + if (s_rc == 0) { + errno = ETIMEDOUT; + return -1; + } + + return s_rc; +} + +static void _modbus_tcp_free(modbus_t *ctx) { + free(ctx->backend_data); + free(ctx); +} + +const modbus_backend_t _modbus_tcp_backend = { + _MODBUS_BACKEND_TYPE_TCP, + _MODBUS_TCP_HEADER_LENGTH, + _MODBUS_TCP_CHECKSUM_LENGTH, + MODBUS_TCP_MAX_ADU_LENGTH, + _modbus_set_slave, + _modbus_tcp_build_request_basis, + _modbus_tcp_build_response_basis, + _modbus_tcp_prepare_response_tid, + _modbus_tcp_send_msg_pre, + _modbus_tcp_send, + _modbus_tcp_receive, + _modbus_tcp_recv, + _modbus_tcp_check_integrity, + _modbus_tcp_pre_check_confirmation, + _modbus_tcp_connect, + _modbus_tcp_close, + _modbus_tcp_flush, + _modbus_tcp_select, + _modbus_tcp_free +}; + + +const modbus_backend_t _modbus_tcp_pi_backend = { + _MODBUS_BACKEND_TYPE_TCP, + _MODBUS_TCP_HEADER_LENGTH, + _MODBUS_TCP_CHECKSUM_LENGTH, + MODBUS_TCP_MAX_ADU_LENGTH, + _modbus_set_slave, + _modbus_tcp_build_request_basis, + _modbus_tcp_build_response_basis, + _modbus_tcp_prepare_response_tid, + _modbus_tcp_send_msg_pre, + _modbus_tcp_send, + _modbus_tcp_receive, + _modbus_tcp_recv, + _modbus_tcp_check_integrity, + _modbus_tcp_pre_check_confirmation, + _modbus_tcp_pi_connect, + _modbus_tcp_close, + _modbus_tcp_flush, + _modbus_tcp_select, + _modbus_tcp_free +}; + +modbus_t* modbus_new_tcp(const char *ip, int port) +{ + modbus_t *ctx; + modbus_tcp_t *ctx_tcp; + size_t dest_size; + size_t ret_size; + +#if defined(OS_BSD) + /* MSG_NOSIGNAL is unsupported on *BSD so we install an ignore + handler for SIGPIPE. */ + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &sa, NULL) < 0) { + /* The debug flag can't be set here... */ + fprintf(stderr, "Coud not install SIGPIPE handler.\n"); + return NULL; + } +#endif + + ctx = (modbus_t *)malloc(sizeof(modbus_t)); + _modbus_init_common(ctx); + + /* Could be changed after to reach a remote serial Modbus device */ + ctx->slave = MODBUS_TCP_SLAVE; + + ctx->backend = &_modbus_tcp_backend; + + ctx->backend_data = (modbus_tcp_t *)malloc(sizeof(modbus_tcp_t)); + ctx_tcp = (modbus_tcp_t *)ctx->backend_data; + + if (ip != NULL) { + dest_size = sizeof(char) * 16; + ret_size = strlcpy(ctx_tcp->ip, ip, dest_size); + if (ret_size == 0) { + fprintf(stderr, "The IP string is empty\n"); + modbus_free(ctx); + errno = EINVAL; + return NULL; + } + + if (ret_size >= dest_size) { + fprintf(stderr, "The IP string has been truncated\n"); + modbus_free(ctx); + errno = EINVAL; + return NULL; + } + } else { + ctx_tcp->ip[0] = '0'; + } + ctx_tcp->port = port; + ctx_tcp->t_id = 0; + + return ctx; +} + + +modbus_t* modbus_new_tcp_pi(const char *node, const char *service) +{ + modbus_t *ctx; + modbus_tcp_pi_t *ctx_tcp_pi; + size_t dest_size; + size_t ret_size; + + ctx = (modbus_t *)malloc(sizeof(modbus_t)); + _modbus_init_common(ctx); + + /* Could be changed after to reach a remote serial Modbus device */ + ctx->slave = MODBUS_TCP_SLAVE; + + ctx->backend = &_modbus_tcp_pi_backend; + + ctx->backend_data = (modbus_tcp_pi_t *)malloc(sizeof(modbus_tcp_pi_t)); + ctx_tcp_pi = (modbus_tcp_pi_t *)ctx->backend_data; + + if (node == NULL) { + /* The node argument can be empty to indicate any hosts */ + ctx_tcp_pi->node[0] = 0; + } else { + dest_size = sizeof(char) * _MODBUS_TCP_PI_NODE_LENGTH; + ret_size = strlcpy(ctx_tcp_pi->node, node, dest_size); + if (ret_size == 0) { + fprintf(stderr, "The node string is empty\n"); + modbus_free(ctx); + errno = EINVAL; + return NULL; + } + + if (ret_size >= dest_size) { + fprintf(stderr, "The node string has been truncated\n"); + modbus_free(ctx); + errno = EINVAL; + return NULL; + } + } + + if (service != NULL) { + dest_size = sizeof(char) * _MODBUS_TCP_PI_SERVICE_LENGTH; + ret_size = strlcpy(ctx_tcp_pi->service, service, dest_size); + } else { + /* Empty service is not allowed, error catched below. */ + ret_size = 0; + } + + if (ret_size == 0) { + fprintf(stderr, "The service string is empty\n"); + modbus_free(ctx); + errno = EINVAL; + return NULL; + } + + if (ret_size >= dest_size) { + fprintf(stderr, "The service string has been truncated\n"); + modbus_free(ctx); + errno = EINVAL; + return NULL; + } + + ctx_tcp_pi->t_id = 0; + + return ctx; +} diff --git a/Applications/qModMaster/3rdparty/libmodbus/modbus-tcp.h b/Applications/qModMaster/3rdparty/libmodbus/modbus-tcp.h new file mode 100644 index 0000000..abaef27 --- /dev/null +++ b/Applications/qModMaster/3rdparty/libmodbus/modbus-tcp.h @@ -0,0 +1,52 @@ +/* + * Copyright © 2001-2010 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_TCP_H +#define MODBUS_TCP_H + +#include "modbus.h" + +MODBUS_BEGIN_DECLS + +#if defined(_WIN32) && !defined(__CYGWIN__) +/* Win32 with MinGW, supplement to */ +#include +#if !defined(ECONNRESET) +#define ECONNRESET WSAECONNRESET +#endif +#if !defined(ECONNREFUSED) +#define ECONNREFUSED WSAECONNREFUSED +#endif +#if !defined(ETIMEDOUT) +#define ETIMEDOUT WSAETIMEDOUT +#endif +#if !defined(ENOPROTOOPT) +#define ENOPROTOOPT WSAENOPROTOOPT +#endif +#if !defined(EINPROGRESS) +#define EINPROGRESS WSAEINPROGRESS +#endif +#endif + +#define MODBUS_TCP_DEFAULT_PORT 502 +#define MODBUS_TCP_SLAVE 0xFF + +/* Modbus_Application_Protocol_V1_1b.pdf Chapter 4 Section 1 Page 5 + * TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes + */ +#define MODBUS_TCP_MAX_ADU_LENGTH 260 + +MODBUS_API modbus_t* modbus_new_tcp(const char *ip_address, int port); +MODBUS_API int modbus_tcp_listen(modbus_t *ctx, int nb_connection); +MODBUS_API int modbus_tcp_accept(modbus_t *ctx, int *s); + +MODBUS_API modbus_t* modbus_new_tcp_pi(const char *node, const char *service); +MODBUS_API int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection); +MODBUS_API int modbus_tcp_pi_accept(modbus_t *ctx, int *s); + +MODBUS_END_DECLS + +#endif /* MODBUS_TCP_H */ diff --git a/Applications/qModMaster/3rdparty/libmodbus/modbus-version.h b/Applications/qModMaster/3rdparty/libmodbus/modbus-version.h new file mode 100644 index 0000000..a2bbfe4 --- /dev/null +++ b/Applications/qModMaster/3rdparty/libmodbus/modbus-version.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2010-2014 Stéphane Raimbault + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MODBUS_VERSION_H +#define MODBUS_VERSION_H + +/* The major version, (1, if %LIBMODBUS_VERSION is 1.2.3) */ +#define LIBMODBUS_VERSION_MAJOR (3) + +/* The minor version (2, if %LIBMODBUS_VERSION is 1.2.3) */ +#define LIBMODBUS_VERSION_MINOR (1) + +/* The micro version (3, if %LIBMODBUS_VERSION is 1.2.3) */ +#define LIBMODBUS_VERSION_MICRO (4) + +/* The full version, like 1.2.3 */ +#define LIBMODBUS_VERSION 3.1.4 + +/* The full version, in string form (suited for string concatenation) + */ +#define LIBMODBUS_VERSION_STRING "3.1.4" + +/* Numerically encoded version, like 0x010203 */ +#define LIBMODBUS_VERSION_HEX ((LIBMODBUS_VERSION_MAJOR << 24) | \ + (LIBMODBUS_VERSION_MINOR << 16) | \ + (LIBMODBUS_VERSION_MICRO << 8)) + +/* Evaluates to True if the version is greater than @major, @minor and @micro + */ +#define LIBMODBUS_VERSION_CHECK(major,minor,micro) \ + (LIBMODBUS_VERSION_MAJOR > (major) || \ + (LIBMODBUS_VERSION_MAJOR == (major) && \ + LIBMODBUS_VERSION_MINOR > (minor)) || \ + (LIBMODBUS_VERSION_MAJOR == (major) && \ + LIBMODBUS_VERSION_MINOR == (minor) && \ + LIBMODBUS_VERSION_MICRO >= (micro))) + +#endif /* MODBUS_VERSION_H */ diff --git a/Applications/qModMaster/3rdparty/libmodbus/modbus.c b/Applications/qModMaster/3rdparty/libmodbus/modbus.c new file mode 100644 index 0000000..0e8809b --- /dev/null +++ b/Applications/qModMaster/3rdparty/libmodbus/modbus.c @@ -0,0 +1,1867 @@ +/* + * Copyright © 2001-2011 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + * + * This library implements the Modbus protocol. + * http://libmodbus.org/ + */ + +#include +#include +#include +#include +#include +#include +#include +#ifndef _MSC_VER +#include +#endif + +#include + +#include "modbus.h" +#include "modbus-private.h" + +/* Internal use */ +#define MSG_LENGTH_UNDEFINED -1 + +/* Exported version */ +const unsigned int libmodbus_version_major = LIBMODBUS_VERSION_MAJOR; +const unsigned int libmodbus_version_minor = LIBMODBUS_VERSION_MINOR; +const unsigned int libmodbus_version_micro = LIBMODBUS_VERSION_MICRO; + +/* Max between RTU and TCP max adu length (so TCP) */ +#define MAX_MESSAGE_LENGTH 260 + +/* 3 steps are used to parse the query */ +typedef enum { + _STEP_FUNCTION, + _STEP_META, + _STEP_DATA +} _step_t; + +const char *modbus_strerror(int errnum) { + switch (errnum) { + case EMBXILFUN: + return "Illegal function"; + case EMBXILADD: + return "Illegal data address"; + case EMBXILVAL: + return "Illegal data value"; + case EMBXSFAIL: + return "Slave device or server failure"; + case EMBXACK: + return "Acknowledge"; + case EMBXSBUSY: + return "Slave device or server is busy"; + case EMBXNACK: + return "Negative acknowledge"; + case EMBXMEMPAR: + return "Memory parity error"; + case EMBXGPATH: + return "Gateway path unavailable"; + case EMBXGTAR: + return "Target device failed to respond"; + case EMBBADCRC: + return "Invalid CRC"; + case EMBBADDATA: + return "Invalid data"; + case EMBBADEXC: + return "Invalid exception code"; + case EMBMDATA: + return "Too many data"; + case EMBBADSLAVE: + return "Response not from requested slave"; + default: + return strerror(errnum); + } +} + +void _error_print(modbus_t *ctx, const char *context) +{ + if (ctx->debug) { + fprintf(stderr, "ERROR %s", modbus_strerror(errno)); + if (context != NULL) { + fprintf(stderr, ": %s\n", context); + } else { + fprintf(stderr, "\n"); + } + } +} + +static void _sleep_response_timeout(modbus_t *ctx) +{ + /* Response timeout is always positive */ +#ifdef _WIN32 + /* usleep doesn't exist on Windows */ + Sleep((ctx->response_timeout.tv_sec * 1000) + + (ctx->response_timeout.tv_usec / 1000)); +#else + /* usleep source code */ + struct timespec request, remaining; + request.tv_sec = ctx->response_timeout.tv_sec; + request.tv_nsec = ((long int)ctx->response_timeout.tv_usec) * 1000; + while (nanosleep(&request, &remaining) == -1 && errno == EINTR) { + request = remaining; + } +#endif +} + +int modbus_flush(modbus_t *ctx) +{ + int rc; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + rc = ctx->backend->flush(ctx); + if (rc != -1 && ctx->debug) { + /* Not all backends are able to return the number of bytes flushed */ + printf("Bytes flushed (%d)\n", rc); + } + return rc; +} + +/* Computes the length of the expected response */ +static unsigned int compute_response_length_from_request(modbus_t *ctx, uint8_t *req) +{ + int length; + const int offset = ctx->backend->header_length; + + switch (req[offset]) { + case MODBUS_FC_READ_COILS: + case MODBUS_FC_READ_DISCRETE_INPUTS: { + /* Header + nb values (code from write_bits) */ + int nb = (req[offset + 3] << 8) | req[offset + 4]; + length = 2 + (nb / 8) + ((nb % 8) ? 1 : 0); + } + break; + case MODBUS_FC_WRITE_AND_READ_REGISTERS: + case MODBUS_FC_READ_HOLDING_REGISTERS: + case MODBUS_FC_READ_INPUT_REGISTERS: + /* Header + 2 * nb values */ + length = 2 + 2 * (req[offset + 3] << 8 | req[offset + 4]); + break; + case MODBUS_FC_READ_EXCEPTION_STATUS: + length = 3; + break; + case MODBUS_FC_REPORT_SLAVE_ID: + /* The response is device specific (the header provides the + length) */ + return MSG_LENGTH_UNDEFINED; + case MODBUS_FC_MASK_WRITE_REGISTER: + length = 7; + break; + default: + length = 5; + } + + return offset + length + ctx->backend->checksum_length; +} + +/* Sends a request/response */ +static int send_msg(modbus_t *ctx, uint8_t *msg, int msg_length) +{ + int rc; + int i; + + msg_length = ctx->backend->send_msg_pre(msg, msg_length); + + if (ctx->debug) { + for (i = 0; i < msg_length; i++) + printf("[%.2X]", msg[i]); + printf("\n"); + } + + //***Not part of libmodbus - added for QModMaster***// + busMonitorRawRequestData(msg,msg_length); + + /* In recovery mode, the write command will be issued until to be + successful! Disabled by default. */ + do { + rc = ctx->backend->send(ctx, msg, msg_length); + if (rc == -1) { + _error_print(ctx, NULL); + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) { + int saved_errno = errno; + + if ((errno == EBADF || errno == ECONNRESET || errno == EPIPE)) { + modbus_close(ctx); + _sleep_response_timeout(ctx); + modbus_connect(ctx); + } else { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } + errno = saved_errno; + } + } + } while ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) && + rc == -1); + + if (rc > 0 && rc != msg_length) { + errno = EMBBADDATA; + return -1; + } + + return rc; +} + +int modbus_send_raw_request(modbus_t *ctx, uint8_t *raw_req, int raw_req_length) +{ + sft_t sft; + uint8_t req[MAX_MESSAGE_LENGTH]; + int req_length; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (raw_req_length < 2 || raw_req_length > (MODBUS_MAX_PDU_LENGTH + 1)) { + /* The raw request must contain function and slave at least and + must not be longer than the maximum pdu length plus the slave + address. */ + errno = EINVAL; + return -1; + } + + sft.slave = raw_req[0]; + sft.function = raw_req[1]; + /* The t_id is left to zero */ + sft.t_id = 0; + /* This response function only set the header so it's convenient here */ + req_length = ctx->backend->build_response_basis(&sft, req); + + if (raw_req_length > 2) { + /* Copy data after function code */ + memcpy(req + req_length, raw_req + 2, raw_req_length - 2); + req_length += raw_req_length - 2; + } + + return send_msg(ctx, req, req_length); +} + +/* + * ---------- Request Indication ---------- + * | Client | ---------------------->| Server | + * ---------- Confirmation Response ---------- + */ + +/* Computes the length to read after the function received */ +static uint8_t compute_meta_length_after_function(int function, + msg_type_t msg_type) +{ + int length; + + if (msg_type == MSG_INDICATION) { + if (function <= MODBUS_FC_WRITE_SINGLE_REGISTER) { + length = 4; + } else if (function == MODBUS_FC_WRITE_MULTIPLE_COILS || + function == MODBUS_FC_WRITE_MULTIPLE_REGISTERS) { + length = 5; + } else if (function == MODBUS_FC_MASK_WRITE_REGISTER) { + length = 6; + } else if (function == MODBUS_FC_WRITE_AND_READ_REGISTERS) { + length = 9; + } else { + /* MODBUS_FC_READ_EXCEPTION_STATUS, MODBUS_FC_REPORT_SLAVE_ID */ + length = 0; + } + } else { + /* MSG_CONFIRMATION */ + switch (function) { + case MODBUS_FC_WRITE_SINGLE_COIL: + case MODBUS_FC_WRITE_SINGLE_REGISTER: + case MODBUS_FC_WRITE_MULTIPLE_COILS: + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + length = 4; + break; + case MODBUS_FC_MASK_WRITE_REGISTER: + length = 6; + break; + default: + length = 1; + } + } + + return length; +} + +/* Computes the length to read after the meta information (address, count, etc) */ +static int compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, + msg_type_t msg_type) +{ + int function = msg[ctx->backend->header_length]; + int length; + + if (msg_type == MSG_INDICATION) { + switch (function) { + case MODBUS_FC_WRITE_MULTIPLE_COILS: + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + length = msg[ctx->backend->header_length + 5]; + break; + case MODBUS_FC_WRITE_AND_READ_REGISTERS: + length = msg[ctx->backend->header_length + 9]; + break; + default: + length = 0; + } + } else { + /* MSG_CONFIRMATION */ + if (function <= MODBUS_FC_READ_INPUT_REGISTERS || + function == MODBUS_FC_REPORT_SLAVE_ID || + function == MODBUS_FC_WRITE_AND_READ_REGISTERS) { + length = msg[ctx->backend->header_length + 1]; + } else { + length = 0; + } + } + + length += ctx->backend->checksum_length; + + return length; +} + + +/* Waits a response from a modbus server or a request from a modbus client. + This function blocks if there is no replies (3 timeouts). + + The function shall return the number of received characters and the received + message in an array of uint8_t if successful. Otherwise it shall return -1 + and errno is set to one of the values defined below: + - ECONNRESET + - EMBBADDATA + - EMBUNKEXC + - ETIMEDOUT + - read() or recv() error codes +*/ + +int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) +{ + int rc; + fd_set rset; + struct timeval tv; + struct timeval *p_tv; + int length_to_read; + int msg_length = 0; + _step_t step; + + if (ctx->debug) { + if (msg_type == MSG_INDICATION) { + printf("Waiting for a indication...\n"); + } else { + printf("Waiting for a confirmation...\n"); + } + } + + /* Add a file descriptor to the set */ + FD_ZERO(&rset); + FD_SET(ctx->s, &rset); + + /* We need to analyse the message step by step. At the first step, we want + * to reach the function code because all packets contain this + * information. */ + step = _STEP_FUNCTION; + length_to_read = ctx->backend->header_length + 1; + + if (msg_type == MSG_INDICATION) { + /* Wait for a message, we don't know when the message will be + * received */ + p_tv = NULL; + } else { + tv.tv_sec = ctx->response_timeout.tv_sec; + tv.tv_usec = ctx->response_timeout.tv_usec; + p_tv = &tv; + } + + while (length_to_read != 0) { + rc = ctx->backend->select(ctx, &rset, p_tv, length_to_read); + if (rc == -1) { + _error_print(ctx, "select"); + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) { + int saved_errno = errno; + + if (errno == ETIMEDOUT) { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } else if (errno == EBADF) { + modbus_close(ctx); + modbus_connect(ctx); + } + errno = saved_errno; + } + return -1; + } + + rc = ctx->backend->recv(ctx, msg + msg_length, length_to_read); + if (rc == 0) { + errno = ECONNRESET; + rc = -1; + } + + if (rc == -1) { + _error_print(ctx, "read"); + if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) && + (errno == ECONNRESET || errno == ECONNREFUSED || + errno == EBADF)) { + int saved_errno = errno; + modbus_close(ctx); + modbus_connect(ctx); + /* Could be removed by previous calls */ + errno = saved_errno; + } + return -1; + } + + /* Display the hex code of each character received */ + if (ctx->debug) { + int i; + for (i=0; i < rc; i++) + printf("<%.2X>", msg[msg_length + i]); + } + + /* Sums bytes received */ + msg_length += rc; + /* Computes remaining bytes */ + length_to_read -= rc; + + if (length_to_read == 0) { + switch (step) { + case _STEP_FUNCTION: + /* Function code position */ + length_to_read = compute_meta_length_after_function( + msg[ctx->backend->header_length], + msg_type); + if (length_to_read != 0) { + step = _STEP_META; + break; + } /* else switches straight to the next step */ + case _STEP_META: + length_to_read = compute_data_length_after_meta( + ctx, msg, msg_type); + if ((msg_length + length_to_read) > (int)ctx->backend->max_adu_length) { + errno = EMBBADDATA; + _error_print(ctx, "too many data"); + return -1; + } + step = _STEP_DATA; + break; + default: + break; + } + } + + if (length_to_read > 0 && + (ctx->byte_timeout.tv_sec > 0 || ctx->byte_timeout.tv_usec > 0)) { + /* If there is no character in the buffer, the allowed timeout + interval between two consecutive bytes is defined by + byte_timeout */ + tv.tv_sec = ctx->byte_timeout.tv_sec; + tv.tv_usec = ctx->byte_timeout.tv_usec; + p_tv = &tv; + } + /* else timeout isn't set again, the full response must be read before + expiration of response timeout (for CONFIRMATION only) */ + } + + if (ctx->debug) + printf("\n"); + + //***Not part of libmodbus - added for QModMaster***// + busMonitorRawResponseData(msg, msg_length); + + return ctx->backend->check_integrity(ctx, msg, msg_length); +} + +/* Receive the request from a modbus master */ +int modbus_receive(modbus_t *ctx, uint8_t *req) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return ctx->backend->receive(ctx, req); +} + +/* Receives the confirmation. + + The function shall store the read response in rsp and return the number of + values (bits or words). Otherwise, its shall return -1 and errno is set. + + The function doesn't check the confirmation is the expected response to the + initial request. +*/ +int modbus_receive_confirmation(modbus_t *ctx, uint8_t *rsp) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); +} + +static int check_confirmation(modbus_t *ctx, uint8_t *req, + uint8_t *rsp, int rsp_length) +{ + int rc; + int rsp_length_computed; + const int offset = ctx->backend->header_length; + const int function = rsp[offset]; + + if (ctx->backend->pre_check_confirmation) { + rc = ctx->backend->pre_check_confirmation(ctx, req, rsp, rsp_length); + if (rc == -1) { + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } + return -1; + } + } + + rsp_length_computed = compute_response_length_from_request(ctx, req); + + /* Exception code */ + if (function >= 0x80) { + if (rsp_length == (offset + 2 + (int)ctx->backend->checksum_length) && + req[offset] == (rsp[offset] - 0x80)) { + /* Valid exception code received */ + + int exception_code = rsp[offset + 1]; + if (exception_code < MODBUS_EXCEPTION_MAX) { + errno = MODBUS_ENOBASE + exception_code; + } else { + errno = EMBBADEXC; + } + _error_print(ctx, NULL); + return -1; + } else { + errno = EMBBADEXC; + _error_print(ctx, NULL); + return -1; + } + } + + /* Check length */ + if ((rsp_length == rsp_length_computed || + rsp_length_computed == MSG_LENGTH_UNDEFINED) && + function < 0x80) { + int req_nb_value; + int rsp_nb_value; + + /* Check function code */ + if (function != req[offset]) { + if (ctx->debug) { + fprintf(stderr, + "Received function not corresponding to the request (0x%X != 0x%X)\n", + function, req[offset]); + } + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } + errno = EMBBADDATA; + return -1; + } + + /* Check the number of values is corresponding to the request */ + switch (function) { + case MODBUS_FC_READ_COILS: + case MODBUS_FC_READ_DISCRETE_INPUTS: + /* Read functions, 8 values in a byte (nb + * of values in the request and byte count in + * the response. */ + req_nb_value = (req[offset + 3] << 8) + req[offset + 4]; + req_nb_value = (req_nb_value / 8) + ((req_nb_value % 8) ? 1 : 0); + rsp_nb_value = rsp[offset + 1]; + break; + case MODBUS_FC_WRITE_AND_READ_REGISTERS: + case MODBUS_FC_READ_HOLDING_REGISTERS: + case MODBUS_FC_READ_INPUT_REGISTERS: + /* Read functions 1 value = 2 bytes */ + req_nb_value = (req[offset + 3] << 8) + req[offset + 4]; + rsp_nb_value = (rsp[offset + 1] / 2); + break; + case MODBUS_FC_WRITE_MULTIPLE_COILS: + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + /* N Write functions */ + req_nb_value = (req[offset + 3] << 8) + req[offset + 4]; + rsp_nb_value = (rsp[offset + 3] << 8) | rsp[offset + 4]; + break; + case MODBUS_FC_REPORT_SLAVE_ID: + /* Report slave ID (bytes received) */ + req_nb_value = rsp_nb_value = rsp[offset + 1]; + break; + default: + /* 1 Write functions & others */ + req_nb_value = rsp_nb_value = 1; + } + + if (req_nb_value == rsp_nb_value) { + rc = rsp_nb_value; + } else { + if (ctx->debug) { + fprintf(stderr, + "Quantity not corresponding to the request (%d != %d)\n", + rsp_nb_value, req_nb_value); + } + + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } + + errno = EMBBADDATA; + rc = -1; + } + } else { + if (ctx->debug) { + fprintf(stderr, + "Message length not corresponding to the computed length (%d != %d)\n", + rsp_length, rsp_length_computed); + } + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } + errno = EMBBADDATA; + rc = -1; + } + + return rc; +} + +static int response_io_status(uint8_t *tab_io_status, + int address, int nb, + uint8_t *rsp, int offset) +{ + int shift = 0; + /* Instead of byte (not allowed in Win32) */ + int one_byte = 0; + int i; + + for (i = address; i < address + nb; i++) { + one_byte |= tab_io_status[i] << shift; + if (shift == 7) { + /* Byte is full */ + rsp[offset++] = one_byte; + one_byte = shift = 0; + } else { + shift++; + } + } + + if (shift != 0) + rsp[offset++] = one_byte; + + return offset; +} + +/* Build the exception response */ +static int response_exception(modbus_t *ctx, sft_t *sft, + int exception_code, uint8_t *rsp, + unsigned int to_flush, + const char* template, ...) +{ + int rsp_length; + + /* Print debug message */ + if (ctx->debug) { + va_list ap; + + va_start(ap, template); + vfprintf(stderr, template, ap); + va_end(ap); + } + + /* Flush if required */ + if (to_flush) { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } + + /* Build exception response */ + sft->function = sft->function + 0x80; + rsp_length = ctx->backend->build_response_basis(sft, rsp); + rsp[rsp_length++] = exception_code; + + return rsp_length; +} + +/* Send a response to the received request. + Analyses the request and constructs a response. + + If an error occurs, this function construct the response + accordingly. +*/ +int modbus_reply(modbus_t *ctx, const uint8_t *req, + int req_length, modbus_mapping_t *mb_mapping) +{ + int offset; + int slave; + int function; + uint16_t address; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + int rsp_length = 0; + sft_t sft; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + offset = ctx->backend->header_length; + slave = req[offset - 1]; + function = req[offset]; + address = (req[offset + 1] << 8) + req[offset + 2]; + + sft.slave = slave; + sft.function = function; + sft.t_id = ctx->backend->prepare_response_tid(req, &req_length); + + /* Data are flushed on illegal number of values errors. */ + switch (function) { + case MODBUS_FC_READ_COILS: + case MODBUS_FC_READ_DISCRETE_INPUTS: { + unsigned int is_input = (function == MODBUS_FC_READ_DISCRETE_INPUTS); + int start_bits = is_input ? mb_mapping->start_input_bits : mb_mapping->start_bits; + int nb_bits = is_input ? mb_mapping->nb_input_bits : mb_mapping->nb_bits; + uint8_t *tab_bits = is_input ? mb_mapping->tab_input_bits : mb_mapping->tab_bits; + const char * const name = is_input ? "read_input_bits" : "read_bits"; + int nb = (req[offset + 3] << 8) + req[offset + 4]; + /* The mapping can be shifted to reduce memory consumption and it + doesn't always start at address zero. */ + int mapping_address = address - start_bits; + + if (nb < 1 || MODBUS_MAX_READ_BITS < nb) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + "Illegal nb of values %d in %s (max %d)\n", + nb, name, MODBUS_MAX_READ_BITS); + } else if (mapping_address < 0 || (mapping_address + nb) > nb_bits) { + rsp_length = response_exception( + ctx, &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, + "Illegal data address 0x%0X in %s\n", + mapping_address < 0 ? address : address + nb, name); + } else { + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + rsp[rsp_length++] = (nb / 8) + ((nb % 8) ? 1 : 0); + rsp_length = response_io_status(tab_bits, mapping_address, nb, + rsp, rsp_length); + } + } + break; + case MODBUS_FC_READ_HOLDING_REGISTERS: + case MODBUS_FC_READ_INPUT_REGISTERS: { + unsigned int is_input = (function == MODBUS_FC_READ_INPUT_REGISTERS); + int start_registers = is_input ? mb_mapping->start_input_registers : mb_mapping->start_registers; + int nb_registers = is_input ? mb_mapping->nb_input_registers : mb_mapping->nb_registers; + uint16_t *tab_registers = is_input ? mb_mapping->tab_input_registers : mb_mapping->tab_registers; + const char * const name = is_input ? "read_input_registers" : "read_registers"; + int nb = (req[offset + 3] << 8) + req[offset + 4]; + /* The mapping can be shifted to reduce memory consumption and it + doesn't always start at address zero. */ + int mapping_address = address - start_registers; + + if (nb < 1 || MODBUS_MAX_READ_REGISTERS < nb) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + "Illegal nb of values %d in %s (max %d)\n", + nb, name, MODBUS_MAX_READ_REGISTERS); + } else if (mapping_address < 0 || (mapping_address + nb) > nb_registers) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, + "Illegal data address 0x%0X in %s\n", + mapping_address < 0 ? address : address + nb, name); + } else { + int i; + + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + rsp[rsp_length++] = nb << 1; + for (i = mapping_address; i < mapping_address + nb; i++) { + rsp[rsp_length++] = tab_registers[i] >> 8; + rsp[rsp_length++] = tab_registers[i] & 0xFF; + } + } + } + break; + case MODBUS_FC_WRITE_SINGLE_COIL: { + int mapping_address = address - mb_mapping->start_bits; + + if (mapping_address < 0 || mapping_address >= mb_mapping->nb_bits) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, + "Illegal data address 0x%0X in write_bit\n", + address); + } else { + int data = (req[offset + 3] << 8) + req[offset + 4]; + + if (data == 0xFF00 || data == 0x0) { + mb_mapping->tab_bits[mapping_address] = data ? ON : OFF; + memcpy(rsp, req, req_length); + rsp_length = req_length; + } else { + rsp_length = response_exception( + ctx, &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, FALSE, + "Illegal data value 0x%0X in write_bit request at address %0X\n", + data, address); + } + } + } + break; + case MODBUS_FC_WRITE_SINGLE_REGISTER: { + int mapping_address = address - mb_mapping->start_registers; + + if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) { + rsp_length = response_exception( + ctx, &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, + "Illegal data address 0x%0X in write_register\n", + address); + } else { + int data = (req[offset + 3] << 8) + req[offset + 4]; + + mb_mapping->tab_registers[mapping_address] = data; + memcpy(rsp, req, req_length); + rsp_length = req_length; + } + } + break; + case MODBUS_FC_WRITE_MULTIPLE_COILS: { + int nb = (req[offset + 3] << 8) + req[offset + 4]; + int mapping_address = address - mb_mapping->start_bits; + + if (nb < 1 || MODBUS_MAX_WRITE_BITS < nb) { + /* May be the indication has been truncated on reading because of + * invalid address (eg. nb is 0 but the request contains values to + * write) so it's necessary to flush. */ + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + "Illegal number of values %d in write_bits (max %d)\n", + nb, MODBUS_MAX_WRITE_BITS); + } else if (mapping_address < 0 || + (mapping_address + nb) > mb_mapping->nb_bits) { + rsp_length = response_exception( + ctx, &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, + "Illegal data address 0x%0X in write_bits\n", + mapping_address < 0 ? address : address + nb); + } else { + /* 6 = byte count */ + modbus_set_bits_from_bytes(mb_mapping->tab_bits, mapping_address, nb, + &req[offset + 6]); + + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + /* 4 to copy the bit address (2) and the quantity of bits */ + memcpy(rsp + rsp_length, req + rsp_length, 4); + rsp_length += 4; + } + } + break; + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: { + int nb = (req[offset + 3] << 8) + req[offset + 4]; + int mapping_address = address - mb_mapping->start_registers; + + if (nb < 1 || MODBUS_MAX_WRITE_REGISTERS < nb) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + "Illegal number of values %d in write_registers (max %d)\n", + nb, MODBUS_MAX_WRITE_REGISTERS); + } else if (mapping_address < 0 || + (mapping_address + nb) > mb_mapping->nb_registers) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, + "Illegal data address 0x%0X in write_registers\n", + mapping_address < 0 ? address : address + nb); + } else { + int i, j; + for (i = mapping_address, j = 6; i < mapping_address + nb; i++, j += 2) { + /* 6 and 7 = first value */ + mb_mapping->tab_registers[i] = + (req[offset + j] << 8) + req[offset + j + 1]; + } + + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + /* 4 to copy the address (2) and the no. of registers */ + memcpy(rsp + rsp_length, req + rsp_length, 4); + rsp_length += 4; + } + } + break; + case MODBUS_FC_REPORT_SLAVE_ID: { + int str_len; + int byte_count_pos; + + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + /* Skip byte count for now */ + byte_count_pos = rsp_length++; + rsp[rsp_length++] = _REPORT_SLAVE_ID; + /* Run indicator status to ON */ + rsp[rsp_length++] = 0xFF; + /* LMB + length of LIBMODBUS_VERSION_STRING */ + str_len = 3 + strlen(LIBMODBUS_VERSION_STRING); + memcpy(rsp + rsp_length, "LMB" LIBMODBUS_VERSION_STRING, str_len); + rsp_length += str_len; + rsp[byte_count_pos] = rsp_length - byte_count_pos - 1; + } + break; + case MODBUS_FC_READ_EXCEPTION_STATUS: + if (ctx->debug) { + fprintf(stderr, "FIXME Not implemented\n"); + } + errno = ENOPROTOOPT; + return -1; + break; + case MODBUS_FC_MASK_WRITE_REGISTER: { + int mapping_address = address - mb_mapping->start_registers; + + if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, + "Illegal data address 0x%0X in write_register\n", + address); + } else { + uint16_t data = mb_mapping->tab_registers[mapping_address]; + uint16_t and = (req[offset + 3] << 8) + req[offset + 4]; + uint16_t or = (req[offset + 5] << 8) + req[offset + 6]; + + data = (data & and) | (or & (~and)); + mb_mapping->tab_registers[mapping_address] = data; + memcpy(rsp, req, req_length); + rsp_length = req_length; + } + } + break; + case MODBUS_FC_WRITE_AND_READ_REGISTERS: { + int nb = (req[offset + 3] << 8) + req[offset + 4]; + uint16_t address_write = (req[offset + 5] << 8) + req[offset + 6]; + int nb_write = (req[offset + 7] << 8) + req[offset + 8]; + int nb_write_bytes = req[offset + 9]; + int mapping_address = address - mb_mapping->start_registers; + int mapping_address_write = address_write - mb_mapping->start_registers; + + if (nb_write < 1 || MODBUS_MAX_WR_WRITE_REGISTERS < nb_write || + nb < 1 || MODBUS_MAX_WR_READ_REGISTERS < nb || + nb_write_bytes != nb_write * 2) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE, + "Illegal nb of values (W%d, R%d) in write_and_read_registers (max W%d, R%d)\n", + nb_write, nb, MODBUS_MAX_WR_WRITE_REGISTERS, MODBUS_MAX_WR_READ_REGISTERS); + } else if (mapping_address < 0 || + (mapping_address + nb) > mb_mapping->nb_registers || + mapping_address < 0 || + (mapping_address_write + nb_write) > mb_mapping->nb_registers) { + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE, + "Illegal data read address 0x%0X or write address 0x%0X write_and_read_registers\n", + mapping_address < 0 ? address : address + nb, + mapping_address_write < 0 ? address_write : address_write + nb_write); + } else { + int i, j; + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + rsp[rsp_length++] = nb << 1; + + /* Write first. + 10 and 11 are the offset of the first values to write */ + for (i = mapping_address_write, j = 10; + i < mapping_address_write + nb_write; i++, j += 2) { + mb_mapping->tab_registers[i] = + (req[offset + j] << 8) + req[offset + j + 1]; + } + + /* and read the data for the response */ + for (i = mapping_address; i < mapping_address + nb; i++) { + rsp[rsp_length++] = mb_mapping->tab_registers[i] >> 8; + rsp[rsp_length++] = mb_mapping->tab_registers[i] & 0xFF; + } + } + } + break; + + default: + rsp_length = response_exception( + ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_FUNCTION, rsp, TRUE, + "Unknown Modbus function code: 0x%0X\n", function); + break; + } + + /* Suppress any responses when the request was a broadcast */ + return (slave == MODBUS_BROADCAST_ADDRESS) ? 0 : send_msg(ctx, rsp, rsp_length); +} + +int modbus_reply_exception(modbus_t *ctx, const uint8_t *req, + unsigned int exception_code) +{ + int offset; + int slave; + int function; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + int rsp_length; + int dummy_length = 99; + sft_t sft; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + offset = ctx->backend->header_length; + slave = req[offset - 1]; + function = req[offset]; + + sft.slave = slave; + sft.function = function + 0x80;; + sft.t_id = ctx->backend->prepare_response_tid(req, &dummy_length); + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + + /* Positive exception code */ + if (exception_code < MODBUS_EXCEPTION_MAX) { + rsp[rsp_length++] = exception_code; + return send_msg(ctx, rsp, rsp_length); + } else { + errno = EINVAL; + return -1; + } +} + +/* Reads IO status */ +static int read_io_status(modbus_t *ctx, int function, + int addr, int nb, uint8_t *dest) +{ + int rc; + int req_length; + + uint8_t req[_MIN_REQ_LENGTH]; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + req_length = ctx->backend->build_request_basis(ctx, function, addr, nb, req); + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + int i, temp, bit; + int pos = 0; + int offset; + int offset_end; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + if (rc == -1) + return -1; + + offset = ctx->backend->header_length + 2; + offset_end = offset + rc; + for (i = offset; i < offset_end; i++) { + /* Shift reg hi_byte to temp */ + temp = rsp[i]; + + for (bit = 0x01; (bit & 0xff) && (pos < nb);) { + dest[pos++] = (temp & bit) ? TRUE : FALSE; + bit = bit << 1; + } + + } + } + + return rc; +} + +/* Reads the boolean status of bits and sets the array elements + in the destination to TRUE or FALSE (single bits). */ +int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest) +{ + int rc; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (nb > MODBUS_MAX_READ_BITS) { + if (ctx->debug) { + fprintf(stderr, + "ERROR Too many bits requested (%d > %d)\n", + nb, MODBUS_MAX_READ_BITS); + } + errno = EMBMDATA; + return -1; + } + + rc = read_io_status(ctx, MODBUS_FC_READ_COILS, addr, nb, dest); + + if (rc == -1) + return -1; + else + return nb; +} + + +/* Same as modbus_read_bits but reads the remote device input table */ +int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest) +{ + int rc; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (nb > MODBUS_MAX_READ_BITS) { + if (ctx->debug) { + fprintf(stderr, + "ERROR Too many discrete inputs requested (%d > %d)\n", + nb, MODBUS_MAX_READ_BITS); + } + errno = EMBMDATA; + return -1; + } + + rc = read_io_status(ctx, MODBUS_FC_READ_DISCRETE_INPUTS, addr, nb, dest); + + if (rc == -1) + return -1; + else + return nb; +} + +/* Reads the data from a remove device and put that data into an array */ +static int read_registers(modbus_t *ctx, int function, int addr, int nb, + uint16_t *dest) +{ + int rc; + int req_length; + uint8_t req[_MIN_REQ_LENGTH]; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + if (nb > MODBUS_MAX_READ_REGISTERS) { + if (ctx->debug) { + fprintf(stderr, + "ERROR Too many registers requested (%d > %d)\n", + nb, MODBUS_MAX_READ_REGISTERS); + } + errno = EMBMDATA; + return -1; + } + + req_length = ctx->backend->build_request_basis(ctx, function, addr, nb, req); + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + int offset; + int i; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + if (rc == -1) + return -1; + + offset = ctx->backend->header_length; + + for (i = 0; i < rc; i++) { + /* shift reg hi_byte to temp OR with lo_byte */ + dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | + rsp[offset + 3 + (i << 1)]; + } + } + + return rc; +} + +/* Reads the holding registers of remote device and put the data into an + array */ +int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest) +{ + int status; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (nb > MODBUS_MAX_READ_REGISTERS) { + if (ctx->debug) { + fprintf(stderr, + "ERROR Too many registers requested (%d > %d)\n", + nb, MODBUS_MAX_READ_REGISTERS); + } + errno = EMBMDATA; + return -1; + } + + status = read_registers(ctx, MODBUS_FC_READ_HOLDING_REGISTERS, + addr, nb, dest); + return status; +} + +/* Reads the input registers of remote device and put the data into an array */ +int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, + uint16_t *dest) +{ + int status; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (nb > MODBUS_MAX_READ_REGISTERS) { + fprintf(stderr, + "ERROR Too many input registers requested (%d > %d)\n", + nb, MODBUS_MAX_READ_REGISTERS); + errno = EMBMDATA; + return -1; + } + + status = read_registers(ctx, MODBUS_FC_READ_INPUT_REGISTERS, + addr, nb, dest); + + return status; +} + +/* Write a value to the specified register of the remote device. + Used by write_bit and write_register */ +static int write_single(modbus_t *ctx, int function, int addr, int value) +{ + int rc; + int req_length; + uint8_t req[_MIN_REQ_LENGTH]; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + req_length = ctx->backend->build_request_basis(ctx, function, addr, value, req); + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + /* Used by write_bit and write_register */ + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + } + + return rc; +} + +/* Turns ON or OFF a single bit of the remote device */ +int modbus_write_bit(modbus_t *ctx, int addr, int status) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return write_single(ctx, MODBUS_FC_WRITE_SINGLE_COIL, addr, + status ? 0xFF00 : 0); +} + +/* Writes a value in one register of the remote device */ +int modbus_write_register(modbus_t *ctx, int addr, int value) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return write_single(ctx, MODBUS_FC_WRITE_SINGLE_REGISTER, addr, value); +} + +/* Write the bits of the array in the remote device */ +int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *src) +{ + int rc; + int i; + int byte_count; + int req_length; + int bit_check = 0; + int pos = 0; + uint8_t req[MAX_MESSAGE_LENGTH]; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (nb > MODBUS_MAX_WRITE_BITS) { + if (ctx->debug) { + fprintf(stderr, "ERROR Writing too many bits (%d > %d)\n", + nb, MODBUS_MAX_WRITE_BITS); + } + errno = EMBMDATA; + return -1; + } + + req_length = ctx->backend->build_request_basis(ctx, + MODBUS_FC_WRITE_MULTIPLE_COILS, + addr, nb, req); + byte_count = (nb / 8) + ((nb % 8) ? 1 : 0); + req[req_length++] = byte_count; + + for (i = 0; i < byte_count; i++) { + int bit; + + bit = 0x01; + req[req_length] = 0; + + while ((bit & 0xFF) && (bit_check++ < nb)) { + if (src[pos++]) + req[req_length] |= bit; + else + req[req_length] &=~ bit; + + bit = bit << 1; + } + req_length++; + } + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + } + + + return rc; +} + +/* Write the values from the array to the registers of the remote device */ +int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src) +{ + int rc; + int i; + int req_length; + int byte_count; + uint8_t req[MAX_MESSAGE_LENGTH]; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (nb > MODBUS_MAX_WRITE_REGISTERS) { + if (ctx->debug) { + fprintf(stderr, + "ERROR Trying to write to too many registers (%d > %d)\n", + nb, MODBUS_MAX_WRITE_REGISTERS); + } + errno = EMBMDATA; + return -1; + } + + req_length = ctx->backend->build_request_basis(ctx, + MODBUS_FC_WRITE_MULTIPLE_REGISTERS, + addr, nb, req); + byte_count = nb * 2; + req[req_length++] = byte_count; + + for (i = 0; i < nb; i++) { + req[req_length++] = src[i] >> 8; + req[req_length++] = src[i] & 0x00FF; + } + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + } + + return rc; +} + +int modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask) +{ + int rc; + int req_length; + /* The request length can not exceed _MIN_REQ_LENGTH - 2 and 4 bytes to + * store the masks. The ugly substraction is there to remove the 'nb' value + * (2 bytes) which is not used. */ + uint8_t req[_MIN_REQ_LENGTH + 2]; + + req_length = ctx->backend->build_request_basis(ctx, + MODBUS_FC_MASK_WRITE_REGISTER, + addr, 0, req); + + /* HACKISH, count is not used */ + req_length -= 2; + + req[req_length++] = and_mask >> 8; + req[req_length++] = and_mask & 0x00ff; + req[req_length++] = or_mask >> 8; + req[req_length++] = or_mask & 0x00ff; + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + /* Used by write_bit and write_register */ + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + } + + return rc; +} + +/* Write multiple registers from src array to remote device and read multiple + registers from remote device to dest array. */ +int modbus_write_and_read_registers(modbus_t *ctx, + int write_addr, int write_nb, + const uint16_t *src, + int read_addr, int read_nb, + uint16_t *dest) + +{ + int rc; + int req_length; + int i; + int byte_count; + uint8_t req[MAX_MESSAGE_LENGTH]; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (write_nb > MODBUS_MAX_WR_WRITE_REGISTERS) { + if (ctx->debug) { + fprintf(stderr, + "ERROR Too many registers to write (%d > %d)\n", + write_nb, MODBUS_MAX_WR_WRITE_REGISTERS); + } + errno = EMBMDATA; + return -1; + } + + if (read_nb > MODBUS_MAX_WR_READ_REGISTERS) { + if (ctx->debug) { + fprintf(stderr, + "ERROR Too many registers requested (%d > %d)\n", + read_nb, MODBUS_MAX_WR_READ_REGISTERS); + } + errno = EMBMDATA; + return -1; + } + req_length = ctx->backend->build_request_basis(ctx, + MODBUS_FC_WRITE_AND_READ_REGISTERS, + read_addr, read_nb, req); + + req[req_length++] = write_addr >> 8; + req[req_length++] = write_addr & 0x00ff; + req[req_length++] = write_nb >> 8; + req[req_length++] = write_nb & 0x00ff; + byte_count = write_nb * 2; + req[req_length++] = byte_count; + + for (i = 0; i < write_nb; i++) { + req[req_length++] = src[i] >> 8; + req[req_length++] = src[i] & 0x00FF; + } + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + int offset; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + if (rc == -1) + return -1; + + offset = ctx->backend->header_length; + for (i = 0; i < rc; i++) { + /* shift reg hi_byte to temp OR with lo_byte */ + dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | + rsp[offset + 3 + (i << 1)]; + } + } + + return rc; +} + +/* Send a request to get the slave ID of the device (only available in serial + communication). */ +int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest) +{ + int rc; + int req_length; + uint8_t req[_MIN_REQ_LENGTH]; + + if (ctx == NULL || max_dest <= 0) { + errno = EINVAL; + return -1; + } + + req_length = ctx->backend->build_request_basis(ctx, MODBUS_FC_REPORT_SLAVE_ID, + 0, 0, req); + + /* HACKISH, addr and count are not used */ + req_length -= 4; + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + int i; + int offset; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + if (rc == -1) + return -1; + + offset = ctx->backend->header_length + 2; + + /* Byte count, slave id, run indicator status and + additional data. Truncate copy to max_dest. */ + for (i=0; i < rc && i < max_dest; i++) { + dest[i] = rsp[offset + i]; + } + } + + return rc; +} + +void _modbus_init_common(modbus_t *ctx) +{ + /* Slave and socket are initialized to -1 */ + ctx->slave = -1; + ctx->s = -1; + + ctx->debug = FALSE; + ctx->error_recovery = MODBUS_ERROR_RECOVERY_NONE; + + ctx->response_timeout.tv_sec = 0; + ctx->response_timeout.tv_usec = _RESPONSE_TIMEOUT; + + ctx->byte_timeout.tv_sec = 0; + ctx->byte_timeout.tv_usec = _BYTE_TIMEOUT; +} + +/* Define the slave number */ +int modbus_set_slave(modbus_t *ctx, int slave) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return ctx->backend->set_slave(ctx, slave); +} + +int modbus_set_error_recovery(modbus_t *ctx, + modbus_error_recovery_mode error_recovery) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + /* The type of modbus_error_recovery_mode is unsigned enum */ + ctx->error_recovery = (uint8_t) error_recovery; + return 0; +} + +int modbus_set_socket(modbus_t *ctx, int s) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + ctx->s = s; + return 0; +} + +int modbus_get_socket(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return ctx->s; +} + +/* Get the timeout interval used to wait for a response */ +int modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + *to_sec = ctx->response_timeout.tv_sec; + *to_usec = ctx->response_timeout.tv_usec; + return 0; +} + +int modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec) +{ + if (ctx == NULL || + (to_sec == 0 && to_usec == 0) || to_usec > 999999) { + errno = EINVAL; + return -1; + } + + ctx->response_timeout.tv_sec = to_sec; + ctx->response_timeout.tv_usec = to_usec; + return 0; +} + +/* Get the timeout interval between two consecutive bytes of a message */ +int modbus_get_byte_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + *to_sec = ctx->byte_timeout.tv_sec; + *to_usec = ctx->byte_timeout.tv_usec; + return 0; +} + +int modbus_set_byte_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec) +{ + /* Byte timeout can be disabled when both values are zero */ + if (ctx == NULL || to_usec > 999999) { + errno = EINVAL; + return -1; + } + + ctx->byte_timeout.tv_sec = to_sec; + ctx->byte_timeout.tv_usec = to_usec; + return 0; +} + +int modbus_get_header_length(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return ctx->backend->header_length; +} + +int modbus_connect(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return ctx->backend->connect(ctx); +} + +void modbus_close(modbus_t *ctx) +{ + if (ctx == NULL) + return; + + ctx->backend->close(ctx); +} + +void modbus_free(modbus_t *ctx) +{ + if (ctx == NULL) + return; + + ctx->backend->free(ctx); +} + +int modbus_set_debug(modbus_t *ctx, int flag) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + ctx->debug = flag; + return 0; +} + +/* Allocates 4 arrays to store bits, input bits, registers and inputs + registers. The pointers are stored in modbus_mapping structure. + + The modbus_mapping_new_ranges() function shall return the new allocated + structure if successful. Otherwise it shall return NULL and set errno to + ENOMEM. */ +modbus_mapping_t* modbus_mapping_new_start_address( + unsigned int start_bits, unsigned int nb_bits, + unsigned int start_input_bits, unsigned int nb_input_bits, + unsigned int start_registers, unsigned int nb_registers, + unsigned int start_input_registers, unsigned int nb_input_registers) +{ + modbus_mapping_t *mb_mapping; + + mb_mapping = (modbus_mapping_t *)malloc(sizeof(modbus_mapping_t)); + if (mb_mapping == NULL) { + return NULL; + } + + /* 0X */ + mb_mapping->nb_bits = nb_bits; + mb_mapping->start_bits = start_bits; + if (nb_bits == 0) { + mb_mapping->tab_bits = NULL; + } else { + /* Negative number raises a POSIX error */ + mb_mapping->tab_bits = + (uint8_t *) malloc(nb_bits * sizeof(uint8_t)); + if (mb_mapping->tab_bits == NULL) { + free(mb_mapping); + return NULL; + } + memset(mb_mapping->tab_bits, 0, nb_bits * sizeof(uint8_t)); + } + + /* 1X */ + mb_mapping->nb_input_bits = nb_input_bits; + mb_mapping->start_input_bits = start_input_bits; + if (nb_input_bits == 0) { + mb_mapping->tab_input_bits = NULL; + } else { + mb_mapping->tab_input_bits = + (uint8_t *) malloc(nb_input_bits * sizeof(uint8_t)); + if (mb_mapping->tab_input_bits == NULL) { + free(mb_mapping->tab_bits); + free(mb_mapping); + return NULL; + } + memset(mb_mapping->tab_input_bits, 0, nb_input_bits * sizeof(uint8_t)); + } + + /* 4X */ + mb_mapping->nb_registers = nb_registers; + mb_mapping->start_registers = start_registers; + if (nb_registers == 0) { + mb_mapping->tab_registers = NULL; + } else { + mb_mapping->tab_registers = + (uint16_t *) malloc(nb_registers * sizeof(uint16_t)); + if (mb_mapping->tab_registers == NULL) { + free(mb_mapping->tab_input_bits); + free(mb_mapping->tab_bits); + free(mb_mapping); + return NULL; + } + memset(mb_mapping->tab_registers, 0, nb_registers * sizeof(uint16_t)); + } + + /* 3X */ + mb_mapping->nb_input_registers = nb_input_registers; + mb_mapping->start_input_registers = start_input_registers; + if (nb_input_registers == 0) { + mb_mapping->tab_input_registers = NULL; + } else { + mb_mapping->tab_input_registers = + (uint16_t *) malloc(nb_input_registers * sizeof(uint16_t)); + if (mb_mapping->tab_input_registers == NULL) { + free(mb_mapping->tab_registers); + free(mb_mapping->tab_input_bits); + free(mb_mapping->tab_bits); + free(mb_mapping); + return NULL; + } + memset(mb_mapping->tab_input_registers, 0, + nb_input_registers * sizeof(uint16_t)); + } + + return mb_mapping; +} + +modbus_mapping_t* modbus_mapping_new(int nb_bits, int nb_input_bits, + int nb_registers, int nb_input_registers) +{ + return modbus_mapping_new_start_address( + 0, nb_bits, 0, nb_input_bits, 0, nb_registers, 0, nb_input_registers); +} + +/* Frees the 4 arrays */ +void modbus_mapping_free(modbus_mapping_t *mb_mapping) +{ + if (mb_mapping == NULL) { + return; + } + + free(mb_mapping->tab_input_registers); + free(mb_mapping->tab_registers); + free(mb_mapping->tab_input_bits); + free(mb_mapping->tab_bits); + free(mb_mapping); +} + +#ifndef HAVE_STRLCPY +/* + * Function strlcpy was originally developed by + * Todd C. Miller to simplify writing secure code. + * See ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/strlcpy.3 + * for more information. + * + * Thank you Ulrich Drepper... not! + * + * Copy src to string dest of size dest_size. At most dest_size-1 characters + * will be copied. Always NUL terminates (unless dest_size == 0). Returns + * strlen(src); if retval >= dest_size, truncation occurred. + */ +size_t strlcpy(char *dest, const char *src, size_t dest_size) +{ + register char *d = dest; + register const char *s = src; + register size_t n = dest_size; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + /* Not enough room in dest, add NUL and traverse rest of src */ + if (n == 0) { + if (dest_size != 0) + *d = '\0'; /* NUL-terminate dest */ + while (*s++) + ; + } + + return (s - src - 1); /* count does not include NUL */ +} +#endif diff --git a/Applications/qModMaster/3rdparty/libmodbus/modbus.h b/Applications/qModMaster/3rdparty/libmodbus/modbus.h new file mode 100644 index 0000000..e3e9f86 --- /dev/null +++ b/Applications/qModMaster/3rdparty/libmodbus/modbus.h @@ -0,0 +1,289 @@ +/* + * Copyright © 2001-2013 Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef MODBUS_H +#define MODBUS_H + +/* Add this for macros that defined unix flavor */ +#if (defined(__unix__) || defined(unix)) && !defined(USG) +#include +#endif + +#ifndef _MSC_VER +#include +#else +#include "stdint.h" +#endif + +#include "modbus-version.h" + +#if defined(_MSC_VER) +# if defined(DLLBUILD) +/* define DLLBUILD when building the DLL */ +# define MODBUS_API __declspec(dllexport) +# else +# define MODBUS_API __declspec(dllimport) +# endif +#else +# define MODBUS_API +#endif + +#ifdef __cplusplus +# define MODBUS_BEGIN_DECLS extern "C" { +# define MODBUS_END_DECLS } +#else +# define MODBUS_BEGIN_DECLS +# define MODBUS_END_DECLS +#endif + +MODBUS_BEGIN_DECLS + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef OFF +#define OFF 0 +#endif + +#ifndef ON +#define ON 1 +#endif + +/* Modbus function codes */ +#define MODBUS_FC_READ_COILS 0x01 +#define MODBUS_FC_READ_DISCRETE_INPUTS 0x02 +#define MODBUS_FC_READ_HOLDING_REGISTERS 0x03 +#define MODBUS_FC_READ_INPUT_REGISTERS 0x04 +#define MODBUS_FC_WRITE_SINGLE_COIL 0x05 +#define MODBUS_FC_WRITE_SINGLE_REGISTER 0x06 +#define MODBUS_FC_READ_EXCEPTION_STATUS 0x07 +#define MODBUS_FC_WRITE_MULTIPLE_COILS 0x0F +#define MODBUS_FC_WRITE_MULTIPLE_REGISTERS 0x10 +#define MODBUS_FC_REPORT_SLAVE_ID 0x11 +#define MODBUS_FC_MASK_WRITE_REGISTER 0x16 +#define MODBUS_FC_WRITE_AND_READ_REGISTERS 0x17 + +#define MODBUS_BROADCAST_ADDRESS 0 + +/* Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 1 page 12) + * Quantity of Coils to read (2 bytes): 1 to 2000 (0x7D0) + * (chapter 6 section 11 page 29) + * Quantity of Coils to write (2 bytes): 1 to 1968 (0x7B0) + */ +#define MODBUS_MAX_READ_BITS 2000 +#define MODBUS_MAX_WRITE_BITS 1968 + +/* Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 3 page 15) + * Quantity of Registers to read (2 bytes): 1 to 125 (0x7D) + * (chapter 6 section 12 page 31) + * Quantity of Registers to write (2 bytes) 1 to 123 (0x7B) + * (chapter 6 section 17 page 38) + * Quantity of Registers to write in R/W registers (2 bytes) 1 to 121 (0x79) + */ +#define MODBUS_MAX_READ_REGISTERS 125 +#define MODBUS_MAX_WRITE_REGISTERS 123 +#define MODBUS_MAX_WR_WRITE_REGISTERS 121 +#define MODBUS_MAX_WR_READ_REGISTERS 125 + +/* The size of the MODBUS PDU is limited by the size constraint inherited from + * the first MODBUS implementation on Serial Line network (max. RS485 ADU = 256 + * bytes). Therefore, MODBUS PDU for serial line communication = 256 - Server + * address (1 byte) - CRC (2 bytes) = 253 bytes. + */ +#define MODBUS_MAX_PDU_LENGTH 253 + +/* Consequently: + * - RTU MODBUS ADU = 253 bytes + Server address (1 byte) + CRC (2 bytes) = 256 + * bytes. + * - TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes. + * so the maximum of both backend in 260 bytes. This size can used to allocate + * an array of bytes to store responses and it will be compatible with the two + * backends. + */ +#define MODBUS_MAX_ADU_LENGTH 260 + +/* Random number to avoid errno conflicts */ +#define MODBUS_ENOBASE 112345678 + +/* Protocol exceptions */ +enum { + MODBUS_EXCEPTION_ILLEGAL_FUNCTION = 0x01, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE, + MODBUS_EXCEPTION_ACKNOWLEDGE, + MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY, + MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE, + MODBUS_EXCEPTION_MEMORY_PARITY, + MODBUS_EXCEPTION_NOT_DEFINED, + MODBUS_EXCEPTION_GATEWAY_PATH, + MODBUS_EXCEPTION_GATEWAY_TARGET, + MODBUS_EXCEPTION_MAX +}; + +#define EMBXILFUN (MODBUS_ENOBASE + MODBUS_EXCEPTION_ILLEGAL_FUNCTION) +#define EMBXILADD (MODBUS_ENOBASE + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS) +#define EMBXILVAL (MODBUS_ENOBASE + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE) +#define EMBXSFAIL (MODBUS_ENOBASE + MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE) +#define EMBXACK (MODBUS_ENOBASE + MODBUS_EXCEPTION_ACKNOWLEDGE) +#define EMBXSBUSY (MODBUS_ENOBASE + MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY) +#define EMBXNACK (MODBUS_ENOBASE + MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE) +#define EMBXMEMPAR (MODBUS_ENOBASE + MODBUS_EXCEPTION_MEMORY_PARITY) +#define EMBXGPATH (MODBUS_ENOBASE + MODBUS_EXCEPTION_GATEWAY_PATH) +#define EMBXGTAR (MODBUS_ENOBASE + MODBUS_EXCEPTION_GATEWAY_TARGET) + +/* Native libmodbus error codes */ +#define EMBBADCRC (EMBXGTAR + 1) +#define EMBBADDATA (EMBXGTAR + 2) +#define EMBBADEXC (EMBXGTAR + 3) +#define EMBUNKEXC (EMBXGTAR + 4) +#define EMBMDATA (EMBXGTAR + 5) +#define EMBBADSLAVE (EMBXGTAR + 6) + +extern const unsigned int libmodbus_version_major; +extern const unsigned int libmodbus_version_minor; +extern const unsigned int libmodbus_version_micro; + +typedef struct _modbus modbus_t; + +typedef struct { + int nb_bits; + int start_bits; + int nb_input_bits; + int start_input_bits; + int nb_input_registers; + int start_input_registers; + int nb_registers; + int start_registers; + uint8_t *tab_bits; + uint8_t *tab_input_bits; + uint16_t *tab_input_registers; + uint16_t *tab_registers; +} modbus_mapping_t; + +typedef enum +{ + MODBUS_ERROR_RECOVERY_NONE = 0, + MODBUS_ERROR_RECOVERY_LINK = (1<<1), + MODBUS_ERROR_RECOVERY_PROTOCOL = (1<<2) +} modbus_error_recovery_mode; + +MODBUS_API int modbus_set_slave(modbus_t* ctx, int slave); +MODBUS_API int modbus_set_error_recovery(modbus_t *ctx, modbus_error_recovery_mode error_recovery); +MODBUS_API int modbus_set_socket(modbus_t *ctx, int s); +MODBUS_API int modbus_get_socket(modbus_t *ctx); + +MODBUS_API int modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); +MODBUS_API int modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); + +MODBUS_API int modbus_get_byte_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); +MODBUS_API int modbus_set_byte_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); + +MODBUS_API int modbus_get_header_length(modbus_t *ctx); + +MODBUS_API int modbus_connect(modbus_t *ctx); +MODBUS_API void modbus_close(modbus_t *ctx); + +MODBUS_API void modbus_free(modbus_t *ctx); + +MODBUS_API int modbus_flush(modbus_t *ctx); +MODBUS_API int modbus_set_debug(modbus_t *ctx, int flag); + +MODBUS_API const char *modbus_strerror(int errnum); + +MODBUS_API int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest); +MODBUS_API int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest); +MODBUS_API int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest); +MODBUS_API int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest); +MODBUS_API int modbus_write_bit(modbus_t *ctx, int coil_addr, int status); +MODBUS_API int modbus_write_register(modbus_t *ctx, int reg_addr, int value); +MODBUS_API int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *data); +MODBUS_API int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *data); +MODBUS_API int modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask); +MODBUS_API int modbus_write_and_read_registers(modbus_t *ctx, int write_addr, int write_nb, + const uint16_t *src, int read_addr, int read_nb, + uint16_t *dest); +MODBUS_API int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest); + +MODBUS_API modbus_mapping_t* modbus_mapping_new_start_address( + unsigned int start_bits, unsigned int nb_bits, + unsigned int start_input_bits, unsigned int nb_input_bits, + unsigned int start_registers, unsigned int nb_registers, + unsigned int start_input_registers, unsigned int nb_input_registers); + +MODBUS_API modbus_mapping_t* modbus_mapping_new(int nb_bits, int nb_input_bits, + int nb_registers, int nb_input_registers); +MODBUS_API void modbus_mapping_free(modbus_mapping_t *mb_mapping); + +MODBUS_API int modbus_send_raw_request(modbus_t *ctx, uint8_t *raw_req, int raw_req_length); + +MODBUS_API int modbus_receive(modbus_t *ctx, uint8_t *req); + +MODBUS_API int modbus_receive_confirmation(modbus_t *ctx, uint8_t *rsp); + +MODBUS_API int modbus_reply(modbus_t *ctx, const uint8_t *req, + int req_length, modbus_mapping_t *mb_mapping); +MODBUS_API int modbus_reply_exception(modbus_t *ctx, const uint8_t *req, + unsigned int exception_code); + +/** + * UTILS FUNCTIONS + **/ + +#define MODBUS_GET_HIGH_BYTE(data) (((data) >> 8) & 0xFF) +#define MODBUS_GET_LOW_BYTE(data) ((data) & 0xFF) +#define MODBUS_GET_INT64_FROM_INT16(tab_int16, index) \ + (((int64_t)tab_int16[(index) ] << 48) + \ + ((int64_t)tab_int16[(index) + 1] << 32) + \ + ((int64_t)tab_int16[(index) + 2] << 16) + \ + (int64_t)tab_int16[(index) + 3]) +#define MODBUS_GET_INT32_FROM_INT16(tab_int16, index) ((tab_int16[(index)] << 16) + tab_int16[(index) + 1]) +#define MODBUS_GET_INT16_FROM_INT8(tab_int8, index) ((tab_int8[(index)] << 8) + tab_int8[(index) + 1]) +#define MODBUS_SET_INT16_TO_INT8(tab_int8, index, value) \ + do { \ + tab_int8[(index)] = (value) >> 8; \ + tab_int8[(index) + 1] = (value) & 0xFF; \ + } while (0) +#define MODBUS_SET_INT32_TO_INT16(tab_int16, index, value) \ + do { \ + tab_int16[(index) ] = (value) >> 16; \ + tab_int16[(index) + 1] = (value); \ + } while (0) +#define MODBUS_SET_INT64_TO_INT16(tab_int16, index, value) \ + do { \ + tab_int16[(index) ] = (value) >> 48; \ + tab_int16[(index) + 1] = (value) >> 32; \ + tab_int16[(index) + 2] = (value) >> 16; \ + tab_int16[(index) + 3] = (value); \ + } while (0) + +MODBUS_API void modbus_set_bits_from_byte(uint8_t *dest, int idx, const uint8_t value); +MODBUS_API void modbus_set_bits_from_bytes(uint8_t *dest, int idx, unsigned int nb_bits, + const uint8_t *tab_byte); +MODBUS_API uint8_t modbus_get_byte_from_bits(const uint8_t *src, int idx, unsigned int nb_bits); +MODBUS_API float modbus_get_float(const uint16_t *src); +MODBUS_API float modbus_get_float_abcd(const uint16_t *src); +MODBUS_API float modbus_get_float_dcba(const uint16_t *src); +MODBUS_API float modbus_get_float_badc(const uint16_t *src); +MODBUS_API float modbus_get_float_cdab(const uint16_t *src); + +MODBUS_API void modbus_set_float(float f, uint16_t *dest); +MODBUS_API void modbus_set_float_abcd(float f, uint16_t *dest); +MODBUS_API void modbus_set_float_dcba(float f, uint16_t *dest); +MODBUS_API void modbus_set_float_badc(float f, uint16_t *dest); +MODBUS_API void modbus_set_float_cdab(float f, uint16_t *dest); + +#include "modbus-tcp.h" +#include "modbus-rtu.h" + +MODBUS_END_DECLS + +#endif /* MODBUS_H */ diff --git a/Applications/qModMaster/Docs/Modbus_Application_Protocol_V1_1b3.pdf b/Applications/qModMaster/Docs/Modbus_Application_Protocol_V1_1b3.pdf new file mode 100644 index 0000000..b3c61ca Binary files /dev/null and b/Applications/qModMaster/Docs/Modbus_Application_Protocol_V1_1b3.pdf differ diff --git a/Applications/qModMaster/LICENSE b/Applications/qModMaster/LICENSE new file mode 100644 index 0000000..80e659d --- /dev/null +++ b/Applications/qModMaster/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/Applications/qModMaster/ManModbus/READ_ME/HTML5WebTemplates.co.uk.url b/Applications/qModMaster/ManModbus/READ_ME/HTML5WebTemplates.co.uk.url new file mode 100644 index 0000000..6da05cd --- /dev/null +++ b/Applications/qModMaster/ManModbus/READ_ME/HTML5WebTemplates.co.uk.url @@ -0,0 +1,8 @@ +[{000214A0-0000-0000-C000-000000000046}] +Prop3=19,2 +[InternetShortcut] +IDList= +URL=http://www.html5webtemplates.co.uk/ +HotKey=0 +IconFile=C:\Windows\system32\SHELL32.dll +IconIndex=277 diff --git a/Applications/qModMaster/ManModbus/READ_ME/PLEASE READ.txt b/Applications/qModMaster/ManModbus/READ_ME/PLEASE READ.txt new file mode 100644 index 0000000..49fd57a --- /dev/null +++ b/Applications/qModMaster/ManModbus/READ_ME/PLEASE READ.txt @@ -0,0 +1,13 @@ +Thanks for downloading this template from HTML5WebTemplates.co.uk + +I hope that it suits your needs. + +If you wish to remove the footer link (to http://www.html5webtemplates.co.uk/) I ask that you make a donation of 15 via Paypal. + +You can make a donation at the following address:http://www.html5webtemplates.co.uk/faqs.html + +If you have any questions please feel free to e-mail me at contact@html5webtemplates.co.uk + +Best regards, + +HTML5WebTemplates.co.uk \ No newline at end of file diff --git a/Applications/qModMaster/ManModbus/READ_ME/Remove the footer link.URL b/Applications/qModMaster/ManModbus/READ_ME/Remove the footer link.URL new file mode 100644 index 0000000..13d925c --- /dev/null +++ b/Applications/qModMaster/ManModbus/READ_ME/Remove the footer link.URL @@ -0,0 +1,6 @@ +[InternetShortcut] +URL=http://www.html5webtemplates.co.uk/faqs.html +IDList= +HotKey=0 +IconFile=C:\Windows\system32\SHELL32.dll +IconIndex=44 diff --git a/Applications/qModMaster/ManModbus/exceptions.html b/Applications/qModMaster/ManModbus/exceptions.html new file mode 100644 index 0000000..edd0870 --- /dev/null +++ b/Applications/qModMaster/ManModbus/exceptions.html @@ -0,0 +1,110 @@ + + + + + MODBUS_MANUAL + + + + + +
+ +
+ +
+ +

MODBUS Exception Responses

+ When a client device sends a request to a server device it expects a + normal response. One of four possible events can occur from the + clients query:
+
+
    +
  • If the server device receives the request without a + communication error, and can handle the query normally, it returns + a normal response.
  • +
+
    +
  • If the server does not receive the request due to a + communication error, no response is returned. The client program + will eventually process a timeout condition for the request.
  • +
+
    +
  • If the server receives the request, but detects a communication + error (parity, LRC, CRC, ...), no response is returned. The client + program will eventually process a timeout condition for the + request.
  • +
+
    +
  • If the server receives the request without a communication + error, but cannot handle it (for example, if the request is to + read a non existent output or register), the server will return + an exception response informing the client of the nature of the + error.
  • +
+ The exception response message has two fields that differentiate it + from a nor mal response:
+
+
    +
  • Function Code Field: In a normal response, the server echoes the + function code of the original request in the function code field + of the response. All function codes have a most significant bit + (MSB) of 0 (their values are all below 80 hexad ecimal). In an + exception response, the server sets the MSB of the function code + to 1. This makes the function code value in an exception response + exactly 80 hexadecimal higher than the value would be for a normal + response.With the function codes MSB set, the client's + application program can recognize the exception response and can + examine the data field for the exception code.
  • +
+
    +
  • Data Field: In a normal response, the server may return data or + statistics in the data field (any information that was requested + in the request). In an exception response, the server returns an + exception code in the data field. This defines the server + condition that caused the exception.
  • +
+

MODBUS Exception Codes

+
+
+
+
+
+
+ +
+ + diff --git a/Applications/qModMaster/ManModbus/func_descr.html b/Applications/qModMaster/ManModbus/func_descr.html new file mode 100644 index 0000000..9f82e04 --- /dev/null +++ b/Applications/qModMaster/ManModbus/func_descr.html @@ -0,0 +1,775 @@ + + + + + MODBUS_MANUAL + + + + + +
+ +
+ +
+ +

01(0x01) Read Coils
+

+ This function code is used to read from 1 to 2000 contiguous status of + coils in a remote device. The Request PDU specifies the starting + address, i.e. the address of the first coil specified, and the number + of coils. In the PDU Coils are addressed starting at zero. Therefore + coils numbered 1-16 are addressed as 0-15.
+
+ The coils in the response message are packed as one coil per bit of + the data field. Status is indicated as 1= ON and 0= OFF. The LSB of + the first data byte contains the output addressed in the query. The + other coils follow toward the high order end of this byte, and from + low order to high order in subsequent bytes.
+
+ If the returned output quantity is not a multiple of eight, the + remaining bits in the final data byte will be padded with zeros + (toward the high order end of the byte). The Byte Count field + specifies the quantity of complete bytes of data.
+
+ Request
+
+
+ Response
+
+ *N = Quantity of Outputs / 8, if the remainder is different of 0 -> + N = N+1
+
+ Error
+
+
+

02(0x02) Read Discrete Inputs

+ This function code is used to read from 1 to 2000 contiguous status of + discrete inputs in a remote device. The Request PDU specifies the + starting address, i.e. the address of the first input specified, and + the number of inputs. In the PDU Discrete Inputs a re addressed + starting at zero. Therefore Discrete inputs numbered 1 -16 are + addressed as 0-15.
+
+ The discrete inputs in the response message are packed as one input + per bit of the data field. Status is indicated as 1= ON; 0= OFF. The + LSB of the first data byte contains the input addressed in the query. + The other inputs follow toward the high order end of this byte, and + from low order to high order in subsequent bytes.
+
+ If the returned input quantity is not a multiple of eight, the + remaining bits in the final d ata byte will be padded with zeros + (toward the high order end of the byte). The Byte Count field + specifies the quantity of complete bytes of data.
+
+ Request
+
+
+ Response
+
+ *N = Quantity of Outputs / 8, if the remainder is different of 0 -> + N = N+1
+
+ Error
+
+
+

03(0x03) Read Holding Registers

+ This function code is used to read the contents of a contiguous block + of holding registers in a remote device. The Request PDU specifies the + starting register address and the number of registers. In the PDU + Registers are addressed starting at zero. Therefore registers numbered + 1-16 are addressed as 0-15.
+
+ The register data in the response message are packed as two bytes per + register, with the binary contents right justified within each byte. + For each register, the first byte contains the high order bits and the + second contains the low order bits.
+
+ Request
+
+
+ Response
+
+ *N = Quantity of registers
+
+ Error
+
+
+

04(0x04) Read Input Registers

+ This function code is used to read from 1 to 125 contiguous input + registers in a remote device. The Request PDU specifies the starting + register address and the number of registers. In the PDU Registers are + addressed starting at zero. Therefore input registers n umbered 1-16 + are addressed as 0-15.
+
+ The register data in the response message are packed as two bytes per + register, with the binary contents right justified within each byte. + For each register, the first byte contains the high order bits and the + second contains the low order bits.
+
+ Request
+
+
+ Response
+
+ *N = Quantity of registers
+
+ Error
+
+
+

05(0x05) Write Single Coil

+ This function code is used to write a single output to either ON or + OFF in a remote device.
+
+ The requested ON/OFF state is specified by a constant in the request + data field. A value of FF 00 hex requests the output to be ON. A value + of 00 00 requests it to be OFF. All other values are illegal and will + not affect the output.
+
+ The Request PDU specifies the address of the coil to be forced. Coils + are addressed starting at zero. Therefore coil numbered 1 is addressed + as 0. The requested ON/OFF state is specified by a constant in the + Coil Value field. A value of 0XFF00 requests the coil to be ON. A + value of 0X0000 requests the coil to be off. All other values are + illegal and will not affect the coil.
+
+
+ The normal response is an echo of the request, returned after the coil + state has been written.
+
+ Request
+
+
+ Response
+
+
+
+ Error
+
+
+

06(0x06) Write Single Register

+ This function code is used to write a single holding register in a + remote device.
+
+ The Request PDU specifies the address of the register to be written. + Registers are addressed starting at zero. Therefore register numbered + 1 is addressed as 0.
+
+ The normal response is an echo of the request, returned after the + register contents have been written.
+
+ Request
+
+
+ Response
+
+
+ Error
+
+
+

07(0x07) Read Exception Status (Serial Line only) +

+ This function code is used to read the contents of eight Exception + Status outputs in a remote device.
+
+ The function provides a simple method for accessing this information, + because the Exception Output references are known (no output reference + is needed in the function).
+
+ The normal response contains the status of the eight Exception Status + outputs. The outputs are packed into one data byte, with one bit per + output. The statu s of the lowest output reference is contained in the + least significant bit of the byte.
+
+ The contents of the eight Exception Status outputs are device + specific.
+
+ Request
+
+
+ Response
+
+
+ Error
+
+
+

08(0x08) Diagnostics (Serial Line only)

+ MODBUS function code 08 provides a series of tests for checking the + communication system between a client device and a server, or for + checking various internal error conditions within a server.
+
+ The function uses a twobyte sub-function code field in the query to + define the type of test to be performed. The server echoes both the + function code and sub -function code in a normal response. Some of the + diagnostics cause data to be returned from the remote device in the + data field of a normal response.
+
+ In general, issuing a diagnostic function to a remote device does not + affect the running of the user program in the remote device. User + logic, like discrete and registers, is not accessed by the + diagnostics. Certain functions can optionally reset error counters in + the remote device.
+
+ A server device can, however, be forced into Listen Only Mode in + which it will monitor the messages on the communications system but + not respond to them. This can affect the outcome of your application + program if it depends upon any fu rther exchange of data with the + remote device. Generally, the mode is forced to remove a + malfunctioning remote device from the communications system.
+
+ The following diagnostic functions are dedicated to serial line + devices.
+
+ The normal response to the Return Query Data request is to loopback + the same data. The function code and sub-function codes are also + echoed.
+
+ Request
+
+
+ Response
+
+
+ Error
+
+
+

Sub-function codes supported by the serial line devices

+
+ Here the list of sub-function codes supported by the serial line + devices.
+
+
+
+

11(0x0B) Get Comm Event Counter (Serial Line + only)

+ This function code is used to get a status word and an event count + from the remote device's communication event counter.
+
+ By fetching the current count before and after a series of messages, a + client can determine whether the messages were handled normally by the + remote device.
+
+ The devices event counter is incremented once for each successful + message completion. It is not incremented for exception responses, + poll commands, or fetch event counter commands.
+
+ The event counter can be reset by means of the Diagnostics function + (code 08), with a sub - function of Restart Communications Option + (code 00 01) or Clear Counters and Diagnostic Register (code 00 0A).
+
+ The normal response contains a two byte status word, and a twobyte + event count. The status word will be all ones (FF FF hex) if a + previouslyissued program command is still being processed by the + remote device (a busy condition exists). Otherwise, the status word + will be all zeros.
+
+ Request
+
+
+ Response
+
+
+ Error
+
+
+

12(0x0C) Get Comm Event Log (Serial Line only) +

+ This function code is used to get a status word, event count, message + count, and a field of event bytes from the remote device.
+
+ The status word and event counts are identical to that returned by the + Get Communications Event Counter function (11, 0B hex).
+
+ The message counter contains the quantity of messages processed by the + remote device since its last restart, clear counters operation, or + power up. This count is identical to that returned by the Diagnostic + function (code 08), sub -function Return Bus Message Count (code 11, + 0B hex).
+
+ The event bytes field contains 0-64 bytes, with each byte + corresponding to the status of one MODBUS send or receive operation + for the remote device. The rem ote device enters the events into the + field in chronological order. Byte 0 is the most recent event. Each + new byte flushes the oldest byte from the field.
+
+ The normal response contains a two byte status word field, a twobyte + event count field, a twobyte message count field, and a field + containing 0 -64 bytes of events. A byte count field defines the total + length of the data in these four fields.
+
+ Request
+
+
+ Response
+
+ *N = Quantity of Events + 3 x 2 Bytes, (Length of Status, Event Count + and Message Count)
+
+ Error
+
+
+

15(0x0F) Write Multiple Coils

+ This function code is used to force each coil in a sequence of coils + to either ON or OFF in a remote device. The Request PDU specifies the + coil references to be forced. Coils are addressed starting at zero. + Therefore coil numbered 1 is addressed as 0.
+
+ The requested ON/OFF states are specified by contents of the request + data field. A logical ' 1' in a bit position of the field requests the + corresponding output to be ON. A logical '0' requests it to be OFF.
+
+ The normal response returns the function code, starting address, and + quantity of coils forced.
+
+ Request
+
+ N = Quantity of Outputs / 8, if the remainder is different of 0 N = + N+1
+ Response
+
+
+ Error
+
+
+

16(0x10) Write Multiple registers

+ This function code is used to write a block of contiguous registers (1 + to 123 registers) in a remote device.
+
+ The requested written values are specified in the request data field. + Data is packed as two bytes per register.
+
+ The normal response returns the function code, starting address, and + quantity of registers written.
+
+ Request
+
+ *N + = Quantity of registers
+

+ Response
+
+
+ Error
+
+
+

17(0x11) Report Server ID (Serial Line only) +

+ This function code is used to read the description of the type, the + current status, and other information specific to a remote device.
+
+ The format of a normal response is shown in the following example. The + data contents are specific to each type of device.
+
+ Request
+
+

+ Response
+
+
+ Error
+
+
+

20(0x14) Read File Record

+ This function code is used to perform a file record read. All Request + Data Lengths are provided in terms of number of bytes and all Record + Lengths are provided in terms of registers.
+
+ A file is an organization of records. Each file contains 10000 + records, addressed 0000 to 9999 decimal or 0X0000 to 0X270F. For + example, record 12 is addressed as 12.
+
+ The function can read multiple groups of references. The groups can be + separating (non - contiguous), but the references within each group + must be sequential.
+
+ Each group is defined in a separate sub-request field that contains + 7 bytes:
+
+
    +
  • The reference type: 1 byte (must be specified as 6) The File + number: 2 bytes
  • +
+
    +
  • The starting record number within the file: 2 bytes The length + of the record to be read: 2 bytes.
  • +
+
    +
  • The quantity of registers to be read, combined with all other + fields in the expected response, must not exceed the allowable + length of the MODBUS PDU : 253 bytes.
  • +
+
    +
  • The normal response is a series of sub-responses, one for each + sub-request. The byte count field is the total combined count of + bytes in all sub -responses. In addition, each sub-response + contains a field that shows its own byte count.
  • +
+ Request
+
+
+
+ Response
+
+
+ Error
+
+
+

21(0x15) Write File Record

+ This function code is used to perform a file record write. All Request + Data Lengths are provided in terms of number of bytes and all Record + Lengths are provided in terms of the number of 16-bit words.
+
+ A file is an organization of records. Each file contains 10000 + records, addressed 0000 to 9999 decimal or 0X0000 to 0X270F. For + example, record 12 is addressed as 12.
+
+ The function can write multiple groups of references. The groups can + be separate, i.e. n on contiguous, but the references within each + group must be sequential.
+
+ Each group is defined in a separate sub-request field that contains + 7 bytes plus the data:
+
+ The reference type: 1 byte (must be specified as 6) The file number: 2 + bytes
+
+ The starting record number within the file: 2 bytes The length of the + record to be written: 2 bytes The data to be written: 2 bytes per + register.
+
+ The quantity of registers to be written, combined with all other + fields in the request, must not exceed the allowable length of the + MODBUS PDU : 253bytes.
+
+ The normal response is an echo of the request.
+
+ Request
+
+
+
+ Response
+
+
+ Error
+
+
+

22(0x16) Mask Write Register

+ This function code is used to modify the contents of a specified + holding register using a combination of an AND mask, an OR mask, and + the register's current contents. The function can be used to set or + clear individual bits in the register.
+
+ The request specifies the holding register to be written, the data to + be used as the AND mask, and the data to be used as the OR mask. + Registers are addressed starting at zero. Therefore registers 1-16 are + addressed as 0-15.
+
+ The functions algorithm is:
+
+ Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT + And_Mask))
+
+ Request
+
+
+ Response
+
+
+ Error
+
+
+

23(0x17) Read/Write Multiple Registers

+ This function code performs a combination of one read operation and + one write operation in a single MODBUS transaction. The write + operation is performed before the read.
+
+ Holding registers are addressed starting at zero. Therefore holding + registers 1 -16 are addressed in the PDU as 0-15.
+
+ The request specifies the starting address and number of holding + registers to be read as well as the starting address, number of + holding registers, and the data to be written. The byte count + specifies the number of bytes to follow in the write data field.
+
+ The normal response contains the data from the group of registers that + were read. The byte count field specifies the quantity of bytes to + follow in the read data field.
+
+ Request
+
+ N = Quantity to Write
+
+ Response
+
+ *N' + = Quantity to Read
+

+ Error
+
+
+

24(0x18) Read FIFO Queue

+ This function code allows to read the contents of a First-In-First-Out + (FIFO) queue of register in a remote device. The function returns a + count of the registers in the queue, followed by the queued data. Up + to 32 registers can be read: the count, plus up to 31 queued data + registers. The queue count register is returned first, followed by the + queued data registers.
+
+ The function reads the queue contents, but does not clear them.
+
+ In a normal response, the byte count shows the quantity of bytes to + follow, including the queue count bytes and value register bytes (but + not including the error check field).
+
+ The queue count is the quantity of data registers in the queue (not + including the count register).
+
+ If the queue count exceeds 31, an exception response is returned with + an error code of 03 (Illegal Data Value).
+
+ Request
+
+
+ Response
+
+ *N + = FIFO count
+

+ Error
+
+
+
+
+
+
+
+
+
+
+ +
+ + diff --git a/Applications/qModMaster/ManModbus/funcs.html b/Applications/qModMaster/ManModbus/funcs.html new file mode 100644 index 0000000..88f593b --- /dev/null +++ b/Applications/qModMaster/ManModbus/funcs.html @@ -0,0 +1,123 @@ + + + + + MODBUS_MANUAL + + + + + +
+ +
+ +
+ +

Function Code Categories

+ There are three categories of MODBUS Functions codes. They are :
+
+

Public Function Codes

+
+
    +
  • Are well defined function codes ,
  • +
+
    +
  • guaranteed to be unique,
  • +
+
    +
  • validated by the MODBUS.org community,
  • +
+
    +
  • publicly documented
  • +
+
    +
  • have available conformance test,
  • +
+
    +
  • includes both defined public assigned function codes as well as + unassigned function codes reserved for future use.
  • +
+

User-Defined Function Codes

+
+
    +
  • there are two ranges of user-defined function codes, i.e. 65 to + 72 and from 100 to 110 decimal.
  • +
+
    +
  • user can select and implement a function code that is not + supported by the specification.
  • +
+
    +
  • there is no guarantee that the use of the selected function code + will be unique
  • +
+
    +
  • if the user wants to re-position the functionality as a public + function code, he must initiate an RFC to introduce the change + into the public category and to have a new public function code + assigned.
  • +
+
    +
  • MODBUS Organization, Inc expressly reserves the right to develop + the proposed RFC.
  • +
+

Reserved Function Codes

+
+
    +
  • Function Codes currently used by some companies for legacy + products and that are not available for public use.
  • +
+
    +
  • Informative Note: The reader is asked refer to Annex A + (Informative) MODBUS RESERVED FUNCTION CODES, SUBCODES AND MEI + TYPES.
  • +
+
+
+

Public Function Code Definition

+
+
+
+
+
+
+ +
+ + diff --git a/Applications/qModMaster/ManModbus/index.html b/Applications/qModMaster/ManModbus/index.html new file mode 100644 index 0000000..c39b222 --- /dev/null +++ b/Applications/qModMaster/ManModbus/index.html @@ -0,0 +1,282 @@ + + + + + MODBUS_MANUAL + + + + + +
+ +
+ +
+ +

Introduction

+ MODBUS is an application layer messaging protocol, positioned at level + 7 of the OSI model, which provides client/server communication between + devices connected on different types of buses or networks.
+
+ The industrys serial de facto standard since 1979, MOD BUS continues + to enable millions of automation devices to communicate. Today, + support for the simple and elegant structure of MODBUS continues to + grow. The Internet community can access MODBUS at a reserved system + port 502 on the TCP/IP stack.
+
+ MODBUS is a request/reply protocol and offers services specified by + function codes. MODBUS function codes are elements of MODBUS + request/reply PDUs. The objective of this document is to describe the + function codes used within the framework of MODBUS transactions.
+
+ MODBUS is an application layer messaging protocol for client/server + communication between devices connected on different types of buses or + networks.
+
+ It is currently implemented using:
+
    +
  • TCP/IP over Ethernet
  • +
+
    +
  • Asynchronous serial transmission over a variety of media (wire : + EIA/TIA -232-E, EIA-422, EIA/TIA-485-A; fiber, radio, etc.)
  • +
+
    +
  • MODBUS PLUS, a high speed token passing network
  • +
+

Protocol description

+ The MODBUS protocol defines a simple protocol data unit (PDU) + independent of the underlying communication layers. The mapping of + MODBUS protocol on specific buses or network can introduce some + additional fields on the application data unit (ADU).
+
+
+
+ The MODBUS application data unit is built by the client that initiates + a MODBUS transaction. The function indicates to the server what kind + of action to perform. The MODBUS application protocol establishes the + format of a request initiated by a client.
+
+ The function code field of a MODBUS data unit is coded in one byte. + Valid codes are in the range of 1 ... 255 decimal (the range 128 255 + is reserved and used for exception responses). When a message is sent + from a Client to a Server device the function code field tells the + server what kind of action to perform. Function code "0" is not valid.
+
+ Sub-function codes are added to some function codes  to define + multiple actions.
+
+ The data field of messages sent from a client to server devices + contains additional information that the server uses to take the + action defined by the function code. This can include items like + discrete and register addresses, the quantity of items to be handled, + and the count of actual data bytes in the field.
+
+ The data field may be nonexistent (of zero length) in certain kinds of + requests, in this case the server does not require any additional + information. The function code alone specifies the action.
+
+ If no error occurs related to the MODBUS function requested in a + properly received MODBUS ADU the data field of a response from a + server to a client contains the data requested. If an error related to + the MODBUS function requested occurs, the field contains an exception + code that the server application can use to determine the next action + to be taken.
+
+ For example a client can read the ON / OFF states of a group of + discrete outputs or inputs or it can read/write the data contents of a + group of registers.
+
+ When the server responds to the client, it uses the function code + field to indicate either a normal (error-free) response or that some + kind of error occurred (called an exception response). For a normal + response, the server simply echoes to the request the original + function code.
+
+
+
+
+ For an exception response, the server returns a code that is + equivalent to the original function code from the request PDU with its + most significant bit set to logic 1.
+
+
+
+ Note: It is desirable to manage a time out in order not to + indefinitely wait for an answer which will perhaps never arrive.
+
+ The size of the MODBUS PDU is limited by the size constraint inherited + from the first MODBUS implementation on Serial Line network (max. + RS485 ADU = 256 bytes).
+
+ Therefore:
+
+ MODBUS PDU for serial line communication = 256 - Server address (1 + byte) - CRC (2 bytes) = 253 bytes.
+
+ Consequently:
+
+ RS232 / RS485 ADU = 253 bytes + Server address (1 byte) + CRC (2 + bytes) = 256 bytes. TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 + bytes.
+
+ The MODBUS protocol defines three PDUs. They are :
+
+     MODBUS Request PDU, mb_req_pdu
+
+     MODBUS Response PDU, mb_rsp_pdu
+
+     MODBUS Exception Response PDU, mb_excep_rsp_pdu
+
+ The mb_req_pdu is defined as:
+
+ mb_req_pdu = {function_code, request_data},    where
+ function_code = [1 byte] MODBUS function code,
+ request_data = [n bytes] This field is function code dependent and + usually contains information such as variable references, variable + counts, data offsets, sub-function codes etc.
+
+ The mb_rsp_pdu is defined as:
+ mb_rsp_pdu = {function_code, response_data}, where function_code = [1 + byte] MODBUS function code response_data = [n bytes] This field is + function code dependent and usually contains information such as + variable references, variable counts, data offsets, sub-function + codes, etc.
+
+ The mb_excep_rsp_pdu is defined as:
+ mb_excep_rsp_pdu = {exception-function_code, request_data}, where + exception-function_code = [1 byte] MODBUS function code + 0x80 + exception_code = [1 byte] MODBUS Exception Code Defined in table.
+
+

MODBUS Data model

+ MODBUS bases its data model on a series of tables that have + distinguishing characteristics. The four primary tables are:
+
+
+
+ The distinctions between inputs and outputs, and between bit + -addressable and word-addressable data items, do not imply any + application behavior. It is perfectly acceptable, and very common, to + regard all four tables as overlaying one another, if this is the most + natural interpretation on the target machine in question.
+
+ For each of the primary tables, the protocol allows individual + selection of 65536 data items, and the operations of read or write of + those items are designed to span multiple consecutive data items up to + a data size limit which is dependent on the transaction function code.
+
+ Its obvious that all the data handled via MODBUS (bits, registers) + must be located in device application memory. But physical address in + memory should not be confused with data reference. The only + requirement is to link data reference with physical address.
+
+ MODBUS logical reference numbers, which are used in MODBUS funct ions, + are unsigned integer indices starting at zero.
+
+

MODBUS Addressing model

+ The MODBUS application protocol defines precisely PDU addressing + rules.
+
+ In a MODBUS PDU each data is addressed from 0 to 65535.
+
+ It also defines clearly a MODBUS data model composed of 4 blocks that + comprises several elements numbered from 1 to n.
+
+ In the MODBUS data Model each element within a data block is numbered + from 1 to n.
+
+ Afterwards the MODBUS data model has to be bound to the device + application ( IEC -61131 object, or other application model).
+
+ The pre-mapping between the MODBUS data model and the device + application is totally vendor device specific.
+
+
+
+

Define MODBUS Transaction

+
+ The following state diagram describes the generic processing of a + MODBUS transaction in server side.
+
+
+
+ Once the request has been processed by a server, a MODBUS response + using the adequate MODBUS server transaction is built.
+
+ Depending on the result of the processing two types of response are + built :
+ A positive MODBUS response :
+
+
    +
  • the response function code = the request function code
  • +
+
+ A MODBUS Exception response:
+
+
    +
  • the objective is to provide to the client relevant information + concerning the error detected during the processing 
  • +
+
    +
  • the exception function code = the request function code + 0x80
  • +
+
    +
  • an  exception  code  is  provided  + to  indicate  the  reason  of  the  + error.
  • +
+  
+
+
+
+
+
+
+ +
+ + diff --git a/Applications/qModMaster/ManModbus/links.html b/Applications/qModMaster/ManModbus/links.html new file mode 100644 index 0000000..e93bcfe --- /dev/null +++ b/Applications/qModMaster/ManModbus/links.html @@ -0,0 +1,87 @@ + + + + + MODBUS_MANUAL + + + + + +
+ +
+ + +
+ +
+ + diff --git a/Applications/qModMaster/ManModbus/style/Thumbs.db b/Applications/qModMaster/ManModbus/style/Thumbs.db new file mode 100644 index 0000000..ce37b19 Binary files /dev/null and b/Applications/qModMaster/ManModbus/style/Thumbs.db differ diff --git a/Applications/qModMaster/ManModbus/style/background.png b/Applications/qModMaster/ManModbus/style/background.png new file mode 100644 index 0000000..53b01a7 Binary files /dev/null and b/Applications/qModMaster/ManModbus/style/background.png differ diff --git a/Applications/qModMaster/ManModbus/style/bullet.png b/Applications/qModMaster/ManModbus/style/bullet.png new file mode 100644 index 0000000..ae6094b Binary files /dev/null and b/Applications/qModMaster/ManModbus/style/bullet.png differ diff --git a/Applications/qModMaster/ManModbus/style/graphic.png b/Applications/qModMaster/ManModbus/style/graphic.png new file mode 100644 index 0000000..cd4e4be Binary files /dev/null and b/Applications/qModMaster/ManModbus/style/graphic.png differ diff --git a/Applications/qModMaster/ManModbus/style/header.jpg b/Applications/qModMaster/ManModbus/style/header.jpg new file mode 100644 index 0000000..3c9e3fc Binary files /dev/null and b/Applications/qModMaster/ManModbus/style/header.jpg differ diff --git a/Applications/qModMaster/ManModbus/style/link.png b/Applications/qModMaster/ManModbus/style/link.png new file mode 100644 index 0000000..8e16400 Binary files /dev/null and b/Applications/qModMaster/ManModbus/style/link.png differ diff --git a/Applications/qModMaster/ManModbus/style/search.png b/Applications/qModMaster/ManModbus/style/search.png new file mode 100644 index 0000000..01109b1 Binary files /dev/null and b/Applications/qModMaster/ManModbus/style/search.png differ diff --git a/Applications/qModMaster/ManModbus/style/style.css b/Applications/qModMaster/ManModbus/style/style.css new file mode 100644 index 0000000..332059e --- /dev/null +++ b/Applications/qModMaster/ManModbus/style/style.css @@ -0,0 +1,296 @@ +html +{ height: 100%;} + +* +{ margin: 0; + padding: 0;} + +body +{ font: normal .80em 'trebuchet ms', arial, sans-serif; + background: #F0EFE2 url(background.png) repeat; + color: #777;} + +p +{ padding: 0 0 20px 0; + line-height: 1.7em;} + +img +{ border: 0;} + +h1, h2, h3, h4, h5, h6 +{ font: normal 175% 'century gothic', arial, sans-serif; + color: #43423F; + margin: 0 0 15px 0; + padding: 15px 0 5px 0;} + +h2 +{ font: normal 175% 'century gothic', arial, sans-serif; + color: #000;} + +h4, h5, h6 +{ margin: 0; + padding: 0 0 5px 0; + font: normal 120% arial, sans-serif; + color: #000;} + +h5, h6 +{ font: italic 95% arial, sans-serif; + padding: 0 0 15px 0; + color: #000;} + +h6 +{ color: #362C20;} + +a, a:hover +{ outline: none; + text-decoration: underline; + color: #000;} + +a:hover +{ text-decoration: none;} + +.left +{ float: left; + width: auto; + margin-right: 10px;} + +.right +{ float: right; + width: auto; + margin-left: 10px;} + +.center +{ display: block; + text-align: center; + margin: 20px auto;} + +blockquote +{ margin: 20px 0; + padding: 10px 20px 0 20px; + border: 1px solid #E5E5DB; + background: #FFF;} + +ul +{ margin: 2px 0 22px 17px;} + +ul li +{ list-style-type: circle; + margin: 0 0 6px 0; + padding: 0 0 4px 5px;} + +ol +{ margin: 8px 0 22px 20px;} + +ol li +{ margin: 0 0 11px 0;} + +#main, #logo, #menubar, #site_content, #footer +{ margin-left: auto; + margin-right: auto;} + +#header +{ background: #1D1D1D url(header.jpg) repeat; + height: 240px;} + +#logo +{ width: 825px; + position: relative; + height: 168px;} + +#logo #logo_text +{ position: absolute; + top: 20px; + left: 0;} + +#logo h1, #logo h2 +{ font: normal 300% 'century gothic', arial, sans-serif; + border-bottom: 0; + text-transform: none; + margin: 0;} + +#logo_text h1, #logo_text h1 a, #logo_text h1 a:hover +{ padding: 22px 0 0 0; + color: #FFF; + letter-spacing: 0.1em; + text-decoration: none;} + +#logo_text h1 a .logo_colour +{ color: #000;} + +#logo_text h2 +{ font-size: 100%; + padding: 4px 0 0 0; + color: #DDD;} + +#menubar +{ width: 900px; + height: 72px; + padding: 0; + background: transparent url(transparent_light.png) repeat;} + +ul#menu, ul#menu li +{ float: left; + margin: 0; + padding: 0;} + +ul#menu li +{ list-style: none;} + +ul#menu li a +{ letter-spacing: 0.1em; + font: normal 100% arial, sans-serif; + display: block; + float: left; + height: 37px; + padding: 29px 26px 6px 26px; + text-align: center; + color: #000; + text-transform: uppercase; + text-decoration: none; + background: transparent;} + +ul#menu li a:hover, ul#menu li.selected a, ul#menu li.selected a:hover +{ color: #FFF; + background: transparent url(transparent.png) repeat;} + +#site_content +{ width: 854px; + overflow: hidden; + margin: 0 auto 0 auto; + padding: 20px 24px 20px 20px; + background: transparent url(transparent_light.png) repeat;} + +.sidebar +{ float: left; + width: 190px; + padding: 0 15px 20px 15px;} + +.sidebar ul +{ width: 178px; + padding: 4px 0 0 0; + margin: 4px 0 30px 0;} + +.sidebar li +{ list-style: none; + padding: 0 0 7px 0; } + +.sidebar li a, .sidebar li a:hover +{ padding: 0 0 0 40px; + display: block; + background: transparent url(link.png) no-repeat left center;} + +.sidebar li a.selected +{ color: #444; + text-decoration: none;} + +#content +{ text-align: left; + float: right; + width: 595px; + padding: 0;} + +#content ul +{ margin: 2px 0 22px 0px;} + +#content ul li +{ list-style-type: none; + background: url(bullet.png) no-repeat; + margin: 0 0 6px 0; + padding: 0 0 4px 25px; + line-height: 1.5em;} + +#footer +{ width: 900px; + font: normal 100% 'lucida sans unicode', arial, sans-serif; + height: 33px; + padding: 24px 0 5px 0; + text-align: center; + background: transparent url(transparent_light.png) repeat; + color: #000; + text-transform: uppercase; + letter-spacing: 0.1em;} + +#footer a +{ color: #000; + text-decoration: none;} + +#footer a:hover +{ color: #000; + text-decoration: underline;} + +.search +{ color: #5D5D5D; + border: 1px solid #BBB; + width: 134px; + padding: 4px; + font: 100% arial, sans-serif;} + +#colours +{ height: 0px; + text-align: right; + padding: 66px 16px 0px 300px;} + +.form_settings +{ margin: 15px 0 0 0;} + +.form_settings p +{ padding: 0 0 4px 0;} + +.form_settings span +{ float: left; + width: 200px; + text-align: left;} + +.form_settings input, .form_settings textarea +{ padding: 5px; + width: 299px; + font: 100% arial; + border: 1px solid #E5E5DB; + background: #FFF; + color: #47433F;} + +.form_settings .submit +{ font: 100% arial; + border: 1px solid; + width: 99px; + margin: 0 0 0 212px; + height: 33px; + padding: 2px 0 3px 0; + cursor: pointer; + background: #000; + color: #FFF;} + +.form_settings textarea, .form_settings select +{ font: 100% arial; + width: 299px;} + +.form_settings select +{ width: 310px;} + +.form_settings .checkbox +{ margin: 4px 0; + padding: 0; + width: 14px; + border: 0; + background: none;} + +.separator +{ width: 100%; + height: 0; + border-top: 1px solid #D9D5CF; + border-bottom: 1px solid #FFF; + margin: 0 0 20px 0;} + +table +{ margin: 10px 0 30px 0;} + +table tr th, table tr td +{ background: #3B3B3B; + color: #FFF; + padding: 7px 4px; + text-align: left;} + +table tr td +{ background: #F0EFE2; + color: #47433F; + border-top: 1px solid #FFF;} \ No newline at end of file diff --git a/Applications/qModMaster/ManModbus/style/transparent.png b/Applications/qModMaster/ManModbus/style/transparent.png new file mode 100644 index 0000000..93e4d8f Binary files /dev/null and b/Applications/qModMaster/ManModbus/style/transparent.png differ diff --git a/Applications/qModMaster/ManModbus/style/transparent_light.png b/Applications/qModMaster/ManModbus/style/transparent_light.png new file mode 100644 index 0000000..ca943f3 Binary files /dev/null and b/Applications/qModMaster/ManModbus/style/transparent_light.png differ diff --git a/Applications/qModMaster/README.txt b/Applications/qModMaster/README.txt new file mode 100644 index 0000000..2ca76bf --- /dev/null +++ b/Applications/qModMaster/README.txt @@ -0,0 +1,31 @@ +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 . + +1.Description +QModMaster is a free Qt-based implementation of a ModBus master application. A graphical user interface allows easy communication with ModBus RTU and TCP slaves. QModMaster also includes a bus monitor for examining all traffic on the bus. + +2.Software +QModMaster is based on libmodbus 3.1.0-1 for modbus communication and on QsLog for logging. Supports both Windows and Linux. + +3.Source code is availiable for Windows and Linux for compilation using Qt 5.2.1 . Install Qt on your system,open QModMaster project file (QModMaster.pro) and compile. + +4.For Windows a pre-compiled binary is availiable. It does not require instalation, just unzip and run. + +5.To configure the logging level set the 'LoggingLevel' in QModMaster.ini file +- TraceLevel : 0 +- DebugLevel : 1 +- InfoLevel : 2 +- WarnLevel : 3 [default] +- ErrorLevel : 4 +- FatalLevel : 5 +- OffLevel : 6 \ No newline at end of file diff --git a/Applications/qModMaster/READMEWin32.txt b/Applications/qModMaster/READMEWin32.txt new file mode 100644 index 0000000..6ebf881 --- /dev/null +++ b/Applications/qModMaster/READMEWin32.txt @@ -0,0 +1,5 @@ + +libmodbus - config.h +-------------------- +#define HAVE_DECL_TIOCSRS485 0 +#define HAVE_DECL_TIOCM_RTS 0 diff --git a/Applications/qModMaster/forms/about.cpp b/Applications/qModMaster/forms/about.cpp new file mode 100644 index 0000000..8edc4d3 --- /dev/null +++ b/Applications/qModMaster/forms/about.cpp @@ -0,0 +1,22 @@ +#include "about.h" +#include "ui_about.h" +#include "modbus-version.h" + +const QString VER = "QModMaster 0.5.3-beta"; +const QString LIB_VER = LIBMODBUS_VERSION_STRING; +const QString URL = "Sourceforge Project Home Page"; + +About::About(QWidget *parent) : + QDialog(parent), + ui(new Ui::About) +{ + ui->setupUi(this); + ui->lblVersion->setText(VER); + ui->lblLibVersion->setText("libmodbus " + LIB_VER); + ui->lblURL->setText(URL); +} + +About::~About() +{ + delete ui; +} diff --git a/Applications/qModMaster/forms/about.h b/Applications/qModMaster/forms/about.h new file mode 100644 index 0000000..989b44e --- /dev/null +++ b/Applications/qModMaster/forms/about.h @@ -0,0 +1,25 @@ +#ifndef ABOUT_H +#define ABOUT_H + +#include + +namespace Ui { + class About; +} + +class About : public QDialog +{ + Q_OBJECT + +public: + explicit About(QWidget *parent = 0); + ~About(); + +private: + Ui::About *ui; + +protected: + +}; + +#endif // ABOUT_H diff --git a/Applications/qModMaster/forms/about.ui b/Applications/qModMaster/forms/about.ui new file mode 100644 index 0000000..5013ee1 --- /dev/null +++ b/Applications/qModMaster/forms/about.ui @@ -0,0 +1,92 @@ + + + About + + + + 0 + 0 + 320 + 90 + + + + + 320 + 90 + + + + + 400 + 150 + + + + About + + + + :/icons/info16.png:/icons/info16.png + + + true + + + + + + + 18 + 75 + true + + + + QModMaster + + + Qt::AlignCenter + + + + + + + + 75 + true + + + + lib version + + + Qt::AlignCenter + + + + + + + + 75 + true + + + + http:// + + + Qt::AlignCenter + + + true + + + + + + + + diff --git a/Applications/qModMaster/forms/busmonitor.cpp b/Applications/qModMaster/forms/busmonitor.cpp new file mode 100644 index 0000000..d103f88 --- /dev/null +++ b/Applications/qModMaster/forms/busmonitor.cpp @@ -0,0 +1,353 @@ +#include +#include +#include +#include +#include +#include "busmonitor.h" +#include "ui_busmonitor.h" +#include "./src/rawdatadelegate.h" + + +BusMonitor::BusMonitor(QWidget *parent, RawDataModel *rawDataModel) : + QMainWindow(parent), + ui(new Ui::BusMonitor), + m_rawDataModel(rawDataModel) +{ + ui->setupUi(this); + ui->lstRawData->setModel(m_rawDataModel->model); + //TODO : use delegate + //ui->lstRawData->setItemDelegate(new RawDataDelegate()); + //Setup Toolbar + ui->toolBar->addAction(ui->actionSave); + ui->toolBar->addAction(ui->actionClear); + ui->toolBar->addAction(ui->actionSxS); + ui->toolBar->addAction(ui->actionExit); + connect(ui->actionSave,SIGNAL(triggered()),this,SLOT(save())); + connect(ui->actionClear,SIGNAL(triggered()),this,SLOT(clear())); + connect(ui->actionSxS,SIGNAL(triggered()),this,SLOT(SxS())); + connect(ui->actionExit,SIGNAL(triggered()),this,SLOT(exit())); + connect(ui->lstRawData,SIGNAL(activated(QModelIndex)),this,SLOT(selectedRow(QModelIndex))); + connect(ui->lstRawData,SIGNAL(clicked(QModelIndex)),this,SLOT(selectedRow(QModelIndex))); + ui->lblADU2->setVisible(false); + ui->txtADU2->setVisible(false); + +} + +BusMonitor::~BusMonitor() +{ + delete ui; +} + +void BusMonitor::save() +{ + + + //Select file + QString fileName = QFileDialog::getSaveFileName(NULL,"Save File As...", + QDir::homePath(),"Text (*.txt)",0, + QFileDialog::DontConfirmOverwrite); + + //Open File + if (fileName.isEmpty()) + return; + //continue only if a file name exists + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly)) return; + + //Text Stream + QTextStream ts(&file); + QStringList sl = m_rawDataModel->model->stringList(); + + //iterate + for (int i = 0; i < sl.size(); ++i) + ts << sl.at(i) << Qt::endl; + + //Close File + file.close(); + +} + +void BusMonitor::clear() +{ + + //qDebug()<< "BusMonitor : clear" ; + + m_rawDataModel->clear(); + ui->txtADU1->clear(); + +} + +void BusMonitor::SxS() +{ + + //qDebug()<< "BusMonitor : SxS" ; + if (ui->actionSxS->isChecked()) { + ui->lblADU1->setText("Tx ADU"); + ui->lblADU2->setVisible(true); + ui->txtADU2->setVisible(true); + } + else { + ui->lblADU1->setText("ADU"); + ui->lblADU2->setVisible(false); + ui->txtADU2->setVisible(false); + } + + ui->txtADU1->setPlainText(""); + ui->txtADU2->setPlainText(""); + +} + +void BusMonitor::exit() +{ + + //qDebug()<< "BusMonitor : exit" ; + + this->close(); + +} + +void BusMonitor::closeEvent(QCloseEvent *event) +{ + + m_rawDataModel->enableAddLines(false); + clear(); + event->accept(); + +} + +void BusMonitor::showEvent(QShowEvent *event) +{ + + m_rawDataModel->enableAddLines(true); + event->accept(); + +} + +void BusMonitor::selectedRow(const QModelIndex & selected) +{ +int rowCount; +int currRow; +QModelIndex next; +QModelIndex prev; + + rowCount = ui->lstRawData->model()->rowCount(); + currRow = selected.row(); + if (currRow < rowCount - 1){ + next = ui->lstRawData->model()->index(currRow +1, 0); + qDebug()<< "Next Row : "<< next.row(); + qDebug()<< "Next Data : "<< next.data(); + } + + + if (ui->actionSxS->isChecked()) { //Side by Side Tx - Rx packests + if (selected.data().canConvert(QVariant::String)) { + QString val = selected.data().value(); + qDebug()<< "BusMonitor : selectedRow - " << val; + if (val.indexOf("Sys") > -1) + parseSysMsg(val, ui->txtADU1); + else if (val.indexOf("Tx") > -1){ + parseTxMsg(val, ui->txtADU1); + if (currRow < rowCount - 1){ + next = ui->lstRawData->model()->index(currRow + 1, 0); + QString valNext = next.data().value(); + if (valNext.indexOf("Rx") > -1) + parseRxMsg(valNext, ui->txtADU2); + } + else { + ui->txtADU2->setPlainText("-"); + } + } + else if (val.indexOf("Rx") > -1){ + parseRxMsg(val, ui->txtADU2); + if (currRow > 0){ + prev = ui->lstRawData->model()->index(currRow - 1, 0); + QString valPrev = prev.data().value(); + if (valPrev.indexOf("Tx") > -1) + parseTxMsg(valPrev, ui->txtADU1); + } + else { + ui->txtADU1->setPlainText("-"); + } + } + else + ui->txtADU1->setPlainText("Type : Unknown Message"); + } + } + else { + if (selected.data().canConvert(QVariant::String)) { + QString val = selected.data().value(); + qDebug()<< "BusMonitor : selectedRow - " << val; + if (val.indexOf("Sys") > -1) + parseSysMsg(val, ui->txtADU1); + else if (val.indexOf("Tx") > -1) + parseTxMsg(val, ui->txtADU1); + else if (val.indexOf("Rx") > -1) + parseRxMsg(val, ui->txtADU1); + else + ui->txtADU1->setPlainText("Type : Unknown Message"); + } + } +} + +void BusMonitor::parseTxMsg(QString msg, QPlainTextEdit* txtADU) +{ + txtADU->setPlainText("Type : Tx Message"); + QStringList row = msg.split(QRegularExpression("\\s+")); + txtADU->appendPlainText("Timestamp : " + row[2]); + if (msg.indexOf("RTU") > -1){//RTU message + QStringList pdu; + if (row.length() < 5){//check message length + txtADU->appendPlainText("Error! Cannot parse Message"); + return; + } + for (int i = 4; i < row.length() - 1 ; i++) + pdu.append(row[i]); + parseTxPDU(pdu, "Slave Addr : ", txtADU); + txtADU->appendPlainText("CRC : " + pdu[pdu.length() - 2] + pdu[pdu.length() - 1]); + } + else if (msg.indexOf("TCP") > -1){//TCP message + if (row.length() < 11){//check message length + txtADU->appendPlainText("Error! Cannot parse Message"); + return; + } + txtADU->appendPlainText("Transaction ID : " + row[4] + row[5]); + txtADU->appendPlainText("Protocol ID : " + row[6] + row[7]); + txtADU->appendPlainText("Length : " + row[8] + row[9]); + QStringList pdu; + for (int i = 10; i < row.length() - 1 ; i++) + pdu.append(row[i]); + parseTxPDU(pdu, "Unit ID : ", txtADU); + } + else + txtADU->appendPlainText("Error! Cannot parse Message"); + +} + +void BusMonitor::parseTxPDU(QStringList pdu, QString slave, QPlainTextEdit* txtADU) +{ + + if (pdu.length() < 6){//check message length + txtADU->appendPlainText(slave + pdu[0]); + txtADU->appendPlainText("Function Code : " + pdu[1]); + //txtADU->appendPlainText("Error! Cannot parse Message"); + return; + } + txtADU->appendPlainText(slave + pdu[0]); + txtADU->appendPlainText("Function Code : " + pdu[1]); + txtADU->appendPlainText("Starting Address : " + pdu[2] + pdu[3]); + bool ok; + int fcode = pdu[1].toInt(&ok,16); + if (fcode == 1 || fcode == 2 || fcode == 3 || fcode == 4){//read + txtADU->appendPlainText("Quantity of Registers : " + pdu[4] + pdu[5]); + } + else if (fcode == 5 || fcode == 6){//write + txtADU->appendPlainText("Output Value : " + pdu[4] + pdu[5]); + } + else if (fcode == 15 || fcode == 16){//write multiple + txtADU->appendPlainText("Quantity of Registers : " + pdu[4] + pdu[5]); + if (pdu.length() < 8){//check message length + txtADU->appendPlainText("Error! Cannot parse Message"); + return; + } + txtADU->appendPlainText("Byte Count : " + pdu[6]); + int byteCount = pdu[6].toInt(&ok,16); + QString outputValues = ""; + for (int i = 7; i < 7 + byteCount; i++) + outputValues += pdu[i] + " "; + txtADU->appendPlainText("Output Values : " + outputValues); + } + +} + +void BusMonitor::parseRxMsg(QString msg, QPlainTextEdit* txtADU) +{ + txtADU->setPlainText("Type : Rx Message"); + QStringList row = msg.split(QRegularExpression("\\s+")); + txtADU->appendPlainText("Timestamp : " + row[2]); + if (msg.indexOf("RTU") > -1){//RTU message + QStringList pdu; + if (row.length() < 5){//check message length + txtADU->appendPlainText("Error! Cannot parse Message"); + return; + } + for (int i = 4; i < row.length() - 1 ; i++) + pdu.append(row[i]); + parseRxPDU(pdu, "Slave Addr : ", txtADU); + txtADU->appendPlainText("CRC : " + pdu[pdu.length() - 2] + pdu[pdu.length() - 1]); + } + else if (msg.indexOf("TCP") > -1){//TCP message + if (row.length() < 11){//check message length + txtADU->appendPlainText("Error! Cannot parse Message"); + return; + } + txtADU->appendPlainText("Transaction ID : " + row[4] + row[5]); + txtADU->appendPlainText("Protocol ID : " + row[6] + row[7]); + txtADU->appendPlainText("Length : " + row[8] + row[9]); + QStringList pdu; + for (int i = 10; i < row.length() - 1 ; i++) + pdu.append(row[i]); + parseRxPDU(pdu, "Unit ID : ", txtADU); + } + else + txtADU->appendPlainText("Error! Cannot parse Message"); + +} + +void BusMonitor::parseRxPDU(QStringList pdu, QString slave, QPlainTextEdit* txtADU) +{ + + bool ok; + int fcode = pdu[1].toInt(&ok,16); + if (fcode == 1 || fcode == 2 || fcode == 3 || fcode == 4){//read + if (pdu.length() < 4){//check message length + txtADU->appendPlainText("Error! Cannot parse Message"); + return; + } + txtADU->appendPlainText(slave + pdu[0]); + txtADU->appendPlainText("Function Code : " + pdu[1]); + txtADU->appendPlainText("Byte Count : " + pdu[2]); + int byteCount = pdu[2].toInt(&ok,16); + QString inputValues = ""; + for (int i = 3; i < 3 + byteCount; i++) + inputValues += pdu[i] + " "; + txtADU->appendPlainText("Register Values : " + inputValues); + } + else if (fcode == 5 || fcode == 6){//write + if (pdu.length() < 6){//check message length + txtADU->appendPlainText("Error! Cannot parse Message"); + return; + } + txtADU->appendPlainText(slave + pdu[0]); + txtADU->appendPlainText("Function Code : " + pdu[1]); + txtADU->appendPlainText("Starting Address : " + pdu[2] + pdu[3]); + txtADU->appendPlainText("Output Value : " + pdu[4] + pdu[5]); + } + else if (fcode == 15 || fcode == 16){//write multiple + if (pdu.length() < 6){//check message length + txtADU->appendPlainText("Error! Cannot parse Message"); + return; + } + txtADU->appendPlainText(slave + pdu[0]); + txtADU->appendPlainText("Function Code : " + pdu[1]); + txtADU->appendPlainText("Starting Address : " + pdu[2] + pdu[3]); + txtADU->appendPlainText("Quantity of Registers : " + pdu[4] + pdu[5]); + } + else if (fcode > 0x80){//exception + if (pdu.length() < 3){//check message length + txtADU->appendPlainText("Error! Cannot parse Message"); + return; + } + txtADU->appendPlainText(slave + pdu[0]); + txtADU->appendPlainText("Function Code [80 + Rx Function Code] : " + pdu[1]); + txtADU->appendPlainText("Exception Code : " + pdu[2]); + } + +} + +void BusMonitor::parseSysMsg(QString msg, QPlainTextEdit* txtADU) +{ + txtADU->setPlainText("Type : System Message"); + QStringList row = msg.split(QRegularExpression("\\s+")); + txtADU->appendPlainText("Timestamp : " + row[2]); + txtADU->appendPlainText("Message" + msg.mid(msg.indexOf(" : "))); +} diff --git a/Applications/qModMaster/forms/busmonitor.h b/Applications/qModMaster/forms/busmonitor.h new file mode 100644 index 0000000..f7c1667 --- /dev/null +++ b/Applications/qModMaster/forms/busmonitor.h @@ -0,0 +1,43 @@ +#ifndef BUSMONITOR_H +#define BUSMONITOR_H + +#include +#include +#include +#include "src/rawdatamodel.h" + +namespace Ui { + class BusMonitor; +} + +class BusMonitor : public QMainWindow +{ + Q_OBJECT + +public: + explicit BusMonitor(QWidget *parent, RawDataModel *rawDataModel); + ~BusMonitor(); + +private: + Ui::BusMonitor *ui; + RawDataModel *m_rawDataModel; + void parseTxMsg(QString msg, QPlainTextEdit* txtADU); + void parseTxPDU(QStringList pdu, QString slave, QPlainTextEdit* txtADU); + void parseRxMsg(QString msg, QPlainTextEdit* txtADU); + void parseRxPDU(QStringList pdu, QString slave, QPlainTextEdit* txtADU); + void parseSysMsg(QString msg, QPlainTextEdit* txtADU); + +protected: + void closeEvent(QCloseEvent *event); + void showEvent(QShowEvent *event); + +private slots: + void clear(); + void exit(); + void save(); + void SxS(); + void selectedRow(const QModelIndex & selected); + +}; + +#endif // BUSMONITOR_H diff --git a/Applications/qModMaster/forms/busmonitor.ui b/Applications/qModMaster/forms/busmonitor.ui new file mode 100644 index 0000000..be5b910 --- /dev/null +++ b/Applications/qModMaster/forms/busmonitor.ui @@ -0,0 +1,233 @@ + + + BusMonitor + + + Qt::NonModal + + + + 0 + 0 + 542 + 474 + + + + + 540 + 474 + + + + + 720 + 720 + + + + Bus Monitor + + + + :/icons/TV-16.png:/icons/TV-16.png + + + + + 0 + 0 + + + + + 540 + 460 + + + + + + 12 + 2 + 522 + 432 + + + + + + + + 75 + true + + + + Raw Data + + + + + + + + 0 + 0 + + + + + 8 + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + + + + + + + + 75 + true + + + + ADU + + + + + + + + 75 + true + + + + Rx ADU + + + + + + + + 50 + false + + + + false + + + true + + + Qt::NoTextInteraction + + + + + + + true + + + true + + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + :/icons/edit-clear-16.png:/icons/edit-clear-16.png + + + Clear + + + Clear + + + true + + + + + + :/icons/Close-16.png:/icons/Close-16.png + + + Exit + + + Exit + + + + + + :/icons/save-16.png:/icons/save-16.png + + + Save + + + Save + + + + + true + + + + :/icons/data-sort-16.png:/icons/data-sort-16.png + + + actionSxS + + + Tx-Rx Side by Side + + + + + + + + diff --git a/Applications/qModMaster/forms/mainwindow.ui b/Applications/qModMaster/forms/mainwindow.ui new file mode 100644 index 0000000..cc604c7 --- /dev/null +++ b/Applications/qModMaster/forms/mainwindow.ui @@ -0,0 +1,779 @@ + + + MainWindow + + + + 0 + 0 + 564 + 400 + + + + + 564 + 400 + + + + + 720 + 720 + + + + QModMaster + + + + :/icons/connect-24.png:/icons/connect-24.png + + + + + + + + + + + + + 0 + 38 + + + + QFrame::Box + + + QFrame::Sunken + + + + 6 + + + 6 + + + + + Modbus Mode + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + cmbModbusMode + + + + + + + + RTU + + + + + TCP + + + + + + + + Slave Addr + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + sbSlaveID + + + + + + + 0 + + + 255 + + + 1 + + + + + + + Scan Rate (ms) + + + + + + + 1000 + + + 10000 + + + 500 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + 38 + + + + QFrame::Box + + + QFrame::Sunken + + + + 6 + + + 6 + + + + + Function Code + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + cmbFunctionCode + + + + + + + true + + + + Read Coils (0x01) + + + + + Read Discrete Inputs (0x02) + + + + + Read Holding Registers (0x03) + + + + + Read Input Registers (0x04) + + + + + Write Single Coil (0x05) + + + + + Write Single Register (0x06) + + + + + Write Multiple Coils (0x0f) + + + + + Write Multiple Registers (0x10) + + + + + + + + Start Address + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + sbStartAddress + + + + + + + 65535 + + + 10 + + + + + + + 0 + + + + Dec + + + + + Hex + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + 38 + + + + QFrame::Box + + + QFrame::Sunken + + + + 6 + + + + + Number of Coils + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + sbNoOfRegs + + + + + + + 1 + + + 2000 + + + + + + + Data Format + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + sbStartAddress + + + + + + + Bin + + + 0 + + + + Bin + + + + + Dec + + + + + Hex + + + + + Float + + + + + + + + Qt::RightToLeft + + + Signed + + + + + + + Precision + + + + + + + -1 + + + 7 + + + 3 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + false + + + true + + + true + + + true + + + true + + + + + + + + + 0 + 0 + 564 + 21 + + + + + File + + + + + + + + + Options + + + + + + + + + Help + + + + + + + View + + + + + + + + + + Commands + + + + + + + + + + + + + + + + TopToolBarArea + + + false + + + + + false + + + + + + :/icons/exit-16.png:/icons/exit-16.png + + + Exit + + + true + + + + + + :/icons/serial-pot-16.png:/icons/serial-pot-16.png + + + Modbus RTU... + + + true + + + + + + :/icons/ethernet-port-16.png:/icons/ethernet-port-16.png + + + Modbus TCP... + + + true + + + + + + :/icons/info-sign-16.png:/icons/info-sign-16.png + + + About... + + + true + + + + + + :/icons/TV-16.png:/icons/TV-16.png + + + Bus Monitor + + + true + + + + + + :/icons/options-16.png:/icons/options-16.png + + + Settings... + + + true + + + + + + :/icons/data-sort-16.png:/icons/data-sort-16.png + + + Read / Write + + + + + true + + + + :/icons/plug-disconnect-16.png + :/icons/plug-connect-16.png:/icons/plug-disconnect-16.png + + + Connect + + + + + true + + + + :/icons/cyclic-process-16.png:/icons/cyclic-process-16.png + + + Scan + + + + + + :/icons/reset-16.png:/icons/reset-16.png + + + Reset Counters + + + + + + :/icons/China-flag-16.png:/icons/China-flag-16.png + + + 简体中文 (zh_CN) + + + 简体中文 (zh_CN) + + + 简体中文 (zh_CN) + + + false + + + + + + :/icons/Taiwan-flag-16.png:/icons/Taiwan-flag-16.png + + + 繁體中文 (zh_TW) + + + 繁體中文 (zh_TW) + + + 繁體中文 (zh_TW) + + + false + + + + + + :/icons/usa-flag-16.png:/icons/usa-flag-16.png + + + English (en_US) + + + English (en_US) + + + English (en_US) + + + false + + + + + + :/icons/text-x-log-16.png:/icons/text-x-log-16.png + + + Log File + + + + + + :/icons/edit-clear-16.png:/icons/edit-clear-16.png + + + Clear Table + + + + + + :/icons/help-desk-icon-16.png:/icons/help-desk-icon-16.png + + + Modbus Manual + + + + + + :/icons/document-import-16.png:/icons/document-import-16.png + + + Load Session... + + + + + + :/icons/document-export-16.png:/icons/document-export-16.png + + + Save Session... + + + + + true + + + + :/icons/Header16.png:/icons/Header16.png + + + Headers + + + + + + :/icons/tools-16.png:/icons/tools-16.png + + + Tools + + + + + + + InfoBar + QFrame +
src/infobar.h
+ 1 +
+
+ + tblRegisters + + + + + + + actionExit + triggered() + MainWindow + close() + + + -1 + -1 + + + 199 + 149 + + + + +
diff --git a/Applications/qModMaster/forms/settings.cpp b/Applications/qModMaster/forms/settings.cpp new file mode 100644 index 0000000..2343f09 --- /dev/null +++ b/Applications/qModMaster/forms/settings.cpp @@ -0,0 +1,49 @@ +#include +#include "settings.h" +#include "ui_settings.h" + +Settings::Settings(QWidget *parent ,ModbusCommSettings *settings) : + QDialog(parent), + ui(new Ui::Settings), + m_settings(settings) +{ + ui->setupUi(this); + + connect(ui->buttonBox,SIGNAL(accepted()),this,SLOT(changesAccepted())); + +} + +Settings::~Settings() +{ + delete ui; +} + +void Settings::showEvent(QShowEvent * event) +{ + + //Load Settings + ui->sbMaxNoOfRawDataLines->setEnabled(!modbus_connected); + ui->sbResponseTimeout->setEnabled(!modbus_connected); + if (m_settings != NULL) { + ui->sbMaxNoOfRawDataLines->setValue(m_settings->maxNoOfLines().toInt()); + ui->sbResponseTimeout->setValue(m_settings->timeOut().toInt()); + ui->sbBaseAddr->setValue(m_settings->baseAddr().toInt()); + ui->cmbEndian->setCurrentIndex(m_settings->endian()); + } + +} + +void Settings::changesAccepted() +{ + + //Save Settings + if (m_settings != NULL) { + m_settings->setMaxNoOfLines(ui->sbMaxNoOfRawDataLines->cleanText()); + m_settings->setTimeOut(ui->sbResponseTimeout->cleanText()); + m_settings->setBaseAddr(ui->sbBaseAddr->cleanText()); + if (m_settings->endian() != ui->cmbEndian->currentIndex()) + emit changedEndianess(ui->cmbEndian->currentIndex()); + m_settings->setEndian(ui->cmbEndian->currentIndex()); + } + +} diff --git a/Applications/qModMaster/forms/settings.h b/Applications/qModMaster/forms/settings.h new file mode 100644 index 0000000..4d55798 --- /dev/null +++ b/Applications/qModMaster/forms/settings.h @@ -0,0 +1,38 @@ +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include + +#include "src/modbuscommsettings.h" + +namespace Ui { + class Settings; +} + +class Settings : public QDialog +{ + Q_OBJECT + +public: + explicit Settings(QWidget *parent = 0 ,ModbusCommSettings * settings = 0); + ~Settings(); + bool modbus_connected; + +private: + Ui::Settings *ui; + ModbusCommSettings *m_settings; + +signals: + void changedEndianess(int endian); + +private slots: + void changesAccepted(); + + +protected: + void showEvent(QShowEvent * event); + +}; + +#endif // SETTINGS_H diff --git a/Applications/qModMaster/forms/settings.ui b/Applications/qModMaster/forms/settings.ui new file mode 100644 index 0000000..eed0cf5 --- /dev/null +++ b/Applications/qModMaster/forms/settings.ui @@ -0,0 +1,212 @@ + + + Settings + + + + 0 + 0 + 220 + 150 + + + + + 0 + 0 + + + + + 220 + 150 + + + + + 320 + 150 + + + + Settings + + + + :/icons/options-16.png:/icons/options-16.png + + + + + + + + 0 + + + 100 + + + + + + + Base Addr + + + + + + + + 0 + 0 + + + + + 132 + 0 + + + + + 16777215 + 16777215 + + + + Max No Of Bus Monitor Lines + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Response Timeout (sec) + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + 75 + 16777215 + + + + 1 + + + 600 + + + 60 + + + 60 + + + + + + + 1 + + + 0 + + + + + + + Endian + + + + + + + + Little + + + + + Big + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + Settings + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Settings + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Applications/qModMaster/forms/settingsmodbusrtu.cpp b/Applications/qModMaster/forms/settingsmodbusrtu.cpp new file mode 100644 index 0000000..c81572e --- /dev/null +++ b/Applications/qModMaster/forms/settingsmodbusrtu.cpp @@ -0,0 +1,81 @@ +#include +#include "settingsmodbusrtu.h" +#include "ui_settingsmodbusrtu.h" + +SettingsModbusRTU::SettingsModbusRTU(QWidget *parent,ModbusCommSettings * settings) : + QDialog(parent), + ui(new Ui::SettingsModbusRTU), + m_settings(settings) +{ + ui->setupUi(this); + + /* device name is needed only in Linux */ + #ifdef Q_OS_WIN32 + ui->cmbDev->setDisabled(true); + #else + ui->cmbDev->setDisabled(false); + #endif + + connect(ui->buttonBox,SIGNAL(accepted()),this,SLOT(changesAccepted())); + +} + +SettingsModbusRTU::~SettingsModbusRTU() +{ + delete ui; +} + +void SettingsModbusRTU::showEvent(QShowEvent * event) +{ + + //Load Settings + ui->cmbDataBits->setEnabled(!modbus_connected); + ui->cmbBaud->setEnabled(!modbus_connected); + ui->sbPort->setEnabled(!modbus_connected); + ui->cmbParity->setEnabled(!modbus_connected); + ui->cmbRTS->setEnabled(!modbus_connected); + ui->cmbStopBits->setEnabled(!modbus_connected); + if (m_settings != NULL) { + + ui->cmbRTS->clear(); + + //Populate cmbPort-cmbRTS + #ifdef Q_OS_WIN32 + ui->cmbRTS->addItem("Disable"); + ui->cmbRTS->addItem("Enable"); + ui->cmbRTS->addItem("HandShake"); + ui->cmbRTS->addItem("Toggle"); + #else + ui->cmbRTS->addItem("None"); + ui->cmbRTS->addItem("Up"); + ui->cmbRTS->addItem("Down"); + #endif + + ui->cmbDev->setCurrentText(m_settings->serialDev()); + ui->sbPort->setValue(m_settings->serialPort().toInt()); + ui->cmbBaud->setCurrentIndex(ui->cmbBaud->findText(m_settings->baud())); + ui->cmbDataBits->setCurrentIndex(ui->cmbDataBits->findText(m_settings->dataBits())); + ui->cmbStopBits->setCurrentIndex(ui->cmbStopBits->findText(m_settings->stopBits())); + ui->cmbParity->setCurrentIndex(ui->cmbParity->findText(m_settings->parity())); + ui->cmbRTS->setCurrentIndex(ui->cmbRTS->findText(m_settings->RTS())); + } + + +} + +void SettingsModbusRTU::changesAccepted() +{ + + //Save Settings + if (m_settings != NULL) { + + m_settings->setSerialPort(QString::number(ui->sbPort->value()), ui->cmbDev->currentText()); + m_settings->setBaud(ui->cmbBaud->currentText()); + m_settings->setDataBits(ui->cmbDataBits->currentText()); + m_settings->setStopBits(ui->cmbStopBits->currentText()); + m_settings->setParity(ui->cmbParity->currentText()); + m_settings->setRTS((QString)ui->cmbRTS->currentText()); + } + +} + diff --git a/Applications/qModMaster/forms/settingsmodbusrtu.h b/Applications/qModMaster/forms/settingsmodbusrtu.h new file mode 100644 index 0000000..8ad8e0f --- /dev/null +++ b/Applications/qModMaster/forms/settingsmodbusrtu.h @@ -0,0 +1,34 @@ +#ifndef SETTINGSMODBUSRTU_H +#define SETTINGSMODBUSRTU_H + +#include +#include + +#include "src/modbuscommsettings.h" + +namespace Ui { + class SettingsModbusRTU; +} + +class SettingsModbusRTU : public QDialog +{ + Q_OBJECT + +public: + explicit SettingsModbusRTU(QWidget *parent = 0 ,ModbusCommSettings * settings = 0); + ~SettingsModbusRTU(); + bool modbus_connected; + +private: + Ui::SettingsModbusRTU *ui; + ModbusCommSettings * m_settings; + +private slots: + void changesAccepted(); + +protected: + void showEvent(QShowEvent * event); + +}; + +#endif // SETTINGSMODBUSRTU_H diff --git a/Applications/qModMaster/forms/settingsmodbusrtu.ui b/Applications/qModMaster/forms/settingsmodbusrtu.ui new file mode 100644 index 0000000..261ac02 --- /dev/null +++ b/Applications/qModMaster/forms/settingsmodbusrtu.ui @@ -0,0 +1,352 @@ + + + SettingsModbusRTU + + + + 0 + 0 + 220 + 256 + + + + + 220 + 256 + + + + + 220 + 300 + + + + Modbus RTU Settings + + + + :/icons/options-16.png:/icons/options-16.png + + + true + + + + + + + + true + + + + /dev/ttyS + + + + + /dev/ttyUSB + + + + + + + + Serial device + + + + + + + Baud + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + cmbBaud + + + + + + + 1 + + + 128 + + + + + + + -1 + + + + 110 + + + + + 300 + + + + + 600 + + + + + 1200 + + + + + 2400 + + + + + 4800 + + + + + 9600 + + + + + 14400 + + + + + 19200 + + + + + 28800 + + + + + 38400 + + + + + 57600 + + + + + 115200 + + + + + 128000 + + + + + 256000 + + + + + 921600 + + + + + + + + -1 + + + + 7 + + + + + 8 + + + + + + + + Data Bits + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + cmbDataBits + + + + + + + RTS + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Stop Bits + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + cmbStopBits + + + + + + + + + + -1 + + + + None + + + + + Odd + + + + + Even + + + + + + + + -1 + + + + 1 + + + + + 1.5 + + + + + 2 + + + + + + + + Parity + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + cmbParity + + + + + + + + 0 + 0 + + + + Serial port + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + SettingsModbusRTU + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SettingsModbusRTU + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Applications/qModMaster/forms/settingsmodbustcp.cpp b/Applications/qModMaster/forms/settingsmodbustcp.cpp new file mode 100644 index 0000000..936f7fd --- /dev/null +++ b/Applications/qModMaster/forms/settingsmodbustcp.cpp @@ -0,0 +1,81 @@ +#include +#include +#include "settingsmodbustcp.h" +#include "ui_settingsmodbustcp.h" + +SettingsModbusTCP::SettingsModbusTCP(QWidget *parent, ModbusCommSettings * settings) : + QDialog(parent), + ui(new Ui::SettingsModbusTCP), + m_settings(settings) +{ + ui->setupUi(this); + + connect(ui->buttonBox,SIGNAL(accepted()),this,SLOT(changesAccepted())); +} + +SettingsModbusTCP::~SettingsModbusTCP() +{ + delete ui; +} + +void SettingsModbusTCP::showEvent(QShowEvent * event) +{ + + //Load Settings + ui->leSlaveIP->setEnabled(!modbus_connected); + ui->leTCPPort->setEnabled(!modbus_connected); + if (m_settings != NULL) { + ui->leTCPPort->setText(m_settings->TCPPort()); + ui->leSlaveIP->setText(m_settings->slaveIP()); + } + +} + +void SettingsModbusTCP::changesAccepted() +{ + int validation; + + validation = validateInputs(); + switch(validation){ + case 0 : // ok + //Save Settings + if (m_settings != NULL) { + m_settings->setTCPPort(ui->leTCPPort->text()); + m_settings->setSlaveIP(ui->leSlaveIP->text()); + } + break; + case 1 : // wrong ip + QMessageBox::critical(NULL, "Modbus TCP Settings","Wrong IP Address."); + break; + case 2 : // wrong port + QMessageBox::critical(NULL, "Modbus TCP Settings","Wrong Port Number."); + break; + } + +} + +int SettingsModbusTCP::validateInputs() +{ + //Strip zero's from IP + QStringList ipBytes; + bool ok; + int i, ipByte, port; + + ipBytes = (ui->leSlaveIP->text()).split("."); + if (ipBytes.size() == 4){ + for (i = 0; i < ipBytes.size(); i++){ + ipByte = ipBytes[i].toInt(&ok); + if (!ok || ipByte > 255 ) + return 1; // wrong ip + } + } + else + return 1; // wrong ip + + port = (ui->leTCPPort->text()).toInt(&ok); + if (!ok || port <= 0 || port > 65535) + return 2; // wrong port + + return 0; // validate ok + +} diff --git a/Applications/qModMaster/forms/settingsmodbustcp.h b/Applications/qModMaster/forms/settingsmodbustcp.h new file mode 100644 index 0000000..b9224c0 --- /dev/null +++ b/Applications/qModMaster/forms/settingsmodbustcp.h @@ -0,0 +1,37 @@ +#ifndef SETTINGSMODBUSTCP_H +#define SETTINGSMODBUSTCP_H + +#include +#include +#include + +#include "src/modbuscommsettings.h" + +namespace Ui { + class SettingsModbusTCP; +} + +class SettingsModbusTCP : public QDialog +{ + Q_OBJECT + +public: + explicit SettingsModbusTCP(QWidget *parent = 0 ,ModbusCommSettings * settings = 0); + ~SettingsModbusTCP(); + bool modbus_connected; + +private: + Ui::SettingsModbusTCP *ui; + ModbusCommSettings * m_settings; + int validateInputs(); + +private slots: + void changesAccepted(); + + +protected: + void showEvent(QShowEvent * event); + +}; + +#endif // SETTINGSMODBUSTCP_H diff --git a/Applications/qModMaster/forms/settingsmodbustcp.ui b/Applications/qModMaster/forms/settingsmodbustcp.ui new file mode 100644 index 0000000..3e77f81 --- /dev/null +++ b/Applications/qModMaster/forms/settingsmodbustcp.ui @@ -0,0 +1,127 @@ + + + SettingsModbusTCP + + + + 0 + 0 + 240 + 110 + + + + + 240 + 110 + + + + + 240 + 160 + + + + Modbus TCP Settings + + + + :/img/network-16.png:/img/network-16.png + + + true + + + + + + + + Slave IP + + + + + + + TCP Port + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + leTCPPort + + + + + + + + + + + + + 5 + + + + + + + 999.999.999.999;_ + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SettingsModbusTCP + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SettingsModbusTCP + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Applications/qModMaster/forms/tools.cpp b/Applications/qModMaster/forms/tools.cpp new file mode 100644 index 0000000..81f8328 --- /dev/null +++ b/Applications/qModMaster/forms/tools.cpp @@ -0,0 +1,216 @@ +#include "tools.h" +#include "ui_tools.h" + +#include "QsLog.h" + +Tools::Tools(QWidget *parent, ModbusAdapter *adapter, ModbusCommSettings *settings) : + QMainWindow(parent), + m_modbusAdapter(adapter), m_modbusCommSettings(settings), + ui(new Ui::Tools) +{ + //setup UI + ui->setupUi(this); + cmbModbusMode = new QComboBox(this); + cmbModbusMode->setMinimumWidth(96); + cmbModbusMode->addItem("RTU/TCP");cmbModbusMode->addItem("TCP"); + cmbCmd = new QComboBox(this); + cmbCmd->setMinimumWidth(96); + cmbCmd->addItem("Report Slave ID"); + ui->toolBar->addWidget(cmbModbusMode); + ui->toolBar->addWidget(cmbCmd); + ui->toolBar->addSeparator(); + ui->toolBar->addAction(ui->actionExec); + ui->toolBar->addAction(ui->actionClear); + ui->toolBar->addAction(ui->actionExit); + + //UI - connections + connect(cmbModbusMode,SIGNAL(currentIndexChanged(int)),this,SLOT(changedModbusMode(int))); + connect(ui->actionExec,SIGNAL(triggered(bool)),this,SLOT(execCmd())); + connect(ui->actionClear,SIGNAL(triggered(bool)),this,SLOT(clear())); + connect(ui->actionExit,SIGNAL(triggered()),this,SLOT(exit())); + connect(&m_pingProc,SIGNAL(readyReadStandardOutput()),this,SLOT(pingData())); + connect(&m_pingProc,SIGNAL(readyReadStandardError()),this,SLOT(pingData())); + +} + +Tools::~Tools() +{ + delete ui; +} + +void Tools::exit() +{ + + this->close(); + +} + +QString Tools::ipConv(QString ip) +{ + /* convert ip - remove leding 0's */ + + QStringList m_ip; + QStringList m_ip_conv; + QString m_ip_byte; + + m_ip = ip.split("."); + for (int i = 0; i < m_ip.size(); i++){ + m_ip_byte = m_ip.at(i); + if (m_ip_byte.at(0)=='0') + m_ip_conv << m_ip_byte.remove(0,1); + else + m_ip_conv << m_ip_byte; + } + + return (m_ip_conv.at(0) + "." + m_ip_conv.at(1) + "." + m_ip_conv.at(2) + "." + m_ip_conv.at(3)); + +} + +void Tools::changedModbusMode(int currIndex) +{ + + QLOG_TRACE()<< "Modbus Mode changed. Index = " << currIndex; + + cmbCmd->clear(); + if (currIndex == 0) { //RTU/TCP + cmbCmd->addItem("Report Slave ID"); + } + else { //TCP + cmbCmd->addItem("Report Slave ID");cmbCmd->addItem("Ping");cmbCmd->addItem("Port Status"); + } + +} + +void Tools::execCmd() +{ + + ui->txtOutput->moveCursor(QTextCursor::End); + + QLOG_TRACE()<< "Tools Execute Cmd " << cmbCmd->currentText(); + switch (cmbCmd->currentIndex()){ + case 0: + ui->txtOutput->appendPlainText(QString("------- Modbus Diagnotics : Report Slave ID %1 -------\n").arg(m_modbusCommSettings->slaveID())); + diagnosticsProc(); + break; + + case 1: + ui->txtOutput->appendPlainText(QString("------- Modbus TCP : Ping IP %1 -------\n").arg(m_modbusCommSettings->slaveIP())); + pingProc(); + break; + + case 2: + ui->txtOutput->appendPlainText(QString("------- Modbus TCP : Check Port %1:%2 Status -------\n").arg(m_modbusCommSettings->slaveIP(),m_modbusCommSettings->TCPPort())); + portProc(); + break; + + default: + ui->txtOutput->appendPlainText("------- No Valid Selection -------\n"); + break; + + } + +} + +void Tools::clear() +{ + + QLOG_TRACE()<< "Tools Clear Ouput"; + ui->txtOutput->clear(); + +} + +void Tools::diagnosticsProc() +{ + + qApp->processEvents(); + ui->txtOutput->moveCursor(QTextCursor::End); + if(m_modbusAdapter->m_modbus != NULL){ + modbusDiagnostics(); + } + else{ + ui->txtOutput->insertPlainText("Not Connected.\n"); + } + +} + +void Tools::pingProc() +{ + + qApp->processEvents(); + m_pingProc.start("ping", QStringList() << ipConv(m_modbusCommSettings->slaveIP())); + if (m_pingProc.waitForFinished(5000)){ + //just wait -> execute button is pressed + } +} + +void Tools::pingData() +{ + + qApp->processEvents(); + ui->txtOutput->moveCursor(QTextCursor::End); + ui->txtOutput->insertPlainText(m_pingProc.readAll()); + +} + +void Tools::portProc() +{ + + qApp->processEvents(); + ui->txtOutput->moveCursor(QTextCursor::End); + m_portProc.connectToHost(ipConv(m_modbusCommSettings->slaveIP()),m_modbusCommSettings->TCPPort().toInt()); + if (m_portProc.waitForConnected(5000)){//wait -> execute button is pressed + ui->txtOutput->insertPlainText("Connected.Port is opened\n"); + m_portProc.close(); + } + else{ + ui->txtOutput->insertPlainText("Not connected.Port is closed\n"); + } + +} + +void Tools::modbusDiagnostics() +{ + //Modbus diagnostics - RTU/TCP + QLOG_TRACE()<< "Modbus diagnostics."; + + //Modbus data + m_modbusAdapter->setFunctionCode(0x11); + uint8_t dest[1024]; //setup memory for data + memset(dest, 0, 1024); + int ret = -1; //return value from read functions + + modbus_set_slave(m_modbusAdapter->m_modbus, m_modbusCommSettings->slaveID()); + //request data from modbus + ret = modbus_report_slave_id(m_modbusAdapter->m_modbus, MODBUS_MAX_PDU_LENGTH, dest); + QLOG_TRACE() << "Modbus Read Data return value = " << ret << ", errno = " << errno; + + //update data model + if(ret > 1) + { + QString line; + line = dest[1]?"ON":"OFF"; + ui->txtOutput->insertPlainText("Run Status : " + line + "\n"); + QString id = QString::fromUtf8((char*)dest); + ui->txtOutput->insertPlainText("ID : " + id.right(id.size()-2) + "\n");; + } + else + { + QString line = ""; + if(ret < 0) { + line = QString("Error : ") + EUtils::libmodbus_strerror(errno); + QLOG_ERROR() << "Read diagnostics data failed. " << line; + line = QString(tr("Read diagnostics data failed.\nError : ")) + EUtils::libmodbus_strerror(errno); + ui->txtOutput->insertPlainText(line); + } + else { + line = QString("Unknown Error : ") + EUtils::libmodbus_strerror(errno); + QLOG_ERROR() << "Read diagnostics data failed. " << line; + line = QString(tr("Read diagnostics data failed.\nUnknown Error : ")) + EUtils::libmodbus_strerror(errno); + ui->txtOutput->insertPlainText(line); + } + + modbus_flush(m_modbusAdapter->m_modbus); //flush data + } + +} + diff --git a/Applications/qModMaster/forms/tools.h b/Applications/qModMaster/forms/tools.h new file mode 100644 index 0000000..4c45025 --- /dev/null +++ b/Applications/qModMaster/forms/tools.h @@ -0,0 +1,47 @@ +#ifndef TOOLS_H +#define TOOLS_H + +#include +#include +#include +#include + +#include "src/modbusadapter.h" +#include "src/modbuscommsettings.h" + +namespace Ui { +class Tools; +} + +class Tools : public QMainWindow +{ + Q_OBJECT + +public: + explicit Tools(QWidget *parent = 0, ModbusAdapter *adapter = 0, ModbusCommSettings *settings = 0); + ~Tools(); + +private: + Ui::Tools *ui; + QComboBox *cmbModbusMode; + QComboBox *cmbCmd; + ModbusAdapter *m_modbusAdapter; + ModbusCommSettings *m_modbusCommSettings; + QProcess m_pingProc; + QTcpSocket m_portProc; + QString ipConv(QString ip); + void pingProc(); + void portProc(); + void diagnosticsProc(); + void modbusDiagnostics(); + +private slots: + void exit(); + void changedModbusMode(int currIndex); + void execCmd(); + void clear(); + void pingData(); + +}; + +#endif // TOOLS_H diff --git a/Applications/qModMaster/forms/tools.ui b/Applications/qModMaster/forms/tools.ui new file mode 100644 index 0000000..6738793 --- /dev/null +++ b/Applications/qModMaster/forms/tools.ui @@ -0,0 +1,99 @@ + + + Tools + + + + 0 + 0 + 564 + 400 + + + + + 500 + 400 + + + + + 720 + 720 + + + + Tools + + + + + + + + Tahoma + + + + false + + + true + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + :/icons/Close-16.png:/icons/Close-16.png + + + Exit + + + Exit + + + + + + :/icons/play-16.png:/icons/play-16.png + + + Exec + + + Execute Command + + + + + + :/icons/edit-clear-16.png:/icons/edit-clear-16.png + + + Clear + + + Clear Output + + + + + + + + diff --git a/Applications/qModMaster/icons/China-flag-16.png b/Applications/qModMaster/icons/China-flag-16.png new file mode 100644 index 0000000..d0d0250 Binary files /dev/null and b/Applications/qModMaster/icons/China-flag-16.png differ diff --git a/Applications/qModMaster/icons/Close-16.png b/Applications/qModMaster/icons/Close-16.png new file mode 100644 index 0000000..c49e80e Binary files /dev/null and b/Applications/qModMaster/icons/Close-16.png differ diff --git a/Applications/qModMaster/icons/Header16.png b/Applications/qModMaster/icons/Header16.png new file mode 100644 index 0000000..3796247 Binary files /dev/null and b/Applications/qModMaster/icons/Header16.png differ diff --git a/Applications/qModMaster/icons/TV-16.png b/Applications/qModMaster/icons/TV-16.png new file mode 100644 index 0000000..add3bc3 Binary files /dev/null and b/Applications/qModMaster/icons/TV-16.png differ diff --git a/Applications/qModMaster/icons/Taiwan-flag-16.png b/Applications/qModMaster/icons/Taiwan-flag-16.png new file mode 100644 index 0000000..4bde6c4 Binary files /dev/null and b/Applications/qModMaster/icons/Taiwan-flag-16.png differ diff --git a/Applications/qModMaster/icons/bullet-green-16.png b/Applications/qModMaster/icons/bullet-green-16.png new file mode 100644 index 0000000..dca4800 Binary files /dev/null and b/Applications/qModMaster/icons/bullet-green-16.png differ diff --git a/Applications/qModMaster/icons/bullet-red-16.png b/Applications/qModMaster/icons/bullet-red-16.png new file mode 100644 index 0000000..a1639d3 Binary files /dev/null and b/Applications/qModMaster/icons/bullet-red-16.png differ diff --git a/Applications/qModMaster/icons/close8-black.png b/Applications/qModMaster/icons/close8-black.png new file mode 100644 index 0000000..2d6a88f Binary files /dev/null and b/Applications/qModMaster/icons/close8-black.png differ diff --git a/Applications/qModMaster/icons/close8-red.png b/Applications/qModMaster/icons/close8-red.png new file mode 100644 index 0000000..768384c Binary files /dev/null and b/Applications/qModMaster/icons/close8-red.png differ diff --git a/Applications/qModMaster/icons/connect-24.png b/Applications/qModMaster/icons/connect-24.png new file mode 100644 index 0000000..dc44c0d Binary files /dev/null and b/Applications/qModMaster/icons/connect-24.png differ diff --git a/Applications/qModMaster/icons/cyclic-process-16.png b/Applications/qModMaster/icons/cyclic-process-16.png new file mode 100644 index 0000000..d58e2ff Binary files /dev/null and b/Applications/qModMaster/icons/cyclic-process-16.png differ diff --git a/Applications/qModMaster/icons/data-sort-16.png b/Applications/qModMaster/icons/data-sort-16.png new file mode 100644 index 0000000..76b927d Binary files /dev/null and b/Applications/qModMaster/icons/data-sort-16.png differ diff --git a/Applications/qModMaster/icons/document-export-16.png b/Applications/qModMaster/icons/document-export-16.png new file mode 100644 index 0000000..c8e80af Binary files /dev/null and b/Applications/qModMaster/icons/document-export-16.png differ diff --git a/Applications/qModMaster/icons/document-import-16.png b/Applications/qModMaster/icons/document-import-16.png new file mode 100644 index 0000000..239cd3b Binary files /dev/null and b/Applications/qModMaster/icons/document-import-16.png differ diff --git a/Applications/qModMaster/icons/edit-16.png b/Applications/qModMaster/icons/edit-16.png new file mode 100644 index 0000000..64c0822 Binary files /dev/null and b/Applications/qModMaster/icons/edit-16.png differ diff --git a/Applications/qModMaster/icons/edit-clear-16.png b/Applications/qModMaster/icons/edit-clear-16.png new file mode 100644 index 0000000..b61e566 Binary files /dev/null and b/Applications/qModMaster/icons/edit-clear-16.png differ diff --git a/Applications/qModMaster/icons/ethernet-port-16.png b/Applications/qModMaster/icons/ethernet-port-16.png new file mode 100644 index 0000000..24ca737 Binary files /dev/null and b/Applications/qModMaster/icons/ethernet-port-16.png differ diff --git a/Applications/qModMaster/icons/exit-16.png b/Applications/qModMaster/icons/exit-16.png new file mode 100644 index 0000000..6323241 Binary files /dev/null and b/Applications/qModMaster/icons/exit-16.png differ diff --git a/Applications/qModMaster/icons/help-desk-icon-16.png b/Applications/qModMaster/icons/help-desk-icon-16.png new file mode 100644 index 0000000..a7ddadc Binary files /dev/null and b/Applications/qModMaster/icons/help-desk-icon-16.png differ diff --git a/Applications/qModMaster/icons/icons.qrc b/Applications/qModMaster/icons/icons.qrc new file mode 100644 index 0000000..15ed6c7 --- /dev/null +++ b/Applications/qModMaster/icons/icons.qrc @@ -0,0 +1,34 @@ + + + close8-black.png + close8-red.png + options-16.png + edit-16.png + save-16.png + ethernet-port-16.png + serial-pot-16.png + edit-clear-16.png + reset-16.png + plug-connect-16.png + plug-disconnect-16.png + data-sort-16.png + China-flag-16.png + Taiwan-flag-16.png + usa-flag-16.png + cyclic-process-16.png + exit-16.png + text-x-log-16.png + info-sign-16.png + Close-16.png + connect-24.png + TV-16.png + bullet-green-16.png + bullet-red-16.png + help-desk-icon-16.png + document-export-16.png + document-import-16.png + Header16.png + tools-16.png + play-16.png + + diff --git a/Applications/qModMaster/icons/info-sign-16.png b/Applications/qModMaster/icons/info-sign-16.png new file mode 100644 index 0000000..91fc2e4 Binary files /dev/null and b/Applications/qModMaster/icons/info-sign-16.png differ diff --git a/Applications/qModMaster/icons/options-16.png b/Applications/qModMaster/icons/options-16.png new file mode 100644 index 0000000..b843afc Binary files /dev/null and b/Applications/qModMaster/icons/options-16.png differ diff --git a/Applications/qModMaster/icons/play-16.png b/Applications/qModMaster/icons/play-16.png new file mode 100644 index 0000000..976c48a Binary files /dev/null and b/Applications/qModMaster/icons/play-16.png differ diff --git a/Applications/qModMaster/icons/plug-connect-16.png b/Applications/qModMaster/icons/plug-connect-16.png new file mode 100644 index 0000000..7af930e Binary files /dev/null and b/Applications/qModMaster/icons/plug-connect-16.png differ diff --git a/Applications/qModMaster/icons/plug-disconnect-16.png b/Applications/qModMaster/icons/plug-disconnect-16.png new file mode 100644 index 0000000..6010982 Binary files /dev/null and b/Applications/qModMaster/icons/plug-disconnect-16.png differ diff --git a/Applications/qModMaster/icons/reset-16.png b/Applications/qModMaster/icons/reset-16.png new file mode 100644 index 0000000..77e12d1 Binary files /dev/null and b/Applications/qModMaster/icons/reset-16.png differ diff --git a/Applications/qModMaster/icons/save-16.png b/Applications/qModMaster/icons/save-16.png new file mode 100644 index 0000000..f515c00 Binary files /dev/null and b/Applications/qModMaster/icons/save-16.png differ diff --git a/Applications/qModMaster/icons/serial-pot-16.png b/Applications/qModMaster/icons/serial-pot-16.png new file mode 100644 index 0000000..1d6d39b Binary files /dev/null and b/Applications/qModMaster/icons/serial-pot-16.png differ diff --git a/Applications/qModMaster/icons/text-x-log-16.png b/Applications/qModMaster/icons/text-x-log-16.png new file mode 100644 index 0000000..99f4514 Binary files /dev/null and b/Applications/qModMaster/icons/text-x-log-16.png differ diff --git a/Applications/qModMaster/icons/tools-16.png b/Applications/qModMaster/icons/tools-16.png new file mode 100644 index 0000000..bf3fa13 Binary files /dev/null and b/Applications/qModMaster/icons/tools-16.png differ diff --git a/Applications/qModMaster/icons/usa-flag-16.png b/Applications/qModMaster/icons/usa-flag-16.png new file mode 100644 index 0000000..a9836ff Binary files /dev/null and b/Applications/qModMaster/icons/usa-flag-16.png differ diff --git a/Applications/qModMaster/qModMaster.pro b/Applications/qModMaster/qModMaster.pro new file mode 100644 index 0000000..91c7cca --- /dev/null +++ b/Applications/qModMaster/qModMaster.pro @@ -0,0 +1,113 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2010-11-24T09:57:26 +# +#------------------------------------------------- + +QT += core gui network +QT += core5compat +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = qModMaster +TEMPLATE = app + +SOURCES += src/main.cpp \ + src/mainwindow.cpp \ + 3rdparty/libmodbus/modbus.c \ + forms/about.cpp \ + forms/settingsmodbusrtu.cpp \ + forms/settingsmodbustcp.cpp \ + src/modbusadapter.cpp \ + src/eutils.cpp \ + src/registersmodel.cpp \ + src/rawdatamodel.cpp \ + forms/settings.cpp \ + forms/busmonitor.cpp \ + 3rdparty/libmodbus/modbus-data.c \ + 3rdparty/libmodbus/modbus-tcp.c \ + 3rdparty/libmodbus/modbus-rtu.c \ + src/rawdatadelegate.cpp \ + src/registersdatadelegate.cpp \ + src/modbuscommsettings.cpp \ + 3rdparty/QsLog/QsLogDest.cpp \ + 3rdparty/QsLog/QsLog.cpp \ + 3rdparty/QsLog/QsLogDestConsole.cpp \ + 3rdparty/QsLog/QsLogDestFile.cpp \ + src/infobar.cpp \ + forms/tools.cpp + +HEADERS += src/mainwindow.h \ + 3rdparty/libmodbus/modbus.h \ + forms/about.h \ + forms/settingsmodbusrtu.h \ + forms/settingsmodbustcp.h \ + src/modbusadapter.h \ + src/eutils.h \ + src/registersmodel.h \ + src/rawdatamodel.h \ + forms/settings.h \ + forms/busmonitor.h \ + src/rawdatadelegate.h \ + src/registersdatadelegate.h \ + src/modbuscommsettings.h \ + 3rdparty/QsLog/QsLog.h \ + 3rdparty/QsLog/QsLogDest.h \ + 3rdparty/QsLog/QsLogDestConsole.h \ + 3rdparty/QsLog/QsLogLevel.h \ + 3rdparty/QsLog/QsLogDisableForThisFile.h \ + 3rdparty/QsLog/QsLogDestFile.h \ + src/infobar.h \ + forms/tools.h + +INCLUDEPATH += 3rdparty/libmodbus \ + 3rdparty/QsLog + +TRANSLATIONS += translations/$$TARGET"_zh_CN.ts" +TRANSLATIONS += translations/$$TARGET"_zh_TW.ts" + +unix:SOURCES += + +unix:DEFINES += _TTY_POSIX_ + +win32:SOURCES += + +win32:DEFINES += _TTY_WIN_ WINVER=0x0501 + +win32:LIBS += -lsetupapi -lwsock32 -lws2_32 + +QMAKE_CXXFLAGS += -std=gnu++17 + +DEFINES += QS_LOG_LINE_NUMBERS # automatically writes the file and line for each log message +#DEFINES += QS_LOG_DISABLE # logging code is replaced with a no-op +#DEFINES += QS_LOG_SEPARATE_THREAD # messages are queued and written from a separate thread +#DEFINES += LIB_MODBUS_DEBUG_OUTPUT # enable debug output from libmodbus + +FORMS += forms/mainwindow.ui \ + forms/about.ui \ + forms/settingsmodbusrtu.ui \ + forms/settingsmodbustcp.ui \ + forms/settings.ui \ + forms/busmonitor.ui \ + forms/tools.ui + +RESOURCES += \ + icons/icons.qrc \ + translations/translations.qrc + + + + + + + + + + + + + + + + + + diff --git a/Applications/qModMaster/qModMaster.pro.user b/Applications/qModMaster/qModMaster.pro.user new file mode 100644 index 0000000..e55a321 --- /dev/null +++ b/Applications/qModMaster/qModMaster.pro.user @@ -0,0 +1,261 @@ + + + + + + EnvironmentId + {48575134-1fc3-4253-940c-17c134c3fd80} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + false + true + false + 0 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 4 + true + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop Qt 6.4.3 GCC 64bit + Desktop Qt 6.4.3 GCC 64bit + qt.qt6.643.gcc_64_kit + 0 + 0 + 0 + + 0 + /home/sanderspeetjens/Documents/ThomasMore/Phase3/Semester1/Webservices_And_Applications/lab/2023-Webservices_And_Applications/Applications/qModMaster/build-Desktop_Qt_6_4_3_GCC_64bit-Debug + /home/sanderspeetjens/Documents/ThomasMore/Phase3/Semester1/Webservices_And_Applications/lab/2023-Webservices_And_Applications/Applications/qModMaster/build-Desktop_Qt_6_4_3_GCC_64bit-Debug + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + /home/sanderspeetjens/Documents/ThomasMore/Phase3/Semester1/Webservices_And_Applications/lab/2023-Webservices_And_Applications/Applications/qModMaster/build-Desktop_Qt_6_4_3_GCC_64bit-Release + /home/sanderspeetjens/Documents/ThomasMore/Phase3/Semester1/Webservices_And_Applications/lab/2023-Webservices_And_Applications/Applications/qModMaster/build-Desktop_Qt_6_4_3_GCC_64bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + /home/sanderspeetjens/Documents/ThomasMore/Phase3/Semester1/Webservices_And_Applications/lab/2023-Webservices_And_Applications/Applications/qModMaster/build-Desktop_Qt_6_4_3_GCC_64bit-Profile + /home/sanderspeetjens/Documents/ThomasMore/Phase3/Semester1/Webservices_And_Applications/lab/2023-Webservices_And_Applications/Applications/qModMaster/build-Desktop_Qt_6_4_3_GCC_64bit-Profile + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + true + + 2 + + false + ProjectExplorer.CustomExecutableRunConfiguration + + true + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/Applications/qModMaster/qModmaster.exe.manifest b/Applications/qModMaster/qModmaster.exe.manifest new file mode 100644 index 0000000..9192aa8 --- /dev/null +++ b/Applications/qModMaster/qModmaster.exe.manifest @@ -0,0 +1,8 @@ + + + + + False + + + \ No newline at end of file diff --git a/Applications/qModMaster/src/eutils.cpp b/Applications/qModMaster/src/eutils.cpp new file mode 100644 index 0000000..b9f3252 --- /dev/null +++ b/Applications/qModMaster/src/eutils.cpp @@ -0,0 +1,107 @@ +#include +#include "eutils.h" + +EUtils::EUtils() +{ +} + +QString EUtils::formatValue(int value,int frmt, bool is16Bit, bool isSigned=false) +{ + QString convertedValue; + + switch(frmt){ + + case EUtils::Bin://Binary + if (is16Bit) + if (isSigned) + convertedValue = QString("%1").arg((signed short)value,16,2,QLatin1Char('0')); + else + convertedValue = QString("%1").arg((unsigned short)value,16,2,QLatin1Char('0')); + else + + convertedValue = QString("%1").arg(value,0,2); + break; + + case EUtils::Dec://Decimal - Unsigned Integer + if (isSigned) + convertedValue = QString("%1").arg((signed short)value,0,10); + else + convertedValue = QString("%1").arg((unsigned short)value,0,10); + break; + + case EUtils::Hex://Hex + if (is16Bit) + convertedValue = QString("%1").arg(value,4,16,QLatin1Char('0')); + else + convertedValue = QString("%1").arg(value,0,16); + break; + + default://Default + convertedValue = QString("%1").arg(value,0,10); + + } + + return convertedValue.toUpper(); + +} + +QString EUtils::formatValue32(int valueHi, int valueLo, int endian = EUtils::Little, int precision = -1) +{ + union{ + struct{qint16 low, high;} reg; + float value; + } data; + QString convertedValue; + + if (endian == EUtils::Little){ + data.reg.high = valueLo; + data.reg.low = valueHi; + } + else if (endian == EUtils::Big){ + data.reg.high = valueHi; + data.reg.low = valueLo; + } + + convertedValue = QString("%1").arg(data.value, 0, 'G', precision); + + return convertedValue.toUpper(); + +} + +QString EUtils::formatValue32(float value, int precision = -1) +{ + + QString convertedValue; + + convertedValue = QString("%1").arg(value, 0, 'G', precision); + + return convertedValue.toUpper(); + +} + +QString EUtils::libmodbus_strerror(int errnum) +{ + switch (errnum) { + + case EINVAL: + return "Protocol context is NULL"; + + case ETIMEDOUT: + return "Timeout"; + + case ECONNRESET: + return "Connection reset"; + + case ECONNREFUSED: + return "Connection refused"; + + case EPIPE: + return "Socket error"; + + default://Default + return modbus_strerror(errno); + + } + +} + diff --git a/Applications/qModMaster/src/eutils.h b/Applications/qModMaster/src/eutils.h new file mode 100644 index 0000000..04154aa --- /dev/null +++ b/Applications/qModMaster/src/eutils.h @@ -0,0 +1,173 @@ +#ifndef EUTILS_H +#define EUTILS_H + +#include +#include +#include +#include "modbus.h" + +static const QString ModbusFunctionNames[]={"Read Coils (0x01)","Read Discrete Inputs (0x02)","Read Holding Registers (0x03)", + "Read Input Registers (0x04)","Write Single Coil (0x05)","Write Single Register (0x06)", + "Write Multiple Coils (0x0f)","Write Multiple Registers (0x10)","Report Server ID (0x11)"}; +static const int ModbusFunctionCodes[]={0x1,0x2,0x3,0x4,0x5,0x6,0xf,0x10,0x11}; +static const QString ModbusModeStamp[]={"[RTU]>","[TCP]>",""}; + +class EUtils +{ +private: + EUtils(); + +public: + + static QString ModbusDataTypeName(int fCode) + { + switch(fCode) + { + case MODBUS_FC_READ_COILS: + case MODBUS_FC_WRITE_SINGLE_COIL: + case MODBUS_FC_WRITE_MULTIPLE_COILS: + return "Coil (binary)"; + case MODBUS_FC_READ_DISCRETE_INPUTS: + return "Discrete Input (binary)"; + case MODBUS_FC_READ_HOLDING_REGISTERS: + case MODBUS_FC_WRITE_SINGLE_REGISTER: + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + return "Holding Register (16 bit)"; + case MODBUS_FC_READ_INPUT_REGISTERS: + return "Input Register (16 bit)"; + default: + break; + } + return "Unknown"; + } + + static bool ModbusIsWriteFunction(int fCode) + { + switch(fCode) + { + case MODBUS_FC_READ_COILS: + case MODBUS_FC_READ_DISCRETE_INPUTS: + case MODBUS_FC_READ_HOLDING_REGISTERS: + case MODBUS_FC_READ_INPUT_REGISTERS: + return false; + + case MODBUS_FC_WRITE_SINGLE_COIL: + case MODBUS_FC_WRITE_MULTIPLE_COILS: + case MODBUS_FC_WRITE_SINGLE_REGISTER: + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + return true; + + default: + break; + } + return false; + } + + static bool ModbusIsWriteCoilsFunction(int fCode) + { + switch(fCode) + { + case MODBUS_FC_READ_COILS: + case MODBUS_FC_READ_DISCRETE_INPUTS: + case MODBUS_FC_READ_HOLDING_REGISTERS: + case MODBUS_FC_READ_INPUT_REGISTERS: + case MODBUS_FC_WRITE_SINGLE_REGISTER: + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + return false; + + case MODBUS_FC_WRITE_SINGLE_COIL: + case MODBUS_FC_WRITE_MULTIPLE_COILS: + return true; + + default: + break; + } + return false; + } + + static bool ModbusIsWriteRegistersFunction(int fCode) + { + switch(fCode) + { + case MODBUS_FC_READ_COILS: + case MODBUS_FC_READ_DISCRETE_INPUTS: + case MODBUS_FC_READ_HOLDING_REGISTERS: + case MODBUS_FC_READ_INPUT_REGISTERS: + case MODBUS_FC_WRITE_SINGLE_COIL: + case MODBUS_FC_WRITE_MULTIPLE_COILS: + return false; + + case MODBUS_FC_WRITE_SINGLE_REGISTER: + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + return true; + + default: + break; + } + return false; + } + + static QString ModbusFunctionName(int index) + { + return ModbusFunctionNames[index]; + } + + static int ModbusFunctionCode(int index) + { + return ModbusFunctionCodes[index]; + } + + static QString TxTimeStamp(int md) + { + return (ModbusModeStamp[md] + "Tx > " + QTime::currentTime().toString("HH:mm:ss:zzz")); + } + + static QString RxTimeStamp(int md) + { + return (ModbusModeStamp[md] + "Rx > " + QTime::currentTime().toString("HH:mm:ss:zzz")); + } + + static QString SysTimeStamp() + { + return ("Sys > " + QTime::currentTime().toString("HH:mm:ss:zzz")); + } + + static QChar parity(QString p) + { + //the first char is what we need + return p.at(0); + } + + static QString endianness(int endian) + { + switch (endian) + { + case 0: + return "Little"; + case 1: + return "Big"; + default: + return "N/A"; + } + } + + static enum {RTU = 0, TCP = 1, None = 0} ModbusMode; + + static enum {Bin = 2, Dec = 10, Float = 11, Hex = 16} NumberFormat; + static enum {Little =0, Big = 1} Endianness; + + static enum {ReadCoils = 0x1, ReadDisInputs = 0x2, + ReadHoldRegs = 0x3, ReadInputRegs = 0x4, + WriteSingleCoil = 0x5, WriteSingleReg = 0x6, + WriteMultiCoils = 0xf, WriteMultiRegs = 0x10} FunctionCodes; + + static QString formatValue(int value,int frmt, bool is16Bit, bool isSigned); + + static QString formatValue32(int valueHi, int valueLo, int endian, int precision); + static QString formatValue32(float value, int precision); + + static QString libmodbus_strerror(int errnum); + +}; + +#endif // EUTILS_H diff --git a/Applications/qModMaster/src/infobar.cpp b/Applications/qModMaster/src/infobar.cpp new file mode 100644 index 0000000..149ea0e --- /dev/null +++ b/Applications/qModMaster/src/infobar.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include "infobar.h" + +InfoBar::InfoBar(QWidget *parent) : QFrame(parent) +{ + label = new QLabel; + + QPushButton *button = new QPushButton; + button->setStyleSheet("QPushButton{image: url(:/icons/close8-black.png);\ + background-color: lightgrey;\ + border: none;\ + height: 12px;\ + width: 12px}\ + QPushButton:hover{image: url(:/icons/close8-red.png);\ + background-color: lightgrey;\ + border: none;\ + height: 12px;\ + width: 12px}\ + QPushButton:pressed{image: url(:/icons/close8-red.png);\ + background-color: lightgrey;\ + border: 1px solid darkgrey;\ + border-radius: 2px;\ + height: 12px;\ + width: 12px}"); + button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + button->setFocusPolicy(Qt::NoFocus); + connect(button, SIGNAL(clicked()), this, SLOT(hide())); + + QHBoxLayout *hboxlayout = new QHBoxLayout; + hboxlayout->addWidget(label); + hboxlayout->addWidget(button); + + setLayout(hboxlayout); + setFrameStyle(QFrame::Box); + hide(); +} + +InfoBar::InfoBar(QString message, InfoType type, QWidget *parent) : InfoBar(parent) +{ + setMessage(message); + setInfoType(type); +} + +void InfoBar::setInfoType(InfoType type) +{ + switch (type) { + case Question: + setStyleSheet("color: black;\ + background-color: skyblue"); + break; + case Error: + setStyleSheet("color: white;\ + background-color: red"); + break; + default: + case Warning: + setStyleSheet("color: black;\ + background-color: yellow"); + break; + } +} + +void InfoBar::setMessage(QString message) +{ + label->setText(message); +} + +void InfoBar::show(QString message, InfoType type) +{ + setMessage(message); + setInfoType(type); + QFrame::show(); +} diff --git a/Applications/qModMaster/src/infobar.h b/Applications/qModMaster/src/infobar.h new file mode 100644 index 0000000..a25e3c9 --- /dev/null +++ b/Applications/qModMaster/src/infobar.h @@ -0,0 +1,26 @@ +#ifndef _MyInfoBar_H_ +#define _MyInfoBar_H_ + +#include +#include + +class InfoBar : public QFrame +{ + Q_OBJECT + private: + QLabel *label; + public: + enum InfoType { + Question, + Warning, + Error + }; + + InfoBar(QWidget *parent = 0); + InfoBar(QString message, InfoType type, QWidget *parent = 0); + void setInfoType(InfoType type); + void setMessage(QString message); + void show(QString message, InfoType type); +}; + +#endif diff --git a/Applications/qModMaster/src/main.cpp b/Applications/qModMaster/src/main.cpp new file mode 100644 index 0000000..74ccfa9 --- /dev/null +++ b/Applications/qModMaster/src/main.cpp @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include + +#include "QsLog.h" +#include "QsLogDest.h" +#include "mainwindow.h" +#include "modbusadapter.h" +#include "modbuscommsettings.h" + +QTranslator *Translator; + +//Logging Levels +//TraceLevel : 0 +//DebugLevel : 1 +//InfoLevel : 2 +//WarnLevel : 3 +//ErrorLevel : 4 +//FatalLevel : 5 +//OffLevel : 6 + +int main(int argc, char *argv[]) +{ + + //qt dpi warnings + qputenv("QT_DEVICE_PIXEL_RATIO", "0"); + qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1"); + qputenv("QT_SCREEN_SCALE_FACTORS", "1"); + qputenv("QT_SCALE_FACTOR", "1"); + + //application startup + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication app(argc, argv); + Translator = new QTranslator; + Translator->load(":/translations/" + QCoreApplication::applicationName() + "_" + QLocale::system().name()); + app.installTranslator(Translator); + + //init the logging mechanism + QsLogging::Logger& logger = QsLogging::Logger::instance(); + logger.setLoggingLevel(QsLogging::OffLevel); // start with no logging + const QString sLogPath(QDir(app.applicationDirPath()).filePath("QModMaster.log")); + QsLogging::DestinationPtr fileDestination(QsLogging::DestinationFactory::MakeFileDestination(sLogPath,true,65535,7)); + QsLogging::DestinationPtr debugDestination(QsLogging::DestinationFactory::MakeDebugOutputDestination()); + logger.addDestination(debugDestination); + logger.addDestination(fileDestination); + + //Modbus Adapter + ModbusAdapter modbus_adapt(NULL); + //Program settings + ModbusCommSettings settings("qModMaster.ini"); + + //show main window + mainWin = new MainWindow(NULL, &modbus_adapt, &settings); + //connect signals - slots + QObject::connect(&modbus_adapt, SIGNAL(refreshView()), mainWin, SLOT(refreshView())); + QObject::connect(mainWin, SIGNAL(resetCounters()), &modbus_adapt, SLOT(resetCounters())); + mainWin->show(); + + return app.exec(); + +} diff --git a/Applications/qModMaster/src/mainwindow.cpp b/Applications/qModMaster/src/mainwindow.cpp new file mode 100644 index 0000000..f1d1783 --- /dev/null +++ b/Applications/qModMaster/src/mainwindow.cpp @@ -0,0 +1,860 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "QsLog.h" +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "eutils.h" + +MainWindow *mainWin; + +MainWindow::MainWindow(QWidget *parent, ModbusAdapter *adapter, ModbusCommSettings *settings) : + QMainWindow(parent), m_modbus(adapter), m_modbusCommSettings(settings), + ui(new Ui::MainWindow) +{ + //setup UI + ui->setupUi(this); + ui->sbNoOfRegs->setEnabled(true); + ui->actionRead_Write->setEnabled(false); + ui->actionScan->setEnabled(false); + ui->sbStartAddress->setMinimum(m_modbusCommSettings->baseAddr().toInt()); + ui->cmbFrmt->setCurrentIndex(m_modbusCommSettings->frmt()); + ui->cmbFunctionCode->setCurrentIndex(m_modbusCommSettings->functionCode()); + ui->cmbModbusMode->setCurrentIndex(m_modbusCommSettings->modbusMode()); + ui->sbSlaveID->setValue(m_modbusCommSettings->slaveID()); + ui->spInterval->setValue(m_modbusCommSettings->scanRate()); + ui->sbStartAddress->setValue(m_modbusCommSettings->startAddr()); + ui->sbNoOfRegs->setValue(m_modbusCommSettings->noOfRegs()); + ui->chkSigned->setVisible(false); + ui->tblRegisters->setStyleSheet("QHeaderView::section { background-color:lightGray }"); + + //UI - dialogs + m_dlgAbout = new About(); + connect(ui->actionAbout,SIGNAL(triggered()),m_dlgAbout,SLOT(show())); + m_dlgModbusRTU = new SettingsModbusRTU(this,m_modbusCommSettings); + connect(ui->actionSerial_RTU,SIGNAL(triggered()),this,SLOT(showSettingsModbusRTU())); + m_dlgModbusTCP = new SettingsModbusTCP(this,m_modbusCommSettings); + connect(ui->actionTCP,SIGNAL(triggered()),this,SLOT(showSettingsModbusTCP())); + m_dlgSettings = new Settings(this,m_modbusCommSettings); + connect(ui->actionSettings,SIGNAL(triggered()),this,SLOT(showSettings())); + m_busMonitor = new BusMonitor(this, m_modbus->rawModel); + connect(ui->actionBus_Monitor,SIGNAL(triggered()),this,SLOT(showBusMonitor())); + m_tools = new Tools(this, m_modbus, m_modbusCommSettings); + connect(ui->actionTools,SIGNAL(triggered()),this,SLOT(showTools())); + + //UI - connections + connect(ui->cmbModbusMode,SIGNAL(currentIndexChanged(int)),this,SLOT(changedModbusMode(int))); + connect(ui->cmbFunctionCode,SIGNAL(currentIndexChanged(int)),this,SLOT(changedFunctionCode(int))); + connect(ui->cmbFrmt,SIGNAL(currentIndexChanged(int)),this,SLOT(changedFrmt(int))); + connect(ui->chkSigned,SIGNAL(toggled(bool)),this,SLOT(changedDecSign(bool))); + connect(ui->cmbStartAddrBase,SIGNAL(currentIndexChanged(int)),this,SLOT(changedStartAddrBase(int))); + connect(ui->sbSlaveID,SIGNAL(valueChanged(int)),this,SLOT(changedSlaveID(int))); + connect(ui->sbNoOfRegs,SIGNAL(valueChanged(int)),this,SLOT(changedNoOfRegs(int))); + connect(ui->sbStartAddress,SIGNAL(valueChanged(int)),this,SLOT(changedStartAddress(int))); + connect(ui->spInterval,SIGNAL(valueChanged(int)),this,SLOT(changedScanRate(int))); + connect(ui->sbPrecision,SIGNAL(valueChanged(int)),this,SLOT(changedFloatPrecision(int))); + connect(ui->actionClear,SIGNAL(triggered()),this,SLOT(clearItems())); + connect(ui->actionRead_Write,SIGNAL(triggered()),this,SLOT(modbusRequest())); + connect(ui->actionScan,SIGNAL(toggled(bool)),this,SLOT(modbusScanCycle(bool))); + connect(ui->actionConnect,SIGNAL(toggled(bool)),this,SLOT(changedConnect(bool))); + connect(ui->actionReset_Counters,SIGNAL(triggered()),this,SIGNAL(resetCounters())); + connect(ui->actionOpenLogFile,SIGNAL(triggered()),this,SLOT(openLogFile())); + connect(ui->actionHeaders,SIGNAL(triggered(bool)),this,SLOT(showHeaders(bool))); + connect(ui->actionModbus_Manual,SIGNAL(triggered()),this,SLOT(openModbusManual())); + //connect(ui->actionEnglish_en_US,SIGNAL(triggered()),this,SLOT(changeLanguage())); + //connect(ui->actionSimplified_Chinese_zh_CN,SIGNAL(triggered()),this,SLOT(changeLanguage())); + //connect(ui->actionTraditional_Chinese_zh_TW,SIGNAL(triggered()),this,SLOT(changeLanguage())); + connect(ui->actionLoad_Session,SIGNAL(triggered(bool)),this,SLOT(loadSession())); + connect(ui->actionSave_Session,SIGNAL(triggered(bool)),this,SLOT(saveSession())); + connect(m_dlgSettings, SIGNAL(changedEndianess(int)),this,SLOT(changedEndianess(int))); + + //UI - status + m_statusInd = new QLabel; + m_statusInd->setFixedSize( 16, 16 ); + m_statusText = new QLabel; + m_baseAddr = new QLabel(tr("Base Addr : ") + "0"); + m_statusPackets = new QLabel(tr("Packets : ") + "0"); + m_statusPackets->setStyleSheet("QLabel {color:blue;}"); + m_statusErrors = new QLabel(tr("Errors : ") + "0"); + m_statusErrors->setStyleSheet("QLabel {color:red;}"); + m_endian = new QLabel(tr("Endian : ") + EUtils::endianness(m_modbusCommSettings->endian())); + m_endian->setStyleSheet("QLabel {color:black;}"); + ui->statusBar->addWidget(m_statusInd); + ui->statusBar->addWidget(m_statusText, 10); + ui->statusBar->addWidget(m_baseAddr, 10); + ui->statusBar->addWidget(m_statusPackets, 10); + ui->statusBar->addWidget(m_endian, 10); + ui->statusBar->addWidget(m_statusErrors, 10); + m_statusInd->setPixmap(QPixmap(":/img/ballorange-16.png")); + + //Setup Toolbar + ui->mainToolBar->addAction(ui->actionLoad_Session); + ui->mainToolBar->addAction(ui->actionSave_Session); + ui->mainToolBar->addAction(ui->actionConnect); + ui->mainToolBar->addAction(ui->actionRead_Write); + ui->mainToolBar->addAction(ui->actionScan); + ui->mainToolBar->addAction(ui->actionClear); + ui->mainToolBar->addAction(ui->actionReset_Counters); + ui->mainToolBar->addSeparator(); + ui->mainToolBar->addAction(ui->actionOpenLogFile); + ui->mainToolBar->addAction(ui->actionBus_Monitor); + ui->mainToolBar->addAction(ui->actionTools); + ui->mainToolBar->addAction(ui->actionHeaders); + ui->mainToolBar->addSeparator(); + ui->mainToolBar->addAction(ui->actionSerial_RTU); + ui->mainToolBar->addAction(ui->actionTCP); + ui->mainToolBar->addAction(ui->actionSettings); + ui->mainToolBar->addSeparator(); + ui->mainToolBar->addAction(ui->actionModbus_Manual); + ui->mainToolBar->addAction(ui->actionAbout); + ui->mainToolBar->addAction(ui->actionExit); + + //Init models + ui->tblRegisters->setItemDelegate(m_modbus->regModel->itemDelegate()); + ui->tblRegisters->setModel(m_modbus->regModel->model); + ui->tblRegisters->horizontalHeader()->hide(); + ui->tblRegisters->verticalHeader()->hide(); + changedFrmt(m_modbusCommSettings->frmt()); + m_modbus->regModel->setStartAddrBase(10); + m_modbus->regModel->setEndian(m_modbusCommSettings->endian()); + m_modbus->setBaseAddr(m_modbusCommSettings->baseAddr().toInt()); + m_modbus->regModel->setFloatPrecision(m_modbusCommSettings->floatPrecision()); + m_modbus->setReadOutputsBeforeWrite(m_modbusCommSettings->readOutputsBeforeWrite()); + clearItems();//init model ui + + //Update UI + changedFunctionCode(m_modbusCommSettings->functionCode()); + changedModbusMode(m_modbusCommSettings->modbusMode()); + updateStatusBar(); + refreshView(); + + //Logging level + QsLogging::Logger::instance().setLoggingLevel((QsLogging::Level)m_modbusCommSettings->loggingLevel()); + QLOG_INFO()<< "Start Program" ; + +} + +MainWindow::~MainWindow() +{ + + if (m_modbus) + m_modbus->modbusDisConnect(); + delete ui; + + QLOG_INFO()<< "Stop Program" ; + +} + +void MainWindow::showSettingsModbusRTU() +{ + + //Show RTU Settings Dialog + m_dlgModbusRTU->modbus_connected = m_modbus->isConnected(); + if (m_dlgModbusRTU->exec()==QDialog::Accepted) { + QLOG_TRACE()<< "RTU settings changes accepted "; + updateStatusBar(); + m_modbusCommSettings->saveSettings(); + } + else + QLOG_WARN()<< "RTU settings changes rejected "; + +} + +void MainWindow::showSettingsModbusTCP() +{ + + //Show TCP Settings Dialog + m_dlgModbusTCP->modbus_connected = m_modbus->isConnected(); + if (m_dlgModbusTCP->exec()==QDialog::Accepted) { + QLOG_TRACE()<< "TCP settings changes accepted "; + updateStatusBar(); + m_modbusCommSettings->saveSettings(); + } + else + QLOG_WARN()<< "TCP settings changes rejected "; + +} + +void MainWindow::showSettings() +{ + + //Show General Settings Dialog + m_dlgSettings->modbus_connected = m_modbus->isConnected(); + if (m_dlgSettings->exec()==QDialog::Accepted) { + QLOG_TRACE()<< "Settings changes accepted "; + m_modbus->rawModel->setMaxNoOfLines(m_modbusCommSettings->maxNoOfLines().toInt()); + m_modbus->setTimeOut(m_modbusCommSettings->timeOut().toInt()); + ui->sbStartAddress->setMinimum(m_modbusCommSettings->baseAddr().toInt()); + m_modbus->setBaseAddr(m_modbusCommSettings->baseAddr().toInt()); + m_modbus->regModel->setEndian(m_modbusCommSettings->endian()); + m_modbusCommSettings->saveSettings(); + } + else + QLOG_WARN()<< "Settings changes rejected "; + + updateStatusBar(); + +} + +void MainWindow::showBusMonitor() +{ + + //Show Bus Monitor + + m_modbus->rawModel->setMaxNoOfLines(m_modbusCommSettings->maxNoOfLines().toInt()); + m_busMonitor->move(this->x() + this->width() + 20, this->y()); + m_busMonitor->show(); + +} + +void MainWindow::showTools() +{ + + //Show Tools + + m_tools->move(this->x() + this->width() + 40, this->y() + 20); + m_tools->show(); + +} + +void MainWindow::changedModbusMode(int currIndex) +{ + + //Change lblSlave text : Slave Addr, Unit ID + + QLOG_TRACE()<< "Modbus Mode changed. Index = " << currIndex; + m_modbusCommSettings->setModbusMode(currIndex); + m_modbusCommSettings->saveSettings(); + + if (currIndex == 0) { //RTU + ui->lblSlave->setText("Slave Addr"); + } + else { //TCP + ui->lblSlave->setText("Unit ID"); + } + + updateStatusBar(); + +} + +void MainWindow::changedFunctionCode(int currIndex) +{ + + //Enable-Disable number of coils or registers + + QLOG_TRACE()<< "Function Code changed. Index = " << currIndex; + m_modbusCommSettings->setFunctionCode(currIndex); + m_modbusCommSettings->saveSettings(); + + const int functionCode = EUtils::ModbusFunctionCode(currIndex); + QString String_number_of_coils(tr("Number of Coils")); + QString String_number_of_inputs(tr("Number of Inputs")); + QString String_number_of_registers(tr("Number of Registers")); + switch(functionCode)//Label = Read Request, Write Request + { + case MODBUS_FC_READ_COILS: + m_modbus->regModel->setIs16Bit(false); + ui->sbNoOfRegs->setEnabled(true); + ui->sbNoOfRegs->setMaximum(2000); + ui->lblNoOfCoils->setText(String_number_of_coils); + ui->cmbFrmt->setCurrentIndex(0); + ui->cmbFrmt->setEnabled(false); + break; + case MODBUS_FC_READ_DISCRETE_INPUTS: + m_modbus->regModel->setIs16Bit(false); + ui->sbNoOfRegs->setEnabled(true); + ui->sbNoOfRegs->setMaximum(2000); + ui->lblNoOfCoils->setText(String_number_of_inputs); + ui->cmbFrmt->setCurrentIndex(0); + ui->cmbFrmt->setEnabled(false); + break; + case MODBUS_FC_READ_HOLDING_REGISTERS: + m_modbus->regModel->setIs16Bit(true); + ui->sbNoOfRegs->setEnabled(true); + ui->sbNoOfRegs->setMaximum(125); + ui->lblNoOfCoils->setText(String_number_of_registers); + ui->cmbFrmt->setEnabled(true); + break; + case MODBUS_FC_READ_INPUT_REGISTERS: + m_modbus->regModel->setIs16Bit(true); + ui->sbNoOfRegs->setEnabled(true); + ui->sbNoOfRegs->setMaximum(125); + ui->lblNoOfCoils->setText(String_number_of_registers); + ui->cmbFrmt->setEnabled(true); + break; + case MODBUS_FC_WRITE_SINGLE_COIL: + m_modbus->regModel->setIs16Bit(false); + ui->sbNoOfRegs->setValue(1); + ui->sbNoOfRegs->setEnabled(false); + ui->lblNoOfCoils->setText(String_number_of_coils); + ui->cmbFrmt->setCurrentIndex(0); + ui->cmbFrmt->setEnabled(false); + break; + case MODBUS_FC_WRITE_MULTIPLE_COILS: + m_modbus->regModel->setIs16Bit(false); + if (ui->sbNoOfRegs->value() < 2) + ui->sbNoOfRegs->setValue(2); + ui->sbNoOfRegs->setEnabled(true); + ui->sbNoOfRegs->setMaximum(2000); + ui->lblNoOfCoils->setText(String_number_of_coils); + ui->cmbFrmt->setCurrentIndex(0); + ui->cmbFrmt->setEnabled(false); + break; + case MODBUS_FC_WRITE_SINGLE_REGISTER: + m_modbus->regModel->setIs16Bit(true); + ui->sbNoOfRegs->setValue(1); + ui->sbNoOfRegs->setEnabled(false); + ui->lblNoOfCoils->setText(String_number_of_registers); + ui->cmbFrmt->setEnabled(true); + break; + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + m_modbus->regModel->setIs16Bit(true); + if (ui->sbNoOfRegs->value() < 2) + ui->sbNoOfRegs->setValue(2); + ui->sbNoOfRegs->setEnabled(true); + ui->sbNoOfRegs->setMaximum(125); + ui->lblNoOfCoils->setText(String_number_of_registers); + ui->cmbFrmt->setEnabled(true); + break; + default: + m_modbus->regModel->setIs16Bit(false); + ui->sbNoOfRegs->setValue(1); + ui->sbNoOfRegs->setEnabled(true); + ui->sbNoOfRegs->setMaximum(2000); + ui->lblNoOfCoils->setText(String_number_of_coils); + ui->cmbFrmt->setEnabled(true); + break; + } + + m_modbus->setNumOfRegs(ui->sbNoOfRegs->value()); + addItems(); + +} + +void MainWindow::changedFrmt(int currIndex) +{ + + //Change Format + + QLOG_TRACE()<< "Format changed. Index = " << currIndex; + m_modbusCommSettings->setFrmt(currIndex); + m_modbusCommSettings->saveSettings(); + + switch(currIndex) + { + case 0: + ui->chkSigned->setVisible(false); + ui->chkSigned->setChecked(false); + m_modbus->regModel->setFrmt(EUtils::Bin); + ui->lblPrecision->setVisible(false); + ui->sbPrecision->setVisible(false); + ui->sbNoOfRegs->setMinimum(1); + break; + case 1: + ui->chkSigned->setVisible(true); + m_modbus->regModel->setFrmt(EUtils::Dec); + ui->lblPrecision->setVisible(false); + ui->sbPrecision->setVisible(false); + ui->sbNoOfRegs->setMinimum(1); + break; + case 2: + ui->chkSigned->setVisible(false); + ui->chkSigned->setChecked(false); + m_modbus->regModel->setFrmt(EUtils::Hex); + ui->lblPrecision->setVisible(false); + ui->sbPrecision->setVisible(false); + ui->sbNoOfRegs->setMinimum(1); + break; + case 3: + ui->chkSigned->setVisible(false); + ui->chkSigned->setChecked(false); + m_modbus->regModel->setFrmt(EUtils::Float); + ui->lblPrecision->setVisible(true); + ui->sbPrecision->setVisible(true); + ui->sbNoOfRegs->setMinimum(1); + break; + default: + m_modbus->regModel->setFrmt(EUtils::Dec); + ui->chkSigned->setVisible(true); + ui->lblPrecision->setVisible(false); + ui->sbPrecision->setVisible(false); + ui->sbNoOfRegs->setMinimum(1); + break; + } + + //clear table on format change + addItems(); + +} + +void MainWindow::changedDecSign(bool value) +{ + //Change Dec Signed - Unsigned + + QLOG_TRACE()<< "Dec Signed-Unsigned changed. Signed = " << value; + + m_modbus->regModel->setIsSigned(value); + +} + +void MainWindow::changedScanRate(int value) +{ + + //Enable-Disable Time Interval + + QLOG_TRACE()<< "ScanRate changed. Value = " << value; + m_modbusCommSettings->setScanRate(value); + m_modbusCommSettings->saveSettings(); + + m_modbus->setScanRate(value); + +} + +void MainWindow::changedConnect(bool value) +{ + + //Connect - Disconnect + + if (value) { //Connected + modbusConnect(true); + QLOG_INFO()<< "Connected "; + } + else { //Disconnected + modbusConnect(false); + QLOG_INFO()<< "Disconnected "; + } + + m_modbus->resetCounters(); + refreshView(); + +} + +void MainWindow::changedSlaveID(int value) +{ + + //Slave ID + + QLOG_TRACE()<< "Slave ID Changed. Value = " << value; + m_modbusCommSettings->setSlaveID(value); + m_modbusCommSettings->saveSettings(); + +} + +void MainWindow::changedEndianess(int endian) +{ + + //Endianess + + QLOG_TRACE()<< "Endianess Changed. Value = " << endian; + + //clear table on endianess change + addItems(); + +} + +void MainWindow::openLogFile() +{ + + //Open log file + QString arg; + QLOG_TRACE()<< "Open log file"; + + arg = "file:///" + QCoreApplication::applicationDirPath() + "/QModMaster.log"; + QDesktopServices::openUrl(QUrl(arg)); + + +} + +void MainWindow::openModbusManual() +{ + + //Open Modbus Manual + QString arg; + QLOG_TRACE()<< "Open Modbus Manual"; + + arg = "file:///" + QCoreApplication::applicationDirPath() + "/ManModbus/index.html"; + QDesktopServices::openUrl(QUrl(arg)); + + +} + +void MainWindow::changedStartAddrBase(int currIndex) +{ + + //Change Base + + QLOG_TRACE()<< "Start Addr Base changed. Index = " << currIndex; + + switch(currIndex) + { + case 0: + ui->sbStartAddress->setDisplayIntegerBase(10); + m_modbus->regModel->setStartAddrBase(10); + break; + case 1: + ui->sbStartAddress->setDisplayIntegerBase(16); + m_modbus->regModel->setStartAddrBase(16); + break; + default: + ui->sbStartAddress->setDisplayIntegerBase(10); + m_modbus->regModel->setStartAddrBase(10); + break; + } + +} + +void MainWindow::changedStartAddress(int value) +{ + + //Start Address changed + QLOG_TRACE()<< "Start Address changed. Value = " << value; + m_modbusCommSettings->setStartAddr(value); + m_modbusCommSettings->saveSettings(); + + m_modbus->setStartAddr(value); + addItems(); + +} + +void MainWindow::changedNoOfRegs(int value) +{ + + //No of regs changed + QLOG_TRACE()<< "No Of Regs changed. Value = " << value; + m_modbusCommSettings->setNoOfRegs(value); + m_modbusCommSettings->saveSettings(); + + m_modbus->setNumOfRegs(value); + addItems(); + +} + +void MainWindow::changedFloatPrecision(int precision) +{ + + //Float precision changed + m_modbusCommSettings->setfloatPrecision(precision); + m_modbusCommSettings->saveSettings(); + m_modbus->regModel->setFloatPrecision(precision); + +} +void MainWindow::updateStatusBar() +{ + + //Update status bar + + QString msg; + + if(ui->cmbModbusMode->currentIndex() == 0) { //RTU + msg = "RTU : "; + msg += m_modbusCommSettings->serialPortName() + " | "; + msg += m_modbusCommSettings->baud() + ","; + msg += m_modbusCommSettings->dataBits() + ","; + msg += m_modbusCommSettings->stopBits() + ","; + msg += m_modbusCommSettings->parity(); + } + else { + msg = "TCP : "; + msg += m_modbusCommSettings->slaveIP() + ":"; + msg += m_modbusCommSettings->TCPPort(); + } + + m_statusText->clear(); + m_statusText->setText(msg); + + //Connection is valid + if (m_modbus->isConnected()) { + m_statusInd->setPixmap(QPixmap(":/icons/bullet-green-16.png")); + } + else { + m_statusInd->setPixmap(QPixmap(":/icons/bullet-red-16.png")); + } + + //base Address + m_baseAddr->setText("Base Addr : " + m_modbusCommSettings->baseAddr()); + + //endianness + m_endian->setText(("Endian : ") + EUtils::endianness(m_modbusCommSettings->endian())); + +} + +void MainWindow::addItems() +{ + int baseAddr; + //add items + + m_modbus->setSlave(ui->sbSlaveID->value()); + m_modbus->setFunctionCode(EUtils::ModbusFunctionCode(ui->cmbFunctionCode->currentIndex())); + m_modbus->setNumOfRegs(ui->sbNoOfRegs->value()); + //get base address + baseAddr = m_modbusCommSettings->baseAddr().toInt(); + m_modbus->setStartAddr(ui->sbStartAddress->value() - baseAddr); + + QLOG_INFO()<< "Add Items. Function Code = " << QString::number(EUtils::ModbusFunctionCode(ui->cmbFunctionCode->currentIndex()),16); + + m_modbus->addItems(); + +} + +void MainWindow::clearItems() +{ + + //Clear items from registers model + + QLOG_TRACE()<< "clearItems" ; + + m_modbus->regModel->clear(); + addItems(); + +} + +void MainWindow::modbusRequest() +{ + + //Request items from modbus adapter and add raw data to raw data model + int rowCount = m_modbus->regModel->model->rowCount(); + int baseAddr; + + QLOG_TRACE()<< "Request transaction. No or registers = " << rowCount; + + if (rowCount == 0) { + mainWin->showUpInfoBar(tr("Request failed\nAdd items to Registers Table."), InfoBar::Error); + QLOG_WARN()<< "Request failed. No items in registers table "; + return; + } + else if (m_modbus->regModel->getFrmt() == EUtils::Float && + ui->sbNoOfRegs->value() % 2 != 0) { + mainWin->showUpInfoBar(tr("The number of registers must be even."), InfoBar::Error); + QLOG_WARN()<< "Request failed. The number of registers must be even "; + return; + } + else { + mainWin->hideInfoBar(); + } + + //get base address + baseAddr = m_modbusCommSettings->baseAddr().toInt(); + + m_modbus->setSlave(ui->sbSlaveID->value()); + m_modbus->setFunctionCode(EUtils::ModbusFunctionCode(ui->cmbFunctionCode->currentIndex())); + m_modbus->setStartAddr(ui->sbStartAddress->value() - baseAddr); + m_modbus->setNumOfRegs(ui->sbNoOfRegs->value()); + + //Modbus data + m_modbus->modbusTransaction(); + +} + +void MainWindow::modbusScanCycle(bool value) +{ + + //Request items from modbus adapter and add raw data to raw data model + int rowCount = m_modbus->regModel->model->rowCount(); + int baseAddr; + + if (value && rowCount == 0) { + //mainWin->showUpInfoBar(tr("Request failed\nAdd items to Registers Table."), InfoBar::Error); + QLOG_WARN()<< "Request failed. No items in registers table "; + QMessageBox::critical(this, "QModMaster", tr("Request failed\nAdd items to Registers Table.")); + ui->actionScan->setChecked(false); + return; + } + else if (value && m_modbus->regModel->getFrmt() == EUtils::Float && + ui->sbNoOfRegs->value() % 2 != 0) { + //mainWin->showUpInfoBar(tr("The number of registers must be even."), InfoBar::Error); + QLOG_WARN()<< "Request failed. The number of registers must be even "; + QMessageBox::critical(this, "QModMaster", tr("The number of registers must be even.")); + ui->actionScan->setChecked(false); + return; + } + else { + mainWin->hideInfoBar(); + } + + //get base address + baseAddr = m_modbusCommSettings->baseAddr().toInt(); + + m_modbus->setSlave(ui->sbSlaveID->value()); + m_modbus->setFunctionCode(EUtils::ModbusFunctionCode(ui->cmbFunctionCode->currentIndex())); + m_modbus->setStartAddr(ui->sbStartAddress->value() - baseAddr); + m_modbus->setNumOfRegs(ui->sbNoOfRegs->value()); + + //Start-Stop poll timer + QLOG_TRACE()<< "Scan time = " << value; + if (value){ + if (ui->spInterval->value() < m_modbusCommSettings->timeOut().toInt() * 1000 * 2){ + mainWin->showUpInfoBar(tr("Scan rate should be at least 2 * Timeout."), InfoBar::Error); + QLOG_ERROR()<< "Scan rate error. should be at least 2 * Timeout "; + } + else { + m_modbus->setScanRate(ui->spInterval->value()); + m_modbus->startPollTimer(); + } + } + else + m_modbus->stopPollTimer(); + + //Update UI + ui->cmbFunctionCode->setEnabled(!value); + ui->sbSlaveID->setEnabled(!value); + ui->sbStartAddress->setEnabled(!value); + ui->spInterval->setEnabled(!value); + ui->cmbStartAddrBase->setEnabled(!value); + if (!value) + changedFunctionCode(ui->cmbFunctionCode->currentIndex()); + else + ui->sbNoOfRegs->setEnabled(false); + +} + +void MainWindow::modbusConnect(bool connect) + { + + //Modbus connect - RTU/TCP + QLOG_TRACE()<< "Modbus Connect. Value = " << connect; + + if (connect) { //RTU + if (ui->cmbModbusMode->currentIndex() == EUtils::RTU) { + m_modbus->setSlave(ui->sbSlaveID->value()); + m_modbus->modbusConnectRTU(m_modbusCommSettings->serialPortName(), + m_modbusCommSettings->baud().toInt(), + EUtils::parity(m_modbusCommSettings->parity()), + m_modbusCommSettings->dataBits().toInt(), + m_modbusCommSettings->stopBits().toInt(), + m_modbusCommSettings->RTS().toInt(), + m_modbusCommSettings->timeOut().toInt() + ); + } + else { //TCP + m_modbus->modbusConnectTCP(m_modbusCommSettings->slaveIP(), + m_modbusCommSettings->TCPPort().toInt(), + m_modbusCommSettings->timeOut().toInt()); + } + } + else { //Disconnect + m_modbus->modbusDisConnect(); + ui->actionScan->setChecked(false); + } + + updateStatusBar(); + + //Update UI + ui->actionLoad_Session->setEnabled(!m_modbus->isConnected()); + ui->actionSave_Session->setEnabled(!m_modbus->isConnected()); + ui->actionConnect->setChecked(m_modbus->isConnected()); + ui->actionRead_Write->setEnabled(m_modbus->isConnected()); + ui->actionScan->setEnabled(m_modbus->isConnected()); + ui->cmbModbusMode->setEnabled(!m_modbus->isConnected()); + + } + +void MainWindow::showHeaders(bool value) +{ + QLOG_TRACE()<< "Show Headers = " << value; + + if (value){ + ui->tblRegisters->horizontalHeader()->show(); + ui->tblRegisters->verticalHeader()->show(); + } + else { + ui->tblRegisters->horizontalHeader()->hide(); + ui->tblRegisters->verticalHeader()->hide(); + } + +} + + void MainWindow::refreshView() + { + + QLOG_TRACE()<< "Packets sent / received = " << m_modbus->packets() << ", errors = " << m_modbus->errors(); + ui->tblRegisters->resizeColumnsToContents(); + + m_statusPackets->setText(tr("Packets : ") + QString("%1").arg(m_modbus->packets())); + m_statusErrors->setText(tr("Errors : ") + QString("%1").arg(m_modbus->errors())); + + } + +void MainWindow::loadSession() +{ +QString fName; + + QLOG_TRACE()<< "load session"; + fName = QFileDialog::getOpenFileName(this, + "Load Session file", + "", + "Session Files (*.ses);;All Files (*.*)"); + //check + if (fName != ""){ + m_modbusCommSettings->loadSession(fName); + //Update UI + ui->sbStartAddress->setMinimum(m_modbusCommSettings->baseAddr().toInt()); + ui->cmbFrmt->setCurrentIndex(m_modbusCommSettings->frmt()); + ui->sbPrecision->setValue(m_modbusCommSettings->floatPrecision()); + ui->cmbFunctionCode->setCurrentIndex(m_modbusCommSettings->functionCode()); + ui->cmbModbusMode->setCurrentIndex(m_modbusCommSettings->modbusMode()); + ui->sbSlaveID->setValue(m_modbusCommSettings->slaveID()); + ui->spInterval->setValue(m_modbusCommSettings->scanRate()); + ui->sbStartAddress->setValue(m_modbusCommSettings->startAddr()); + ui->sbNoOfRegs->setValue(m_modbusCommSettings->noOfRegs()); + updateStatusBar(); + refreshView(); + QMessageBox::information(this, "QModMaster", "Load session file : " + fName); + } + else + QMessageBox::information(this, "QModMaster", "Cancel operation Or no file selected"); + +} + +void MainWindow::saveSession() +{ +QString fName; + + QLOG_TRACE()<< "save session"; + fName = QFileDialog::getSaveFileName(this, + "Save Session file", + "", + "Session Files (*.ses)"); + + //check + if (fName != ""){ + m_modbusCommSettings->saveSession(fName); + QMessageBox::information(this, "QModMaster", "Save session file : " + fName); + } + else + QMessageBox::information(this, "QModMaster", "Cancel operation Or no file selected"); + +} +void MainWindow::showUpInfoBar(QString message, InfoBar::InfoType type) +{ + ui->infobar->show(message, type); +} + +void MainWindow::hideInfoBar() +{ + ui->infobar->hide(); +} + +void MainWindow::changeEvent(QEvent* event) +{ + if(event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } + QMainWindow::changeEvent(event); +} + +void MainWindow::changeLanguage() +{ + // Not Used + + extern QTranslator *Translator; + QCoreApplication::removeTranslator(Translator); + Translator->load(":/translations/" + QCoreApplication::applicationName() + sender()->objectName().right(6)); + QCoreApplication::installTranslator(Translator); +} + diff --git a/Applications/qModMaster/src/mainwindow.h b/Applications/qModMaster/src/mainwindow.h new file mode 100644 index 0000000..ef46a02 --- /dev/null +++ b/Applications/qModMaster/src/mainwindow.h @@ -0,0 +1,94 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include + +#include "forms/about.h" +#include "forms/settingsmodbusrtu.h" +#include "forms/settingsmodbustcp.h" +#include "forms/settings.h" +#include "forms/busmonitor.h" +#include "forms/tools.h" +#include "modbuscommsettings.h" +#include "modbusadapter.h" +#include "infobar.h" + +namespace Ui { + class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0, ModbusAdapter *adapter = 0, ModbusCommSettings *settings = 0); + ~MainWindow(); + void showUpInfoBar(QString message, InfoBar::InfoType type); + void hideInfoBar(); + +private: + Ui::MainWindow *ui; + //UI - Dialogs + About *m_dlgAbout; + SettingsModbusRTU *m_dlgModbusRTU; + SettingsModbusTCP *m_dlgModbusTCP; + Settings *m_dlgSettings; + BusMonitor *m_busMonitor; + Tools *m_tools; + + ModbusCommSettings *m_modbusCommSettings; + void updateStatusBar(); + QLabel *m_statusText; + QLabel *m_statusInd; + QLabel *m_baseAddr; + QLabel *m_statusPackets; + QLabel *m_statusErrors; + QLabel *m_endian; + ModbusAdapter *m_modbus; + void modbusConnect(bool connect); + + void changeEvent(QEvent* event); + +private slots: + void showSettingsModbusRTU(); + void showSettingsModbusTCP(); + void showSettings(); + void showBusMonitor(); + void showTools(); + void changedModbusMode(int currIndex); + void changedFunctionCode(int currIndex); + void changedFrmt(int currIndex); + void changedDecSign(bool value); + void changedStartAddrBase(int currIndex); + void changedScanRate(int value); + void changedConnect(bool value); + void changedStartAddress(int value); + void changedNoOfRegs(int value); + void changedSlaveID(int value); + void changedFloatPrecision(int precision); + void changedEndianess(int endian); + void addItems(); + void clearItems(); + void openLogFile(); + void modbusScanCycle(bool value); + void modbusRequest(); + void refreshView(); + void changeLanguage(); + void openModbusManual(); + void loadSession(); + void saveSession(); + void showHeaders(bool value); + +signals: + void resetCounters(); + +}; + +extern MainWindow *mainWin; + +#endif // MAINWINDOW_H diff --git a/Applications/qModMaster/src/modbusadapter.cpp b/Applications/qModMaster/src/modbusadapter.cpp new file mode 100644 index 0000000..2d5eee8 --- /dev/null +++ b/Applications/qModMaster/src/modbusadapter.cpp @@ -0,0 +1,580 @@ +#include +#include +#include "modbusadapter.h" +#include "mainwindow.h" + +#include "QsLog.h" +#include + +ModbusAdapter *m_instance; + +ModbusAdapter::ModbusAdapter(QObject *parent) : + QObject(parent), + m_modbus(NULL) +{ + m_instance = this; + regModel=new RegistersModel(this); + rawModel=new RawDataModel(this); + m_connected = false; + m_ModBusMode = EUtils::None; + m_pollTimer = new QTimer(this); + m_timeOut = 0; + m_transactionIsPending = false; + m_packets = 0; + m_errors = 0; + m_readOutputsBeforeWrite = true; + connect(m_pollTimer,SIGNAL(timeout()),this,SLOT(modbusTransaction())); + connect(regModel,SIGNAL(refreshView()),this,SIGNAL(refreshView())); + //setup memory for data + dest = (uint8_t *) malloc(2000 * sizeof(uint8_t)); + memset(dest, 0, 2000 * sizeof(uint8_t)); + dest16 = (uint16_t *) malloc(125 * sizeof(uint16_t)); + memset(dest16, 0, 125 * sizeof(uint16_t)); + +} + +ModbusAdapter::~ModbusAdapter() +{ + free(dest); + free(dest16); +} + +void ModbusAdapter::modbusConnectRTU(QString port, int baud, QChar parity, int dataBits, int stopBits, int RTS, int timeOut) +{ + //Modbus RTU connect + QString line; + modbusDisConnect(); + + QLOG_INFO()<< "Modbus Connect RTU"; + + m_modbus = modbus_new_rtu(port.toLatin1().constData(),baud,parity.toLatin1(),dataBits,stopBits,RTS); + line = "Connecting to Serial Port [" + port + "]..."; + QLOG_TRACE() << line; + + //Debug messages from libmodbus + #ifdef LIB_MODBUS_DEBUG_OUTPUT + modbus_set_debug(m_modbus, 1); + #endif + + m_timeOut = timeOut; + + if(m_modbus == NULL){ + mainWin->showUpInfoBar(tr("Unable to create the libmodbus context."), InfoBar::Error); + QLOG_ERROR()<< "Connection failed. Unable to create the libmodbus context"; + return; + } + else if(m_modbus && modbus_set_slave(m_modbus, m_slave) == -1){ + modbus_free(m_modbus); + mainWin->showUpInfoBar(tr("Invalid slave ID."), InfoBar::Error); + QLOG_ERROR()<< "Connection failed. Invalid slave ID"; + return; + } + else if(m_modbus && modbus_connect(m_modbus) == -1) { + modbus_free(m_modbus); + mainWin->showUpInfoBar(tr("Connection failed\nCould not connect to serial port."), InfoBar::Error); + QLOG_ERROR()<< "Connection failed. Could not connect to serial port"; + m_connected = false; + line += "Failed"; + } + else { + //error recovery mode + modbus_set_error_recovery(m_modbus, MODBUS_ERROR_RECOVERY_PROTOCOL); + //response_timeout; + modbus_set_response_timeout(m_modbus, timeOut, 0); + m_connected = true; + line += "OK"; + mainWin->hideInfoBar(); + QLOG_TRACE() << line; + } + + m_ModBusMode = EUtils::RTU; + + //Add line to raw data model + line = EUtils::SysTimeStamp() + " - " + line; + rawModel->addLine(line); + +} + +void ModbusAdapter::modbusConnectTCP(QString ip, int port, int timeOut) +{ + //Modbus TCP connect + QString strippedIP = ""; + QString line; + modbusDisConnect(); + + QLOG_INFO()<< "Modbus Connect TCP"; + + line = "Connecting to IP : " + ip + ":" + QString::number(port); + QLOG_TRACE() << line; + strippedIP = stripIP(ip); + if (strippedIP == ""){ + mainWin->showUpInfoBar(tr("Connection failed\nBlank IP Address."), InfoBar::Error); + QLOG_ERROR()<< "Connection failed. Blank IP Address"; + return; + } + else { + m_modbus = modbus_new_tcp(strippedIP.toLatin1().constData(), port); + mainWin->hideInfoBar(); + QLOG_TRACE() << "Connecting to IP : " << ip << ":" << port; + } + + //Debug messages from libmodbus + #ifdef LIB_MODBUS_DEBUG_OUTPUT + modbus_set_debug(m_modbus, 1); + #endif + + m_timeOut = timeOut; + + if(m_modbus == NULL){ + mainWin->showUpInfoBar(tr("Unable to create the libmodbus context."), InfoBar::Error); + QLOG_ERROR()<< "Connection failed. Unable to create the libmodbus context"; + return; + } + else if(m_modbus && modbus_connect(m_modbus) == -1) { + modbus_free(m_modbus); + mainWin->showUpInfoBar(tr("Connection failed\nCould not connect to TCP port."), InfoBar::Error); + QLOG_ERROR()<< "Connection to IP : " << ip << ":" << port << "...failed. Could not connect to TCP port"; + m_connected = false; + line += " Failed"; + } + else { + //error recovery mode + modbus_set_error_recovery(m_modbus, MODBUS_ERROR_RECOVERY_PROTOCOL); + //response_timeout; + modbus_set_response_timeout(m_modbus, timeOut, 0); + m_connected = true; + line += " OK"; + mainWin->hideInfoBar(); + QLOG_TRACE() << line; + } + + m_ModBusMode = EUtils::TCP; + + //Add line to raw data model + line = EUtils::SysTimeStamp() + " - " + line; + rawModel->addLine(line); + +} + + +void ModbusAdapter::modbusDisConnect() +{ + //Modbus disconnect + + QLOG_INFO()<< "Modbus disconnected"; + + if(m_modbus) { + if (m_connected){ + modbus_close(m_modbus); + modbus_free(m_modbus); + } + m_modbus = NULL; + } + + m_connected = false; + + m_ModBusMode = EUtils::None; + +} + +bool ModbusAdapter::isConnected() +{ + //Modbus is connected + + return m_connected; +} + +void ModbusAdapter::modbusTransaction() +{ + //Modbus request data + + QLOG_INFO() << "Modbus Transaction. Function Code = " << m_functionCode; + m_packets += 1; + + QApplication::setOverrideCursor(Qt::WaitCursor); + + switch(m_functionCode) + { + case MODBUS_FC_READ_COILS: + case MODBUS_FC_READ_DISCRETE_INPUTS: + case MODBUS_FC_READ_HOLDING_REGISTERS: + case MODBUS_FC_READ_INPUT_REGISTERS: + modbusReadData(m_slave,m_functionCode,m_startAddr,m_numOfRegs); + break; + + case MODBUS_FC_WRITE_SINGLE_COIL: + case MODBUS_FC_WRITE_SINGLE_REGISTER: + case MODBUS_FC_WRITE_MULTIPLE_COILS: + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + modbusWriteData(m_slave,m_functionCode,m_startAddr,m_numOfRegs); + break; + default: + break; + } + + QApplication::setOverrideCursor(Qt::ArrowCursor); + + emit(refreshView()); + +} + +void ModbusAdapter::modbusReadData(int slave, int functionCode, int startAddress, int noOfItems) +{ + + QLOG_INFO() << "Modbus Read Data "; + + if(m_modbus == NULL) return; + + int ret = -1; //return value from read functions + bool is16Bit = false; + + modbus_set_slave(m_modbus, slave); + //request data from modbus + switch(functionCode) + { + case MODBUS_FC_READ_COILS: + ret = modbus_read_bits(m_modbus, startAddress, noOfItems, dest); + break; + + case MODBUS_FC_READ_DISCRETE_INPUTS: + ret = modbus_read_input_bits(m_modbus, startAddress, noOfItems, dest); + break; + + case MODBUS_FC_READ_HOLDING_REGISTERS: + ret = modbus_read_registers(m_modbus, startAddress, noOfItems, dest16); + is16Bit = true; + break; + + case MODBUS_FC_READ_INPUT_REGISTERS: + ret = modbus_read_input_registers(m_modbus, startAddress, noOfItems, dest16); + is16Bit = true; + break; + + default: + break; + } + + QLOG_TRACE() << "Modbus Read Data return value = " << ret << ", errno = " << errno; + + //update data model + if(ret == noOfItems) + { + if (regModel->getFrmt() != EUtils::Float) + { + for(int i = 0; i < noOfItems; ++i) + { + int data = is16Bit ? dest16[i] : dest[i]; + regModel->setValue(i,data); + } + } + else//read float values + { + for(int i = 0; i < noOfItems - 1; i+=2) + { + int dataHi = is16Bit ? dest16[i] : dest[i]; + int dataLo = is16Bit ? dest16[i+1] : dest[i+1]; + regModel->setValue32(i/2,dataHi,dataLo); + } + } + + mainWin->hideInfoBar(); + } + else + { + + regModel->setNoValidValues(); + m_errors += 1; + + QString line = ""; + if(ret < 0) { + line = QString("Error : ") + EUtils::libmodbus_strerror(errno); + QLOG_ERROR() << "Read Data failed. " << line; + rawModel->addLine(EUtils::SysTimeStamp() + " - " + line); + line = QString(tr("Read data failed.\nError : ")) + EUtils::libmodbus_strerror(errno); + } + else { + line = QString("Number of registers returned does not match number of registers requested!. Error : ") + EUtils::libmodbus_strerror(errno); + QLOG_ERROR() << "Read Data failed. " << line; + rawModel->addLine(EUtils::SysTimeStamp() + " - " + line); + line = QString(tr("Read data failed.\nNumber of registers returned does not match number of registers requested!. Error : ")) + EUtils::libmodbus_strerror(errno); + } + + mainWin->showUpInfoBar(line, InfoBar::Error); + modbus_flush(m_modbus); //flush data + } + +} + +void ModbusAdapter::modbusWriteData(int slave, int functionCode, int startAddress, int noOfItems) +{ + + QLOG_INFO() << "Modbus Write Data "; + + if(m_modbus == NULL) return; + + int ret = -1; //return value from functions + + union{ + struct{qint16 low, high;} reg; + float value; + } modelData; + + modbus_set_slave(m_modbus, slave); + //request data from modbus + switch(functionCode) + { + case MODBUS_FC_WRITE_SINGLE_COIL: + ret = modbus_write_bit(m_modbus, startAddress,regModel->value(0)); + noOfItems = 1; + break; + + case MODBUS_FC_WRITE_SINGLE_REGISTER: + ret = modbus_write_register( m_modbus, startAddress,regModel->value(0)); + noOfItems = 1; + break; + + case MODBUS_FC_WRITE_MULTIPLE_COILS: + { + uint8_t * data = new uint8_t[noOfItems]; + for(int i = 0; i < noOfItems; ++i) + { + data[i] = regModel->value(i); + } + ret = modbus_write_bits(m_modbus, startAddress, noOfItems, data); + delete[] data; + break; + } + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + { + if (regModel->getFrmt() != EUtils::Float) { + uint16_t * data = new uint16_t[noOfItems]; + for(int i = 0; i < noOfItems; ++i) + { + data[i] = regModel->value(i); + } + ret = modbus_write_registers(m_modbus, startAddress, noOfItems, data); + delete[] data; + break; + } + else { //write float values + uint16_t * data = new uint16_t[noOfItems]; + for(int i = 0; i < noOfItems - 1; i+=2) + { + modelData.value = regModel->floatValue(i/2); + if (regModel->getEndian() == EUtils::Little){ + data[i] = modelData.reg.low; + data[i+1] = modelData.reg.high; + } + else if (regModel->getEndian() == EUtils::Big){ + data[i] = modelData.reg.high; + data[i+1] = modelData.reg.low; + } + } + ret = modbus_write_registers(m_modbus, startAddress, noOfItems, data); + delete[] data; + break; + } + } + + default: + break; + } + + QLOG_TRACE() << "Modbus Write Data return value = " << ret << ", errno = " << errno;; + + //update data model + if(ret == noOfItems) + { + //values written correctly + rawModel->addLine(EUtils::SysTimeStamp() + " - values written correctly."); + mainWin->hideInfoBar(); + } + else + { + + regModel->setNoValidValues(); + m_errors += 1; + + QString line; + if(ret < 0) { + line = QString("Error : ") + EUtils::libmodbus_strerror(errno); + QLOG_ERROR() << "Write Data failed. " << line; + rawModel->addLine(EUtils::SysTimeStamp() + " - " + line); + line = QString(tr("Write data failed.\nError : ")) + EUtils::libmodbus_strerror(errno); + } + else { + line = QString("Number of registers returned does not match number of registers requested!. Error : ") + EUtils::libmodbus_strerror(errno); + QLOG_ERROR() << "Write Data failed. " << line; + rawModel->addLine(EUtils::SysTimeStamp() + " - " + line); + line = QString(tr("Write data failed.\nNumber of registers returned does not match number of registers requested!. Error : ")) + EUtils::libmodbus_strerror(errno); + } + + mainWin->showUpInfoBar(line, InfoBar::Error); + modbus_flush(m_modbus); //flush data + } + +} + +void ModbusAdapter::busMonitorRequestData(uint8_t * data, int dataLen) +{ + + //Request Raw data from port - Update raw data model + + QString line; + + for(int i = 0; i < dataLen; ++i ) { + line += QString().asprintf( "%.2x ", data[i] ); + } + + QLOG_INFO() << "Tx Data : " << line; + line = EUtils::TxTimeStamp(m_ModBusMode) + " - " + line.toUpper(); + + rawModel->addLine(line); + + m_transactionIsPending = true; + +} +void ModbusAdapter::busMonitorResponseData(uint8_t * data, int dataLen) +{ + + //Response Raw data from port - Update raw data model + + QString line; + + for(int i = 0; i < dataLen; ++i ) { + line += QString().asprintf( "%.2x ", data[i] ); + } + + QLOG_INFO() << "Rx Data : " << line; + line = EUtils::RxTimeStamp(m_ModBusMode) + " - " + line.toUpper(); + + rawModel->addLine(line); + + m_transactionIsPending = false; + +} + +void ModbusAdapter::setSlave(int slave) +{ + m_slave = slave; +} + +void ModbusAdapter::setFunctionCode(int functionCode) +{ + m_functionCode = functionCode; +} + +void ModbusAdapter::setStartAddr(int addr) +{ + m_startAddr = addr; +} + +void ModbusAdapter::setBaseAddr(int baseAddr) +{ + m_baseAddr = baseAddr; +} + +void ModbusAdapter::setNumOfRegs(int num) +{ + m_numOfRegs = num; +} + +void ModbusAdapter::addItems() +{ + //TODO - fix base addr + regModel->addItems(m_startAddr + m_baseAddr, m_numOfRegs, EUtils::ModbusIsWriteFunction(m_functionCode)); + //If it is a write function -> read registers + if (!m_connected || !m_readOutputsBeforeWrite) + return; + else if (EUtils::ModbusIsWriteCoilsFunction(m_functionCode)){ + modbusReadData(m_slave,EUtils::ReadCoils,m_startAddr,m_numOfRegs); + emit(refreshView()); + } + else if (EUtils::ModbusIsWriteRegistersFunction(m_functionCode)){ + modbusReadData(m_slave,EUtils::ReadHoldRegs,m_startAddr,m_numOfRegs); + emit(refreshView()); + } + +} + +void ModbusAdapter::setScanRate(int scanRate) +{ + m_scanRate = scanRate; +} + +void ModbusAdapter::resetCounters() +{ + m_packets = 0; + m_errors = 0; + emit(refreshView()); +} + +int ModbusAdapter::packets() +{ + return m_packets; +} + +int ModbusAdapter::errors() +{ + return m_errors; +} + +void ModbusAdapter::startPollTimer() +{ + m_pollTimer->start(m_scanRate); +} + +void ModbusAdapter::stopPollTimer() +{ + m_pollTimer->stop(); +} + +void ModbusAdapter::setTimeOut(int timeOut) +{ + + m_timeOut = timeOut; + +} + +QString ModbusAdapter::stripIP(QString ip) +{ + //Strip zero's from IP + QStringList ipBytes; + QString res = ""; + int i; + + ipBytes = ip.split("."); + if (ipBytes.size() == 4){ + res = QString("").setNum(ipBytes[0].toInt()); + i = 1; + while (i < ipBytes.size()){ + res = res + "." + QString("").setNum(ipBytes[i].toInt()); + i++; + } + return res; + } + else + return ""; + +} + +void ModbusAdapter::setReadOutputsBeforeWrite(bool readOutputsBeforeWrite){ + + m_readOutputsBeforeWrite = readOutputsBeforeWrite; + +} + + +extern "C" { + +void busMonitorRawResponseData(uint8_t * data, int dataLen) +{ + m_instance->busMonitorResponseData(data, dataLen); +} + +void busMonitorRawRequestData(uint8_t * data, int dataLen) +{ + m_instance->busMonitorRequestData(data, dataLen); +} + +} diff --git a/Applications/qModMaster/src/modbusadapter.h b/Applications/qModMaster/src/modbusadapter.h new file mode 100644 index 0000000..c356f20 --- /dev/null +++ b/Applications/qModMaster/src/modbusadapter.h @@ -0,0 +1,74 @@ +#ifndef MODBUSADAPTER_H +#define MODBUSADAPTER_H + +#include +#include "modbus.h" +#include "registersmodel.h" +#include "rawdatamodel.h" +#include +#include "eutils.h" + +class ModbusAdapter : public QObject +{ + Q_OBJECT +public: + explicit ModbusAdapter(QObject *parent = 0); + ~ModbusAdapter(); + void busMonitorRequestData(uint8_t * data,int dataLen); + void busMonitorResponseData(uint8_t * data,int dataLen); + + void modbusConnectRTU(QString port, int baud, QChar parity, int dataBits, int stopBits, int RTS, int timeOut=1); + void modbusConnectTCP(QString ip, int port, int timeOut=1); + void modbusDisConnect(); + RegistersModel *regModel; + RawDataModel *rawModel; + bool isConnected(); + + void setSlave(int slave); + void setFunctionCode(int functionCode); + void setStartAddr(int addr); + void setNumOfRegs(int num); + void setBaseAddr(int baseAddr); + void addItems(); + void setReadOutputsBeforeWrite(bool readOutputsBeforeWrite); + + void setScanRate(int scanRate); + void setTimeOut(int timeOut); + void startPollTimer(); + void stopPollTimer(); + int packets(); + int errors(); + modbus_t * m_modbus; + +private: + void modbusReadData(int slave, int functionCode, int startAddress, int noOfItems); + void modbusWriteData(int slave, int functionCode, int startAddress, int noOfItems); + QString stripIP(QString ip); + bool m_connected; + int m_ModBusMode; + int m_slave; + int m_functionCode; + int m_startAddr; + int m_baseAddr; + + int m_numOfRegs; + int m_scanRate; + QTimer *m_pollTimer; + int m_packets; + int m_errors; + int m_timeOut; + bool m_transactionIsPending; + bool m_readOutputsBeforeWrite; + uint8_t *dest; + uint16_t *dest16; + +signals: + void refreshView(); + +public slots: + void modbusTransaction(); + void resetCounters(); + +}; + +#endif // MODBUSADAPTER_H diff --git a/Applications/qModMaster/src/modbuscommsettings.cpp b/Applications/qModMaster/src/modbuscommsettings.cpp new file mode 100644 index 0000000..2352753 --- /dev/null +++ b/Applications/qModMaster/src/modbuscommsettings.cpp @@ -0,0 +1,445 @@ +#include "modbuscommsettings.h" +#include "QsLog.h" + +ModbusCommSettings::ModbusCommSettings(const QString &fileName, Format format , QObject *parent) + : QSettings(fileName, format, parent) +{ + this->loadSettings(); +} + +void ModbusCommSettings::loadSettings() +{ + + load(this); + +} + +void ModbusCommSettings::saveSettings() +{ + + save(this); + +} + +QString ModbusCommSettings::TCPPort() +{ + return m_TCPPort; +} + +void ModbusCommSettings::setTCPPort(QString tcpPort) +{ + m_TCPPort = tcpPort; +} + +void ModbusCommSettings::setSlaveIP(QString IP) +{ + m_slaveIP = IP; +} + +QString ModbusCommSettings::slaveIP() +{ + return m_slaveIP; +} + +QString ModbusCommSettings::serialDev() +{ + return m_serialDev; +} + +QString ModbusCommSettings::serialPort() +{ + return m_serialPort; +} + +QString ModbusCommSettings::serialPortName() +{ + return m_serialPortName; +} + +void ModbusCommSettings::setSerialPort(QString serialPort, QString serialDev) +{ +int serialPortNo; + + m_serialDev = serialDev; + m_serialPort = serialPort; + serialPortNo = serialPort.toInt(); + #ifdef Q_OS_WIN32 + if (serialPortNo > 9) + m_serialPortName = "\\\\.\\COM" + serialPort; + else + m_serialPortName = "COM" + serialPort; + #else + m_serialPortName = serialDev; + m_serialPortName += QStringLiteral("%1").arg(serialPort.toInt() - 1); + #endif +} + +QString ModbusCommSettings::baud() +{ + return m_baud; +} + +void ModbusCommSettings::setBaud(QString baud) +{ + m_baud = baud; +} + +QString ModbusCommSettings::dataBits() +{ + return m_dataBits; +} + +void ModbusCommSettings::setDataBits(QString dataBits) +{ + m_dataBits = dataBits; +} + +QString ModbusCommSettings::stopBits() +{ + return m_stopBits; +} + +void ModbusCommSettings::setStopBits(QString stopBits) +{ + m_stopBits = stopBits; +} + +QString ModbusCommSettings::parity() +{ + return m_parity; +} + +void ModbusCommSettings::setParity(QString parity) +{ + m_parity = parity; +} + +QString ModbusCommSettings::RTS() +{ + return m_RTS; +} + +void ModbusCommSettings::setRTS(QString RTS) +{ + m_RTS = RTS; +} + +QString ModbusCommSettings::maxNoOfLines() +{ + return m_maxNoOfLines; +} + +void ModbusCommSettings::setMaxNoOfLines(QString maxNoOfLines) +{ + m_maxNoOfLines = maxNoOfLines; +} + +QString ModbusCommSettings::baseAddr() +{ + return m_baseAddr; +} + +void ModbusCommSettings::setBaseAddr(QString baseAddr) +{ + m_baseAddr = baseAddr; +} + +QString ModbusCommSettings::timeOut() +{ + return m_timeOut; +} + +void ModbusCommSettings::setTimeOut(QString timeOut) +{ + m_timeOut = timeOut; +} + +int ModbusCommSettings::endian() +{ + return m_endian; +} + +void ModbusCommSettings::setEndian(int endian) +{ + m_endian = endian; +} + +int ModbusCommSettings::loggingLevel() +{ + return m_loggingLevel; +} + +bool ModbusCommSettings::readOutputsBeforeWrite() +{ + return m_readOutputsBeforeWrite; +} + +int ModbusCommSettings::modbusMode() +{ + return m_modbusMode; +} + +void ModbusCommSettings::setModbusMode(int modbusMode) +{ + m_modbusMode = modbusMode; +} + +int ModbusCommSettings::slaveID() +{ + return m_slaveID; +} + +void ModbusCommSettings::setSlaveID(int slaveID) +{ + m_slaveID = slaveID; +} + +int ModbusCommSettings::scanRate() +{ + return m_scanRate; +} + +void ModbusCommSettings::setScanRate(int scanRate) +{ + m_scanRate = scanRate; +} + +int ModbusCommSettings::functionCode() +{ + return m_functionCode; +} + +void ModbusCommSettings::setFunctionCode(int functionCode) +{ + m_functionCode = functionCode; +} + +int ModbusCommSettings::startAddr() +{ + return m_startAddr; +} + +void ModbusCommSettings::setStartAddr(int startAddr) +{ + m_startAddr = startAddr; +} + +int ModbusCommSettings::noOfRegs() +{ + return m_noOfRegs; +} + +void ModbusCommSettings::setNoOfRegs(int noOfRegs) +{ + m_noOfRegs = noOfRegs; +} + +int ModbusCommSettings::frmt() +{ + return m_frmt; +} + +void ModbusCommSettings::setFrmt(int frmt) +{ + m_frmt = frmt; +} + +int ModbusCommSettings::floatPrecision() +{ + return m_floatPrecision; +} + +void ModbusCommSettings::setfloatPrecision(int precision) +{ + m_floatPrecision = precision; +} + +void ModbusCommSettings::loadSession(QString fName) +{ + + QLOG_INFO()<< "Load session config from file " << fName; + QSettings m_save_session(fName, QSettings::IniFormat, this); + load(&m_save_session); + +} + +void ModbusCommSettings::saveSession(QString fName) +{ + + QLOG_INFO()<< "Save session config to file " << fName; + QSettings m_save_session(fName, QSettings::IniFormat, this); + save(&m_save_session); + +} + +void ModbusCommSettings::load(QSettings *s) +{ + + if (s->value("TCP/TCPPort").isNull()) + m_TCPPort = "502"; + else + m_TCPPort = s->value("TCP/TCPPort").toString(); + + if (s->value("TCP/SlaveIP").isNull()) + m_slaveIP = "127.000.000.001"; + else + m_slaveIP = s->value("TCP/SlaveIP").toString(); + + if (s->value("RTU/SerialDev").isNull()) + #ifdef Q_OS_WIN32 + m_serialDev = "COM"; + #else + m_serialDev = "/dev/ttyS"; + #endif + else + #ifdef Q_OS_WIN32 + m_serialDev = "COM"; + #else + m_serialDev = s->value("RTU/SerialDev").toString(); + #endif + if (s->value("RTU/SerialPort").isNull()) + { + m_serialPort = "1"; + #ifdef Q_OS_WIN32 + m_serialPortName = "COM" + m_serialPort; + #else + m_serialPortName = m_serialDev; + m_serialPortName += QStringLiteral("%1").arg(m_serialPort.toInt() - 1); + #endif + } + else { + m_serialPort = s->value("RTU/SerialPort").toString(); + m_serialPortName = s->value("RTU/SerialPortName").toString(); + } + + if (s->value("RTU/Baud").isNull()) + m_baud = "9600"; + else + m_baud = s->value("RTU/Baud").toString(); + + if (s->value("RTU/DataBits").isNull()) + m_dataBits = "8"; + else + m_dataBits = s->value("RTU/DataBits").toString(); + + if (s->value("RTU/StopBits").isNull()) + m_stopBits = "1"; + else + m_stopBits = s->value("RTU/StopBits").toString(); + + if (s->value("RTU/Parity").isNull()) + m_parity = "None"; + else + m_parity = s->value("RTU/Parity").toString(); + + if (s->value("RTU/RTS").isNull()) + #ifdef Q_OS_WIN32 + m_RTS = "Disable"; + #else + m_RTS = "None"; + #endif + else + m_RTS = s->value("RTU/RTS").toString(); + + if (s->value("Var/MaxNoOfLines").toInt() == 0 || + s->value("Var/MaxNoOfLines").isNull()) + m_maxNoOfLines = "60"; + else + m_maxNoOfLines = s->value("Var/MaxNoOfLines").toString(); + + if (s->value("Var/BaseAddr").isNull()) + m_baseAddr = "1"; + else + m_baseAddr = s->value("Var/BaseAddr").toString(); + + if (s->value("Var/TimeOut").isNull()) + m_timeOut = "0"; + else + m_timeOut = s->value("Var/TimeOut").toString(); + + if (s->value("Var/LoggingLevel").isNull()) + m_loggingLevel = 3; //warning level + else + m_loggingLevel = s->value("Var/LoggingLevel").toInt(); + + if (s->value("Var/ReadOutputsBeforeWrite").isNull()) + m_readOutputsBeforeWrite = true; + else + m_readOutputsBeforeWrite = s->value("Var/ReadOutputsBeforeWrite").toBool(); + + if (s->value("Var/Endian").isNull()) + m_endian = 0; + else + m_endian = s->value("Var/Endian").toInt(); + + if (s->value("Session/ModBusMode").isNull()) + m_modbusMode = 0; //RTU + else + m_modbusMode = s->value("Session/ModBusMode").toInt(); + + if (s->value("Session/SlaveID").isNull()) + m_slaveID = 1; + else + m_slaveID = s->value("Session/SlaveID").toInt(); + + if (s->value("Session/ScanRate").isNull()) + m_scanRate = 1000; + else + m_scanRate = s->value("Session/ScanRate").toInt(); + + if (s->value("Session/FunctionCode").isNull()) + m_functionCode = 0; //FC1 : Read Coils + else + m_functionCode = s->value("Session/FunctionCode").toInt(); + + if (s->value("Session/StartAddr").isNull()) + m_startAddr = 0; + else + m_startAddr = s->value("Session/StartAddr").toInt(); + + if (s->value("Session/NoOfRegs").isNull()) + m_noOfRegs = 0; + else + m_noOfRegs = s->value("Session/NoOfRegs").toInt(); + + if (s->value("Session/Format").isNull()) + m_frmt = 1; //Dec + else + m_frmt = s->value("Session/Format").toInt(); + + if (s->value("Session/FloatPrecision").isNull()) + m_floatPrecision = 3; + else + m_floatPrecision = s->value("Session/FloatPrecision").toInt(); + +} + +void ModbusCommSettings::save(QSettings *s) +{ + + s->setValue("TCP/TCPPort",m_TCPPort); + s->setValue("TCP/SlaveIP",m_slaveIP); + s->setValue("RTU/SerialDev",m_serialDev); + s->setValue("RTU/SerialPort",m_serialPort); + s->setValue("RTU/SerialPortName",m_serialPortName); + s->setValue("RTU/Baud",m_baud); + s->setValue("RTU/DataBits",m_dataBits); + s->setValue("RTU/StopBits",m_stopBits); + s->setValue("RTU/Parity",m_parity); + s->setValue("RTU/RTS",m_RTS); + s->setValue("Var/MaxNoOfLines",m_maxNoOfLines); + s->setValue("Var/BaseAddr",m_baseAddr); + s->setValue("Var/TimeOut",m_timeOut); + s->setValue("Var/LoggingLevel",m_loggingLevel); + s->setValue("Var/ReadOutputsBeforeWrite",m_readOutputsBeforeWrite); + s->setValue("Var/Endian",m_endian); + s->setValue("Session/ModBusMode",m_modbusMode); + s->setValue("Session/SlaveID",m_slaveID); + s->setValue("Session/ScanRate",m_scanRate); + s->setValue("Session/FunctionCode",m_functionCode); + s->setValue("Session/StartAddr",m_startAddr); + s->setValue("Session/NoOfRegs",m_noOfRegs); + s->setValue("Session/Format",m_frmt); + s->setValue("Session/FloatPrecision",m_floatPrecision); + +} diff --git a/Applications/qModMaster/src/modbuscommsettings.h b/Applications/qModMaster/src/modbuscommsettings.h new file mode 100644 index 0000000..a3bb034 --- /dev/null +++ b/Applications/qModMaster/src/modbuscommsettings.h @@ -0,0 +1,107 @@ +#ifndef MODBUSCOMMSETTINGS_H +#define MODBUSCOMMSETTINGS_H + +#include + +class ModbusCommSettings : public QSettings +{ + Q_OBJECT +public: + explicit ModbusCommSettings(QObject *parent = 0) : QSettings(parent) { } + ModbusCommSettings(const QString &fileName, Format format = QSettings::IniFormat, QObject *parent = 0); + //TCP + QString TCPPort(); + void setTCPPort(QString tcpPort); + void setSlaveIP(QString IP); + QString slaveIP(); + //Serial + QString serialDev(); + QString serialPort(); + QString serialPortName(); + void setSerialPort(QString serialPort, QString serialDev = ""); + QString baud(); + void setBaud(QString baud); + QString dataBits(); + void setDataBits(QString dataBits); + QString stopBits(); + void setStopBits(QString stopBits); + QString parity(); + void setParity(QString parity); + QString RTS(); + void setRTS(QString RTS); + //Var + QString maxNoOfLines(); + void setMaxNoOfLines(QString maxNoOfLines); + QString baseAddr(); + void setBaseAddr(QString baseAddr); + QString timeOut(); + void setTimeOut(QString timeOut); + int endian(); + void setEndian(int endian); + void loadSettings(); + void saveSettings(); + //logging + int loggingLevel(); + // + bool readOutputsBeforeWrite(); + //session + int modbusMode(); + void setModbusMode(int modbusMode); + int slaveID(); + void setSlaveID(int slaveID); + int scanRate(); + void setScanRate(int scanRate); + int functionCode(); + void setFunctionCode(int functionCode); + int startAddr(); + void setStartAddr(int startAddr); + int noOfRegs(); + void setNoOfRegs(int noOfRegs); + int frmt(); + void setFrmt(int frmt); + int floatPrecision(); + void setfloatPrecision(int precision); + void loadSession(QString fName); + void saveSession(QString fName); + +private: + //TCP + QString m_TCPPort; + QString m_slaveIP; + //Serial + QString m_serialDev; + QString m_serialPort; + QString m_serialPortName; + QString m_baud; + QString m_dataBits; + QString m_stopBits; + QString m_parity; + QString m_RTS; + //Var + QString m_maxNoOfLines; + QString m_baseAddr; + QString m_timeOut; + int m_endian; + void load(QSettings *s); + void save(QSettings *s); + //Log + int m_loggingLevel; + //read outputs before write + bool m_readOutputsBeforeWrite; + //Session vars + int m_modbusMode; + int m_slaveID; + int m_scanRate; + int m_functionCode; + int m_startAddr; + int m_noOfRegs; + int m_frmt; + int m_floatPrecision; + +signals: + +public slots: + +}; + +#endif // MODBUSCOMMSETTINGS_H diff --git a/Applications/qModMaster/src/rawdatadelegate.cpp b/Applications/qModMaster/src/rawdatadelegate.cpp new file mode 100644 index 0000000..c8c1f75 --- /dev/null +++ b/Applications/qModMaster/src/rawdatadelegate.cpp @@ -0,0 +1,12 @@ +#include "rawdatadelegate.h" +#include +#include + +//TODO : use delegate +void RawDataDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + + QStyledItemDelegate::paint(painter, option, index); + +} diff --git a/Applications/qModMaster/src/rawdatadelegate.h b/Applications/qModMaster/src/rawdatadelegate.h new file mode 100644 index 0000000..73024a2 --- /dev/null +++ b/Applications/qModMaster/src/rawdatadelegate.h @@ -0,0 +1,17 @@ +#ifndef RAWDATADELEGATE_H +#define RAWDATADELEGATE_H + +#include + +class RawDataDelegate : public QStyledItemDelegate + { + Q_OBJECT + + public: + RawDataDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) {} + + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + }; +#endif // RAWDATADELEGATE_H diff --git a/Applications/qModMaster/src/rawdatamodel.cpp b/Applications/qModMaster/src/rawdatamodel.cpp new file mode 100644 index 0000000..1d36eea --- /dev/null +++ b/Applications/qModMaster/src/rawdatamodel.cpp @@ -0,0 +1,44 @@ +#include "rawdatamodel.h" +#include "QsLog.h" +#include + +RawDataModel::RawDataModel(QObject *parent) : + QObject(parent) +{ + model = new QStringListModel(this); + m_addLinesEnabled = false; +} + +void RawDataModel::addLine(QString line) +{ + + if (!m_addLinesEnabled) return; + + QLOG_TRACE() << "Raw Data Model Line = " << line << " , No of lines = " << m_rawData.length(); + + if (m_rawData.length() == m_maxNoOfLines) + m_rawData.removeFirst(); + m_rawData.append(line); + model->setStringList(m_rawData); + +} + +void RawDataModel::clear() +{ + + QLOG_TRACE() << "Raw Data Model cleared" ; + + m_rawData.clear(); + model->setStringList(m_rawData); + +} + +void RawDataModel::setMaxNoOfLines(int noOfLines) +{ + m_maxNoOfLines = noOfLines; +} + +void RawDataModel::enableAddLines(bool en) +{ + m_addLinesEnabled = en; +} diff --git a/Applications/qModMaster/src/rawdatamodel.h b/Applications/qModMaster/src/rawdatamodel.h new file mode 100644 index 0000000..0a62fbb --- /dev/null +++ b/Applications/qModMaster/src/rawdatamodel.h @@ -0,0 +1,31 @@ +#ifndef RAWDATAMODEL_H +#define RAWDATAMODEL_H + +#include +#include + +class RawDataModel : public QObject +{ + Q_OBJECT +public: + explicit RawDataModel(QObject *parent = 0); + + QStringListModel *model; + void addLine(QString line); + void enableAddLines(bool en); + void clear(); + void setMaxNoOfLines(int noOfLines); + int maxNoOfLines() { return m_maxNoOfLines; } + +signals: + +public slots: + +private: + QStringList m_rawData; + int m_maxNoOfLines; + bool m_addLinesEnabled; + +}; + +#endif // RAWDATAMODEL_H diff --git a/Applications/qModMaster/src/registersdatadelegate.cpp b/Applications/qModMaster/src/registersdatadelegate.cpp new file mode 100644 index 0000000..ce8dc48 --- /dev/null +++ b/Applications/qModMaster/src/registersdatadelegate.cpp @@ -0,0 +1,158 @@ +#include "registersdatadelegate.h" +#include "mainwindow.h" +#include "QsLog.h" +#include +#include +#include +#include + +#include "eutils.h" + +void RegistersDataDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + + QStyledItemDelegate::paint(painter, option, index); + +} + +QWidget *RegistersDataDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &/* option */, + const QModelIndex &/* index */) const +{ + + if (m_frmt == EUtils::Bin) {//Bin + if (m_is16Bit) { + QLineEdit *editor = new QLineEdit(parent); + editor->setInputMask("bbbbbbbbbbbbbbbb"); + return editor; + } + else { + QSpinBox *editor = new QSpinBox(parent); + editor->setMinimum(0); + editor->setMaximum(1); + return editor; + } + } + else if (m_frmt == EUtils::Dec) {//Dec + QLineEdit *editor = new QLineEdit(parent); + QRegularExpression rx("-{0,1}[0-9]{1,5}"); + QValidator *validator = new QRegularExpressionValidator(rx); + editor->setValidator(validator); + return editor; + } + else if (m_frmt == EUtils::Float) {//Float + QLineEdit *editor = new QLineEdit(parent); + QRegularExpression rx("^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$"); + QValidator *validator = new QRegularExpressionValidator(rx); + editor->setValidator(validator); + return editor; + } + else if (m_frmt == EUtils::Hex) {//Hex + QLineEdit *editor = new QLineEdit(parent); + editor->setInputMask("hhhh"); + return editor; + } + else {//Default = Dec + QLineEdit *editor = new QLineEdit(parent); + QRegularExpression rx("-{0,1}[0-9]{1,5}"); + QValidator *validator = new QRegularExpressionValidator(rx); + editor->setValidator(validator); + return editor; + } + +} + +void RegistersDataDelegate::setEditorData(QWidget *editor, + const QModelIndex &index) const +{ + + QString value = index.model()->data(index, Qt::EditRole).toString(); + + if (m_frmt == EUtils::Bin && !m_is16Bit) {//Bin + QSpinBox *spinBox = static_cast(editor); + spinBox->setValue(value.toInt()); + } + else { //Bin 16 Bit, Dec, Hex, Float? + QLineEdit *lineEdit = static_cast(editor); + lineEdit->setText(value); + } +} + +void RegistersDataDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const +{ + + QString value; + int intVal; + float floatVal; + bool ok; + + if (m_frmt == EUtils::Bin && !m_is16Bit) {//Bin + QSpinBox *spinBox = static_cast(editor); + intVal = (spinBox->text()).toInt(&ok,m_frmt); + value = EUtils::formatValue(intVal, m_frmt, m_is16Bit, m_isSigned); + } + else if (m_frmt == EUtils::Float){ //Float + QLineEdit *lineEdit = static_cast(editor); + floatVal = (lineEdit->text()).toFloat(&ok); + value = EUtils::formatValue32(floatVal, m_floatPrecision); + } + else { //Bin 16 Bit, Dec, Hex + QLineEdit *lineEdit = static_cast(editor); + intVal = (lineEdit->text()).toInt(&ok,m_frmt); + if (intVal > 65535){ + mainWin->showUpInfoBar(tr("Set value failed\nValue is greater than 65535."), InfoBar::Error); + QLOG_WARN() << "Set value failed. Value is greater than 65535"; + return; + } + else if (intVal < -32768){ + mainWin->showUpInfoBar(tr("Set value failed\nValue is smaller than -32768."), InfoBar::Error); + QLOG_WARN() << "Set value failed. Value is smaller than -32768"; + return; + } + else + { + mainWin->hideInfoBar(); + } + value = EUtils::formatValue(intVal, m_frmt, m_is16Bit, m_isSigned); + } + + QLOG_TRACE() << "Set model data value = " << value; + + model->setData(index, value, Qt::EditRole); +} + +void RegistersDataDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex &/* index */) const +{ + editor->setGeometry(option.rect); +} + +void RegistersDataDelegate::setFrmt(int frmt) +{ + + m_frmt = frmt; + +} + +void RegistersDataDelegate::setIs16Bit(bool is16Bit) +{ + + m_is16Bit = is16Bit; + +} + +void RegistersDataDelegate::setIsSigned(bool isSigned) +{ + + m_isSigned = isSigned; + +} + +void RegistersDataDelegate::setFloatPrecision(int precision) +{ + + m_floatPrecision = precision; + +} diff --git a/Applications/qModMaster/src/registersdatadelegate.h b/Applications/qModMaster/src/registersdatadelegate.h new file mode 100644 index 0000000..696ab3a --- /dev/null +++ b/Applications/qModMaster/src/registersdatadelegate.h @@ -0,0 +1,39 @@ +#ifndef REGISTERSDELEGATE_H +#define REGISTERSDELEGATE_H + +#include + +class RegistersDataDelegate : public QStyledItemDelegate + { + Q_OBJECT + + private: + int m_frmt; + bool m_is16Bit; + bool m_isSigned; + int m_floatPrecision; + + public: + RegistersDataDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) { } + + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const; + + void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex &index) const; + + void setFrmt(int frmt); + void setIs16Bit(bool is16Bit); + void setIsSigned(bool isSigned); + void setFloatPrecision(int precision); + + }; + +#endif // REGISTERSDELEGATE_H diff --git a/Applications/qModMaster/src/registersmodel.cpp b/Applications/qModMaster/src/registersmodel.cpp new file mode 100644 index 0000000..24f881b --- /dev/null +++ b/Applications/qModMaster/src/registersmodel.cpp @@ -0,0 +1,383 @@ +#include "registersmodel.h" +#include "QsLog.h" +#include +#include + +#include "eutils.h" + +RegistersModel::RegistersModel(QObject *parent) : + QObject(parent) +{ + model = new QStandardItemModel(0,0,this); + m_regDataDelegate = new RegistersDataDelegate(0); + m_noOfItems = 0; + m_is16Bit = false; + m_isSigned = false; + m_startAddrBase = 10; + clear(); +} + +void RegistersModel::addItems(int startAddress, int noOfItems, bool valueIsEditable) +{ + int row; + int col; + m_startAddress = startAddress; + m_noOfItems = noOfItems; + + QLOG_TRACE() << "Registers Model Address = " << startAddress << " , noOfItems = " << noOfItems + << " , first row = " << m_firstRow << " , last row = " << m_lastRow; + + //Format Vertical - Horizontal Headers + clear(); + if (noOfItems > 1) { + if (m_frmt == EUtils::Float){ + m_firstRow = 0; + m_lastRow = (noOfItems - 1) / 20; + model->setHorizontalHeaderLabels(QStringList()<setVerticalHeaderLabels(vertHeader); + } + else { + m_firstRow = 0; + m_lastRow = (noOfItems - 1) / 10; + model->setHorizontalHeaderLabels(QStringList()<setVerticalHeaderLabels(vertHeader); + } + } + else { + model->setHorizontalHeaderLabels(QStringList()<setVerticalHeaderLabels(QStringList()<setItem(0, 0, valueItem); + valueItem->setEditable(valueIsEditable); + } + else { + if (m_frmt == EUtils::Float){ + m_firstRow = 0; + m_lastRow = (noOfItems - 1) / 20; + for (int i = m_firstRow; i <= m_lastRow; i++) { + row = i; + for (int j = 0; j < 10; j++) { + col = j; + if ((row * 10 + col) >= (noOfItems / 2)){//not used cells + QStandardItem *valueItem = new QStandardItem("x");model->setItem(row, col, valueItem); + valueItem->setEditable(false); + valueItem->setForeground(QBrush(Qt::red)); + valueItem->setBackground(QBrush(Qt::lightGray)); + } + else { + QStandardItem *valueItem = new QStandardItem("-");model->setItem(row, col, valueItem); + valueItem->setEditable(valueIsEditable); + } + } + } + } + else { + m_firstRow = 0; + m_lastRow = (noOfItems - 1) / 10; + for (int i = m_firstRow; i <= m_lastRow; i++) { + row = i; + for (int j = 0; j < 10; j++) { + col = j; + if ((row * 10 + col) >= (noOfItems)){//not used cells + QStandardItem *valueItem = new QStandardItem("x");model->setItem(row, col, valueItem); + valueItem->setEditable(false); + valueItem->setForeground(QBrush(Qt::red)); + valueItem->setBackground(QBrush(Qt::lightGray)); + } + else { + QStandardItem *valueItem = new QStandardItem("-");model->setItem(row, col, valueItem); + valueItem->setEditable(valueIsEditable); + } + } + } + } + } + + + emit(refreshView()); + +} + +void RegistersModel::setNoValidValues() +{ + + int row; + int col; + //if we have no valid values we set as value = '-/-' + + if (m_frmt == EUtils::Float){ + for (int i = 0; i < (m_noOfItems / 2); i++){ + row = i / 10; + col = i % 10; + QModelIndex index = model->index(row, col, QModelIndex()); + model->setData(index,QBrush(Qt::red),Qt::ForegroundRole); + model->setData(index,"-/-",Qt::DisplayRole); + } + } + else { + for (int i = 0; i < m_noOfItems; i++){ + row = i / 10; + col = i % 10; + QModelIndex index = model->index(row, col, QModelIndex()); + model->setData(index,QBrush(Qt::red),Qt::ForegroundRole); + model->setData(index,"-/-",Qt::DisplayRole); + } + } + +} + +void RegistersModel::setValue(int idx, int value) +{ + int row; + int col; + QString convertedValue; + + convertedValue = EUtils::formatValue(value, m_frmt, m_is16Bit, m_isSigned); + + //set model data + if (m_noOfItems == 1){ + row = 0; + col = 0; + } + else { + row = (idx) / 10; + col = (idx) % 10; + } + + QModelIndex index = model->index(row, col, QModelIndex()); + model->setData(index,QBrush(Qt::black),Qt::ForegroundRole); + model->setData(index,convertedValue,Qt::DisplayRole); + model->setData(index,QString("Address : %1").arg(m_startAddress + idx, 1, m_startAddrBase).toUpper(),Qt::ToolTipRole); +} + +void RegistersModel::setValue32(int idx, int valueHi, int valueLo) +{//update model with float values + int row; + int col; + QString convertedValue; + + convertedValue = EUtils::formatValue32(valueHi, valueLo, m_endian, m_floatPrecision); + + //set model data + if (m_noOfItems == 1){ + row = 0; + col = 0; + } + else { + row = (idx) / 10; + col = (idx) % 10; + } + + QModelIndex index = model->index(row, col, QModelIndex()); + model->setData(index,QBrush(Qt::black),Qt::ForegroundRole); + model->setData(index,convertedValue,Qt::DisplayRole); + model->setData(index,QString("Address : %1").arg(m_startAddress + 2*idx, 1, m_startAddrBase).toUpper(),Qt::ToolTipRole); +} + +int RegistersModel::value(int idx) +{ + QString stringVal; + int intVal; + bool ok; + + //Get Value + stringVal = strValue(idx); + intVal = stringVal.toInt(&ok,m_frmt); + if (ok) + return intVal; + else + return -1; + +} + +float RegistersModel::floatValue(int idx) +{ + QString stringVal; + float floatVal; + bool ok; + + //Get Value + stringVal = strValue(idx); + floatVal = stringVal.toFloat(&ok); + if (ok) + return floatVal; + else + return -1; + +} + +QString RegistersModel::strValue(int idx) +{ + int row; + int col; + + //get model data + if (m_noOfItems == 1){ + row = 0; + col = 0; + } + else { + row = (idx) / 10; + col = (idx) % 10; + } + QModelIndex index = model->index(row, col, QModelIndex()); + QVariant value = model->data(index,Qt::DisplayRole); + if (value.canConvert()) + return value.toString(); + else + return "-/-"; + +} + +void RegistersModel::changeFrmt(int frmt) +{ + + QString stringVal; + int intVal; + int row; + int col; + bool ok; + QString convertedVal; + + QLOG_TRACE()<< "Registers Model changed format from " << m_frmt << " to " << frmt ; + + //change base + for (int idx = 0; idx < m_noOfItems ; idx++) { + //Get Value + stringVal = strValue(idx); + intVal = stringVal.toInt(&ok,m_frmt); + //Format Value + if (ok) + convertedVal = EUtils::formatValue(intVal, frmt, m_is16Bit, m_isSigned); + else + convertedVal = "-/-"; + //Update + if (m_noOfItems == 1){ + row = 0; + col = 0; + } + else { + row = (idx) / 10; + col = (idx) % 10; + } + QModelIndex index = model->index(row, col, QModelIndex()); + model->setData(index,convertedVal,Qt::DisplayRole); + model->setData(index,QString("Address : %1").arg(m_startAddress + idx, m_startAddrBase).toUpper(),Qt::ToolTipRole); + } + + emit(refreshView()); + +} + +void RegistersModel::clear() +{ + + QLOG_TRACE()<< "Registers Model Cleared" ; + + //Clear model + model->clear(); + +} + +void RegistersModel::setStartAddrBase(int base) +{ + + QLOG_TRACE()<< "Registers Model start addr set base = " << base ; + + m_startAddrBase = base; + changeFrmt(m_frmt); + +} + +void RegistersModel::setFrmt(int frmt) +{ + + QLOG_TRACE()<< "Registers Model set base = " << frmt ; + + m_regDataDelegate->setFrmt(frmt); + changeFrmt(frmt); + m_frmt = frmt; + +} + +int RegistersModel::getFrmt() +{ + + QLOG_TRACE()<< "Registers Model get format = " << m_frmt ; + + return m_frmt; + +} + +void RegistersModel::setIs16Bit(bool is16Bit) +{ + + QLOG_TRACE()<< "Registers Model Is16Bit = " << is16Bit ; + m_is16Bit = is16Bit; + m_regDataDelegate->setIs16Bit(is16Bit); + +} + +void RegistersModel::setIsSigned(bool isSigned) +{ + + QLOG_TRACE()<< "Registers Model IsSigned = " << isSigned ; + m_isSigned = isSigned; + m_regDataDelegate->setIsSigned(isSigned); + changeFrmt(m_frmt); + +} + +void RegistersModel::setEndian(int endian) +{ + + QLOG_TRACE()<< "Registers Model endianness = " << endian ; + m_endian = endian; + +} + +int RegistersModel::getEndian() +{ + + QLOG_TRACE()<< "Registers Model endianness = " << m_endian ; + return m_endian; + +} + +void RegistersModel::setFloatPrecision(int precision) +{ + + QLOG_TRACE()<< "Registers Model float precision = " << precision ; + m_floatPrecision = precision; + m_regDataDelegate->setFloatPrecision(precision); + +} + +RegistersDataDelegate* RegistersModel::itemDelegate() +{ + + return m_regDataDelegate; + +} diff --git a/Applications/qModMaster/src/registersmodel.h b/Applications/qModMaster/src/registersmodel.h new file mode 100644 index 0000000..7b87743 --- /dev/null +++ b/Applications/qModMaster/src/registersmodel.h @@ -0,0 +1,59 @@ +#ifndef REGISTERSMODEL_H +#define REGISTERSMODEL_H + +#include +#include +#include "registersdatadelegate.h" + +static const QString RegModelHeaderLabels[]={"+00", "+01", "+02", "+03", "+04", "+05", "+06", "+07", "+08", "+09"}; +static const QString RegModelFloatHeaderLabels[]={"+00", "+02", "+04", "+06", "+08", "+10", "+12", "+14", "+16", "+18"}; +static const int AddressColumn=0; +static const int ValueColumn=1; + +class RegistersModel : public QObject +{ + Q_OBJECT +public: + explicit RegistersModel(QObject *parent = 0); + + void addItems(int startAddress, int noOfItems, bool valueIsEditable); + void setValue(int idx, int value); + void setValue32(int idx, int valueHi, int valueLo); + void setFrmt(int frmt); + int getFrmt(); + void setStartAddrBase(int base); + void setIs16Bit(bool is16Bit); + void setIsSigned(bool isSigned); + void setEndian(int endian); + int getEndian(); + void setFloatPrecision(int precision); + QString strValue(int idx); + int value(int idx); + float floatValue(int idx); + QStandardItemModel *model; + void clear(); + void setNoValidValues(); + RegistersDataDelegate* itemDelegate(); + +private: + void changeFrmt(int frmt); + int m_startAddress; + int m_noOfItems; + int m_firstRow; + int m_lastRow; + bool m_is16Bit; + bool m_isSigned; + int m_startAddrBase; + int m_frmt; + int m_endian; + int m_floatPrecision; + RegistersDataDelegate *m_regDataDelegate; + +signals: + void refreshView(); + +public slots: + +}; + +#endif // REGISTERSMODEL_H diff --git a/Applications/qModMaster/translations/qModMaster_zh_CN.qm b/Applications/qModMaster/translations/qModMaster_zh_CN.qm new file mode 100644 index 0000000..ce36a69 Binary files /dev/null and b/Applications/qModMaster/translations/qModMaster_zh_CN.qm differ diff --git a/Applications/qModMaster/translations/qModMaster_zh_CN.ts b/Applications/qModMaster/translations/qModMaster_zh_CN.ts new file mode 100644 index 0000000..7b4aac7 --- /dev/null +++ b/Applications/qModMaster/translations/qModMaster_zh_CN.ts @@ -0,0 +1,358 @@ + + + + + About + + + About + 关于 + + + + BusMonitor + + + Bus Monitor + 总线监视器 + + + + Raw Data + 原始数据 + + + + + Clear + 清空 + + + + + Exit + 退出 + + + + + Save + 保存 + + + + Start-Stop Monitor + 开始-停止监视器 + + + + MainWindow + + + Modbus Master + Modbus 主机 + + + + Modbus Mode + Modbus 模式 + + + + Slave ID + 从机 ID + + + + Scan Rate (ms) + 扫描速率(毫秒) + + + + Function Code + 功能码 + + + + Format + 格式 + + + + Binary + 二进制 + + + + Decimal + 十进制 + + + + Hex + 十六进制 + + + + Start Address + 起始地址 + + + + + Number of Coils + 线圈数量 + + + + Add Items + 增加项目 + + + + Clear Items + 清除项目 + + + + File + 文件 + + + + Options + 选项 + + + + Help + 帮助 + + + + View + 视图 + + + + Commands + 命令 + + + + Exit + 退出 + + + + About... + 关于... + + + + Bus Monitor + 总线监视器 + + + + Settings... + 设置... + + + + Read / Write + 读/写 + + + + Connect + 连接 + + + + Scan + 扫描 + + + + Reset Counters + 重置计数器 + + + + + Packets : + 包: + + + + + Errors : + 错误: + + + + Number of Registers + 寄存器数量 + + + + + Request failed +Add items to Registers Table. + 请求失败\n向寄存器表加入项目。 + + + + ModbusAdapter + + + Connection failed +Could not connect to serial port. + 连接失败 +无法连接串口。 + + + + Connection failed +Wrong IP Address. + 连接失败 +IP地址错误。 + + + + Connection failed +Could not connect to TCP port. + 连接失败 +无法连接TCP口。 + + + + Read data failed. +Slave threw exception > + 读数据失败 +从机抛出异常 > + + + + Read data failed. +Number of registers returned does not match number of registers requested!. [ + 读数据失败 +返回的寄存器数量与请求的不匹配! [ + + + + Write data failed. +Slave threw exception > + 写数据失败 +从机抛出异常 > + + + + Write data failed. +Number of registers returned does not match number of registers requested!. [ + 写数据失败 +返回的寄存器数量与请求的不匹配! [ + + + + RegistersDataDelegate + + + Set value failed +Value is greater than 65535. + 设置值失败 +该值大于65535。 + + + + Set value failed +Value is smaller than -32768. + 设置值失败\n该值小于-32768。 + + + + Settings + + + Settings + 设置 + + + + Max No Of Bus Monitor Lines + 监视器行数上限 + + + + Response Timeout (sec) + 响应超时(秒) + + + + SettingsModbusRTU + + + Modbus RTU Settings + Modbus RTU设置 + + + + Serial port + 串口 + + + + Baud + 波特 + + + + Data Bits + 数据位数 + + + + Stop Bits + 停止位数 + + + + Parity + 奇偶 + + + + None + + + + + Odd + + + + + Even + + + + + SettingsModbusTCP + + + Modbus TCP Settings + Modbus TCP设置 + + + + Slave IP + 从机IP + + + + TCP Port + TCP口 + + + diff --git a/Applications/qModMaster/translations/qModMaster_zh_TW.qm b/Applications/qModMaster/translations/qModMaster_zh_TW.qm new file mode 100644 index 0000000..a642c81 Binary files /dev/null and b/Applications/qModMaster/translations/qModMaster_zh_TW.qm differ diff --git a/Applications/qModMaster/translations/qModMaster_zh_TW.ts b/Applications/qModMaster/translations/qModMaster_zh_TW.ts new file mode 100644 index 0000000..626fc8f --- /dev/null +++ b/Applications/qModMaster/translations/qModMaster_zh_TW.ts @@ -0,0 +1,358 @@ + + + + + About + + + About + 關於 + + + + BusMonitor + + + Bus Monitor + 總線監視器 + + + + Raw Data + 原始數據 + + + + + Clear + 清空 + + + + + Exit + 退出 + + + + + Save + 保存 + + + + Start-Stop Monitor + 開始-停止監視器 + + + + MainWindow + + + Modbus Master + Modbus 主機 + + + + Modbus Mode + Modbus 模式 + + + + Slave ID + 從機 ID + + + + Scan Rate (ms) + 掃描速率(毫秒) + + + + Function Code + 功能碼 + + + + Format + 格式 + + + + Binary + 二進制 + + + + Decimal + 十進制 + + + + Hex + 十六進制 + + + + Start Address + 起始地址 + + + + + Number of Coils + 線圈數量 + + + + Add Items + 增加項目 + + + + Clear Items + 清除項目 + + + + File + 文檔 + + + + Options + 選項 + + + + Help + 幫助 + + + + View + 視圖 + + + + Commands + 命令 + + + + Exit + 退出 + + + + About... + 關於... + + + + Bus Monitor + 總線監視器 + + + + Settings... + 設置... + + + + Read / Write + 讀/寫 + + + + Connect + 連接 + + + + Scan + 掃描 + + + + Reset Counters + 重置計數器 + + + + + Packets : + 包: + + + + + Errors : + 錯誤: + + + + Number of Registers + 寄存器數量 + + + + + Request failed +Add items to Registers Table. + 請求失敗\n嚮寄存器表加入項目。 + + + + ModbusAdapter + + + Connection failed +Could not connect to serial port. + 連接失敗 +無法連接串口。 + + + + Connection failed +Wrong IP Address. + 連接失敗 +IP地址錯誤。 + + + + Connection failed +Could not connect to TCP port. + 連接失敗 +無法連接TCP口。 + + + + Read data failed. +Slave threw exception > + 讀數據失敗 +從機拋出異常 > + + + + Read data failed. +Number of registers returned does not match number of registers requested!. [ + 讀數據失敗 +返回的寄存器數量與請求的不匹配! [ + + + + Write data failed. +Slave threw exception > + 寫數據失敗 +從機拋出異常 > + + + + Write data failed. +Number of registers returned does not match number of registers requested!. [ + 寫數據失敗 +返回的寄存器數量與請求的不匹配! [ + + + + RegistersDataDelegate + + + Set value failed +Value is greater than 65535. + 設置值失敗 +該值大於65535。 + + + + Set value failed +Value is smaller than -32768. + 設置值失敗\n該值小於-32768。 + + + + Settings + + + Settings + 設置 + + + + Max No Of Bus Monitor Lines + 監視器行數上限 + + + + Response Timeout (sec) + 響應超時(秒) + + + + SettingsModbusRTU + + + Modbus RTU Settings + Modbus RTU設置 + + + + Serial port + 串口 + + + + Baud + 波特 + + + + Data Bits + 數據位數 + + + + Stop Bits + 停止位數 + + + + Parity + 奇偶 + + + + None + + + + + Odd + + + + + Even + + + + + SettingsModbusTCP + + + Modbus TCP Settings + Modbus TCP設置 + + + + Slave IP + 從機IP + + + + TCP Port + TCP口 + + + diff --git a/Applications/qModMaster/translations/translations.qrc b/Applications/qModMaster/translations/translations.qrc new file mode 100644 index 0000000..b311545 --- /dev/null +++ b/Applications/qModMaster/translations/translations.qrc @@ -0,0 +1,6 @@ + + + qModMaster_zh_CN.qm + qModMaster_zh_TW.qm + + diff --git a/docs/img/QmodMaster.jpg b/docs/img/QmodMaster.jpg new file mode 100644 index 0000000..1e3eab4 Binary files /dev/null and b/docs/img/QmodMaster.jpg differ diff --git a/docs/modbus_tcp.md b/docs/modbus_tcp.md new file mode 100644 index 0000000..5fd2507 --- /dev/null +++ b/docs/modbus_tcp.md @@ -0,0 +1,33 @@ +# Modbus-TCP +This is the documentation of the Modbus-TCP task + +## Table of contents +- [Table of contents](#table-of-contents) +- [Initialization](#initialization) +- [Usage](#usage) +- [Guide](#Guide) +- [Extra](#Extra) + +## Initialization +The Modbus task is initialized in the main function. +```c +// Initialize Modbus task +modbus_init(); +``` +## Usage + +![QmodMaster.exe](./img/QmodMaster.jpg) + +## Guide + +- Set qmodmaster to tcp mode +- Set IP of device +- Set the device in multiple register mode +- Connect with the board +- Fill in the data as in the example +- Send the data + +## Extra + +The start addres is ignored and the timeout warnings arne normal because there is no response from my code back to QModMaster. + diff --git a/docs/udp_broadcast.md b/docs/udp_broadcast.md index 8e8cf46..56846c9 100644 --- a/docs/udp_broadcast.md +++ b/docs/udp_broadcast.md @@ -9,6 +9,8 @@ there are currently 2 types of datagrams it processes: It also writes the current owner's name on the screen and updates it everytime it's changed. +To use the Qt application for this part of the project you can use [App](https://github.com/Sani7/2023-Webservices_And_Applications/releases/tag/V1.1) + ## Table of contents - [Introduction](#introduction) - [Table of contents](#table-of-contents) @@ -68,7 +70,7 @@ connected: ``` ### Owner details interface -The interface to ask for the owner's details and change them is a modified version of the [Qt application](https://github.com/wimdams/Device_finder) Wim Dams build. His only has the functionality to ask for the owner's details. +The interface to ask for the owner's details and change them is a modified version of the [Qt application](https://github.com/wimdams/Device_finder) Wim Dams build. His only has the functionality to ask for the owner's details. The changed version can be found at [App](https://github.com/Sani7/2023-Webservices_And_Applications/releases/tag/V1.1) Just because the owner's details might want to be used in other code, some functions have been written for obtaining these in the STM32 code aswell. #### Setting owner details diff --git a/project/CMakeLists.txt b/project/CMakeLists.txt index 06d823d..01f8a61 100644 --- a/project/CMakeLists.txt +++ b/project/CMakeLists.txt @@ -1,7 +1,7 @@ #THIS FILE IS AUTO GENERATED FROM THE TEMPLATE! DO NOT CHANGE! set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_VERSION 1) -cmake_minimum_required(VERSION 3.26) +cmake_minimum_required(VERSION 3.27) # specify cross-compilers and tools set(CMAKE_C_COMPILER arm-none-eabi-gcc) diff --git a/project/Core/Inc/llfs.h b/project/Core/Inc/llfs.h index 4707873..5b30d0d 100644 --- a/project/Core/Inc/llfs.h +++ b/project/Core/Inc/llfs.h @@ -86,8 +86,8 @@ size_t llfs_file_count(void); /** * @brief This function returns the extension of a file * @param[in] filename The filename to get the extension from - * @return char* The extension of the file + * @return const char* The extension of the file */ -char* llfs_get_filename_ext(char* filename); +const char* llfs_get_filename_ext(const char* filename); #endif // LLFS_H diff --git a/project/Core/Src/llfs.c b/project/Core/Src/llfs.c index 155c9bb..352813a 100644 --- a/project/Core/Src/llfs.c +++ b/project/Core/Src/llfs.c @@ -19,7 +19,7 @@ #define POSIX_MAX_FILES 10 extern struct llfs_data_file* llfs_root; -const char* TAG = "llfs"; +static const char* TAG = "llfs"; static size_t file_count = 0; // Cache for the number of files in the filesystem static FILE* file_table[POSIX_MAX_FILES]; @@ -141,7 +141,7 @@ size_t llfs_file_count(void) { return file_count; } -char* llfs_get_filename_ext(char* filename) { +const char* llfs_get_filename_ext(const char* filename) { char* dot = strrchr(filename, '.'); if (dot == NULL || dot == filename) return NULL; diff --git a/project/Core/Src/modbus_tcp.c b/project/Core/Src/modbus_tcp.c index d3abf82..fd06279 100644 --- a/project/Core/Src/modbus_tcp.c +++ b/project/Core/Src/modbus_tcp.c @@ -49,7 +49,8 @@ static err_t modbus_incoming_data(void* arg, struct tcp_pcb* pcb, struct pbuf* p char text[TEXT_LENGTH]; uint32_t result_background = 0xff000000; uint32_t text_foreground_color = 0xff000000; - LWIP_UNUSED_ARG(arg); + + LWIP_UNUSED_ARG(arg); // This is used to prevent a warning // Putting underscores in the whole array memset(text, '_', TEXT_LENGTH); @@ -148,7 +149,7 @@ static err_t modbus_accept(void* arg, struct tcp_pcb* pcb, err_t err) { * @brief Initializes the modbus tcp */ void modbus_init(void) { - LOG_INFO(TAG, "Initializing\n"); + LOG_INFO(TAG, "Initializing"); // Creating a new tcp pcb modbus_pcb = tcp_new(); @@ -158,5 +159,5 @@ void modbus_init(void) { modbus_pcb = tcp_listen(modbus_pcb); // Set callback function for incoming connections tcp_accept(modbus_pcb, modbus_accept); - LOG_INFO(TAG, "initialized\n"); + LOG_INFO(TAG, "initialized"); } diff --git a/project/Core/Src/tftp.c b/project/Core/Src/tftp.c index c443c55..c3b8aa4 100644 --- a/project/Core/Src/tftp.c +++ b/project/Core/Src/tftp.c @@ -24,27 +24,11 @@ static tftp_custom_file_t virt_file[] = {{.name = "index.txt", .data = NULL, .le {.name = "virtImage.bmp", .data = NULL, .len = 0, .offset = 0}, {.name = "virtImage.gif", .data = NULL, .len = 0, .offset = 0}, {.name = "virtText.txt", .data = NULL, .len = 0, .offset = 0}}; - -static int str_cat_str(char* dest, size_t dest_size, const char* src) { - size_t dest_len = strlen(dest); - size_t src_len = strlen(src); - if (dest_len + src_len > dest_size) { - return -1; - } - memcpy(dest + dest_len, src, src_len); - return 0; -} - -static int str_cat(char* dest, size_t dest_size, char c) { - size_t dest_len = strlen(dest); - if (dest_len + 1 > dest_size) { - return -1; - } - dest[dest_len] = c; - dest[dest_len + 1] = '\0'; - return 0; -} - +static void* tftp_open(const char* fname, const char* mode, uint8_t write); +static void tftp_close(void* handle); +static int tftp_read(void* handle, void* buf, int bytes); +static int tftp_write(void* handle, struct pbuf* p); +static struct tftp_context tftpContext_s = {.open = tftp_open, .close = tftp_close, .read = tftp_read, .write = tftp_write}; /** * @brief tftp custom file functions to set the offset and read the data * @param[in,out] handle Custom file handles @@ -72,6 +56,7 @@ void tftp_custom_fseek(tftp_custom_file_t* handle, size_t offset, int whence) { * auto rolling over the offset * if the bytes to read is bigger than the remaining bytes * it will read the remaining bytes and set the bytes to 0 + * this function does not handle null termination * @param[out] buf The buffer to write the data to * @param[in] bytes The number of bytes to read * @param[in,out] handle Custom file handles @@ -82,7 +67,6 @@ size_t tftp_custom_fread(void* buf, size_t bytes, tftp_custom_file_t* handle) { } memcpy(buf, handle->data + handle->offset, bytes); handle->offset += bytes; - ((char*)buf)[bytes] = '\0'; if (handle->offset > handle->len) { bytes = 0; } @@ -91,7 +75,12 @@ size_t tftp_custom_fread(void* buf, size_t bytes, tftp_custom_file_t* handle) { /** * @brief tftp custom file functions to write the data - * auto rolling over the offset + * auto rolling over the offset + * if the bytes to write is bigger than the remaining bytes + * it will write the remaining bytes and set the bytes to 0 + * this function does not handle null termination + * if you want to write a null termination you have to add it to the data + * and set the len to the new length before calling this function * * @param buf The buffer to write the data to * @param bytes The number of bytes to write @@ -104,7 +93,7 @@ size_t tftp_custom_fwrite(const void* buf, size_t bytes, tftp_custom_file_t* han } memcpy(handle->data + handle->offset, buf, bytes); handle->offset += bytes; - if (handle->offset > handle->len) { + if (handle->offset >= handle->len) { bytes = 0; } return bytes; @@ -124,7 +113,7 @@ size_t tftp_custom_fwrite(const void* buf, size_t bytes, tftp_custom_file_t* han * @param write Flag indicating read (0) or write (!= 0) access * @return void* File handle supplied to other functions */ -void* tftp_open(const char* fname, const char* mode, uint8_t write) { +static void* tftp_open(const char* fname, const char* mode, uint8_t write) { LOG_INFO(TAG, "Opening %s", fname); UNUSED(mode); @@ -148,7 +137,7 @@ void* tftp_open(const char* fname, const char* mode, uint8_t write) { * * @param handle The handle to the file to close */ -void tftp_close(void* handle) { +static void tftp_close(void* handle) { LOG_INFO(TAG, "closing file"); if (handle == NULL) { LOG_CRIT(TAG, "handle is null"); @@ -170,7 +159,7 @@ void tftp_close(void* handle) { if (handle == &virt_file[VIRT_TEXT_TXT]) { lcd_clear_images(); lcd_clear_text(); - lcd_display_text((const char*)virt_file[VIRT_TEXT_TXT].data, 0, 0, LCD_COLOR_WHITE, LCD_TRANSPARENT, LCD_FONT16); + lcd_display_text((char*)virt_file[VIRT_TEXT_TXT].data, 0, 0, LCD_COLOR_WHITE, LCD_TRANSPARENT, LCD_FONT16); } if (handle == &virt_file[VIRT_INDEX_TXT] || handle == &virt_file[VIRT_IMAGE_BMP] @@ -192,7 +181,7 @@ void tftp_close(void* handle) { * @param bytes Number of bytes to copy to buf * @return int >= 0: Success; < 0: Error */ -int tftp_read(void* handle, void* buf, int bytes) { +static int tftp_read(void* handle, void* buf, int bytes) { int ret = 0; LOG_INFO(TAG, "reading file"); if (handle == NULL) { @@ -230,7 +219,7 @@ int tftp_read(void* handle, void* buf, int bytes) { * In other words, TFTP headers are stripped off. * @return int >= 0: Success; < 0: Error */ -int tftp_write(void* handle, struct pbuf* p) { +static int tftp_write(void* handle, struct pbuf* p) { LOG_INFO(TAG, "Writing file"); tftp_custom_file_t* file = (tftp_custom_file_t*)handle; if (file == &virt_file[VIRT_IMAGE_BMP] || file == &virt_file[VIRT_IMAGE_GIF] || file == &virt_file[VIRT_TEXT_TXT]) { @@ -262,20 +251,22 @@ void init_index(void) { virt_file[VIRT_INDEX_TXT].len = len; for (int i = 0; i < MAX_VIRT_FILES; i++) { - str_cat_str(virt_file[VIRT_INDEX_TXT].data, len, virt_file[i].name); - str_cat(virt_file[VIRT_INDEX_TXT].data, len, '\n'); + strncat(virt_file[VIRT_INDEX_TXT].data, virt_file[i].name, len); + strncat(virt_file[VIRT_INDEX_TXT].data, "\n", len); } mem = NULL; file = NULL; while ((file = llfs_next_file(&mem, NULL)) != NULL) { - str_cat_str(virt_file[VIRT_INDEX_TXT].data, len, file->name); - str_cat(virt_file[VIRT_INDEX_TXT].data, len, '\n'); + strncat(virt_file[VIRT_INDEX_TXT].data, file->name, len); + strncat(virt_file[VIRT_INDEX_TXT].data, "\n", len); } -} -struct tftp_context tftpContext_s = {.open = tftp_open, .close = tftp_close, .read = tftp_read, .write = tftp_write}; + LOG_DEBUG(TAG, "index.txt:\n%s", virt_file[VIRT_INDEX_TXT].data); + LOG_DEBUG(TAG, "index.txt: %u strlen: %u", virt_file[VIRT_INDEX_TXT].len, strlen(virt_file[VIRT_INDEX_TXT].data)); + LOG_INFO(TAG, "index.txt created"); +} /** * @brief Initialize tftp server @@ -284,8 +275,6 @@ void tftp_server_init(void) { LOG_INFO(TAG, "Initializing tftp server"); init_index(); - LOG_DEBUG(TAG, "index.txt: %s", virt_file[VIRT_INDEX_TXT].data); - // init the virtImage virt_file with 80kb of ram virt_file[VIRT_IMAGE_BMP].data = calloc(IMAGE_BUFFER_SIZE, sizeof(char)); if (virt_file[VIRT_IMAGE_BMP].data == NULL) { diff --git a/tests/mocs.c b/tests/mocs.c index bceea4e..19db235 100644 --- a/tests/mocs.c +++ b/tests/mocs.c @@ -12,7 +12,7 @@ int tftp_init(struct tftp_context* context) { return 0; } -void lcd_display_text(uint8_t* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT* font) { +void lcd_display_text(const char* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT* font) { UNUSED(color); UNUSED(bg_color); UNUSED(font); diff --git a/tests/mocs.h b/tests/mocs.h index d978179..b67fab7 100644 --- a/tests/mocs.h +++ b/tests/mocs.h @@ -93,7 +93,7 @@ typedef err_t (*tcp_accept_fn)(void *arg, struct tcp_pcb *newpcb, err_t err); uint32_t logger_get_timestamp(void); -void lcd_display_text(uint8_t* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT *font); +void lcd_display_text(const char* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT *font); void lcd_draw_img_from_fs(char* filename, uint32_t x_pos, uint32_t y_pos); void lcd_draw_gif_from_fs(char* filename, uint32_t x_pos, uint32_t y_pos); void lcd_draw_bmp_img(uint8_t* bmp_buff, uint32_t x_pos, uint32_t y_pos); diff --git a/tests/tfpt_tests.cpp b/tests/tfpt_tests.cpp index 1fa2d95..f58a197 100644 --- a/tests/tfpt_tests.cpp +++ b/tests/tfpt_tests.cpp @@ -33,15 +33,15 @@ TEST(TFTP, custom_fread) char buf[11]; tftp_custom_fseek(&file, 0, SEEK_SET); size_t bytes = tftp_custom_fread(buf, 11, &file); - EXPECT_EQ(bytes, 11); - EXPECT_EQ(file.offset, 11); + EXPECT_EQ(bytes, 10); + EXPECT_EQ(file.offset, 10); EXPECT_EQ(memcmp(buf, "1234567890", 10), 0); memset(buf, 0, 11); tftp_custom_fseek(&file, 0, SEEK_SET); bytes = tftp_custom_fread(buf, 11, &file); - EXPECT_EQ(bytes, 11); + EXPECT_EQ(bytes, 10); EXPECT_EQ(memcmp(buf, "1234567890", 10), 0); memset(buf, 0, 11); @@ -59,19 +59,46 @@ TEST(TFTP, custom_fread) } TEST(TFTP, custom_fwrite) { - write_file.data = (char*)malloc(21 * sizeof(char)); - write_file.len = 21; + write_file.data = (char*)malloc(22 * sizeof(char)); + write_file.len = 22; tftp_custom_fwrite("0987654321", 10, &write_file); EXPECT_EQ(write_file.offset, 10); - EXPECT_EQ(write_file.len, 21); + EXPECT_EQ(write_file.len, 22); EXPECT_EQ(memcmp(write_file.data, "0987654321", 10), 0); tftp_custom_fwrite("1234567890", 10, &write_file); EXPECT_EQ(write_file.offset, 20); - EXPECT_EQ(write_file.len, 21); - EXPECT_EQ(memcmp(write_file.data, "09876543211234567890", 20), 0); + EXPECT_EQ(write_file.len, 22); + EXPECT_EQ(memcmp(write_file.data, "09876543211234567890", 21), 0); free(write_file.data); write_file.data = NULL; write_file.len = 0; +} + +TEST(TFTP, custom_fread_fwrite_suggested_by_Lorenz) { + char buf[21]; + write_file.data = (char*)malloc(21 * sizeof(char)); + write_file.len = 21; + write_file.offset = 0; + tftp_custom_fwrite("1234567890123456789", 19, &write_file); + + tftp_custom_fwrite("0987654321", 11, &write_file); + // the above function does not write the null terminator + EXPECT_EQ(write_file.offset, 21); + EXPECT_EQ(write_file.len, 21); + EXPECT_EQ(memcmp(write_file.data, "12345678901234567890", 20), 0); + + tftp_custom_fseek(&write_file, 0, SEEK_SET); + tftp_custom_fread(buf, 20, &write_file); + // the above function does not write the null terminator + buf[20] = '\0'; + EXPECT_EQ(memcmp(buf, "12345678901234567890", 21), 0); + free(write_file.data); + write_file.data = NULL; + write_file.len = 0; + + file.offset = 9; + tftp_custom_fread(buf, 20, &file); + EXPECT_EQ(memcmp(buf, "0", 2), 0); } \ No newline at end of file