/*************************************************************************************************** * Copyright 2023 x-tools-author(x-tools@outlook.com). All rights reserved. * * The file is encoded using "utf8 with bom", it is a part of xTools project. * * xTools is licensed according to the terms in the file LICENCE(GPL V3) in the root of the source * code directory. **************************************************************************************************/ #include "sakmodbusui.h" #include "ui_sakmodbusui.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION < QT_VERSION_CHECK(6, 2, 0) #include #include #else #include #include #endif #include "sakmodbusfactory.h" #include "xToolsSettings.h" #define RXCOLOR "green" #define TXCOLOR "blue" #define RXFLAG "rx:" #define TXFLAG "tx:" #define MAX_HISTORY_INDEX 9 struct SAKModbusUiSettingKeys { const QString device_index = "SAKModbus/deviceIndex"; const QString port_name = "SAKModbus/portName"; const QString parity = "SAKModbus/parity"; const QString baud_rate = "SAKModbus/baudRate"; const QString data_bits = "SAKModbus/dataBits"; const QString stop_bits = "SAKModbus/stopBits"; const QString custom_baud_rate = "SAKModbus/customBaudRate"; const QString address = "SAKModbus/address"; const QString port = "SAKModbus/port"; const QString custom_address = "SAKModbus/customAddress"; const QString client_timeout = "SAKModbus/clientTimeout"; const QString client_repeat_time = "SAKModbus/clientRepeatTime"; const QString server_is_busy = "SAKModbus/serverIsBusy"; const QString server_just_listen = "SAKModbus/serverJustListen"; const QString server_address = "SAKModbus/serverAddress"; const QString function_code = "SAKModbus/functionCode"; const QString target_address = "SAKModbus/targetAddress"; const QString start_address = "SAKModbus/startAddress"; const QString address_number = "SAKModbus/addressNumber"; const QString send_history = "SAKModbus/sendHistory"; const QString send_history_index = "SAKModbus/sendHistoryIndex"; const QString pdu = "pdu"; }; class ReadOnlyDelegate : public QItemDelegate { public: ReadOnlyDelegate(QWidget *parent = Q_NULLPTR) : QItemDelegate(parent) {} QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override { Q_UNUSED(parent); Q_UNUSED(option); Q_UNUSED(index); return Q_NULLPTR; } }; SAKModbusUi::SAKModbusUi(QWidget *parent) : QWidget{parent} , ui(new Ui::SAKModbusUi) , modbus_device_(Q_NULLPTR) , register_model_(Q_NULLPTR) , key_ctx_(new SAKModbusUiSettingKeys) { if (!settings_) { settings_ = xToolsSettings::instance(); } ui->setupUi(this); InitComponents(); InitSettings(); InitSignals(); UpdateUiState(false); OnDeviceTypeChanged(); UpdateClientTableView(); UpdateClientReadWriteButtonState(); } SAKModbusUi::~SAKModbusUi() { delete ui; } void SAKModbusUi::InitComponents() { InitComponentDevices(); InitComponentAddress(); InitComponentPortName(); InitComponnetBaudRate(); InitComponnetDataBits(); InitComponnetStopBits(); InitComponnetParity(); InitComponentFunctionCode(); InitComponentRegisterTableView(); InitComponentInput(); InitComponentRegisterTabWidget(); } void SAKModbusUi::InitComponentDevices() { ui->device_list_->addItem(tr("RtuClient"), SAKModbusFactory::kModbusRtuSerialClient); ui->device_list_->addItem(tr("RtuServer"), SAKModbusFactory::kModbusRtuSerialServer); ui->device_list_->addItem(tr("TcpClient"), SAKModbusFactory::kModbusTcpClient); ui->device_list_->addItem(tr("TcpServer"), SAKModbusFactory::kModbusTcpServer); } void SAKModbusUi::InitComponentAddress() { ui->address_combo_box->clear(); QList addresses = QNetworkInterface::allAddresses(); for (QHostAddress &address : addresses) { if (address.protocol() == QAbstractSocket::IPv4Protocol) { ui->address_combo_box->addItem(address.toString()); } } } void SAKModbusUi::InitComponentPortName() { ui->port_name_->clear(); QList infos = QSerialPortInfo::availablePorts(); for (QSerialPortInfo &info : infos) { ui->port_name_->addItem(info.portName()); } } void SAKModbusUi::InitComponnetBaudRate() { ui->baud_rate_->clear(); QList bds = QSerialPortInfo::standardBaudRates(); for (qint32 &bd : bds) { ui->baud_rate_->addItem(QString::number(bd), bd); } } void SAKModbusUi::InitComponnetDataBits() { ui->data_bits_->clear(); ui->data_bits_->addItem("8", QSerialPort::Data8); ui->data_bits_->addItem("7", QSerialPort::Data7); ui->data_bits_->addItem("6", QSerialPort::Data6); ui->data_bits_->addItem("5", QSerialPort::Data5); } void SAKModbusUi::InitComponnetStopBits() { ui->stop_bits_->clear(); ui->stop_bits_->addItem("1", QSerialPort::OneStop); #ifdef Q_OS_WIN ui->stop_bits_->addItem("1.5", QSerialPort::OneAndHalfStop); #endif ui->stop_bits_->addItem("2", QSerialPort::TwoStop); } void SAKModbusUi::InitComponnetParity() { ui->parity_->clear(); ui->parity_->addItem(tr("NoParity"), QSerialPort::NoParity); ui->parity_->addItem(tr("EvenParity"), QSerialPort::EvenParity); ui->parity_->addItem(tr("OddParity"), QSerialPort::OddParity); ui->parity_->addItem(tr("SpaceParity"), QSerialPort::SpaceParity); ui->parity_->addItem(tr("MarkParity"), QSerialPort::MarkParity); } void SAKModbusUi::InitComponentFunctionCode() { const QString str0x01 = tr("0x01-ReadCoils"); const QString str0x02 = tr("0x02-ReadDiscreteInputs"); const QString str0x03 = tr("0x03-ReadHoldingRegisters"); const QString str0x04 = tr("0x04-ReadInputRegisters"); const QString str0x05 = tr("0x05-WriteSingleCoil"); const QString str0x06 = tr("0x06-WriteSingleRegister"); const QString str0x0f = tr("0x0f-WriteMultipleCoils"); const QString str0x10 = tr("0x10-WriteMultipleRegisters"); int func0x01 = QModbusDataUnit::Coils; int func0x02 = QModbusDataUnit::DiscreteInputs; int func0x03 = QModbusDataUnit::HoldingRegisters; int func0x04 = QModbusDataUnit::InputRegisters; int func0x05 = QModbusDataUnit::Coils; int func0x06 = QModbusDataUnit::HoldingRegisters; int func0x0f = QModbusDataUnit::Coils; int func0x10 = QModbusDataUnit::HoldingRegisters; ui->function_code_->addItem(str0x01, func0x01); ui->function_code_->addItem(str0x02, func0x02); ui->function_code_->addItem(str0x03, func0x03); ui->function_code_->addItem(str0x04, func0x04); ui->function_code_->addItem(str0x05, func0x05); ui->function_code_->addItem(str0x06, func0x06); ui->function_code_->addItem(str0x0f, func0x0f); ui->function_code_->addItem(str0x10, func0x10); } void SAKModbusUi::InitComponentRegisterTableView() { QTableView *table_view = CreateTableView(1, ui->client_registers_); register_model_ = qobject_cast(table_view->model()); } void SAKModbusUi::InitComponentInput() { QRegularExpression re("([0-9a-fA-F][0-9a-fA-F][ ])*"); QLineEdit *le = ui->pdu_->lineEdit(); QRegularExpressionValidator *rev = new QRegularExpressionValidator(re, le); le->setValidator(rev); le->clear(); } void SAKModbusUi::InitComponentRegisterTabWidget() { QTabWidget *tab_widget = ui->server_registers_; QStringList titles = QStringList() << tr("Coils") << tr("DiscreteInputs") << tr("InputRegisters") << tr("HoldingRegisters"); for (QString &title : titles) { QTableView *table_view = CreateTableView(UINT16_MAX + 1, Q_NULLPTR); table_view->verticalHeader()->hide(); tab_widget->addTab(table_view, title); } } void SAKModbusUi::InitSettings() { InitSettingsDevice(); InitSettingsNetwork(); InitSettingsSerialPort(); InitSettingsClient(); InitSettingsServer(); InitSettingsClientOperations(); InitSettingsInput(); } void SAKModbusUi::InitSettingsDevice() { int deviceIndex = settings_->value(key_ctx_->device_index).toInt(); if (deviceIndex >= 0 && deviceIndex < ui->device_list_->count()) { ui->device_list_->setCurrentIndex(deviceIndex); } } void SAKModbusUi::InitSettingsNetwork() { QString address = settings_->value(key_ctx_->address).toString(); ui->address_combo_box->setCurrentText(address); QVariant portValiant = settings_->value(key_ctx_->port); int port = portValiant.toInt(); if (portValiant.isNull()) { port = 502; } ui->port_spin_box->setValue(port); } void SAKModbusUi::InitSettingsSerialPort() { auto SetComboBoxIndex = [](int index, QComboBox *cb) { if (index >= 0 && index < cb->count()) { cb->setCurrentIndex(index); } }; QString portName = settings_->value(key_ctx_->port_name).toString(); int index = ui->port_name_->findText(portName); SetComboBoxIndex(index, ui->port_name_); index = settings_->value(key_ctx_->parity).toInt(); SetComboBoxIndex(index, ui->parity_); QString bd = settings_->value(key_ctx_->baud_rate).toString(); ui->baud_rate_->setCurrentText(bd); index = settings_->value(key_ctx_->data_bits).toInt(); SetComboBoxIndex(index, ui->data_bits_); index = settings_->value(key_ctx_->stop_bits).toInt(); SetComboBoxIndex(index, ui->stop_bits_); } void SAKModbusUi::InitSettingsClient() { int timeout = settings_->value(key_ctx_->client_timeout).toInt(); ui->timeout_->setValue(timeout < 100 ? 100 : timeout); const QString key = key_ctx_->client_repeat_time; int repeatTimes = settings_->value(key).toInt(); ui->repeat_time_->setValue(repeatTimes); } void SAKModbusUi::InitSettingsServer() { bool isBusy = settings_->value(key_ctx_->server_is_busy).toBool(); ui->device_busy_->setChecked(isBusy); QString key = key_ctx_->server_just_listen; bool just_listen = settings_->value(key).toBool(); ui->listen_only_mode_->setChecked(just_listen); int address = settings_->value(key_ctx_->server_address).toInt(); ui->server_address->setValue(address); } void SAKModbusUi::InitSettingsClientOperations() { int index = settings_->value(key_ctx_->function_code).toInt(); if (index >= 0 && index < ui->function_code_->count()) { ui->function_code_->setCurrentIndex(index); } int address = settings_->value(key_ctx_->target_address).toInt(); ui->device_address_->setValue(address); int start = settings_->value(key_ctx_->start_address).toInt(); ui->start_address_->setValue(start); int number = settings_->value(key_ctx_->address_number).toInt(); ui->quantity_->setValue(number); } void SAKModbusUi::InitSettingsInput() { ui->pdu_->clear(); settings_->beginReadArray(key_ctx_->send_history); for (int i = 0; i < 10; i++) { settings_->setArrayIndex(i); QString text = settings_->value(key_ctx_->pdu).toString(); if (!text.isEmpty()) { ui->pdu_->addItem(text); } } settings_->endArray(); int index = settings_->value(key_ctx_->send_history_index).toInt(); ui->pdu_->setCurrentIndex(index - 1); } void SAKModbusUi::InitSignals() { InitSignalsDevice(); InitSignalsNetworking(); InitSignalsSerialPort(); InitSignalsClient(); InitSignalsServer(); InitSignalsClientOperations(); } void SAKModbusUi::InitSignalsDevice() { connect(ui->device_list_, QOverload::of(&QComboBox::currentIndexChanged), this, &SAKModbusUi::OnDeviceTypeChanged); connect(ui->open_button_, &QPushButton::clicked, this, &SAKModbusUi::OnOpenClicked); connect(ui->cloese_button_, &QPushButton::clicked, this, &SAKModbusUi::OnCloseClicked); } void SAKModbusUi::InitSignalsNetworking() { connect(ui->address_combo_box, &QComboBox::currentTextChanged, this, &SAKModbusUi::OnAddressChanged); connect(ui->port_spin_box, QOverload::of(&QSpinBox::valueChanged), this, &SAKModbusUi::OnPortChanged); } void SAKModbusUi::InitSignalsSerialPort() { connect(ui->port_name_, &QComboBox::currentTextChanged, this, &SAKModbusUi::OnPortNameChanged); connect(ui->parity_, QOverload::of(&QComboBox::currentIndexChanged), this, &SAKModbusUi::OnParityChanged); connect(ui->baud_rate_, &QComboBox::currentTextChanged, this, &SAKModbusUi::OnBaudRateChanged); connect(ui->data_bits_, QOverload::of(&QComboBox::currentIndexChanged), this, &SAKModbusUi::OnDataBitsChanged); connect(ui->stop_bits_, QOverload::of(&QComboBox::currentIndexChanged), this, &SAKModbusUi::OnStopBistChanged); } void SAKModbusUi::InitSignalsClient() { connect(ui->timeout_, QOverload::of(&QSpinBox::valueChanged), this, &SAKModbusUi::OnClientTimeoutChanged); connect(ui->repeat_time_, QOverload::of(&QSpinBox::valueChanged), this, &SAKModbusUi::OnClientRepeatTimeChanged); } void SAKModbusUi::InitSignalsServer() { connect(ui->device_busy_, &QCheckBox::clicked, this, &SAKModbusUi::OnServerIsBusyChanged); connect(ui->listen_only_mode_, &QCheckBox::clicked, this, &SAKModbusUi::OnServerJustListenChanged); connect(ui->server_address, QOverload::of(&QSpinBox::valueChanged), this, &SAKModbusUi::OnServerAddressChanged); } void SAKModbusUi::InitSignalsClientOperations() { connect(ui->function_code_, &QComboBox::currentTextChanged, this, &SAKModbusUi::OnFunctionCodeChanged); connect(ui->device_address_, QOverload::of(&QSpinBox::valueChanged), this, &SAKModbusUi::OnTargetAddressChanged); connect(ui->start_address_, QOverload::of(&QSpinBox::valueChanged), this, &SAKModbusUi::OnStartAddressChanged); connect(ui->quantity_, QOverload::of(&QSpinBox::valueChanged), this, &SAKModbusUi::OnAddressNumberChanged); connect(ui->read_, &QPushButton::clicked, this, &SAKModbusUi::OnReadClicked); connect(ui->write_, &QPushButton::clicked, this, &SAKModbusUi::OnWriteClicked); connect(ui->send_, &QPushButton::clicked, this, &SAKModbusUi::OnSendClicked); } void SAKModbusUi::OnErrorOccurred() { outputMessage(modbus_device_->errorString(), true, "", "error"); if (modbus_device_->error() == QModbusDevice::ConnectionError) { const QString msg = modbus_device_->errorString(); OnCloseClicked(); QMessageBox::warning(this, tr("Error occured"), msg); } } void SAKModbusUi::OnDeviceTypeChanged() { int type = ui->device_list_->currentData().toInt(); bool isSerial = (type == SAKModbusFactory::kModbusRtuSerialClient || type == SAKModbusFactory::kModbusRtuSerialServer); bool isClient = (type == SAKModbusFactory::kModbusRtuSerialClient || type == SAKModbusFactory::kModbusTcpClient); // Hide ui component first then show ui component, // or the window will be resize to the max size of default. if (isSerial) { ui->networkGroupBox->setHidden(true); ui->serialPortGroupBox->setHidden(false); } else { ui->serialPortGroupBox->setHidden(true); ui->networkGroupBox->setHidden(false); } if (isClient) { ui->console_->setVisible(true); ui->serverGroupBox->setHidden(true); ui->registersGroupBox->setHidden(true); ui->clientOperationsGroupBox->setHidden(false); ui->clientCustomCommandGroupBox->setHidden(false); ui->clientRegistersGroupBox->setHidden(false); } else { ui->console_->setVisible(false); ui->clientCustomCommandGroupBox->setHidden(true); ui->clientRegistersGroupBox->setHidden(true); ui->clientOperationsGroupBox->setHidden(true); ui->serverGroupBox->setHidden(false); ui->registersGroupBox->setHidden(false); } settings_->setValue(key_ctx_->device_index, type); } void SAKModbusUi::OnCloseClicked() { SAKModbusFactory::Instance()->DeleteModbusDevuce(&modbus_device_); UpdateUiState(false); } void SAKModbusUi::OnOpenClicked() { ui->open_button_->setEnabled(false); SAKModbusFactory::Instance()->DeleteModbusDevuce(&modbus_device_); modbus_device_ = CreateModbusDevice(); if (SAKModbusFactory::Instance()->IsServerDevice(modbus_device_)) { if (!UpdateServerMap(modbus_device_)) { ui->open_button_->setEnabled(true); qCWarning(kLoggingCategory) << "Can not reset server map!"; return; } UpdateServerParameters(); QModbusServer *server = qobject_cast(modbus_device_); UpdateServerRegistersData(); connect(server, &QModbusServer::dataWritten, this, &SAKModbusUi::OnDateWritten); } else if (SAKModbusFactory::Instance()->IsClientDevice(modbus_device_)) { UpdateClientParameters(); } else { ui->open_button_->setEnabled(true); qCWarning(kLoggingCategory) << "Can not create modbus devices!"; return; } connect(modbus_device_, &QModbusDevice::errorOccurred, this, &SAKModbusUi::OnErrorOccurred); SAKModbusFactory *factory = SAKModbusFactory::Instance(); bool connected = factory->ConnectDeivce(modbus_device_); if (!connected) { QString errStr = modbus_device_->errorString(); QString info = tr("Can not open device: %1." "Please check the parameters and try again!") .arg(errStr); QMessageBox::warning(this, tr("Can not open device"), info); } UpdateUiState(connected); } void SAKModbusUi::OnAddressChanged() { settings_->setValue(key_ctx_->address, ui->address_combo_box->currentText()); } void SAKModbusUi::OnPortChanged() { settings_->setValue(key_ctx_->port, ui->port_spin_box->value()); } void SAKModbusUi::OnPortNameChanged() { settings_->setValue(key_ctx_->port_name, ui->port_name_->currentText()); } void SAKModbusUi::OnParityChanged() { settings_->setValue(key_ctx_->parity, ui->parity_->currentIndex()); } void SAKModbusUi::OnBaudRateChanged() { settings_->setValue(key_ctx_->baud_rate, ui->baud_rate_->currentText()); } void SAKModbusUi::OnDataBitsChanged() { settings_->setValue(key_ctx_->data_bits, ui->data_bits_->currentIndex()); } void SAKModbusUi::OnStopBistChanged() { settings_->setValue(key_ctx_->stop_bits, ui->stop_bits_->currentIndex()); } void SAKModbusUi::OnInvokeRefresh() { InitComponentPortName(); } void SAKModbusUi::OnClientTimeoutChanged() { settings_->setValue(key_ctx_->client_timeout, ui->timeout_->value()); UpdateClientParameters(); } void SAKModbusUi::OnClientRepeatTimeChanged() { settings_->setValue(key_ctx_->client_repeat_time, ui->repeat_time_->value()); UpdateClientParameters(); } void SAKModbusUi::OnServerIsBusyChanged() { settings_->setValue(key_ctx_->server_is_busy, ui->device_busy_->isChecked()); UpdateServerParameters(); } void SAKModbusUi::OnServerJustListenChanged() { settings_->setValue(key_ctx_->server_just_listen, ui->listen_only_mode_->isChecked()); UpdateServerParameters(); } void SAKModbusUi::OnServerAddressChanged() { settings_->setValue(key_ctx_->server_address, ui->server_address->value()); UpdateServerParameters(); } void SAKModbusUi::OnFunctionCodeChanged() { settings_->setValue(key_ctx_->function_code, ui->function_code_->currentIndex()); UpdateClientReadWriteButtonState(); } void SAKModbusUi::OnTargetAddressChanged() { settings_->setValue(key_ctx_->target_address, ui->device_address_->value()); } void SAKModbusUi::OnStartAddressChanged() { settings_->setValue(key_ctx_->start_address, ui->start_address_->value()); UpdateClientTableView(); } void SAKModbusUi::OnAddressNumberChanged() { settings_->setValue(key_ctx_->address_number, ui->quantity_->value()); UpdateClientTableView(); } void SAKModbusUi::OnReadClicked() { if (!IsConnected()) { return; } if (!SAKModbusFactory::Instance()->IsClientDevice(modbus_device_)) { return; } int register_type = ui->function_code_->currentData().toInt(); quint16 start_address = ui->start_address_->value(); quint16 quantity = ui->quantity_->value(); quint16 server_address = ui->device_address_->value(); quint8 function_code = GetClientFunctionCode(); qCInfo(kLoggingCategory) << "[SendReadRequest]" << "register type:" << register_type << "start address:" << start_address << "quantity:" << quantity << "server address:" << server_address; typedef QModbusDataUnit::RegisterType RegisterType; RegisterType type = static_cast(register_type); QModbusDataUnit data_unit(type, start_address, quantity); QModbusClient *client = qobject_cast(modbus_device_); QModbusReply *reply = client->sendReadRequest(data_unit, server_address); if (!SAKModbusFactory::Instance()->IsValidModbusReply(reply)) { return; } QString info = ui->function_code_->currentText(); outputMessage(info, false, TXCOLOR, TXFLAG); connect(reply, &QModbusReply::finished, this, [=]() { OutputModbusReply(reply, function_code); if (reply->error() == QModbusDevice::NoError) { UpdateClientTableViewData(reply->result().values()); reply->deleteLater(); } }); } void SAKModbusUi::OnWriteClicked() { if (!IsConnected()) { return; } int registerType = ui->function_code_->currentData().toInt(); int start_address = ui->start_address_->value(); int server_address = ui->device_address_->value(); quint8 function_code = GetClientFunctionCode(); QList values = GetClientRegisterValue(); SAKModbusFactory *factory = SAKModbusFactory::Instance(); QModbusReply *reply = factory->SendWriteRequest(modbus_device_, registerType, start_address, values, server_address); if (SAKModbusFactory::Instance()->IsValidModbusReply(reply)) { connect(reply, &QModbusReply::finished, this, [=]() { OutputModbusReply(reply, function_code); reply->deleteLater(); }); QString info = ui->function_code_->currentText(); outputMessage(info, false, TXCOLOR, TXFLAG); } } void SAKModbusUi::OnSendClicked() { if (!IsConnected()) { return; } quint8 server_address = ui->device_address_->value(); QByteArray pdu = GetClientPdu(); QByteArray data = pdu; if (!data.isEmpty()) { data = data.remove(0, 1); } int function_code = int(QModbusDataUnit::Invalid); if (!pdu.isEmpty()) { function_code = pdu.at(0); } SAKModbusFactory *factory = SAKModbusFactory::Instance(); QModbusReply *reply = factory->SendRawRequest(modbus_device_, server_address, function_code, data); qCWarning(kLoggingCategory) << "Send raw request:" << "server address:" << server_address << "function code:" << function_code << "data:" << QString(pdu.toHex(' ')); if (SAKModbusFactory::Instance()->IsValidModbusReply(reply)) { connect(reply, &QModbusReply::finished, this, [=]() { OutputModbusReply(reply, function_code); reply->deleteLater(); }); QString info = "pdu(No server address, no crc):"; info += QString(pdu.toHex(' ')); outputMessage(info, false, TXCOLOR, TXFLAG); } // Update settings data. int index = settings_->value(key_ctx_->send_history_index).toInt(); bool ret = WriteSettingsArray(key_ctx_->send_history, key_ctx_->pdu, QString(pdu.toHex(' ')), index, MAX_HISTORY_INDEX); if (!ret) { return; } if (index > ui->pdu_->count()) { ui->pdu_->addItem(QString(pdu.toHex(' '))); } else { ui->pdu_->insertItem(index, QString(pdu.toHex(' '))); } index = index + 1 > MAX_HISTORY_INDEX ? 0 : index + 1; settings_->setValue(key_ctx_->send_history_index, index); } void SAKModbusUi::OnDateWritten(QModbusDataUnit::RegisterType table, int address, int size) { qCInfo(kLoggingCategory) << "Data written:" << "table:" << table << "start address:" << address << "size:" << size; QTableView *tv = GetTableView(table); QStandardItemModel *model = qobject_cast(tv->model()); QModbusServer *server = qobject_cast(modbus_device_); QList data = SAKModbusFactory::Instance()->GetServerData(server, table, address, size); size = qMin(data.count(), size); for (int i = 0; i < size; i++) { int row = address + i; int base = 16; int width = base == 2 ? 16 : (base == 10 ? 5 : 4); int value = data.at(i); QString cooked_str = QString::number(value, base); cooked_str = cooked_str.rightJustified(width, '0', true); QStandardItem *item = model->item(row, 1); if (item) { item->setData(cooked_str, Qt::DisplayRole); item->setTextAlignment(Qt::AlignCenter); } } tv->viewport()->update(); } void SAKModbusUi::OnItemChanged(QStandardItem *item) { if (!item) { return; } if (item->column() != 1) { return; } if (SAKModbusFactory::Instance()->IsServerDevice(modbus_device_)) { int address = item->row(); int current_index = ui->server_registers_->currentIndex(); QModbusDataUnit::RegisterType table = QModbusDataUnit::Invalid; if (current_index == 0) { table = QModbusDataUnit::Coils; } else if (current_index == 1) { table = QModbusDataUnit::DiscreteInputs; } else if (current_index == 2) { table = QModbusDataUnit::InputRegisters; } else if (current_index == 3) { table = QModbusDataUnit::HoldingRegisters; } else { Q_ASSERT_X(false, __FUNCTION__, "Unknow table type!"); return; } quint16 value = item->text().toInt(Q_NULLPTR, 16); SAKModbusFactory::Instance()->SetServerData(modbus_device_, table, address, value); } } QModbusDevice *SAKModbusUi::CreateModbusDevice() { QModbusDevice *device = Q_NULLPTR; int type = ui->device_list_->currentData().toInt(); if (SAKModbusFactory::Instance()->IsRtuSerialDeviceType(type)) { QString port_name = ui->port_name_->currentText(); int parity = ui->parity_->currentData().toInt(); int baud_rate = ui->baud_rate_->currentData().toInt(); int data_bits = ui->data_bits_->currentData().toInt(); int stop_bits = ui->stop_bits_->currentData().toInt(); SAKModbusFactory *factory = SAKModbusFactory::Instance(); device = factory->CreateRtuSerialDevice(type, port_name, parity, baud_rate, data_bits, stop_bits); } else if (SAKModbusFactory::Instance()->IsTcpDeviceType(type)) { QString address = ui->address_combo_box->currentText(); int port = ui->port_spin_box->value(); SAKModbusFactory *factory = SAKModbusFactory::Instance(); device = factory->CreateTcpDevice(type, address, port); } else { Q_ASSERT_X(false, __FUNCTION__, "Unknown device type"); } return device; } QTableView *SAKModbusUi::CreateTableView(int row_count, QTableView *table_view) { if (!table_view) { table_view = new QTableView(this); } QHeaderView *hv = table_view->horizontalHeader(); QStandardItemModel *model = new QStandardItemModel(table_view); QStringList labels = QStringList() << tr("Address") << tr("Value") << tr("Description"); model->setHorizontalHeaderLabels(labels); model->setColumnCount(3); model->setRowCount(row_count); table_view->setModel(model); table_view->verticalHeader()->hide(); table_view->setItemDelegateForColumn(0, new ReadOnlyDelegate(table_view)); UpdateClientTableViewAddress(table_view, 0); hv->setStretchLastSection(true); // Set the default value to 0. model->blockSignals(true); for (int row = 0; row < row_count; row++) { QModelIndex index = model->index(row, 1); QMap roles; roles.insert(Qt::DisplayRole, "0000"); model->setItemData(index, roles); QStandardItem *item = model->item(row, 1); item->setTextAlignment(Qt::AlignCenter); } model->blockSignals(false); connect(model, &QStandardItemModel::itemChanged, this, &SAKModbusUi::OnItemChanged); return table_view; } void SAKModbusUi::UpdateUiState(bool connected) { ui->device_list_->setEnabled(!connected); ui->cloese_button_->setEnabled(connected); ui->open_button_->setEnabled(!connected); ui->networkGroupBox->setEnabled(!connected); ui->serialPortGroupBox->setEnabled(!connected); #if 0 ui->clientGroupBox->setEnabled(!connected); ui->serverGroupBox->setEnabled(!connected); #endif } void SAKModbusUi::UpdateClientTableView() { int number = ui->quantity_->value(); int rowCount = register_model_->rowCount(); if (number > rowCount) { register_model_->insertRows(rowCount, number - rowCount); } else if (number < rowCount) { register_model_->removeRows(number, rowCount - number); } // Update address. int start_address = ui->start_address_->value(); UpdateClientTableViewAddress(ui->client_registers_, start_address); } void SAKModbusUi::UpdateClientTableViewData(const QList &values) { for (int row = 0; row < values.count(); row++) { int value = values.at(row); QModelIndex index = register_model_->index(row, 1); QMap roles; QString str = QString("%1").arg(QString::number(value, 16), 4, '0'); roles.insert(Qt::DisplayRole, str); register_model_->setItemData(index, roles); QStandardItem *item = register_model_->item(row, 1); if (item) { item->setTextAlignment(Qt::AlignCenter); } } // Refresh the view, or the new value will not be show. ui->client_registers_->viewport()->update(); } void SAKModbusUi::UpdateClientReadWriteButtonState() { QStringList list = ui->function_code_->currentText().split('-'); int code = list.length() ? list.first().toInt(Q_NULLPTR, 16) : 0; bool is_reading_operation = false; if (code == 0x01 || code == 0x02 || code == 0x03 || code == 0x04) { is_reading_operation = true; } if (code == 0x05 || code == 0x06) { ui->quantity_->setValue(1); } ui->read_->setEnabled(is_reading_operation); ui->write_->setEnabled(!is_reading_operation); } void SAKModbusUi::UpdateClientParameters() { int timeout = ui->timeout_->value(); int repeat_time = ui->repeat_time_->value(); SAKModbusFactory::Instance()->SetClientDeviceParameters(modbus_device_, timeout, repeat_time); } void SAKModbusUi::UpdateClientTableViewAddress(QTableView *view, int start_address) { auto *model = qobject_cast(view->model()); for (int row = 0; row < model->rowCount(); row++) { int address = row + start_address; QString text = QString("%1").arg(QString::number(address), 5, '0'); QModelIndex index = model->index(row, 0); QMap roles; roles.insert(Qt::DisplayRole, text); model->setItemData(index, roles); QStandardItem *item = model->item(row, 0); if (item) { item->setTextAlignment(Qt::AlignCenter); } } } void SAKModbusUi::UpdateServerParameters() { bool device_busy = ui->device_busy_->isChecked(); bool listen_only_mode = ui->listen_only_mode_->isChecked(); int address = ui->server_address->value(); SAKModbusFactory::Instance()->SetServerDeviceParameters(modbus_device_, address, device_busy, listen_only_mode); } bool SAKModbusUi::UpdateServerMap(QModbusDevice *server) { if (server && qobject_cast(server)) { QVector values(UINT16_MAX + 1, 0); QModbusDataUnit dataUnit(QModbusDataUnit::Coils, 0, values); QModbusDataUnitMap map; map.insert(QModbusDataUnit::Coils, dataUnit); map.insert(QModbusDataUnit::DiscreteInputs, dataUnit); map.insert(QModbusDataUnit::HoldingRegisters, dataUnit); map.insert(QModbusDataUnit::InputRegisters, dataUnit); QModbusServer *cooked_server = qobject_cast(server); cooked_server->blockSignals(true); bool is_ok = cooked_server->setMap(map); cooked_server->blockSignals(false); return is_ok; } return false; } void SAKModbusUi::UpdateServerRegistersData() { for (int i = 0; i < 4; i++) { QWidget *widget = ui->server_registers_->widget(i); QTableView *table_view = qobject_cast(widget); auto *model = qobject_cast(table_view->model()); int type = QModbusDataUnit::Invalid; if (i == 0) { type = QModbusDataUnit::Coils; } else if (i == 1) { type = QModbusDataUnit::DiscreteInputs; } else if (i == 2) { type = QModbusDataUnit::InputRegisters; } else if (i == 3) { type = QModbusDataUnit::HoldingRegisters; } else { qCWarning(kLoggingCategory) << "Unknown register type."; continue; } for (int row = 0; row < model->rowCount(); row++) { QStandardItem *item = model->item(row, 1); quint16 value = item ? item->text().toInt(Q_NULLPTR, 16) : 0; auto table = static_cast(type); SAKModbusFactory::Instance()->SetServerData(modbus_device_, table, row, value, false); } } } quint8 SAKModbusUi::GetClientFunctionCode() { QString txt = ui->function_code_->currentText(); QStringList list = txt.split('-', Qt::SkipEmptyParts); if (list.length()) { return list.first().toInt(Q_NULLPTR, 16); } return 0; } QList SAKModbusUi::GetClientRegisterValue() { QList values; for (int row = 0; row < register_model_->rowCount(); row++) { QStandardItem *item = register_model_->item(row, 1); if (item) { QString text = item->text(); values.append(text.toInt(Q_NULLPTR, 16)); } else { values.append(0); } } return values; } QByteArray SAKModbusUi::GetClientPdu() { QString text = ui->pdu_->currentText(); #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) QStringList valueList = text.split(' ', QString::SkipEmptyParts); #else QStringList valueList = text.split(' ', Qt::SkipEmptyParts); #endif QByteArray data; for (QString &value : valueList) { data.append(char(value.toInt(Q_NULLPTR, 16))); } return data; } QTableView *SAKModbusUi::GetTableView(QModbusDataUnit::RegisterType table) { QWidget *tv = Q_NULLPTR; if (table == QModbusDataUnit::Coils) { tv = ui->server_registers_->widget(0); } else if (table == QModbusDataUnit::DiscreteInputs) { tv = ui->server_registers_->widget(1); } else if (table == QModbusDataUnit::InputRegisters) { tv = ui->server_registers_->widget(2); } else if (table == QModbusDataUnit::HoldingRegisters) { tv = ui->server_registers_->widget(3); } else { tv = ui->server_registers_->widget(3); qCWarning(kLoggingCategory) << "Get table view failed: unknow register type!"; } return qobject_cast(tv); } QList SAKModbusUi::GetTableValues(QTableView *table_view, int row, int count) { if (!table_view) { qCWarning(kLoggingCategory) << "Table view can not be null!"; return QList(); } auto *model = qobject_cast(table_view->model()); if (!model) { qCWarning(kLoggingCategory) << "Model can not be null!"; return QList(); } QList values; for (int i = row; i < count; i++) { QStandardItem *item = model->item(i, 1); if (item) { QString text = item->text(); values.append(text.toInt(Q_NULLPTR, 16)); } else { values.append(0); } } return values; } void SAKModbusUi::OutputModbusReply(QModbusReply *reply, int function_code) { if (reply->error() != QModbusDevice::NoError) { outputMessage(reply->errorString(), true, "", "error"); QMessageBox::warning(this, tr("Error occured"), reply->errorString()); return; } int server_address = reply->serverAddress(); if (reply->type() == QModbusReply::ReplyType::Raw) { QByteArray data = reply->rawResult().data(); QString info = QString("(from address: %1, " "function code: %2), " "data unit: %3") .arg(server_address) .arg(function_code) .arg(QString::fromLatin1(data.toHex(' '))); outputMessage(info, false, RXCOLOR, RXFLAG); } else if (reply->type() == QModbusReply::ReplyType::Common) { QString info = ui->function_code_->currentText(); outputMessage(info, false, RXCOLOR, RXFLAG); } } void SAKModbusUi::outputMessage(const QString &msg, bool isError, const QString &color, const QString &flag) { QString cookedMsg = QDateTime::currentDateTime().toString("hh:mm:ss.zzz"); cookedMsg = QString("%1 ").arg(cookedMsg); QString cookedColor = color; if (cookedColor.isEmpty()) { if (isError) { cookedColor = "red"; } else { cookedColor = "black"; } } cookedMsg += flag.isEmpty() ? "" : QString("[%2] ").arg(cookedColor, flag); cookedMsg += msg; ui->text_browser_->append(cookedMsg); } bool SAKModbusUi::IsConnected() { if (SAKModbusFactory::Instance()->IsConnected(modbus_device_)) { return true; } QMessageBox::warning(this, tr("Device is not Ready"), tr("The modbus device is not ready, " "please check settings and try again later!")); return false; } bool SAKModbusUi::WriteSettingsArray( const QString &group, const QString &key, const QString &value, int index, int max_index) { settings_->beginWriteArray(group); for (int i = 0; i < max_index; i++) { settings_->setArrayIndex(i); QString v = settings_->value(key).toString(); if (v == value) { settings_->endArray(); return false; } } settings_->setArrayIndex(index); settings_->setValue(key, value); settings_->endArray(); return true; }