├── QModbusTool.ico ├── resources.qrc ├── QModbusTool_en_US.ts ├── PLUGIN.md ├── metadata_structs.cpp ├── write_event.h ├── QModbusTool.pro ├── inputs_display.cpp ├── main.cpp ├── inputs_display.h ├── exceptions.cpp ├── exceptions.h ├── coils_display.h ├── configure_trend_line.h ├── configure_trend.h ├── metadata_structs.h ├── coils_display.cpp ├── holding_register_display.h ├── metadata_wrapper.h ├── csv_importer.h ├── base_dialog.h ├── base_dialog.cpp ├── configure_trend.ui ├── trend_line.cpp ├── trend_line.h ├── README.md ├── metadata.h ├── modbusthread.h ├── configure_trend.cpp ├── configure_trend_line.cpp ├── trend_window.h ├── mainwindow.ui ├── metadata_wrapper.cpp ├── scheduler.h ├── holding_register_display.cpp ├── register_display.h ├── mainwindow.h ├── modbusthread.cpp ├── configure_trend_line.ui ├── scheduler.cpp ├── csv_importer.cpp └── trend_window.cpp /QModbusTool.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KB3HNS/QModbusTool/HEAD/QModbusTool.ico -------------------------------------------------------------------------------- /resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | QModbusTool.ico 4 | 5 | 6 | -------------------------------------------------------------------------------- /QModbusTool_en_US.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /PLUGIN.md: -------------------------------------------------------------------------------- 1 | ## QModbusTool - A QT Based Modbus Client 2 | The application has the ability to query the connected device for certain metadata items that pertain to a register including: 3 | 4 | * Register minimum and maximum values 5 | * Register default value 6 | * Register encoding (signed, unsigned, bits, bytes, etc) 7 | * A brief text string describing the register 8 | 9 | Some manufactures provide the ability to read some, or all of this data through the use of non-standard or application specific Modbus function codes. Modbus its self (unlike other industrial protocols) does not provide a standard mechanism for accessing this type of data. Because in many cases this functionality is greatly helpful some manufacturers have implemented their own proprietary methods for accessing this type of information. 10 | QModbusTool was written to allow manufacturer-defined meta data querying implementations. 11 | 12 | To make the interface as versatile as possible the interface has been broken up into 5 sections: 13 | 14 | 1. Create request instance for a register. 15 | 2. Encode the outgoing request. 16 | 3. Decode the incoming response. 17 | 4. Extract various sections returning optional responses if specific element is unsupported. 18 | 5. Dispose of the previously created instance. 19 | 20 | ### Getting started 21 | Review the available documentation in the header "[metadata.h][1]" for interface specification. The application expects the file called "mod\_plugin.so" to be located in the same directory as the application its self. This behavior can be modified in `metadata_wrapper.cpp`. 22 | The plugin is a singleton wrapper that is initialized during start-up shortly before rendering the main window. The poll scheduling logic has a special priority dedicated to just retrieving register metadata. 23 | 24 | [1]: metadata.h 25 | -------------------------------------------------------------------------------- /metadata_structs.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file metadata_structs.cpp 3 | * \brief Containers and definitions used by the program for metadata 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | */ 25 | 26 | // c++ includes 27 | /* -none- */ 28 | 29 | // C includes 30 | /* -none- */ 31 | 32 | // project includes 33 | #include "metadata_structs.h" // local include 34 | #include "metadata_wrapper.h" // MetadataWrapper 35 | #include // modbus_plugin: DATA_BUFFER_REQUIRED_SIZE 36 | 37 | 38 | Metadata::Metadata(const quint16 reg_num, void *const instance, const quint8 fc) : 39 | register_number{reg_num}, 40 | label{}, 41 | encoding{RegisterEncoding::ENCODING_UNKNOWN}, 42 | min(false), 43 | max(false), 44 | dflt(false), 45 | function_code{qint8(fc)}, 46 | m_request_instance{instance}, 47 | m_request(DATA_BUFFER_REQUIRED_SIZE) 48 | { 49 | } 50 | 51 | 52 | Metadata::~Metadata() 53 | { 54 | auto inst = MetadataWrapper::get_instance(); 55 | inst->dispose_metadata(this); 56 | } 57 | -------------------------------------------------------------------------------- /write_event.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file write_event.h 3 | * \brief Write requests shared object. 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | */ 25 | 26 | #ifndef WRITE_EVENT_H 27 | #define WRITE_EVENT_H 28 | 29 | // c++ includes 30 | #include // std::vector 31 | #include // quint8, quint16 32 | 33 | // C includes 34 | /* -none- */ 35 | 36 | // project includes 37 | /* -none- */ 38 | 39 | 40 | // ///////////////////////////////////////////////////////////////////////////// 41 | // Forward declarations 42 | // ///////////////////////////////////////////////////////////////////////////// 43 | class BaseDialog; 44 | 45 | 46 | /** 47 | * \brief Structure outlining a write request 48 | */ 49 | struct WriteRequest { 50 | BaseDialog *requester; /**< Request source */ 51 | 52 | quint8 node; /**< Send request to node */ 53 | 54 | quint16 first_register; /**< Starting register number (eg: 42, 40023) */ 55 | 56 | std::vector values; /**< List of values to be written */ 57 | }; 58 | 59 | 60 | #endif // WRITE_EVENT_H 61 | -------------------------------------------------------------------------------- /QModbusTool.pro: -------------------------------------------------------------------------------- 1 | QT += core gui xml 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport 4 | 5 | CONFIG += c++17 6 | QMAKE_CXXFLAGS += -std=c++17 -Wextra -Wpedantic 7 | 8 | # You can make your code fail to compile if it uses deprecated APIs. 9 | # In order to do so, uncomment the following line. 10 | DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 11 | DEFINES += QCUSTOMPLOT_USE_OPENGL # enable OpenGL accelleration for trend 12 | 13 | SOURCES += \ 14 | coils_display.cpp \ 15 | exceptions.cpp \ 16 | holding_register_display.cpp \ 17 | main.cpp \ 18 | mainwindow.cpp \ 19 | modbusthread.cpp \ 20 | register_display.cpp \ 21 | scheduler.cpp \ 22 | metadata_wrapper.cpp \ 23 | metadata_structs.cpp \ 24 | inputs_display.cpp \ 25 | csv_importer.cpp \ 26 | trend_window.cpp \ 27 | base_dialog.cpp \ 28 | trend_line.cpp \ 29 | configure_trend_line.cpp \ 30 | configure_trend.cpp 31 | 32 | HEADERS += \ 33 | coils_display.h \ 34 | exceptions.h \ 35 | holding_register_display.h \ 36 | mainwindow.h \ 37 | modbusthread.h \ 38 | register_display.h \ 39 | scheduler.h \ 40 | write_event.h \ 41 | metadata_wrapper.h \ 42 | metadata_structs.h \ 43 | inputs_display.h \ 44 | csv_importer.h \ 45 | trend_window.h \ 46 | base_dialog.h \ 47 | trend_line.h \ 48 | configure_trend_line.h \ 49 | configure_trend.h 50 | 51 | FORMS += \ 52 | mainwindow.ui \ 53 | configure_trend_line.ui \ 54 | configure_trend.ui 55 | 56 | TRANSLATIONS += \ 57 | QModbusTool_en_US.ts 58 | 59 | LIBS += \ 60 | -L/usr/local/lib -lmodbus -ldl -lqtcsv -lqcustomplot 61 | 62 | INCLUDEPATH += \ 63 | /usr/local/include 64 | 65 | # Default rules for deployment. 66 | qnx: target.path = /tmp/$${TARGET}/bin 67 | else: unix:!android: target.path = /opt/$${TARGET}/bin 68 | !isEmpty(target.path): INSTALLS += target 69 | 70 | RC_ICONS = QModbusTool.ico 71 | 72 | RESOURCES += \ 73 | resources.qrc 74 | -------------------------------------------------------------------------------- /inputs_display.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file inputs_display.cpp 3 | * \brief [Digital] Inputs display window 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | */ 25 | 26 | // c++ includes 27 | /* -none- */ 28 | 29 | // C includes 30 | /* -none- */ 31 | 32 | // project includes 33 | #include "inputs_display.h" // local include 34 | 35 | 36 | InputsDisplay::InputsDisplay(QWidget *parent, const quint16 base_reg, const quint16 count, const quint8 uid) 37 | : RegisterDisplay(parent, base_reg, count, uid) 38 | { 39 | } 40 | 41 | 42 | bool InputsDisplay::load_configuration_parameters(const QDomElement &node) 43 | { 44 | auto reg = node.attribute("register").toUInt(); 45 | if (reg <= 10000 || reg >= 20000) { 46 | return false; 47 | } 48 | 49 | return RegisterDisplay::load_configuration_parameters(node); 50 | } 51 | 52 | 53 | QString InputsDisplay::get_object_name() const 54 | { 55 | return "InputsDisplay"; 56 | } 57 | 58 | 59 | void InputsDisplay::updateRegisterValue(const size_t index, const QString &value) 60 | { 61 | static_cast(value); 62 | const auto wvalue = (m_raw_values[index] > 0 ? QString("1") : QString("0")); 63 | RegisterDisplay::updateRegisterValue(index, wvalue); 64 | } 65 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file main.cpp 3 | * \brief Application entry point 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | * 25 | * 26 | * \section DESCRIPTION 27 | * 28 | * QModbusTool is a general-purpose modbus polling system based on libmodbus. 29 | * QModbusTool is intended to be a straight-forward implementation of a modbus 30 | * viewer supporting session and register set save and restore as well as 31 | * simple poll-driven trending. QModbusTool has been release under the GPL-2 32 | * license and includes properietary decoding logic for polling metadata. A 33 | * proprietary license may be available by contacting the author: 34 | * Andrew Buettner: leeloo cletis net 35 | */ 36 | 37 | // c++ includes 38 | #include // QApplication 39 | 40 | // C includes 41 | /* -none- */ 42 | 43 | // project includes 44 | #include "mainwindow.h" // MainWindow 45 | 46 | 47 | /** 48 | * \brief Main entry point 49 | * 50 | * @param argc standard argument 51 | * @param argv standard argument 52 | * 53 | * @return exit code at exit 54 | */ 55 | int main(int argc, char *argv[]) 56 | { 57 | QApplication a(argc, argv); 58 | MainWindow w; 59 | w.show(); 60 | return a.exec(); 61 | } 62 | -------------------------------------------------------------------------------- /inputs_display.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file inputs_display.h 3 | * \brief [Digital] Inputs display window 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | * 25 | * \section DESCRIPTION 26 | * 27 | * Derivitave of RegisterDisplay for interacting with Coils (0,000 series 28 | * Modbus registers). 29 | */ 30 | 31 | #ifndef INPUTS_DISPLAY_H 32 | #define INPUTS_DISPLAY_H 33 | 34 | 35 | // c++ includes 36 | /* -none- */ 37 | 38 | // C includes 39 | /* -none- */ 40 | 41 | // project includes 42 | #include "register_display.h" // RegisterDisplay 43 | 44 | 45 | class InputsDisplay : public RegisterDisplay 46 | { 47 | Q_OBJECT 48 | 49 | public: 50 | 51 | /** 52 | * \brief constructor 53 | * @param parent parent QObject owner 54 | * @param base_reg first register number 55 | * @param count number of registers to poll 56 | * @param uid device ID, unit ID, node of server to poll 57 | */ 58 | InputsDisplay(QWidget *parent, const quint16 base_reg, const quint16 count=32, const quint8 uid=0); 59 | 60 | virtual QString get_object_name() const override; 61 | virtual bool load_configuration_parameters(const QDomElement &node) override; 62 | 63 | protected: 64 | virtual void updateRegisterValue(const size_t index, const QString &value) override; 65 | 66 | }; 67 | 68 | #endif // INPUTS_DISPLAY_H 69 | -------------------------------------------------------------------------------- /exceptions.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file exceptions.cpp 3 | * \brief Exceptions used throughout the application 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | */ 25 | 26 | // c++ includes 27 | #include // operator % 28 | 29 | // C includes 30 | /* -none- */ 31 | 32 | // project includes 33 | #include "exceptions.h" // local include 34 | 35 | 36 | AppException::AppException(const QString &reason) : 37 | QException(), 38 | text(reason) 39 | { 40 | } 41 | 42 | 43 | AppException::operator QString () const 44 | { 45 | return text; 46 | } 47 | 48 | 49 | AppException* AppException::clone() const 50 | { 51 | return new AppException(*this); 52 | } 53 | 54 | 55 | void AppException::raise() const 56 | { 57 | throw *this; 58 | } 59 | 60 | 61 | const char* AppException::what() const noexcept 62 | { 63 | return text.toLocal8Bit(); 64 | } 65 | 66 | 67 | FileLoadException::FileLoadException(const QString &reason, const QString &filename) : 68 | AppException(reason % QString(": ") % filename) 69 | { 70 | } 71 | 72 | 73 | FileLoadException::FileLoadException(const QString &reason, const QString &filename, const size_t line) : 74 | AppException(reason % 75 | QString(": ") % 76 | filename % 77 | QChar('@') % 78 | QString::number(line)) 79 | { 80 | } 81 | -------------------------------------------------------------------------------- /exceptions.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file exceptions.h 3 | * \brief Exceptions used throughout the application 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | */ 25 | 26 | #ifndef EXCEPTIONS_H 27 | #define EXCEPTIONS_H 28 | 29 | // c++ includes 30 | #include // QString 31 | #include // QException 32 | 33 | // C includes 34 | /* -none- */ 35 | 36 | // project includes 37 | /* -none- */ 38 | 39 | 40 | /** 41 | * \brief Base class for all exceptions thrown by the application logic 42 | */ 43 | class AppException : public QException 44 | { 45 | public: 46 | 47 | /** 48 | * \brief constructor 49 | * @param reason Reason that the exception was thrown. 50 | */ 51 | AppException(const QString &reason); 52 | 53 | /** 54 | * \brief Convert to a QString 55 | * @return String representation of this exception 56 | */ 57 | operator QString () const; 58 | 59 | AppException* clone() const override; 60 | 61 | void raise() const override; 62 | 63 | const char* what() const noexcept override; 64 | 65 | private: 66 | const QString text; 67 | }; 68 | 69 | 70 | /** 71 | * \brief Exception thrown during file loading 72 | */ 73 | class FileLoadException : public AppException 74 | { 75 | public: 76 | 77 | /** 78 | * \brief constructor 79 | * @param reason Reason that the exception was thrown. 80 | * @param filename name of file being parsed 81 | */ 82 | FileLoadException(const QString &reason, const QString &filename); 83 | 84 | /** 85 | * \brief constructor including line number 86 | * @param reason Reason that the exception was thrown. 87 | * @param filename name of file being parsed 88 | * @param line line number error encountered 89 | */ 90 | FileLoadException(const QString &reason, const QString &filename, const size_t line); 91 | }; 92 | 93 | 94 | #endif // EXCEPTIONS_H 95 | -------------------------------------------------------------------------------- /coils_display.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file coils_display.h 3 | * \brief Coils display window 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | * 25 | * \section DESCRIPTION 26 | * 27 | * Derivitave of RegisterDisplay for interacting with Coils (0,000 series 28 | * Modbus registers). 29 | */ 30 | 31 | #ifndef COILSDISPLAY_H 32 | #define COILSDISPLAY_H 33 | 34 | // c++ includes 35 | #include // std::unordered_map 36 | 37 | // C includes 38 | /* -none- */ 39 | 40 | // project includes 41 | #include "register_display.h" // RegisterDisplay 42 | 43 | 44 | /** 45 | * \brief Holding Registers windows 46 | */ 47 | class CoilsDisplay : public RegisterDisplay 48 | { 49 | Q_OBJECT 50 | 51 | public: 52 | /** 53 | * \brief constructor 54 | * @param parent parent QObject owner 55 | * @param base_reg first register number 56 | * @param count number of registers to poll 57 | * @param uid device ID, unit ID, node of server to poll 58 | */ 59 | CoilsDisplay(QWidget *parent, const quint16 base_reg, const quint16 count=32, const quint8 uid=0); 60 | 61 | virtual QString get_object_name() const override; 62 | virtual bool load_configuration_parameters(const QDomElement &node) override; 63 | 64 | protected: 65 | virtual void updateRegisterValue(const size_t index, const QString &value) override; 66 | virtual QWidget* create_value_widget(const quint16 index, const bool initial) override; 67 | virtual QString get_display_value(const size_t index) const override; 68 | [[nodiscard]] virtual QString get_register_number_text(const quint16 reg_number) override; 69 | 70 | private slots: 71 | /** 72 | * \brief Signal when a user has toggled a checkbox (write request). 73 | * @param index register index in window 74 | */ 75 | void on_checkbox_checked(quint16 index); 76 | 77 | /** 78 | * \brief Signal that a control has been destroted. 79 | * @param register_widget widget reference 80 | * @param index register index in window 81 | */ 82 | void on_register_destroyed(QWidget *register_widget, const quint16 index); 83 | 84 | private: 85 | std::unordered_map m_remote_state; 86 | 87 | }; 88 | 89 | #endif // COILSDISPLAY_H 90 | -------------------------------------------------------------------------------- /configure_trend_line.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file configure_trend_line.h 3 | * \brief Trend configuration dialog box 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | * 25 | * \section DESCRIPTION 26 | * 27 | * Configure a trend. 28 | */ 29 | 30 | #ifndef CONFIGURE_TREND_LINE_H 31 | #define CONFIGURE_TREND_LINE_H 32 | 33 | // c++ includes 34 | #include // QColor 35 | 36 | // C includes 37 | /* -none- */ 38 | 39 | // project includes 40 | #include "base_dialog.h" // BaseDialog 41 | #include "ui_configure_trend_line.h" // Ui::ConfigureTrendDialog 42 | #include "trend_line.h" // TrendLine 43 | #include "trend_window.h" // TrendWindow 44 | 45 | 46 | /** 47 | * @brief The ConfigureTrendLine class 48 | */ 49 | class ConfigureTrendLine : public BaseDialog 50 | { 51 | Q_OBJECT 52 | 53 | public: 54 | 55 | /** 56 | * @brief "new" trend constructor 57 | * @param parent parent window 58 | */ 59 | ConfigureTrendLine(TrendWindow *const parent); 60 | 61 | /** 62 | * @brief "Update existing trend" constructor 63 | * @param parent trend to update 64 | */ 65 | ConfigureTrendLine(TrendLine *const parent); 66 | 67 | /** 68 | * @brief create_trend 69 | * \note 70 | * Only to be called once the dialog is complete. 71 | * 72 | * @throw AppException if a line is already associated 73 | * @return Trend based on the current configuration 74 | */ 75 | [[nodiscard]] TrendLine* create_trend(); 76 | 77 | /** 78 | * @brief get the key (IE node<<16|reg) 79 | */ 80 | operator quint32() const; 81 | 82 | virtual ~ConfigureTrendLine() override; 83 | 84 | protected: 85 | virtual void setupUi() final override; 86 | 87 | private slots: 88 | 89 | void on_Accept_pressed(); 90 | void on_Cancel_pressed(); 91 | void on_Delete_pressed(); 92 | void on_ColorButton_pressed(); 93 | 94 | private: 95 | 96 | void update_color_labels(); 97 | 98 | Ui::ConfigureTrendDialog *const m_ui; 99 | QColor m_display_color; 100 | TrendWindow *const m_parent; 101 | TrendLine *m_trend; 102 | }; 103 | 104 | 105 | #endif // CONFIGURE_TREND_LINE_H 106 | -------------------------------------------------------------------------------- /configure_trend.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file configure_trend.h 3 | * \brief Trend graph configuration dialog box 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | * 25 | * \section DESCRIPTION 26 | * 27 | * Configure the trend graph. 28 | */ 29 | 30 | #ifndef CONFIGURE_TREND_H 31 | #define CONFIGURE_TREND_H 32 | 33 | // c++ includes 34 | #include // std::pair 35 | #include // std::optional 36 | 37 | // C includes 38 | /* -none- */ 39 | 40 | // project includes 41 | #include "base_dialog.h" // BaseDialog 42 | #include "ui_configure_trend.h" // Ui::ConfigureTrend 43 | 44 | 45 | /** 46 | * @brief Dialog box presenting the graph configuration options. 47 | */ 48 | class ConfigureTrend : public BaseDialog 49 | { 50 | Q_OBJECT 51 | 52 | public: 53 | 54 | /** 55 | * @brief constructor 56 | * @param parent parent widget 57 | * @param min graph minimum 58 | * @param max graph maximum 59 | * @param n_points number of points in graph 60 | */ 61 | ConfigureTrend(QWidget *const parent, 62 | const double min, 63 | const double max, 64 | const int n_points); 65 | 66 | /** 67 | * @brief Get min and max values. 68 | * @return min,max specified in controls 69 | */ 70 | [[nodiscard]] std::pair get_min_max() const; 71 | 72 | /** 73 | * @brief Get the number of points from the control. 74 | * @return Number of points. Null (no value) if the value hasn't changed. 75 | */ 76 | [[nodiscard]] std::optional get_num_points() const; 77 | 78 | /** 79 | * @brief Get the "Use fixed limits" status. 80 | */ 81 | operator bool() const; 82 | 83 | virtual void accept() override; 84 | 85 | virtual ~ConfigureTrend() override; 86 | 87 | protected: 88 | virtual void setupUi() final override; 89 | 90 | private slots: 91 | 92 | /** 93 | * @brief Action taken when the History Size control is edited. 94 | */ 95 | void on_HistoryInput_textEdited(); 96 | 97 | private: 98 | Ui::ConfigureTrend *const m_ui; 99 | const int m_initial_points; 100 | const double m_initial_min; 101 | const double m_initial_max; 102 | bool m_first_edit; 103 | }; 104 | 105 | #endif // CONFIGURE_TREND_H 106 | -------------------------------------------------------------------------------- /metadata_structs.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file metadata_structs.h 3 | * \brief Containers and definitions used by the program for metadata 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | */ 25 | 26 | #ifndef METADATA_STRUCTS_H 27 | #define METADATA_STRUCTS_H 28 | 29 | // c++ includes 30 | #include // quint8 and friends 31 | #include // std::optional 32 | #include // std::shared_ptr 33 | 34 | // C includes 35 | /* -none- */ 36 | 37 | // project includes 38 | /* -none- */ 39 | 40 | 41 | // ///////////////////////////////////////////////////////////////////////////// 42 | // Forward declarations 43 | // ///////////////////////////////////////////////////////////////////////////// 44 | class BaseDialog; 45 | 46 | 47 | /** 48 | * \brief Encoding types recognized in the proprietary "read metadata" request 49 | * \note 50 | * These must match definition in project 51 | */ 52 | enum RegisterEncoding : qint8 { 53 | ENCODING_NONE=0, 54 | ENCODING_UINT16, 55 | ENCODINT_INT16, 56 | ENCODING_SIGNED_BYTES, 57 | ENCODING_BYTES, 58 | ENCODING_BITS, 59 | ENCODING_USER, 60 | ENCODING_UNKNOWN 61 | }; 62 | 63 | 64 | /** 65 | * \brief All data that can be returned by querying the register metadata 66 | */ 67 | class Metadata { 68 | friend class MetadataWrapper; 69 | 70 | public: 71 | /** 72 | * \brief default constructor 73 | * \note 74 | * This function shall only be called by the Metadata wrapper 75 | * 76 | * @param reg_num Register number 77 | * @param instance plugin instance value 78 | * @param fc function code 79 | */ 80 | Metadata(const quint16 reg_num, void *const instance, const quint8 fc); 81 | Metadata(const Metadata&) = delete; 82 | 83 | const quint16 register_number; /**< Register numer */ 84 | QString label; /**< Brief discription */ 85 | RegisterEncoding encoding; /**< Data encoding method */ 86 | std::optional min; /**< Minimum allowed range */ 87 | std::optional max; /**< Maximum allowed range */ 88 | std::optional dflt; /**< register default value */ 89 | const qint8 function_code; /**< function code of the request */ 90 | 91 | ~Metadata(); 92 | 93 | private: 94 | void *const m_request_instance; 95 | std::vector m_request; 96 | 97 | }; 98 | 99 | 100 | /** 101 | * \brief State tracking of a sequence of metadata requests 102 | */ 103 | struct WindowMetadataRequest { 104 | quint16 current_register; /**< Currently polled register */ 105 | quint16 last_register; /**< Last register to be polled in sequence */ 106 | quint8 node; /**< Node to poll */ 107 | BaseDialog *requester; /**< Pointer to window requesting */ 108 | std::shared_ptr request = nullptr; /**< Pointer to container */ 109 | }; 110 | 111 | 112 | #endif // METADATA_STRUCTS_H 113 | -------------------------------------------------------------------------------- /coils_display.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file coils_display.cpp 3 | * \brief Coils display window 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | */ 25 | 26 | // c++ includes 27 | #include // QCheckBox 28 | #include // operator% 29 | 30 | // C includes 31 | /* -none- */ 32 | 33 | // project includes 34 | #include "coils_display.h" // local include 35 | 36 | 37 | CoilsDisplay::CoilsDisplay(QWidget *parent, const quint16 base_reg, const quint16 count, const quint8 uid) 38 | :RegisterDisplay(parent, base_reg, count, uid), 39 | m_remote_state() 40 | { 41 | 42 | } 43 | 44 | 45 | void CoilsDisplay::updateRegisterValue(const size_t index, const QString &value) 46 | { 47 | static_cast(value); 48 | auto widget = dynamic_cast(m_register_values[index]); 49 | auto bvalue = (m_raw_values[index] > 0); 50 | m_remote_state[quint16(index + m_starting_register)] = bvalue; 51 | widget->setChecked(bvalue); 52 | } 53 | 54 | 55 | QWidget* CoilsDisplay::create_value_widget(const quint16 index, const bool initial) 56 | { 57 | static_cast(initial); 58 | auto widget = new QCheckBox(this); 59 | connect(widget, &QCheckBox::stateChanged, this, [=](int state) { 60 | static_cast(state); 61 | on_checkbox_checked(index); 62 | }); 63 | connect(widget, &QCheckBox::destroyed, this, [=](QWidget *w=nullptr) { 64 | static_cast(w); 65 | on_register_destroyed(widget, index); 66 | }); 67 | return widget; 68 | } 69 | 70 | 71 | void CoilsDisplay::on_checkbox_checked(quint16 index) 72 | { 73 | const auto checkbox = dynamic_cast(m_register_values[index]); 74 | const auto a = m_remote_state[index + m_starting_register]; 75 | const auto b = checkbox->isChecked(); 76 | if (a != b) { 77 | WriteRequest req{}; 78 | req.first_register = m_starting_register + index; 79 | req.node = m_node; 80 | req.values = {quint16(b)}; 81 | req.requester = this; 82 | emit write_requested(req); 83 | } 84 | } 85 | 86 | 87 | void CoilsDisplay::on_register_destroyed(QWidget *register_widget, const quint16 index) 88 | { 89 | static_cast(register_widget); 90 | static_cast(index); 91 | } 92 | 93 | 94 | QString CoilsDisplay::get_object_name() const 95 | { 96 | return "CoilsDisplay"; 97 | } 98 | 99 | 100 | bool CoilsDisplay::load_configuration_parameters(const QDomElement &node) 101 | { 102 | const auto reg = node.attribute("register").toUInt(); 103 | if (reg >= 10000) { 104 | return false; 105 | } 106 | 107 | return RegisterDisplay::load_configuration_parameters(node); 108 | } 109 | 110 | 111 | QString CoilsDisplay::get_display_value(const size_t index) const 112 | { 113 | auto widget = dynamic_cast(m_register_values[index]); 114 | return (widget->isChecked() ? "1" : "0"); 115 | } 116 | 117 | QString CoilsDisplay::get_register_number_text(const quint16 reg_number) 118 | { 119 | const auto base_text = RegisterDisplay::get_register_number_text(reg_number); 120 | const auto padding = QString(5 - base_text.size(), QChar('0')); 121 | return padding % base_text; 122 | } 123 | -------------------------------------------------------------------------------- /holding_register_display.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file holding_register_display.h 3 | * \brief Holding Registers window 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | * 25 | * \section DESCRIPTION 26 | * 27 | * Derivitave of Register Display for interacting with the Holding Registers 28 | * (40,000 series) 29 | */ 30 | 31 | #ifndef HOLDINGREGISTERDISPLAY_H 32 | #define HOLDINGREGISTERDISPLAY_H 33 | 34 | // c++ includes 35 | #include // QTimer 36 | #include // std::vector 37 | 38 | // C includes 39 | /* -none- */ 40 | 41 | // project includes 42 | #include "register_display.h" // RegisterDisplay 43 | 44 | 45 | /** 46 | * \brief Holding Registers windows 47 | */ 48 | class HoldingRegisterDisplay : public RegisterDisplay 49 | { 50 | Q_OBJECT 51 | 52 | public: 53 | 54 | /** 55 | * \brief constructor 56 | * @param parent parent QObject owner 57 | * @param base_reg first register number 58 | * @param count number of registers to poll 59 | * @param uid device ID, unit ID, node of server to poll 60 | */ 61 | HoldingRegisterDisplay(QWidget *parent, const quint16 base_reg, const quint16 count=32, const quint8 uid=0); 62 | 63 | virtual QString get_object_name() const override; 64 | virtual bool load_configuration_parameters(const QDomElement &node) override; 65 | 66 | protected: 67 | 68 | /** 69 | * \brief Encode display string to raw unsigned 16 value(s) 70 | * @param value String value 71 | * @param encoding Register's encoding 72 | * @return list of values to build as a write request 73 | */ 74 | [[nodiscard]] virtual std::vector encode_register(const QString &value, 75 | const RegisterEncoding encoding) const; 76 | 77 | virtual void updateRegisterValue(const size_t index, const QString &value) override; 78 | virtual QWidget* create_value_widget(const quint16 index, const bool initial) override; 79 | virtual QString get_display_value(const size_t index) const override; 80 | virtual void setupUi() override; 81 | 82 | private slots: 83 | /** 84 | * \brief Signal that a register text box is active for editing. 85 | * @param index register index in window 86 | */ 87 | void on_register_textEdited(const quint16 index); 88 | 89 | /** 90 | * \brief Signal that enter has been pressed on a text box (write request). 91 | * @param index register index in window 92 | */ 93 | void on_register_returnPressed(const quint16 index); 94 | 95 | /** 96 | * \brief Signal that a text box is no longer being edited. 97 | * @param index register index in window 98 | */ 99 | void on_register_editingFinished(const quint16 index); 100 | 101 | /** 102 | * \brief Signal that a control has been destroted. 103 | * @param register_widget widget reference 104 | * @param index register index in window 105 | */ 106 | void on_register_destroyed(QWidget *const register_widget, const quint16 index); 107 | 108 | /** 109 | * \brief Signal that the edit idle timer has expired. 110 | */ 111 | void on_timer_expired(); 112 | 113 | private: 114 | int m_active_index=-1; 115 | QTimer *const m_activity_timer; 116 | 117 | }; 118 | 119 | #endif // HOLDINGREGISTERDISPLAY_H 120 | -------------------------------------------------------------------------------- /metadata_wrapper.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file metadata_wrapper.h 3 | * \brief Wrapper singleton around the Read Metadata plugin shared object. 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | * 25 | * \section DESCRIPTION 26 | * 27 | * The MetadataWrapper class is a singleton class providing access the 28 | * metadata modbus plugin (non-GPL). It also provides safe abstraction so 29 | * functionality isn't lost (aside from the data its self) if the plugin isn't 30 | * present. It also allows for easy replacement with another interface that 31 | * can provide the same information. 32 | */ 33 | 34 | #ifndef METADATA_WRAPPER_H 35 | #define METADATA_WRAPPER_H 36 | 37 | 38 | // c++ includes 39 | #include // QObject 40 | #include // QPair 41 | #include // QLibrary 42 | #include // std::string_view 43 | #include // std::vector 44 | #include // std::shared_ptr 45 | #include // std::string 46 | 47 | // C includes 48 | /* -none- */ 49 | 50 | // project includes 51 | #include "metadata_structs.h" // Metadata 52 | 53 | 54 | /** 55 | * \brief State-aware wrapper around the metadata modbus plugin 56 | */ 57 | class MetadataWrapper : protected QObject 58 | { 59 | Q_OBJECT 60 | // friend class Metadata; 61 | 62 | public: 63 | 64 | /** 65 | * \brief Test if the library has been successfully loaded 66 | * @return ``true`` if loaded, ``false`` otherwise 67 | */ 68 | [[nodiscard]] bool loaded() const; 69 | 70 | /** 71 | * \brief Generate request container 72 | * @param reg_number Request metadata for this register number 73 | * @return Metadata container 74 | */ 75 | [[nodiscard]] std::shared_ptr create_request(const quint16 reg_number); 76 | 77 | /** 78 | * \brief Generate an outgoing request PDU 79 | * @param request Request container 80 | * @return raw PDU (length and data pointer) 81 | */ 82 | [[nodiscard]] QPair encode_request(std::shared_ptr request); 83 | 84 | /** 85 | * \brief Decode a response PDU 86 | * @param request Request container (updated) 87 | * @param data response PDU 88 | */ 89 | void decode_response(std::shared_ptr request, const std::vector &data); 90 | 91 | /** 92 | * \brief This class is a singleton, get the instance. 93 | * @return Singleton instance 94 | */ 95 | static MetadataWrapper* get_instance(); 96 | 97 | /** 98 | * \brief Dispose metadata during destructor 99 | * \note 100 | * This function is only called by the MetaData object, and only during 101 | * destruction. It is responsible for instructing the plugin to free 102 | * internal resources. 103 | * 104 | * @param m pointer to metadata object 105 | */ 106 | void dispose_metadata(const Metadata *const m); 107 | 108 | virtual ~MetadataWrapper(); 109 | 110 | private: 111 | 112 | /** 113 | * \brief constructor 114 | * @param lib_path Path to the shared object 115 | */ 116 | MetadataWrapper(const QString &lib_path); 117 | 118 | /** 119 | * \fn decode_labels, decode_defaults, decode_encoding, decode_limits 120 | * \brief Internal decoder functions 121 | * @param request pointer to request instance 122 | */ 123 | void decode_labels(Metadata *const request); 124 | void decode_defaults(Metadata *const request); 125 | void decode_encoding(Metadata *const request); 126 | void decode_limits(Metadata *const request); 127 | 128 | QLibrary *const m_dll_reference; 129 | // void *m_dll_reference = nullptr; 130 | }; 131 | 132 | #endif // METADATA_WRAPPER_H 133 | -------------------------------------------------------------------------------- /csv_importer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file csv_importer.h 3 | * \brief Import CSV data interactive modal dialog. 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | * 25 | * \section DESCRIPTION 26 | * 27 | * A complex dialog to allow the user to select columns containing data and 28 | * import them. 29 | */ 30 | 31 | #ifndef CSV_IMPORTER_H 32 | #define CSV_IMPORTER_H 33 | 34 | // c++ includes 35 | #include // QList 36 | #include // QStringList 37 | #include // QGridLayout 38 | #include // QTableWidget 39 | #include // QPushButton 40 | #include // QComboBox 41 | #include // QCheckBox 42 | #include // QSpinBox 43 | #include // std::vector 44 | #include // std::array 45 | #include // std::tuple 46 | #include // std::pair 47 | 48 | // C includes 49 | /* -none- */ 50 | 51 | // project includes 52 | #include "base_dialog.h" 53 | 54 | 55 | /** 56 | * \brief Field data configured by this dialog box. 57 | */ 58 | using FieldData = std::tuple; 59 | 60 | 61 | /** 62 | * \brief Field -> data mapping in ``FieldData`` tuple 63 | */ 64 | enum TestFields : size_t { 65 | REG_NUMBER=0, 66 | REG_VALUE, 67 | NODE_ID, 68 | HEADER_ROW 69 | }; 70 | 71 | 72 | /** 73 | * \brief CSV Data Import Selection 74 | */ 75 | class CsvImporter : public BaseDialog 76 | { 77 | Q_OBJECT 78 | 79 | public: 80 | 81 | /** 82 | * \brief constructor 83 | * @param parent parent QObject owner 84 | * @param all_data Data decoded by the CSV parser 85 | */ 86 | CsvImporter(QWidget *parent, const QList &all_data); 87 | 88 | /** 89 | * \brief Get the configuration specified. 90 | * @throws AppException if the dialog is not "finished" 91 | * @return tuple representing fields \sa TestFields 92 | */ 93 | [[nodiscard]] const FieldData get_config() const; 94 | 95 | private slots: 96 | 97 | /** 98 | * \brief OK Button clicked - verify input 99 | */ 100 | void on_ok_clicked(); 101 | 102 | /** 103 | * \brief Use fixed node checked / unchecked 104 | */ 105 | void on_single_node_checked(int status); 106 | 107 | /** 108 | * \brief First row contains headers checked / unchecked 109 | */ 110 | void on_header_row_checked(int status); 111 | 112 | /** 113 | * \brief Attempt to automatically deduce fields from input 114 | * \note 115 | * Made a slot so it can be directly invoked from a QTimer::singleShot 116 | */ 117 | void attempt_autoconfig(); 118 | 119 | private: 120 | 121 | /** 122 | * \brief Initialize the UI components. 123 | */ 124 | void setupUi() override; 125 | 126 | /** 127 | * \brief Called when a column header is checked/unchecked. 128 | * @param checkbox Widget that generated the event 129 | * @param column column that has been updated 130 | */ 131 | void on_column_checked(const QCheckBox *const checkbox, const size_t column); 132 | 133 | 134 | bool m_is_valid = false; 135 | QTableWidget *const m_preview_table; 136 | QWidget *const m_grid_container; 137 | QGridLayout *const m_control_grid; 138 | QPushButton *const m_ok; 139 | QPushButton *const m_cancel; 140 | QCheckBox *const m_first_row_headers; 141 | QCheckBox *const m_fixed_node; 142 | QSpinBox *const m_node_select; 143 | 144 | /** 145 | * \var m_test_text 146 | * Contains the first few rows of data in column-major order. 147 | */ 148 | std::vector> m_test_text; 149 | std::vector> m_role_selection; 150 | 151 | /* Order must match that in the tuple */ 152 | const QStringList m_options; 153 | 154 | /* Only updated when ok clicked is successful */ 155 | FieldData m_config; 156 | }; 157 | 158 | #endif // CSV_IMPORTER_H 159 | -------------------------------------------------------------------------------- /base_dialog.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file base_dialog.h 3 | * \brief Base class for most screens 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | * 25 | * \section DESCRIPTION 26 | * 27 | * This class acts as a base class for many of the modal windows used in the 28 | * project. It specifically provides some basic definitions needed to interact 29 | * with the scheduler as well as delayed widget population. 30 | */ 31 | 32 | #ifndef BASE_DIALOG_H 33 | #define BASE_DIALOG_H 34 | 35 | // c++ includes 36 | #include // QDialog 37 | #include // QResizeEvent 38 | #include // QVBoxLayout 39 | #include // QAbstractButton 40 | #include // QStyle 41 | 42 | // C includes 43 | /* -none- */ 44 | 45 | // project includes 46 | #include "write_event.h" // WriteRequest 47 | #include "metadata_structs.h" // WindowMetadataRequest 48 | #include "modbusthread.h" // ModbusThread 49 | 50 | 51 | /** 52 | * \brief Modal window 53 | */ 54 | class BaseDialog : public QDialog 55 | { 56 | Q_OBJECT 57 | 58 | public: 59 | 60 | /** 61 | * \brief constructor 62 | * @param parent parent QObject owner 63 | * @param create_layout set ``false`` to omit creating the top QVBoxLayout 64 | */ 65 | BaseDialog(QWidget *const parent, const bool create_layout=true); 66 | 67 | void add_icon_to_button(QAbstractButton *const button, 68 | const QStyle::StandardPixmap icon_name) const; 69 | 70 | /** 71 | * \brief Set metadata callback from scheduler 72 | * @param metadata Metadata returned from poller 73 | * @param node Node (slave ID) that was polled 74 | */ 75 | virtual void set_metadata(std::shared_ptr metadata, const quint8 node); 76 | 77 | /** 78 | * \brief Callback from scheduler to poll register data (1-poll) 79 | * @param engine Modbus connection 80 | */ 81 | virtual void poll_register_set(ModbusThread *const engine); 82 | 83 | public slots: 84 | 85 | /** 86 | * \brief Signal to update a register value. 87 | * @param reg register number 88 | * @param value raw register value 89 | * @param unit_id node / unit ID polled 90 | */ 91 | virtual void on_new_value(const quint16 reg, const quint16 value, const quint8 unit_id); 92 | 93 | /** 94 | * \brief Signal that a modbus exception occurred. 95 | * @param requester request source associated with the exception 96 | * @param exception exception text 97 | */ 98 | virtual void on_exception_status(BaseDialog *requester, const QString exception); 99 | 100 | signals: 101 | 102 | /** 103 | * \brief Emit that the window has been closed. 104 | * @param window this window 105 | */ 106 | void window_closed(BaseDialog *window); 107 | 108 | /** 109 | * \brief Emit that the window has been shown for the first time. 110 | * @param window this window 111 | */ 112 | void window_first_display(BaseDialog *window); 113 | 114 | /** 115 | * \brief Emit data write request. 116 | * @param req write request structure 117 | */ 118 | void write_requested(WriteRequest req); 119 | 120 | /** 121 | * \brief Emit read metadata request. 122 | * @param req read metadata request structure 123 | */ 124 | void metadata_requested(WindowMetadataRequest req); 125 | 126 | protected: 127 | 128 | /** 129 | * \brief Initialize the UI components. 130 | */ 131 | virtual void setupUi() = 0; 132 | 133 | virtual void showEvent(QShowEvent *evt) override; 134 | virtual void resizeEvent(QResizeEvent *evt) override; 135 | virtual void closeEvent(QCloseEvent *evt) override; 136 | 137 | QVBoxLayout *const m_top_layout; /**< Window layout control */ 138 | bool m_debug_resize = false; /*< derived classes may change this to print window sizes */ 139 | 140 | private: 141 | bool m_first_display; 142 | bool m_emit_close; 143 | }; 144 | 145 | 146 | #endif // BASE_DIALOG_H 147 | -------------------------------------------------------------------------------- /base_dialog.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file base_dialog.cpp 3 | * \brief Base class for most screens 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | */ 25 | 26 | 27 | // c++ includes 28 | #include // qDebug 29 | #include // QIcon 30 | 31 | // C includes 32 | /* -none- */ 33 | 34 | // project includes 35 | #include "base_dialog.h" // local include 36 | #include "exceptions.h" 37 | 38 | 39 | BaseDialog::BaseDialog(QWidget *const parent, const bool create_layout) : 40 | QDialog(parent), 41 | m_top_layout{(create_layout ? new QVBoxLayout(this) : nullptr)}, 42 | m_first_display{true}, 43 | m_emit_close{true} 44 | { 45 | if (create_layout) { 46 | setLayout(m_top_layout); 47 | } 48 | 49 | connect(this, &BaseDialog::rejected, this, [=]() { 50 | if (m_emit_close) { 51 | m_emit_close = false; 52 | emit window_closed(this); 53 | } 54 | }); 55 | 56 | // Solution found at: 57 | // https://stackoverflow.com/questions/42655988/adjust-title-position-in-a-qgroupbox-using-style-sheets 58 | // To the Qt developers: Why the ~ [ H 3 1 ! ] ~ is this not the 59 | // default and where the ~ [ F * * * ] ~ is this documented? 60 | // I mean, come on! Scroll area has a frame by default which no one uses, 61 | // but this requires this long of a string to be consistent with every 62 | // other WM out there? Really? 63 | setStyleSheet("QGroupBox{" 64 | "font: bold;" 65 | "border: 1px solid silver;" 66 | "border-radius: 6px;" 67 | "margin-top: 6px;} " 68 | "QGroupBox::title{" 69 | "subcontrol-origin: margin;" 70 | "left: 7px;" 71 | "top: -5px;" 72 | "padding: 0px 5px 0px 5px}"); 73 | 74 | auto icon = QIcon(":/QModbusTool.ico"); 75 | setWindowIcon(icon); 76 | } 77 | 78 | 79 | void BaseDialog::showEvent(QShowEvent *evt) 80 | { 81 | if (m_first_display) { 82 | m_first_display = false; 83 | setupUi(); 84 | emit window_first_display(this); 85 | } 86 | QDialog::showEvent(evt); 87 | } 88 | 89 | 90 | void BaseDialog::resizeEvent(QResizeEvent *evt) 91 | { 92 | QDialog::resizeEvent(evt); 93 | if (m_debug_resize) { 94 | const auto size = evt->size(); 95 | qDebug() << size.width() << ',' << size.height(); 96 | } 97 | } 98 | 99 | 100 | void BaseDialog::closeEvent(QCloseEvent *evt) 101 | { 102 | QDialog::closeEvent(evt); 103 | if (m_emit_close) { 104 | m_emit_close = false; 105 | emit window_closed(this); 106 | } 107 | } 108 | 109 | 110 | void BaseDialog::add_icon_to_button(QAbstractButton *const button, 111 | const QStyle::StandardPixmap icon_name) const 112 | { 113 | // As far as I'm concerned, the fact that I need to do this is a bug in QT. 114 | // https://forum.learnpyqt.com/t/are-there-any-built-in-qicons/185/2 115 | auto style = button->style(); 116 | auto icon = style->standardIcon(icon_name); 117 | button->setIcon(icon); 118 | } 119 | 120 | 121 | void BaseDialog::on_new_value(const quint16 reg, const quint16 value, const quint8 unit_id) 122 | { 123 | static_cast(reg); 124 | static_cast(value); 125 | static_cast(unit_id); 126 | } 127 | 128 | 129 | void BaseDialog::on_exception_status(BaseDialog *requester, const QString exception) 130 | { 131 | static_cast(requester); 132 | static_cast(exception); 133 | } 134 | 135 | 136 | void BaseDialog::set_metadata(std::shared_ptr metadata, const quint8 node) 137 | { 138 | static_cast(metadata); 139 | static_cast(node); 140 | throw AppException("Metadata not configured in this object"); 141 | } 142 | 143 | 144 | void BaseDialog::poll_register_set(ModbusThread *const engine) 145 | { 146 | static_cast(engine); 147 | throw AppException("Polling not configured in this object"); 148 | } 149 | -------------------------------------------------------------------------------- /configure_trend.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ConfigureTrend 4 | 5 | 6 | 7 | 0 8 | 0 9 | 472 10 | 181 11 | 12 | 13 | 14 | Configure Graph 15 | 16 | 17 | 18 | :/QModbusTool.ico:/QModbusTool.ico 19 | 20 | 21 | 22 | 23 | 290 24 | 130 25 | 171 26 | 32 27 | 28 | 29 | 30 | Qt::Horizontal 31 | 32 | 33 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 34 | 35 | 36 | 37 | 38 | 39 | 20 40 | 10 41 | 261 42 | 161 43 | 44 | 45 | 46 | Graph Setup 47 | 48 | 49 | 50 | 51 | 10 52 | 50 53 | 241 54 | 97 55 | 56 | 57 | 58 | 59 | 6 60 | 61 | 62 | 63 | 64 | Graph Minimum: 65 | 66 | 67 | 68 | 69 | 70 | 71 | 1.0 72 | 73 | 74 | 75 | 76 | 77 | 78 | Qt::RightToLeft 79 | 80 | 81 | QCheckBox {padding-right: 25px;} 82 | 83 | 84 | Use Fixed Limits? 85 | 86 | 87 | true 88 | 89 | 90 | 91 | 92 | 93 | 94 | Graph Maximum: 95 | 96 | 97 | 98 | 99 | 100 | 101 | 0.0 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 310 112 | 20 113 | 141 114 | 80 115 | 116 | 117 | 118 | History Size 119 | 120 | 121 | 122 | 123 | 20 124 | 40 125 | 101 126 | 30 127 | 128 | 129 | 130 | 100 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | buttonBox 141 | accepted() 142 | ConfigureTrend 143 | accept() 144 | 145 | 146 | 248 147 | 254 148 | 149 | 150 | 157 151 | 274 152 | 153 | 154 | 155 | 156 | buttonBox 157 | rejected() 158 | ConfigureTrend 159 | reject() 160 | 161 | 162 | 316 163 | 260 164 | 165 | 166 | 286 167 | 274 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /trend_line.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file trend_line.cpp 3 | * \brief Individual trend line 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | */ 25 | 26 | 27 | // c++ includes 28 | #include // std::numeric_limits 29 | #include // operator% 30 | #include // std::fill 31 | 32 | // C includes 33 | /* -none- */ 34 | 35 | // project includes 36 | #include "trend_line.h" // local include 37 | #include "exceptions.h" // AppException 38 | #include "configure_trend_line.h" // ConfigureTrendLine 39 | 40 | 41 | TrendLine::TrendLine(TrendWindow *parent, 42 | const quint16 reg, 43 | const quint8 node) : 44 | QPushButton(parent->m_scroll_container), 45 | m_reg_number{reg}, 46 | m_device_id{node}, 47 | m_num_points{parent->m_timestamps.size()}, 48 | m_signed_value{true}, 49 | m_mult{1.0}, 50 | m_offset{0.0}, 51 | m_pen_color(Qt::blue), 52 | m_history(m_num_points), 53 | m_last_value(), 54 | m_next_index{0}, 55 | m_parent{parent} 56 | { 57 | connect(this, &TrendLine::clicked, this, &TrendLine::on_clicked); 58 | setupUi(); 59 | } 60 | 61 | 62 | void TrendLine::set_data(const quint16 value) noexcept 63 | { 64 | if (m_signed_value) { 65 | const auto v = static_cast(value); 66 | m_last_value = qint32(v); 67 | } else { 68 | m_last_value = qint32(value); 69 | } 70 | } 71 | 72 | 73 | void TrendLine::update() 74 | { 75 | if (!bool(m_last_value)) { 76 | throw AppException(tr("Update called on invalid data")); 77 | } 78 | 79 | auto v = double(m_last_value.value()) * m_mult; 80 | v += m_offset; 81 | m_parent->update_min_max(v); 82 | m_history[m_next_index] = v; 83 | 84 | if (++m_next_index >= m_num_points) { 85 | m_next_index = 0; 86 | } 87 | 88 | m_last_value.reset(); 89 | } 90 | 91 | 92 | TrendLine::operator bool() const noexcept 93 | { 94 | return m_last_value.has_value(); 95 | } 96 | 97 | 98 | const double& TrendLine::operator[] (int index) const 99 | { 100 | auto array_index = m_next_index + index; 101 | if (array_index >= m_num_points) { 102 | array_index -= m_num_points; 103 | } 104 | 105 | return m_history.at(array_index); 106 | } 107 | 108 | 109 | void TrendLine::configure(const double m, const double b, const bool set_signed) 110 | { 111 | m_mult = m; 112 | m_offset = b; 113 | m_signed_value = set_signed; 114 | } 115 | 116 | 117 | void TrendLine::set_color(const QColor &pen_color) noexcept 118 | { 119 | m_pen_color = pen_color; 120 | setupUi(); 121 | } 122 | 123 | 124 | TrendLine::operator quint32() const noexcept 125 | { 126 | return m_parent->get_key(m_reg_number, m_device_id); 127 | } 128 | 129 | 130 | TrendLine::operator QPen() const noexcept 131 | { 132 | return QPen(m_pen_color); 133 | } 134 | 135 | 136 | void TrendLine::on_clicked() 137 | { 138 | auto dlg = ConfigureTrendLine(this); 139 | dlg.exec(); 140 | } 141 | 142 | 143 | void TrendLine::setupUi() 144 | { 145 | setFixedWidth(m_parent->m_add_button->size().width()); 146 | setAutoDefault(false); 147 | const auto reg_number = QString::number(m_reg_number); 148 | const auto reg_padding = QString(5 - reg_number.size(), QChar('0')); 149 | setText("------\n" % reg_padding % 150 | reg_number % QChar('@') % QString::number(m_device_id)); 151 | setStyleSheet("QPushButton {color: " % m_pen_color.name() % ";}"); 152 | } 153 | 154 | 155 | void TrendLine::save_configuration(QDomElement &node) const 156 | { 157 | node.setAttribute("register", QString::number(m_reg_number)); 158 | node.setAttribute("node", QString::number(m_device_id)); 159 | node.setAttribute("signed", QString::number(int(m_signed_value))); 160 | node.setAttribute("m", QString::number(m_mult)); 161 | node.setAttribute("b", QString::number(m_offset)); 162 | node.setAttribute("color", m_pen_color.name()); 163 | 164 | } 165 | 166 | 167 | void TrendLine::resize(int new_size) 168 | { 169 | m_next_index = 0; 170 | m_num_points = new_size; 171 | m_history.resize(new_size); 172 | std::fill(m_history.begin(), m_history.end(), m_offset); 173 | } 174 | -------------------------------------------------------------------------------- /trend_line.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file trend_line.h 3 | * \brief Individual trend line 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | * 25 | * \section DESCRIPTION 26 | * 27 | * The trend data is encapsulated within the "Button" located in the "Legend" 28 | * section of the graph. It overlads the actual button and provides a bunch of 29 | * syntactic sugar to interface to the underlying trend data. This class shall 30 | * not be used outside of the TrendWindow class. 31 | */ 32 | 33 | #ifndef TREND_LINE_H 34 | #define TREND_LINE_H 35 | 36 | // c++ includes 37 | #include // QVector 38 | #include // QColor 39 | #include // QPushButton 40 | #include // QPen 41 | #include // QDomElement 42 | #include // std::optional 43 | 44 | // C includes 45 | /* -none- */ 46 | 47 | // project includes 48 | #include "trend_window.h" 49 | 50 | 51 | /** 52 | * @brief Graph indivdual line configuration and storage 53 | */ 54 | class TrendLine : public QPushButton 55 | { 56 | friend class ConfigureTrendLine; 57 | 58 | Q_OBJECT 59 | 60 | public: 61 | 62 | /** 63 | * @brief constructor 64 | * @param parent parent TrendWindo 65 | * @param reg modbus register to monitor 66 | * @param node node / device ID to monitor 67 | */ 68 | TrendLine(TrendWindow *parent, const quint16 reg, const quint8 node=0); 69 | 70 | /** 71 | * @brief Set the current data 72 | * @param value current data value 73 | */ 74 | void set_data(const quint16 value) noexcept; 75 | 76 | /** 77 | * @brief configure trend internals 78 | * @param m multiplier 79 | * @param b offset 80 | * @param set_signed interpret register as signed (``true``) 81 | * or unsigned (``false``) 82 | */ 83 | void configure(const double m, const double b, const bool set_signed=true); 84 | 85 | /** 86 | * @brief Set the trend line color 87 | * @param pen_color line color 88 | */ 89 | void set_color(const QColor &pen_color) noexcept; 90 | 91 | /** 92 | * @brief Update history with current value 93 | * \note 94 | * This also updates the parent min/max values 95 | * 96 | * @throw AppException if no value has been stored. 97 | */ 98 | void update(); 99 | 100 | /** 101 | * @brief get current state : IE is there a value currently stored? 102 | */ 103 | [[nodiscard]] operator bool() const noexcept; 104 | 105 | /** 106 | * \brief Get the QPen for graph drawing 107 | */ 108 | [[nodiscard]] operator QPen() const noexcept; 109 | 110 | /** 111 | * \brief get hash key from TrendLine instance 112 | */ 113 | [[nodiscard]] operator quint32() const noexcept; 114 | 115 | /** 116 | * @brief Get the value at an index 117 | * @param index index: 0 => oldest, g_num_points-1 => newest 118 | * @return historical value 119 | */ 120 | [[nodiscard]] const double& operator[] (int index) const; 121 | 122 | /** 123 | * @brief Save trend line configuration 124 | * @param node fill data in node 125 | */ 126 | void save_configuration(QDomElement &node) const; 127 | 128 | /** 129 | * @brief Resize the number of history items 130 | * @param new_size new number of history items 131 | */ 132 | void resize(int new_size); 133 | 134 | const quint16 m_reg_number; /**< Register number associated with line */ 135 | const quint8 m_device_id; /**< Device ID to monitor */ 136 | 137 | private slots: 138 | 139 | /** 140 | * @brief On button clicked handler 141 | */ 142 | void on_clicked(); 143 | 144 | private: 145 | 146 | /** 147 | * @brief Update the UI (Button contents) 148 | */ 149 | void setupUi(); 150 | 151 | int m_num_points; 152 | bool m_signed_value; /**< Treat incoming data as signed? */ 153 | double m_mult; /**< Multiply value by m */ 154 | double m_offset; /**< Add b to value after multiplication */ 155 | 156 | QColor m_pen_color; /**< Desired pen color */ 157 | QVector m_history; 158 | std::optional m_last_value; 159 | qint32 m_next_index; 160 | TrendWindow *const m_parent; 161 | }; 162 | 163 | 164 | #endif // TREND_LINE_H 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## QModbusTool - A QT Based Modbus Client 2 | QModbusTool is a multi-modal interface for interacting with Modbus registers on devices ranging from mid-range PLCs to smaller industrial end-devices that provide access through Modbus/TCP. It aims to be simple and light weight while providing several key capabilities that are extremely helpful when commissioning and calibrating Modbus enabled devices including: 3 | 4 | * **Live data trend** 5 | * Save/Restore register sets 6 | * Save/Restore session 7 | * Support for loading register metadata using a simple plugin interface (see [PLUGIN.md][1]). 8 | 9 | ### License 10 | Copyright (C) 2021 Andrew Buettner (ABi) 11 | 12 | QModbusTool is released under the GPLv2 license. See [LICENSE.md][2] for complete terms and conditions. 13 | 14 | The metadata plugin header "[metadata.h][3]" is released under the 3-clause BSD license to allow equipment developers to create proprietary plugins specific to their equipment. 15 | 16 | ### Basic usage 17 | Open the application and add 1 or more register sets to poll. Each window represents a sequential block of registers that are polled with a single poll. The number of registers presented can be polled is between 1 and the protocol maximum (125 for 16-bit analog values, 2000 for digital signals). Polls can be directed to a specific "Slave ID", also known as an "Instance ID", "Device ID", or "Node". 18 | 19 | The communication parameters may also be configured (remote device IP address and port). The timeout is a local timeout to wait for a response. Generally, Modbus/TCP does not implement a timeout in the way that it does on other transports such as UDP, RTU, or ASCII. This is provided for recovery from Modbus/TCP devices and protocol gateways that don't handle Modbus timeouts correctly. Alternatively, a previously saved session can be restored. 20 | 21 | Optionally, a trend window can be created. Using the available controls on the trend add one or more registers to be graphed. These registers must be polled VIA another register window. The trend will be updated once for each set of registers polled. 22 | 23 | Once the communication parameters have been correctly configured and the desired windows have been created, select "Connect" from the "File" menu and the program will connect. If the device connected to supports "Read Device ID" at address 0, the device name will briefly appear in the status bar section. Once connected data may be polled either on request or automatically by selecting the appropriate option from the "Poll" menu. If the meta data plug-in is available, the system may also poll register meta data from the the connected device. The session may also be saved as can any window data and the trend. 24 | 25 | ### Building 26 | QModbusTool was specifically designed for Linux, it should be reasonably easy to build under both Windows and macOS. However, the plugin interface has not been ported to these platforms. 27 | QModbusTool is built with qmake and requires a compiler that supports C++17 or newer. It has been successfully built using both GCC and CLang. 28 | 29 | ### Prerequisites 30 | * Qt 5+ 31 | - += core gui xml widgets printsupport 32 | * [LibModbus][4] (Modbus back-end) 33 | * [QtCSV][5] (saving / restoring register and trend data) 34 | * [QCustomPlot][6] (trending widget) 35 | - The build process expects this to be available as a library. It does not ship with the amalgamation. 36 | - It is recommended to build with OpenGL support as it will utilize that functionality if available to improve redraw performance. 37 | 38 | ### Expanding 39 | QModbusTool can easily have functionality expanded. The base class for nearly all data-driven displays is defined in [base\_dialog.h][7]/.cpp. This provides a bare-minimum interface needed to send and receive data from the scheduler. The most important interfaces are: 40 | 41 | * *`window_closed`* - Guaranteed to be emitted exactly once when the window is closed regardless of method. 42 | * *`setupUi`* - Guaranteed to be called exactly once the very first time that the *show* event is called. 43 | * *`window_first_display`* - Guaranteed to be emitted exactly once after the *startUi* function has been called. 44 | * *`write_requested`* - may be emitted if the window requests to write registers. 45 | * *`metadata_requested`* - may be emitted if the window requests to read metadata _(`set_metadata` must be implemented)_. 46 | * *`on_new_value`* - received whenever new data is received or a scheduler/system event occurs. 47 | * *`on_exception_status`* - received if a read, write, or metadata request results in a Modbus exception. 48 | 49 | The appropriate signals and slots must be connected to the Scheduler in order for the window to receive Modbus events. 50 | 51 | ### Missing Features 52 | While the application is mostly complete and should be usable for many applications, there are some notable items currently missing: 53 | 54 | 1. Modbus/RTU - I had to do some "hackey" stuff in order to handle custom modbus requests see [this issue][8]. This may not translate well to other flavors of the protocol. 55 | 2. Translations - All strings should be annotated. 56 | 3. Help documentation / about box 57 | 58 | [1]: PLUGIN.md 59 | [2]: LICENSE.md 60 | [3]: metadata.h 61 | [4]: https://libmodbus.org/ 62 | [5]: https://github.com/iamantony/qtcsv 63 | [6]: https://www.qcustomplot.com/ 64 | [7]: base_data.h 65 | [8]: https://github.com/stephane/libmodbus/issues/231 66 | -------------------------------------------------------------------------------- /metadata.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file metadata.h 3 | * \brief Modbus metadata plugin public interface 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * All rights reserved. 7 | * 8 | * \section LICENSE 9 | * 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions are met: 12 | * (1) Redistributions of source code must retain the above copyright notice, 13 | * this list of conditions and the following disclaimer. 14 | * (2) Redistributions in binary form must reproduce the above copyright notice, 15 | * this list of conditions and the following disclaimer in the documentation 16 | * and/or other materials provided with the distribution. 17 | * (3) Neither the name of the nor the names of its 18 | * contributors may be used to endorse or promote products derived from this 19 | * software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | * ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 25 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef METADATA_DLL_H 34 | #define METADATA_DLL_H 35 | 36 | 37 | #ifdef __cplusplus 38 | extern "C" { 39 | #endif 40 | 41 | #include // uint8_t and friends 42 | 43 | #define DATA_BUFFER_REQUIRED_SIZE 252 44 | 45 | 46 | /** 47 | * \fn create_request 48 | * \brief Create a request instance. 49 | * @param register_number Register number (IE 40001, or 10003) 50 | * @param function_code [optional, out] update with the function code 51 | * @return pointer to data, or ``NULL`` if invalid 52 | */ 53 | typedef void* (*create)(uint16_t /*register_number*/, uint8_t* /*function_code*/); 54 | #define CREATE_SYMBOL "create_request" 55 | 56 | /** 57 | * \fn encode_request 58 | * \brief Encode outgoing request to databuffer 59 | * @param request Request instance previously created 60 | * @param data Data buffer (must be at least 252 bytes long) 61 | * @return number of bytes written to output data, 0 on error 62 | */ 63 | typedef uint8_t (*encode)(void* /*request*/, uint8_t* /*data*/); 64 | #define ENCODE_SYMBOL "encode_request" 65 | 66 | /** 67 | * \fn decode_response 68 | * \brief Decode incoming request 69 | * @param request Request instance previously created 70 | * @param data response buffer 71 | * @param data_len response length 72 | * 73 | * @return 0 on success, != 0 on error 74 | */ 75 | typedef int (*decode)(void* /*request*/, const uint8_t* /*data*/, uint8_t /*data_len*/); 76 | #define DECODE_SYMBOL "decode_response" 77 | 78 | /** 79 | * \fn decode_label 80 | * \brief Decode the label section of a response 81 | * @param request Request instance previously created 82 | * @param data (Optional) write null-terminated string to buffer 83 | * @return Number of bytes that have been (or would be) written to data, 84 | * <0 for error 85 | */ 86 | typedef int32_t (*label)(void* /*request*/, char* /*data*/); 87 | #define LABEL_SYMBOL "decode_label" 88 | 89 | /** 90 | * \fn read_min_max 91 | * \brief Decode the min/max fields of the response 92 | * 93 | * @param request Request instance previously created 94 | * @param min Update with min value 95 | * @param max Update with max value 96 | * 97 | * @return 0 if response contains data, != 0 otherwise (min/max not updated) 98 | */ 99 | typedef int (*minmax)(void* /*request*/, int32_t* /*min*/, int32_t* /*max*/); 100 | #define MINMAX_SYMBOL "read_min_max" 101 | 102 | /** 103 | * \fn read_default 104 | * \brief Decode the default value field of the response 105 | * @param request Request instance previously created 106 | * @param dflt Update with default value 107 | * @param max Update with max value 108 | * @return 0 if response contains data, != 0 otherwise (dflt not updated) 109 | */ 110 | typedef int (*get_default)(void* /*request*/, int32_t* /*dflt*/); 111 | #define DEFAULT_SYMBOL "read_default" 112 | 113 | /** 114 | * \fn get_encoding 115 | * \brief Decode the register encoding field of the response 116 | * @param request Request instance previously created 117 | * @param data Data buffer from protocol 118 | * @param data_len Total length of data 119 | * @return Encoding enumeration as int8_t (<0 for no data) 120 | */ 121 | typedef int8_t (*encoding)(void* /*request*/); 122 | #define ENCODING_SYMBOL "get_encoding" 123 | 124 | /** 125 | * \fn release_request 126 | * \brief Delete a previously created request and release all resources 127 | * @param request Request instance previously created 128 | */ 129 | typedef void (*release)(void* /*request*/); 130 | #define RELEASE_SYMBOL "release_request" 131 | 132 | #ifdef __cplusplus 133 | } 134 | #endif 135 | 136 | #endif 137 | -------------------------------------------------------------------------------- /modbusthread.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file modbusthread.h 3 | * \brief Modbus thread container 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | * 25 | * \section DESCRIPTION 26 | * 27 | * Modbus protocol functionality is abstracted (somewhat) from the display and 28 | * takes place in a separate thread because all libmodbus transactions are 29 | * treated as blocking calls. 30 | */ 31 | 32 | #ifndef MODBUSTHREAD_H 33 | #define MODBUSTHREAD_H 34 | 35 | // c++ includes 36 | #include // QThread 37 | #include // QMutex 38 | #include // QWaitCondition 39 | #include // QString 40 | 41 | // C includes 42 | #include // modbus_t 43 | 44 | // project includes 45 | /* -none- */ 46 | 47 | 48 | /** 49 | * \brief Modbus communication thread 50 | */ 51 | class ModbusThread : public QThread 52 | { 53 | Q_OBJECT 54 | 55 | public: 56 | 57 | /** 58 | * \brief constructor 59 | * @param parent parent QObject owner 60 | * @param host host to be resolved and connected to 61 | * @param port destination port 62 | */ 63 | ModbusThread(QObject *parent, const QString &host, const quint16 port); 64 | 65 | /** 66 | * \brief Close and exit thread. 67 | * \note 68 | * Blocking call 69 | */ 70 | void close(); 71 | 72 | /** 73 | * \brief Issue a modbus read request 74 | * @param first_reg first register number (0 = device id) 75 | * @param num_regs number of registers to read 76 | * @param uid Unit ID / Node to poll 77 | */ 78 | void modbus_request(const quint16 first_reg, const quint16 num_regs, const quint8 uid); 79 | 80 | /** 81 | * \brief Issue a modbus write request 82 | * @param first_reg first register number 83 | * @param regs_to_write list of values to be written 84 | * @param uid Unit ID / Node to poll 85 | */ 86 | void modbus_request(const quint16 first_reg, std::vector &®s_to_write, const quint8 uid); 87 | 88 | /** 89 | * \brief Issue a raw modbus PDU 90 | * @param pdu Modbus raw PDU to send 91 | * @param length length of PDU 92 | * @param fc function code 93 | * @param uid Unit ID / Node to poll 94 | */ 95 | void modbus_request(const quint8 *pdu, const quint8 length, const qint8 fc, const quint8 uid); 96 | 97 | /** 98 | * \brief Obtain results from a previous modbus transaction. 99 | * \note 100 | * vector will contain a character array (quint8) if the request was 101 | * device ID or a custom response. Values promoted from quint8 to 102 | * quint16. This call results in the value being std::move'd from 103 | * internal storage and can be made only once for each complete 104 | * signal. 105 | * 106 | * @return register list 107 | */ 108 | [[nodiscard]] std::vector modbus_result(); 109 | 110 | /** 111 | * \brief Get the starting register for the most recent request call. 112 | * \note 113 | * This call and the next can't be const because they invoke lock. 114 | * \sa get_unit_id 115 | * @return register number (0 = id, 0xffff = custom) 116 | */ 117 | [[nodiscard]] quint16 get_start_reg(); 118 | 119 | /** 120 | * \brief Get the node (unit id) that the most recent request was sent to. 121 | * @return device / node / unit ID 122 | */ 123 | [[nodiscard]] quint8 get_unit_id(); 124 | 125 | virtual void run() override; 126 | 127 | ~ModbusThread() override; 128 | 129 | signals: 130 | 131 | /** 132 | * \brief Emit poll exception. 133 | * @param error_code Modbus error code reported 134 | */ 135 | void modbus_error(const int error_code); 136 | 137 | /** 138 | * \brief Emit poll complete. 139 | */ 140 | void complete(); 141 | 142 | private: 143 | 144 | /** 145 | * \brief Assemble a write multiple registers request 146 | * @return result from modbus call 147 | */ 148 | int do_write_request(); 149 | 150 | /** 151 | * \brief Consolidate the logic for custom requests (Modbus/TCP). 152 | * @return result code 153 | */ 154 | int do_custom_request_tcp(); 155 | 156 | const QString m_host; 157 | const quint16 m_port; 158 | modbus_t *m_ctx=nullptr; 159 | QMutex m_mx; 160 | QWaitCondition m_cond; 161 | bool m_quit=false; 162 | bool m_write_request=false; 163 | const quint8 *m_raw_request=nullptr; 164 | 165 | std::vector m_regs; 166 | quint16 m_reg_number=0; 167 | quint16 m_count=0; 168 | quint8 m_node=0; 169 | }; 170 | 171 | 172 | #endif // MODBUSTHREAD_H 173 | -------------------------------------------------------------------------------- /configure_trend.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file configure_trend.cpp 3 | * \brief Trend graph configuration dialog box 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | */ 25 | 26 | 27 | // c++ includes 28 | #include // QPushButton 29 | #include // QMessageBox 30 | #include // QStringList 31 | 32 | // C includes 33 | /* -none- */ 34 | 35 | // project includes 36 | #include "configure_trend.h" // local include 37 | 38 | 39 | namespace { 40 | const auto g_max_points = 1000000; // some arbitrary limit. 41 | } 42 | 43 | 44 | ConfigureTrend::ConfigureTrend(QWidget *const parent, 45 | const double min, 46 | const double max, 47 | const int n_points) : 48 | BaseDialog(parent, false), 49 | m_ui{new Ui::ConfigureTrend()}, 50 | m_initial_points{n_points}, 51 | m_initial_min{min}, 52 | m_initial_max{max}, 53 | m_first_edit{true} 54 | { 55 | m_ui->setupUi(this); 56 | m_ui->MinInput->setText(QString::number(min)); 57 | m_ui->MaxInput->setText(QString::number(max)); 58 | } 59 | 60 | 61 | void ConfigureTrend::setupUi() 62 | { 63 | m_ui->HistoryInput->setText(QString::number(m_initial_points)); 64 | QPushButton *btn1 = m_ui->buttonBox->button(QDialogButtonBox::Cancel); 65 | add_icon_to_button(btn1, QStyle::SP_DialogCloseButton); 66 | 67 | QPushButton *btn2 = m_ui->buttonBox->button(QDialogButtonBox::Ok); 68 | add_icon_to_button(btn2, QStyle::SP_DialogApplyButton); 69 | } 70 | 71 | 72 | void ConfigureTrend::on_HistoryInput_textEdited() 73 | { 74 | if (m_first_edit) { 75 | m_first_edit = false; 76 | auto msg_box = QMessageBox(this); 77 | msg_box.setIcon(QMessageBox::Warning); 78 | msg_box.setStandardButtons(QMessageBox::Ok|QMessageBox::Cancel); 79 | msg_box.setText(tr("Change history size")); 80 | msg_box.setInformativeText(tr( 81 | "Notice:\n" 82 | "Changing the history size will clear currently captured data.")); 83 | if (msg_box.exec() != QMessageBox::Ok) { 84 | m_ui->HistoryInput->setText(QString::number(m_initial_points)); 85 | } 86 | } 87 | } 88 | 89 | 90 | std::pair ConfigureTrend::get_min_max() const 91 | { 92 | bool okmin, okmax; 93 | auto min = m_ui->MinInput->text().toDouble(&okmin); 94 | if (!okmin) { 95 | min = m_initial_min; 96 | } 97 | 98 | auto max = m_ui->MaxInput->text().toDouble(&okmax); 99 | if (!okmax) { 100 | max = m_initial_max; 101 | } 102 | 103 | if (min >= max) { 104 | min = m_initial_min; 105 | max = m_initial_max; 106 | } 107 | 108 | return { min, max }; 109 | } 110 | 111 | 112 | std::optional ConfigureTrend::get_num_points() const 113 | { 114 | bool ok; 115 | const auto new_points = m_ui->HistoryInput->text().toInt(&ok); 116 | if ((m_initial_points == new_points) || !ok || (new_points < 3)) { 117 | return std::optional(); 118 | } 119 | 120 | return new_points; 121 | } 122 | 123 | 124 | void ConfigureTrend::accept() 125 | { 126 | QStringList error_text; 127 | bool okmin, okmax; 128 | const auto min = m_ui->MinInput->text().toDouble(&okmin); 129 | if (!okmin) { 130 | error_text << tr("Invalid minimum value specified."); 131 | } 132 | 133 | const auto max = m_ui->MaxInput->text().toDouble(&okmax); 134 | if (!okmax) { 135 | error_text << tr("Invalid maximum value specified."); 136 | } 137 | 138 | if (okmin && okmax && (min >= max)) { 139 | error_text << tr("Max value must be greater than min value."); 140 | } 141 | 142 | bool ok; 143 | const auto points = m_ui->HistoryInput->text().toInt(&ok); 144 | if (!ok) { 145 | error_text << tr("Invalid number of points specified."); 146 | } else if (points < 3 || points > g_max_points) { 147 | error_text << tr("Points must be between 3 and %1").arg(g_max_points); 148 | } 149 | 150 | if (error_text.size() > 0) { 151 | auto error_box = QMessageBox(this); 152 | error_box.setIcon(QMessageBox::Critical); 153 | error_box.setStandardButtons(QMessageBox::Ok); 154 | error_box.setText(tr("Invalid trend configuration specified")); 155 | error_box.setInformativeText(tr("Errors were detected")); 156 | error_box.setWindowTitle(tr("Invalid Configuration")); 157 | error_box.setDetailedText(error_text.join('\n')); 158 | error_box.exec(); 159 | } else { 160 | BaseDialog::accept(); 161 | } 162 | } 163 | 164 | 165 | ConfigureTrend::operator bool() const 166 | { 167 | return m_ui->DynamicCheck->isChecked(); 168 | } 169 | 170 | 171 | ConfigureTrend::~ConfigureTrend() 172 | { 173 | delete m_ui; 174 | } 175 | -------------------------------------------------------------------------------- /configure_trend_line.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file configure_trend_line.cpp 3 | * \brief Trend configuration dialog box 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | */ 25 | 26 | 27 | // c++ includes 28 | #include // QColorDialog 29 | #include // QMessageBox 30 | #include // operator% 31 | 32 | // C includes 33 | /* -none- */ 34 | 35 | // project includes 36 | #include "configure_trend_line.h" // local include 37 | #include "exceptions.h" // AppException 38 | 39 | 40 | ConfigureTrendLine::ConfigureTrendLine(TrendWindow *const parent) : 41 | BaseDialog(parent, false), 42 | m_ui{new Ui::ConfigureTrendDialog()}, 43 | m_display_color{Qt::black}, 44 | m_parent{parent}, 45 | m_trend{nullptr} 46 | { 47 | m_ui->setupUi(this); 48 | } 49 | 50 | 51 | ConfigureTrendLine::ConfigureTrendLine(TrendLine *const parent) : 52 | ConfigureTrendLine(parent->m_parent) 53 | { 54 | m_trend = parent; 55 | m_display_color = parent->m_pen_color; 56 | } 57 | 58 | 59 | void ConfigureTrendLine::setupUi() 60 | { 61 | update_color_labels(); 62 | 63 | add_icon_to_button(m_ui->Accept, QStyle::SP_DialogApplyButton); 64 | add_icon_to_button(m_ui->Delete, QStyle::SP_BrowserStop); 65 | add_icon_to_button(m_ui->Cancel, QStyle::SP_DialogCloseButton); 66 | 67 | if (nullptr == m_trend) { 68 | m_ui->Accept->setText(tr("Create")); 69 | m_ui->Delete->setHidden(true); 70 | } else { 71 | m_ui->RegEdit->setText(QString::number(m_trend->m_reg_number)); 72 | m_ui->RegEdit->setEnabled(false); 73 | m_ui->NodeEdit->setValue(int(m_trend->m_device_id)); 74 | m_ui->NodeEdit->setEnabled(false); 75 | m_ui->SignedEdit->setChecked(m_trend->m_signed_value); 76 | m_ui->MultBox->setText(QString::number(m_trend->m_mult)); 77 | m_ui->OffsetBox->setText(QString::number(m_trend->m_offset)); 78 | } 79 | } 80 | 81 | 82 | void ConfigureTrendLine::on_Accept_pressed() 83 | { 84 | bool ok; 85 | const auto reg = m_ui->RegEdit->text().toInt(&ok); 86 | QStringList error_text; 87 | if (!ok || (reg <= 0) || (reg >= 50000) || (reg >= 20000 && reg <= 30000)) { 88 | error_text << tr("Illegal register number"); 89 | } 90 | 91 | const auto m = m_ui->MultBox->text().toDouble(&ok); 92 | if (!ok) { 93 | error_text << tr("Illegal multiply value"); 94 | } 95 | 96 | const auto b = m_ui->OffsetBox->text().toDouble(&ok); 97 | if (!ok) { 98 | error_text << tr("Illegal offset value"); 99 | } 100 | 101 | if (error_text.size() > 0) { 102 | auto error_box = QMessageBox(this); 103 | error_box.setIcon(QMessageBox::Critical); 104 | error_box.setStandardButtons(QMessageBox::Ok); 105 | error_box.setText(tr("Invalid trend configuration specified")); 106 | error_box.setInformativeText(tr("Errors were detected")); 107 | error_box.setWindowTitle(tr("Invalid Configuration")); 108 | error_box.setDetailedText(error_text.join('\n')); 109 | error_box.exec(); 110 | return; 111 | } 112 | 113 | if (nullptr != m_trend) { 114 | m_trend->configure(m, b, m_ui->SignedEdit->isChecked()); 115 | m_trend->set_color(m_display_color); 116 | } 117 | 118 | accept(); 119 | } 120 | 121 | 122 | void ConfigureTrendLine::on_Cancel_pressed() 123 | { 124 | reject(); 125 | } 126 | 127 | 128 | void ConfigureTrendLine::on_Delete_pressed() 129 | { 130 | reject(); 131 | m_parent->remove_trend(quint32(*m_trend)); 132 | } 133 | 134 | 135 | void ConfigureTrendLine::on_ColorButton_pressed() 136 | { 137 | auto dlg = QColorDialog(m_display_color, this); 138 | if (dlg.exec() != 0) { 139 | m_display_color = dlg.selectedColor(); 140 | update_color_labels(); 141 | } 142 | } 143 | 144 | 145 | void ConfigureTrendLine::update_color_labels() 146 | { 147 | m_ui->ColorText->setText(m_display_color.name()); 148 | m_ui->ColorSample->setStyleSheet("QLabel {" 149 | "color: " % m_display_color.name() % 150 | "; background-color: #FFFFFF;}"); 151 | } 152 | 153 | 154 | TrendLine* ConfigureTrendLine::create_trend() 155 | { 156 | if (nullptr != m_trend) { 157 | throw AppException(tr("Error, creating duplicate TrendLine")); 158 | } 159 | 160 | const auto m = m_ui->MultBox->text().toDouble(); 161 | const auto b = m_ui->OffsetBox->text().toDouble(); 162 | const auto node = m_ui->NodeEdit->value(); 163 | const auto reg = m_ui->RegEdit->text().toInt(); 164 | auto trend = new TrendLine(m_parent, quint16(reg), quint8(node)); 165 | trend->configure(m, b, m_ui->SignedEdit->isChecked()); 166 | trend->set_color(m_display_color); 167 | m_trend = trend; 168 | 169 | return trend; 170 | } 171 | 172 | 173 | ConfigureTrendLine::operator quint32() const 174 | { 175 | return m_parent->get_key(quint16(m_ui->RegEdit->text().toUInt()), 176 | quint8(m_ui->NodeEdit->value())); 177 | } 178 | 179 | 180 | ConfigureTrendLine::~ConfigureTrendLine() 181 | { 182 | delete m_ui; 183 | } 184 | -------------------------------------------------------------------------------- /trend_window.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file trend_window.h 3 | * \brief Graphing / Trend support 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | * 25 | * \section DESCRIPTION 26 | * 27 | * The project may include 1 trend window with many trend lines updated from 28 | * polling data. 29 | */ 30 | 31 | #ifndef TREND_WINDOW_H 32 | #define TREND_WINDOW_H 33 | 34 | // c++ includes 35 | #include // QList 36 | #include // QHBoxLayout 37 | #include // QScrollArea 38 | #include // QPushButton 39 | #include // QGroupBox 40 | #include // QMenu 41 | #include // QDomElement 42 | #include // QDomDocument 43 | #include // QCustomPlot 44 | #include // std::chrono 45 | #include // std::unordered_map 46 | 47 | // C includes 48 | /* -none- */ 49 | 50 | // project includes 51 | #include "base_dialog.h" 52 | 53 | 54 | // ///////////////////////////////////////////////////////////////////////////// 55 | // Forward declarations 56 | // ///////////////////////////////////////////////////////////////////////////// 57 | class TrendLine; 58 | 59 | 60 | /** 61 | * \brief Graphing window 62 | */ 63 | class TrendWindow : public BaseDialog 64 | { 65 | // Allow individual trends to access internal widgets to self-configure. 66 | friend class TrendLine; 67 | 68 | Q_OBJECT 69 | 70 | public: 71 | 72 | /** 73 | * \brief constructor 74 | * @param parent parent QObject owner 75 | */ 76 | TrendWindow(QWidget *const parent); 77 | 78 | /** 79 | * \brief get hash key from reg, node 80 | * @param reg register number 81 | * @param node node/device ID 82 | */ 83 | [[nodiscard]] quint32 get_key(const quint16 reg, const quint8 node) const noexcept; 84 | 85 | /** 86 | * @brief remove_trend 87 | * \note 88 | * This function shall only be called by the ConfigureTrendLine dialog box. 89 | * 90 | * @param trend_key 91 | */ 92 | void remove_trend(const quint32 trend_key); 93 | 94 | /** 95 | * @brief Update Min/Max value 96 | * \note 97 | * This function is only called by connected trends 98 | * 99 | * @param value new value 100 | */ 101 | void update_min_max(const double value) noexcept; 102 | 103 | /** 104 | * @brief save_configuration 105 | * @param root 106 | * @return 107 | */ 108 | QDomElement save_configuration(QDomDocument &root) const; 109 | 110 | /** 111 | * @brief load_configuration 112 | * @param node 113 | * @return 114 | */ 115 | bool load_configuration(const QDomElement &node); 116 | 117 | public slots: 118 | 119 | virtual void on_new_value(const quint16 reg, const quint16 value, const quint8 unit_id) override; 120 | 121 | protected: 122 | 123 | /** 124 | * @brief Re-draw the graph from class data 125 | */ 126 | void redraw_graph(); 127 | 128 | /** 129 | * @brief Scan current data set to see if it's complete. If it is, update 130 | * and re-draw the graph. 131 | */ 132 | void scan(); 133 | 134 | /** 135 | * @brief Add trend to the graph 136 | * @param trend newly created trend 137 | */ 138 | void add_trend(TrendLine *const trend); 139 | 140 | virtual void setupUi() override; 141 | 142 | QCustomPlot *const m_plot; 143 | std::unordered_map m_data; 144 | QList m_timestamps; 145 | const std::chrono::time_point m_start_time; 146 | 147 | private slots: 148 | 149 | /** 150 | * @brief Action when the "Add new trend" button is clicked 151 | */ 152 | void on_add_button_clicked(); 153 | 154 | /** 155 | * @brief Action taken when the "Configure Graph" menu item is selected 156 | */ 157 | void on_configure_triggered(); 158 | 159 | /** 160 | * @brief Action taken when the "Save data" menu item is selected 161 | */ 162 | void on_save_triggered(); 163 | 164 | /** 165 | * @brief Action taken when the "Save image" menu item is selected 166 | */ 167 | void on_capture_triggered(); 168 | 169 | private: 170 | 171 | /** 172 | * \brief Save register data to CSV file 173 | * @param path absolute path and file name to save to 174 | */ 175 | bool save_register_set(const QString &path); 176 | 177 | /** 178 | * @brief Resize the history 179 | * @param points new number of points to include in history 180 | * @param redraw set ``false`` to skip graph redraw 181 | */ 182 | void resize_history(const int points, bool redraw=true); 183 | 184 | QHBoxLayout *const m_layout; 185 | QGroupBox *const m_legend; 186 | QVBoxLayout *const m_legend_layout; 187 | QScrollArea *const m_button_area; 188 | QWidget *const m_scroll_container; 189 | QVBoxLayout *const m_scroll_layout; 190 | QPushButton *const m_add_button; 191 | QPushButton *const m_configure_button; 192 | QMenu *const m_main_menu; 193 | bool m_fixed_limits = false; 194 | 195 | double m_miny; 196 | double m_maxy; 197 | }; 198 | 199 | 200 | #endif // TREND_WINDOW_H 201 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 297 10 | 174 11 | 12 | 13 | 14 | QModbusTool 15 | 16 | 17 | 18 | :/QModbusTool.ico:/QModbusTool.ico 19 | 20 | 21 | 22 | 23 | 24 | 25 | 502 26 | 27 | 28 | 29 | 30 | 31 | 32 | 127.0.0.1 33 | 34 | 35 | 36 | 37 | 38 | 39 | Slave IP Address: 40 | 41 | 42 | 43 | 44 | 45 | 46 | Slave Port: 47 | 48 | 49 | 50 | 51 | 52 | 53 | 3000 54 | 55 | 56 | 57 | 58 | 59 | 60 | Request Timeout (ms): 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | true 69 | 70 | 71 | 72 | 73 | 74 | 0 75 | 0 76 | 297 77 | 27 78 | 79 | 80 | 81 | 82 | Window 83 | 84 | 85 | 86 | New 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | File 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | false 111 | 112 | 113 | Poll 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | Connect 127 | 128 | 129 | 130 | 131 | Coils 132 | 133 | 134 | 135 | 136 | Inputs 137 | 138 | 139 | 140 | 141 | Input Registers 142 | 143 | 144 | 145 | 146 | Holding Registers 147 | 148 | 149 | 150 | 151 | Save Session 152 | 153 | 154 | 155 | 156 | Restore Session 157 | 158 | 159 | 160 | 161 | true 162 | 163 | 164 | true 165 | 166 | 167 | Continuous 168 | 169 | 170 | 171 | 172 | Once 173 | 174 | 175 | 176 | 177 | Read Metadata 178 | 179 | 180 | 181 | 182 | true 183 | 184 | 185 | Load Register Data 186 | 187 | 188 | 189 | 190 | Trend 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /metadata_wrapper.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file metadata_wrapper.cpp 3 | * \brief Wrapper singleton around the Read Metadata plugin shared object. 4 | * \copyright 5 | * 2021 Andrew Buettner (ABi) 6 | * 7 | * \section LICENSE 8 | * 9 | * QModbusTool - A QT Based Modbus Client 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License 13 | * as published by the Free Software Foundation; either version 2 14 | * of the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program; if not, write to the Free Software 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | */ 25 | 26 | // c++ includes 27 | #include // operator % 28 | #include // QCoreApplication 29 | 30 | 31 | // C includes 32 | #include // modbus_plugin: all symbols and definitions in plugin 33 | 34 | // project includes 35 | #include "metadata_wrapper.h" // local include 36 | #include "exceptions.h" // AppException 37 | 38 | 39 | using namespace std::string_view_literals; 40 | 41 | 42 | namespace { 43 | const auto g_dll_name = "mod_plugin.so"sv; 44 | } 45 | 46 | 47 | MetadataWrapper::MetadataWrapper(const QString &lib_path) : 48 | QObject(nullptr), 49 | m_dll_reference{new QLibrary(lib_path, this)} 50 | { 51 | m_dll_reference->load(); 52 | } 53 | 54 | 55 | MetadataWrapper* MetadataWrapper::get_instance() 56 | { 57 | static MetadataWrapper *inst = nullptr; 58 | if (nullptr == inst) { 59 | const QString dll_path = QCoreApplication::applicationDirPath() % 60 | QChar('/') % 61 | QString(g_dll_name.data()); 62 | inst = new MetadataWrapper(dll_path); 63 | } 64 | 65 | return inst; 66 | } 67 | 68 | 69 | bool MetadataWrapper::loaded() const 70 | { 71 | return m_dll_reference->isLoaded(); 72 | } 73 | 74 | 75 | std::shared_ptr MetadataWrapper::create_request(const quint16 reg_number) 76 | { 77 | if (!loaded()) { 78 | throw AppException(tr("Plugin unavailable")); 79 | } 80 | 81 | quint8 fc; 82 | // auto p_fn = reinterpret_cast(dlsym(m_dll_reference, CREATE_SYMBOL)); 83 | auto p_fn = reinterpret_cast(m_dll_reference->resolve(CREATE_SYMBOL)); 84 | auto inst = p_fn(reg_number, &fc); 85 | if (nullptr == inst) { 86 | throw AppException(tr("Illegal request: ") % QString::number(reg_number)); 87 | } 88 | 89 | return std::make_shared(reg_number, inst, fc); 90 | } 91 | 92 | 93 | QPair MetadataWrapper::encode_request(std::shared_ptr request) 94 | { 95 | // auto p_fn = reinterpret_cast(dlsym(m_dll_reference, ENCODE_SYMBOL)); 96 | auto p_fn = reinterpret_cast(m_dll_reference->resolve(ENCODE_SYMBOL)); 97 | 98 | auto length = p_fn(request->m_request_instance, request->m_request.data()); 99 | return {request->m_request.data(), quint8(length)}; 100 | } 101 | 102 | 103 | void MetadataWrapper::decode_response(std::shared_ptr request, const std::vector &data) 104 | { 105 | // auto p_fn = reinterpret_cast(dlsym(m_dll_reference, DECODE_SYMBOL)); 106 | auto p_fn = reinterpret_cast(m_dll_reference->resolve(DECODE_SYMBOL)); 107 | 108 | if (p_fn(request->m_request_instance, data.data(), uint8_t(data.size())) == 0) { 109 | decode_labels(request.get()); 110 | decode_defaults(request.get()); 111 | decode_encoding(request.get()); 112 | decode_limits(request.get()); 113 | } 114 | } 115 | 116 | 117 | void MetadataWrapper::dispose_metadata(const Metadata *const m) 118 | { 119 | // auto p_fn = reinterpret_cast(dlsym(m_dll_reference, RELEASE_SYMBOL)); 120 | auto p_fn = reinterpret_cast(m_dll_reference->resolve(RELEASE_SYMBOL)); 121 | p_fn(m->m_request_instance); 122 | } 123 | 124 | 125 | void MetadataWrapper::decode_labels(Metadata *const request) 126 | { 127 | // auto p_fn = reinterpret_cast