diff --git a/README.md b/README.md index 9ba8aa2..8e0f1f8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # Open ModScan Open ModScan is a free implimentation of modbus master (client) utility for modbus-tcp and modbus-rtu protocols. -![изображение](https://github.com/sanny32/OpenModScan/assets/13627951/aa912ece-4b76-44b4-9523-5b0b0156a64b) +![image](https://github.com/sanny32/OpenModScan/assets/13627951/aa912ece-4b76-44b4-9523-5b0b0156a64b) -![изображение](https://github.com/sanny32/OpenModScan/assets/13627951/77bee5d8-09a8-4845-8d64-02b7bf3cf592) - +![image](https://github.com/sanny32/OpenModScan/assets/13627951/77bee5d8-09a8-4845-8d64-02b7bf3cf592) ## Features @@ -26,18 +25,28 @@ Registers 0x06 - Write Single Register 0x10 - Write Multiple Registers 0x16 - Mask Write Register + +Modbus Logging + +![image](https://github.com/sanny32/OpenModScan/assets/13627951/de14b977-08ba-460c-814c-7affd0d88f91) + ## Extended Featues - Modbus Address Scan +- Modbus Address Scan - ![image](https://user-images.githubusercontent.com/13627951/226310086-4160c8c7-503a-48c0-bdf8-f5bb970592d3.png) + ![image](https://github.com/sanny32/OpenModScan/assets/13627951/8989fbde-09f1-435c-a9a7-31e27a0ec576) - Modbus Scanner (supports both Modbus RTU and Modbus TCP scanning) +- Modbus Scanner (supports both Modbus RTU and Modbus TCP scanning) - - ![image](https://github.com/sanny32/OpenModScan/assets/13627951/056de3b1-026b-498b-914c-4d1e565067bb) + ![image](https://github.com/sanny32/OpenModScan/assets/13627951/768a9c69-0201-4c1e-bad5-4fa9b24d9553) + +- Modbus Message Parser + ![image](https://github.com/sanny32/OpenModScan/assets/13627951/86a82340-015e-4ee9-a483-b5ab83527cc1) +- Modbus User Message + + ![image](https://github.com/sanny32/OpenModScan/assets/13627951/5ee959a2-9c5c-4ffe-8056-d6205e3b5aa3) ## Building Now building is available with Qt/qmake (version 5.15 and above) or Qt Creator. Supports both OS Microsoft Windows and Linux. diff --git a/omodscan/controls/bytelistlineedit.cpp b/omodscan/controls/bytelistlineedit.cpp deleted file mode 100644 index 5e64a79..0000000 --- a/omodscan/controls/bytelistlineedit.cpp +++ /dev/null @@ -1,210 +0,0 @@ -#include -#include "bytelistlineedit.h" - -/// -/// \brief ByteListLineEdit::ByteListLineEdit -/// \param parent -/// -ByteListLineEdit::ByteListLineEdit(QWidget* parent) - :QLineEdit(parent) -{ - setInputMode(DecMode); - - connect(this, &QLineEdit::editingFinished, this, &ByteListLineEdit::on_editingFinished); - connect(this, &QLineEdit::textChanged, this, &ByteListLineEdit::on_textChanged); -} - -/// -/// \brief ByteListLineEdit::ByteListLineEdit -/// \param mode -/// \param parent -/// -ByteListLineEdit::ByteListLineEdit(InputMode mode, QWidget *parent) - :QLineEdit(parent) -{ - setInputMode(mode); - - connect(this, &QLineEdit::editingFinished, this, &ByteListLineEdit::on_editingFinished); - connect(this, &QLineEdit::textChanged, this, &ByteListLineEdit::on_textChanged); -} - -/// -/// \brief ByteListLineEdit::value -/// \return -/// -QByteArray ByteListLineEdit::value() const -{ - return _value; -} - -/// -/// \brief ByteListLineEdit::setValue -/// \param value -/// -void ByteListLineEdit::setValue(const QByteArray& value) -{ - switch(_inputMode) - { - case DecMode: - { - QStringList text; - for(auto&& v : value) - text.push_back(QString::number((quint8)v)); - QLineEdit::setText(text.join(',')); - QLineEdit::setCursorPosition(0); - } - break; - - case HexMode: - { - QStringList text; - for(auto&& v : value) - text.push_back(QString("%1").arg((quint8)v, 2, 16, QLatin1Char('0'))); - QLineEdit::setText(text.join(',').toUpper()); - QLineEdit::setCursorPosition(0); - } - break; - } - - if(value != _value) - { - _value = value; - emit valueChanged(_value); - } -} - -/// -/// \brief ByteListLineEdit::inputMode -/// \return -/// -ByteListLineEdit::InputMode ByteListLineEdit::inputMode() const -{ - return _inputMode; -} - -/// -/// \brief ByteListLineEdit::setInputMode -/// \param mode -/// -void ByteListLineEdit::setInputMode(InputMode mode) -{ - _inputMode = mode; - - setValidator(nullptr); - switch(mode) - { - case DecMode: - setValidator(new QRegularExpressionValidator(QRegularExpression("(?:[0-9]{1,2}[,]{0,1})*"), this)); - break; - - case HexMode: - setValidator(new QRegularExpressionValidator(QRegularExpression("(?:[0-9a-fA-F]{1,2}[,]{0,1})*"), this)); - break; - } - setValue(_value); -} - -/// -/// \brief ByteListLineEdit::setText -/// \param text -/// -void ByteListLineEdit::setText(const QString& text) -{ - QLineEdit::setText(text); - updateValue(); -} - -/// -/// \brief ByteListLineEdit::focusOutEvent -/// \param event -/// -void ByteListLineEdit::focusOutEvent(QFocusEvent* event) -{ - updateValue(); - QLineEdit::focusOutEvent(event); -} - -/// -/// \brief ByteListLineEdit::on_editingFinished -/// -void ByteListLineEdit::on_editingFinished() -{ - updateValue(); -} - -/// -/// \brief ByteListLineEdit::on_textChanged -/// \param text -/// -void ByteListLineEdit::on_textChanged(const QString& text) -{ - QByteArray value; - switch(_inputMode) - { - case DecMode: - { - for(auto&& s : text.split(',')) - { - bool ok; - const quint8 v = s.toUInt(&ok); - if(ok) value.push_back(v); - } - } - break; - - case HexMode: - { - for(auto&& s : text.split(',')) - { - bool ok; - const quint8 v = s.toUInt(&ok, 16); - if(ok) value.push_back(v); - } - } - break; - } - - if(!value.isEmpty() && value != _value) - { - _value = value; - emit valueChanged(_value); - } -} - -/// -/// \brief ByteListLineEdit::updateValue -/// -void ByteListLineEdit::updateValue() -{ - QByteArray value; - switch(_inputMode) - { - case DecMode: - { - for(auto&& s : text().split(',')) - { - bool ok; - const quint8 v = s.toUInt(&ok); - if(ok) value.push_back(v); - } - - if(!value.isEmpty()) setValue(value); - else setValue(_value); - } - break; - - case HexMode: - { - for(auto&& s : text().split(',')) - { - bool ok; - const quint8 v = s.toUInt(&ok, 16); - if(ok) value.push_back(v); - } - - if(!value.isEmpty()) setValue(value); - else setValue(_value); - } - break; - } -} diff --git a/omodscan/controls/bytelistlineedit.h b/omodscan/controls/bytelistlineedit.h deleted file mode 100644 index cf7711c..0000000 --- a/omodscan/controls/bytelistlineedit.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef BYTELISTLINEEDIT_H -#define BYTELISTLINEEDIT_H - -#include - -/// -/// \brief The ByteListLineEdit class -/// -class ByteListLineEdit : public QLineEdit -{ - Q_OBJECT - -public: - enum InputMode - { - DecMode = 0, - HexMode - }; - - explicit ByteListLineEdit(QWidget* parent = nullptr); - explicit ByteListLineEdit(InputMode mode, QWidget *parent = nullptr); - - QByteArray value() const; - void setValue(const QByteArray& value); - - InputMode inputMode() const; - void setInputMode(InputMode mode); - - void setText(const QString& text); - -signals: - void valueChanged(const QByteArray& value); - -protected: - void focusOutEvent(QFocusEvent*) override; - -private slots: - void on_editingFinished(); - void on_textChanged(const QString& text); - -private: - void updateValue(); - -private: - InputMode _inputMode; - QByteArray _value; -}; - -#endif // BYTELISTLINEEDIT_H diff --git a/omodscan/controls/bytelisttextedit.cpp b/omodscan/controls/bytelisttextedit.cpp new file mode 100644 index 0000000..66dbfae --- /dev/null +++ b/omodscan/controls/bytelisttextedit.cpp @@ -0,0 +1,283 @@ +#include +#include +#include "formatutils.h" +#include "bytelisttextedit.h" + +/// +/// \brief ByteListTextEdit::ByteListTextEdit +/// \param parent +/// +ByteListTextEdit::ByteListTextEdit(QWidget* parent) + :QPlainTextEdit(parent) + ,_separator(' ') + ,_validator(nullptr) +{ + setInputMode(DecMode); + connect(this, &QPlainTextEdit::textChanged, this, &ByteListTextEdit::on_textChanged); +} + +/// +/// \brief ByteListTextEdit::ByteListTextEdit +/// \param mode +/// \param parent +/// +ByteListTextEdit::ByteListTextEdit(InputMode mode, QWidget *parent) + :QPlainTextEdit(parent) + ,_separator(' ') + ,_validator(nullptr) +{ + setInputMode(mode); + connect(this, &QPlainTextEdit::textChanged, this, &ByteListTextEdit::on_textChanged); +} + +/// +/// \brief ByteListTextEdit::value +/// \return +/// +QByteArray ByteListTextEdit::value() const +{ + return _value; +} + +/// +/// \brief ByteListTextEdit::setValue +/// \param value +/// +void ByteListTextEdit::setValue(const QByteArray& value) +{ + switch(_inputMode) + { + case DecMode: + { + const auto text = formatByteArray(DataDisplayMode::Decimal, value); + if(text != toPlainText()) + setPlainText(formatByteArray(DataDisplayMode::Decimal, value)); + } + break; + + case HexMode: + { + const auto text = formatByteArray(DataDisplayMode::Hex, value); + if(text != toPlainText()) + setPlainText(formatByteArray(DataDisplayMode::Hex, value)); + } + break; + } + + if(value != _value) + { + _value = value; + emit valueChanged(_value); + } +} + +/// +/// \brief ByteListTextEdit::inputMode +/// \return +/// +ByteListTextEdit::InputMode ByteListTextEdit::inputMode() const +{ + return _inputMode; +} + +/// +/// \brief ByteListTextEdit::setInputMode +/// \param mode +/// +void ByteListTextEdit::setInputMode(InputMode mode) +{ + _inputMode = mode; + + if(_validator) + { + delete _validator; + _validator = nullptr; + } + + const auto sep = (_separator == ' ')? "\\s" : QString(_separator); + switch(_inputMode) + { + case DecMode: + _validator =new QRegularExpressionValidator(QRegularExpression("(?:[0-9]{1,2}[" + sep + "]{0,1})*"), this); + break; + + case HexMode: + _validator = new QRegularExpressionValidator(QRegularExpression("(?:[0-9a-fA-F]{1,2}[" + sep + "]{0,1})*"), this); + break; + } + + setValue(_value); +} + +/// +/// \brief ByteListTextEdit::text +/// \return +/// +QString ByteListTextEdit::text() const +{ + return toPlainText(); +} + +/// +/// \brief ByteListTextEdit::setText +/// \param text +/// +void ByteListTextEdit::setText(const QString& text) +{ + setPlainText(text); + updateValue(); +} + +/// +/// \brief ByteListTextEdit::focusOutEvent +/// \param event +/// +void ByteListTextEdit::focusOutEvent(QFocusEvent* e) +{ + updateValue(); + QPlainTextEdit::focusOutEvent(e); +} + +/// +/// \brief ByteListTextEdit::keyPressEvent +/// \param e +/// +void ByteListTextEdit::keyPressEvent(QKeyEvent *e) +{ + if(!_validator) + { + QPlainTextEdit::keyPressEvent(e); + return; + } + + if(e->key() == Qt::Key_Enter || + e->key() == Qt::Key_Return) + { + return; + } + + int pos = 0; + auto text = toPlainText() + e->text(); + const auto state = _validator->validate(text, pos); + + if(state == QValidator::Acceptable || + e->key() == Qt::Key_Backspace || + e->key() == Qt::Key_Delete || + e->key() == Qt::Key_Space || + e->matches(QKeySequence::Cut) || + e->matches(QKeySequence::Copy) || + e->matches(QKeySequence::Paste) || + e->matches(QKeySequence::Undo) || + e->matches(QKeySequence::Redo) || + e->matches(QKeySequence::SelectAll)) + { + QPlainTextEdit::keyPressEvent(e); + } +} + +/// +/// \brief ByteListTextEdit::canInsertFromMimeData +/// \param source +/// \return +/// +bool ByteListTextEdit::canInsertFromMimeData(const QMimeData* source) const +{ + int pos = 0; + auto text = source->text().trimmed(); + const auto state = _validator->validate(text, pos); + + return state == QValidator::Acceptable; +} + +/// +/// \brief ByteListTextEdit::insertFromMimeData +/// \param source +/// +void ByteListTextEdit::insertFromMimeData(const QMimeData* source) +{ + int pos = 0; + auto text = source->text().trimmed(); + const auto state = _validator->validate(text, pos); + + if(state == QValidator::Acceptable) + { + QPlainTextEdit::insertFromMimeData(source); + updateValue(); + } +} + +/// +/// \brief ByteListTextEdit::on_textChanged +/// +void ByteListTextEdit::on_textChanged() +{ + QByteArray value; + switch(_inputMode) + { + case DecMode: + { + for(auto&& s : text().split(_separator)) + { + bool ok; + const quint8 v = s.trimmed().toUInt(&ok); + if(ok) value.push_back(v); + } + } + break; + + case HexMode: + { + for(auto&& s : text().split(_separator)) + { + bool ok; + const quint8 v = s.trimmed().toUInt(&ok, 16); + if(ok) value.push_back(v); + } + } + break; + } + + if(value != _value) + { + _value = value; + emit valueChanged(_value); + } +} + +/// +/// \brief ByteListTextEdit::updateValue +/// +void ByteListTextEdit::updateValue() +{ + QByteArray value; + switch(_inputMode) + { + case DecMode: + { + for(auto&& s : text().split(_separator)) + { + bool ok; + const quint8 v = s.trimmed().toUInt(&ok); + if(ok) value.push_back(v); + } + + if(!value.isEmpty()) setValue(value); + else setValue(_value); + } + break; + + case HexMode: + { + for(auto&& s : text().split(_separator)) + { + bool ok; + const quint8 v = s.trimmed().toUInt(&ok, 16); + if(ok) value.push_back(v); + } + + if(!value.isEmpty()) setValue(value); + else setValue(_value); + } + break; + } +} diff --git a/omodscan/controls/bytelisttextedit.h b/omodscan/controls/bytelisttextedit.h new file mode 100644 index 0000000..b2d3058 --- /dev/null +++ b/omodscan/controls/bytelisttextedit.h @@ -0,0 +1,59 @@ +#ifndef BYTELISTTEXTEDIT_H +#define BYTELISTTEXTEDIT_H + +#include +#include + +/// +/// \brief The ByteListTextEdit class +/// +class ByteListTextEdit : public QPlainTextEdit +{ + Q_OBJECT + +public: + enum InputMode + { + DecMode = 0, + HexMode + }; + + explicit ByteListTextEdit(QWidget* parent = nullptr); + explicit ByteListTextEdit(InputMode mode, QWidget *parent = nullptr); + + QByteArray value() const; + void setValue(const QByteArray& value); + + InputMode inputMode() const; + void setInputMode(InputMode mode); + + QString text() const; + void setText(const QString& text); + + bool isEmpty() const { + return text().isEmpty(); + } + +signals: + void valueChanged(const QByteArray& value); + +protected: + void focusOutEvent(QFocusEvent* e) override; + void keyPressEvent(QKeyEvent* e) override; + bool canInsertFromMimeData(const QMimeData* source) const override; + void insertFromMimeData(const QMimeData* source) override; + +private slots: + void on_textChanged(); + +private: + void updateValue(); + +private: + InputMode _inputMode; + QByteArray _value; + QChar _separator; + QValidator* _validator; +}; + +#endif // BYTELISTTEXTEDIT_H diff --git a/omodscan/controls/byteordercombobox.cpp b/omodscan/controls/byteordercombobox.cpp index 7918464..e50151d 100644 --- a/omodscan/controls/byteordercombobox.cpp +++ b/omodscan/controls/byteordercombobox.cpp @@ -10,7 +10,7 @@ ByteOrderComboBox::ByteOrderComboBox(QWidget *parent) addItem("Little-Endian", QVariant::fromValue(ByteOrder::LittleEndian)); addItem("Big-Endian", QVariant::fromValue(ByteOrder::BigEndian)); - connect(this, SIGNAL(currentIndexChanged(int)), this, SLOT(on_currentIndexChanged(int))); + connect(this, static_cast(&QComboBox::currentIndexChanged), this, &ByteOrderComboBox::on_currentIndexChanged); } /// diff --git a/omodscan/controls/functioncodecombobox.cpp b/omodscan/controls/functioncodecombobox.cpp index 3bde5da..458734f 100644 --- a/omodscan/controls/functioncodecombobox.cpp +++ b/omodscan/controls/functioncodecombobox.cpp @@ -1,3 +1,8 @@ +#include +#include +#include "modbusfunction.h" +#include "qhexvalidator.h" +#include "quintvalidator.h" #include "functioncodecombobox.h" /// @@ -5,9 +10,14 @@ /// \param parent /// FunctionCodeComboBox::FunctionCodeComboBox(QWidget *parent) - :QComboBox(parent) + : QComboBox(parent) + ,_currentFunc(QModbusPdu::Invalid) + ,_validator(nullptr) { - connect(this, SIGNAL(currentIndexChanged(int)), this, SLOT(on_currentIndexChanged(int))); + setInputMode(InputMode::DecMode); + + connect(this, &QComboBox::currentTextChanged, this, &FunctionCodeComboBox::on_currentTextChanged); + connect(this, static_cast(&QComboBox::currentIndexChanged), this, &FunctionCodeComboBox::on_currentIndexChanged); } /// @@ -16,7 +26,7 @@ FunctionCodeComboBox::FunctionCodeComboBox(QWidget *parent) /// QModbusPdu::FunctionCode FunctionCodeComboBox::currentFunctionCode() const { - return currentData().value(); + return _currentFunc; } /// @@ -25,104 +35,206 @@ QModbusPdu::FunctionCode FunctionCodeComboBox::currentFunctionCode() const /// void FunctionCodeComboBox::setCurrentFunctionCode(QModbusPdu::FunctionCode funcCode) { + _currentFunc = funcCode; + const auto idx = findData(funcCode); - setCurrentIndex(idx); + if(idx != -1) setCurrentIndex(idx); + else setCurrentText(formatFuncCode(funcCode)); } /// -/// \brief FunctionCodeComboBox::addItem -/// \param funcCode +/// \brief FunctionCodeComboBox::inputMode +/// \return /// -void FunctionCodeComboBox::addItem(QModbusPdu::FunctionCode funcCode) +FunctionCodeComboBox::InputMode FunctionCodeComboBox::inputMode() const { - switch(funcCode) - { - case QModbusPdu::ReadCoils: - QComboBox::addItem("01: READ COILS", QModbusPdu::ReadCoils); - break; - - case QModbusPdu::ReadDiscreteInputs: - QComboBox::addItem("02: READ INPUTS", QModbusPdu::ReadDiscreteInputs); - break; + return _inputMode; +} - case QModbusPdu::ReadHoldingRegisters: - QComboBox::addItem("03: READ HOLDING REGS", QModbusPdu::ReadHoldingRegisters); - break; +/// +/// \brief FunctionCodeComboBox::setInputMode +/// \param on +/// +void FunctionCodeComboBox::setInputMode(FunctionCodeComboBox::InputMode mode) +{ + _inputMode = mode; - case QModbusPdu::ReadInputRegisters: - QComboBox::addItem("04: READ INPUT REGS", QModbusPdu::ReadInputRegisters); - break; + if(!isEditable()) + return; - case QModbusPdu::WriteSingleCoil: - QComboBox::addItem("05: WRITE SINGLE COIL", QModbusPdu::WriteSingleCoil); - break; + if(_validator) + { + delete _validator; + _validator = nullptr; + } - case QModbusPdu::WriteSingleRegister: - QComboBox::addItem("06: WRITE SINGLE REG", QModbusPdu::WriteSingleRegister); + switch(mode) + { + case DecMode: + _validator = new QUIntValidator(0, 255, this); break; - case QModbusPdu::ReadExceptionStatus: - QComboBox::addItem("07: READ EXCEPTION STAT", QModbusPdu::ReadExceptionStatus); + case HexMode: + _validator = new QHexValidator(0, 0xFF, this); break; + } - case QModbusPdu::Diagnostics: - QComboBox::addItem("08: DIAGNOSTICS", QModbusPdu::Diagnostics); - break; + blockSignals(true); + lineEdit()->blockSignals(true); + for (int i = 0; i < count(); i++) + { + const auto funcCode = itemData(i).value(); + setItemText(i, QString("%1: %2").arg(formatFuncCode(funcCode), ModbusFunction(funcCode))); + } + blockSignals(false); + lineEdit()->blockSignals(false); - case QModbusPdu::GetCommEventCounter: - QComboBox::addItem("11: GET COMM EVENT CNT", QModbusPdu::GetCommEventCounter); - break; + update(); +} - case QModbusPdu::GetCommEventLog: - QComboBox::addItem("12: GET COMM EVENT LOG", QModbusPdu::GetCommEventLog); - break; +/// +/// \brief FunctionCodeComboBox::addItem +/// \param funcCode +/// +void FunctionCodeComboBox::addItem(QModbusPdu::FunctionCode funcCode) +{ + const auto code = formatFuncCode(funcCode); + QComboBox::addItem(QString("%1: %2").arg(code, ModbusFunction(funcCode)), funcCode); +} - case QModbusPdu::WriteMultipleCoils: - QComboBox::addItem("15: WRITE MULT COILS", QModbusPdu::WriteMultipleCoils); - break; +/// +/// \brief FunctionCodeComboBox::addItems +/// \param funcCodes +/// +void FunctionCodeComboBox::addItems(const QVector& funcCodes) +{ + for(auto&& item : funcCodes) + addItem(item); +} - case QModbusPdu::WriteMultipleRegisters: - QComboBox::addItem("16: WRITE MULT REGS", QModbusPdu::WriteMultipleRegisters); - break; +/// +/// \brief FunctionCodeComboBox::update +/// +void FunctionCodeComboBox::update() +{ + const auto idx = findData(_currentFunc, Qt::UserRole); + if(idx != -1) + setCurrentIndex(idx); + else + setCurrentText(formatFuncCode(_currentFunc)); +} - case QModbusPdu::ReportServerId: - QComboBox::addItem("17: REPORT SLAVE ID", QModbusPdu::ReportServerId); - break; +/// +/// \brief FunctionCodeComboBox::formatFuncCode +/// \param funcCode +/// \return +/// +QString FunctionCodeComboBox::formatFuncCode(QModbusPdu::FunctionCode funcCode) const +{ + switch(_inputMode) + { + case DecMode: + return QString("%1").arg(QString::number(funcCode), 2, '0'); - case QModbusPdu::ReadFileRecord: - QComboBox::addItem("20: READ FILE RECORD", QModbusPdu::ReadFileRecord); - break; + case HexMode: + return (isEditable() && hasFocus() ? "" : "0x") + QString("%1").arg(QString::number(funcCode, 16).toUpper(), 2, '0'); - case QModbusPdu::WriteFileRecord: - QComboBox::addItem("21: WRITE FILE RECORD", QModbusPdu::WriteFileRecord); - break; + default: + return QString::number(funcCode); + } +} - case QModbusPdu::MaskWriteRegister: - QComboBox::addItem("22: MASK WRITE REG", QModbusPdu::MaskWriteRegister); - break; +/// +/// \brief FunctionCodeComboBox::on_currentIndexChanged +/// \param index +/// +void FunctionCodeComboBox::on_currentIndexChanged(int index) +{ + if(index >= 0) + { + _currentFunc = itemData(index).value(); + emit functionCodeChanged(_currentFunc); + } +} - case QModbusPdu::ReadWriteMultipleRegisters: - QComboBox::addItem("23: READ WRITE MULT REGS", QModbusPdu::ReadWriteMultipleRegisters); - break; +/// +/// \brief FunctionCodeComboBox::on_currentTextChanged +/// +void FunctionCodeComboBox::on_currentTextChanged(const QString& text) +{ + const auto idx = findData(text, Qt::DisplayRole); + if(idx != -1) + { + _currentFunc = itemData(idx).value(); + } + else + { + bool ok; + quint8 func = 0; - case QModbusPdu::ReadFifoQueue: - QComboBox::addItem("24: READ FIFO QUEUE", QModbusPdu::ReadFifoQueue); - break; + switch(_inputMode) + { + case InputMode::DecMode: + func = text.toUInt(&ok); + break; - case QModbusPdu::EncapsulatedInterfaceTransport: - QComboBox::addItem("43: ENC IFACE TRANSPORT", QModbusPdu::EncapsulatedInterfaceTransport); - break; + case InputMode::HexMode: + func = text.toUInt(&ok, 16); + break; + } - default: - break; + _currentFunc = ok ? (QModbusPdu::FunctionCode)func : QModbusPdu::Invalid; } } /// -/// \brief FunctionCodeComboBox::on_currentIndexChanged -/// \param index +/// \brief FunctionCodeComboBox::focusInEvent +/// \param e /// -void FunctionCodeComboBox::on_currentIndexChanged(int index) +void FunctionCodeComboBox::focusInEvent(QFocusEvent* e) +{ + if(isEditable()) + setCurrentText(formatFuncCode(_currentFunc)); + + QComboBox::focusInEvent(e); +} + +/// +/// \brief FunctionCodeComboBox::focusOutEvent +/// \param e +/// +void FunctionCodeComboBox::focusOutEvent(QFocusEvent* e) +{ + update(); + QComboBox::focusOutEvent(e); +} + +/// +/// \brief FunctionCodeComboBox::keyPressEvent +/// \param e +/// +void FunctionCodeComboBox::keyPressEvent(QKeyEvent* e) { - emit functionCodeChanged(itemData(index).value()); + if(!_validator || !isEditable()) + { + QComboBox::keyPressEvent(e); + return; + } + + int pos = 0; + auto text = e->text(); + const auto state = _validator->validate(text, pos); + + if(state == QValidator::Acceptable || + e->key() == Qt::Key_Backspace || + e->key() == Qt::Key_Delete || + e->matches(QKeySequence::Cut) || + e->matches(QKeySequence::Copy) || + e->matches(QKeySequence::Paste) || + e->matches(QKeySequence::Undo) || + e->matches(QKeySequence::Redo) || + e->matches(QKeySequence::SelectAll)) + { + QComboBox::keyPressEvent(e); + } } diff --git a/omodscan/controls/functioncodecombobox.h b/omodscan/controls/functioncodecombobox.h index e29bbde..595f22b 100644 --- a/omodscan/controls/functioncodecombobox.h +++ b/omodscan/controls/functioncodecombobox.h @@ -9,18 +9,43 @@ class FunctionCodeComboBox : public QComboBox Q_OBJECT public: - FunctionCodeComboBox(QWidget *parent = nullptr); + enum InputMode + { + DecMode = 0, + HexMode + }; + + explicit FunctionCodeComboBox(QWidget *parent = nullptr); QModbusPdu::FunctionCode currentFunctionCode() const; void setCurrentFunctionCode(QModbusPdu::FunctionCode funcCode); + InputMode inputMode() const; + void setInputMode(InputMode mode); + void addItem(QModbusPdu::FunctionCode funcCode); + void addItems(const QVector& funcCodes); signals: void functionCodeChanged(QModbusPdu::FunctionCode funcCode); +protected: + void focusInEvent(QFocusEvent* e) override; + void focusOutEvent(QFocusEvent* e) override; + void keyPressEvent(QKeyEvent* e) override; + private slots: void on_currentIndexChanged(int); + void on_currentTextChanged(const QString&); + +private: + void update(); + QString formatFuncCode(QModbusPdu::FunctionCode funcCode) const; + +private: + InputMode _inputMode; + QModbusPdu::FunctionCode _currentFunc; + QValidator* _validator; }; #endif // FUNCTIONCODECOMBOBOX_H diff --git a/omodscan/controls/modbuslogwidget.cpp b/omodscan/controls/modbuslogwidget.cpp new file mode 100644 index 0000000..7313d6d --- /dev/null +++ b/omodscan/controls/modbuslogwidget.cpp @@ -0,0 +1,268 @@ +#include +#include "htmldelegate.h" +#include "modbuslogwidget.h" + +/// +/// \brief ModbusLogModel::ModbusLogModel +/// \param parent +/// +ModbusLogModel::ModbusLogModel(ModbusLogWidget* parent) + : QAbstractListModel(parent) + ,_parentWidget(parent) +{ +} + +/// +/// \brief ModbusLogModel::~ModbusLogModel +/// +ModbusLogModel::~ModbusLogModel() +{ + deleteItems(); +} + +/// +/// \brief ModbusLogModel::rowCount +/// \param parent +/// \return +/// +int ModbusLogModel::rowCount(const QModelIndex&) const +{ + return _items.size(); +} + +/// +/// \brief ModbusLogModel::data +/// \param index +/// \param role +/// \return +/// +QVariant ModbusLogModel::data(const QModelIndex& index, int role) const +{ + if(!index.isValid() || index.row() >= rowCount()) + return QVariant(); + + const auto& item = _items.at(index.row()); + switch(role) + { + case Qt::DisplayRole: + return QString("%1 %2 %3").arg(item->timestamp().toString(Qt::ISODateWithMs), + (item->isRequest()? "←" : "→"), + item->toString(_parentWidget->dataDisplayMode())); + + case Qt::UserRole: + return QVariant::fromValue(item); + } + + return QVariant(); +} + +/// +/// \brief ModbusLogModel::clear +/// +void ModbusLogModel::clear() +{ + beginResetModel(); + deleteItems(); + endResetModel(); +} + +/// +/// \brief ModbusLogModel::append +/// \param data +/// +void ModbusLogModel::append(const ModbusMessage* data) +{ + if(data == nullptr) return; + + while(rowCount() >= _rowLimit) + { + delete _items.first(); + _items.removeFirst(); + } + + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + _items.push_back(data); + endInsertRows(); +} + +/// +/// \brief ModbusLogModel::rowLimit +/// \return +/// +int ModbusLogModel::rowLimit() const +{ + return _rowLimit; +} + +/// +/// \brief ModbusLogModel::setRowLimit +/// \param val +/// +void ModbusLogModel::setRowLimit(int val) +{ + _rowLimit = qMax(1, val); +} + +/// +/// \brief ModbusLogModel::deleteItems +/// +void ModbusLogModel::deleteItems() +{ + for(auto&& i : _items) + delete i; + + _items.clear(); +} + +/// +/// \brief ModbusLogWidget::ModbusLogWidget +/// \param parent +/// +ModbusLogWidget::ModbusLogWidget(QWidget* parent) + : QListView(parent) + , _autoscroll(false) +{ + setItemDelegate(new HtmlDelegate(this)); + setModel(new ModbusLogModel(this)); + + connect(model(), &ModbusLogModel::rowsInserted, + this, [&]{ + if(_autoscroll) scrollToBottom(); + setCurrentIndex(QModelIndex()); + }); +} + +/// +/// \brief ModbusLogWidget::changeEvent +/// \param event +/// +void ModbusLogWidget::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) + { + update(); + } + QListView::changeEvent(event); +} + +/// +/// \brief ModbusLogWidget::clear +/// +void ModbusLogWidget::clear() +{ + if(model()) + ((ModbusLogModel*)model())->clear(); +} + +/// +/// \brief ModbusLogWidget::rowCount +/// \return +/// +int ModbusLogWidget::rowCount() const +{ + return model() ? model()->rowCount() : 0; +} + +/// +/// \brief ModbusLogWidget::index +/// \param row +/// \return +/// +QModelIndex ModbusLogWidget::index(int row) +{ + return model() ? model()->index(row, 0) : QModelIndex(); +} + +/// +/// \brief ModbusLogWidget::addItem +/// \param pdu +/// \param deviceId +/// \param timestamp +/// \param request +/// \return +/// +const ModbusMessage* ModbusLogWidget::addItem(const QModbusPdu& pdu, int deviceId, const QDateTime& timestamp, bool request) +{ + const ModbusMessage* msg = nullptr; + if(model()) + { + msg = ModbusMessage::create(pdu, (QModbusAdu::Type)-1, deviceId, timestamp, request); + ((ModbusLogModel*)model())->append(msg); + } + return msg; +} + +/// +/// \brief ModbusLogWidget::itemAt +/// \param index +/// \return +/// +const ModbusMessage* ModbusLogWidget::itemAt(const QModelIndex& index) +{ + if(!index.isValid()) + return nullptr; + + return model() ? + model()->data(index, Qt::UserRole).value() : + nullptr; +} + +/// +/// \brief ModbusLogWidget::dataDisplayMode +/// \return +/// +DataDisplayMode ModbusLogWidget::dataDisplayMode() const +{ + return _dataDisplayMode; +} + +/// +/// \brief ModbusLogWidget::setDataDisplayMode +/// \param mode +/// +void ModbusLogWidget::setDataDisplayMode(DataDisplayMode mode) +{ + _dataDisplayMode = mode; + + if(model()) { + ((ModbusLogModel*)model())->update(); + } +} + +/// +/// \brief ModbusLogWidget::rowLimit +/// \return +/// +int ModbusLogWidget::rowLimit() const +{ + return model() ? ((ModbusLogModel*)model())->rowLimit() : 0; +} + +/// +/// \brief ModbusLogWidget::setRowLimit +/// \param val +/// +void ModbusLogWidget::setRowLimit(int val) +{ + if(model()) { + ((ModbusLogModel*)model())->setRowLimit(val); + } +} + +/// +/// \brief ModbusLogWidget::autoscroll +/// \return +/// +bool ModbusLogWidget::autoscroll() const +{ + return _autoscroll; +} + +/// +/// \brief ModbusLogWidget::setAutoscroll +/// \param on +/// +void ModbusLogWidget::setAutoscroll(bool on) +{ + _autoscroll = on; +} diff --git a/omodscan/controls/modbuslogwidget.h b/omodscan/controls/modbuslogwidget.h new file mode 100644 index 0000000..21f3d1f --- /dev/null +++ b/omodscan/controls/modbuslogwidget.h @@ -0,0 +1,76 @@ +#ifndef MODBUSLOGWIDGET_H +#define MODBUSLOGWIDGET_H + +#include +#include +#include "modbusmessage.h" + +class ModbusLogWidget; + +/// +/// \brief The ModbusLogModel class +/// +class ModbusLogModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit ModbusLogModel(ModbusLogWidget* parent); + ~ModbusLogModel(); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role) const override; + + void clear(); + void append(const ModbusMessage* data); + void update(){ + emit dataChanged(index(0), index(_items.size() - 1)); + } + + int rowLimit() const; + void setRowLimit(int val); + +private: + void deleteItems(); + +private: + int _rowLimit = 30; + ModbusLogWidget* _parentWidget; + QQueue _items; +}; + +/// +/// \brief The ModbusLogWidget class +/// +class ModbusLogWidget : public QListView +{ + Q_OBJECT +public: + explicit ModbusLogWidget(QWidget* parent = nullptr); + + void clear(); + + int rowCount() const; + QModelIndex index(int row); + + const ModbusMessage* addItem(const QModbusPdu& pdu, int deviceId, const QDateTime& timestamp, bool request); + const ModbusMessage* itemAt(const QModelIndex& index); + + DataDisplayMode dataDisplayMode() const; + void setDataDisplayMode(DataDisplayMode mode); + + int rowLimit() const; + void setRowLimit(int val); + + bool autoscroll() const; + void setAutoscroll(bool on); + +protected: + void changeEvent(QEvent* event) override; + +private: + bool _autoscroll; + DataDisplayMode _dataDisplayMode; +}; + +#endif // MODBUSLOGWIDGET_H diff --git a/omodscan/controls/modbusmessagewidget.cpp b/omodscan/controls/modbusmessagewidget.cpp new file mode 100644 index 0000000..6ae1e9e --- /dev/null +++ b/omodscan/controls/modbusmessagewidget.cpp @@ -0,0 +1,540 @@ +#include +#include "formatutils.h" +#include "htmldelegate.h" +#include "modbusmessagewidget.h" +#include "modbusmessages.h" + +/// +/// \brief ModbusMessageWidget::ModbusMessageWidget +/// \param parent +/// +ModbusMessageWidget::ModbusMessageWidget(QWidget *parent) + : QListWidget(parent) + ,_statusClr(Qt::red) + ,_byteOrder(ByteOrder::LittleEndian) + ,_dataDisplayMode(DataDisplayMode::Decimal) + ,_showTimestamp(true) + ,_mm(nullptr) +{ + setItemDelegate(new HtmlDelegate(this)); + setEditTriggers(QAbstractItemView::NoEditTriggers); +} + +/// +/// \brief ModbusMessageWidget::changeEvent +/// \param event +/// +void ModbusMessageWidget::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) + { + update(); + } + QListWidget::changeEvent(event); +} + +/// +/// \brief ModbusMessageWidget::clear +/// +void ModbusMessageWidget::clear() +{ + _mm = nullptr; + update(); +} + +/// +/// \brief ModbusMessageWidget::dataDisplayMode +/// \return +/// +DataDisplayMode ModbusMessageWidget::dataDisplayMode() const +{ + return _dataDisplayMode; +} + +/// +/// \brief ModbusMessageWidget::setDataDisplayMode +/// \param mode +/// +void ModbusMessageWidget::setDataDisplayMode(DataDisplayMode mode) +{ + _dataDisplayMode = mode; + update(); +} + +/// +/// \brief ModbusMessageWidget::byteOrder +/// \return +/// +ByteOrder ModbusMessageWidget::byteOrder() const +{ + return _byteOrder; +} + +/// +/// \brief ModbusMessageWidget::setByteOrder +/// \param order +/// +void ModbusMessageWidget::setByteOrder(ByteOrder order) +{ + _byteOrder = order; + update(); +} + +/// +/// \brief ModbusMessageWidget::showTimestamp +/// \return +/// +bool ModbusMessageWidget::showTimestamp() const +{ + return _showTimestamp; +} + +/// +/// \brief ModbusMessageWidget::setShowTimestamp +/// \param on +/// +void ModbusMessageWidget::setShowTimestamp(bool on) +{ + _showTimestamp = on; + update(); +} + +/// +/// \brief ModbusMessageWidget::setStatusColor +/// \param clr +/// +void ModbusMessageWidget::setStatusColor(const QColor& clr) +{ + _statusClr = clr; + update(); +} + +/// +/// \brief ModbusMessageWidget::modbusMessage +/// \return +/// +const ModbusMessage* ModbusMessageWidget::modbusMessage() const +{ + return _mm; +} + +/// +/// \brief ModbusMessageWidget::setModbusMessage +/// \param msg +/// +void ModbusMessageWidget::setModbusMessage(const ModbusMessage* msg) +{ + _mm = msg; + update(); +} + +/// +/// \brief ModbusMessageWidget::update +/// +void ModbusMessageWidget::update() +{ + QListWidget::clear(); + + if(_mm == nullptr) + return; + + if(!_mm->isValid()) + { + if(_mm->isRequest()) + addItem(tr("*** INVALID MODBUS REQUEST ***").arg(_statusClr.name())); + else if(!_mm->isException()) + addItem(tr("*** INVALID MODBUS RESPONSE ***").arg(_statusClr.name())); + } + + auto addChecksum = [&]{ + if(_mm->protocolType() == QModbusAdu::Rtu) + { + const auto checksum = formatWordValue(_dataDisplayMode, _mm->checksum()); + if(_mm->matchingChecksum()) + { + addItem(tr("Checksum: %1").arg(checksum)); + } + else + { + const auto calcChecksum = formatWordValue(_dataDisplayMode, _mm->calcChecksum()); + addItem(tr("Checksum: %1 (Expected: %2)").arg(checksum, calcChecksum, _statusClr.name())); + } + } + }; + + addItem(tr("Type: %1").arg(_mm->isRequest() ? tr("Request (Tx)") : tr("Response (Rx)"))); + if(_showTimestamp) addItem(tr("Timestamp: %1").arg(_mm->timestamp().toString(Qt::ISODateWithMs))); + + if(_mm->type() == ModbusMessage::Adu) + { + const auto transactionId = _mm->isValid() ? formatWordValue(_dataDisplayMode, _mm->transactionId()) : "??"; + const auto protocolId = _mm->isValid() ? formatWordValue(_dataDisplayMode, _mm->protocolId()): "??"; + const auto length = _mm->isValid() ? formatWordValue(_dataDisplayMode, _mm->length()): "??"; + addItem(tr("Transaction ID: %1").arg(transactionId)); + addItem(tr("Protocol ID: %1").arg(protocolId)); + addItem(tr("Length: %1").arg(length)); + } + + addItem(tr("Device ID: %1").arg(formatByteValue(_dataDisplayMode, _mm->deviceId()))); + + if(_mm->isException()) + { + const auto exception = QString("%1 (%2)").arg(formatByteValue(_dataDisplayMode, _mm->exception()), _mm->exception()); + addItem(tr("Error Code: %1").arg(formatByteValue(_dataDisplayMode, _mm->function()))); + addItem(tr("Exception Code: %1").arg(exception)); + addChecksum(); + return; + } + + const auto func = _mm->function(); + const auto function = func.isValid() ? + QString("%1 (%2)").arg(formatByteValue(_dataDisplayMode, func), func) : + formatByteValue(_dataDisplayMode, func); + addItem(tr("Function Code: %1").arg(function)); + + switch(_mm->function()) + { + case QModbusPdu::ReadCoils: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto startAddress = req->isValid() ? formatWordValue(_dataDisplayMode, req->startAddress()) : "??"; + const auto length = req->isValid() ? formatWordValue(_dataDisplayMode, req->length()): "??"; + addItem(tr("Start Address: %1").arg(startAddress)); + addItem(tr("Length: %1").arg(length)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto byteCount = resp->isValid() ? formatByteValue(_dataDisplayMode, resp->byteCount()) : "?"; + const auto coilStatus = resp->isValid() ? formatByteArray(_dataDisplayMode, resp->coilStatus()) : "???"; + addItem(tr("Byte Count: %1").arg(byteCount)); + addItem(tr("Coil Status: %1").arg(coilStatus)); + } + break; + + case QModbusPdu::ReadDiscreteInputs: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto startAddress = req->isValid() ? formatWordValue(_dataDisplayMode, req->startAddress()) : "??"; + const auto length = req->isValid() ? formatWordValue(_dataDisplayMode, req->length()): "??"; + addItem(tr("Start Address: %1").arg(startAddress)); + addItem(tr("Length: %1").arg(length)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto byteCount = resp->isValid() ? formatByteValue(_dataDisplayMode, resp->byteCount()) : "?"; + const auto inputStatus = resp->isValid() ? formatByteArray(_dataDisplayMode, resp->inputStatus()) : "???"; + addItem(tr("Byte Count: %1").arg(byteCount)); + addItem(tr("Input Status: %1").arg(inputStatus)); + } + break; + + case QModbusPdu::ReadHoldingRegisters: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto startAddress = req->isValid() ? formatWordValue(_dataDisplayMode, req->startAddress()) : "??"; + const auto length = req->isValid() ? formatWordValue(_dataDisplayMode, req->length()): "??"; + addItem(tr("Start Address: %1").arg(startAddress)); + addItem(tr("Length: %1").arg(length)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto byteCount = resp->isValid() ? formatByteValue(_dataDisplayMode, resp->byteCount()) : "?"; + const auto registerValue = resp->isValid() ? formatWordArray(_dataDisplayMode, resp->registerValue(), _byteOrder) : "???"; + addItem(tr("Byte Count: %1").arg(byteCount)); + addItem(tr("Register Value: %1").arg(registerValue)); + } + break; + + case QModbusPdu::ReadInputRegisters: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto startAddress = req->isValid() ? formatWordValue(_dataDisplayMode, req->startAddress()) : "??"; + const auto length = req->isValid() ? formatWordValue(_dataDisplayMode, req->length()): "??"; + addItem(tr("Start Address: %1").arg(startAddress)); + addItem(tr("Length: %1").arg(length)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto byteCount = resp->isValid() ? formatByteValue(_dataDisplayMode, resp->byteCount()) : "?"; + const auto registerValue = resp->isValid() ? formatWordArray(_dataDisplayMode, resp->registerValue(), _byteOrder) : "???"; + addItem(tr("Byte Count: %1").arg(byteCount)); + addItem(tr("Input Registers: %1").arg(registerValue)); + } + break; + + case QModbusPdu::WriteSingleCoil: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto outputAddress = req->isValid() ? formatWordValue(_dataDisplayMode, req->address()) : "??"; + const auto outputValue = req->isValid() ? formatWordValue(_dataDisplayMode, req->value()) : "??"; + addItem(tr("Output Address: %1").arg(outputAddress)); + addItem(tr("Output Value: %1").arg(outputValue)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto outputAddress = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->address()) : "??"; + const auto outputValue = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->value()) : "??"; + addItem(tr("Output Address: %1").arg(outputAddress)); + addItem(tr("Output Value: %1").arg(outputValue)); + } + break; + + case QModbusPdu::WriteSingleRegister: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto registerAddress = req->isValid() ? formatWordValue(_dataDisplayMode, req->address()) : "??"; + const auto registerValue = req->isValid() ? formatWordValue(_dataDisplayMode, req->value()) : "??"; + addItem(tr("Register Address: %1").arg(registerAddress)); + addItem(tr("Register Value: %1").arg(registerValue)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto registerAddress = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->address()) : "??"; + const auto registerValue = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->value()) : "??"; + addItem(tr("Register Address: %1").arg(registerAddress)); + addItem(tr("Register Value: %1").arg(registerValue)); + } + break; + + case QModbusPdu::ReadExceptionStatus: + if(!_mm->isRequest()) + { + auto resp = reinterpret_cast(_mm); + const auto outputData = resp->isValid() ? formatByteValue(_dataDisplayMode, resp->outputData()) : "?"; + addItem(tr("Output Data: %1").arg(outputData)); + } + break; + + case QModbusPdu::Diagnostics: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto subFunc = req->isValid() ? formatWordValue(_dataDisplayMode, req->subfunc()) : "??"; + const auto data = req->isValid() ? formatByteArray(_dataDisplayMode, req->data()) : "???"; + addItem(tr("Sub-function: %1").arg(subFunc)); + addItem(tr("Data: %1").arg(data)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto subFunc = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->subfunc()) : "??"; + const auto data = resp->isValid() ? formatByteArray(_dataDisplayMode, resp->data()) : "???"; + addItem(tr("Sub-function: %1").arg(subFunc)); + addItem(tr("Data: %1").arg(data)); + } + break; + + case QModbusPdu::GetCommEventCounter: + if(!_mm->isRequest()) + { + auto resp = reinterpret_cast(_mm); + const auto status = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->status()) : "??"; + const auto eventCount = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->eventCount()) : "??"; + addItem(tr("Status: %1").arg(status)); + addItem(tr("Event Count: %1").arg(eventCount)); + } + break; + + case QModbusPdu::GetCommEventLog: + if(!_mm->isRequest()) + { + auto resp = reinterpret_cast(_mm); + const auto byteCount = resp->isValid() ? formatByteValue(_dataDisplayMode, resp->byteCount()) : "?"; + const auto status = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->status()) : "??"; + const auto eventCount = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->eventCount()) : "??"; + const auto messageCount = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->messageCount()) : "??"; + const auto events = resp->isValid() ? formatByteArray(_dataDisplayMode, resp->events()) : "???"; + addItem(tr("Byte Count: %1").arg(byteCount)); + addItem(tr("Status: %1").arg(status)); + addItem(tr("Event Count: %1").arg(eventCount)); + addItem(tr("Message Count: %1").arg(messageCount)); + addItem(tr("Events: %1").arg(events)); + } + break; + + case QModbusPdu::WriteMultipleCoils: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto startAddr = req->isValid() ? formatWordValue(_dataDisplayMode, req->startAddress()) : "??"; + const auto quantity = req->isValid() ? formatWordValue(_dataDisplayMode, req->quantity()) : "??"; + const auto byteCount = req->isValid() ? formatByteValue(_dataDisplayMode, req->byteCount()) : "?"; + const auto values = req->isValid() ? formatByteArray(_dataDisplayMode, req->values()) : "???"; + addItem(tr("Starting Address: %1").arg(startAddr)); + addItem(tr("Quantity of Outputs: %1").arg(quantity)); + addItem(tr("Byte Count: %1").arg(byteCount)); + addItem(tr("Output Value: %1").arg(values)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto startAddr = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->startAddress()) : "??"; + const auto quantity = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->quantity()) : "??"; + addItem(tr("Starting Address: %1").arg(startAddr)); + addItem(tr("Quantity of Outputs: %1").arg(quantity)); + } + break; + + case QModbusPdu::WriteMultipleRegisters: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto startAddr = req->isValid() ? formatWordValue(_dataDisplayMode, req->startAddress()) : "??"; + const auto quantity = req->isValid() ? formatWordValue(_dataDisplayMode, req->quantity()) : "??"; + const auto byteCount = req->isValid() ? formatByteValue(_dataDisplayMode, req->byteCount()) : "?"; + const auto values = req->isValid() ? formatWordArray(_dataDisplayMode, req->values(), _byteOrder) : "???"; + addItem(tr("Starting Address: %1").arg(startAddr)); + addItem(tr("Quantity of Registers: %1").arg(quantity)); + addItem(tr("Byte Count: %1").arg(byteCount)); + addItem(tr("Registers Value: %1").arg(values)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto startAddr = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->startAddress()) : "??"; + const auto quantity = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->quantity()) : "??"; + addItem(tr("Starting Address: %1").arg(startAddr)); + addItem(tr("Quantity of Registers: %1").arg(quantity)); + } + break; + + case QModbusPdu::ReportServerId: + if(!_mm->isRequest()) + { + auto resp = reinterpret_cast(_mm); + const auto byteCount = resp->isValid() ? formatByteValue(_dataDisplayMode, resp->byteCount()) : "?"; + const auto data = resp->isValid() ? formatByteArray(_dataDisplayMode, resp->data()) : "?"; + addItem(tr("Byte Count: %1").arg(byteCount)); + addItem(tr("Data: %1").arg(data)); + } + break; + + case QModbusPdu::ReadFileRecord: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto byteCount = req->isValid() ? formatByteValue(_dataDisplayMode, req->byteCount()) : "?"; + const auto data = req->isValid() ? formatByteArray(_dataDisplayMode, req->data()) : "?"; + addItem(tr("Byte Count: %1").arg(byteCount)); + addItem(tr("Data: %1").arg(data)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto byteCount = resp->isValid() ? formatByteValue(_dataDisplayMode, resp->byteCount()) : "?"; + const auto data = resp->isValid() ? formatByteArray(_dataDisplayMode, resp->data()) : "?"; + addItem(tr("Byte Count: %1").arg(byteCount)); + addItem(tr("Data: %1").arg(data)); + } + break; + + case QModbusPdu::WriteFileRecord: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto length = req->isValid() ? formatByteValue(_dataDisplayMode, req->length()) : "?"; + const auto data = req->isValid() ? formatByteArray(_dataDisplayMode, req->data()) : "???"; + addItem(tr("Request Data Length: %1").arg(length)); + addItem(tr("Data: %1").arg(data)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto length = resp->isValid() ? formatByteValue(_dataDisplayMode, resp->length()) : "?"; + const auto data = resp->isValid() ? formatByteArray(_dataDisplayMode, resp->data()) : "???"; + addItem(tr("Response Data Length: %1").arg(length)); + addItem(tr("Data: %1").arg(data)); + } + break; + + case QModbusPdu::MaskWriteRegister: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto address = req->isValid() ? formatWordValue(_dataDisplayMode, req->address()) : "??"; + const auto andMask = req->isValid() ? formatWordValue(_dataDisplayMode, req->andMask()) : "??"; + const auto orMask = req->isValid() ? formatWordValue(_dataDisplayMode, req->orMask()) : "??"; + addItem(tr("Address: %1").arg(address)); + addItem(tr("And Mask: %1").arg(andMask)); + addItem(tr("Or Mask: %1").arg(orMask)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto address = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->address()) : "??"; + const auto andMask = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->andMask()) : "??"; + const auto orMask = resp->isValid() ? formatWordValue(_dataDisplayMode, resp->orMask()) : "??"; + addItem(tr("Address: %1").arg(address)); + addItem(tr("And Mask: %1").arg(andMask)); + addItem(tr("Or Mask: %1").arg(orMask)); + } + break; + + case QModbusPdu::ReadWriteMultipleRegisters: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto readStartAddr = req->isValid() ? formatWordValue(_dataDisplayMode, req->readStartAddress()) : "??"; + const auto readLength = req->isValid() ? formatWordValue(_dataDisplayMode, req->readLength()) : "??"; + const auto writeStartAddr = req->isValid() ? formatWordValue(_dataDisplayMode, req->writeStartAddress()) : "??"; + const auto writeLength = req->isValid() ? formatWordValue(_dataDisplayMode, req->writeLength()) : "??"; + const auto writeByteCount = req->isValid() ? formatByteValue(_dataDisplayMode, req->writeByteCount()) : "?"; + const auto writeValues = req->isValid() ? formatWordArray(_dataDisplayMode, req->writeValues(), _byteOrder) : "???"; + addItem(tr("Read Starting Address: %1").arg(readStartAddr)); + addItem(tr("Quantity to Read: %1").arg(readLength)); + addItem(tr("Write Starting Address: %1").arg(writeStartAddr)); + addItem(tr("Quantity to Write: %1").arg(writeLength)); + addItem(tr("Write Byte Count: %1").arg(writeByteCount)); + addItem(tr("Write Registers Value: %1").arg(writeValues)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto byteCount = resp->isValid() ? formatByteValue(_dataDisplayMode, resp->byteCount()): "?"; + const auto values = resp->isValid() ? formatWordArray(_dataDisplayMode, resp->values(), _byteOrder) : "???"; + addItem(tr("Byte Count: %1").arg(byteCount)); + addItem(tr("Registers Value: %1").arg(values)); + } + break; + + case QModbusPdu::ReadFifoQueue: + if(_mm->isRequest()) + { + auto req = reinterpret_cast(_mm); + const auto fifoAddr = req->isValid() ? formatWordValue(_dataDisplayMode, req->fifoAddress()) : "??"; + addItem(tr("FIFO Point Address: %1").arg(fifoAddr)); + } + else + { + auto resp = reinterpret_cast(_mm); + const auto byteCount = resp->isValid() ? formatByteValue(_dataDisplayMode, resp->byteCount()) : "?"; + const auto fifoCount = resp->isValid() ? formatByteValue(_dataDisplayMode, resp->fifoCount()) : "?"; + const auto fifoValue = resp->isValid() ? formatWordArray(_dataDisplayMode, resp->fifoValue(), _byteOrder) : "???"; + addItem(tr("Byte Count: %1").arg(byteCount)); + addItem(tr("FIFO Count: %1").arg(fifoCount)); + addItem(tr("FIFO Value Register: %1").arg(fifoValue)); + } + break; + + default: + { + const auto data = _mm->isValid() ? formatByteArray(_dataDisplayMode, _mm->rawData()) : "???"; + addItem(tr("Data: %1").arg(data)); + } + break; + } + + addChecksum(); +} diff --git a/omodscan/controls/modbusmessagewidget.h b/omodscan/controls/modbusmessagewidget.h new file mode 100644 index 0000000..24ee223 --- /dev/null +++ b/omodscan/controls/modbusmessagewidget.h @@ -0,0 +1,46 @@ +#ifndef MODBUSMESSAGEWIDGET_H +#define MODBUSMESSAGEWIDGET_H + +#include +#include "modbusmessage.h" + +/// +/// \brief The ModbusMessageWidget class +/// +class ModbusMessageWidget : public QListWidget +{ + Q_OBJECT +public: + explicit ModbusMessageWidget(QWidget *parent = nullptr); + + void clear(); + + DataDisplayMode dataDisplayMode() const; + void setDataDisplayMode(DataDisplayMode mode); + + ByteOrder byteOrder() const; + void setByteOrder(ByteOrder order); + + const ModbusMessage* modbusMessage() const; + void setModbusMessage(const ModbusMessage* msg); + + bool showTimestamp() const; + void setShowTimestamp(bool on); + + void setStatusColor(const QColor& clr); + +protected: + void changeEvent(QEvent* event) override; + +private: + void update(); + +private: + QColor _statusClr; + ByteOrder _byteOrder; + DataDisplayMode _dataDisplayMode; + bool _showTimestamp; + const ModbusMessage* _mm; +}; + +#endif // MODBUSMESSAGEWIDGET_H diff --git a/omodscan/controls/numericlineedit.cpp b/omodscan/controls/numericlineedit.cpp index 1a332d6..6785ef1 100644 --- a/omodscan/controls/numericlineedit.cpp +++ b/omodscan/controls/numericlineedit.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include "qhexvalidator.h" @@ -68,7 +68,7 @@ NumericLineEdit::InputMode NumericLineEdit::inputMode() const } /// -/// \brief NumericLineEdit::setHexInput +/// \brief NumericLineEdit::setInputMode /// \param on /// void NumericLineEdit::setInputMode(InputMode mode) @@ -126,12 +126,14 @@ void NumericLineEdit::internalSetValue(QVariant value) if(_paddingZeroes) { const auto text = QStringLiteral("%1").arg(value.toInt(), _paddingZeroWidth, 10, QLatin1Char('0')); - QLineEdit::setText(text); + if(text != QLineEdit::text()) + QLineEdit::setText(text); } else { const auto text = QString::number(value.toInt()); - QLineEdit::setText(text); + if(text != QLineEdit::text()) + QLineEdit::setText(text); } break; @@ -139,38 +141,53 @@ void NumericLineEdit::internalSetValue(QVariant value) value = qBound(_minValue.toUInt(), value.toUInt(), _maxValue.toUInt()); if(_paddingZeroes) { - const auto text = QStringLiteral("%1").arg(value.toUInt(), _paddingZeroWidth, 10, QLatin1Char('0')); + const auto text = QStringLiteral("%1").arg(value.toUInt(), _paddingZeroWidth, 10, QLatin1Char('0')); + if(text != QLineEdit::text()) QLineEdit::setText(text); } else { - const auto text = QString::number(value.toUInt()); + const auto text = QString::number(value.toUInt()); + if(text != QLineEdit::text()) QLineEdit::setText(text); } break; case HexMode: + { value = qBound(_minValue.toInt() > 0 ? _minValue.toUInt() : 0, value.toUInt(), _maxValue.toUInt()); + const QString prefix = (hasFocus() ? "" : "0x"); if(_paddingZeroes) { - const auto text = QStringLiteral("%1").arg(value.toUInt(), _paddingZeroWidth, 16, QLatin1Char('0')); - QLineEdit::setText(text.toUpper()); + const auto text = prefix + QStringLiteral("%1").arg(value.toUInt(), _paddingZeroWidth, 16, QLatin1Char('0')).toUpper(); + if(text != QLineEdit::text()) + QLineEdit::setText(text); } else { - const auto text = QString("%1").arg(value.toUInt(), -1, 16); - QLineEdit::setText(text.toUpper()); + const auto text = prefix + QString("%1").arg(value.toUInt(), -1, 16).toUpper(); + if(text != QLineEdit::text()) + QLineEdit::setText(text); } + } break; case FloatMode: + { value = qBound(_minValue.toFloat(), value.toFloat(), _maxValue.toFloat()); - QLineEdit::setText(QLocale().toString(value.toFloat())); + const auto text = QLocale().toString(value.toFloat()); + if(text != QLineEdit::text()) + QLineEdit::setText(text); + } break; case DoubleMode: + { value = qBound(_minValue.toDouble(), value.toDouble(), _maxValue.toDouble()); - QLineEdit::setText(QLocale().toString(value.toDouble())); + const auto text = QLocale().toString(value.toFloat()); + if(text != QLineEdit::text()) + QLineEdit::setText(text); + } break; } @@ -235,14 +252,33 @@ void NumericLineEdit::updateValue() } } +/// +/// \brief NumericLineEdit::focusInEvent +/// \param e +/// +void NumericLineEdit::focusInEvent(QFocusEvent* e) +{ + updateValue(); + QLineEdit::focusInEvent(e); +} + /// /// \brief NumberLineEdit::focusOutEvent -/// \param event +/// \param e /// -void NumericLineEdit::focusOutEvent(QFocusEvent* event) +void NumericLineEdit::focusOutEvent(QFocusEvent* e) { updateValue(); - QLineEdit::focusOutEvent(event); + QLineEdit::focusOutEvent(e); +} + +/// +/// \brief NumericLineEdit::keyPressEvent +/// \param e +/// +void NumericLineEdit::keyPressEvent(QKeyEvent* e) +{ + QLineEdit::keyPressEvent(e); } /// @@ -343,7 +379,7 @@ void NumericLineEdit::on_rangeChanged(const QVariant& bottom, const QVariant& to { const int nums = QString::number(top.toUInt(), 16).length(); _paddingZeroWidth = qMax(1, nums); - setMaxLength(qMax(1, nums)); + setMaxLength(qMax(1, nums + 2)); setValidator(new QHexValidator(bottom.toUInt(), top.toUInt(), this)); } break; diff --git a/omodscan/controls/numericlineedit.h b/omodscan/controls/numericlineedit.h index cf35aa8..fb072e6 100644 --- a/omodscan/controls/numericlineedit.h +++ b/omodscan/controls/numericlineedit.h @@ -59,7 +59,9 @@ class NumericLineEdit : public QLineEdit void rangeChanged(const QVariant& bottom, const QVariant& top); protected: + void focusInEvent(QFocusEvent*) override; void focusOutEvent(QFocusEvent*) override; + void keyPressEvent(QKeyEvent*) override; private slots: void on_editingFinished(); diff --git a/omodscan/controls/outputwidget.cpp b/omodscan/controls/outputwidget.cpp index 464180d..f336dc2 100644 --- a/omodscan/controls/outputwidget.cpp +++ b/omodscan/controls/outputwidget.cpp @@ -2,8 +2,9 @@ #include #include #include -#include "floatutils.h" +#include "formatutils.h" #include "outputwidget.h" +#include "modbusmessages.h" #include "ui_outputwidget.h" /// @@ -36,299 +37,6 @@ const int AddressRole = Qt::UserRole + 5; /// const int ValueRole = Qt::UserRole + 6; -/// -/// \brief formatBinaryValue -/// \param pointType -/// \param value -/// \param outValue -/// \return -/// -QString formatBinaryValue(QModbusDataUnit::RegisterType pointType, quint16 value, ByteOrder order, QVariant& outValue) -{ - QString result; - value = toByteOrderValue(value, order); - - switch(pointType) - { - case QModbusDataUnit::Coils: - case QModbusDataUnit::DiscreteInputs: - result = QString("<%1>").arg(value); - break; - case QModbusDataUnit::HoldingRegisters: - case QModbusDataUnit::InputRegisters: - result = QStringLiteral("<%1>").arg(value, 16, 2, QLatin1Char('0')); - break; - default: - break; - } - outValue = value; - return result; -} - -/// -/// \brief formatDecimalValue -/// \param pointType -/// \param value -/// \param outValue -/// \return -/// -QString formatDecimalValue(QModbusDataUnit::RegisterType pointType, quint16 value, ByteOrder order, QVariant& outValue) -{ - QString result; - value = toByteOrderValue(value, order); - - switch(pointType) - { - case QModbusDataUnit::Coils: - case QModbusDataUnit::DiscreteInputs: - result = QStringLiteral("<%1>").arg(value, 1, 10, QLatin1Char('0')); - break; - case QModbusDataUnit::HoldingRegisters: - case QModbusDataUnit::InputRegisters: - result = QStringLiteral("<%1>").arg(value, 5, 10, QLatin1Char('0')); - break; - default: - break; - } - outValue = value; - return result; -} - -/// -/// \brief formatIntegerValue -/// \param pointType -/// \param value -/// \param outValue -/// \return -/// -QString formatIntegerValue(QModbusDataUnit::RegisterType pointType, qint16 value, ByteOrder order, QVariant& outValue) -{ - QString result; - value = toByteOrderValue(value, order); - - switch(pointType) - { - case QModbusDataUnit::Coils: - case QModbusDataUnit::DiscreteInputs: - result = QString("<%1>").arg(value); - break; - case QModbusDataUnit::HoldingRegisters: - case QModbusDataUnit::InputRegisters: - result = QStringLiteral("<%1>").arg(value, 5, 10, QLatin1Char(' ')); - break; - default: - break; - } - outValue = value; - return result; -} - -/// -/// \brief formatHexValue -/// \param pointType -/// \param value -/// \param outValue -/// \return -/// -QString formatHexValue(QModbusDataUnit::RegisterType pointType, quint16 value, ByteOrder order, QVariant& outValue) -{ - QString result; - value = toByteOrderValue(value, order); - - switch(pointType) - { - case QModbusDataUnit::Coils: - case QModbusDataUnit::DiscreteInputs: - result = QString("<%1>").arg(value); - break; - case QModbusDataUnit::HoldingRegisters: - case QModbusDataUnit::InputRegisters: - result = QStringLiteral("<%1H>").arg(value, 4, 16, QLatin1Char('0')); - break; - default: - break; - } - outValue = value; - return result.toUpper(); -} - -/// -/// \brief formatFloatValue -/// \param pointType -/// \param value1 -/// \param value2 -/// \param order -/// \param flag -/// \param outValue -/// \return -/// -QString formatFloatValue(QModbusDataUnit::RegisterType pointType, quint16 value1, quint16 value2, ByteOrder order, bool flag, QVariant& outValue) -{ - QString result; - switch(pointType) - { - case QModbusDataUnit::Coils: - case QModbusDataUnit::DiscreteInputs: - outValue = value1; - result = QString("<%1>").arg(value1); - break; - case QModbusDataUnit::HoldingRegisters: - case QModbusDataUnit::InputRegisters: - { - if(flag) break; - - const float value = makeFloat(value1, value2, order); - outValue = value; - result = QLocale().toString(value); - } - break; - default: - break; - } - return result; -} - -/// -/// \brief formatLongValue -/// \param pointType -/// \param value1 -/// \param value2 -/// \param order -/// \param flag -/// \param outValue -/// \return -/// -QString formatLongValue(QModbusDataUnit::RegisterType pointType, quint16 value1, quint16 value2, ByteOrder order, bool flag, QVariant& outValue) -{ - QString result; - switch(pointType) - { - case QModbusDataUnit::Coils: - case QModbusDataUnit::DiscreteInputs: - outValue = value1; - result = QString("<%1>").arg(value1); - break; - case QModbusDataUnit::HoldingRegisters: - case QModbusDataUnit::InputRegisters: - { - if(flag) break; - - const qint32 value = makeLong(value1, value2, order); - outValue = value; - result = result = QString("<%1>").arg(value, 10, 10, QLatin1Char(' ')); - } - break; - default: - break; - } - return result; -} - -/// -/// \brief formatUnsignedLongValue -/// \param pointType -/// \param value1 -/// \param value2 -/// \param order -/// \param flag -/// \param outValue -/// \return -/// -QString formatUnsignedLongValue(QModbusDataUnit::RegisterType pointType, quint16 value1, quint16 value2, ByteOrder order, bool flag, QVariant& outValue) -{ - QString result; - switch(pointType) - { - case QModbusDataUnit::Coils: - case QModbusDataUnit::DiscreteInputs: - outValue = value1; - result = QString("<%1>").arg(value1); - break; - case QModbusDataUnit::HoldingRegisters: - case QModbusDataUnit::InputRegisters: - { - if(flag) break; - - const quint32 value = makeULong(value1, value2, order); - outValue = value; - result = result = QString("<%1>").arg(value, 10, 10, QLatin1Char('0')); - } - break; - default: - break; - } - return result; -} - -/// -/// \brief formatDoubleValue -/// \param pointType -/// \param value1 -/// \param value2 -/// \param value3 -/// \param value4 -/// \param order -/// \param flag -/// \param outValue -/// \return -/// -QString formatDoubleValue(QModbusDataUnit::RegisterType pointType, quint16 value1, quint16 value2, quint16 value3, quint16 value4, ByteOrder order, bool flag, QVariant& outValue) -{ - QString result; - switch(pointType) - { - case QModbusDataUnit::Coils: - case QModbusDataUnit::DiscreteInputs: - outValue = value1; - result = QString("<%1>").arg(value1); - break; - case QModbusDataUnit::HoldingRegisters: - case QModbusDataUnit::InputRegisters: - { - if(flag) break; - - const double value = makeDouble(value1, value2, value3, value4, order); - outValue = value; - result = QLocale().toString(value, 'g', 16); - } - break; - default: - break; - } - return result; -} - -/// -/// \brief formatAddress -/// \param pointType -/// \param address -/// \param hexFormat -/// \return -/// -QString formatAddress(QModbusDataUnit::RegisterType pointType, int address, bool hexFormat) -{ - QString prefix; - switch(pointType) - { - case QModbusDataUnit::Coils: - prefix = "0"; - break; - case QModbusDataUnit::DiscreteInputs: - prefix = "1"; - break; - case QModbusDataUnit::HoldingRegisters: - prefix = "4"; - break; - case QModbusDataUnit::InputRegisters: - prefix = "3"; - break; - default: - break; - } - - return hexFormat ? QStringLiteral("%1H").arg(address, 4, 16, QLatin1Char('0')) : - prefix + QStringLiteral("%1").arg(address, 4, 10, QLatin1Char('0')); -} /// /// \brief OutputListModel::OutputListModel @@ -599,6 +307,13 @@ OutputWidget::OutputWidget(QWidget *parent) : setStatusColor(Qt::red); setUninitializedStatus(); + + connect(ui->logView->selectionModel(), + &QItemSelectionModel::selectionChanged, + this, [&](const QItemSelection& sel) { + if(!sel.indexes().isEmpty()) + showModbusMessage(sel.indexes().first()); + }); } /// @@ -643,6 +358,8 @@ void OutputWidget::setup(const DisplayDefinition& dd, const ModbusSimulationMap& _descriptionMap.insert(descriptionMap()); _displayDefinition = dd; + setLogViewLimit(dd.LogViewLimit); + _listModel->clear(); for(auto&& key : simulations.keys()) @@ -760,6 +477,7 @@ void OutputWidget::setStatusColor(const QColor& clr) auto pal = ui->labelStatus->palette(); pal.setColor(QPalette::WindowText, clr); ui->labelStatus->setPalette(pal); + ui->modbusMsg->setStatusColor(clr); } /// @@ -779,6 +497,26 @@ void OutputWidget::setFont(const QFont& font) { ui->listView->setFont(font); ui->labelStatus->setFont(font); + ui->logView->setFont(font); + ui->modbusMsg->setFont(font); +} + +/// +/// \brief OutputWidget::logViewLimit +/// \return +/// +int OutputWidget::logViewLimit() const +{ + return ui->logView->rowLimit(); +} + +/// +/// \brief OutputWidget::setLogViewLimit +/// \param l +/// +void OutputWidget::setLogViewLimit(int l) +{ + ui->logView->setRowLimit(l); } /// @@ -793,12 +531,9 @@ void OutputWidget::setStatus(const QString& status) } else { - const auto info = QString("** %1 **").arg(status); + const auto info = QString("*** %1 ***").arg(status); if(info != ui->labelStatus->text()) - { ui->labelStatus->setText(info); - captureString(info); - } } } @@ -851,17 +586,17 @@ void OutputWidget::paint(const QRect& rc, QPainter& painter) /// void OutputWidget::updateTraffic(const QModbusRequest& request, int server) { - updateTrafficWidget(true, server, request); + updateLogView(true, server, request); } /// /// \brief OutputWidget::updateTraffic /// \param response -/// \param server +/// \param deviceId /// -void OutputWidget::updateTraffic(const QModbusResponse& response, int server) +void OutputWidget::updateTraffic(const QModbusResponse& response, int deviceId) { - updateTrafficWidget(false, server, response); + updateLogView(false, deviceId, response); } /// @@ -870,14 +605,6 @@ void OutputWidget::updateTraffic(const QModbusResponse& response, int server) void OutputWidget::updateData(const QModbusDataUnit& data) { _listModel->updateData(data); - - if(captureMode() == CaptureMode::TextCapture) - { - QStringList capstr; - for(int i = 0; i < _listModel->rowCount(); i++) - capstr.push_back(_listModel->data(_listModel->index(i), CaptureRole).toString()); - captureString(capstr.join(' ')); - } } /// @@ -962,6 +689,9 @@ DataDisplayMode OutputWidget::dataDisplayMode() const void OutputWidget::setDataDisplayMode(DataDisplayMode mode) { _dataDisplayMode = mode; + ui->logView->setDataDisplayMode(mode); + ui->modbusMsg->setDataDisplayMode(mode); + _listModel->update(); } @@ -981,6 +711,8 @@ ByteOrder OutputWidget::byteOrder() const void OutputWidget::setByteOrder(ByteOrder order) { _byteOrder = order; + ui->modbusMsg->setByteOrder(order); + _listModel->update(); } @@ -1050,6 +782,7 @@ void OutputWidget::on_listView_doubleClicked(const QModelIndex& index) emit itemDoubleClicked(address, value); } + /// /// \brief OutputWidget::setUninitializedStatus /// @@ -1067,51 +800,35 @@ void OutputWidget::captureString(const QString& s) if(_fileCapture.isOpen()) { QTextStream stream(&_fileCapture); - stream << QDateTime::currentDateTime().toString(Qt::ISODateWithMs) << " " << - formatAddress(_displayDefinition.PointType, _displayDefinition.PointAddress, false) << " " - << s << "\n"; + stream << s << "\n"; } } /// -/// \brief OutputWidget::updateTrafficWidget +/// \brief OutputWidget::showModbusMessage +/// \param index +/// +void OutputWidget::showModbusMessage(const QModelIndex& index) +{ + const auto msg = ui->logView->itemAt(index); + ui->modbusMsg->setModbusMessage(msg); +} + +/// +/// \brief OutputWidget::updateLogView /// \param request /// \param pdu /// -void OutputWidget::updateTrafficWidget(bool request, int server, const QModbusPdu& pdu) +void OutputWidget::updateLogView(bool request, int server, const QModbusPdu& pdu) { - QByteArray rawData; - rawData.push_back(server); - rawData.push_back(pdu.functionCode() | ( pdu.isException() ? QModbusPdu::ExceptionByte : 0)); - rawData.push_back(pdu.data()); - - QString text; - for(auto&& c : rawData) + auto msg = ui->logView->addItem(pdu, server, QDateTime::currentDateTime(), request); + if(captureMode() == CaptureMode::TextCapture && msg != nullptr) { - switch(dataDisplayMode()) - { - case DataDisplayMode::Decimal: - case DataDisplayMode::Integer: - text+= QString("[%1]").arg(QString::number((uchar)c), 3, '0'); - break; - - default: - text+= QString("[%1]").arg(QString::number((uchar)c, 16), 2, '0'); - break; - } + const auto str = QString("%1: %2 %3 %4").arg( + (msg->isRequest()? "Tx" : "Rx"), + msg->timestamp().toString(Qt::ISODateWithMs), + (msg->isRequest()? "<<" : ">>"), + msg->toString(DataDisplayMode::Hex)); + captureString(str); } - if(text.isEmpty()) return; - - ui->plainTextEdit->moveCursor(QTextCursor::End); - - QTextCharFormat fmt; - fmt.setForeground(request? foregroundColor() : Qt::white); - fmt.setBackground(request? Qt::transparent : Qt::black); - ui->plainTextEdit->mergeCurrentCharFormat(fmt); - - if(request && ui->plainTextEdit->toPlainText().length() > 22000) - ui->plainTextEdit->clear(); - - ui->plainTextEdit->insertPlainText(text); - ui->plainTextEdit->moveCursor(QTextCursor::End); } diff --git a/omodscan/controls/outputwidget.h b/omodscan/controls/outputwidget.h index 904760f..abd7c97 100644 --- a/omodscan/controls/outputwidget.h +++ b/omodscan/controls/outputwidget.h @@ -3,10 +3,12 @@ #include #include +#include #include #include #include "enums.h" -#include +#include "modbusmessage.h" +#include "datasimulator.h" #include "displaydefinition.h" namespace Ui { @@ -103,6 +105,9 @@ class OutputWidget : public QWidget QFont font() const; void setFont(const QFont& font); + int logViewLimit() const; + void setLogViewLimit(int l); + void setStatus(const QString& status); void paint(const QRect& rc, QPainter& painter); @@ -129,7 +134,8 @@ private slots: private: void setUninitializedStatus(); void captureString(const QString& s); - void updateTrafficWidget(bool request, int server, const QModbusPdu& pdu); + void showModbusMessage(const QModelIndex& index); + void updateLogView(bool request, int deviceId, const QModbusPdu& pdu); private: Ui::OutputWidget *ui; diff --git a/omodscan/controls/outputwidget.ui b/omodscan/controls/outputwidget.ui index daa2e68..0314a85 100644 --- a/omodscan/controls/outputwidget.ui +++ b/omodscan/controls/outputwidget.ui @@ -29,7 +29,7 @@ - 0 + 1 @@ -110,10 +110,7 @@ - - - 0 - + 0 @@ -127,28 +124,68 @@ 0 - - - Qt::NoFocus - - - Qt::NoContextMenu - - - QFrame::NoFrame + + + Qt::Vertical - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - Qt::NoTextInteraction - - - 3 + + true + + + + 0 + 2 + + + + true + + + QAbstractItemView::NoEditTriggers + + + true + + + false + + + Qt::IgnoreAction + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + + + + 0 + 1 + + + + Qt::NoFocus + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + @@ -157,6 +194,18 @@ + + + ModbusMessageWidget + QListWidget +
modbusmessagewidget.h
+
+ + ModbusLogWidget + QListView +
modbuslogwidget.h
+
+
diff --git a/omodscan/controls/paritytypecombobox.cpp b/omodscan/controls/paritytypecombobox.cpp index 24e9fb1..20b036b 100644 --- a/omodscan/controls/paritytypecombobox.cpp +++ b/omodscan/controls/paritytypecombobox.cpp @@ -10,6 +10,8 @@ ParityTypeComboBox::ParityTypeComboBox(QWidget* parent) addItem(tr("ODD"), QSerialPort::OddParity); addItem(tr("EVEN"), QSerialPort::EvenParity); addItem(tr("NONE"), QSerialPort::NoParity); + + connect(this, static_cast(&QComboBox::currentIndexChanged), this, &ParityTypeComboBox::on_currentIndexChanged); } /// @@ -30,3 +32,12 @@ void ParityTypeComboBox::setCurrentParity(QSerialPort::Parity parity) const auto idx = findData(parity); setCurrentIndex(idx); } + +/// +/// \brief ParityTypeComboBox::on_currentIndexChanged +/// \param index +/// +void ParityTypeComboBox::on_currentIndexChanged(int index) +{ + emit parityTypeChanged(itemData(index).value()); +} diff --git a/omodscan/controls/paritytypecombobox.h b/omodscan/controls/paritytypecombobox.h index 7cb98de..9953975 100644 --- a/omodscan/controls/paritytypecombobox.h +++ b/omodscan/controls/paritytypecombobox.h @@ -10,11 +10,18 @@ class ParityTypeComboBox : public QComboBox { Q_OBJECT + public: ParityTypeComboBox(QWidget *parent = nullptr); QSerialPort::Parity currentParity() const; void setCurrentParity(QSerialPort::Parity parity); + +signals: + void parityTypeChanged(QSerialPort::Parity parity); + +private slots: + void on_currentIndexChanged(int index); }; #endif // PARITYTYPECOMBOBOX_H diff --git a/omodscan/controls/pointtypecombobox.cpp b/omodscan/controls/pointtypecombobox.cpp index c07e55f..8350404 100644 --- a/omodscan/controls/pointtypecombobox.cpp +++ b/omodscan/controls/pointtypecombobox.cpp @@ -13,7 +13,7 @@ PointTypeComboBox::PointTypeComboBox(QWidget *parent) addItem("03: HOLDING REGISTER", QModbusDataUnit::HoldingRegisters); addItem("04: INPUT REGISTER", QModbusDataUnit::InputRegisters); - connect(this, SIGNAL(currentIndexChanged(int)), this, SLOT(on_currentIndexChanged(int))); + connect(this, static_cast(&QComboBox::currentIndexChanged), this, &PointTypeComboBox::on_currentIndexChanged); } /// diff --git a/omodscan/controls/simulationmodecombobox.cpp b/omodscan/controls/simulationmodecombobox.cpp index 9201f17..7492a2a 100644 --- a/omodscan/controls/simulationmodecombobox.cpp +++ b/omodscan/controls/simulationmodecombobox.cpp @@ -7,8 +7,7 @@ SimulationModeComboBox::SimulationModeComboBox(QWidget *parent) : QComboBox(parent) { - - connect(this, SIGNAL(currentIndexChanged(int)), this, SLOT(on_currentIndexChanged(int))); + connect(this, static_cast(&QComboBox::currentIndexChanged), this, &SimulationModeComboBox::on_currentIndexChanged); } /// diff --git a/omodscan/dialogs/dialogaddressscan.cpp b/omodscan/dialogs/dialogaddressscan.cpp index 56c72a7..b57d8a6 100644 --- a/omodscan/dialogs/dialogaddressscan.cpp +++ b/omodscan/dialogs/dialogaddressscan.cpp @@ -1,91 +1,16 @@ #include #include #include -#include "byteorderutils.h" #include "modbuslimits.h" #include "dialogaddressscan.h" #include "ui_dialogaddressscan.h" -/// -/// \brief formatAddress -/// \param pointType -/// \param address -/// \return -/// -QString formatAddress(QModbusDataUnit::RegisterType pointType, int address) -{ - QString prefix; - switch(pointType) - { - case QModbusDataUnit::Coils: - prefix = "0"; - break; - case QModbusDataUnit::DiscreteInputs: - prefix = "1"; - break; - case QModbusDataUnit::HoldingRegisters: - prefix = "4"; - break; - case QModbusDataUnit::InputRegisters: - prefix = "3"; - break; - default: - break; - } - - return prefix + QStringLiteral("%1").arg(address, 4, 10, QLatin1Char('0')); -} - -/// -/// \brief formatValue -/// \param pointType -/// \param value -/// \param mode -/// \param order -/// \return -/// -QString formatValue(QModbusDataUnit::RegisterType pointType, quint16 value, DataDisplayMode mode, ByteOrder order) -{ - QString result; - value = toByteOrderValue(value, order); - - switch(pointType) - { - case QModbusDataUnit::Coils: - case QModbusDataUnit::DiscreteInputs: - result = QString("%1").arg(value); - break; - case QModbusDataUnit::HoldingRegisters: - case QModbusDataUnit::InputRegisters: - { - switch(mode) - { - case DataDisplayMode::Hex: - result = QStringLiteral("%1H").arg(value, 4, 16, QLatin1Char('0')); - break; - - default: - result = QString("%1").arg(value); - break; - } - } - break; - default: - break; - } - return result.toUpper(); -} - /// /// \brief TableViewItemModel::TableViewItemModel -/// \param data -/// \param columns /// \param parent /// -TableViewItemModel::TableViewItemModel(const ModbusDataUnit& data, int columns, QObject* parent) +TableViewItemModel::TableViewItemModel(QObject* parent) : QAbstractTableModel(parent) - ,_columns(columns) - ,_data(data) { } @@ -125,12 +50,17 @@ QVariant TableViewItemModel::data(const QModelIndex &index, int role) const switch(role) { case Qt::ToolTipRole: - return formatAddress(_data.registerType(), _data.startAddress() + idx); + return formatAddress(_data.registerType(), _data.startAddress() + idx, false); case Qt::DisplayRole: { - const auto mode = _hexView ? DataDisplayMode::Hex : DataDisplayMode::Decimal; - return _data.hasValue(idx) ? formatValue(_data.registerType(), _data.value(idx), mode, _byteOrder) : "-"; + QVariant outValue; + const auto value = _data.value(idx); + const auto pointType = _data.registerType(); + auto result = _hexView ? formatHexValue(pointType, value, _byteOrder, outValue) : + formatDecimalValue(pointType, value, _byteOrder, outValue); + + return _data.hasValue(idx) ? result.remove('<').remove('>') : "-"; } case Qt::TextAlignmentRole: @@ -207,7 +137,7 @@ QVariant TableViewItemModel::headerData(int section, Qt::Orientation orientation const auto pointAddress = _data.startAddress(); const auto addressFrom = pointAddress + section * _columns; const auto addressTo = pointAddress + qMin(length - 1, (section + 1) * _columns - 1); - return QString("%1-%2").arg(formatAddress(pointType, addressFrom), formatAddress(pointType, addressTo)); + return QString("%1-%2").arg(formatAddress(pointType, addressFrom, false), formatAddress(pointType, addressTo, false)); } } break; @@ -232,33 +162,39 @@ Qt::ItemFlags TableViewItemModel::flags(const QModelIndex &index) const } /// -/// \brief LogViewItemModel::LogViewItemModel -/// \param items +/// \brief LogViewModel::LogViewModel /// \param parent /// -LogViewItemModel::LogViewItemModel(QVector& items, QObject* parent) +LogViewModel::LogViewModel(QObject* parent) : QAbstractListModel(parent) - ,_items(items) { } /// -/// \brief LogViewItemModel::rowCount +/// \brief LogViewModel::~LogViewModel +/// +LogViewModel::~LogViewModel() +{ + void deleteItems(); +} + +/// +/// \brief LogViewModel::rowCount /// \param parent /// \return /// -int LogViewItemModel::rowCount(const QModelIndex&) const +int LogViewModel::rowCount(const QModelIndex&) const { return _items.size(); } /// -/// \brief LogViewItemModel::data +/// \brief LogViewModel::data /// \param index /// \param role /// \return /// -QVariant LogViewItemModel::data(const QModelIndex& index, int role) const +QVariant LogViewModel::data(const QModelIndex& index, int role) const { if(!index.isValid() || index.row() < 0 || @@ -271,42 +207,55 @@ QVariant LogViewItemModel::data(const QModelIndex& index, int role) const switch(role) { case Qt::DisplayRole: - return item.Text; + { + const DataDisplayMode mode = _hexView ? DataDisplayMode::Hex : DataDisplayMode::Decimal; + return QString("[%1] %2 [%3]").arg(formatAddress(item.Type, item.Addr, false), + item.Msg->isRequest() ? "<<" : ">>", + item.Msg->toString(mode)); + } case Qt::BackgroundRole: - return item.IsRequest ? QVariant() : QColor(0xDCDCDC); - - case Qt::TextAlignmentRole: - return Qt::AlignVCenter; + return item.Msg->isRequest() ? QVariant() : QColor(0xDCDCDC); case Qt::UserRole: - return QVariant::fromValue(item); + return QVariant::fromValue(item.Msg); } return QVariant(); } /// -/// \brief LogViewItemProxyModel::LogViewItemProxyModel +/// \brief LogViewModel::deleteItems +/// +void LogViewModel::deleteItems() +{ + for(auto&& i : _items) + delete i.Msg; + + _items.clear(); +} + +/// +/// \brief LogViewProxyModel::LogViewProxyModel /// \param parent /// -LogViewItemProxyModel::LogViewItemProxyModel(QObject* parent) +LogViewProxyModel::LogViewProxyModel(QObject* parent) : QSortFilterProxyModel(parent) ,_showValid(false) { } /// -/// \brief LogViewItemProxyModel::filterAcceptsRow +/// \brief LogViewProxyModel::filterAcceptsRow /// \param source_row /// \param source_parent /// \return /// -bool LogViewItemProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +bool LogViewProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { const auto index = sourceModel()->index(source_row, 0, source_parent); - const auto item = sourceModel()->data(index, Qt::UserRole).value(); - return _showValid ? item.IsValid && !item.IsRequest : true; + const auto msg = sourceModel()->data(index, Qt::UserRole).value(); + return _showValid ? msg->isValid() && !msg->isRequest() && !msg->isException() : true; } /// @@ -327,6 +276,13 @@ DialogAddressScan::DialogAddressScan(const DisplayDefinition& dd, DataDisplayMod Qt::WindowCloseButtonHint | Qt::WindowMaximizeButtonHint); + auto viewModel = new TableViewItemModel(this); + ui->tableView->setModel(viewModel); + + auto proxyLogModel = new LogViewProxyModel(this); + proxyLogModel->setSourceModel(new LogViewModel(this)); + ui->logView->setModel(proxyLogModel); + ui->comboBoxPointType->setCurrentPointType(dd.PointType); ui->lineEditStartAddress->setPaddingZeroes(true); ui->lineEditStartAddress->setInputRange(ModbusLimits::addressRange()); @@ -345,6 +301,7 @@ DialogAddressScan::DialogAddressScan(const DisplayDefinition& dd, DataDisplayMod connect(&_scanTimer, &QTimer::timeout, this, &DialogAddressScan::on_timeout); connect(&_modbusClient, &ModbusClient::modbusReply, this, &DialogAddressScan::on_modbusReply); connect(&_modbusClient, &ModbusClient::modbusRequest, this, &DialogAddressScan::on_modbusRequest); + connect(proxyLogModel->sourceModel(), &LogViewModel::rowsInserted, ui->logView, &QListView::scrollToBottom); clearTableView(); } @@ -398,11 +355,9 @@ void DialogAddressScan::on_timeout() /// \param on /// void DialogAddressScan::on_checkBoxHexView_toggled(bool on) -{ - if(!_viewModel) - return; - - _viewModel->setHexView(on); +{ + ((TableViewItemModel*)ui->tableView->model())->setHexView(on); + ((LogViewProxyModel*)ui->logView->model())->setHexView(on); } /// @@ -410,11 +365,8 @@ void DialogAddressScan::on_checkBoxHexView_toggled(bool on) /// \param on /// void DialogAddressScan::on_checkBoxShowValid_toggled(bool on) -{ - if(!_proxyLogModel) - return; - - _proxyLogModel->setShowValid(on); +{ + ((LogViewProxyModel*)ui->logView->model())->setShowValid(on); } /// @@ -423,25 +375,19 @@ void DialogAddressScan::on_checkBoxShowValid_toggled(bool on) /// void DialogAddressScan::on_comboBoxByteOrder_byteOrderChanged(ByteOrder order) { - if(!_viewModel) - return; - - _viewModel->setByteOrder(order); + ((TableViewItemModel*)ui->tableView->model())->setByteOrder(order); } /// /// \brief DialogAddressScan::on_modbusRequest /// \param requestId +/// \param deviceId /// \param request /// -void DialogAddressScan::on_modbusRequest(int requestId, const QModbusRequest& request) +void DialogAddressScan::on_modbusRequest(int requestId, int deviceId, const QModbusRequest& request) { - if(requestId != -1) - { - return; - } - - updateLogView(request); + if(requestId == -1) + updateLogView(deviceId, request); } /// @@ -569,10 +515,7 @@ void DialogAddressScan::clearTableView() const auto pointAddress = ui->lineEditStartAddress->value(); ModbusDataUnit data(pointType, pointAddress, length); - _viewModel = QSharedPointer(new TableViewItemModel(data, 10, this)); - _viewModel->setHexView(ui->checkBoxHexView->isChecked()); - _viewModel->setByteOrder(ui->comboBoxByteOrder->currentByteOrder()); - ui->tableView->setModel(_viewModel.get()); + ((TableViewItemModel*)ui->tableView->model())->reset(data); ui->tableView->resizeColumnsToContents(); ui->tableView->horizontalHeader()->setMinimumSectionSize(80); @@ -585,12 +528,8 @@ void DialogAddressScan::clearTableView() /// void DialogAddressScan::clearLogView() { - _logItems.clear(); - _logModel = QSharedPointer(new LogViewItemModel(_logItems, this)); - _proxyLogModel = QSharedPointer(new LogViewItemProxyModel(this)); - _proxyLogModel->setSourceModel(_logModel.get()); - _proxyLogModel->setShowValid(ui->checkBoxShowValid->isChecked()); - ui->logView->setModel(_proxyLogModel.get()); + auto proxyLogModel = ((LogViewProxyModel*)ui->logView->model()); + proxyLogModel->clear(); } /// @@ -628,15 +567,15 @@ void DialogAddressScan::updateProgress() /// void DialogAddressScan::updateTableView(int pointAddress, QVector values) { - if(!_viewModel) return; - for(int i = 0; i < _viewModel->rowCount(); i++) + auto model = ui->tableView->model(); + for(int i = 0; i < model->rowCount(); i++) { - for(int j = 0; j < _viewModel->columnCount(); j++) + for(int j = 0; j < model->columnCount(); j++) { - const auto index = _viewModel->index(i, j); - if(_viewModel->data(index, Qt::UserRole).toInt() == pointAddress) + const auto index = model->index(i, j); + if(model->data(index, Qt::UserRole).toInt() == pointAddress) { - _viewModel->setData(index, QVariant::fromValue(values), Qt::DisplayRole); + model->setData(index, QVariant::fromValue(values), Qt::DisplayRole); return; } } @@ -645,36 +584,17 @@ void DialogAddressScan::updateTableView(int pointAddress, QVector value /// /// \brief DialogAddressScan::updateLogView +/// \param deviceId /// \param request /// -void DialogAddressScan::updateLogView(const QModbusRequest& request) +void DialogAddressScan::updateLogView(int deviceId, const QModbusRequest& request) { - const auto deviceId = ui->lineEditSlaveAddress->value(); - const auto pointType = ui->comboBoxPointType->currentPointType(); - quint16 pointAddress; request.decodeData(&pointAddress); - const auto address = formatAddress(pointType, pointAddress + 1); - QByteArray rawData; - rawData.push_back(deviceId); - rawData.push_back(request.functionCode() | ( request.isException() ? QModbusPdu::ExceptionByte : 0)); - rawData.push_back(request.data()); - - QStringList textData; - for(auto&& c : rawData) - textData.append(QString("%1").arg(QString::number((uchar)c), 3, '0')); - - LogViewItem item; - item.IsRequest = true; - item.IsValid = true; - item.PointAddress = pointAddress; - item.Text = QString("[%1] << [%2]").arg(address, textData.join(' ')); - - _logItems.push_back(item); - _logModel->update(); - - ui->logView->scrollTo(_proxyLogModel->index(_logItems.size() - 1, 0), QAbstractItemView::PositionAtBottom); + auto proxyLogModel = ((LogViewProxyModel*)ui->logView->model()); + proxyLogModel->append(pointAddress + 1, ui->comboBoxPointType->currentPointType(), + ModbusMessage::create(request, (QModbusAdu::Type)-1, deviceId, QDateTime::currentDateTime(), true)); } /// @@ -686,31 +606,13 @@ void DialogAddressScan::updateLogView(const QModbusReply* reply) if(!reply) return; - const auto deviceId = ui->lineEditSlaveAddress->value(); - const auto pointType = ui->comboBoxPointType->currentPointType(); + const auto deviceId = reply->serverAddress(); const auto pointAddress = reply->property("RequestData").value().startAddress() + 1; - const auto address = formatAddress(pointType, pointAddress); const auto pdu = reply->rawResult(); - QByteArray rawData; - rawData.push_back(deviceId); - rawData.push_back(pdu.functionCode() | ( pdu.isException() ? QModbusPdu::ExceptionByte : 0)); - rawData.push_back(pdu.data()); - - QStringList textData; - for(auto&& c : rawData) - textData.append(QString("%1").arg(QString::number((uchar)c), 3, '0')); - - LogViewItem item; - item.IsRequest = false; - item.IsValid = (reply->error() == QModbusDevice::NoError); - item.PointAddress = pointAddress; - item.Text = QString("[%1] >> [%2]").arg(address, textData.join(' ')); - - _logItems.push_back(item); - _logModel->update(); - - ui->logView->scrollTo(_proxyLogModel->index(_logItems.size() - 1, 0), QAbstractItemView::PositionAtBottom); + auto proxyLogModel = ((LogViewProxyModel*)ui->logView->model()); + proxyLogModel->append(pointAddress, ui->comboBoxPointType->currentPointType(), + ModbusMessage::create(pdu, (QModbusAdu::Type)-1, deviceId, QDateTime::currentDateTime(), false)); } /// @@ -719,7 +621,7 @@ void DialogAddressScan::updateLogView(const QModbusReply* reply) /// void DialogAddressScan::exportPdf(const QString& filename) { - PdfExporter exporter(_viewModel.get(), + PdfExporter exporter(ui->tableView->model(), ui->lineEditStartAddress->text(), ui->lineEditLength->text(), ui->lineEditSlaveAddress->text(), @@ -736,7 +638,7 @@ void DialogAddressScan::exportPdf(const QString& filename) /// void DialogAddressScan::exportCsv(const QString& filename) { - CsvExporter exporter(_viewModel.get(), + CsvExporter exporter(ui->tableView->model(), ui->lineEditStartAddress->text(), ui->lineEditLength->text(), ui->lineEditSlaveAddress->text(), @@ -756,7 +658,7 @@ void DialogAddressScan::exportCsv(const QString& filename) /// \param pointType /// \param parent /// -PdfExporter::PdfExporter(QAbstractTableModel* model, +PdfExporter::PdfExporter(QAbstractItemModel* model, const QString& startAddress, const QString& length, const QString& devId, @@ -992,7 +894,7 @@ void PdfExporter::paintVLine(int top, int bottom, QPainter& painter) /// \param regsOnQuery /// \param parent /// -CsvExporter::CsvExporter(QAbstractTableModel* model, +CsvExporter::CsvExporter(QAbstractItemModel* model, const QString& startAddress, const QString& length, const QString& devId, diff --git a/omodscan/dialogs/dialogaddressscan.h b/omodscan/dialogs/dialogaddressscan.h index 0d154e8..052fa1b 100644 --- a/omodscan/dialogs/dialogaddressscan.h +++ b/omodscan/dialogs/dialogaddressscan.h @@ -6,6 +6,7 @@ #include #include #include +#include "modbusmessage.h" #include "modbusdataunit.h" #include "modbusclient.h" #include "displaydefinition.h" @@ -22,7 +23,7 @@ class TableViewItemModel : public QAbstractTableModel Q_OBJECT public: - explicit TableViewItemModel(const ModbusDataUnit& data, int columns, QObject* parent = nullptr); + explicit TableViewItemModel(QObject* parent = nullptr); int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; @@ -31,6 +32,13 @@ class TableViewItemModel : public QAbstractTableModel QVariant headerData(int section, Qt::Orientation orientation, int role) const override; Qt::ItemFlags flags(const QModelIndex& index) const override; + void reset(const ModbusDataUnit& data, int columns = 10){ + beginResetModel(); + _columns = columns; + _data = data; + endResetModel(); + } + void setHexView(bool on){ beginResetModel(); _hexView = on; @@ -44,57 +52,86 @@ class TableViewItemModel : public QAbstractTableModel } private: - int _columns; + int _columns = 10; ModbusDataUnit _data; bool _hexView = false; ByteOrder _byteOrder = ByteOrder::LittleEndian; }; /// -/// \brief The LogViewItem class -/// -struct LogViewItem -{ - quint16 PointAddress = 0; - QString Text; - bool IsRequest = false; - bool IsValid = false; -}; -Q_DECLARE_METATYPE(LogViewItem) - -/// -/// \brief The LogViewItemModel class +/// \brief The LogViewModel class /// -class LogViewItemModel : public QAbstractListModel +class LogViewModel : public QAbstractListModel { Q_OBJECT public: - explicit LogViewItemModel(QVector& items, QObject* parent = nullptr); + explicit LogViewModel(QObject* parent = nullptr); + ~LogViewModel(); int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role) const override; - void update(){ + void append(quint16 addr, QModbusDataUnit::RegisterType type, const ModbusMessage* msg) { + if(msg == nullptr) return; + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + _items.push_back({ addr, type, msg }); + endInsertRows(); + } + + void clear() { beginResetModel(); + deleteItems(); endResetModel(); } + void setHexView(bool on) { + beginResetModel(); + _hexView = on; + endResetModel(); + } + +private: + void deleteItems(); + private: - QVector& _items; + struct LogViewItem{ + quint16 Addr; + QModbusDataUnit::RegisterType Type; + const ModbusMessage* Msg; + }; + +private: + bool _hexView = false; + QVector _items; }; /// -/// \brief The LogViewItemProxyModel class +/// \brief The LogViewProxyModel class /// -class LogViewItemProxyModel : public QSortFilterProxyModel +class LogViewProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - explicit LogViewItemProxyModel(QObject* parent = nullptr); + explicit LogViewProxyModel(QObject* parent = nullptr); + + void append(quint16 addr, QModbusDataUnit::RegisterType type, const ModbusMessage* msg) { + if(sourceModel()) + ((LogViewModel*)sourceModel())->append(addr, type, msg); + } + + void clear() { + if(sourceModel()) + ((LogViewModel*)sourceModel())->clear(); + } + + void setHexView(bool on) { + if(sourceModel()) + ((LogViewModel*)sourceModel())->setHexView(on); + } - void setShowValid(bool on){ + void setShowValid(bool on) { beginResetModel(); _showValid = on; endResetModel(); @@ -115,7 +152,7 @@ class PdfExporter : public QObject Q_OBJECT public: - explicit PdfExporter(QAbstractTableModel* model, + explicit PdfExporter(QAbstractItemModel* model, const QString& startAddress, const QString& length, const QString& devId, @@ -141,7 +178,7 @@ class PdfExporter : public QObject const int _cy = 4; const int _cx = 10; QRect _pageRect; - QAbstractTableModel* _model; + QAbstractItemModel* _model; const QString _startAddress; const QString _length; const QString _deviceId; @@ -158,7 +195,7 @@ class CsvExporter: public QObject Q_OBJECT public: - explicit CsvExporter(QAbstractTableModel* model, + explicit CsvExporter(QAbstractItemModel* model, const QString& startAddress, const QString& length, const QString& devId, @@ -168,7 +205,7 @@ class CsvExporter: public QObject void exportCsv(const QString& filename); private: - QAbstractTableModel* _model; + QAbstractItemModel* _model; const QString _startAddress; const QString _length; const QString _deviceId; @@ -194,7 +231,7 @@ private slots: void on_awake(); void on_timeout(); void on_modbusReply(QModbusReply* reply); - void on_modbusRequest(int requestId, const QModbusRequest& data); + void on_modbusRequest(int requestId, int deviceId, const QModbusRequest& data); void on_checkBoxHexView_toggled(bool); void on_checkBoxShowValid_toggled(bool); void on_comboBoxByteOrder_byteOrderChanged(ByteOrder); @@ -215,7 +252,7 @@ private slots: void updateProgress(); void updateTableView(int pointAddress, QVector values); - void updateLogView(const QModbusRequest& request); + void updateLogView(int deviceId, const QModbusRequest& request); void updateLogView(const QModbusReply* reply); void exportPdf(const QString& filename); @@ -231,10 +268,6 @@ private slots: quint64 _scanTime = 0; QTimer _scanTimer; ModbusClient& _modbusClient; - QVector _logItems; - QSharedPointer _logModel; - QSharedPointer _proxyLogModel; - QSharedPointer _viewModel; }; #endif // DIALOGADDRESSSCAN_H diff --git a/omodscan/dialogs/dialogdisplaydefinition.cpp b/omodscan/dialogs/dialogdisplaydefinition.cpp index a4c5988..08818c5 100644 --- a/omodscan/dialogs/dialogdisplaydefinition.cpp +++ b/omodscan/dialogs/dialogdisplaydefinition.cpp @@ -5,23 +5,26 @@ /// /// \brief DialogDisplayDefinition::DialogDisplayDefinition +/// \param dd /// \param parent /// -DialogDisplayDefinition::DialogDisplayDefinition(FormModSca* parent) : - QFixedSizeDialog(parent), - ui(new Ui::DialogDisplayDefinition) +DialogDisplayDefinition::DialogDisplayDefinition(DisplayDefinition dd, QWidget* parent) + : QFixedSizeDialog(parent) + ,_displayDefinition(dd) + , ui(new Ui::DialogDisplayDefinition) { ui->setupUi(this); - ui->lineEditScanRate->setInputRange(20, 10000); + ui->lineEditScanRate->setInputRange(20, 36000000); ui->lineEditPointAddress->setInputRange(ModbusLimits::addressRange()); ui->lineEditLength->setInputRange(ModbusLimits::lengthRange()); ui->lineEditSlaveAddress->setInputRange(ModbusLimits::slaveRange()); + ui->lineEditLogLimit->setInputRange(4, 1000); - const auto dd = parent->displayDefinition(); ui->lineEditScanRate->setValue(dd.ScanRate); ui->lineEditPointAddress->setValue(dd.PointAddress); ui->lineEditSlaveAddress->setValue(dd.DeviceId); ui->lineEditLength->setValue(dd.Length); + ui->lineEditLogLimit->setValue(dd.LogViewLimit); ui->comboBoxPointType->setCurrentPointType(dd.PointType); ui->buttonBox->setFocus(); @@ -40,13 +43,12 @@ DialogDisplayDefinition::~DialogDisplayDefinition() /// void DialogDisplayDefinition::accept() { - DisplayDefinition dd; - dd.DeviceId = ui->lineEditSlaveAddress->value(); - dd.PointAddress = ui->lineEditPointAddress->value(); - dd.PointType = ui->comboBoxPointType->currentPointType(); - dd.Length = ui->lineEditLength->value(); - dd.ScanRate = ui->lineEditScanRate->value(); - ((FormModSca*)parentWidget())->setDisplayDefinition(dd); + _displayDefinition.DeviceId = ui->lineEditSlaveAddress->value(); + _displayDefinition.PointAddress = ui->lineEditPointAddress->value(); + _displayDefinition.PointType = ui->comboBoxPointType->currentPointType(); + _displayDefinition.Length = ui->lineEditLength->value(); + _displayDefinition.ScanRate = ui->lineEditScanRate->value(); + _displayDefinition.LogViewLimit = ui->lineEditLogLimit->value(); QFixedSizeDialog::accept(); } diff --git a/omodscan/dialogs/dialogdisplaydefinition.h b/omodscan/dialogs/dialogdisplaydefinition.h index 3edd82b..75cdc01 100644 --- a/omodscan/dialogs/dialogdisplaydefinition.h +++ b/omodscan/dialogs/dialogdisplaydefinition.h @@ -2,7 +2,7 @@ #define DIALOGDISPLAYDEFINITION_H #include "qfixedsizedialog.h" -#include "formmodsca.h" +#include "displaydefinition.h" namespace Ui { class DialogDisplayDefinition; @@ -16,12 +16,17 @@ class DialogDisplayDefinition : public QFixedSizeDialog Q_OBJECT public: - explicit DialogDisplayDefinition(FormModSca* parent); + explicit DialogDisplayDefinition(DisplayDefinition dd, QWidget* parent); ~DialogDisplayDefinition(); + DisplayDefinition displayDefinition() const { + return _displayDefinition; + } + void accept() override; private: + DisplayDefinition _displayDefinition; Ui::DialogDisplayDefinition *ui; }; diff --git a/omodscan/dialogs/dialogdisplaydefinition.ui b/omodscan/dialogs/dialogdisplaydefinition.ui index 4fbf4f0..bde2209 100644 --- a/omodscan/dialogs/dialogdisplaydefinition.ui +++ b/omodscan/dialogs/dialogdisplaydefinition.ui @@ -7,7 +7,7 @@ 0 0 350 - 262 + 277 @@ -15,68 +15,104 @@ - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 40 + + + 9 + + + 9 + + + 9 + + Scan Rate: - - - - - 0 - 0 - - - - - 0 - 25 - - - - - 60 - 16777215 - - - + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + + 60 + 16777215 + + + + + + + + (msecs) + + + + - - + + - (msecs) + Log View Limit: - - - - Qt::Horizontal - - - - 40 - 20 - - - + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + + 60 + 16777215 + + + + + + + + (rows) + + + + diff --git a/omodscan/dialogs/dialogforcemultiplecoils.cpp b/omodscan/dialogs/dialogforcemultiplecoils.cpp index b2013fd..38ce387 100644 --- a/omodscan/dialogs/dialogforcemultiplecoils.cpp +++ b/omodscan/dialogs/dialogforcemultiplecoils.cpp @@ -1,5 +1,6 @@ #include #include +#include "formatutils.h" #include "dialogforcemultiplecoils.h" #include "ui_dialogforcemultiplecoils.h" @@ -18,8 +19,9 @@ DialogForceMultipleCoils::DialogForceMultipleCoils(ModbusWriteParams& params, in Qt::CustomizeWindowHint | Qt::WindowTitleHint); - ui->labelAddress->setText(QString(tr("Address: %1")).arg(params.Address, 5, 10, QLatin1Char('0'))); - ui->labelLength->setText(QString(tr("Length: %1")).arg(length, 3, 10, QLatin1Char('0'))); + ui->labelAddress->setText(QString(tr("Address: %1")).arg(formatAddress(QModbusDataUnit::Coils, params.Address, false))); + ui->labelLength->setText(QString(tr("Length: %1")).arg(length, 3, 10, QLatin1Char('0'))); + ui->labelSlaveDevice->setText(QString(tr("Slave Device: %1")).arg(params.Node, 2, 10, QLatin1Char('0'))); _data = params.Value.value>(); if(_data.length() != length) _data.resize(length); @@ -107,8 +109,8 @@ void DialogForceMultipleCoils::updateTableWidget() for(int i = 0; i < ui->tableWidget->rowCount(); i++) { - const auto addressFrom = QString("%1").arg(_writeParams.Address + i * columns, 5, 10, QLatin1Char('0')); - const auto addressTo = QString("%1").arg(_writeParams.Address + qMin(length - 1, (i + 1) * columns - 1), 5, 10, QLatin1Char('0')); + const auto addressFrom = formatAddress(QModbusDataUnit::Coils, _writeParams.Address + i * columns, false); + const auto addressTo = formatAddress(QModbusDataUnit::Coils, _writeParams.Address + qMin(length - 1, (i + 1) * columns - 1), false); ui->tableWidget->setVerticalHeaderItem(i, new QTableWidgetItem(QString("%1-%2").arg(addressFrom, addressTo))); for(int j = 0; j < columns; j++) @@ -119,7 +121,7 @@ void DialogForceMultipleCoils::updateTableWidget() auto item = new QTableWidgetItem(QString::number(_data[idx])); item->setData(Qt::UserRole, idx); item->setTextAlignment(Qt::AlignCenter); - item->setToolTip(QString("%1").arg(_writeParams.Address + idx, 5, 10, QLatin1Char('0'))); + item->setToolTip(formatAddress(QModbusDataUnit::Coils,_writeParams.Address + idx, false)); ui->tableWidget->setItem(i, j, item); } else diff --git a/omodscan/dialogs/dialogforcemultiplecoils.ui b/omodscan/dialogs/dialogforcemultiplecoils.ui index fe44177..88723ba 100644 --- a/omodscan/dialogs/dialogforcemultiplecoils.ui +++ b/omodscan/dialogs/dialogforcemultiplecoils.ui @@ -22,14 +22,69 @@ - - - - Address: + + + + Qt::Horizontal - + + + 40 + 20 + + + + + + + + + + Slave Device: + + + + + + + Address: + + + Qt::AlignCenter + + + + + + + Length: + + + Qt::AlignCenter + + + + - + + + + + + Set Values to 0 + + + + + + + Set Values to 1 + + + + + + Qt::NoFocus @@ -63,38 +118,11 @@ - - + + Qt::Horizontal - - - 40 - 20 - - - - - - - - Set Values to 0 - - - - - - - Set Values to 1 - - - - - - - Length: - diff --git a/omodscan/dialogs/dialogforcemultipleregisters.cpp b/omodscan/dialogs/dialogforcemultipleregisters.cpp index a284d99..d5ef0fa 100644 --- a/omodscan/dialogs/dialogforcemultipleregisters.cpp +++ b/omodscan/dialogs/dialogforcemultipleregisters.cpp @@ -1,6 +1,7 @@ #include #include -#include "floatutils.h" +#include "formatutils.h" +#include "numericutils.h" #include "numericlineedit.h" #include "dialogforcemultipleregisters.h" #include "ui_dialogforcemultipleregisters.h" @@ -21,8 +22,9 @@ DialogForceMultipleRegisters::DialogForceMultipleRegisters(ModbusWriteParams& pa Qt::CustomizeWindowHint | Qt::WindowTitleHint); - ui->labelAddress->setText(QString(tr("Address: %1")).arg(params.Address, 5, 10, QLatin1Char('0'))); - ui->labelLength->setText(QString(tr("Length: %1")).arg(length, 3, 10, QLatin1Char('0'))); + ui->labelAddress->setText(QString(tr("Address: %1")).arg(formatAddress(QModbusDataUnit::HoldingRegisters, params.Address, _hexView))); + ui->labelLength->setText(QString(tr("Length: %1")).arg(length, 3, 10, QLatin1Char('0'))); + ui->labelSlaveDevice->setText(QString(tr("Slave Device: %1")).arg(params.Node, 2, 10, QLatin1Char('0'))); _data = params.Value.value>(); if(_data.length() != length) _data.resize(length); @@ -270,8 +272,8 @@ NumericLineEdit* DialogForceMultipleRegisters::createNumEdit(int idx) case DataDisplayMode::SwappedUnsignedLI: if(!(idx % 2) && (idx + 1 < _data.size())) { - numEdit = new NumericLineEdit(NumericLineEdit::UnsignedMode, ui->tableWidget); - numEdit->setValue(makeULong(_data[idx + 1], _data[idx], _writeParams.Order)); + numEdit = new NumericLineEdit(NumericLineEdit::UnsignedMode, ui->tableWidget); + numEdit->setValue(makeULong(_data[idx + 1], _data[idx], _writeParams.Order)); } break; @@ -313,7 +315,7 @@ NumericLineEdit* DialogForceMultipleRegisters::createNumEdit(int idx) numEdit->setFrame(false); numEdit->setMaximumWidth(80); numEdit->setAlignment(Qt::AlignCenter); - numEdit->setToolTip(QString("%1").arg(_writeParams.Address + idx, 5, 10, QLatin1Char('0'))); + numEdit->setToolTip(formatAddress(QModbusDataUnit::HoldingRegisters, _writeParams.Address + idx, _hexView)); } return numEdit; @@ -354,8 +356,8 @@ void DialogForceMultipleRegisters::updateTableWidget() for(int i = 0; i < ui->tableWidget->rowCount(); i++) { - const auto addressFrom = QString("%1").arg(_writeParams.Address + i * columns, 5, 10, QLatin1Char('0')); - const auto addressTo = QString("%1").arg(_writeParams.Address + qMin(length - 1, (i + 1) * columns - 1), 5, 10, QLatin1Char('0')); + const auto addressFrom = formatAddress(QModbusDataUnit::HoldingRegisters, _writeParams.Address + i * columns, _hexView); + const auto addressTo = formatAddress(QModbusDataUnit::HoldingRegisters, _writeParams.Address + qMin(length - 1, (i + 1) * columns - 1), _hexView); ui->tableWidget->setVerticalHeaderItem(i, new QTableWidgetItem(QString("%1-%2").arg(addressFrom, addressTo))); for(int j = 0; j < columns; j++) diff --git a/omodscan/dialogs/dialogforcemultipleregisters.h b/omodscan/dialogs/dialogforcemultipleregisters.h index 2ea7cb2..8ea3c86 100644 --- a/omodscan/dialogs/dialogforcemultipleregisters.h +++ b/omodscan/dialogs/dialogforcemultipleregisters.h @@ -36,6 +36,7 @@ private slots: Ui::DialogForceMultipleRegisters *ui; QVector _data; ModbusWriteParams& _writeParams; + bool _hexView = false; }; #endif // DIALOGFORCEMULTIPLEREGISTERS_H diff --git a/omodscan/dialogs/dialogforcemultipleregisters.ui b/omodscan/dialogs/dialogforcemultipleregisters.ui index b8740c1..0ce920b 100644 --- a/omodscan/dialogs/dialogforcemultipleregisters.ui +++ b/omodscan/dialogs/dialogforcemultipleregisters.ui @@ -22,48 +22,35 @@ - - - - Length: + + + + 10 - - - - - - Set Random Values - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Set Values to 0 - - - - - - - Address: - - + + + + Slave Device: + + + + + + + Address: + + + + + + + Length: + + + + - + Qt::NoFocus @@ -97,6 +84,56 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + Set Values to 0 + + + + + + + + 0 + 0 + + + + Set Random Values + + + + + + + + + Qt::Horizontal + + + diff --git a/omodscan/dialogs/dialogmodbusscanner.cpp b/omodscan/dialogs/dialogmodbusscanner.cpp index 8bfae46..a01fc90 100644 --- a/omodscan/dialogs/dialogmodbusscanner.cpp +++ b/omodscan/dialogs/dialogmodbusscanner.cpp @@ -135,7 +135,7 @@ void DialogModbusScanner::on_awake() ui->groupBoxSubnetMask->setEnabled(!inProgress); ui->groupBoxRequest->setEnabled(!inProgress); ui->pushButtonClear->setEnabled(!inProgress); - ui->pushButtonScan->setEnabled(ui->comboBoxSerial->count() > 0); + ui->pushButtonScan->setEnabled((rtuScanning && ui->comboBoxSerial->count() > 0) || !rtuScanning); ui->pushButtonScan->setText(inProgress ? tr("Stop Scan") : tr("Start Scan")); } diff --git a/omodscan/dialogs/dialogmodbusscanner.ui b/omodscan/dialogs/dialogmodbusscanner.ui index db20b5c..aa65252 100644 --- a/omodscan/dialogs/dialogmodbusscanner.ui +++ b/omodscan/dialogs/dialogmodbusscanner.ui @@ -7,7 +7,7 @@ 0 0 707 - 863 + 810 @@ -204,15 +204,28 @@ Address Range - - + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + from - + + + + to + + + + @@ -226,16 +239,15 @@ 25 - - - - - - to + + + 100 + 16777215 + - + @@ -249,6 +261,12 @@ 25 + + + 100 + 16777215 + + @@ -285,6 +303,12 @@ Port Range + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + @@ -356,7 +380,7 @@ - 150 + 152 60 @@ -559,6 +583,9 @@ font-weight: bold; Device Id + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignHCenter|Qt::AlignTop diff --git a/omodscan/dialogs/dialogmsgparser.cpp b/omodscan/dialogs/dialogmsgparser.cpp new file mode 100644 index 0000000..0d1e2f7 --- /dev/null +++ b/omodscan/dialogs/dialogmsgparser.cpp @@ -0,0 +1,122 @@ +#include +#include +#include "dialogmsgparser.h" +#include "ui_dialogmsgparser.h" + +/// +/// \brief DialogMsgParser::DialogMsgParser +/// \param mode +/// \param parent +/// +DialogMsgParser::DialogMsgParser(DataDisplayMode mode, QWidget *parent) + : QDialog(parent) + , ui(new Ui::DialogMsgParser) + ,_mm(nullptr) +{ + ui->setupUi(this); + + setWindowFlags(Qt::Dialog | + Qt::CustomizeWindowHint | + Qt::WindowTitleHint); + + ui->info->setShowTimestamp(false); + ui->hexView->setCheckState(mode == DataDisplayMode::Hex ? Qt::Checked : Qt::Unchecked); + + auto dispatcher = QAbstractEventDispatcher::instance(); + connect(dispatcher, &QAbstractEventDispatcher::awake, this, &DialogMsgParser::on_awake); +} + +/// +/// \brief DialogMsgParser::~DialogMsgParser +/// +DialogMsgParser::~DialogMsgParser() +{ + delete ui; + if(_mm) delete _mm; +} + +/// +/// \brief DialogMsgParser::changeEvent +/// \param event +/// +void DialogMsgParser::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } + + QDialog::changeEvent(event); +} + +/// +/// \brief DialogMsgParser::on_awake +/// +void DialogMsgParser::on_awake() +{ + ui->deviceIdIncluded->setVisible(ui->buttonPdu->isChecked()); + ui->pushButtonParse->setEnabled(!ui->bytesData->isEmpty()); +} + +/// +/// \brief DialogMsgParser::on_hexView_toggled +/// \param checked +/// +void DialogMsgParser::on_hexView_toggled(bool checked) +{ + ui->bytesData->setInputMode(checked ? ByteListTextEdit::HexMode : ByteListTextEdit::DecMode); + ui->info->setDataDisplayMode(checked ? DataDisplayMode::Hex : DataDisplayMode::Decimal); +} + +/// +/// \brief DialogMsgParser::on_bytesData_valueChanged +/// \param value +/// +void DialogMsgParser::on_bytesData_valueChanged(const QByteArray& value) +{ + if(value.size() < 3) + { + if(value.isEmpty()) + ui->info->clear(); + + return; + } + + const auto data = value.left(value.size() - 2); + const int checksum = makeWord(value[value.size() - 1], value[value.size() - 2], ByteOrder::LittleEndian); + ui->checksumIncluded->setChecked(QModbusAdu::calculateCRC(data, data.size()) == checksum); +} + +/// +/// \brief DialogMsgParser::on_pushButtonParse_clicked +/// +void DialogMsgParser::on_pushButtonParse_clicked() +{ + auto data = ui->bytesData->value(); + if(data.isEmpty()) return; + + ModbusMessage::Type type = ModbusMessage::Adu; + auto protocol = QModbusAdu::Tcp; + int checksum = 0; + + if(ui->buttonPdu->isChecked()) + { + type = ModbusMessage::Pdu; + } + + if(!ui->deviceIdIncluded->isChecked()) + { + data.push_front('\0'); + } + + if(ui->checksumIncluded->isChecked()) + { + protocol = QModbusAdu::Rtu; + checksum = makeWord(data[data.size() - 1], data[data.size() - 2], ByteOrder::LittleEndian); + data = data.left(data.size() - 2); + } + + if(_mm) delete _mm; + _mm = ModbusMessage::parse(data, type, protocol, ui->request->isChecked(), checksum); + ui->info->setModbusMessage(_mm); +} diff --git a/omodscan/dialogs/dialogmsgparser.h b/omodscan/dialogs/dialogmsgparser.h new file mode 100644 index 0000000..54b22bc --- /dev/null +++ b/omodscan/dialogs/dialogmsgparser.h @@ -0,0 +1,37 @@ +#ifndef DIALOGMSGPARSER_H +#define DIALOGMSGPARSER_H + +#include +#include "enums.h" +#include "modbusmessage.h" + +namespace Ui { +class DialogMsgParser; +} + +/// +/// \brief The DialogMsgParser class +/// +class DialogMsgParser : public QDialog +{ + Q_OBJECT + +public: + explicit DialogMsgParser(DataDisplayMode mode, QWidget *parent = nullptr); + ~DialogMsgParser(); + +protected: + void changeEvent(QEvent* event) override; + +private slots: + void on_awake(); + void on_hexView_toggled(bool); + void on_bytesData_valueChanged(const QByteArray& value); + void on_pushButtonParse_clicked(); + +private: + Ui::DialogMsgParser *ui; + const ModbusMessage* _mm; +}; + +#endif // DIALOGMSGPARSER_H diff --git a/omodscan/dialogs/dialogmsgparser.ui b/omodscan/dialogs/dialogmsgparser.ui new file mode 100644 index 0000000..fd04c62 --- /dev/null +++ b/omodscan/dialogs/dialogmsgparser.ui @@ -0,0 +1,298 @@ + + + DialogMsgParser + + + + 0 + 0 + 662 + 424 + + + + Modbus Message Parser + + + + + + + + padding: 4px; + + + PDU Message + + + true + + + true + + + true + + + false + + + true + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + + + + padding: 4px; + + + ADU Message + + + true + + + true + + + false + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::RightToLeft + + + Hex View + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Request + + + + + + + Device Id included + + + true + + + + + + + Checksum included + + + + + + + + + Qt::Vertical + + + + + 0 + 1 + + + + Enter bytes value separated by spaces + + + + + + 0 + 2 + + + + Qt::NoFocus + + + Qt::NoContextMenu + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::NoSelection + + + true + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Parse + + + false + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + ModbusMessageWidget + QListWidget +
modbusmessagewidget.h
+
+ + ByteListTextEdit + QPlainTextEdit +
bytelisttextedit.h
+
+
+ + + + buttonBox + accepted() + DialogMsgParser + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DialogMsgParser + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/omodscan/dialogs/dialogsetuppresetdata.cpp b/omodscan/dialogs/dialogsetuppresetdata.cpp index 7e860af..fa04eb7 100644 --- a/omodscan/dialogs/dialogsetuppresetdata.cpp +++ b/omodscan/dialogs/dialogsetuppresetdata.cpp @@ -16,7 +16,6 @@ DialogSetupPresetData::DialogSetupPresetData(SetupPresetParams& params, QModbus ui->setupUi(this); ui->lineEditSlaveDevice->setInputRange(ModbusLimits::slaveRange()); ui->lineEditAddress->setInputRange(ModbusLimits::addressRange()); - ui->lineEditNumberOfPoints->setInputRange(ModbusLimits::lengthRange()); ui->lineEditSlaveDevice->setValue(params.SlaveAddress); ui->lineEditAddress->setValue(params.PointAddress); ui->lineEditNumberOfPoints->setValue(params.Length); @@ -25,9 +24,11 @@ DialogSetupPresetData::DialogSetupPresetData(SetupPresetParams& params, QModbus { case QModbusDataUnit::Coils: setWindowTitle("15: FORCE MULTIPLE COILS"); + ui->lineEditNumberOfPoints->setInputRange(1, 1968); break; case QModbusDataUnit::HoldingRegisters: setWindowTitle("16: FORCE MULTIPLE REGISTERS"); + ui->lineEditNumberOfPoints->setInputRange(1, 123); break; default: break; diff --git a/omodscan/dialogs/dialogusermsg.cpp b/omodscan/dialogs/dialogusermsg.cpp index cd4379c..8ad38a5 100644 --- a/omodscan/dialogs/dialogusermsg.cpp +++ b/omodscan/dialogs/dialogusermsg.cpp @@ -9,19 +9,28 @@ /// /// \brief DialogUserMsg::DialogUserMsg /// \param slaveAddress +/// \param func /// \param mode +/// \param client /// \param parent /// -DialogUserMsg::DialogUserMsg(quint8 slaveAddress, DataDisplayMode mode, ModbusClient& client, QWidget *parent) : - QFixedSizeDialog(parent) +DialogUserMsg::DialogUserMsg(quint8 slaveAddress, QModbusPdu::FunctionCode func, DataDisplayMode mode, ModbusClient& client, QWidget *parent) + : QDialog(parent) , ui(new Ui::DialogUserMsg) + ,_mm(nullptr) ,_modbusClient(client) { ui->setupUi(this); + setWindowFlags(Qt::Dialog | + Qt::CustomizeWindowHint | + Qt::WindowTitleHint); + ui->lineEditSlaveAddress->setInputRange(ModbusLimits::slaveRange()); ui->lineEditSlaveAddress->setValue(slaveAddress); - ui->lineEditFunction->setInputRange(0, 255); + ui->comboBoxFunction->addItems(ModbusFunction::validCodes()); + ui->comboBoxFunction->setCurrentFunctionCode(func); + ui->responseInfo->setShowTimestamp(false); switch(mode) { @@ -34,8 +43,7 @@ DialogUserMsg::DialogUserMsg(quint8 slaveAddress, DataDisplayMode mode, ModbusCl break; } - ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Send"); - + ui->sendData->setFocus(); connect(&_modbusClient, &ModbusClient::modbusReply, this, &DialogUserMsg::on_modbusReply); } @@ -45,31 +53,47 @@ DialogUserMsg::DialogUserMsg(quint8 slaveAddress, DataDisplayMode mode, ModbusCl DialogUserMsg::~DialogUserMsg() { delete ui; + if(_mm) delete _mm; +} + +/// +/// \brief DialogUserMsg::changeEvent +/// \param event +/// +void DialogUserMsg::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } + + QDialog::changeEvent(event); } /// -/// \brief DialogUserMsg::accept +/// \brief DialogUserMsg::on_pushButtonSend_clicked /// -void DialogUserMsg::accept() +void DialogUserMsg::on_pushButtonSend_clicked() { - ui->lineEditResponse->clear(); + ui->responseBuffer->clear(); + ui->responseInfo->clear(); if(_modbusClient.state() != QModbusDevice::ConnectedState) { - QMessageBox::warning(this, parentWidget()->windowTitle(), "Custom Cmd Write Failure"); + QMessageBox::warning(this, windowTitle(), tr("No connection to device")); return; } - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + ui->pushButtonSend->setEnabled(false); QModbusRequest request; - request.setFunctionCode(ui->lineEditFunction->value()); - request.setData(ui->lineEditSendData->value()); + request.setFunctionCode(ui->comboBoxFunction->currentFunctionCode()); + request.setData(ui->sendData->value()); _modbusClient.sendRawRequest(request, ui->lineEditSlaveAddress->value(), 0); const auto timeout = _modbusClient.timeout() * _modbusClient.numberOfRetries(); - QTimer::singleShot(timeout, this, [&] { ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); }); + QTimer::singleShot(timeout, this, [&] { ui->pushButtonSend->setEnabled(true); }); } /// @@ -85,15 +109,20 @@ void DialogUserMsg::on_modbusReply(QModbusReply* reply) return; } - const auto raw = reply->rawResult(); + if(reply->error() != QModbusDevice::NoError && + reply->error() != QModbusDevice::ProtocolError) + { + QMessageBox::warning(this, windowTitle(), reply->errorString()); + return; + } + + if(_mm) delete _mm; + _mm = ModbusMessage::create(reply->rawResult(), QModbusAdu::Tcp, reply->serverAddress(), QDateTime::currentDateTime(), false); - QByteArray data; - data.push_back(reply->serverAddress()); - data.push_back(raw.functionCode() | ( raw.isException() ? QModbusPdu::ExceptionByte : 0)); - data.push_back(raw.data()); + ui->responseBuffer->setValue(*_mm); + ui->responseInfo->setModbusMessage(_mm); - ui->lineEditResponse->setValue(data); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + ui->pushButtonSend->setEnabled(true); } /// @@ -104,12 +133,10 @@ void DialogUserMsg::on_radioButtonHex_clicked(bool checked) { if(checked) { - ui->lineEditSlaveAddress->setPaddingZeroes(true); - ui->lineEditSlaveAddress->setInputMode(NumericLineEdit::HexMode); - ui->lineEditFunction->setPaddingZeroes(true); - ui->lineEditFunction->setInputMode(NumericLineEdit::HexMode); - ui->lineEditSendData->setInputMode(ByteListLineEdit::HexMode); - ui->lineEditResponse->setInputMode(ByteListLineEdit::HexMode); + ui->comboBoxFunction->setInputMode(FunctionCodeComboBox::HexMode); + ui->sendData->setInputMode(ByteListTextEdit::HexMode); + ui->responseBuffer->setInputMode(ByteListTextEdit::HexMode); + ui->responseInfo->setDataDisplayMode(DataDisplayMode::Hex); } } @@ -121,11 +148,9 @@ void DialogUserMsg::on_radioButtonDecimal_clicked(bool checked) { if(checked) { - ui->lineEditSlaveAddress->setPaddingZeroes(false); - ui->lineEditSlaveAddress->setInputMode(NumericLineEdit::DecMode); - ui->lineEditFunction->setPaddingZeroes(false); - ui->lineEditFunction->setInputMode(NumericLineEdit::DecMode); - ui->lineEditSendData->setInputMode(ByteListLineEdit::DecMode); - ui->lineEditResponse->setInputMode(ByteListLineEdit::DecMode); + ui->comboBoxFunction->setInputMode(FunctionCodeComboBox::DecMode); + ui->sendData->setInputMode(ByteListTextEdit::DecMode); + ui->responseBuffer->setInputMode(ByteListTextEdit::DecMode); + ui->responseInfo->setDataDisplayMode(DataDisplayMode::Decimal); } } diff --git a/omodscan/dialogs/dialogusermsg.h b/omodscan/dialogs/dialogusermsg.h index 26a17ee..84b460c 100644 --- a/omodscan/dialogs/dialogusermsg.h +++ b/omodscan/dialogs/dialogusermsg.h @@ -2,31 +2,35 @@ #define DIALOGUSERMSG_H #include -#include "qfixedsizedialog.h" +#include #include "modbusclient.h" +#include "modbusmessage.h" #include "enums.h" namespace Ui { class DialogUserMsg; } -class DialogUserMsg : public QFixedSizeDialog +class DialogUserMsg : public QDialog { Q_OBJECT public: - explicit DialogUserMsg(quint8 slaveAddres, DataDisplayMode mode, ModbusClient& client, QWidget *parent = nullptr); + explicit DialogUserMsg(quint8 slaveAddres, QModbusPdu::FunctionCode func, DataDisplayMode mode, ModbusClient& client, QWidget *parent = nullptr); ~DialogUserMsg(); - void accept() override; +protected: + void changeEvent(QEvent* event) override; private slots: void on_modbusReply(QModbusReply* reply); void on_radioButtonHex_clicked(bool checked); void on_radioButtonDecimal_clicked(bool checked); + void on_pushButtonSend_clicked(); private: Ui::DialogUserMsg *ui; + const ModbusMessage* _mm; ModbusClient& _modbusClient; }; diff --git a/omodscan/dialogs/dialogusermsg.ui b/omodscan/dialogs/dialogusermsg.ui index 9ed16ca..0701fd5 100644 --- a/omodscan/dialogs/dialogusermsg.ui +++ b/omodscan/dialogs/dialogusermsg.ui @@ -6,8 +6,8 @@ 0 0 - 442 - 200 + 574 + 389 @@ -18,6 +18,9 @@ + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + @@ -39,6 +42,9 @@ 16777215 + + Qt::NoContextMenu + @@ -49,19 +55,19 @@
- - - - 0 - 0 - - - + + - 40 - 16777215 + 200 + 0 + + Qt::NoContextMenu + + + true +
@@ -85,7 +91,7 @@ - + 0 0 @@ -116,9 +122,6 @@ Qt::Horizontal - - QSizePolicy::Fixed - 40 @@ -139,7 +142,29 @@ - + + + + 0 + 0 + + + + + 16777215 + 52 + + + + Qt::ImhNone + + + true + + + Enter bytes value separated by spaces + +
@@ -148,48 +173,135 @@ Response Buffer - + - - - true + + + Qt::Vertical + + + + 0 + 1 + + + + false + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + 0 + 3 + + + + Qt::NoFocus + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + true + + - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - true - - + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Send + + + false + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - NumericLineEdit - QLineEdit -
numericlineedit.h
+ ModbusMessageWidget + QListWidget +
modbusmessagewidget.h
- ByteListLineEdit + ByteListTextEdit + QPlainTextEdit +
bytelisttextedit.h
+
+ + FunctionCodeComboBox + QComboBox +
functioncodecombobox.h
+
+ + NumericLineEdit QLineEdit -
bytelistlineedit.h
+
numericlineedit.h
- lineEditFunction - lineEditSendData - lineEditResponse radioButtonDecimal radioButtonHex lineEditSlaveAddress diff --git a/omodscan/displaydefinition.h b/omodscan/displaydefinition.h index c7a01c2..ca0208c 100644 --- a/omodscan/displaydefinition.h +++ b/omodscan/displaydefinition.h @@ -15,14 +15,16 @@ struct DisplayDefinition quint16 PointAddress = 1; QModbusDataUnit::RegisterType PointType = QModbusDataUnit::Coils; quint16 Length = 50; + quint16 LogViewLimit = 30; void normalize() { - ScanRate = qBound(20U, ScanRate, 10000U); + ScanRate = qBound(20U, ScanRate, 3600000U); DeviceId = qMax(ModbusLimits::slaveRange().from(), DeviceId); PointAddress = qMax(ModbusLimits::addressRange().from(), PointAddress); PointType = qBound(QModbusDataUnit::DiscreteInputs, PointType, QModbusDataUnit::HoldingRegisters); Length = qBound(ModbusLimits::lengthRange().from(), Length, ModbusLimits::lengthRange().to()); + LogViewLimit = qBound(4, LogViewLimit, 1000); } }; Q_DECLARE_METATYPE(DisplayDefinition) @@ -34,6 +36,7 @@ inline QSettings& operator <<(QSettings& out, const DisplayDefinition& dd) out.setValue("DisplayDefinition/PointAddress", dd.PointAddress); out.setValue("DisplayDefinition/PointType", dd.PointType); out.setValue("DisplayDefinition/Length", dd.Length); + out.setValue("DisplayDefinition/LogViewLimit", dd.LogViewLimit); return out; } @@ -51,6 +54,7 @@ inline QSettings& operator >>(QSettings& in, DisplayDefinition& dd) dd.PointAddress = in.value("DisplayDefinition/PointAddress", 1).toUInt(); dd.PointType = (QModbusDataUnit::RegisterType)in.value("DisplayDefinition/PointType", 1).toUInt(); dd.Length = in.value("DisplayDefinition/Length", 50).toUInt(); + dd.LogViewLimit = in.value("DisplayDefinition/LogViewLimit", 30).toUInt(); dd.normalize(); return in; diff --git a/omodscan/formatutils.h b/omodscan/formatutils.h new file mode 100644 index 0000000..c9d964a --- /dev/null +++ b/omodscan/formatutils.h @@ -0,0 +1,399 @@ +#ifndef FORMATUTILS_H +#define FORMATUTILS_H + +#include +#include +#include +#include +#include "enums.h" +#include "numericutils.h" +#include "byteorderutils.h" + + +/// +/// \brief formatByteValue +/// \param mode +/// \param c +/// \return +/// +inline QString formatByteValue(DataDisplayMode mode, quint8 c) +{ + switch(mode) + { + case DataDisplayMode::Decimal: + case DataDisplayMode::Integer: + return QString("%1").arg(QString::number(c), 3, '0'); + + default: + return QString("0x%1").arg(QString::number(c, 16).toUpper(), 2, '0'); + } +} + +/// +/// \brief formatByteArray +/// \param mode +/// \param ar +/// \return +/// +inline QString formatByteArray(DataDisplayMode mode, const QByteArray& ar) +{ + QStringList values; + for(quint8 i : ar) + switch(mode) + { + case DataDisplayMode::Decimal: + case DataDisplayMode::Integer: + values += QString("%1").arg(QString::number(i), 3, '0'); + break; + + default: + values += QString("%1").arg(QString::number(i, 16).toUpper(), 2, '0'); + break; + } + + return values.join(" "); +} + +/// +/// \brief formatWordArray +/// \param mode +/// \param ar +/// \param order +/// \return +/// +inline QString formatWordArray(DataDisplayMode mode, const QByteArray& ar, ByteOrder order) +{ + QStringList values; + for(int i = 0; i < ar.size(); i+=2) + { + const quint16 value = makeWord(ar[i+1], ar[i], order); + switch(mode) + { + case DataDisplayMode::Decimal: + case DataDisplayMode::Integer: + values += QString("%1").arg(QString::number(value), 5, '0'); + break; + + default: + values += QString("0x%1").arg(QString::number(value, 16).toUpper(), 4, '0'); + break; + } + } + + return values.join(" "); +} + +/// +/// \brief formatWordValue +/// \param mode +/// \param v +/// \return +/// +inline QString formatWordValue(DataDisplayMode mode, quint16 v) +{ + switch(mode) + { + case DataDisplayMode::Decimal: + case DataDisplayMode::Integer: + return QString("%1").arg(QString::number(v), 5, '0'); + + default: + return QString("0x%1").arg(QString::number(v, 16).toUpper(), 4, '0'); + } +} + +/// +/// \brief formatBinaryValue +/// \param pointType +/// \param value +/// \param outValue +/// \return +/// +inline QString formatBinaryValue(QModbusDataUnit::RegisterType pointType, quint16 value, ByteOrder order, QVariant& outValue) +{ + QString result; + value = toByteOrderValue(value, order); + + switch(pointType) + { + case QModbusDataUnit::Coils: + case QModbusDataUnit::DiscreteInputs: + result = QString("<%1>").arg(value); + break; + case QModbusDataUnit::HoldingRegisters: + case QModbusDataUnit::InputRegisters: + result = QStringLiteral("<%1>").arg(value, 16, 2, QLatin1Char('0')); + break; + default: + break; + } + outValue = value; + return result; +} + +/// +/// \brief formatDecimalValue +/// \param pointType +/// \param value +/// \param outValue +/// \return +/// +inline QString formatDecimalValue(QModbusDataUnit::RegisterType pointType, quint16 value, ByteOrder order, QVariant& outValue) +{ + QString result; + value = toByteOrderValue(value, order); + + switch(pointType) + { + case QModbusDataUnit::Coils: + case QModbusDataUnit::DiscreteInputs: + result = QStringLiteral("<%1>").arg(value, 1, 10, QLatin1Char('0')); + break; + case QModbusDataUnit::HoldingRegisters: + case QModbusDataUnit::InputRegisters: + result = QStringLiteral("<%1>").arg(value, 5, 10, QLatin1Char('0')); + break; + default: + break; + } + outValue = value; + return result; +} + +/// +/// \brief formatIntegerValue +/// \param pointType +/// \param value +/// \param outValue +/// \return +/// +inline QString formatIntegerValue(QModbusDataUnit::RegisterType pointType, qint16 value, ByteOrder order, QVariant& outValue) +{ + QString result; + value = toByteOrderValue(value, order); + + switch(pointType) + { + case QModbusDataUnit::Coils: + case QModbusDataUnit::DiscreteInputs: + result = QString("<%1>").arg(value); + break; + case QModbusDataUnit::HoldingRegisters: + case QModbusDataUnit::InputRegisters: + result = QStringLiteral("<%1>").arg(value, 5, 10, QLatin1Char(' ')); + break; + default: + break; + } + outValue = value; + return result; +} + +/// +/// \brief formatHexValue +/// \param pointType +/// \param value +/// \param outValue +/// \return +/// +inline QString formatHexValue(QModbusDataUnit::RegisterType pointType, quint16 value, ByteOrder order, QVariant& outValue) +{ + QString result; + value = toByteOrderValue(value, order); + + switch(pointType) + { + case QModbusDataUnit::Coils: + case QModbusDataUnit::DiscreteInputs: + result = QString("<%1>").arg(value); + break; + case QModbusDataUnit::HoldingRegisters: + case QModbusDataUnit::InputRegisters: + result = QString("<0x%1>").arg(QString::number(value, 16).toUpper(), 4, '0'); + break; + default: + break; + } + outValue = value; + return result; +} + +/// +/// \brief formatFloatValue +/// \param pointType +/// \param value1 +/// \param value2 +/// \param order +/// \param flag +/// \param outValue +/// \return +/// +inline QString formatFloatValue(QModbusDataUnit::RegisterType pointType, quint16 value1, quint16 value2, ByteOrder order, bool flag, QVariant& outValue) +{ + QString result; + switch(pointType) + { + case QModbusDataUnit::Coils: + case QModbusDataUnit::DiscreteInputs: + outValue = value1; + result = QString("<%1>").arg(value1); + break; + case QModbusDataUnit::HoldingRegisters: + case QModbusDataUnit::InputRegisters: + { + if(flag) break; + + const float value = makeFloat(value1, value2, order); + outValue = value; + result = QLocale().toString(value); + } + break; + default: + break; + } + return result; +} + +/// +/// \brief formatLongValue +/// \param pointType +/// \param value1 +/// \param value2 +/// \param order +/// \param flag +/// \param outValue +/// \return +/// +inline QString formatLongValue(QModbusDataUnit::RegisterType pointType, quint16 value1, quint16 value2, ByteOrder order, bool flag, QVariant& outValue) +{ + QString result; + switch(pointType) + { + case QModbusDataUnit::Coils: + case QModbusDataUnit::DiscreteInputs: + outValue = value1; + result = QString("<%1>").arg(value1); + break; + case QModbusDataUnit::HoldingRegisters: + case QModbusDataUnit::InputRegisters: + { + if(flag) break; + + const qint32 value = makeLong(value1, value2, order); + outValue = value; + result = result = QString("<%1>").arg(value, 10, 10, QLatin1Char(' ')); + } + break; + default: + break; + } + return result; +} + +/// +/// \brief formatUnsignedLongValue +/// \param pointType +/// \param value1 +/// \param value2 +/// \param order +/// \param flag +/// \param outValue +/// \return +/// +inline QString formatUnsignedLongValue(QModbusDataUnit::RegisterType pointType, quint16 value1, quint16 value2, ByteOrder order, bool flag, QVariant& outValue) +{ + QString result; + switch(pointType) + { + case QModbusDataUnit::Coils: + case QModbusDataUnit::DiscreteInputs: + outValue = value1; + result = QString("<%1>").arg(value1); + break; + case QModbusDataUnit::HoldingRegisters: + case QModbusDataUnit::InputRegisters: + { + if(flag) break; + + const quint32 value = makeULong(value1, value2, order); + outValue = value; + result = result = QString("<%1>").arg(value, 10, 10, QLatin1Char('0')); + } + break; + default: + break; + } + return result; +} + +/// +/// \brief formatDoubleValue +/// \param pointType +/// \param value1 +/// \param value2 +/// \param value3 +/// \param value4 +/// \param order +/// \param flag +/// \param outValue +/// \return +/// +inline QString formatDoubleValue(QModbusDataUnit::RegisterType pointType, quint16 value1, quint16 value2, quint16 value3, quint16 value4, ByteOrder order, bool flag, QVariant& outValue) +{ + QString result; + switch(pointType) + { + case QModbusDataUnit::Coils: + case QModbusDataUnit::DiscreteInputs: + outValue = value1; + result = QString("<%1>").arg(value1); + break; + case QModbusDataUnit::HoldingRegisters: + case QModbusDataUnit::InputRegisters: + { + if(flag) break; + + const double value = makeDouble(value1, value2, value3, value4, order); + outValue = value; + result = QLocale().toString(value, 'g', 16); + } + break; + default: + break; + } + return result; +} + +/// +/// \brief formatAddress +/// \param pointType +/// \param address +/// \param hexFormat +/// \return +/// +inline QString formatAddress(QModbusDataUnit::RegisterType pointType, int address, bool hexFormat) +{ + QString prefix; + switch(pointType) + { + case QModbusDataUnit::Coils: + prefix = "0"; + break; + case QModbusDataUnit::DiscreteInputs: + prefix = "1"; + break; + case QModbusDataUnit::HoldingRegisters: + prefix = "4"; + break; + case QModbusDataUnit::InputRegisters: + prefix = "3"; + break; + default: + break; + } + + return hexFormat ? QString("0x%1").arg(QString::number(address, 16).toUpper(), 4, '0') : + prefix + QStringLiteral("%1").arg(address, 4, 10, QLatin1Char('0')); +} + +#endif // FORMATUTILS_H diff --git a/omodscan/formmodsca.cpp b/omodscan/formmodsca.cpp index f4db92a..d35defb 100644 --- a/omodscan/formmodsca.cpp +++ b/omodscan/formmodsca.cpp @@ -10,7 +10,7 @@ #include "formmodsca.h" #include "ui_formmodsca.h" -QVersionNumber FormModSca::VERSION = QVersionNumber(1, 3); +QVersionNumber FormModSca::VERSION = QVersionNumber(1, 4); /// /// \brief FormModSca::FormModSca @@ -19,7 +19,7 @@ QVersionNumber FormModSca::VERSION = QVersionNumber(1, 3); /// \param ver /// \param parent /// -FormModSca::FormModSca(int id, ModbusClient& client, QSharedPointer simulator, MainWindow* parent) +FormModSca::FormModSca(int id, ModbusClient& client, DataSimulator* simulator, MainWindow* parent) : QWidget(parent) , ui(new Ui::FormModSca) ,_formId(id) @@ -27,6 +27,7 @@ FormModSca::FormModSca(int id, ModbusClient& client, QSharedPointerlineEditAddress->value(); dd.PointType = ui->comboBoxModbusPointType->currentPointType(); dd.Length = ui->lineEditLength->value(); + dd.LogViewLimit = ui->outputWidget->logViewLimit(); return dd; } @@ -132,14 +134,28 @@ DisplayDefinition FormModSca::displayDefinition() const /// void FormModSca::setDisplayDefinition(const DisplayDefinition& dd) { - _timer.setInterval(qBound(20U, dd.ScanRate, 10000U)); + _timer.setInterval(dd.ScanRate); + + ui->lineEditDeviceId->blockSignals(true); ui->lineEditDeviceId->setValue(dd.DeviceId); + ui->lineEditDeviceId->blockSignals(false); + + ui->lineEditAddress->blockSignals(true); ui->lineEditAddress->setValue(dd.PointAddress); + ui->lineEditAddress->blockSignals(false); + + ui->lineEditLength->blockSignals(true); ui->lineEditLength->setValue(dd.Length); + ui->lineEditLength->blockSignals(false); + + ui->comboBoxModbusPointType->blockSignals(true); ui->comboBoxModbusPointType->setCurrentPointType(dd.PointType); + ui->comboBoxModbusPointType->blockSignals(false); ui->outputWidget->setStatus(tr("Data Uninitialized")); ui->outputWidget->setup(dd, _dataSimulator->simulationMap(dd.DeviceId)); + + beginUpdate(); } /// @@ -470,30 +486,40 @@ void FormModSca::show() /// void FormModSca::on_timeout() { - if(!_modbusClient.isValid()) return; if(_modbusClient.state() != QModbusDevice::ConnectedState) - { - ui->outputWidget->setStatus(tr("Device NOT CONNECTED!")); return; - } const auto dd = displayDefinition(); - if(dd.PointAddress + dd.Length - 1 > ModbusLimits::addressRange().to()) - { - ui->outputWidget->setStatus(tr("Invalid Data Length Specified")); - return; - } - - if(_validSlaveResponses == ui->statisticWidget->validSlaveResposes()) + if(dd.PointAddress + dd.Length - 1 <= ModbusLimits::addressRange().to()) { - _noSlaveResponsesCounter++; - if(_noSlaveResponsesCounter > _modbusClient.numberOfRetries()) + if(_validSlaveResponses == ui->statisticWidget->validSlaveResposes()) { - ui->outputWidget->setStatus(tr("No Responses from Slave Device")); + _noSlaveResponsesCounter++; + if(_noSlaveResponsesCounter > _modbusClient.numberOfRetries()) + { + ui->outputWidget->setStatus(tr("No Responses from Slave Device")); + } } + + _modbusClient.sendReadRequest(dd.PointType, dd.PointAddress - 1, dd.Length, dd.DeviceId, _formId); } +} + +/// +/// \brief FormModSca::beginUpdate +/// +void FormModSca::beginUpdate() +{ + if(_modbusClient.state() != QModbusDevice::ConnectedState) + return; - _modbusClient.sendReadRequest(dd.PointType, dd.PointAddress - 1, dd.Length, dd.DeviceId, _formId); + const auto dd = displayDefinition(); + if(dd.PointAddress + dd.Length - 1 <= ModbusLimits::addressRange().to()) + _modbusClient.sendReadRequest(dd.PointType, dd.PointAddress - 1, dd.Length, dd.DeviceId, _formId); + else + ui->outputWidget->setStatus(tr("No Scan: Invalid Data Length Specified")); + + _timer.start(); } /// @@ -501,7 +527,7 @@ void FormModSca::on_timeout() /// \param reply /// \return /// -bool FormModSca::isValidReply(const QModbusReply* reply) +bool FormModSca::isValidReply(const QModbusReply* reply) const { const auto dd = displayDefinition(); const auto data = reply->result(); @@ -509,37 +535,94 @@ bool FormModSca::isValidReply(const QModbusReply* reply) switch(response.functionCode()) { - case QModbusRequest::ReadCoils: - case QModbusRequest::ReadDiscreteInputs: + case QModbusPdu::ReadCoils: + case QModbusPdu::ReadDiscreteInputs: return (data.startAddress() == dd.PointAddress - 1) && (data.valueCount() - dd.Length) < 8; break; - case QModbusRequest::ReadInputRegisters: - case QModbusRequest::ReadHoldingRegisters: + case QModbusPdu::ReadInputRegisters: + case QModbusPdu::ReadHoldingRegisters: return (data.valueCount() == dd.Length) && (data.startAddress() == dd.PointAddress - 1); default: - break; + return true; } +} - return false; +/// +/// \brief FormModSca::logRequest +/// \param requestId +/// \param deviceId +/// \param request +/// +void FormModSca::logRequest(int requestId, int deviceId, const QModbusRequest& request) +{ + if(requestId == _formId && deviceId == ui->lineEditDeviceId->value()) + ui->outputWidget->updateTraffic(request, deviceId); + else if(requestId == 0 && isActive()) + ui->outputWidget->updateTraffic(request, deviceId); } /// -/// \brief FormModSca::on_modbusReply +/// \brief FormModSca::on_modbusRequest +/// \param requestId +/// \param deviceId +/// \param request +/// +void FormModSca::on_modbusRequest(int requestId, int deviceId, const QModbusRequest& request) +{ + logRequest(requestId, deviceId, request); + + switch(request.functionCode()) + { + case QModbusPdu::ReadCoils: + case QModbusPdu::ReadDiscreteInputs: + case QModbusPdu::ReadHoldingRegisters: + case QModbusPdu::ReadInputRegisters: + if(requestId == _formId) + ui->statisticWidget->increaseNumberOfPolls(); + break; + + default: + break; + } +} + +/// +/// \brief FormModSca::logReply /// \param reply /// -void FormModSca::on_modbusReply(QModbusReply* reply) +void FormModSca::logReply(const QModbusReply* reply) { if(!reply) return; - if(_formId != reply->property("RequestId").toInt()) + if(reply->error() != QModbusDevice::NoError && + reply->error() != QModbusDevice::ProtocolError) { return; } - const auto data = reply->result(); + const auto deviceId = reply->serverAddress(); + const auto requestId = reply->property("RequestId").toInt(); + + if(requestId == _formId && deviceId == ui->lineEditDeviceId->value()) + ui->outputWidget->updateTraffic(reply->rawResult(), reply->serverAddress()); + else if(requestId == 0 && isActive()) + ui->outputWidget->updateTraffic(reply->rawResult(), reply->serverAddress()); +} + +/// +/// \brief FormModSca::on_modbusReply +/// \param reply +/// +void FormModSca::on_modbusReply(QModbusReply* reply) +{ + if(!reply) return; + + logReply(reply); + const auto response = reply->rawResult(); + const bool hasError = reply->error() != QModbusDevice::NoError; switch(response.functionCode()) { @@ -550,12 +633,14 @@ void FormModSca::on_modbusReply(QModbusReply* reply) break; default: + if(!hasError) beginUpdate(); return; } - ui->outputWidget->updateTraffic(response, reply->serverAddress()); + if(reply->property("RequestId").toInt() != _formId) + return; - if (reply->error() == QModbusDevice::NoError) + if (!hasError) { if(!isValidReply(reply)) { @@ -563,14 +648,16 @@ void FormModSca::on_modbusReply(QModbusReply* reply) } else { - ui->outputWidget->updateData(data); + ui->outputWidget->updateData(reply->result()); ui->outputWidget->setStatus(QString()); ui->statisticWidget->increaseValidSlaveResponses(); } } else if (reply->error() == QModbusDevice::ProtocolError) { - ui->outputWidget->setStatus(ModbusException(response.exceptionCode())); + const auto ex = ModbusException(response.exceptionCode()); + const auto errorString = QString("%1 (%2)").arg(ex, formatByteValue(DataDisplayMode::Hex, ex)); + ui->outputWidget->setStatus(errorString); } else { @@ -582,33 +669,20 @@ void FormModSca::on_modbusReply(QModbusReply* reply) } /// -/// \brief FormModSca::on_modbusRequest -/// \param requestId -/// \param request +/// \brief FormModSca::on_modbusConnected /// -void FormModSca::on_modbusRequest(int requestId, const QModbusRequest& request) +void FormModSca::on_modbusConnected(const ConnectionDetails&) { - if(requestId != _formId) - { - return; - } - - switch(request.functionCode()) - { - case QModbusPdu::ReadCoils: - case QModbusPdu::ReadDiscreteInputs: - case QModbusPdu::ReadHoldingRegisters: - case QModbusPdu::ReadInputRegisters: - { - const auto deviceId = ui->lineEditDeviceId->value(); - ui->outputWidget->updateTraffic(request, deviceId); - ui->statisticWidget->increaseNumberOfPolls(); - } - break; + beginUpdate(); +} - default: - break; - } +/// +/// \brief FormModSca::on_modbusDisconnected +/// +void FormModSca::on_modbusDisconnected(const ConnectionDetails&) +{ + _timer.stop(); + ui->outputWidget->setStatus(tr("Device NOT CONNECTED!")); } /// @@ -618,6 +692,7 @@ void FormModSca::on_lineEditAddress_valueChanged(const QVariant&) { const quint8 deviceId = ui->lineEditDeviceId->value(); ui->outputWidget->setup(displayDefinition(), _dataSimulator->simulationMap(deviceId)); + beginUpdate(); } /// @@ -627,6 +702,7 @@ void FormModSca::on_lineEditLength_valueChanged(const QVariant&) { const quint8 deviceId = ui->lineEditDeviceId->value(); ui->outputWidget->setup(displayDefinition(), _dataSimulator->simulationMap(deviceId)); + beginUpdate(); } /// @@ -636,6 +712,7 @@ void FormModSca::on_lineEditDeviceId_valueChanged(const QVariant&) { const quint8 deviceId = ui->lineEditDeviceId->value(); ui->outputWidget->setup(displayDefinition(), _dataSimulator->simulationMap(deviceId)); + beginUpdate(); } /// @@ -645,6 +722,7 @@ void FormModSca::on_comboBoxModbusPointType_pointTypeChanged(QModbusDataUnit::Re { const quint8 deviceId = ui->lineEditDeviceId->value(); ui->outputWidget->setup(displayDefinition(), _dataSimulator->simulationMap(deviceId)); + beginUpdate(); } /// @@ -671,11 +749,11 @@ void FormModSca::on_outputWidget_itemDoubleClicked(quint16 addr, const QVariant& case QModbusDataUnit::Coils: { ModbusWriteParams params = { node, addr, value, mode, byteOrder() }; - DialogWriteCoilRegister dlg(params, simParams, this); + DialogWriteCoilRegister dlg(params, simParams, _parent); switch(dlg.exec()) { case QDialog::Accepted: - _modbusClient.writeRegister(pointType, params, 0); + _modbusClient.writeRegister(pointType, params, _formId); break; case 2: @@ -691,17 +769,17 @@ void FormModSca::on_outputWidget_itemDoubleClicked(quint16 addr, const QVariant& ModbusWriteParams params = { node, addr, value, mode, byteOrder()}; if(mode == DataDisplayMode::Binary) { - DialogWriteHoldingRegisterBits dlg(params, this); + DialogWriteHoldingRegisterBits dlg(params, _parent); if(dlg.exec() == QDialog::Accepted) - _modbusClient.writeRegister(pointType, params, 0); + _modbusClient.writeRegister(pointType, params, _formId); } else { - DialogWriteHoldingRegister dlg(params, simParams, mode, this); + DialogWriteHoldingRegister dlg(params, simParams, mode, _parent); switch(dlg.exec()) { case QDialog::Accepted: - _modbusClient.writeRegister(pointType, params, 0); + _modbusClient.writeRegister(pointType, params, _formId); break; case 2: diff --git a/omodscan/formmodsca.h b/omodscan/formmodsca.h index f605a60..cbdb865 100644 --- a/omodscan/formmodsca.h +++ b/omodscan/formmodsca.h @@ -31,10 +31,16 @@ class FormModSca : public QWidget public: static QVersionNumber VERSION; - explicit FormModSca(int id, ModbusClient& client, QSharedPointer simulator, MainWindow* parent); + explicit FormModSca(int id, ModbusClient& client, DataSimulator* simulator, MainWindow* parent); ~FormModSca(); - int formId() const { return _formId; } + int formId() const { + return _formId; + } + + bool isActive() const { + return property("isActive").toBool(); + } QString filename() const; void setFilename(const QString& filename); @@ -98,8 +104,10 @@ public slots: private slots: void on_timeout(); + void on_modbusConnected(const ConnectionDetails& cd); + void on_modbusDisconnected(const ConnectionDetails& cd); void on_modbusReply(QModbusReply* reply); - void on_modbusRequest(int requestId, const QModbusRequest& request); + void on_modbusRequest(int requestId, int deviceId, const QModbusRequest& request); void on_lineEditAddress_valueChanged(const QVariant&); void on_lineEditLength_valueChanged(const QVariant&); void on_lineEditDeviceId_valueChanged(const QVariant&); @@ -112,7 +120,11 @@ private slots: void on_dataSimulated(DataDisplayMode mode, QModbusDataUnit::RegisterType type, quint16 addr, quint8 deviceId, QVariant value); private: - bool isValidReply(const QModbusReply* reply); + void beginUpdate(); + bool isValidReply(const QModbusReply* reply) const; + + void logReply(const QModbusReply* reply); + void logRequest(int requestId, int deviceId, const QModbusRequest& request); private: Ui::FormModSca *ui; @@ -122,7 +134,8 @@ private slots: QTimer _timer; QString _filename; ModbusClient& _modbusClient; - QSharedPointer _dataSimulator; + DataSimulator* _dataSimulator; + MainWindow* _parent; }; /// @@ -234,6 +247,7 @@ inline QDataStream& operator <<(QDataStream& out, const FormModSca* frm) out << dd.PointType; out << dd.PointAddress; out << dd.Length; + out << dd.LogViewLimit; out << frm->byteOrder(); out << frm->simulationMap(); @@ -251,6 +265,7 @@ inline QDataStream& operator <<(QDataStream& out, const FormModSca* frm) inline QDataStream& operator >>(QDataStream& in, FormModSca* frm) { if(!frm) return in; + const auto ver = frm->property("Version").value(); bool isMaximized; in >> isMaximized; @@ -285,8 +300,10 @@ inline QDataStream& operator >>(QDataStream& in, FormModSca* frm) in >> dd.PointType; in >> dd.PointAddress; in >> dd.Length; - - const auto ver = frm->property("Version").value(); + if(ver >= QVersionNumber(1, 4)) + { + in >> dd.LogViewLimit; + } ByteOrder byteOrder = ByteOrder::LittleEndian; ModbusSimulationMap simulationMap; diff --git a/omodscan/htmldelegate.cpp b/omodscan/htmldelegate.cpp new file mode 100644 index 0000000..08fddad --- /dev/null +++ b/omodscan/htmldelegate.cpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include +#include "htmldelegate.h" + +/// +/// \brief HtmlDelegate::HtmlDelegate +/// \param parent +/// +HtmlDelegate::HtmlDelegate(QObject* parent) + : QStyledItemDelegate(parent) +{ +} + +/// +/// \brief HtmlDelegate::paint +/// \param painter +/// \param option +/// \param index +/// +void HtmlDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + if (opt.text.isEmpty()) { + // This is nothing this function is supposed to handle + QStyledItemDelegate::paint(painter, option, index); + + return; + } + + QStyle *style = opt.widget? opt.widget->style() : QApplication::style(); + + QTextOption textOption; + textOption.setWrapMode(opt.features & QStyleOptionViewItem::WrapText ? QTextOption::WordWrap + : QTextOption::ManualWrap); + textOption.setTextDirection(opt.direction); + + QTextDocument doc; + doc.setHtml(opt.text); + doc.setDocumentMargin(2); + doc.setDefaultFont(opt.font); + doc.setDefaultTextOption(textOption); + doc.setTextWidth(opt.rect.width()); + + /// Painting item without text + opt.text = QString(); + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter); + + QAbstractTextDocumentLayout::PaintContext ctx; + + // Highlighting text if item is selected + if (opt.state & QStyle::State_Selected) + ctx.palette.setColor(QPalette::Text, opt.palette.color(QPalette::Active, QPalette::HighlightedText)); + + QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt); + painter->save(); + painter->translate(textRect.topLeft()); + painter->setClipRect(textRect.translated(-textRect.topLeft())); + doc.documentLayout()->draw(painter, ctx); + painter->restore(); +} + +/// +/// \brief HtmlDelegate::sizeHint +/// \param option +/// \param index +/// \return +/// +QSize HtmlDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + if (opt.text.isEmpty()) { + // This is nothing this function is supposed to handle + return QStyledItemDelegate::sizeHint(option, index); + } + + QTextDocument doc; + doc.setHtml(opt.text); + doc.setDocumentMargin(2); + doc.setDefaultFont(opt.font); + + if(opt.features & QStyleOptionViewItem::WrapText) + doc.setTextWidth(opt.rect.width()); + + return QSize(doc.idealWidth(), doc.size().height()); +} diff --git a/omodscan/htmldelegate.h b/omodscan/htmldelegate.h new file mode 100644 index 0000000..8f6269e --- /dev/null +++ b/omodscan/htmldelegate.h @@ -0,0 +1,23 @@ +#ifndef HTMLDELEGATE_H +#define HTMLDELEGATE_H + +#include + +/// +/// \brief The HtmlDelegate class +/// +class HtmlDelegate : public QStyledItemDelegate +{ +public: + /// + /// \brief HtmlDelegate + /// \param parent + /// + explicit HtmlDelegate(QObject* parent = nullptr); + +protected: + void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const; + QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const; +}; + +#endif // HTMLDELEGATE_H diff --git a/omodscan/mainwindow.cpp b/omodscan/mainwindow.cpp index 6356cd4..6ee0a78 100644 --- a/omodscan/mainwindow.cpp +++ b/omodscan/mainwindow.cpp @@ -11,6 +11,7 @@ #include "dialogforcemultiplecoils.h" #include "dialogforcemultipleregisters.h" #include "dialogusermsg.h" +#include "dialogmsgparser.h" #include "dialogaddressscan.h" #include "dialogmodbusscanner.h" #include "dialogwindowsmanager.h" @@ -48,7 +49,7 @@ MainWindow::MainWindow(QWidget *parent) const auto defaultPrinter = QPrinterInfo::defaultPrinter(); if(!defaultPrinter.isNull()) - _selectedPrinter = QSharedPointer(new QPrinter(defaultPrinter)); + _selectedPrinter = new QPrinter(defaultPrinter); _recentFileActionList = new RecentFileActionList(ui->menuFile, ui->actionRecentFile); connect(_recentFileActionList, &RecentFileActionList::triggered, this, &MainWindow::openFile); @@ -75,6 +76,7 @@ MainWindow::MainWindow(QWidget *parent) MainWindow::~MainWindow() { delete ui; + delete _selectedPrinter; } /// @@ -277,6 +279,7 @@ void MainWindow::on_actionNew_triggered() frm->setByteOrder(cur->byteOrder()); frm->setDisplayMode(cur->displayMode()); frm->setDataDisplayMode(cur->dataDisplayMode()); + frm->setDisplayDefinition(cur->displayDefinition()); frm->setFont(cur->font()); frm->setStatusColor(cur->statusColor()); @@ -347,10 +350,10 @@ void MainWindow::on_actionPrint_triggered() auto frm = currentMdiChild(); if(!frm) return; - QPrintDialog dlg(_selectedPrinter.get(), this); + QPrintDialog dlg(_selectedPrinter, this); if(dlg.exec() == QDialog::Accepted) { - frm->print(_selectedPrinter.get()); + frm->print(_selectedPrinter); } } @@ -359,7 +362,7 @@ void MainWindow::on_actionPrint_triggered() /// void MainWindow::on_actionPrintSetup_triggered() { - DialogPrintSettings dlg(_selectedPrinter.get(), this); + DialogPrintSettings dlg(_selectedPrinter, this); dlg.exec(); } @@ -472,8 +475,9 @@ void MainWindow::on_actionDataDefinition_triggered() auto frm = currentMdiChild(); if(!frm) return; - DialogDisplayDefinition dlg(frm); - dlg.exec(); + DialogDisplayDefinition dlg(frm->displayDefinition(), this); + if(dlg.exec() == QDialog::Accepted) + frm->setDisplayDefinition(dlg.displayDefinition()); } /// @@ -634,7 +638,9 @@ void MainWindow::on_actionForceCoils_triggered() params.Node = presetParams.SlaveAddress; params.Address = presetParams.PointAddress; - if(dd.PointType == QModbusDataUnit::Coils) + if(dd.PointType == QModbusDataUnit::Coils && + dd.DeviceId == params.Node && + dd.PointAddress == params.Address) { params.Value = QVariant::fromValue(frm->data()); } @@ -668,7 +674,9 @@ void MainWindow::on_actionPresetRegs_triggered() params.DisplayMode = frm->dataDisplayMode(); params.Order = frm->byteOrder(); - if(dd.PointType == QModbusDataUnit::HoldingRegisters) + if(dd.PointType == QModbusDataUnit::HoldingRegisters && + dd.DeviceId == params.Node && + dd.PointAddress == params.Address) { params.Value = QVariant::fromValue(frm->data()); } @@ -685,7 +693,12 @@ void MainWindow::on_actionPresetRegs_triggered() /// void MainWindow::on_actionMaskWrite_triggered() { - ModbusMaskWriteParams params = { 1, 1, 0xFFFF, 0}; + auto frm = currentMdiChild(); + if(!frm) return; + + const auto dd = frm->displayDefinition(); + ModbusMaskWriteParams params = { dd.DeviceId, dd.PointAddress, 0xFFFF, 0}; + DialogMaskWriteRegiter dlg(params, this); if(dlg.exec() == QDialog::Accepted) { @@ -704,10 +717,47 @@ void MainWindow::on_actionUserMsg_triggered() const auto dd = frm->displayDefinition(); const auto mode = frm->dataDisplayMode(); - DialogUserMsg dlg(dd.DeviceId, mode, _modbusClient, this); + QModbusPdu::FunctionCode func; + switch(dd.PointType) + { + case QModbusDataUnit::Coils: + func = QModbusPdu::ReadCoils; + break; + + case QModbusDataUnit::DiscreteInputs: + func = QModbusPdu::ReadDiscreteInputs; + break; + + case QModbusDataUnit::HoldingRegisters: + func = QModbusPdu::ReadHoldingRegisters; + break; + + case QModbusDataUnit::InputRegisters: + func = QModbusPdu::ReadInputRegisters; + break; + + default: + func = QModbusPdu::Invalid; + break; + } + + DialogUserMsg dlg(dd.DeviceId, func, mode, _modbusClient, this); dlg.exec(); } +/// +/// \brief MainWindow::on_actionMsgParser_triggered +/// +void MainWindow::on_actionMsgParser_triggered() +{ + auto frm = currentMdiChild(); + const auto mode = frm ? frm->dataDisplayMode() : DataDisplayMode::Hex; + + auto dlg = new DialogMsgParser(mode, this); + dlg->setAttribute(Qt::WA_DeleteOnClose, true); + dlg->show(); +} + /// /// \brief MainWindow::on_actionAddressScan_triggered /// @@ -902,6 +952,7 @@ void MainWindow::updateMenuWindow() for(auto&& wnd : ui->mdiArea->subWindowList()) { wnd->setProperty("isActive", wnd == activeWnd); + wnd->widget()->setProperty("isActive", wnd == activeWnd); } _windowActionList->update(); } @@ -912,7 +963,10 @@ void MainWindow::updateMenuWindow() /// void MainWindow::windowActivate(QMdiSubWindow* wnd) { - if(wnd) ui->mdiArea->setActiveSubWindow(wnd); + if(wnd) + { + ui->mdiArea->setActiveSubWindow(wnd); + } } /// diff --git a/omodscan/mainwindow.h b/omodscan/mainwindow.h index b87f990..06347d4 100644 --- a/omodscan/mainwindow.h +++ b/omodscan/mainwindow.h @@ -76,6 +76,7 @@ private slots: void on_actionPresetRegs_triggered(); void on_actionMaskWrite_triggered(); void on_actionUserMsg_triggered(); + void on_actionMsgParser_triggered(); void on_actionAddressScan_triggered(); void on_actionTextCapture_triggered(); void on_actionCaptureOff_triggered(); @@ -148,7 +149,7 @@ private slots: WindowActionList* _windowActionList; RecentFileActionList* _recentFileActionList; - QSharedPointer _selectedPrinter; - QSharedPointer _dataSimulator; + QPrinter* _selectedPrinter; + DataSimulator* _dataSimulator; }; #endif // MAINWINDOW_H diff --git a/omodscan/mainwindow.ui b/omodscan/mainwindow.ui index 6a02a70..27c73e1 100644 --- a/omodscan/mainwindow.ui +++ b/omodscan/mainwindow.ui @@ -127,6 +127,7 @@ + @@ -755,6 +756,17 @@ Ctrl+Shift+5 + + + Msg Parser + + + Msg Parser + + + F9 + + diff --git a/omodscan/modbusclient.cpp b/omodscan/modbusclient.cpp index 059ceef..ba9f18a 100644 --- a/omodscan/modbusclient.cpp +++ b/omodscan/modbusclient.cpp @@ -1,15 +1,16 @@ #include -#include "floatutils.h" -#include "modbusexception.h" -#include "modbusclient.h" #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - #include - typedef QModbusRtuSerialMaster QModbusRtuSerialClient; +#include +typedef QModbusRtuSerialMaster QModbusRtuSerialClient; #else - #include +#include #endif +#include "formatutils.h" +#include "numericutils.h" +#include "modbusexception.h" +#include "modbusclient.h" /// /// \brief ModbusClient::ModbusClient @@ -129,6 +130,7 @@ void ModbusClient::sendRawRequest(const QModbusRequest& request, int server, int return; } + emit modbusRequest(requestId, server, request); if(auto reply = _modbusClient->sendRawRequest(request, server)) { reply->setProperty("RequestId", requestId); @@ -164,7 +166,7 @@ void ModbusClient::sendReadRequest(QModbusDataUnit::RegisterType pointType, int const auto request = createReadRequest(dataUnit); if(!request.isValid()) return; - emit modbusRequest(requestId, request); + emit modbusRequest(requestId, server, request); if(auto reply = _modbusClient->sendReadRequest(dataUnit, server)) { reply->setProperty("RequestId", requestId); @@ -467,7 +469,7 @@ void ModbusClient::writeRegister(QModbusDataUnit::RegisterType pointType, const const auto request = createWriteRequest(data, useMultipleWriteFunc); if(!request.isValid()) return; - emit modbusRequest(requestId, request); + emit modbusRequest(requestId, params.Node, request); if(auto reply = _modbusClient->sendRawRequest(request, params.Node)) { @@ -499,7 +501,7 @@ void ModbusClient::maskWriteRegister(const ModbusMaskWriteParams& params, int re } QModbusRequest request(QModbusRequest::MaskWriteRegister, quint16(params.Address - 1), params.AndMask, params.OrMask); - emit modbusRequest(requestId, request); + emit modbusRequest(requestId, params.Node, request); if(auto reply = _modbusClient->sendRawRequest(request, params.Node)) { @@ -601,13 +603,27 @@ void ModbusClient::on_writeReply() auto reply = qobject_cast(sender()); if (!reply) return; + const auto raw = reply->rawResult(); + +#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) + if(raw.functionCode() == QModbusRequest::MaskWriteRegister && + reply->error() == QModbusDevice::InvalidResponseError) + { + reply->blockSignals(true); + reply->setError(QModbusDevice::NoError, QString()); + reply->blockSignals(false); + } +#endif + emit modbusReply(reply); - const auto raw = reply->rawResult(); auto onError = [this, reply, raw](const QString& errorDesc, int requestId) { if (reply->error() == QModbusDevice::ProtocolError) - emit modbusError(QString("%1. %2").arg(errorDesc, ModbusException(raw.exceptionCode())), requestId); + { + ModbusException ex(raw.exceptionCode()); + emit modbusError(QString("%1. %2 (%3)").arg(errorDesc, ex, formatByteValue(DataDisplayMode::Hex, ex)), requestId); + } else if(reply->error() != QModbusDevice::NoError) emit modbusError(QString("%1. %2").arg(errorDesc, reply->errorString()), requestId); }; diff --git a/omodscan/modbusclient.h b/omodscan/modbusclient.h index 8028abc..482d276 100644 --- a/omodscan/modbusclient.h +++ b/omodscan/modbusclient.h @@ -35,7 +35,7 @@ class ModbusClient : public QObject void maskWriteRegister(const ModbusMaskWriteParams& params, int requestId); signals: - void modbusRequest(int requestId, const QModbusRequest& request); + void modbusRequest(int requestId, int deviceId, const QModbusRequest& request); void modbusReply(QModbusReply* reply); void modbusError(const QString& error, int requestId); void modbusConnectionError(const QString& error); diff --git a/omodscan/modbusexception.h b/omodscan/modbusexception.h index fdd59f9..113bcb1 100644 --- a/omodscan/modbusexception.h +++ b/omodscan/modbusexception.h @@ -14,7 +14,12 @@ class ModbusException { } - operator QString() + operator int() const + { + return _code; + } + + operator QString() const { QString desc; switch (_code) @@ -53,7 +58,7 @@ class ModbusException desc = "EXTENDED EXCEPTION"; break; } - return QString("%1 (0x%2)").arg(desc, QString::number(_code, 16)); + return desc; } private: diff --git a/omodscan/modbusfunction.h b/omodscan/modbusfunction.h new file mode 100644 index 0000000..b210a5b --- /dev/null +++ b/omodscan/modbusfunction.h @@ -0,0 +1,134 @@ +#ifndef MODBUSFUNCTION_H +#define MODBUSFUNCTION_H + +#include + +/// +/// \brief The ModbusFunction class +/// +class ModbusFunction +{ +public: + explicit ModbusFunction(QModbusPdu::FunctionCode code) + :_code(code) + { + } + + static QVector validCodes() { + static const QVector codes = { + QModbusPdu::ReadCoils, QModbusPdu::ReadDiscreteInputs, QModbusPdu::ReadHoldingRegisters, QModbusPdu::ReadInputRegisters, + QModbusPdu::WriteSingleCoil, QModbusPdu::WriteSingleRegister, QModbusPdu::ReadExceptionStatus, QModbusPdu::Diagnostics, + QModbusPdu::GetCommEventCounter, QModbusPdu::GetCommEventLog, QModbusPdu::WriteMultipleCoils, QModbusPdu::WriteMultipleRegisters, + QModbusPdu::ReportServerId, QModbusPdu::ReadFileRecord, QModbusPdu::WriteFileRecord, QModbusPdu::MaskWriteRegister, + QModbusPdu::ReadWriteMultipleRegisters, QModbusPdu::ReadFifoQueue, QModbusPdu::EncapsulatedInterfaceTransport + }; + return codes; + } + + bool isValid() const + { + return validCodes().contains(_code); + } + + bool isException() const + { + return _code & QModbusPdu::ExceptionByte; + } + + operator int() const + { + return _code; + } + + operator QString() const + { + QString name; + switch(_code & ~QModbusPdu::ExceptionByte) + { + case QModbusPdu::ReadCoils: + name = "READ COILS"; + break; + + case QModbusPdu::ReadDiscreteInputs: + name = "READ INPUTS"; + break; + + case QModbusPdu::ReadHoldingRegisters: + name = "READ HOLDING REGS"; + break; + + case QModbusPdu::ReadInputRegisters: + name = "READ INPUT REGS"; + break; + + case QModbusPdu::WriteSingleCoil: + name = "WRITE SINGLE COIL"; + break; + + case QModbusPdu::WriteSingleRegister: + name = "WRITE SINGLE REG"; + break; + + case QModbusPdu::ReadExceptionStatus: + name = "READ EXCEPTION STAT"; + break; + + case QModbusPdu::Diagnostics: + name = "DIAGNOSTICS"; + break; + + case QModbusPdu::GetCommEventCounter: + name = "GET COMM EVENT CNT"; + break; + + case QModbusPdu::GetCommEventLog: + name = "GET COMM EVENT LOG"; + break; + + case QModbusPdu::WriteMultipleCoils: + name = "WRITE MULT COILS"; + break; + + case QModbusPdu::WriteMultipleRegisters: + name = "WRITE MULT REGS"; + break; + + case QModbusPdu::ReportServerId: + name = "REPORT SLAVE ID"; + break; + + case QModbusPdu::ReadFileRecord: + name = "READ FILE RECORD"; + break; + + case QModbusPdu::WriteFileRecord: + name = "WRITE FILE RECORD"; + break; + + case QModbusPdu::MaskWriteRegister: + name = "MASK WRITE REG"; + break; + + case QModbusPdu::ReadWriteMultipleRegisters: + name = "READ WRITE MULT REGS"; + break; + + case QModbusPdu::ReadFifoQueue: + name ="READ FIFO QUEUE"; + break; + + case QModbusPdu::EncapsulatedInterfaceTransport: + name = "ENC IFACE TRANSPORT"; + break; + + default: + break; + } + return name; + } + +private: + QModbusPdu::FunctionCode _code; +}; + +#endif // MODBUSFUNCTION_H diff --git a/omodscan/modbusmessages/diagnostics.h b/omodscan/modbusmessages/diagnostics.h new file mode 100644 index 0000000..4cc1261 --- /dev/null +++ b/omodscan/modbusmessages/diagnostics.h @@ -0,0 +1,119 @@ +#ifndef DIAGNOSTICS_H +#define DIAGNOSTICS_H + +#include "modbusmessage.h" + +/// +/// \brief The DiagnosticsRequest class +/// +class DiagnosticsRequest : public ModbusMessage +{ +public: + /// + /// \brief DiagnosticsRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + DiagnosticsRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::Diagnostics); + } + + /// + /// \brief DiagnosticsRequest + /// \param adu + /// \param timestamp + /// + DiagnosticsRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::Diagnostics); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && dataSize() > 2; + } + + + /// + /// \brief subfunc + /// \return + /// + quint16 subfunc() const { + return makeWord(ModbusMessage::data(1), ModbusMessage::data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief data + /// \return + /// + QByteArray data() const { + return slice(2); + } +}; + +/// +/// \brief The DiagnosticsResponse class +/// +class DiagnosticsResponse : public ModbusMessage +{ +public: + /// + /// \brief DiagnosticsResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + DiagnosticsResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::Diagnostics); + } + + /// + /// \brief DiagnosticsResponse + /// \param adu + /// \param timestamp + /// + DiagnosticsResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::Diagnostics); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && dataSize() > 2; + } + + /// + /// \brief subfunc + /// \return + /// + quint16 subfunc() const { + return makeWord(ModbusMessage::data(1), ModbusMessage::data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief data + /// \return + /// + QByteArray data() const { + return slice(2); + } +}; + +#endif // DIAGNOSTICS_H diff --git a/omodscan/modbusmessages/getcommeventcounter.h b/omodscan/modbusmessages/getcommeventcounter.h new file mode 100644 index 0000000..85aec41 --- /dev/null +++ b/omodscan/modbusmessages/getcommeventcounter.h @@ -0,0 +1,94 @@ +#ifndef GETCOMMEVENTCOUNTER_H +#define GETCOMMEVENTCOUNTER_H + +#include "modbusmessage.h" + +/// +/// \brief The GetCommEventCounterRequest class +/// +class GetCommEventCounterRequest : public ModbusMessage +{ +public: + /// + /// \brief GetCommEventCounterRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + GetCommEventCounterRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::GetCommEventCounter); + } + + /// + /// \brief GetCommEventCounterRequest + /// \param adu + /// \param timestamp + /// + GetCommEventCounterRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::GetCommEventCounter); + } +}; + +/// +/// \brief The GetCommEventCounterResponse class +/// +class GetCommEventCounterResponse : public ModbusMessage +{ +public: + /// + /// \brief GetCommEventCounterResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + GetCommEventCounterResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::GetCommEventCounter); + } + + /// + /// \brief GetCommEventCounterResponse + /// \param adu + /// \param timestamp + /// + GetCommEventCounterResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::GetCommEventCounter); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && dataSize() == 4; + } + + /// + /// \brief status + /// \return + /// + quint16 status() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief eventCount + /// \return + /// + quint16 eventCount() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } +}; + +#endif // GETCOMMEVENTCOUNTER_H diff --git a/omodscan/modbusmessages/getcommeventlog.h b/omodscan/modbusmessages/getcommeventlog.h new file mode 100644 index 0000000..f22c440 --- /dev/null +++ b/omodscan/modbusmessages/getcommeventlog.h @@ -0,0 +1,120 @@ +#ifndef GETCOMMEVENTLOG_H +#define GETCOMMEVENTLOG_H + +#include "modbusmessage.h" + +/// +/// \brief The GetCommEventLogRequest class +/// +class GetCommEventLogRequest : public ModbusMessage +{ +public: + /// + /// \brief GetCommEventLogRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + GetCommEventLogRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::GetCommEventLog); + } + + /// + /// \brief GetCommEventLogRequest + /// \param adu + /// \param timestamp + /// + GetCommEventLogRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::GetCommEventLog); + } +}; + +/// +/// \brief The GetCommEventLogResponse class +/// +class GetCommEventLogResponse : public ModbusMessage +{ +public: + /// + /// \brief GetCommEventLogResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + GetCommEventLogResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::GetCommEventLog); + } + + /// + /// \brief GetCommEventLogResponse + /// \param adu + /// \param timestamp + /// + GetCommEventLogResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::GetCommEventLog); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + byteCount() > 0 && + byteCount() == events().size() - 6; + } + + /// + /// \brief byteCount + /// \return + /// + quint8 byteCount() const { + return data(0); + } + + /// + /// \brief status + /// \return + /// + quint16 status() const { + return makeWord(data(2), data(1), ByteOrder::LittleEndian); + } + + /// + /// \brief eventCount + /// \return + /// + quint16 eventCount() const { + return makeWord(data(4), data(3), ByteOrder::LittleEndian); + } + + /// + /// \brief messageCount + /// \return + /// + quint16 messageCount() const { + return makeWord(data(6), data(5), ByteOrder::LittleEndian); + } + + /// + /// \brief events + /// \return + /// + QByteArray events() const { + return slice(7); + } +}; + +#endif // GETCOMMEVENTLOG_H diff --git a/omodscan/modbusmessages/maskwriteregister.h b/omodscan/modbusmessages/maskwriteregister.h new file mode 100644 index 0000000..a622bde --- /dev/null +++ b/omodscan/modbusmessages/maskwriteregister.h @@ -0,0 +1,134 @@ +#ifndef MASKWRITEREGISTER_H +#define MASKWRITEREGISTER_H + +#include "modbusmessage.h" + +/// +/// \brief The MaskWriteRegisterRequest class +/// +class MaskWriteRegisterRequest : public ModbusMessage +{ +public: + /// + /// \brief MaskWriteRegisterRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + MaskWriteRegisterRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::MaskWriteRegister); + } + + /// + /// \brief MaskWriteRegisterRequest + /// \param adu + /// \param timestamp + /// + MaskWriteRegisterRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::MaskWriteRegister); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && dataSize() == 6; + } + + /// + /// \brief address + /// \return + /// + quint16 address() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief andMask + /// \return + /// + quint16 andMask() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } + + /// + /// \brief orMask + /// \return + /// + quint16 orMask() const { + return makeWord(data(5), data(4), ByteOrder::LittleEndian); + } +}; + +/// +/// \brief The MaskWriteRegisterResponse class +/// +class MaskWriteRegisterResponse : public ModbusMessage +{ +public: + /// + /// \brief MaskWriteRegisterResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + MaskWriteRegisterResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::MaskWriteRegister); + } + + /// + /// \brief MaskWriteRegisterResponse + /// \param adu + /// \param timestamp + /// + MaskWriteRegisterResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::MaskWriteRegister); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && dataSize() == 6; + } + + /// + /// \brief address + /// \return + /// + quint16 address() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief andMask + /// \return + /// + quint16 andMask() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } + + /// + /// \brief orMask + /// \return + /// + quint16 orMask() const { + return makeWord(data(5), data(4), ByteOrder::LittleEndian); + } +}; + +#endif // MASKWRITEREGISTER_H diff --git a/omodscan/modbusmessages/modbusmessage.cpp b/omodscan/modbusmessages/modbusmessage.cpp new file mode 100644 index 0000000..6a5aefc --- /dev/null +++ b/omodscan/modbusmessages/modbusmessage.cpp @@ -0,0 +1,214 @@ +#include "modbusmessages.h" + + +/// +/// \brief ModbusMessage::create +/// \param pdu +/// \param protocol +/// \param deviceId +/// \param timestamp +/// \param request +/// \param checksum +/// \return +/// +const ModbusMessage* ModbusMessage::create(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, bool request, int checksum) +{ + switch(pdu.functionCode()) + { + case QModbusPdu::ReadCoils: + if(request) return new ReadCoilsRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new ReadCoilsResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::ReadDiscreteInputs: + if(request) return new ReadDiscreteInputsRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new ReadDiscreteInputsResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::ReadHoldingRegisters: + if(request) return new ReadHoldingRegistersRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new ReadHoldingRegistersResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::ReadInputRegisters: + if(request) return new ReadInputRegistersRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new ReadInputRegistersResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::WriteSingleCoil: + if(request) return new WriteSingleCoilRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new WriteSingleCoilResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::WriteSingleRegister: + if(request) return new WriteSingleRegisterRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new WriteSingleRegisterResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::ReadExceptionStatus: + if(request) return new ReadExceptionStatusRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new ReadExceptionStatusResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::Diagnostics: + if(request) return new DiagnosticsRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new DiagnosticsResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::GetCommEventCounter: + if(request) return new GetCommEventCounterRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new GetCommEventCounterResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::GetCommEventLog: + if(request) return new GetCommEventLogRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new GetCommEventLogResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::WriteMultipleCoils: + if(request) return new WriteMultipleCoilsRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new WriteMultipleCoilsResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::WriteMultipleRegisters: + if(request) return new WriteMultipleRegistersRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new WriteMultipleRegistersResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::ReportServerId: + if(request) return new ReportServerIdRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new ReportServerIdResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::ReadFileRecord: + if(request) return new ReadFileRecordRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new ReadFileRecordResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::WriteFileRecord: + if(request) return new WriteFileRecordRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new WriteFileRecordResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::MaskWriteRegister: + if(request) return new MaskWriteRegisterRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new MaskWriteRegisterResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::ReadWriteMultipleRegisters: + if(request) return new ReadWriteMultipleRegistersRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new ReadWriteMultipleRegistersResponse(pdu, protocol, deviceId, timestamp, checksum); + + case QModbusPdu::ReadFifoQueue: + if(request) return new ReadFifoQueueRequest(pdu, protocol, deviceId, timestamp, checksum); + else return new ReadFifoQueueResponse(pdu, protocol, deviceId, timestamp, checksum); + + default: + return new ModbusMessage(pdu, protocol, deviceId, timestamp, request, checksum); + } +} + +/// +/// \brief ModbusMessage::create +/// \param adu +/// \param timestamp +/// \param request +/// \return +/// +const ModbusMessage* ModbusMessage::create(const QModbusAdu& adu, const QDateTime& timestamp, bool request) +{ + switch(adu.functionCode()) + { + case QModbusPdu::ReadCoils: + if(request) return new ReadCoilsRequest(adu, timestamp); + else return new ReadCoilsResponse(adu, timestamp); + + case QModbusPdu::ReadDiscreteInputs: + if(request) return new ReadDiscreteInputsRequest(adu, timestamp); + else return new ReadDiscreteInputsResponse(adu, timestamp); + + case QModbusPdu::ReadHoldingRegisters: + if(request) return new ReadHoldingRegistersRequest(adu, timestamp); + else return new ReadHoldingRegistersResponse(adu, timestamp); + + case QModbusPdu::ReadInputRegisters: + if(request) return new ReadInputRegistersRequest(adu, timestamp); + else return new ReadInputRegistersResponse(adu, timestamp); + + case QModbusPdu::WriteSingleCoil: + if(request) return new WriteSingleCoilRequest(adu, timestamp); + else return new WriteSingleCoilResponse(adu, timestamp); + + case QModbusPdu::WriteSingleRegister: + if(request) return new WriteSingleRegisterRequest(adu, timestamp); + else return new WriteSingleRegisterResponse(adu, timestamp); + + case QModbusPdu::ReadExceptionStatus: + if(request) return new ReadExceptionStatusRequest(adu, timestamp); + else return new ReadExceptionStatusResponse(adu, timestamp); + + case QModbusPdu::Diagnostics: + if(request) return new DiagnosticsRequest(adu, timestamp); + else return new DiagnosticsResponse(adu, timestamp); + + case QModbusPdu::GetCommEventCounter: + if(request) return new GetCommEventCounterRequest(adu, timestamp); + else return new GetCommEventCounterResponse(adu, timestamp); + + case QModbusPdu::GetCommEventLog: + if(request) return new GetCommEventLogRequest(adu, timestamp); + else return new GetCommEventLogResponse(adu, timestamp); + + case QModbusPdu::WriteMultipleCoils: + if(request) return new WriteMultipleCoilsRequest(adu, timestamp); + else return new WriteMultipleCoilsResponse(adu, timestamp); + + case QModbusPdu::WriteMultipleRegisters: + if(request) return new WriteMultipleRegistersRequest(adu, timestamp); + else return new WriteMultipleRegistersResponse(adu, timestamp); + + case QModbusPdu::ReportServerId: + if(request) return new ReportServerIdRequest(adu, timestamp); + else return new ReportServerIdResponse(adu, timestamp); + + case QModbusPdu::ReadFileRecord: + if(request) return new ReadFileRecordRequest(adu, timestamp); + else return new ReadFileRecordResponse(adu, timestamp); + + case QModbusPdu::WriteFileRecord: + if(request) return new WriteFileRecordRequest(adu, timestamp); + else return new WriteFileRecordResponse(adu, timestamp); + + case QModbusPdu::MaskWriteRegister: + if(request) return new MaskWriteRegisterRequest(adu, timestamp); + else return new MaskWriteRegisterResponse(adu, timestamp); + + case QModbusPdu::ReadWriteMultipleRegisters: + if(request) return new ReadWriteMultipleRegistersRequest(adu, timestamp); + else return new ReadWriteMultipleRegistersResponse(adu, timestamp); + + case QModbusPdu::ReadFifoQueue: + if(request) return new ReadFifoQueueRequest(adu, timestamp); + else return new ReadFifoQueueResponse(adu, timestamp); + + default: + return new ModbusMessage(adu, timestamp, request); + } +} + +/// +/// \brief ModbusMessage::parse +/// \param data +/// \param type +/// \param protocol +/// \param request +/// \param checksum +/// \return +/// +const ModbusMessage* ModbusMessage::parse(const QByteArray& data, Type type, QModbusAdu::Type protocol, bool request, int checksum) +{ + switch(type) + { + case Adu: + { + QModbusAdu adu(protocol, data); + return create(adu, QDateTime::currentDateTime(), request); + } + + case Pdu: + { + QModbusPdu pdu; + pdu.setFunctionCode((QModbusPdu::FunctionCode)data[1]); + pdu.setData(data.mid(2, data.size() - 2)); + + return create(pdu, protocol, data[0], QDateTime::currentDateTime(), request, checksum); + } + + default: + return nullptr; + } +} diff --git a/omodscan/modbusmessages/modbusmessage.h b/omodscan/modbusmessages/modbusmessage.h new file mode 100644 index 0000000..3343878 --- /dev/null +++ b/omodscan/modbusmessages/modbusmessage.h @@ -0,0 +1,345 @@ +#ifndef MODBUSMESSAGE_H +#define MODBUSMESSAGE_H + +#include +#include "qmodbusadu.h" +#include "modbuslimits.h" +#include "formatutils.h" +#include "modbusfunction.h" +#include "modbusexception.h" + +/// +/// \brief The ModbusMessage class +/// +class ModbusMessage +{ +public: + enum Type{ + Adu, + Pdu + }; + + /// + /// \brief ModbusMessage + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param request + /// \param checksum + /// + explicit ModbusMessage(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, bool request, int checksum) + :_type(Type::Pdu) + ,_protocol(protocol) + ,_data(pdu.data()) + ,_funcCode(pdu.isException() ? (pdu.functionCode() | QModbusPdu::ExceptionByte) : pdu.functionCode()) + ,_exceptionCode(pdu.exceptionCode()) + ,_timestamp(timestamp) + ,_transactionId(0) + ,_protocolId(0) + ,_length(0) + ,_deviceId(deviceId) + ,_request(request) + ,_isValid(pdu.isValid()) + ,_checksum((protocol == QModbusAdu::Rtu) ? checksum : 0) + ,_calcChecksum((protocol == QModbusAdu::Rtu) ? calcCRC(pdu, deviceId) : 0) + { + } + + /// + /// \brief ModbusMessage + /// \param adu + /// \param timestamp + /// \param request + /// + explicit ModbusMessage(const QModbusAdu& adu, const QDateTime& timestamp, bool request) + :_type(Type::Adu) + ,_protocol(adu.type()) + ,_data(adu.data()) + ,_funcCode(adu.isException() ? (adu.functionCode() | QModbusPdu::ExceptionByte) : adu.functionCode()) + ,_exceptionCode(adu.exceptionCode()) + ,_timestamp(timestamp) + ,_transactionId(adu.transactionId()) + ,_protocolId(adu.protocolId()) + ,_length(adu.length()) + ,_deviceId(adu.serverAddress()) + ,_request(request) + ,_isValid(adu.isValid()) + ,_checksum(adu.checksum()) + ,_calcChecksum(adu.calcChecksum()) + { + } + + /// + /// \brief ~ModbusMessage + /// + virtual ~ModbusMessage() = default; + + /// + /// \brief create + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param request + /// \return + /// + static const ModbusMessage* create(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, bool request, int checksum = 0); + + /// + /// \brief create + /// \param adu + /// \param timestamp + /// \param request + /// \return + /// + static const ModbusMessage* create(const QModbusAdu& adu, const QDateTime& timestamp, bool request); + + /// + /// \brief parse + /// \param data + /// \param type + /// \param protocol + /// \param request + /// \param checksum + /// \return + /// + static const ModbusMessage* parse(const QByteArray& data, Type type, QModbusAdu::Type protocol, bool request, int checksum = 0); + + /// + /// \brief type + /// \return + /// + Type type() const { + return _type; + } + + /// + /// \brief transactionId + /// \return + /// + int transactionId() const { + return _transactionId; + } + + /// + /// \brief protocolId + /// \return + /// + int protocolId() const { + return _protocolId; + } + + /// + /// \brief protocolType + /// \return + /// + QModbusAdu::Type protocolType() const { + return _protocol; + } + + /// + /// \brief length + /// \return + /// + int length() const { + return _length; + } + + /// + /// \brief isValid + /// \return + /// + virtual bool isValid() const { + return (_deviceId == 0 || ModbusLimits::slaveRange().contains(_deviceId)) && + matchingChecksum() && _isValid; + } + + /// + /// \brief timestamp + /// \return + /// + QDateTime timestamp() const { + return _timestamp; + } + + /// + /// \brief request + /// \return + /// + bool isRequest() const { + return _request; + } + + /// + /// \brief deviceId + /// \return + /// + int deviceId() const { + return _deviceId; + } + + /// + /// \brief function + /// \return + /// + ModbusFunction function() const { + return ModbusFunction((QModbusPdu::FunctionCode)_funcCode); + } + + /// + /// \brief functionCode + /// \return + /// + QModbusPdu::FunctionCode functionCode() const { + return (QModbusPdu::FunctionCode)(_funcCode & ~QModbusPdu::ExceptionByte); + } + + /// + /// \brief isException + /// \return + /// + bool isException() const { + return !isRequest() && (_funcCode & QModbusPdu::ExceptionByte); + } + + /// + /// \brief exception + /// \return + /// + ModbusException exception() const { + return ModbusException((QModbusPdu::ExceptionCode)_exceptionCode); + } + + /// + /// \brief toString + /// \param mode + /// \return + /// + QString toString(DataDisplayMode mode) const { + return formatByteArray(mode, *this); + } + + /// + /// \brief rawData + /// \return + /// + QByteArray rawData() const + { + return _data; + } + + /// + /// \brief checksum + /// \return + /// + int checksum() const { + return _checksum; + } + + /// + /// \brief calcChecksum + /// \return + /// + int calcChecksum() const { + return _calcChecksum; + } + + /// + /// \brief matchingChecksum + /// \return + /// + bool matchingChecksum() const { + return _calcChecksum == _checksum; + } + + /// + /// \brief operator QByteArray + /// + operator QByteArray() const { + switch(_type) + { + case Adu: + return _data; + + case Pdu: + { + QByteArray data; + data.push_back(_deviceId); + data.push_back(_funcCode); + data.push_back(_data); + return data; + } + } + return QByteArray(); + } + +protected: + int dataSize() const { + switch(_type) + { + case Adu: + return qMax(0, _data.size() - 8); + + case Pdu: + return _data.size(); + } + return 0; + } + + quint8 data(int idx) const { + switch(_type) + { + case Adu: + return idx < _data.size() - 8 ? _data.at(8 + idx) : 0; + + case Pdu: + return idx < _data.size() ? _data.at(idx) : 0; + } + return 0; + } + + QByteArray slice(int idx, int len = -1) const { + switch(_type) + { + case Adu: + return _data.mid(idx + 8, len); + + case Pdu: + return _data.mid(idx, len); + } + return 0; + } + +private: + int calcCRC(const QModbusPdu& pdu, int deviceId) { + if(_protocol != QModbusAdu::Rtu) + return 0; + + QByteArray data; + data.push_back(deviceId); + data.push_back(pdu.functionCode()); + data.push_back(pdu.data()); + + return QModbusAdu::calculateCRC(data, data.length()); + } + +private: + const Type _type; + QModbusAdu::Type _protocol; + const QByteArray _data; + const quint8 _funcCode; + const quint8 _exceptionCode; + const QDateTime _timestamp; + const int _transactionId; + const int _protocolId; + const int _length; + const int _deviceId; + const bool _request; + const bool _isValid; + const int _checksum; + const int _calcChecksum; +}; +Q_DECLARE_METATYPE(const ModbusMessage*) + +#endif // MODBUSMESSAGE_H diff --git a/omodscan/modbusmessages/modbusmessages.h b/omodscan/modbusmessages/modbusmessages.h new file mode 100644 index 0000000..7d8d2bf --- /dev/null +++ b/omodscan/modbusmessages/modbusmessages.h @@ -0,0 +1,19 @@ +#include "modbusmessage.h" +#include "readcoils.h" +#include "readdiscreteinputs.h" +#include "readholdingregisters.h" +#include "readinputregisters.h" +#include "writesinglecoil.h" +#include "writesingleregister.h" +#include "readexceptionstatus.h" +#include "diagnostics.h" +#include "getcommeventcounter.h" +#include "getcommeventlog.h" +#include "writemultiplecoils.h" +#include "writemultipleregisters.h" +#include "reportserverid.h" +#include "readfilerecord.h" +#include "writefilerecord.h" +#include "maskwriteregister.h" +#include "readwritemultipleregisters.h" +#include "readfifoqueue.h" diff --git a/omodscan/modbusmessages/readcoils.h b/omodscan/modbusmessages/readcoils.h new file mode 100644 index 0000000..a29703b --- /dev/null +++ b/omodscan/modbusmessages/readcoils.h @@ -0,0 +1,122 @@ +#ifndef READCOILS_H +#define READCOILS_H + +#include "numericutils.h" +#include "modbusmessage.h" + +/// +/// \brief The ReadCoilsRequest class +/// +class ReadCoilsRequest : public ModbusMessage +{ +public: + /// + /// \brief ReadCoilsRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadCoilsRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadCoils); + } + + /// + /// \brief ReadCoilsRequest + /// \param adu + /// \param timestamp + /// + ReadCoilsRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadCoils); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + length() >= 1 && length() <= 0x7D0; + } + + /// + /// \brief startAddress + /// \return + /// + quint16 startAddress() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief length + /// \return + /// + quint16 length() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } +}; + +/// +/// \brief The ReadCoilsResponse class +/// +class ReadCoilsResponse : public ModbusMessage +{ +public: + /// + /// \brief ReadCoilsResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadCoilsResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadCoils); + } + + /// + /// \brief ReadCoilsResponse + /// \param adu + /// \param timestamp + /// + ReadCoilsResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadCoils); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + byteCount() > 0 && + (byteCount() == coilStatus().size() || byteCount() == coilStatus().size() - 1); + } + + /// + /// \brief byteCount + /// \return + /// + quint8 byteCount() const { + return data(0); + } + + /// + /// \brief coilStatus + /// \return + /// + QByteArray coilStatus() const { + return slice(1); + } +}; + +#endif // READCOILS_H diff --git a/omodscan/modbusmessages/readdiscreteinputs.h b/omodscan/modbusmessages/readdiscreteinputs.h new file mode 100644 index 0000000..17fa22c --- /dev/null +++ b/omodscan/modbusmessages/readdiscreteinputs.h @@ -0,0 +1,121 @@ +#ifndef READDISCRETEINPUTS_H +#define READDISCRETEINPUTS_H + +#include "modbusmessage.h" + +/// +/// \brief The ReadDiscreteInputsRequest class +/// +class ReadDiscreteInputsRequest : public ModbusMessage +{ +public: + /// + /// \brief ReadDiscreteInputsRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadDiscreteInputsRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadDiscreteInputs); + } + + /// + /// \brief ReadDiscreteInputsRequest + /// \param adu + /// \param timestamp + /// + ReadDiscreteInputsRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadDiscreteInputs); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + length() >= 1 && length() <= 0x7D0; + } + + /// + /// \brief startAddress + /// \return + /// + quint16 startAddress() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief length + /// \return + /// + quint16 length() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } +}; + +/// +/// \brief The ReadDiscreteInputsResponse class +/// +class ReadDiscreteInputsResponse : public ModbusMessage +{ +public: + /// + /// \brief ReadDiscreteInputsResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadDiscreteInputsResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadDiscreteInputs); + } + + /// + /// \brief ReadDiscreteInputsResponse + /// \param adu + /// \param timestamp + /// + ReadDiscreteInputsResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadDiscreteInputs); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + byteCount() > 0 && + (byteCount() == inputStatus().size() || byteCount() == inputStatus().size() - 1); + } + + /// + /// \brief byteCount + /// \return + /// + quint8 byteCount() const { + return data(0); + } + + /// + /// \brief inputStatus + /// \return + /// + QByteArray inputStatus() const { + return slice(1); + } +}; + +#endif // READDISCRETEINPUTS_H diff --git a/omodscan/modbusmessages/readexceptionstatus.h b/omodscan/modbusmessages/readexceptionstatus.h new file mode 100644 index 0000000..d127724 --- /dev/null +++ b/omodscan/modbusmessages/readexceptionstatus.h @@ -0,0 +1,86 @@ +#ifndef READEXCEPTIONSTATUS_H +#define READEXCEPTIONSTATUS_H + +#include "modbusmessage.h" + +/// +/// \brief The ReadExceptionStatusRequest class +/// +class ReadExceptionStatusRequest : public ModbusMessage +{ +public: + /// + /// \brief ReadExceptionStatusRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadExceptionStatusRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadExceptionStatus); + } + + /// + /// \brief ReadExceptionStatusRequest + /// \param adu + /// \param timestamp + /// + ReadExceptionStatusRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadExceptionStatus); + } +}; + +/// +/// \brief The ReadExceptionStatusResponse class +/// +class ReadExceptionStatusResponse : public ModbusMessage +{ +public: + /// + /// \brief ReadExceptionStatusResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadExceptionStatusResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadExceptionStatus); + } + + /// + /// \brief ReadExceptionStatusResponse + /// \param adu + /// \param timestamp + /// + ReadExceptionStatusResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadExceptionStatus); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && dataSize() == 1; + } + + /// + /// \brief outputData + /// \return + /// + quint8 outputData() const { + return data(0); + } +}; + +#endif // READEXCEPTIONSTATUS_H diff --git a/omodscan/modbusmessages/readfifoqueue.h b/omodscan/modbusmessages/readfifoqueue.h new file mode 100644 index 0000000..9be9c1e --- /dev/null +++ b/omodscan/modbusmessages/readfifoqueue.h @@ -0,0 +1,120 @@ +#ifndef READFIFOQUEUE_H +#define READFIFOQUEUE_H + +#include "modbusmessage.h" + +/// +/// \brief The ReadFifoQueueRequest class +/// +class ReadFifoQueueRequest : public ModbusMessage +{ +public: + /// + /// \brief ReadFifoQueueRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadFifoQueueRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadFifoQueue); + } + + /// + /// \brief ReadFifoQueueRequest + /// \param adu + /// \param timestamp + /// + ReadFifoQueueRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadFifoQueue); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && dataSize() == 2; + } + + /// + /// \brief fifoAddress + /// \return + /// + quint16 fifoAddress() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } +}; + +/// +/// \brief The ReadFifoQueueResponse class +/// +class ReadFifoQueueResponse : public ModbusMessage +{ +public: + /// + /// \brief ReadFifoQueueResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadFifoQueueResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadFifoQueue); + } + + /// + /// \brief ReadFifoQueueResponse + /// \param adu + /// \param timestamp + /// + ReadFifoQueueResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadFifoQueue); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + fifoCount() <= 31 && + fifoCount() == fifoValue().size(); + } + + /// + /// \brief byteCount + /// \return + /// + quint16 byteCount() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief fifoCount + /// \return + /// + quint16 fifoCount() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } + + /// + /// \brief fifoValue + /// \return + /// + QByteArray fifoValue() const { + return slice(4); + } +}; + +#endif // READFIFOQUEUE_H diff --git a/omodscan/modbusmessages/readfilerecord.h b/omodscan/modbusmessages/readfilerecord.h new file mode 100644 index 0000000..aabe672 --- /dev/null +++ b/omodscan/modbusmessages/readfilerecord.h @@ -0,0 +1,120 @@ +#ifndef READFILERECORD_H +#define READFILERECORD_H + +#include "modbusmessage.h" + +/// +/// \brief The ReadFileRecordRequest class +/// +class ReadFileRecordRequest : public ModbusMessage +{ +public: + /// + /// \brief ReadFileRecordRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadFileRecordRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadFileRecord); + } + + /// + /// \brief ReadFileRecordRequest + /// \param adu + /// \param timestamp + /// + ReadFileRecordRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadFileRecord); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + byteCount() >= 0x07 && byteCount() <= 0xF5; + } + + /// + /// \brief byteCount + /// \return + /// + quint8 byteCount() const { + return ModbusMessage::data(0); + } + + /// + /// \brief data + /// \return + /// + QByteArray data() const { + return slice(1); + } +}; + +/// +/// \brief The ReadFileRecordResponse class +/// +class ReadFileRecordResponse : public ModbusMessage +{ +public: + /// + /// \brief ReadFileRecordResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadFileRecordResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadFileRecord); + } + + /// + /// \brief ReadFileRecordResponse + /// \param adu + /// \param timestamp + /// + ReadFileRecordResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadFileRecord); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + byteCount() >= 0x07 && byteCount() <= 0xF5;; + } + + /// + /// \brief byteCount + /// \return + /// + quint8 byteCount() const { + return ModbusMessage::data(0); + } + + /// + /// \brief data + /// \return + /// + QByteArray data() const { + return slice(1); + } +}; + +#endif // READFILERECORD_H diff --git a/omodscan/modbusmessages/readholdingregisters.h b/omodscan/modbusmessages/readholdingregisters.h new file mode 100644 index 0000000..3ebdc84 --- /dev/null +++ b/omodscan/modbusmessages/readholdingregisters.h @@ -0,0 +1,121 @@ +#ifndef READHOLDINGREGISTERS_H +#define READHOLDINGREGISTERS_H + +#include "modbusmessage.h" + +/// +/// \brief The ReadHoldingRegistersRequest class +/// +class ReadHoldingRegistersRequest : public ModbusMessage +{ +public: + /// + /// \brief ReadHoldingRegistersRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadHoldingRegistersRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadHoldingRegisters); + } + + /// + /// \brief ReadHoldingRegistersRequest + /// \param adu + /// \param timestamp + /// + ReadHoldingRegistersRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadHoldingRegisters); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + length() >= 1 && length() <= 0x7D; + } + + /// + /// \brief startAddress + /// \return + /// + quint16 startAddress() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief length + /// \return + /// + quint16 length() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } +}; + +/// +/// \brief The ReadHoldingRegistersResponse class +/// +class ReadHoldingRegistersResponse : public ModbusMessage +{ +public: + /// + /// \brief ReadHoldingRegistersResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadHoldingRegistersResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadHoldingRegisters); + } + + /// + /// \brief ReadHoldingRegistersResponse + /// \param adu + /// \param timestamp + /// + ReadHoldingRegistersResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadHoldingRegisters); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + byteCount() > 0 && + byteCount() == registerValue().size(); + } + + /// + /// \brief byteCount + /// \return + /// + quint8 byteCount() const { + return data(0); + } + + /// + /// \brief registerValue + /// \return + /// + QByteArray registerValue() const { + return slice(1); + } +}; + +#endif // READHOLDINGREGISTERS_H diff --git a/omodscan/modbusmessages/readinputregisters.h b/omodscan/modbusmessages/readinputregisters.h new file mode 100644 index 0000000..7b221db --- /dev/null +++ b/omodscan/modbusmessages/readinputregisters.h @@ -0,0 +1,121 @@ +#ifndef READINPUTREGISTERS_H +#define READINPUTREGISTERS_H + +#include "modbusmessage.h" + +/// +/// \brief The ReadInputRegistersRequest class +/// +class ReadInputRegistersRequest : public ModbusMessage +{ +public: + /// + /// \brief ReadInputRegistersRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadInputRegistersRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadInputRegisters); + } + + /// + /// \brief ReadInputRegistersRequest + /// \param adu + /// \param timestamp + /// + ReadInputRegistersRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadInputRegisters); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + length() >= 1 && length() <= 0x7D; + } + + /// + /// \brief startAddress + /// \return + /// + quint16 startAddress() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief length + /// \return + /// + quint16 length() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } +}; + +/// +/// \brief The ReadInputRegistersResponse class +/// +class ReadInputRegistersResponse : public ModbusMessage +{ +public: + /// + /// \brief ReadInputRegistersResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadInputRegistersResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadInputRegisters); + } + + /// + /// \brief ReadInputRegistersResponse + /// \param adu + /// \param timestamp + /// + ReadInputRegistersResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadInputRegisters); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + byteCount() > 0 && + byteCount() == registerValue().size(); + } + + /// + /// \brief byteCount + /// \return + /// + quint8 byteCount() const { + return data(0); + } + + /// + /// \brief registerValue + /// \return + /// + QByteArray registerValue() const { + return slice(1); + } +}; + +#endif // READINPUTREGISTERS_H diff --git a/omodscan/modbusmessages/readwritemultipleregisters.h b/omodscan/modbusmessages/readwritemultipleregisters.h new file mode 100644 index 0000000..93b82ac --- /dev/null +++ b/omodscan/modbusmessages/readwritemultipleregisters.h @@ -0,0 +1,156 @@ +#ifndef READWRITEMULTIPLEREGISTERS_H +#define READWRITEMULTIPLEREGISTERS_H + +#include "modbusmessage.h" + +/// +/// \brief The ReadWriteMultipleRegistersRequest class +/// +class ReadWriteMultipleRegistersRequest : public ModbusMessage +{ +public: + /// + /// \brief ReadWriteMultipleRegistersRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadWriteMultipleRegistersRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadWriteMultipleRegisters); + } + + /// + /// \brief ReadWriteMultipleRegistersRequest + /// \param adu + /// \param timestamp + /// + ReadWriteMultipleRegistersRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadWriteMultipleRegisters); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + readLength() >= 1 && readLength() <= 0x7D && + writeLength() >= 1 && writeLength() <= 0x79 && + writeByteCount() > 0 && + writeByteCount() == writeValues().size(); + } + + /// + /// \brief readStartAddress + /// \return + /// + quint16 readStartAddress() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief readLength + /// \return + /// + quint16 readLength() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } + + /// + /// \brief writeStartAddress + /// \return + /// + quint16 writeStartAddress() const { + return makeWord(data(5), data(4), ByteOrder::LittleEndian); + } + + /// + /// \brief writeLength + /// \return + /// + quint16 writeLength() const { + return makeWord(data(7), data(6), ByteOrder::LittleEndian); + } + + /// + /// \brief writeByteCount + /// \return + /// + quint8 writeByteCount() const { + return data(8); + } + + /// + /// \brief writeValues + /// \return + /// + QByteArray writeValues() const { + return slice(9); + } +}; + +/// +/// \brief The ReadWriteMultipleRegistersResponse class +/// +class ReadWriteMultipleRegistersResponse : public ModbusMessage +{ +public: + /// + /// \brief ReadWriteMultipleRegistersResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReadWriteMultipleRegistersResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadWriteMultipleRegisters); + } + + /// + /// \brief ReadWriteMultipleRegistersResponse + /// \param adu + /// \param timestamp + /// + ReadWriteMultipleRegistersResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::ReadWriteMultipleRegisters); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + byteCount() > 0 && + byteCount() == values().size(); + } + + /// + /// \brief byteCount + /// \return + /// + quint8 byteCount() const { + return data(0); + } + + /// + /// \brief values + /// \return + /// + QByteArray values() const { + return slice(1); + } +}; + +#endif // READWRITEMULTIPLEREGISTERS_H diff --git a/omodscan/modbusmessages/reportserverid.h b/omodscan/modbusmessages/reportserverid.h new file mode 100644 index 0000000..0098fc0 --- /dev/null +++ b/omodscan/modbusmessages/reportserverid.h @@ -0,0 +1,94 @@ +#ifndef REPORTSERVERID_H +#define REPORTSERVERID_H + +#include "modbusmessage.h" + +/// +/// \brief The ReportServerIdRequest class +/// +class ReportServerIdRequest : public ModbusMessage +{ +public: + /// + /// \brief ReportServerIdRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReportServerIdRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReportServerId); + } + + /// + /// \brief ReportServerIdRequest + /// \param adu + /// \param timestamp + /// + ReportServerIdRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::ReportServerId); + } +}; + +/// +/// \brief The ReportServerIdResponse class +/// +class ReportServerIdResponse : public ModbusMessage +{ +public: + /// + /// \brief ReportServerIdResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + ReportServerIdResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::ReportServerId); + } + + /// + /// \brief ReportServerIdResponse + /// \param adu + /// \param timestamp + /// + ReportServerIdResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::ReportServerId); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && dataSize() > 1; + } + + /// + /// \brief byteCount + /// \return + /// + quint8 byteCount() const { + return ModbusMessage::data(0); + } + + /// + /// \brief data + /// \return + /// + QByteArray data() const { + return slice(1); + } +}; + +#endif // REPORTSERVERID_H diff --git a/omodscan/modbusmessages/writefilerecord.h b/omodscan/modbusmessages/writefilerecord.h new file mode 100644 index 0000000..807f260 --- /dev/null +++ b/omodscan/modbusmessages/writefilerecord.h @@ -0,0 +1,122 @@ +#ifndef WRITEFILERECORD_H +#define WRITEFILERECORD_H + +#include "modbusmessage.h" + +/// +/// \brief The WriteFileRecordRequest class +/// +class WriteFileRecordRequest : public ModbusMessage +{ +public: + /// + /// \brief WriteFileRecordRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + WriteFileRecordRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteFileRecord); + } + + /// + /// \brief WriteFileRecordRequest + /// \param adu + /// \param timestamp + /// + WriteFileRecordRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteFileRecord); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + length() >= 0x09 && + length() <= 0xFB; + } + + /// + /// \brief length + /// \return + /// + quint8 length() const { + return ModbusMessage::data(0); + } + + /// + /// \brief data + /// \return + /// + QByteArray data() const { + return slice(1); + } +}; + +/// +/// \brief The WriteFileRecordResponse class +/// +class WriteFileRecordResponse : public ModbusMessage +{ +public: + /// + /// \brief WriteFileRecordResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + WriteFileRecordResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteFileRecord); + } + + /// + /// \brief WriteFileRecordResponse + /// \param adu + /// \param timestamp + /// + WriteFileRecordResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteFileRecord); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + length() >= 0x09 && + length() <= 0xFB; + } + + /// + /// \brief length + /// \return + /// + quint8 length() const { + return ModbusMessage::data(0); + } + + /// + /// \brief data + /// \return + /// + QByteArray data() const { + return slice(1); + } +}; + +#endif // WRITEFILERECORD_H diff --git a/omodscan/modbusmessages/writemultiplecoils.h b/omodscan/modbusmessages/writemultiplecoils.h new file mode 100644 index 0000000..07e776f --- /dev/null +++ b/omodscan/modbusmessages/writemultiplecoils.h @@ -0,0 +1,136 @@ +#ifndef WRITEMULTIPLECOILS_H +#define WRITEMULTIPLECOILS_H + +#include "modbusmessage.h" + +/// +/// \brief The WriteMultipleCoilsRequest class +/// +class WriteMultipleCoilsRequest : public ModbusMessage +{ +public: + /// + /// \brief WriteMultipleCoilsRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + WriteMultipleCoilsRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteMultipleCoils); + } + + /// + /// \brief WriteMultipleCoilsRequest + /// \param adu + /// \param timestamp + /// + WriteMultipleCoilsRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteMultipleCoils); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + byteCount() > 0 && + byteCount() == values().size(); + } + + /// + /// \brief startAddress + /// \return + /// + quint16 startAddress() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief quantity + /// \return + /// + quint16 quantity() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } + + /// + /// \brief byteCount + /// \return + /// + quint8 byteCount() const { + return data(4); + } + + /// + /// \brief values + /// \return + /// + QByteArray values() const { + return slice(5); + } +}; + +/// +/// \brief The WriteMultipleCoilsResponse class +/// +class WriteMultipleCoilsResponse : public ModbusMessage +{ +public: + /// + /// \brief WriteMultipleCoilsResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + WriteMultipleCoilsResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteMultipleCoils); + } + + /// + /// \brief WriteMultipleCoilsResponse + /// \param adu + /// \param timestamp + /// + WriteMultipleCoilsResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteMultipleCoils); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && dataSize() == 4; + } + + /// + /// \brief startAddress + /// \return + /// + quint16 startAddress() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief quantity + /// \return + /// + quint16 quantity() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } +}; + +#endif // WRITEMULTIPLECOILS_H diff --git a/omodscan/modbusmessages/writemultipleregisters.h b/omodscan/modbusmessages/writemultipleregisters.h new file mode 100644 index 0000000..925733f --- /dev/null +++ b/omodscan/modbusmessages/writemultipleregisters.h @@ -0,0 +1,136 @@ +#ifndef WRITEMULTIPLEREGISTERS_H +#define WRITEMULTIPLEREGISTERS_H + +#include "modbusmessage.h" + +/// +/// \brief The WriteMultipleRegistersRequest class +/// +class WriteMultipleRegistersRequest : public ModbusMessage +{ +public: + /// + /// \brief WriteMultipleRegistersRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + WriteMultipleRegistersRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteMultipleRegisters); + } + + /// + /// \brief WriteMultipleRegistersRequest + /// \param adu + /// \param timestamp + /// + WriteMultipleRegistersRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteMultipleRegisters); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + byteCount() > 0 && + byteCount() == values().size(); + } + + /// + /// \brief startAddress + /// \return + /// + quint16 startAddress() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief quantity + /// \return + /// + quint16 quantity() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } + + /// + /// \brief byteCount + /// \return + /// + quint8 byteCount() const { + return data(4); + } + + /// + /// \brief values + /// \return + /// + QByteArray values() const { + return slice(5); + } +}; + +/// +/// \brief The WriteMultipleRegistersResponse class +/// +class WriteMultipleRegistersResponse : public ModbusMessage +{ +public: + /// + /// \brief WriteMultipleRegistersResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + WriteMultipleRegistersResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteMultipleRegisters); + } + + /// + /// \brief WriteMultipleRegistersResponse + /// \param adu + /// \param timestamp + /// + WriteMultipleRegistersResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteMultipleRegisters); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && dataSize() == 4; + } + + /// + /// \brief startAddress + /// \return + /// + quint16 startAddress() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief quantity + /// \return + /// + quint16 quantity() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } +}; + +#endif // WRITEMULTIPLEREGISTERS_H diff --git a/omodscan/modbusmessages/writesinglecoil.h b/omodscan/modbusmessages/writesinglecoil.h new file mode 100644 index 0000000..d02b810 --- /dev/null +++ b/omodscan/modbusmessages/writesinglecoil.h @@ -0,0 +1,120 @@ +#ifndef WRITESINGLECOIL_H +#define WRITESINGLECOIL_H + +#include "modbusmessage.h" + +/// +/// \brief The WriteSingleCoilRequest class +/// +class WriteSingleCoilRequest : public ModbusMessage +{ +public: + /// + /// \brief WriteSingleCoilRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + WriteSingleCoilRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteSingleCoil); + } + + /// + /// \brief WriteSingleCoilRequest + /// \param adu + /// \param timestamp + /// + WriteSingleCoilRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteSingleCoil); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + (value() == 0 || value() == 0xFF00); + } + + /// + /// \brief address + /// \return + /// + quint16 address() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief value + /// \return + /// + quint16 value() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } +}; + +/// +/// \brief The WriteSingleCoilResponse class +/// +class WriteSingleCoilResponse : public ModbusMessage +{ +public: + /// + /// \brief WriteSingleCoilResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + WriteSingleCoilResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteSingleCoil); + } + + /// + /// \brief WriteSingleCoilResponse + /// \param adu + /// \param timestamp + /// + WriteSingleCoilResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteSingleCoil); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && + (value() == 0 || value() == 0xFF00); + } + + /// + /// \brief address + /// \return + /// + quint16 address() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief value + /// \return + /// + quint16 value() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } +}; + +#endif // WRITESINGLECOIL_H diff --git a/omodscan/modbusmessages/writesingleregister.h b/omodscan/modbusmessages/writesingleregister.h new file mode 100644 index 0000000..a24ff74 --- /dev/null +++ b/omodscan/modbusmessages/writesingleregister.h @@ -0,0 +1,118 @@ +#ifndef WRITESINGLEREGISTER_H +#define WRITESINGLEREGISTER_H + +#include "modbusmessage.h" + +/// +/// \brief The WriteSingleRegisterRequest class +/// +class WriteSingleRegisterRequest : public ModbusMessage +{ +public: + /// + /// \brief WriteSingleRegisterRequest + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + WriteSingleRegisterRequest(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + : ModbusMessage(pdu, protocol, deviceId, timestamp, true, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteSingleRegister); + } + + /// + /// \brief WriteSingleRegisterRequest + /// \param adu + /// \param timestamp + /// + WriteSingleRegisterRequest(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, true) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteSingleRegister); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && dataSize() == 4; + } + + /// + /// \brief address + /// \return + /// + quint16 address() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief value + /// \return + /// + quint16 value() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } +}; + +/// +/// \brief The WriteSingleRegisterResponse class +/// +class WriteSingleRegisterResponse : public ModbusMessage +{ +public: + /// + /// \brief WriteSingleRegisterResponse + /// \param pdu + /// \param protocol + /// \param deviceId + /// \param timestamp + /// \param checksum + /// + WriteSingleRegisterResponse(const QModbusPdu& pdu, QModbusAdu::Type protocol, int deviceId, const QDateTime& timestamp, int checksum) + :ModbusMessage(pdu, protocol, deviceId, timestamp, false, checksum) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteSingleRegister); + } + + /// + /// \brief WriteSingleRegisterResponse + /// \param adu + /// \param timestamp + /// + WriteSingleRegisterResponse(const QModbusAdu& adu, const QDateTime& timestamp) + : ModbusMessage(adu, timestamp, false) + { + Q_ASSERT(functionCode() == QModbusPdu::WriteSingleRegister); + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const override { + return ModbusMessage::isValid() && dataSize() == 4; + } + + /// + /// \brief address + /// \return + /// + quint16 address() const { + return makeWord(data(1), data(0), ByteOrder::LittleEndian); + } + + /// + /// \brief value + /// \return + /// + quint16 value() const { + return makeWord(data(3), data(2), ByteOrder::LittleEndian); + } +}; + +#endif // WRITESINGLEREGISTER_H diff --git a/omodscan/floatutils.h b/omodscan/numericutils.h similarity index 77% rename from omodscan/floatutils.h rename to omodscan/numericutils.h index c0d89cd..5a3727e 100644 --- a/omodscan/floatutils.h +++ b/omodscan/numericutils.h @@ -1,10 +1,48 @@ -#ifndef FLOATUTILS_H -#define FLOATUTILS_H +#ifndef NUMERICUTILS_H +#define NUMERICUTILS_H #include #include #include "byteorderutils.h" +/// +/// \brief makeWord +/// \param lo +/// \param hi +/// \return +/// +inline quint16 makeWord(quint8 lo, quint8 hi, ByteOrder order) +{ + union { + quint8 asUint8[2]; + quint16 asUint16; + } v; + + v.asUint8[0] = (order == ByteOrder::LittleEndian) ? lo : hi; + v.asUint8[1] = (order == ByteOrder::LittleEndian) ? hi : lo; + + return v.asUint16; +} + +/// +/// \brief breakWord +/// \param value +/// \param lo +/// \param hi +/// \param order +/// +inline void breakWord(quint16 value, quint8& lo, quint8& hi, ByteOrder order) +{ + union { + quint8 asUint8[2]; + quint16 asUint16; + } v; + v.asUint16 = value; + + lo = (order == ByteOrder::LittleEndian) ? v.asUint8[0] : v.asUint8[1]; + hi = (order == ByteOrder::LittleEndian) ? v.asUint8[1] : v.asUint8[0]; +} + /// /// \brief breakFloat /// \param value @@ -154,4 +192,4 @@ inline double makeDouble(quint16 lolo, quint16 lohi, quint16 hilo, quint16 hihi, return v.asDouble; } -#endif // FLOATUTILS_H +#endif // NUMERICUTILS_H diff --git a/omodscan/omodscan.pro b/omodscan/omodscan.pro index 7da0f51..d94fe6b 100644 --- a/omodscan/omodscan.pro +++ b/omodscan/omodscan.pro @@ -4,7 +4,7 @@ CONFIG += c++17 CONFIG -= debug_and_release CONFIG -= debug_and_release_target -VERSION = 1.5.2 +VERSION = 1.6.0 QMAKE_TARGET_PRODUCT = "Open ModScan" QMAKE_TARGET_DESCRIPTION = "An Open Source Modbus Master (Client) Utility" @@ -21,10 +21,11 @@ win32:RC_ICONS += res/omodscan.ico INCLUDEPATH += controls \ dialogs \ + modbusmessages \ SOURCES += \ controls/booleancombobox.cpp \ - controls/bytelistlineedit.cpp \ + controls/bytelisttextedit.cpp \ controls/byteordercombobox.cpp \ controls/clickablelabel.cpp \ controls/connectioncombobox.cpp \ @@ -35,6 +36,8 @@ SOURCES += \ controls/functioncodecombobox.cpp \ controls/ipaddresslineedit.cpp \ controls/mainstatusbar.cpp \ + controls/modbuslogwidget.cpp \ + controls/modbusmessagewidget.cpp \ controls/numericlineedit.cpp \ controls/paritytypecombobox.cpp \ controls/simulationmodecombobox.cpp \ @@ -43,6 +46,7 @@ SOURCES += \ controls/outputwidget.cpp \ controls/pointtypecombobox.cpp \ datasimulator.cpp \ + dialogs/dialogmsgparser.cpp \ dialogs/dialogabout.cpp \ dialogs/dialogaddressscan.cpp \ dialogs/dialogautosimulation.cpp \ @@ -63,10 +67,12 @@ SOURCES += \ dialogs/dialogwriteholdingregister.cpp \ dialogs/dialogwriteholdingregisterbits.cpp \ formmodsca.cpp \ + htmldelegate.cpp \ main.cpp \ mainwindow.cpp \ modbusclient.cpp \ modbusdataunit.cpp \ + modbusmessages/modbusmessage.cpp \ modbusrtuscanner.cpp \ modbusscanner.cpp \ modbustcpscanner.cpp \ @@ -80,7 +86,7 @@ HEADERS += \ byteorderutils.h \ connectiondetails.h \ controls/booleancombobox.h \ - controls/bytelistlineedit.h \ + controls/bytelisttextedit.h \ controls/byteordercombobox.h \ controls/clickablelabel.h \ controls/connectioncombobox.h \ @@ -91,6 +97,8 @@ HEADERS += \ controls/functioncodecombobox.h \ controls/ipaddresslineedit.h \ controls/mainstatusbar.h \ + controls/modbuslogwidget.h \ + controls/modbusmessagewidget.h \ controls/numericlineedit.h \ controls/paritytypecombobox.h \ controls/simulationmodecombobox.h \ @@ -99,6 +107,7 @@ HEADERS += \ controls/outputwidget.h \ controls/pointtypecombobox.h \ datasimulator.h \ + dialogs/dialogmsgparser.h \ dialogs/dialogabout.h \ dialogs/dialogaddressscan.h \ dialogs/dialogautosimulation.h \ @@ -120,20 +129,44 @@ HEADERS += \ dialogs/dialogwriteholdingregisterbits.h \ displaydefinition.h \ enums.h \ - floatutils.h \ + formatutils.h \ formmodsca.h \ + htmldelegate.h \ mainwindow.h \ modbusclient.h \ modbusdataunit.h \ modbusexception.h \ + modbusfunction.h \ + modbusmessages/diagnostics.h \ + modbusmessages/getcommeventcounter.h \ + modbusmessages/getcommeventlog.h \ + modbusmessages/maskwriteregister.h \ + modbusmessages/modbusmessage.h \ + modbusmessages/modbusmessages.h \ + modbusmessages/readcoils.h \ modbuslimits.h \ + modbusmessages/readdiscreteinputs.h \ + modbusmessages/readexceptionstatus.h \ + modbusmessages/readfifoqueue.h \ + modbusmessages/readfilerecord.h \ + modbusmessages/readholdingregisters.h \ + modbusmessages/readinputregisters.h \ + modbusmessages/readwritemultipleregisters.h \ + modbusmessages/reportserverid.h \ + modbusmessages/writefilerecord.h \ + modbusmessages/writemultiplecoils.h \ + modbusmessages/writemultipleregisters.h \ + modbusmessages/writesinglecoil.h \ + modbusmessages/writesingleregister.h \ modbusrtuscanner.h \ modbusscanner.h \ modbussimulationparams.h \ modbustcpscanner.h \ modbuswriteparams.h \ + numericutils.h \ qfixedsizedialog.h \ qhexvalidator.h \ + qmodbusadu.h \ qrange.h \ quintvalidator.h \ recentfileactionlist.h \ @@ -142,6 +175,7 @@ HEADERS += \ FORMS += \ controls/outputwidget.ui \ controls/statisticwidget.ui \ + dialogs/dialogmsgparser.ui \ dialogs/dialogabout.ui \ dialogs/dialogaddressscan.ui \ dialogs/dialogautosimulation.ui \ diff --git a/omodscan/qfixedsizedialog.cpp b/omodscan/qfixedsizedialog.cpp index f542e16..e694821 100644 --- a/omodscan/qfixedsizedialog.cpp +++ b/omodscan/qfixedsizedialog.cpp @@ -25,7 +25,7 @@ void QFixedSizeDialog::showEvent(QShowEvent* e) if(parentWidget() != nullptr) { - QRect parentRect(parentWidget()->mapToGlobal(QPoint(0, 0)), parentWidget()->size()); - move(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), parentRect).topLeft()); + const auto rc = parentWidget()->frameGeometry(); + move(rc.center() - rect().center()); } } diff --git a/omodscan/qhexvalidator.cpp b/omodscan/qhexvalidator.cpp index 9d2be3e..7b00af4 100644 --- a/omodscan/qhexvalidator.cpp +++ b/omodscan/qhexvalidator.cpp @@ -9,6 +9,12 @@ QHexValidator::QHexValidator(QObject *parent) { } +/// +/// \brief QHexValidator::QHexValidator +/// \param bottom +/// \param top +/// \param parent +/// QHexValidator::QHexValidator(int bottom, int top, QObject* parent) : QIntValidator(bottom, top, parent) { diff --git a/omodscan/qmodbusadu.h b/omodscan/qmodbusadu.h new file mode 100644 index 0000000..5420c12 --- /dev/null +++ b/omodscan/qmodbusadu.h @@ -0,0 +1,185 @@ +#ifndef QMODBUSADU_H +#define QMODBUSADU_H + +#include +#include +#include "numericutils.h" + +class QModbusAdu +{ +public: + enum Type { + Rtu, + Tcp + }; + + /// + /// \brief QModbusAdu + /// \param type + /// \param data + /// + explicit QModbusAdu(Type type, const QByteArray& data) + : _type(type) + { + setData(data); + } + + /// + /// \brief type + /// \return + /// + Type type() const { + return _type; + } + + /// + /// \brief isValid + /// \return + /// + bool isValid() const { + return matchingChecksum() && _pdu.isValid() && length() == _pdu.size(); + } + + /// + /// \brief data + /// \return + /// + QByteArray data() const { return _data; } + + /// + /// \brief setData + /// \param data + /// + void setData(const QByteArray& data) { + _data = data; + + _pdu.setFunctionCode(QModbusPdu::FunctionCode((quint8)_data[7])); + if(_type == Rtu) + _pdu.setData(_data.mid(8, _data.size() - 10)); + else + _pdu.setData(_data.mid(8)); + } + + /// + /// \brief transactionId + /// \return + /// + quint16 transactionId() const { + return makeWord(_data[1], _data[0], ByteOrder::LittleEndian); + } + + /// + /// \brief protocolId + /// \return + /// + quint16 protocolId() const { + return makeWord(_data[3], _data[2], ByteOrder::LittleEndian); + } + + /// + /// \brief length + /// \return + /// + quint16 length() const { + return makeWord(_data[5], _data[4], ByteOrder::LittleEndian); + } + + /// + /// \brief serverAddress + /// \return + /// + quint8 serverAddress() const { + return quint8(_data[6]); + } + + /// + /// \brief functionCode + /// \return + /// + QModbusPdu::FunctionCode functionCode() const { + return _pdu.functionCode(); + } + + /// + /// \brief exceptionCode + /// \return + /// + QModbusPdu::ExceptionCode exceptionCode() const { + return _pdu.exceptionCode(); + } + + /// + /// \brief isException + /// \return + /// + bool isException() const { + return _pdu.isException(); + } + + /// + /// \brief checksum + /// \return + /// + quint16 checksum() const { + return _type == Rtu ? + makeWord(_data[_data.size() - 1], _data[_data.size() - 2], ByteOrder::LittleEndian) : + quint16(0); + } + + /// + /// \brief calcChecksum + /// \return + /// + quint16 calcChecksum() const { + if(_type == Rtu) + { + const auto size = _data.size() - 2; // two bytes, CRC + const auto data = _data.left(size); + return QModbusAdu::calculateCRC(data, size); + } + return 0; + } + + /// + /// \brief matchingChecksum + /// \return + /// + bool matchingChecksum() const { + return checksum() == calcChecksum(); + } + + inline static quint16 calculateCRC(const char* data, qint32 len){ + quint16 crc = 0xFFFF; + while (len--) { + const quint8 c = *data++; + for (qint32 i = 0x01; i & 0xFF; i <<= 1) { + bool bit = crc & 0x8000; + if (c & i) + bit = !bit; + crc <<= 1; + if (bit) + crc ^= 0x8005; + } + crc &= 0xFFFF; + } + crc = crc_reflect(crc & 0xFFFF, 16) ^ 0x0000; + return (crc >> 8) | (crc << 8); // swap bytes + } + +private: + inline static quint16 crc_reflect(quint16 data, qint32 len){ + quint16 ret = data & 0x01; + for (qint32 i = 1; i < len; i++) { + data >>= 1; + ret = (ret << 1) | (data & 0x01); + } + return ret; + } + +private: + Type _type; + QByteArray _data; + QModbusPdu _pdu; +}; + +#endif // QMODBUSADU_H diff --git a/omodscan/quintvalidator.h b/omodscan/quintvalidator.h index 839a245..18476fc 100644 --- a/omodscan/quintvalidator.h +++ b/omodscan/quintvalidator.h @@ -4,6 +4,9 @@ #include #include +/// +/// \brief The QUIntValidator class +/// class QUIntValidator : public QValidator { Q_OBJECT diff --git a/omodscan/translations/omodscan_ru.qm b/omodscan/translations/omodscan_ru.qm index 36d2d90..defbe2e 100644 Binary files a/omodscan/translations/omodscan_ru.qm and b/omodscan/translations/omodscan_ru.qm differ diff --git a/omodscan/translations/omodscan_ru.ts b/omodscan/translations/omodscan_ru.ts index 57c2a29..669f875 100644 --- a/omodscan/translations/omodscan_ru.ts +++ b/omodscan/translations/omodscan_ru.ts @@ -30,27 +30,27 @@ CsvExporter - + Device Id Узел - + Start Address Начальный адрес - + Length Количество - + Point Type Тип регистров - + Registers on Query Количество регистров в запросе @@ -165,7 +165,7 @@ - + Scan Сканировать @@ -200,12 +200,12 @@ Показывать только корректные ответы - + Stop Стоп - + Pdf files (*.pdf);;CSV files (*.csv) Pdf файлы (*.pdf);;CSV файлы (*.csv) @@ -214,7 +214,7 @@ Pdf файлы (*.pdf) - + No connection to MODBUS device! Нет соединения с MODBUS устройством! @@ -384,37 +384,47 @@ Настройки отображения - + Scan Rate: Частота опроса: - + (msecs) (мсек) - + + Log View Limit: + Лимит лога: + + + + (rows) + (строки) + + + Modbus Data Настройки Modbus - + Slave Address: Адрес устройства: - + Point Type: Тип регистров: - + Point Address: Начальный адрес: - + Length: Количество: @@ -427,34 +437,52 @@ - + + Slave Device: + Устройство: + + + Address: Адрес: - + Set Values to 0 Установить в 0 - + Set Values to 1 Установить в 1 - + Length: Количество: - Address: %1 - Адрес: %1 + Адрес: %1 - Length: %1 - Количество: %1 + Количество: %1 + + + + Address: <b>%1</b> + Адрес: <b>%1</b> + + + + Length: <b>%1</b> + Количество: <b>%1</b> + + + + Slave Device: <b>%1</b> + Устройство: <b>%1</b> @@ -465,34 +493,52 @@ - + + Slave Device: + Устройство: + + + Length: Количество: - + Set Random Values Установить случайные - + Set Values to 0 Установить в 0 - + Address: Адрес: - Address: %1 - Адрес: %1 + Адрес: %1 - Length: %1 - Количество: %1 + Количество: %1 + + + + Address: <b>%1</b> + Адрес: <b>%1</b> + + + + Length: <b>%1</b> + Количество: <b>%1</b> + + + + Slave Device: <b>%1</b> + Устройство: <b>%1</b> @@ -551,161 +597,161 @@ Диапазон IP адресов - - - + + + from с - - - + + + to по - + Subnet Mask Маска подсети - + Port Range Диапазон портов - + Start Scan Сканировать - + <html><head/><body><p><span style=" font-weight:700;">Scanning:</span></p></body></html> <html><head/><body><p><span style=" font-weight:700;">Сканирование:</span></p></body></html> - + Address: Адрес: - + Port: Порт: - + Baud Rate: Скорость бит/с: - + Data Bits: Биты данных: - + Parity: Четность: - + Stop Bits: Стоповые биты: - + Device Id: Узел: - + Scan Time Вермя сканирования - + Data Bits Биты данных - + Parity Четность - + None Нет - + Odd Нечет - + Even Чёт - + Stop Bits Стоповые биты - + Device Id Узел - + Modbus Request Запрос Modbus - + Function Функция - + Address Адрес - + Length Количество - + Response Timeout Таймаут ответа - + msec мсек - + <html><head/><body><p><span style=" font-weight:700;">Scan Results:</span></p></body></html> <html><head/><body><p><span style=" font-weight:700;">Результаты:</span></p></body></html> - + PORT: Device Id (serial port settings) Порт: Узел (параметры порта) - + Clear Results Очистить результаты @@ -770,41 +816,90 @@ Узел: - + Baud Rate: %1 Скорость бит/с: %1 - + Data Bits: %1 Биты данных: %1 - + Parity: %1 Четность: %1 - + Stop Bits: %1 Стоповые биты: %1 - + Address: %1 Адрес: %1 - + Port: %1 Порт: %1 - + Device Id: %1 Узел: %1 + + DialogMsgParser + + + Modbus Message Parser + Анализатор сообщений Modbus + + + + PDU Message + PDU сообщение + + + + ADU Message + ADU сообщение + + + + Hex View + Шестнадцатиричный режим + + + + Request + Запрос + + + + Device Id included + Содержит номер устройства + + + + Checksum included + Содержит контрольную сумму + + + + Enter bytes value separated by spaces + Введите значение в байтах, разделенное пробелами + + + + + Parse + Анализ + + DialogPrintSettings @@ -1036,40 +1131,55 @@ single-point write functions 05 and 06.) Пользовательская команда - + Slave Address: Адрес устройства: - + Function: Функция: - + Show Data in Отображать данные в формате - + Decimal Десятичный - + Hex Шестандцатиричный - + Send Data: Данные запроса: - + + Enter bytes value separated by spaces + Введите значение в байтах, разделенное пробелами + + + Response Buffer Ответ + + + Send + Отправить + + + + No connection to device + Нет подключения к устройству + DialogWindowsManager @@ -1229,53 +1339,57 @@ single-point write functions 05 and 06.) Тип регистров MODBUS - + Data Uninitialized Данные не инициализированы - + Device Id: %1 Узел: %1 - + Address: %1 Length: %2 Адрес: %1 Количество: %2 - + MODBUS Point Type: %1 Тип регистров MODBUS: %1 - + Number of Polls: %1 Valid Slave Responses: %2 Количество запросов: %1 Корректных ответов устройства: %2 - + + No Scan: Invalid Data Length Specified + Нет опроса: указана недопустимая длина данных + + + Device NOT CONNECTED! Устройство НЕ ПОДКЛЮЧЕНО! - Invalid Data Length Specified - Указана недопустимая длина данных + Указана недопустимая длина данных - + No Responses from Slave Device Нет ответа от устройства - + Received Invalid Response MODBUS Query Получен некорректный ответ на запрос MODBUS @@ -1374,7 +1488,7 @@ Valid Slave Responses: %2 - + Byte Order Порядок байтов @@ -1384,337 +1498,343 @@ Valid Slave Responses: %2 Расширенные параметры - + View Вид - + Config Конфигурация - + Colors Цвета - + Language Язык - + Window Окно - + Help Помощь - - + + Toolbar Панель инструментов - + Display Bar Панель отображения - + New Новый - + Open... Открыть... - + Close Закрыть - + Save Сохранить - + Save As... Сохранить как... - + Print... Печать... - + Print Setup... Настройка печати... - + Recent File Последние файлы - + Exit Выход - + Connect Подключиться - + Disconnect Отключиться - + Enable Вкл - + Disable Выкл - + Save Config Сохранить конфиг - + Restore Now Восстановить конфиг - + Quick Connect Быстрое подключение - + Data Definition Настройки отображения - + Show Data Данные - + Show Traffic Трафик - + Binary Двоичный - + Hex Шестандцатиричный - + Unsigned Decimal Беззнаковый десятичный - + Integer Целый - + Floating Pt С плавающей точкой - + Swapped FP Перевернутое с плавающей точкой - + Dbl Float Двойной точности - + Swapped Dbl Перевернутое двойной точности - + Hex Addresses Шестнадцатиричные адреса - + Force Coils Предустановка coils - + Preset Regs Предустановка регистров - + Mask Write Запись по маске - + User Msg Пользовательская команда - + Text Capture Захват в файл - + Capture Off Остановить захват - + Reset Ctrs Сброс счетчиков - + Status Bar Строка состояния - + Dsiplay Bar Панель отображения - + Background Задний план - + Foreground Передний план - + Status Статус - + Font Шрифт - + Cascade Каскадно - + Tile Замостить - - + + MODBUS Scanner - MODBUS Сканер + Cканер MODBUS - + Long Integer Длинное целое - + Swapped LI Перевернутое ДЦ - + Swapped Long Integer Перевернутое длинное целое - + Unsigned Long Integer Беззнаковое длинное целое - + Swapped Unsigned LI Перевернутое беззнаковое ДЦ - + Swapped Unsigned Long Integer Перевернутое беззнаковое длинное целое - + + + Msg Parser + Анализатор сообщений + + + About Open ModScan... О программе Open ModScan... - + Windows... Окна... - + English - + Русский - + Address scan Сканирование адресов - - - - + + + + All files (*) Все файлы (*) - + %1 was not found %1 не найден - + Failed to open %1 Ошибка открытия файла %1 @@ -1722,47 +1842,338 @@ Valid Slave Responses: %2 ModbusClient - + Invalid Modbus Request Некорректный запрос Modbus - - + + Coil Write Failure Ошибка записи в Coil регистр - - + + Register Write Failure Ошибка записи в регистр - + Mask Write Register Failure Ошибка записи в регистр по маске - + Mask Register Write Failure Ошибка записи в регистр по маске - + Connection error. %1 Ошибка подключения. %1 + + ModbusMessageWidget + + + <span style='color:%1'>*** INVALID MODBUS REQUEST ***</span> + <span style='color:%1'>*** НЕКОРРЕКТНЫЙ MODBUS ЗАПРОС ***</span> + + + + <span style='color:%1'>*** INVALID MODBUS RESPONSE ***</span> + <span style='color:%1'>*** НЕКОРРЕКТНЫЙ MODBUS ОТВЕТ ***</span> + + + + <b>Checksum:</b> %1 + <b>Контрольная сумма:</b> %1 + + + + <b>Checksum:</b> <span style='color:%3'>%1</span> (Expected: %2) + <b>Контрольная сумма:</b> <span style='color:%3'>%1</span> (ожидается: %2) + + + + <b>Type:</b> %1 + <b>Тип:</b> %1 + + + + Request (Tx) + Запрос (Tx) + + + + Response (Rx) + Ответ (Rx) + + + + <b>Timestamp:</b> %1 + <b>Метка времени:</b> %1 + + + + <b>Transaction ID:</b> %1 + <b>Идентификатор транзакции:</b> %1 + + + + <b>Protocol ID:</b> %1 + <b>Идентификатор протокола:</b> %1 + + + + + + + + <b>Length:</b> %1 + <b>Длина:</b> %1 + + + + <b>Device ID:</b> %1 + <b>Устройство:</b> %1 + + + + <b>Error Code:</b> %1 + <b>Код ошибки:</b> %1 + + + + <b>Exception Code:</b> %1 + <b>Код исключения:</b> %1 + + + + <b>Function Code:</b> %1 + <b>Код функции:</b> %1 + + + + + + + <b>Start Address:</b> %1 + <b>Адрес:</b> %1 + + + + + + + + + + + + + + + <b>Byte Count:</b> %1 + <b>Количество байт:</b> %1 + + + + <b>Coil Status:</b> %1 + <b>Статусы coil:</b> %1 + + + + <b>Input Status:</b> %1 + <b>Статусы input:</b> %1 + + + + + + <b>Register Value:</b> %1 + <b>Значения регистров:</b> %1 + + + + <b>Input Registers:</b> %1 + <b>Значения реистров:</b> %1 + + + + + <b>Output Address:</b> %1 + <b>Адрес:</b> %1 + + + + + + <b>Output Value:</b> %1 + <b>Значение:</b> %1 + + + + + <b>Register Address:</b> %1 + <b>Адрес:</b> %1 + + + + <b>Output Data:</b> %1 + <b>Данные:</b> %1 + + + + + <b>Sub-function:</b> %1 + <b>Подфункция:</b> %1 + + + + + + + + + + + <b>Data:</b> %1 + <b>Данные:</b> %1 + + + + + <b>Status:</b> %1 + <b>Статус:</b> %1 + + + + + <b>Event Count:</b> %1 + <b>Количество событий:</b> %1 + + + + <b>Message Count:</b> %1 + <b>Количество сообщений:</b> %1 + + + + <b>Events:</b> %1 + <b>События:</b> %1 + + + + + + + <b>Starting Address:</b> %1 + <b>Начальный адрес:</b> %1 + + + + + <b>Quantity of Outputs:</b> %1 + <b>Количество:</b> %1 + + + + + <b>Quantity of Registers:</b> %1 + <b>Количество регистров:</b> %1 + + + + + <b>Registers Value:</b> %1 + <b>Значения регистров:</b> %1 + + + + <b>Request Data Length:</b> %1 + <b>Длина данных запроса:</b> %1 + + + + <b>Response Data Length:</b> %1 + <b>Длина данных ответа:</b> %1 + + + + + <b>Address:</b> %1 + <b>Адрес:</b> %1 + + + + + <b>And Mask:</b> %1 + <b>Маска «И»:</b> %1 + + + + + <b>Or Mask:</b> %1 + <b>Маска «ИЛИ»:</b> %1 + + + + <b>Read Starting Address:</b> %1 + <b>Адрес чтения регистров:</b> %1 + + + + <b>Quantity to Read:</b> %1 + <b>Количество регистров для чтения:</b> %1 + + + + <b>Write Starting Address:</b> %1 + <b>Адрес записи регистров:</b> %1 + + + + <b>Quantity to Write:</b> %1 + <b>Количество регистров для записи:</b> %1 + + + + <b>Write Byte Count:</b> %1 + <b>Количество байт записи:</b> %1 + + + + <b>Write Registers Value:</b> %1 + <b>Значения регистров записи:</b> %1 + + + + <b>FIFO Point Address:</b> %1 + <b>Адрес точки FIFO:</b> %1 + + + + <b>FIFO Count:</b> %1 + <b>Количество FIFO:</b> %1 + + + + <b>FIFO Value Register:</b> %1 + <b>Значения регистров FIFO:</b> %1 + + OutputWidget - + %1: Enter Description %1: Введите описание - + Data Uninitialized Данные не инициализированы @@ -1788,19 +2199,19 @@ Valid Slave Responses: %2 PdfExporter - + Error. Failed to write PDF file! Ошибка. Не удалось записать PDF файл! - + Device Id: %1 Length: %2 Point Type: [%3] Узел: %1 Количество: %2 Тип регистров: [%3] - + Start Address: %1 Registers on Query: %2 Начальный адрес: %1 @@ -1810,23 +2221,23 @@ Registers on Query: %2 SimulationModeComboBox - - + + Random Случайное - + Toggle Переключение - + Increment Увеличение - + Decrement Уменьшение