diff --git a/README.md b/README.md index 78daf6c..bbe9813 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Open ModScan Open ModScan is a free implimentation of modbus master (client) utility for modbus-tcp and modbus-rtu protocols. -![image](https://github.com/sanny32/OpenModScan/assets/13627951/c2df0ea1-0f27-4d4b-8cc0-b6268caf8f11) +![image](https://github.com/user-attachments/assets/71563b34-3d85-404b-9d36-03ad5da76684) -![image](https://github.com/user-attachments/assets/aeae5869-b0a1-469f-9e30-3e68a85b23e1) +![image](https://github.com/user-attachments/assets/1a7f45f9-66a8-4591-975f-4ea3b623ac31) @@ -27,20 +27,19 @@ Registers 0x10 - Write Multiple Registers 0x16 - Mask Write Register -Modbus Logging - -![image](https://github.com/sanny32/OpenModScan/assets/13627951/69de27f0-b09b-4587-8493-6d1908610735) +## Modbus Logging +![image](https://github.com/user-attachments/assets/1cc38a65-7631-4122-b975-6a01781131e4) ## Extended Featues - Modbus Address Scan - ![image](https://github.com/sanny32/OpenModScan/assets/13627951/8989fbde-09f1-435c-a9a7-31e27a0ec576) + ![image](https://github.com/user-attachments/assets/dfbe062f-d696-4e38-a75f-d5d53182df58) - Modbus Scanner (supports both Modbus RTU and Modbus TCP scanning) - ![image](https://github.com/sanny32/OpenModScan/assets/13627951/cd35c0c4-ca9c-41fe-872c-1c2bb2286fc7) + ![image](https://github.com/user-attachments/assets/17d5f43d-c341-455d-a9b8-67db50a35699) - Modbus Message Parser diff --git a/omodscan/controls/addressbasecombobox.cpp b/omodscan/controls/addressbasecombobox.cpp new file mode 100644 index 0000000..c844682 --- /dev/null +++ b/omodscan/controls/addressbasecombobox.cpp @@ -0,0 +1,76 @@ +#include +#include "addressbasecombobox.h" + +/// +/// \brief AddressBaseComboBox::AddressBaseComboBox +/// \param parent +/// +AddressBaseComboBox::AddressBaseComboBox(QWidget* parent) + : QComboBox(parent) +{ + addItem(tr("0-based"), QVariant::fromValue(AddressBase::Base0)); + addItem(tr("1-based"), QVariant::fromValue(AddressBase::Base1)); + + connect(this, static_cast(&QComboBox::currentIndexChanged), this, &AddressBaseComboBox::on_currentIndexChanged); +} + +/// +/// \brief AddressBaseComboBox::changeEvent +/// \param event +/// +void AddressBaseComboBox::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) + { + for(int i = 0; i < count(); i++) + { + switch(itemData(i).value()) + { + case AddressBase::Base0: + setItemText(i, tr("0-based")); + break; + + case AddressBase::Base1: + setItemText(i, tr("1-based")); + break; + } + } + } + + QComboBox::changeEvent(event); +} + +/// +/// \brief AddressBaseComboBox::currentAddressBase +/// \return +/// +AddressBase AddressBaseComboBox::currentAddressBase() const +{ + return currentData().value(); +} + +/// +/// \brief AddressBaseComboBox::setCurrentAddressBase +/// \param pointType +/// +void AddressBaseComboBox::setCurrentAddressBase(AddressBase base) +{ + const auto idx = findData(QVariant::fromValue(base)); + if(idx == currentIndex()) + { + emit currentIndexChanged(idx); + } + else if(idx != -1) + { + setCurrentIndex(idx); + } +} + +/// +/// \brief AddressBaseComboBox::on_currentIndexChanged +/// \param index +/// +void AddressBaseComboBox::on_currentIndexChanged(int index) +{ + emit addressBaseChanged(itemData(index).value()); +} diff --git a/omodscan/controls/addressbasecombobox.h b/omodscan/controls/addressbasecombobox.h new file mode 100644 index 0000000..9d30f15 --- /dev/null +++ b/omodscan/controls/addressbasecombobox.h @@ -0,0 +1,29 @@ +#ifndef ADDRESSBASECOMBOBOX_H +#define ADDRESSBASECOMBOBOX_H + +#include +#include "enums.h" + +/// +/// \brief The AddressBaseComboBox class +/// +class AddressBaseComboBox : public QComboBox +{ + Q_OBJECT +public: + explicit AddressBaseComboBox(QWidget *parent = nullptr); + + AddressBase currentAddressBase() const; + void setCurrentAddressBase(AddressBase base); + +signals: + void addressBaseChanged(AddressBase base); + +protected: + void changeEvent(QEvent* event) override; + +private slots: + void on_currentIndexChanged(int); +}; + +#endif // ADDRESSBASECOMBOBOX_H diff --git a/omodscan/controls/outputwidget.cpp b/omodscan/controls/outputwidget.cpp index 903b6f6..b5288c2 100644 --- a/omodscan/controls/outputwidget.cpp +++ b/omodscan/controls/outputwidget.cpp @@ -297,7 +297,8 @@ QModelIndex OutputListModel::find(QModbusDataUnit::RegisterType type, quint16 ad if(_parentWidget->_displayDefinition.PointType != type) return QModelIndex(); - const int row = addr - _parentWidget->_displayDefinition.PointAddress; + const auto dd = _parentWidget->_displayDefinition; + const int row = addr - (dd.PointAddress - (dd.ZeroBasedAddress ? 0 : 1)); if(row >= 0 && row < rowCount()) return index(row); @@ -653,8 +654,8 @@ AddressDescriptionMap OutputWidget::descriptionMap() const for(int i = 0; i < _listModel->rowCount(); i++) { const auto desc = _listModel->data(_listModel->index(i), DescriptionRole).toString(); - const quint16 addr = _listModel->data(_listModel->index(i), AddressRole).toUInt(); - descriptionMap[{_displayDefinition.PointType, addr}] = desc; + const quint16 addr = _listModel->data(_listModel->index(i), AddressRole).toUInt() - (_displayDefinition.ZeroBasedAddress ? 0 : 1); + descriptionMap[{_displayDefinition.PointType, addr }] = desc; } return descriptionMap; } diff --git a/omodscan/controls/outputwidget.h b/omodscan/controls/outputwidget.h index 6f94f3a..e6b95b1 100644 --- a/omodscan/controls/outputwidget.h +++ b/omodscan/controls/outputwidget.h @@ -111,8 +111,6 @@ class OutputWidget : public QWidget int logViewLimit() const; void setLogViewLimit(int l); - void clearLogView(); - void setStatus(const QString& status); void paint(const QRect& rc, QPainter& painter); @@ -126,6 +124,9 @@ class OutputWidget : public QWidget void setSimulated(QModbusDataUnit::RegisterType type, quint16 addr, bool on); +public slots: + void clearLogView(); + signals: void itemDoubleClicked(quint16 address, const QVariant& value); diff --git a/omodscan/controls/statisticwidget.cpp b/omodscan/controls/statisticwidget.cpp index b630b05..70bb0b3 100644 --- a/omodscan/controls/statisticwidget.cpp +++ b/omodscan/controls/statisticwidget.cpp @@ -78,6 +78,7 @@ void StatisticWidget::resetCtrs() void StatisticWidget::on_pushButtonResetCtrs_clicked() { resetCtrs(); + emit ctrsReseted(); } /// diff --git a/omodscan/controls/statisticwidget.h b/omodscan/controls/statisticwidget.h index 4fc5192..1dc320e 100644 --- a/omodscan/controls/statisticwidget.h +++ b/omodscan/controls/statisticwidget.h @@ -28,6 +28,7 @@ class StatisticWidget : public QWidget signals: void numberOfPollsChanged(uint value); void validSlaveResposesChanged(uint value); + void ctrsReseted(); protected: void changeEvent(QEvent* event) override; diff --git a/omodscan/controls/statisticwidget.ui b/omodscan/controls/statisticwidget.ui index abd8f65..8b4f7a0 100644 --- a/omodscan/controls/statisticwidget.ui +++ b/omodscan/controls/statisticwidget.ui @@ -87,14 +87,14 @@ - + Qt::Vertical 20 - 6 + 40 diff --git a/omodscan/datasimulator.cpp b/omodscan/datasimulator.cpp index 38098d9..4118a79 100644 --- a/omodscan/datasimulator.cpp +++ b/omodscan/datasimulator.cpp @@ -46,7 +46,7 @@ void DataSimulator::startSimulation(DataDisplayMode mode, QModbusDataUnit::Regis break; } - _simulationMap[{ type, addr, deviceId}] = { mode, params, value }; + _simulationMap.insert({ type, addr, deviceId}, { mode, params, value }); resumeSimulations(); emit simulationStarted(type, addr, deviceId); diff --git a/omodscan/datasimulator.h b/omodscan/datasimulator.h index cb640a8..fb91b5a 100644 --- a/omodscan/datasimulator.h +++ b/omodscan/datasimulator.h @@ -58,7 +58,8 @@ private slots: quint16 Address; quint8 DeviceId; bool operator<(const SimulationKey& key) const{ - return Type < key.Type && Address < key.Address && DeviceId < key.DeviceId; + return Type < key.Type || (!(Type < key.Type) && (Address < key.Address)) || + (!(Type < key.Type) && !(Address < key.Address) && (DeviceId < key.DeviceId)); } }; diff --git a/omodscan/dialogs/dialogaddressscan.cpp b/omodscan/dialogs/dialogaddressscan.cpp index 101fc15..33803b5 100644 --- a/omodscan/dialogs/dialogaddressscan.cpp +++ b/omodscan/dialogs/dialogaddressscan.cpp @@ -50,7 +50,7 @@ QVariant TableViewItemModel::data(const QModelIndex &index, int role) const switch(role) { case Qt::ToolTipRole: - return formatAddress(_data.registerType(), _data.startAddress() + idx + 1, false); + return formatAddress(_data.registerType(), getAddress(idx), false); case Qt::DisplayRole: { @@ -70,7 +70,7 @@ QVariant TableViewItemModel::data(const QModelIndex &index, int role) const return _data.hasValue(idx) ? QVariant() : parentWidget->palette().color(QPalette::Disabled, QPalette::Base); case Qt::UserRole: - return _data.startAddress() + idx + 1; + return getAddress(idx); } return QVariant(); @@ -134,7 +134,7 @@ QVariant TableViewItemModel::headerData(int section, Qt::Orientation orientation { const auto length = _data.valueCount(); const auto pointType = _data.registerType(); - const auto pointAddress = _data.startAddress() + 1; + const auto pointAddress = getAddress(0); 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, false), formatAddress(pointType, addressTo, false)); @@ -161,6 +161,34 @@ Qt::ItemFlags TableViewItemModel::flags(const QModelIndex &index) const return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } +/// +/// \brief TableViewItemModel::addressBse +/// \return +/// +AddressBase TableViewItemModel::addressBse() const +{ + return _addressBase; +} + +/// +/// \brief TableViewItemModel::setAddressBase +/// \param base +/// +void TableViewItemModel::setAddressBase(AddressBase base) +{ + _addressBase = base; +} + +/// +/// \brief TableViewItemModel::getAddress +/// \param idx +/// \return +/// +int TableViewItemModel::getAddress(int idx) const +{ + return _data.startAddress() + idx + (_addressBase == AddressBase::Base0 ? 0 : 1); +} + /// /// \brief LogViewModel::LogViewModel /// \param parent @@ -209,7 +237,8 @@ QVariant LogViewModel::data(const QModelIndex& index, int role) const case Qt::DisplayRole: { const DataDisplayMode mode = _hexView ? DataDisplayMode::Hex : DataDisplayMode::UInt16; - return QString("[%1] %2 [%3]").arg(formatAddress(item.Type, item.Addr, false), + const auto addr = item.Addr + (_addressBase == AddressBase::Base1 ? 1 : 0); + return QString("[%1] %2 [%3]").arg(formatAddress(item.Type, addr, false), item.Msg->isRequest() ? "<<" : ">>", item.Msg->toString(mode)); } @@ -224,6 +253,24 @@ QVariant LogViewModel::data(const QModelIndex& index, int role) const return QVariant(); } +/// +/// \brief LogViewModel::addressBse +/// \return +/// +AddressBase LogViewModel::addressBse() const +{ + return _addressBase; +} + +/// +/// \brief LogViewModel::setAddressBase +/// \param base +/// +void LogViewModel::setAddressBase(AddressBase base) +{ + _addressBase = base; +} + /// /// \brief LogViewModel::deleteItems /// @@ -284,16 +331,18 @@ DialogAddressScan::DialogAddressScan(const DisplayDefinition& dd, DataDisplayMod ui->logView->setModel(proxyLogModel); ui->comboBoxPointType->setCurrentPointType(dd.PointType); + ui->comboBoxAddressBase->setCurrentAddressBase(dd.ZeroBasedAddress ? AddressBase::Base0 : AddressBase::Base1); ui->lineEditStartAddress->setPaddingZeroes(true); - ui->lineEditStartAddress->setInputRange(ModbusLimits::addressRange()); + ui->lineEditStartAddress->setInputRange(ModbusLimits::addressRange(dd.ZeroBasedAddress)); ui->lineEditSlaveAddress->setInputRange(ModbusLimits::slaveRange()); - ui->lineEditLength->setInputRange(1, 65530); + ui->lineEditLength->setInputRange(2, 65530); ui->lineEditStartAddress->setValue(dd.PointAddress); ui->lineEditSlaveAddress->setValue(dd.DeviceId); ui->lineEditLength->setValue(999); ui->tabWidget->setCurrentIndex(0); ui->checkBoxHexView->setChecked(mode == DataDisplayMode::Hex); ui->comboBoxByteOrder->setCurrentByteOrder(order); + ui->info->setShowTimestamp(false); auto dispatcher = QAbstractEventDispatcher::instance(); connect(dispatcher, &QAbstractEventDispatcher::awake, this, &DialogAddressScan::on_awake); @@ -358,6 +407,7 @@ void DialogAddressScan::on_checkBoxHexView_toggled(bool on) { ((TableViewItemModel*)ui->tableView->model())->setHexView(on); ((LogViewProxyModel*)ui->logView->model())->setHexView(on); + ui->info->setDataDisplayMode(on ? DataDisplayMode::Hex : DataDisplayMode::UInt16); } /// @@ -369,15 +419,87 @@ void DialogAddressScan::on_checkBoxShowValid_toggled(bool on) ((LogViewProxyModel*)ui->logView->model())->setShowValid(on); } +/// +/// \brief DialogAddressScan::on_lineEditStartAddress_valueChanged +/// \param value +/// +void DialogAddressScan::on_lineEditStartAddress_valueChanged(const QVariant& value) +{ + Q_UNUSED(value) + + clearTableView(); + clearLogView(); +} + +/// +/// \brief DialogAddressScan::on_lineEditLength_valueChanged +/// \param value +/// +void DialogAddressScan::on_lineEditLength_valueChanged(const QVariant& value) +{ + Q_UNUSED(value) + + clearTableView(); + clearLogView(); +} + +/// +/// \brief DialogAddressScan::on_comboBoxPointType_pointTypeChanged +/// \param pointType +/// +void DialogAddressScan::on_comboBoxPointType_pointTypeChanged(QModbusDataUnit::RegisterType pointType) +{ + Q_UNUSED(pointType) + + clearTableView(); + clearLogView(); +} + +/// +/// \brief DialogAddressScan::on_comboBoxAddressBase_addressBaseChanged +/// \param base +/// +void DialogAddressScan::on_comboBoxAddressBase_addressBaseChanged(AddressBase base) +{ + const auto addr = ui->lineEditStartAddress->value(); + + ui->lineEditStartAddress->setInputRange(ModbusLimits::addressRange(base == AddressBase::Base0)); + ui->lineEditStartAddress->setValue(base == AddressBase::Base1 ? qMax(1, addr + 1) : qMax(0, addr - 1)); + + ((TableViewItemModel*)ui->tableView->model())->setAddressBase(base); + ((LogViewProxyModel*)ui->logView->model())->setAddressBase(base); + + clearTableView(); + clearLogView(); +} + /// /// \brief DialogAddressScan::on_comboBoxByteOrder_byteOrderChanged /// \param order /// void DialogAddressScan::on_comboBoxByteOrder_byteOrderChanged(ByteOrder order) { + ui->info->setByteOrder(order); ((TableViewItemModel*)ui->tableView->model())->setByteOrder(order); } +/// +/// \brief DialogAddressScan::on_logView_clicked +/// \param index +/// +void DialogAddressScan::on_logView_clicked(const QModelIndex &index) +{ + if(!index.isValid()) + { + ui->info->clear(); + return; + } + + auto proxyLogModel = ((LogViewProxyModel*)ui->logView->model()); + auto msg = proxyLogModel->data(index, Qt::UserRole).value(); + ui->info->setModbusMessage(msg); +} + /// /// \brief DialogAddressScan::on_modbusRequest /// \param requestId @@ -408,7 +530,7 @@ void DialogAddressScan::on_modbusReply(QModbusReply* reply) updateLogView(reply); if (reply->error() == QModbusDevice::NoError) - updateTableView(reply->result().startAddress() + 1, reply->result().values()); + updateTableView(reply->result().startAddress(), reply->result().values()); if(_requestCount > ui->lineEditLength->value() + ui->spinBoxRegsOnQuery->value()) @@ -500,10 +622,13 @@ void DialogAddressScan::sendReadRequest() const auto pointType = ui->comboBoxPointType->currentPointType(); const auto pointAddress = ui->lineEditStartAddress->value(); const auto count = ui->spinBoxRegsOnQuery->value(); - const auto address = pointAddress - 1 + _requestCount; + const auto addressBase = ui->comboBoxAddressBase->currentAddressBase(); + const auto address = (addressBase == AddressBase::Base0 ? pointAddress : pointAddress - 1) + _requestCount; if(address > ModbusLimits::addressRange().to()) + { stopScan(); + } else { _requestCount += count; @@ -519,8 +644,9 @@ void DialogAddressScan::clearTableView() const auto length = ui->lineEditLength->value(); const auto pointType = ui->comboBoxPointType->currentPointType(); const auto pointAddress = ui->lineEditStartAddress->value(); + const auto addressBase = ui->comboBoxAddressBase->currentAddressBase(); - ModbusDataUnit data(pointType, pointAddress - 1, length); + ModbusDataUnit data(pointType, addressBase == AddressBase::Base0 ? pointAddress : pointAddress - 1, length); ((TableViewItemModel*)ui->tableView->model())->reset(data); ui->tableView->resizeColumnsToContents(); @@ -534,8 +660,8 @@ void DialogAddressScan::clearTableView() /// void DialogAddressScan::clearLogView() { - auto proxyLogModel = ((LogViewProxyModel*)ui->logView->model()); - proxyLogModel->clear(); + ui->info->clear(); + ((LogViewProxyModel*)ui->logView->model())->clear(); } /// @@ -574,6 +700,10 @@ void DialogAddressScan::updateProgress() void DialogAddressScan::updateTableView(int pointAddress, QVector values) { auto model = ui->tableView->model(); + + const auto addressBase = ui->comboBoxAddressBase->currentAddressBase(); + pointAddress += (addressBase == AddressBase::Base0 ? 0 : 1); + for(int i = 0; i < model->rowCount(); i++) { for(int j = 0; j < model->columnCount(); j++) @@ -607,7 +737,10 @@ void DialogAddressScan::updateLogView(int deviceId, int transactionId, const QMo if(protocol == ModbusMessage::Tcp) ((QModbusAduTcp*)msg->adu())->setTransactionId(transactionId); - proxyLogModel->append(pointAddress + 1, ui->comboBoxPointType->currentPointType(), msg); + const auto addressBase = ui->comboBoxAddressBase->currentAddressBase(); + pointAddress += (addressBase == AddressBase::Base0 ? 0 : 1); + + proxyLogModel->append(pointAddress, ui->comboBoxPointType->currentPointType(), msg); } /// @@ -620,7 +753,8 @@ void DialogAddressScan::updateLogView(const QModbusReply* reply) return; const auto deviceId = reply->serverAddress(); - const auto pointAddress = reply->property("RequestData").value().startAddress() + 1; + const auto addressBase = ui->comboBoxAddressBase->currentAddressBase(); + const auto pointAddress = reply->property("RequestData").value().startAddress() + (addressBase == AddressBase::Base0 ? 0 : 1); const auto transactionId = reply->property("TransactionId").toInt(); const auto pdu = reply->rawResult(); @@ -642,11 +776,13 @@ void DialogAddressScan::updateLogView(const QModbusReply* reply) void DialogAddressScan::exportPdf(const QString& filename) { PdfExporter exporter(ui->tableView->model(), + ui->comboBoxAddressBase->currentText(), ui->lineEditStartAddress->text(), ui->lineEditLength->text(), ui->lineEditSlaveAddress->text(), ui->comboBoxPointType->currentText(), ui->spinBoxRegsOnQuery->text(), + ui->comboBoxByteOrder->currentText(), this); exporter.exportPdf(filename); @@ -659,11 +795,13 @@ void DialogAddressScan::exportPdf(const QString& filename) void DialogAddressScan::exportCsv(const QString& filename) { CsvExporter exporter(ui->tableView->model(), + ui->comboBoxAddressBase->currentText(), ui->lineEditStartAddress->text(), ui->lineEditLength->text(), ui->lineEditSlaveAddress->text(), ui->comboBoxPointType->currentText(), ui->spinBoxRegsOnQuery->text(), + ui->comboBoxByteOrder->currentText(), this); exporter.exportCsv(filename); @@ -672,26 +810,32 @@ void DialogAddressScan::exportCsv(const QString& filename) /// /// \brief PdfExporter::PdfExporter /// \param model +/// \param addressBase /// \param startAddress /// \param length /// \param devId /// \param pointType +/// \param regsOnQuery /// \param parent /// PdfExporter::PdfExporter(QAbstractItemModel* model, + const QString& addressBase, const QString& startAddress, const QString& length, const QString& devId, const QString& pointType, const QString& regsOnQuery, + const QString& byteOrder, QObject* parent) : QObject(parent) ,_model(model) + ,_addressBase(addressBase) ,_startAddress(startAddress) ,_length(length) ,_deviceId(devId) ,_pointType(pointType) ,_regsOnQuery(regsOnQuery) + ,_byteOrder(byteOrder) { _printer = QSharedPointer(new QPrinter(QPrinter::PrinterResolution)); _printer->setOutputFormat(QPrinter::PdfFormat); @@ -774,18 +918,23 @@ void PdfExporter::paintPageHeader(int& yPos, QPainter& painter) const auto textTime = QLocale().toString(QDateTime::currentDateTime(), QLocale::ShortFormat); auto rcTime = painter.boundingRect(_pageRect, Qt::TextSingleLine, textTime); - const auto text1 = QString(tr("Device Id: %1\tLength: %2\nPoint Type: [%3]")).arg(_deviceId, _length, _pointType); + const auto text1 = QString(tr("Address Base: %1\nStart Address: %2")).arg(_addressBase, _startAddress); auto rc1 = painter.boundingRect(_pageRect, Qt::TextWordWrap, text1); - const auto text2 = QString(tr("Start Address: %1\nRegisters on Query: %2")).arg(_startAddress, _regsOnQuery); + const auto text2 = QString(tr("Device Id: %1\t\tLength: %2\nPoint Type: [%3]")).arg(_deviceId, _length, _pointType); auto rc2 = painter.boundingRect(_pageRect, Qt::TextWordWrap, text2); + const auto text3 = QString(tr("Registers on Query: %1\nByte Order: %2")).arg(_regsOnQuery, _byteOrder); + auto rc3 = painter.boundingRect(_pageRect, Qt::TextWordWrap, text3); + rcTime.moveTopRight({ _pageRect.right(), 10 }); - rc1.moveLeft(rc2.right() + 40); + rc2.moveLeft(rc1.right() + 40); + rc3.moveLeft(rc2.right() + 40); painter.drawText(rcTime, Qt::TextSingleLine, textTime); - painter.drawText(rc2, Qt::TextWordWrap, text2); painter.drawText(rc1, Qt::TextWordWrap, text1); + painter.drawText(rc2, Qt::TextWordWrap, text2); + painter.drawText(rc3, Qt::TextWordWrap, text3); yPos += qMax(rc1.height(), rc2.height()) + 20; } @@ -797,10 +946,10 @@ void PdfExporter::paintPageHeader(int& yPos, QPainter& painter) void PdfExporter::paintPageFooter(QPainter& painter) { const auto textNumber = QString::number(_pageNumber); - auto rc = painter.boundingRect(_pageRect, Qt::TextSingleLine, textNumber); + auto rcNumber = painter.boundingRect(_pageRect, Qt::TextSingleLine, textNumber); - rc.moveTopRight({ _pageRect.right(), _pageRect.bottom() + 10 }); - painter.drawText(rc, Qt::TextSingleLine, textNumber); + rcNumber.moveTopRight({ _pageRect.right(), _pageRect.bottom() + 10 }); + painter.drawText(rcNumber, Qt::TextSingleLine, textNumber); } /// @@ -915,19 +1064,23 @@ void PdfExporter::paintVLine(int top, int bottom, QPainter& painter) /// \param parent /// CsvExporter::CsvExporter(QAbstractItemModel* model, + const QString& addressBase, const QString& startAddress, const QString& length, const QString& devId, const QString& pointType, const QString& regsOnQuery, + const QString& byteOrder, QObject* parent) : QObject(parent) ,_model(model) + ,_addressBase(addressBase) ,_startAddress(startAddress) ,_length(length) ,_deviceId(devId) ,_pointType(pointType) ,_regsOnQuery(regsOnQuery) + ,_byteOrder(byteOrder) { } @@ -945,10 +1098,10 @@ void CsvExporter::exportCsv(const QString& filename) ts.setGenerateByteOrderMark(true); const char* delim = ";"; - const auto header = QString("%2%1%3%1%4%1%5%1%6").arg(delim, tr("Device Id"), tr("Start Address"), tr("Length"), tr("Point Type"), tr("Registers on Query")); + const auto header = QString("%2%1%3%1%4%1%5%1%6%1%7%1%8").arg(delim, tr("Address Base"), tr("Start Address"), tr("Device Id"), tr("Length"), tr("Point Type"), tr("Registers on Query"), tr("Byte Order")); ts << header << "\n"; - const auto headerData = QString("%2%1%3%1%4%1%5%1%6").arg(delim, _deviceId, _startAddress, _length, _pointType, _regsOnQuery); + const auto headerData = QString("%2%1%3%1%4%1%5%1%6%1%7%1%8").arg(delim, _addressBase, _startAddress, _deviceId, _length, _pointType, _regsOnQuery, _byteOrder); ts << headerData << "\n"; ts << "\n"; diff --git a/omodscan/dialogs/dialogaddressscan.h b/omodscan/dialogs/dialogaddressscan.h index b1de47b..3ab2db2 100644 --- a/omodscan/dialogs/dialogaddressscan.h +++ b/omodscan/dialogs/dialogaddressscan.h @@ -32,6 +32,9 @@ class TableViewItemModel : public QAbstractTableModel QVariant headerData(int section, Qt::Orientation orientation, int role) const override; Qt::ItemFlags flags(const QModelIndex& index) const override; + AddressBase addressBse() const; + void setAddressBase(AddressBase base); + void reset(const ModbusDataUnit& data, int columns = 10){ beginResetModel(); _columns = columns; @@ -51,10 +54,14 @@ class TableViewItemModel : public QAbstractTableModel endResetModel(); } +private: + int getAddress(int idx) const; + private: int _columns = 10; ModbusDataUnit _data; bool _hexView = false; + AddressBase _addressBase = AddressBase::Base1; ByteOrder _byteOrder = ByteOrder::LittleEndian; }; @@ -72,6 +79,9 @@ class LogViewModel : public QAbstractListModel int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role) const override; + AddressBase addressBse() const; + void setAddressBase(AddressBase base); + void append(quint16 addr, QModbusDataUnit::RegisterType type, const ModbusMessage* msg) { if(msg == nullptr) return; beginInsertRows(QModelIndex(), rowCount(), rowCount()); @@ -103,6 +113,7 @@ class LogViewModel : public QAbstractListModel private: bool _hexView = false; + AddressBase _addressBase = AddressBase::Base1; QVector _items; }; @@ -126,6 +137,11 @@ class LogViewProxyModel : public QSortFilterProxyModel ((LogViewModel*)sourceModel())->clear(); } + void setAddressBase(AddressBase base) { + if(sourceModel()) + ((LogViewModel*)sourceModel())->setAddressBase(base); + } + void setHexView(bool on) { if(sourceModel()) ((LogViewModel*)sourceModel())->setHexView(on); @@ -153,11 +169,13 @@ class PdfExporter : public QObject public: explicit PdfExporter(QAbstractItemModel* model, + const QString& addressBase, const QString& startAddress, const QString& length, const QString& devId, const QString& pointType, const QString& regsOnQuery, + const QString& byteOrder, QObject* parent = nullptr); void exportPdf(const QString& filename); @@ -179,11 +197,13 @@ class PdfExporter : public QObject const int _cx = 10; QRect _pageRect; QAbstractItemModel* _model; + const QString _addressBase; const QString _startAddress; const QString _length; const QString _deviceId; const QString _pointType; const QString _regsOnQuery; + const QString _byteOrder; QSharedPointer _printer; }; @@ -196,21 +216,25 @@ class CsvExporter: public QObject public: explicit CsvExporter(QAbstractItemModel* model, + const QString& addressBase, const QString& startAddress, const QString& length, const QString& devId, const QString& pointType, const QString& regsOnQuery, + const QString& byteOrder, QObject* parent = nullptr); void exportCsv(const QString& filename); private: QAbstractItemModel* _model; + const QString _addressBase; const QString _startAddress; const QString _length; const QString _deviceId; const QString _pointType; const QString _regsOnQuery; + const QString _byteOrder; }; /// @@ -234,7 +258,12 @@ private slots: void on_modbusRequest(int requestId, int deviceId, int transactionId, const QModbusRequest& data); void on_checkBoxHexView_toggled(bool); void on_checkBoxShowValid_toggled(bool); + void on_lineEditStartAddress_valueChanged(const QVariant& value); + void on_lineEditLength_valueChanged(const QVariant& value); + void on_comboBoxPointType_pointTypeChanged(QModbusDataUnit::RegisterType pointType); + void on_comboBoxAddressBase_addressBaseChanged(AddressBase base); void on_comboBoxByteOrder_byteOrderChanged(ByteOrder); + void on_logView_clicked(const QModelIndex &index); void on_pushButtonScan_clicked(); void on_pushButtonExport_clicked(); diff --git a/omodscan/dialogs/dialogaddressscan.ui b/omodscan/dialogs/dialogaddressscan.ui index 9744ecc..7e8207c 100644 --- a/omodscan/dialogs/dialogaddressscan.ui +++ b/omodscan/dialogs/dialogaddressscan.ui @@ -28,14 +28,14 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + Start Address: - + @@ -51,64 +51,49 @@ - 60 + 90 16777215 - - - - - 0 - 0 - - + + - Registers on Query: + Address Base: - - + + 0 0 + + + 0 + 25 + + - 60 + 90 16777215 - - 1 - - - 125 - - + - Qt::Horizontal + Qt::Vertical - - QSizePolicy::Fixed - - - - 20 - 20 - - - + @@ -208,14 +193,63 @@ + + + + Qt::Vertical + + + + + + + + + + 0 + 0 + + + + Registers on Query: + + + + + + + + 0 + 0 + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + 1 + + + 125 + + + + + Qt::Horizontal - - QSizePolicy::Expanding - 40 @@ -356,7 +390,14 @@ - + + + + 0 + 25 + + + @@ -367,39 +408,52 @@ Log View - - 0 - - - 0 - - - 0 - - - 0 - - - - QAbstractItemView::NoEditTriggers - - - false - - - QAbstractItemView::NoSelection - - - 2 + + + + 0 + 0 + - - true + + Qt::Horizontal + + + + 1 + 0 + + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::NoSelection + + + 2 + + + true + + + + + + + Show Only Valid Responses + + + @@ -413,13 +467,6 @@ - - - - Show Only Valid Responses - - - @@ -482,18 +529,27 @@ QComboBox
pointtypecombobox.h
+ + AddressBaseComboBox + QComboBox +
addressbasecombobox.h
+
ByteOrderComboBox QComboBox
byteordercombobox.h
+ + ModbusMessageWidget + QListWidget +
modbusmessagewidget.h
+
pushButtonScan tabWidget tableView pushButtonExport - checkBoxShowValid logView diff --git a/omodscan/dialogs/dialogdisplaydefinition.cpp b/omodscan/dialogs/dialogdisplaydefinition.cpp index 08818c5..d40bf3a 100644 --- a/omodscan/dialogs/dialogdisplaydefinition.cpp +++ b/omodscan/dialogs/dialogdisplaydefinition.cpp @@ -15,17 +15,18 @@ DialogDisplayDefinition::DialogDisplayDefinition(DisplayDefinition dd, QWidget* { ui->setupUi(this); ui->lineEditScanRate->setInputRange(20, 36000000); - ui->lineEditPointAddress->setInputRange(ModbusLimits::addressRange()); + ui->lineEditPointAddress->setInputRange(ModbusLimits::addressRange(dd.ZeroBasedAddress)); ui->lineEditLength->setInputRange(ModbusLimits::lengthRange()); ui->lineEditSlaveAddress->setInputRange(ModbusLimits::slaveRange()); ui->lineEditLogLimit->setInputRange(4, 1000); + ui->comboBoxAddressBase->setCurrentAddressBase(dd.ZeroBasedAddress ? AddressBase::Base0 : AddressBase::Base1); + ui->comboBoxPointType->setCurrentPointType(dd.PointType); 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(); } @@ -49,6 +50,19 @@ void DialogDisplayDefinition::accept() _displayDefinition.Length = ui->lineEditLength->value(); _displayDefinition.ScanRate = ui->lineEditScanRate->value(); _displayDefinition.LogViewLimit = ui->lineEditLogLimit->value(); + _displayDefinition.ZeroBasedAddress = (ui->comboBoxAddressBase->currentAddressBase() == AddressBase::Base0); QFixedSizeDialog::accept(); } + +/// +/// \brief DialogDisplayDefinition::on_comboBoxAddressBase_currentIndexChanged +/// \param index +/// +void DialogDisplayDefinition::on_comboBoxAddressBase_addressBaseChanged(AddressBase base) +{ + const auto addr = ui->lineEditPointAddress->value(); + + ui->lineEditPointAddress->setInputRange(ModbusLimits::addressRange(base == AddressBase::Base0)); + ui->lineEditPointAddress->setValue(base == AddressBase::Base1 ? qMax(1, addr + 1) : qMax(0, addr - 1)); +} diff --git a/omodscan/dialogs/dialogdisplaydefinition.h b/omodscan/dialogs/dialogdisplaydefinition.h index 75cdc01..539782a 100644 --- a/omodscan/dialogs/dialogdisplaydefinition.h +++ b/omodscan/dialogs/dialogdisplaydefinition.h @@ -1,6 +1,7 @@ #ifndef DIALOGDISPLAYDEFINITION_H #define DIALOGDISPLAYDEFINITION_H +#include "enums.h" #include "qfixedsizedialog.h" #include "displaydefinition.h" @@ -25,6 +26,9 @@ class DialogDisplayDefinition : public QFixedSizeDialog void accept() override; +private slots: + void on_comboBoxAddressBase_addressBaseChanged(AddressBase base); + private: DisplayDefinition _displayDefinition; Ui::DialogDisplayDefinition *ui; diff --git a/omodscan/dialogs/dialogdisplaydefinition.ui b/omodscan/dialogs/dialogdisplaydefinition.ui index 9080979..cb67dcf 100644 --- a/omodscan/dialogs/dialogdisplaydefinition.ui +++ b/omodscan/dialogs/dialogdisplaydefinition.ui @@ -6,8 +6,8 @@ 0 0 - 350 - 304 + 384 + 328 @@ -17,10 +17,10 @@ - Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop + Qt::AlignHCenter|Qt::AlignTop 40 @@ -128,14 +128,14 @@ Modbus Data - - Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter - - - 12 + + Qt::AlignHCenter|Qt::AlignTop - 40 + 30 + + + 30 @@ -166,13 +166,6 @@ - - - - Point Type: - - - @@ -196,14 +189,59 @@ - + + + + + 0 + 0 + + + + + 0 + 25 + + + + + 60 + 16777215 + + + + + + + + Address Base: + + + + + + + + 0 + 0 + + + + + 100 + 25 + + + + + Length: - + @@ -225,54 +263,12 @@ - - - - 8 + + + + Point Type: - - - - - 0 - 0 - - - - - 0 - 25 - - - - - 60 - 16777215 - - - - - - - - (1-based) - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - + @@ -280,10 +276,10 @@ - Qt::Orientation::Horizontal + Qt::Horizontal - QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + QDialogButtonBox::Cancel|QDialogButtonBox::Ok true @@ -303,6 +299,11 @@ QComboBox
pointtypecombobox.h
+ + AddressBaseComboBox + QComboBox +
addressbasecombobox.h
+
lineEditScanRate diff --git a/omodscan/dialogs/dialogmaskwriteregiter.cpp b/omodscan/dialogs/dialogmaskwriteregiter.cpp index 333762f..bbec799 100644 --- a/omodscan/dialogs/dialogmaskwriteregiter.cpp +++ b/omodscan/dialogs/dialogmaskwriteregiter.cpp @@ -13,7 +13,7 @@ DialogMaskWriteRegiter::DialogMaskWriteRegiter(ModbusMaskWriteParams& params, QW { ui->setupUi(this); ui->lineEditNode->setInputRange(ModbusLimits::slaveRange()); - ui->lineEditAddress->setInputRange(ModbusLimits::addressRange()); + ui->lineEditAddress->setInputRange(ModbusLimits::addressRange(params.ZeroBasedAddress)); ui->lineEditNode->setValue(params.Node); ui->lineEditAddress->setValue(params.Address); diff --git a/omodscan/dialogs/dialogmodbusscanner.cpp b/omodscan/dialogs/dialogmodbusscanner.cpp index dc6b7ce..933e619 100644 --- a/omodscan/dialogs/dialogmodbusscanner.cpp +++ b/omodscan/dialogs/dialogmodbusscanner.cpp @@ -4,6 +4,7 @@ #include #include #include +#include "modbuslimits.h" #include "modbusrtuscanner.h" #include "modbustcpscanner.h" #include "dialogmodbusscanner.h" @@ -83,6 +84,7 @@ DialogModbusScanner::DialogModbusScanner(QWidget *parent) ui->comboBoxFunction->addItem(QModbusPdu::ReadInputRegisters); ui->comboBoxFunction->addItem(QModbusPdu::ReportServerId); ui->comboBoxFunction->setCurrentFunctionCode(QModbusPdu::ReadHoldingRegisters); + ui->comboBoxAddressBase->setCurrentAddressBase(AddressBase::Base1); auto dispatcher = QAbstractEventDispatcher::instance(); connect(dispatcher, &QAbstractEventDispatcher::awake, this, &DialogModbusScanner::on_awake); @@ -139,7 +141,7 @@ void DialogModbusScanner::on_awake() ui->groupBoxRequest->setEnabled(!inProgress); ui->pushButtonClear->setEnabled(!inProgress); ui->pushButtonScan->setEnabled((rtuScanning && ui->comboBoxSerial->count() > 0) || !rtuScanning); - ui->pushButtonScan->setText(inProgress ? tr("Stop Scan") : tr("Start Scan")); + ui->pushButtonScan->setText(inProgress ? tr("Stop") : tr("Start")); } /// @@ -191,6 +193,18 @@ void DialogModbusScanner::on_comboBoxFunction_functionCodeChanged(QModbusPdu::Fu ui->spinBoxAddress->setEnabled(funcCode != QModbusPdu::ReportServerId); ui->spinBoxLength->setEnabled(funcCode != QModbusPdu::ReportServerId); + ui->comboBoxAddressBase->setEnabled(funcCode != QModbusPdu::ReportServerId); +} + +/// +/// \brief DialogModbusScanner::on_comboBoxAddressBase_addressBaseChanged +/// \param base +/// +void DialogModbusScanner::on_comboBoxAddressBase_addressBaseChanged(AddressBase base) +{ + const auto addr = ui->spinBoxAddress->value(); + ui->spinBoxAddress->setMinimum(ModbusLimits::addressRange(base == AddressBase::Base0).from()); + ui->spinBoxAddress->setValue(base == AddressBase::Base1 ? qMax(1, addr + 1) : qMax(0, addr - 1)); } /// @@ -577,18 +591,21 @@ const QModbusRequest DialogModbusScanner::createModbusRequest() const switch(ui->comboBoxFunction->currentFunctionCode()) { case QModbusPdu::ReportServerId: - return QModbusRequest(QModbusPdu::ReportServerId); + return QModbusRequest(QModbusPdu::ReportServerId); case QModbusPdu::ReadCoils: case QModbusPdu::ReadDiscreteInputs: case QModbusPdu::ReadHoldingRegisters: case QModbusPdu::ReadInputRegisters: - return QModbusRequest(ui->comboBoxFunction->currentFunctionCode(), - quint16(ui->spinBoxAddress->value() - 1), + { + const auto base = ui->comboBoxAddressBase->currentAddressBase(); + return QModbusRequest(ui->comboBoxFunction->currentFunctionCode(), + quint16(ui->spinBoxAddress->value() - (base == AddressBase::Base0 ? 0 : 1)), quint16(ui->spinBoxLength->value())); + } default: - return QModbusRequest(); + return QModbusRequest(); } } diff --git a/omodscan/dialogs/dialogmodbusscanner.h b/omodscan/dialogs/dialogmodbusscanner.h index d60a458..4aa8b91 100644 --- a/omodscan/dialogs/dialogmodbusscanner.h +++ b/omodscan/dialogs/dialogmodbusscanner.h @@ -38,6 +38,7 @@ private slots: void on_progress(const ConnectionDetails& cd, int deviceId, int progress); void on_comboBoxFunction_functionCodeChanged(QModbusPdu::FunctionCode funcCode); + void on_comboBoxAddressBase_addressBaseChanged(AddressBase base); void on_listWidget_itemDoubleClicked(QListWidgetItem *item); void on_lineEditIPAddressFrom_editingFinished(); void on_lineEditSubnetMask_editingFinished(); diff --git a/omodscan/dialogs/dialogmodbusscanner.ui b/omodscan/dialogs/dialogmodbusscanner.ui index 32a54f4..d70eaa8 100644 --- a/omodscan/dialogs/dialogmodbusscanner.ui +++ b/omodscan/dialogs/dialogmodbusscanner.ui @@ -140,10 +140,10 @@
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 2 @@ -246,10 +246,10 @@ - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -259,7 +259,49 @@ - + + + + + + 0 + 0 + + + + + 62 + 0 + + + + 0 + + + 65536 + + + 0 + + + + + + + + 0 + 0 + + + + -1 + + + + + + + 0 @@ -276,7 +318,7 @@ 1 - 65536 + 125 @@ -287,28 +329,6 @@
- - - - - 0 - 0 - - - - - 62 - 0 - - - - 1 - - - 125 - - -
@@ -683,10 +703,10 @@
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 2 @@ -796,10 +816,10 @@ - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 2 @@ -890,7 +910,7 @@ font-weight: bold; - Start Scan + Scan @@ -902,6 +922,9 @@ font-weight: bold;
+ + 6 + @@ -1008,6 +1031,11 @@ font-weight: bold; + + AddressBaseComboBox + QComboBox +
addressbasecombobox.h
+
IpAddressLineEdit QLineEdit diff --git a/omodscan/dialogs/dialogsetuppresetdata.cpp b/omodscan/dialogs/dialogsetuppresetdata.cpp index fa04eb7..25b0f85 100644 --- a/omodscan/dialogs/dialogsetuppresetdata.cpp +++ b/omodscan/dialogs/dialogsetuppresetdata.cpp @@ -15,7 +15,7 @@ DialogSetupPresetData::DialogSetupPresetData(SetupPresetParams& params, QModbus { ui->setupUi(this); ui->lineEditSlaveDevice->setInputRange(ModbusLimits::slaveRange()); - ui->lineEditAddress->setInputRange(ModbusLimits::addressRange()); + ui->lineEditAddress->setInputRange(ModbusLimits::addressRange(params.ZeroBasedAddress)); ui->lineEditSlaveDevice->setValue(params.SlaveAddress); ui->lineEditAddress->setValue(params.PointAddress); ui->lineEditNumberOfPoints->setValue(params.Length); diff --git a/omodscan/dialogs/dialogsetuppresetdata.h b/omodscan/dialogs/dialogsetuppresetdata.h index 3d7c43e..51acbe6 100644 --- a/omodscan/dialogs/dialogsetuppresetdata.h +++ b/omodscan/dialogs/dialogsetuppresetdata.h @@ -9,6 +9,7 @@ struct SetupPresetParams quint16 SlaveAddress; quint16 PointAddress; quint16 Length; + bool ZeroBasedAddress; }; namespace Ui { diff --git a/omodscan/dialogs/dialogwritecoilregister.cpp b/omodscan/dialogs/dialogwritecoilregister.cpp index d394119..ed14fad 100644 --- a/omodscan/dialogs/dialogwritecoilregister.cpp +++ b/omodscan/dialogs/dialogwritecoilregister.cpp @@ -16,7 +16,7 @@ DialogWriteCoilRegister::DialogWriteCoilRegister(ModbusWriteParams& params, Modb { ui->setupUi(this); ui->lineEditNode->setInputRange(ModbusLimits::slaveRange()); - ui->lineEditAddress->setInputRange(ModbusLimits::addressRange()); + ui->lineEditAddress->setInputRange(ModbusLimits::addressRange(params.ZeroBasedAddress)); ui->lineEditNode->setValue(params.Node); ui->lineEditAddress->setValue(params.Address); ui->radioButtonOn->setChecked(params.Value.toBool()); diff --git a/omodscan/dialogs/dialogwriteholdingregister.cpp b/omodscan/dialogs/dialogwriteholdingregister.cpp index 677f44e..f247052 100644 --- a/omodscan/dialogs/dialogwriteholdingregister.cpp +++ b/omodscan/dialogs/dialogwriteholdingregister.cpp @@ -19,7 +19,7 @@ DialogWriteHoldingRegister::DialogWriteHoldingRegister(ModbusWriteParams& params { ui->setupUi(this); ui->lineEditNode->setInputRange(ModbusLimits::slaveRange()); - ui->lineEditAddress->setInputRange(ModbusLimits::addressRange()); + ui->lineEditAddress->setInputRange(ModbusLimits::addressRange(params.ZeroBasedAddress)); ui->lineEditNode->setValue(params.Node); ui->lineEditAddress->setValue(params.Address); diff --git a/omodscan/dialogs/dialogwriteholdingregisterbits.cpp b/omodscan/dialogs/dialogwriteholdingregisterbits.cpp index 8e598a5..6c2538c 100644 --- a/omodscan/dialogs/dialogwriteholdingregisterbits.cpp +++ b/omodscan/dialogs/dialogwriteholdingregisterbits.cpp @@ -15,11 +15,11 @@ DialogWriteHoldingRegisterBits::DialogWriteHoldingRegisterBits(ModbusWriteParams { ui->setupUi(this); ui->lineEditNode->setInputRange(ModbusLimits::slaveRange()); - ui->lineEditAddress->setInputRange(ModbusLimits::addressRange()); + ui->lineEditAddress->setInputRange(ModbusLimits::addressRange(params.ZeroBasedAddress)); ui->lineEditNode->setValue(params.Node); ui->lineEditAddress->setValue(params.Address); - quint16 value = params.Value.toUInt(); + const quint16 value = params.Value.toUInt(); for (int i = 0; i < 16; i++) { auto ctrl = findChild(QString("checkBox%1").arg(i)); diff --git a/omodscan/displaydefinition.h b/omodscan/displaydefinition.h index ca0208c..d21567b 100644 --- a/omodscan/displaydefinition.h +++ b/omodscan/displaydefinition.h @@ -16,12 +16,13 @@ struct DisplayDefinition QModbusDataUnit::RegisterType PointType = QModbusDataUnit::Coils; quint16 Length = 50; quint16 LogViewLimit = 30; + bool ZeroBasedAddress = false; void normalize() { ScanRate = qBound(20U, ScanRate, 3600000U); DeviceId = qMax(ModbusLimits::slaveRange().from(), DeviceId); - PointAddress = qMax(ModbusLimits::addressRange().from(), PointAddress); + PointAddress = qMax(ModbusLimits::addressRange(ZeroBasedAddress).from(), PointAddress); PointType = qBound(QModbusDataUnit::DiscreteInputs, PointType, QModbusDataUnit::HoldingRegisters); Length = qBound(ModbusLimits::lengthRange().from(), Length, ModbusLimits::lengthRange().to()); LogViewLimit = qBound(4, LogViewLimit, 1000); @@ -31,12 +32,13 @@ Q_DECLARE_METATYPE(DisplayDefinition) inline QSettings& operator <<(QSettings& out, const DisplayDefinition& dd) { - out.setValue("DisplayDefinition/ScanRate", dd.ScanRate); - out.setValue("DisplayDefinition/DeviceId", dd.DeviceId); - out.setValue("DisplayDefinition/PointAddress", dd.PointAddress); - out.setValue("DisplayDefinition/PointType", dd.PointType); - out.setValue("DisplayDefinition/Length", dd.Length); - out.setValue("DisplayDefinition/LogViewLimit", dd.LogViewLimit); + out.setValue("DisplayDefinition/ScanRate", dd.ScanRate); + out.setValue("DisplayDefinition/DeviceId", dd.DeviceId); + out.setValue("DisplayDefinition/PointAddress", dd.PointAddress); + out.setValue("DisplayDefinition/PointType", dd.PointType); + out.setValue("DisplayDefinition/Length", dd.Length); + out.setValue("DisplayDefinition/LogViewLimit", dd.LogViewLimit); + out.setValue("DisplayDefinition/ZeroBasedAddress", dd.ZeroBasedAddress); return out; } @@ -55,6 +57,7 @@ inline QSettings& operator >>(QSettings& in, DisplayDefinition& dd) 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.ZeroBasedAddress = in.value("DisplayDefinition/ZeroBasedAddress").toBool(); dd.normalize(); return in; diff --git a/omodscan/enums.h b/omodscan/enums.h index 378d1ad..5672da7 100644 --- a/omodscan/enums.h +++ b/omodscan/enums.h @@ -4,6 +4,40 @@ #include #include +/// +/// \brief The AddressBase enum +/// +enum class AddressBase +{ + Base0 = 0, + Base1 +}; +Q_DECLARE_METATYPE(AddressBase); + +/// +/// \brief operator << +/// \param out +/// \param params +/// \return +/// +inline QSettings& operator <<(QSettings& out, const AddressBase& base) +{ + out.setValue("AddressBase", (uint)base); + return out; +} + +/// +/// \brief operator >> +/// \param in +/// \param params +/// \return +/// +inline QSettings& operator >>(QSettings& in, AddressBase& base) +{ + base = (AddressBase)in.value("AddressBase").toUInt(); + return in; +} + /// /// \brief The DisplayMode enum /// diff --git a/omodscan/formmodsca.cpp b/omodscan/formmodsca.cpp index 747595e..9365edf 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, 4); +QVersionNumber FormModSca::VERSION = QVersionNumber(1, 5); /// /// \brief FormModSca::FormModSca @@ -38,8 +38,8 @@ FormModSca::FormModSca(int id, ModbusClient& client, DataSimulator* simulator, M _timer.setInterval(1000); ui->lineEditAddress->setPaddingZeroes(true); - ui->lineEditAddress->setInputRange(ModbusLimits::addressRange()); - ui->lineEditAddress->setValue(1); + ui->lineEditAddress->setInputRange(ModbusLimits::addressRange(true)); + ui->lineEditAddress->setValue(0); ui->lineEditLength->setInputRange(ModbusLimits::lengthRange()); ui->lineEditLength->setValue(50); @@ -47,11 +47,15 @@ FormModSca::FormModSca(int id, ModbusClient& client, DataSimulator* simulator, M ui->lineEditDeviceId->setInputRange(ModbusLimits::slaveRange()); ui->lineEditDeviceId->setValue(1); + ui->comboBoxAddressBase->setCurrentAddressBase(AddressBase::Base1); + const auto dd = displayDefinition(); const auto protocol = _modbusClient.connectionType() == ConnectionType::Serial ? ModbusMessage::Rtu : ModbusMessage::Tcp; ui->outputWidget->setup(dd, protocol, _dataSimulator->simulationMap(dd.DeviceId)); ui->outputWidget->setFocus(); + connect(ui->statisticWidget, &StatisticWidget::ctrsReseted, ui->outputWidget, &OutputWidget::clearLogView); + connect(&_modbusClient, &ModbusClient::modbusRequest, this, &FormModSca::on_modbusRequest); connect(&_modbusClient, &ModbusClient::modbusReply, this, &FormModSca::on_modbusReply); connect(&_modbusClient, &ModbusClient::modbusConnected, this, &FormModSca::on_modbusConnected); @@ -125,6 +129,7 @@ DisplayDefinition FormModSca::displayDefinition() const dd.PointType = ui->comboBoxModbusPointType->currentPointType(); dd.Length = ui->lineEditLength->value(); dd.LogViewLimit = ui->outputWidget->logViewLimit(); + dd.ZeroBasedAddress = ui->lineEditAddress->range().from() == 0; return dd; } @@ -141,7 +146,12 @@ void FormModSca::setDisplayDefinition(const DisplayDefinition& dd) ui->lineEditDeviceId->setValue(dd.DeviceId); ui->lineEditDeviceId->blockSignals(false); + ui->comboBoxAddressBase->blockSignals(true); + ui->comboBoxAddressBase->setCurrentAddressBase(dd.ZeroBasedAddress ? AddressBase::Base0 : AddressBase::Base1); + ui->comboBoxAddressBase->blockSignals(false); + ui->lineEditAddress->blockSignals(true); + ui->lineEditAddress->setInputRange(ModbusLimits::addressRange(dd.ZeroBasedAddress)); ui->lineEditAddress->setValue(dd.PointAddress); ui->lineEditAddress->blockSignals(false); @@ -360,29 +370,24 @@ void FormModSca::print(QPrinter* printer) const auto textTime = QLocale().toString(QDateTime::currentDateTime(), QLocale::ShortFormat); auto rcTime = painter.boundingRect(cx, cy, pageWidth, pageHeight, Qt::TextSingleLine, textTime); - const auto textDevId = QString(tr("Device Id: %1")).arg(ui->lineEditDeviceId->text()); - auto rcDevId = painter.boundingRect(cx, cy, pageWidth, pageHeight, Qt::TextSingleLine, textDevId); - - const auto textAddrLen = QString(tr("Address: %1\nLength: %2")).arg(ui->lineEditAddress->text(), ui->lineEditLength->text()); + const auto textAddrLen = QString(tr("Address Base: %1\nStart Address: %2\nLength: %3")).arg(ui->comboBoxAddressBase->currentText(), + ui->lineEditAddress->text(), ui->lineEditLength->text()); auto rcAddrLen = painter.boundingRect(cx, cy, pageWidth, pageHeight, Qt::TextWordWrap, textAddrLen); - const auto textType = QString(tr("MODBUS Point Type:\n%1")).arg(ui->comboBoxModbusPointType->currentText()); - auto rcType = painter.boundingRect(cx, cy, pageWidth, pageHeight, Qt::TextWordWrap, textType); + const auto textDevIdType = QString(tr("Device Id: %1\nMODBUS Point Type:\n%2")).arg(ui->lineEditDeviceId->text(), ui->comboBoxModbusPointType->currentText()); + auto rcDevIdType = painter.boundingRect(cx, cy, pageWidth, pageHeight, Qt::TextWordWrap, textDevIdType); const auto textStat = QString(tr("Number of Polls: %1\nValid Slave Responses: %2")).arg(QString::number(ui->statisticWidget->numberOfPolls()), QString::number(ui->statisticWidget->validSlaveResposes())); auto rcStat = painter.boundingRect(cx, cy, pageWidth, pageHeight, Qt::TextWordWrap, textStat); rcTime.moveTopRight({ pageRect.right(), 10 }); - rcDevId.moveLeft(rcAddrLen.right() + 40); - rcAddrLen.moveTop(rcDevId.bottom() + 10); - rcType.moveTopLeft({ rcDevId.left(), rcAddrLen.top() }); - rcStat.moveLeft(rcType.right() + 40); + rcDevIdType.moveTopLeft({ rcAddrLen.right() + 40, rcAddrLen.top()}); + rcStat.moveLeft(rcDevIdType.right() + 40); painter.drawText(rcTime, Qt::TextSingleLine, textTime); - painter.drawText(rcDevId, Qt::TextSingleLine, textDevId); painter.drawText(rcAddrLen, Qt::TextWordWrap, textAddrLen); - painter.drawText(rcType, Qt::TextWordWrap, textType); + painter.drawText(rcDevIdType, Qt::TextWordWrap, textDevIdType); painter.drawText(rcStat, Qt::TextWordWrap, textStat); painter.drawRect(rcStat.adjusted(-2, -2, 40, 2)); @@ -399,7 +404,7 @@ void FormModSca::print(QPrinter* printer) ModbusSimulationMap FormModSca::simulationMap() const { const auto dd = displayDefinition(); - const auto startAddr = dd.PointAddress - 1; + const auto startAddr = dd.PointAddress - (dd.ZeroBasedAddress ? 0 : 1); const auto endAddr = startAddr + dd.Length; ModbusSimulationMap result; @@ -493,7 +498,8 @@ void FormModSca::on_timeout() return; const auto dd = displayDefinition(); - if(dd.PointAddress + dd.Length - 1 <= ModbusLimits::addressRange().to()) + const auto addr = dd.PointAddress - (dd.ZeroBasedAddress ? 0 : 1); + if(addr + dd.Length <= ModbusLimits::addressRange(dd.ZeroBasedAddress).to()) { if(_validSlaveResponses == ui->statisticWidget->validSlaveResposes()) { @@ -504,7 +510,7 @@ void FormModSca::on_timeout() } } - _modbusClient.sendReadRequest(dd.PointType, dd.PointAddress - 1, dd.Length, dd.DeviceId, _formId); + _modbusClient.sendReadRequest(dd.PointType, addr, dd.Length, dd.DeviceId, _formId); } } @@ -517,8 +523,9 @@ void FormModSca::beginUpdate() return; 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); + const auto addr = dd.PointAddress - (dd.ZeroBasedAddress ? 0 : 1); + if(addr + dd.Length <= ModbusLimits::addressRange(dd.ZeroBasedAddress).to()) + _modbusClient.sendReadRequest(dd.PointType, addr, dd.Length, dd.DeviceId, _formId); else ui->outputWidget->setStatus(tr("No Scan: Invalid Data Length Specified")); @@ -535,17 +542,18 @@ bool FormModSca::isValidReply(const QModbusReply* reply) const const auto dd = displayDefinition(); const auto data = reply->result(); const auto response = reply->rawResult(); + const auto addr = dd.PointAddress - (dd.ZeroBasedAddress ? 0 : 1); switch(response.functionCode()) { case QModbusPdu::ReadCoils: case QModbusPdu::ReadDiscreteInputs: - return (data.startAddress() == dd.PointAddress - 1) && (data.valueCount() - dd.Length) < 8; + return (data.startAddress() == addr) && (data.valueCount() - dd.Length) < 8; break; case QModbusPdu::ReadInputRegisters: case QModbusPdu::ReadHoldingRegisters: - return (data.valueCount() == dd.Length) && (data.startAddress() == dd.PointAddress - 1); + return (data.valueCount() == dd.Length) && (data.startAddress() == addr); default: return true; @@ -728,6 +736,18 @@ void FormModSca::on_lineEditDeviceId_valueChanged(const QVariant&) beginUpdate(); } +/// +/// \brief FormModSca:on_comboBoxAddressBase_addressBaseChanged +/// \param index +/// +void FormModSca::on_comboBoxAddressBase_addressBaseChanged(AddressBase base) +{ + auto dd = displayDefinition(); + dd.PointAddress = (base == AddressBase::Base1 ? qMax(1, dd.PointAddress + 1) : qMax(0, dd.PointAddress - 1)); + dd.ZeroBasedAddress = (base == AddressBase::Base0); + setDisplayDefinition(dd); +} + /// /// \brief FormModSca::on_comboBoxModbusPointType_pointTypeChanged /// @@ -756,13 +776,15 @@ void FormModSca::on_outputWidget_itemDoubleClicked(quint16 addr, const QVariant& const quint32 node = ui->lineEditDeviceId->value(); const quint8 deviceId = ui->lineEditDeviceId->value(); const auto pointType = ui->comboBoxModbusPointType->currentPointType(); - auto simParams = _dataSimulator->simulationParams(pointType, addr, deviceId); + const auto zeroBasedAddress = displayDefinition().ZeroBasedAddress; + const auto simAddr = addr - (zeroBasedAddress ? 0 : 1); + auto simParams = _dataSimulator->simulationParams(pointType, simAddr, deviceId); switch(pointType) { case QModbusDataUnit::Coils: { - ModbusWriteParams params = { node, addr, value, mode, byteOrder() }; + ModbusWriteParams params = { node, addr, value, mode, byteOrder(), zeroBasedAddress }; DialogWriteCoilRegister dlg(params, simParams, _parent); switch(dlg.exec()) { @@ -771,8 +793,8 @@ void FormModSca::on_outputWidget_itemDoubleClicked(quint16 addr, const QVariant& break; case 2: - if(simParams.Mode == SimulationMode::No) _dataSimulator->stopSimulation(pointType, addr, deviceId); - else _dataSimulator->startSimulation(mode, pointType, addr, deviceId, simParams); + if(simParams.Mode == SimulationMode::No) _dataSimulator->stopSimulation(pointType, simAddr, deviceId); + else _dataSimulator->startSimulation(mode, pointType, simAddr, deviceId, simParams); break; } } @@ -780,7 +802,7 @@ void FormModSca::on_outputWidget_itemDoubleClicked(quint16 addr, const QVariant& case QModbusDataUnit::HoldingRegisters: { - ModbusWriteParams params = { node, addr, value, mode, byteOrder()}; + ModbusWriteParams params = { node, addr, value, mode, byteOrder(), zeroBasedAddress }; if(mode == DataDisplayMode::Binary) { DialogWriteHoldingRegisterBits dlg(params, _parent); @@ -797,8 +819,8 @@ void FormModSca::on_outputWidget_itemDoubleClicked(quint16 addr, const QVariant& break; case 2: - if(simParams.Mode == SimulationMode::No) _dataSimulator->stopSimulation(pointType, addr, deviceId); - else _dataSimulator->startSimulation(mode, pointType, addr, deviceId, simParams); + if(simParams.Mode == SimulationMode::No) _dataSimulator->stopSimulation(pointType, simAddr, deviceId); + else _dataSimulator->startSimulation(mode, pointType, simAddr, deviceId, simParams); break; } } @@ -874,9 +896,10 @@ void FormModSca::on_dataSimulated(DataDisplayMode mode, QModbusDataUnit::Registe return; } - if(type == dd.PointType && addr >= dd.PointAddress && addr < dd.PointAddress + dd.Length) + const auto pointAddr = dd.PointAddress - (dd.ZeroBasedAddress ? 0 : 1); + if(type == dd.PointType && addr >= pointAddr && addr <= pointAddr + dd.Length) { - const ModbusWriteParams params = { dd.DeviceId, addr, value, mode, byteOrder() }; + const ModbusWriteParams params = { dd.DeviceId, addr, value, mode, byteOrder(), true }; _modbusClient.writeRegister(type, params, formId()); } } diff --git a/omodscan/formmodsca.h b/omodscan/formmodsca.h index 4c429d2..89587ce 100644 --- a/omodscan/formmodsca.h +++ b/omodscan/formmodsca.h @@ -111,6 +111,7 @@ private slots: void on_lineEditAddress_valueChanged(const QVariant&); void on_lineEditLength_valueChanged(const QVariant&); void on_lineEditDeviceId_valueChanged(const QVariant&); + void on_comboBoxAddressBase_addressBaseChanged(AddressBase base); void on_comboBoxModbusPointType_pointTypeChanged(QModbusDataUnit::RegisterType); void on_outputWidget_itemDoubleClicked(quint16 addr, const QVariant& value); void on_statisticWidget_numberOfPollsChanged(uint value); @@ -248,6 +249,7 @@ inline QDataStream& operator <<(QDataStream& out, const FormModSca* frm) out << dd.PointAddress; out << dd.Length; out << dd.LogViewLimit; + out << dd.ZeroBasedAddress; out << frm->byteOrder(); out << frm->simulationMap(); @@ -304,6 +306,10 @@ inline QDataStream& operator >>(QDataStream& in, FormModSca* frm) { in >> dd.LogViewLimit; } + if(ver >= QVersionNumber(1, 5)) + { + in >> dd.ZeroBasedAddress; + } ByteOrder byteOrder = ByteOrder::LittleEndian; ModbusSimulationMap simulationMap; diff --git a/omodscan/formmodsca.ui b/omodscan/formmodsca.ui index e08cb4c..55f464d 100644 --- a/omodscan/formmodsca.ui +++ b/omodscan/formmodsca.ui @@ -57,7 +57,10 @@ - 16 + 15 + + + 5 @@ -71,25 +74,21 @@ 0 + + 0 + 0 - - - - Address: - - - - + - Length: + Start Address: - + 0 @@ -104,7 +103,7 @@ - 80 + 90 16777215 @@ -116,8 +115,32 @@ + + + + Address Base: + + + - + + + + 0 + 24 + + + + + + + + Length: + + + + + 0 @@ -132,7 +155,7 @@ - 80 + 90 16777215 @@ -147,6 +170,13 @@ + + + + Qt::Vertical + + + @@ -226,6 +256,22 @@
+ + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + @@ -266,7 +312,7 @@ 20 - 40 + 20 @@ -274,6 +320,13 @@ + + + + Qt::Vertical + + + @@ -350,22 +403,27 @@ + + NumericLineEdit + QLineEdit +
numericlineedit.h
+
PointTypeComboBox QComboBox
pointtypecombobox.h
+ + AddressBaseComboBox + QComboBox +
addressbasecombobox.h
+
OutputWidget QWidget
outputwidget.h
1
- - NumericLineEdit - QLineEdit -
numericlineedit.h
-
StatisticWidget QWidget @@ -374,8 +432,6 @@
- lineEditAddress - lineEditLength lineEditDeviceId comboBoxModbusPointType diff --git a/omodscan/mainwindow.cpp b/omodscan/mainwindow.cpp index f3ab7ac..f6046a3 100644 --- a/omodscan/mainwindow.cpp +++ b/omodscan/mainwindow.cpp @@ -669,7 +669,7 @@ void MainWindow::on_actionForceCoils_triggered() if(!frm) return; const auto dd = frm->displayDefinition(); - SetupPresetParams presetParams = { dd.DeviceId, dd.PointAddress, dd.Length }; + SetupPresetParams presetParams = { dd.DeviceId, dd.PointAddress, dd.Length, dd.ZeroBasedAddress }; { DialogSetupPresetData dlg(presetParams, QModbusDataUnit::Coils, this); @@ -679,6 +679,7 @@ void MainWindow::on_actionForceCoils_triggered() ModbusWriteParams params; params.Node = presetParams.SlaveAddress; params.Address = presetParams.PointAddress; + params.ZeroBasedAddress = dd.ZeroBasedAddress; if(dd.PointType == QModbusDataUnit::Coils && dd.DeviceId == params.Node && @@ -703,7 +704,7 @@ void MainWindow::on_actionPresetRegs_triggered() if(!frm) return; const auto dd = frm->displayDefinition(); - SetupPresetParams presetParams = { dd.DeviceId, dd.PointAddress, dd.Length }; + SetupPresetParams presetParams = { dd.DeviceId, dd.PointAddress, dd.Length, dd.ZeroBasedAddress }; { DialogSetupPresetData dlg(presetParams, QModbusDataUnit::HoldingRegisters, this); @@ -715,6 +716,7 @@ void MainWindow::on_actionPresetRegs_triggered() params.Address = presetParams.PointAddress; params.DisplayMode = frm->dataDisplayMode(); params.Order = frm->byteOrder(); + params.ZeroBasedAddress = dd.ZeroBasedAddress; if(dd.PointType == QModbusDataUnit::HoldingRegisters && dd.DeviceId == params.Node && @@ -739,7 +741,7 @@ void MainWindow::on_actionMaskWrite_triggered() if(!frm) return; const auto dd = frm->displayDefinition(); - ModbusMaskWriteParams params = { dd.DeviceId, dd.PointAddress, 0xFFFF, 0}; + ModbusMaskWriteParams params = { dd.DeviceId, dd.PointAddress, 0xFFFF, 0, dd.ZeroBasedAddress}; DialogMaskWriteRegiter dlg(params, this); if(dlg.exec() == QDialog::Accepted) diff --git a/omodscan/modbusclient.cpp b/omodscan/modbusclient.cpp index 3564d19..5694399 100644 --- a/omodscan/modbusclient.cpp +++ b/omodscan/modbusclient.cpp @@ -406,16 +406,18 @@ QModbusDataUnit createHoldingRegistersDataUnit(int newStartAddress, const QVecto void ModbusClient::writeRegister(QModbusDataUnit::RegisterType pointType, const ModbusWriteParams& params, int requestId) { QModbusDataUnit data; + const auto addr = params.ZeroBasedAddress ? params.Address : params.Address - 1; + if(params.Value.userType() == qMetaTypeId>()) { switch (pointType) { case QModbusDataUnit::Coils: - data = createCoilsDataUnit(params.Address - 1, params.Value.value>()); + data = createCoilsDataUnit(addr, params.Value.value>()); break; case QModbusDataUnit::HoldingRegisters: - data = createHoldingRegistersDataUnit(params.Address - 1, params.Value.value>(), params.Order); + data = createHoldingRegistersDataUnit(addr, params.Value.value>(), params.Order); break; default: @@ -427,7 +429,7 @@ void ModbusClient::writeRegister(QModbusDataUnit::RegisterType pointType, const switch (pointType) { case QModbusDataUnit::Coils: - data = createCoilsDataUnit(params.Address - 1, params.Value.toBool()); + data = createCoilsDataUnit(addr, params.Value.toBool()); break; case QModbusDataUnit::HoldingRegisters: @@ -437,39 +439,39 @@ void ModbusClient::writeRegister(QModbusDataUnit::RegisterType pointType, const case DataDisplayMode::UInt16: case DataDisplayMode::Int16: case DataDisplayMode::Hex: - data = createHoldingRegistersDataUnit(params.Address - 1, params.Value.toUInt(), params.Order); + data = createHoldingRegistersDataUnit(addr, params.Value.toUInt(), params.Order); break; case DataDisplayMode::FloatingPt: - data = createHoldingRegistersDataUnit(params.Address - 1, params.Value.toFloat(), params.Order, false); + data = createHoldingRegistersDataUnit(addr, params.Value.toFloat(), params.Order, false); break; case DataDisplayMode::SwappedFP: - data = createHoldingRegistersDataUnit(params.Address - 1, params.Value.toFloat(), params.Order, true); + data = createHoldingRegistersDataUnit(addr, params.Value.toFloat(), params.Order, true); break; case DataDisplayMode::DblFloat: - data = createHoldingRegistersDataUnit(params.Address - 1, params.Value.toDouble(), params.Order, false); + data = createHoldingRegistersDataUnit(addr, params.Value.toDouble(), params.Order, false); break; case DataDisplayMode::SwappedDbl: - data = createHoldingRegistersDataUnit(params.Address - 1, params.Value.toDouble(), params.Order, true); + data = createHoldingRegistersDataUnit(addr, params.Value.toDouble(), params.Order, true); break; case DataDisplayMode::Int32: case DataDisplayMode::UInt32: - data = createHoldingRegistersDataUnit(params.Address - 1, params.Value.toInt(), params.Order, false); + data = createHoldingRegistersDataUnit(addr, params.Value.toInt(), params.Order, false); break; case DataDisplayMode::SwappedInt32: case DataDisplayMode::SwappedUInt32: - data = createHoldingRegistersDataUnit(params.Address - 1, params.Value.toInt(), params.Order, true); + data = createHoldingRegistersDataUnit(addr, params.Value.toInt(), params.Order, true); break; case DataDisplayMode::Int64: case DataDisplayMode::UInt64: - data = createHoldingRegistersDataUnit(params.Address - 1, params.Value.toLongLong(), params.Order, false); + data = createHoldingRegistersDataUnit(addr, params.Value.toLongLong(), params.Order, false); break; case DataDisplayMode::SwappedInt64: case DataDisplayMode::SwappedUInt64: - data = createHoldingRegistersDataUnit(params.Address - 1, params.Value.toLongLong(), params.Order, true); + data = createHoldingRegistersDataUnit(addr, params.Value.toLongLong(), params.Order, true); break; } break; @@ -536,7 +538,8 @@ void ModbusClient::maskWriteRegister(const ModbusMaskWriteParams& params, int re return; } - QModbusRequest request(QModbusRequest::MaskWriteRegister, quint16(params.Address - 1), params.AndMask, params.OrMask); + const auto addr = params.ZeroBasedAddress ? params.Address : params.Address - 1; + QModbusRequest request(QModbusRequest::MaskWriteRegister, quint16(addr), params.AndMask, params.OrMask); emit modbusRequest(requestId, params.Node, ++_transactionId, request); if(auto reply = _modbusClient->sendRawRequest(request, params.Node)) diff --git a/omodscan/modbuslimits.h b/omodscan/modbuslimits.h index 040c842..8b9214d 100644 --- a/omodscan/modbuslimits.h +++ b/omodscan/modbuslimits.h @@ -6,7 +6,7 @@ class ModbusLimits final { public: - static QRange addressRange() { return { 1, 65535 }; } + static QRange addressRange(bool zeroBased = false) { return { (zeroBased ? 0 : 1), 65535 }; } static QRange lengthRange() { return { 1, 125 }; } static QRange slaveRange() { return { 1, 255 }; } }; diff --git a/omodscan/modbuswriteparams.h b/omodscan/modbuswriteparams.h index 3fa9fb4..9353930 100644 --- a/omodscan/modbuswriteparams.h +++ b/omodscan/modbuswriteparams.h @@ -14,6 +14,7 @@ struct ModbusWriteParams QVariant Value; DataDisplayMode DisplayMode; ByteOrder Order; + bool ZeroBasedAddress; }; /// @@ -25,6 +26,7 @@ struct ModbusMaskWriteParams quint32 Address; quint16 AndMask; quint16 OrMask; + bool ZeroBasedAddress; }; #endif // MODBUSWRITEPARAMS_H diff --git a/omodscan/omodscan.pro b/omodscan/omodscan.pro index b64c1c5..93f1757 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.7.1 +VERSION = 1.8.0 QMAKE_TARGET_PRODUCT = "Open ModScan" QMAKE_TARGET_DESCRIPTION = "An Open Source Modbus Master (Client) Utility" @@ -24,6 +24,7 @@ INCLUDEPATH += controls \ modbusmessages \ SOURCES += \ + controls/addressbasecombobox.cpp \ controls/booleancombobox.cpp \ controls/bytelisttextedit.cpp \ controls/byteordercombobox.cpp \ @@ -86,6 +87,7 @@ SOURCES += \ HEADERS += \ byteorderutils.h \ connectiondetails.h \ + controls/addressbasecombobox.h \ controls/booleancombobox.h \ controls/bytelisttextedit.h \ controls/byteordercombobox.h \ diff --git a/omodscan/translations/omodscan_ru.qm b/omodscan/translations/omodscan_ru.qm index 1c1637c..f55f90f 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 d15a06d..1226d7a 100644 --- a/omodscan/translations/omodscan_ru.ts +++ b/omodscan/translations/omodscan_ru.ts @@ -1,6 +1,19 @@ + + AddressBaseComboBox + + + 0-based + 0-первый + + + + 1-based + 1-первый + + BooleanComboBox @@ -30,30 +43,40 @@ CsvExporter - + Device Id Узел - + Start Address Начальный адрес - + Length Количество - + Point Type Тип регистров - + Registers on Query Количество регистров в запросе + + + Address Base + Адресация + + + + Byte Order + Порядок байт + Address Адрес @@ -134,68 +157,73 @@ Начальный адрес: - + Registers on Query: Количество регистров в запросе: - + Length: Количество: - + + Address Base: + Адресация: + + + Point Type: Тип регистров: - + Device Id: Узел: - - + + Scan Сканировать - + Export... Экспортировать... - + Table View Таблица - + Hex View Шестнадцатиричный режим - + Byte Order: - Порядок байтов: + Порядок байт: - + Log View Журнал - + Show Only Valid Responses Показывать только корректные ответы - + Stop Стоп - + Pdf files (*.pdf);;CSV files (*.csv) Pdf файлы (*.pdf);;CSV файлы (*.csv) @@ -204,7 +232,7 @@ Pdf файлы (*.pdf) - + No connection to MODBUS device! Нет соединения с MODBUS устройством! @@ -326,42 +354,42 @@ Скорость бит/с: - + Word Length: Биты данных: - + Parity: Четность: - + Stop Bits: Стоповые биты: - + Hardware Flow Control Управление потоком - + Flow Control Mode: Режим управления: - + DTR Control: Управление DTR: - + RTS Control: Управление RTS: - + Protocol Selections Настройки протокола @@ -404,17 +432,22 @@ Адрес устройства: - + + Address Base: + Адресация: + + + Point Type: Тип регистров: - + Point Address: Начальный адрес: - + Length: Количество: @@ -567,97 +600,95 @@ MODBUS Сканер - + Baud Rate Скорость - + Connection Подключение - + Serial port Порт подключения - + Address Range Диапазон 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 Вермя сканирования @@ -673,19 +704,19 @@ - + None Нет - + Odd Нечет - + Even Чёт @@ -715,133 +746,147 @@ Адрес - + Length Количество - + Response Timeout Таймаут ответа - + msec мсек - + Retry On Timeout Повторить по таймауту - + <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 Очистить результаты - + + Scan + Сканировать + + + Space Пробел - + Mark Маркер - Stop Scan - Остановить + Остановить + + + + Stop + Стоп - + + Start + Старт + + + Address: port (Device Id) Адрес: порт (Узел) - + Clear previous scan results? Очистить результаты предыдущего сканирования? - + Baud Rate: Скорость бит/с: - + Data Bits: Биты данных: - + Parity: Четность: - + Stop Bits: Стоповые биты: - + Address: Адрес: - + Port: Порт: - + Device Id: Узел: - + 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 @@ -1020,7 +1065,7 @@ Tractor - + Трактор @@ -1319,63 +1364,87 @@ single-point write functions 05 and 06.) FormModSca - Address: + Адрес: + + + + Start Address: Адрес: - + + Address Base: + Адресация: + + + Length: Количество: - + Device Id: Узел: - + MODBUS Point Type Тип регистров MODBUS - + Data Uninitialized Данные не инициализированы - Device Id: %1 - Узел: %1 + Узел: %1 - Address: %1 Length: %2 - Адрес: %1 + Адрес: %1 Количество: %2 - MODBUS Point Type: %1 - Тип регистров MODBUS: + Тип регистров MODBUS: %1 - + + Address Base: %1 +Start Address: %2 +Length: %3 + Адресация: %1 +Адрес: %2 +Количество: %3 + + + + Device Id: %1 +MODBUS Point Type: +%2 + Узел: %1 +Тип регистров MODBUS: +%2 + + + Number of Polls: %1 Valid Slave Responses: %2 Количество запросов: %1 Корректных ответов устройства: %2 - + No Scan: Invalid Data Length Specified Нет опроса: указана недопустимая длина данных - + Device NOT CONNECTED! Устройство НЕ ПОДКЛЮЧЕНО! @@ -1384,12 +1453,12 @@ Valid Slave Responses: %2 Указана недопустимая длина данных - + No Responses from Slave Device Нет ответа от устройства - + Received Invalid Response MODBUS Query Получен некорректный ответ на запрос MODBUS @@ -1893,12 +1962,12 @@ Valid Slave Responses: %2 Все файлы (*) - + %1 was not found %1 не найден - + Failed to open %1 Ошибка открытия файла %1 @@ -1911,29 +1980,29 @@ Valid Slave Responses: %2 Некорректный запрос Modbus - - + + Coil Write Failure Ошибка записи в Coil регистр - - + + Register Write Failure Ошибка записи в регистр - + Mask Write Register Failure Ошибка записи в регистр по маске - + Mask Register Write Failure Ошибка записи в регистр по маске - + Connection error. %1 Ошибка подключения. %1 @@ -2022,7 +2091,7 @@ Valid Slave Responses: %2 (0-based) - + (адресация с 0) @@ -2269,12 +2338,12 @@ Valid Slave Responses: %2 OutputWidget - + %1: Enter Description %1: Введите описание - + Data Uninitialized Данные не инициализированы @@ -2300,22 +2369,41 @@ Valid Slave Responses: %2 PdfExporter - + Error. Failed to write PDF file! Ошибка. Не удалось записать PDF файл! - + + Address Base: %1 +Start Address: %2 + Адресация: %1 +Начальный адрес: %2 + + + + Device Id: %1 Length: %2 +Point Type: [%3] + Узел: %1 Количество: %2 +Тип регистров: [%3] + + + + Registers on Query: %1 +Byte Order: %2 + Количество регистров в запросе: %1 +Порядок байт %2 + + Device Id: %1 Length: %2 Point Type: [%3] - Узел: %1 Количество: %2 + Узел: %1 Количество: %2 Тип регистров: [%3] - Start Address: %1 Registers on Query: %2 - Начальный адрес: %1 + Начальный адрес: %1 Количество регистров в запросе: %2 @@ -2361,12 +2449,12 @@ Registers on Query: %2 Сброс - + Number of Polls: %1 Количество запросов: %1 - + Valid Slave Responses: %1 Корректных ответов устройства: %1