├── 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 |
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