#include "plc_communication_service.h" #include "plc_register_repository.h" #include #include #include #include #include #include #include #include namespace { constexpr int kMaximumReadCount = 120; std::string toUtf8(const QString &value) { const QByteArray bytes = value.toUtf8(); return std::string(bytes.constData(), static_cast(bytes.size())); } QModbusDataUnit::RegisterType registerType(RegisterArea area) { return area == RegisterArea::M ? QModbusDataUnit::Coils : QModbusDataUnit::HoldingRegisters; } QString modbusErrorText(QModbusDevice::Error error) { switch (error) { case QModbusDevice::ReadError: return QStringLiteral("读取 PLC 数据失败"); case QModbusDevice::WriteError: return QStringLiteral("写入 PLC 数据失败"); case QModbusDevice::ConnectionError: return QStringLiteral("PLC 串口连接失败"); case QModbusDevice::ConfigurationError: return QStringLiteral("PLC 串口参数配置错误"); case QModbusDevice::TimeoutError: return QStringLiteral("PLC 通信超时,请检查站号、串口参数和 RS-485 接线"); case QModbusDevice::ProtocolError: return QStringLiteral("PLC 返回了无效或异常的 Modbus 响应"); case QModbusDevice::ReplyAbortedError: return QStringLiteral("PLC 通信请求已取消"); case QModbusDevice::UnknownError: return QStringLiteral("PLC 通信发生未知错误"); case QModbusDevice::NoError: default: return QStringLiteral("PLC 通信失败"); } } } // namespace PlcCommunicationService::PlcCommunicationService( PlcRegisterRepository &repository, QObject *parent) : QObject(parent), repository_(repository), master_(std::make_unique()) { repository_.setWriteHandlers( [this](const RegisterAddress &address, bool value) { return sendBitWrite(address, value); }, [this](const RegisterAddress &address, std::int16_t value) { return sendWordWrite(address, value); }); connect(&poll_timer_, &QTimer::timeout, this, &PlcCommunicationService::pollNextBlock); connect(master_.get(), &QModbusClient::stateChanged, this, [this](QModbusDevice::State device_state) { if (device_state == QModbusDevice::ConnectedState) { setState(PlcConnectionState::Connected); poll_timer_.start(configuration_.pollIntervalMs); pollNextBlock(); } else if (device_state == QModbusDevice::ConnectingState) { setState(PlcConnectionState::Connecting); } else if (device_state == QModbusDevice::UnconnectedState && state_ != PlcConnectionState::Faulted) { setState(PlcConnectionState::Disconnected); } }); connect(master_.get(), &QModbusClient::errorOccurred, this, [this](QModbusDevice::Error error) { if (error != QModbusDevice::NoError) { setError(modbusErrorText(error)); } }); } PlcCommunicationService::~PlcCommunicationService() = default; PlcCommunicationResult PlcCommunicationService::connectDevice( const PlcSerialConfiguration &configuration) { if (QString::fromStdString(configuration.portName).trimmed().isEmpty() || configuration.serverAddress < 1 || configuration.serverAddress > 247) { return {false, "必须填写串口端口,并将 PLC 站号设置为 1~247"}; } if (master_->state() != QModbusDevice::UnconnectedState) { return {false, "PLC 连接已经启动,请先断开当前连接"}; } configuration_ = configuration; last_error_.clear(); master_->setConnectionParameter( QModbusDevice::SerialPortNameParameter, QString::fromStdString(configuration.portName)); master_->setConnectionParameter( QModbusDevice::SerialBaudRateParameter, configuration.baudRate); master_->setConnectionParameter( QModbusDevice::SerialDataBitsParameter, static_cast(configuration.dataBits)); master_->setConnectionParameter( QModbusDevice::SerialParityParameter, static_cast(configuration.parity)); master_->setConnectionParameter( QModbusDevice::SerialStopBitsParameter, static_cast(configuration.stopBits)); master_->setTimeout(configuration.responseTimeoutMs); master_->setNumberOfRetries(configuration.retries); repository_.invalidate(); initial_read_completed_ = false; emit initialReadCompletedChanged(false); if (initial_read_changed_callback_) { initial_read_changed_callback_(false); } rebuildPollBlocks(); setState(PlcConnectionState::Connecting); if (!master_->connectDevice()) { setError(modbusErrorText(master_->error())); return {false, last_error_}; } return {true, {}}; } void PlcCommunicationService::disconnectDevice() { poll_timer_.stop(); master_->disconnectDevice(); repository_.invalidate(); initial_read_completed_ = false; emit initialReadCompletedChanged(false); if (initial_read_changed_callback_) { initial_read_changed_callback_(false); } setState(PlcConnectionState::Disconnected); } void PlcCommunicationService::setPollAddresses( const std::vector &addresses) { poll_addresses_ = addresses; rebuildPollBlocks(); } PlcConnectionState PlcCommunicationService::state() const { return state_; } bool PlcCommunicationService::initialReadCompleted() const { return initial_read_completed_; } const std::string &PlcCommunicationService::lastError() const { return last_error_; } const PlcSerialConfiguration &PlcCommunicationService::configuration() const { return configuration_; } void PlcCommunicationService::setCallbacks( std::function state_changed, std::function initial_read_changed, std::function cache_updated, std::function error_reported) { state_changed_callback_ = std::move(state_changed); initial_read_changed_callback_ = std::move(initial_read_changed); cache_updated_callback_ = std::move(cache_updated); error_reported_callback_ = std::move(error_reported); } void PlcCommunicationService::rebuildPollBlocks() { std::vector addresses = poll_addresses_; if (addresses.empty()) { addresses = { RegisterAddress{RegisterArea::M, 0}, RegisterAddress{RegisterArea::D, 0}}; } std::sort( addresses.begin(), addresses.end(), [](const RegisterAddress &left, const RegisterAddress &right) { if (left.area() != right.area()) { return left.area() == RegisterArea::M; } return left.index() < right.index(); }); addresses.erase(std::unique(addresses.begin(), addresses.end()), addresses.end()); poll_blocks_.clear(); for (const RegisterAddress &address : addresses) { if (!address.isValid()) { continue; } if (poll_blocks_.empty() || poll_blocks_.back().area != address.area() || address.index() > poll_blocks_.back().startAddress + poll_blocks_.back().count || poll_blocks_.back().count >= kMaximumReadCount) { poll_blocks_.push_back({address.area(), address.index(), 1}); } else { poll_blocks_.back().count = address.index() - poll_blocks_.back().startAddress + 1; } } next_poll_block_ = 0; initial_blocks_read_.assign(poll_blocks_.size(), false); initial_read_completed_ = false; } void PlcCommunicationService::pollNextBlock() { if (state_ != PlcConnectionState::Connected || pending_reply_ != nullptr || poll_blocks_.empty()) { return; } const std::size_t block_index = next_poll_block_; const PollBlock block = poll_blocks_.at(block_index); next_poll_block_ = (next_poll_block_ + 1U) % poll_blocks_.size(); QModbusDataUnit request( registerType(block.area), block.startAddress, static_cast(block.count)); QModbusReply *reply = master_->sendReadRequest(request, configuration_.serverAddress); if (reply == nullptr) { setError(modbusErrorText(master_->error())); return; } pending_reply_ = reply; connect(reply, &QModbusReply::finished, this, [this, reply, block, block_index] { handleReadFinished(reply, block); if (state_ == PlcConnectionState::Connected && reply->error() == QModbusDevice::NoError && block_index < initial_blocks_read_.size()) { initial_blocks_read_[block_index] = true; const bool completed = std::all_of( initial_blocks_read_.cbegin(), initial_blocks_read_.cend(), [](bool read) { return read; }); if (completed && !initial_read_completed_) { initial_read_completed_ = true; emit initialReadCompletedChanged(true); if (initial_read_changed_callback_) { initial_read_changed_callback_(true); } } } if (pending_reply_ == reply) { pending_reply_ = nullptr; } reply->deleteLater(); }); } void PlcCommunicationService::handleReadFinished(QModbusReply *reply, PollBlock block) { if (state_ != PlcConnectionState::Connected) { return; } if (reply->error() != QModbusDevice::NoError) { setError(modbusErrorText(reply->error())); return; } const QModbusDataUnit result = reply->result(); for (uint index = 0; index < result.valueCount(); ++index) { const int address = block.startAddress + static_cast(index); if (block.area == RegisterArea::M) { repository_.updateBit(address, result.value(index) != 0U); } else { repository_.updateWord(address, static_cast(result.value(index))); } } last_error_.clear(); emit cacheUpdated(); if (cache_updated_callback_) { cache_updated_callback_(); } } RegisterWriteResult PlcCommunicationService::sendBitWrite( const RegisterAddress &address, bool value) { if (state_ != PlcConnectionState::Connected) { return {false, RegisterError::Unavailable}; } QModbusDataUnit unit(QModbusDataUnit::Coils, address.index(), 1); unit.setValue(0, value ? 1U : 0U); QModbusReply *reply = master_->sendWriteRequest(unit, configuration_.serverAddress); if (reply == nullptr) { setError(modbusErrorText(master_->error())); return {false, RegisterError::WriteRejected}; } connect(reply, &QModbusReply::finished, this, [this, reply] { if (reply->error() != QModbusDevice::NoError) { setError(modbusErrorText(reply->error())); } reply->deleteLater(); }); return {true, RegisterError::None}; } RegisterWriteResult PlcCommunicationService::sendWordWrite( const RegisterAddress &address, std::int16_t value) { if (state_ != PlcConnectionState::Connected) { return {false, RegisterError::Unavailable}; } QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, address.index(), 1); unit.setValue(0, static_cast(value)); QModbusReply *reply = master_->sendWriteRequest(unit, configuration_.serverAddress); if (reply == nullptr) { setError(modbusErrorText(master_->error())); return {false, RegisterError::WriteRejected}; } connect(reply, &QModbusReply::finished, this, [this, reply] { if (reply->error() != QModbusDevice::NoError) { setError(modbusErrorText(reply->error())); } reply->deleteLater(); }); return {true, RegisterError::None}; } void PlcCommunicationService::setState(PlcConnectionState state) { if (state_ == state) { return; } state_ = state; emit stateChanged(); if (state_changed_callback_) { state_changed_callback_(); } } void PlcCommunicationService::setError(const QString &message) { last_error_ = toUtf8(message); poll_timer_.stop(); setState(PlcConnectionState::Faulted); emit communicationError(message); if (error_reported_callback_) { error_reported_callback_(last_error_); } }