|
|
@@ -16,6 +16,7 @@ |
|
|
namespace { |
|
|
namespace { |
|
|
|
|
|
|
|
|
constexpr int kMaximumReadCount = 120; |
|
|
constexpr int kMaximumReadCount = 120; |
|
|
|
|
|
constexpr int kRecoveryProbeIntervalMs = 2000; |
|
|
|
|
|
|
|
|
std::string toUtf8(const QString &value) |
|
|
std::string toUtf8(const QString &value) |
|
|
{ |
|
|
{ |
|
|
@@ -29,6 +30,18 @@ QModbusDataUnit::RegisterType registerType(RegisterArea area) |
|
|
? QModbusDataUnit::Coils : QModbusDataUnit::HoldingRegisters; |
|
|
? QModbusDataUnit::Coils : QModbusDataUnit::HoldingRegisters; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool isRecoverableTimeout(PlcCommunicationError error) |
|
|
|
|
|
{ |
|
|
|
|
|
return error == PlcCommunicationError::PlcNotResponding |
|
|
|
|
|
|| error == PlcCommunicationError::CommunicationTimeout; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool isReadingState(PlcConnectionState state) |
|
|
|
|
|
{ |
|
|
|
|
|
return state == PlcConnectionState::Connected |
|
|
|
|
|
|| state == PlcConnectionState::Recovering; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
} // namespace |
|
|
} // namespace |
|
|
|
|
|
|
|
|
PlcCommunicationService::PlcCommunicationService( |
|
|
PlcCommunicationService::PlcCommunicationService( |
|
|
@@ -48,6 +61,12 @@ PlcCommunicationService::PlcCommunicationService( |
|
|
return sendWordWrite(address, value); |
|
|
return sendWordWrite(address, value); |
|
|
}); |
|
|
}); |
|
|
connect(&poll_timer_, &QTimer::timeout, this, &PlcCommunicationService::pollNextBlock); |
|
|
connect(&poll_timer_, &QTimer::timeout, this, &PlcCommunicationService::pollNextBlock); |
|
|
|
|
|
recovery_timer_.setSingleShot(true); |
|
|
|
|
|
connect( |
|
|
|
|
|
&recovery_timer_, |
|
|
|
|
|
&QTimer::timeout, |
|
|
|
|
|
this, |
|
|
|
|
|
&PlcCommunicationService::probeRecovery); |
|
|
connect(master_.get(), &QModbusClient::stateChanged, |
|
|
connect(master_.get(), &QModbusClient::stateChanged, |
|
|
this, |
|
|
this, |
|
|
[this](QModbusDevice::State device_state) |
|
|
[this](QModbusDevice::State device_state) |
|
|
@@ -268,7 +287,7 @@ void PlcCommunicationService::applyPollAddresses( |
|
|
rebuildPollBlocks(); |
|
|
rebuildPollBlocks(); |
|
|
poll_update_pending_ = false; |
|
|
poll_update_pending_ = false; |
|
|
pending_poll_addresses_.clear(); |
|
|
pending_poll_addresses_.clear(); |
|
|
if (state_ == PlcConnectionState::Connected && pending_reply_ == nullptr) |
|
|
|
|
|
|
|
|
if (isReadingState(state_) && pending_reply_ == nullptr) |
|
|
{ |
|
|
{ |
|
|
pollNextBlock(); |
|
|
pollNextBlock(); |
|
|
} |
|
|
} |
|
|
@@ -276,7 +295,7 @@ void PlcCommunicationService::applyPollAddresses( |
|
|
|
|
|
|
|
|
void PlcCommunicationService::pollNextBlock() |
|
|
void PlcCommunicationService::pollNextBlock() |
|
|
{ |
|
|
{ |
|
|
if (state_ != PlcConnectionState::Connected |
|
|
|
|
|
|
|
|
if (!isReadingState(state_) |
|
|
|| pending_reply_ != nullptr || poll_blocks_.empty()) |
|
|
|| pending_reply_ != nullptr || poll_blocks_.empty()) |
|
|
{ |
|
|
{ |
|
|
return; |
|
|
return; |
|
|
@@ -304,7 +323,7 @@ void PlcCommunicationService::pollNextBlock() |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
handleReadFinished(reply, block); |
|
|
handleReadFinished(reply, block); |
|
|
if (state_ == PlcConnectionState::Connected |
|
|
|
|
|
|
|
|
if (isReadingState(state_) |
|
|
&& reply->error() == QModbusDevice::NoError |
|
|
&& reply->error() == QModbusDevice::NoError |
|
|
&& !poll_update_pending_ |
|
|
&& !poll_update_pending_ |
|
|
&& block_index < initial_blocks_read_.size()) |
|
|
&& block_index < initial_blocks_read_.size()) |
|
|
@@ -317,6 +336,10 @@ void PlcCommunicationService::pollNextBlock() |
|
|
if (completed && !initial_read_completed_) |
|
|
if (completed && !initial_read_completed_) |
|
|
{ |
|
|
{ |
|
|
updateInitialReadCompleted(true); |
|
|
updateInitialReadCompleted(true); |
|
|
|
|
|
if (state_ == PlcConnectionState::Recovering) |
|
|
|
|
|
{ |
|
|
|
|
|
setState(PlcConnectionState::Connected); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
if (pending_reply_ == reply) |
|
|
if (pending_reply_ == reply) |
|
|
@@ -332,9 +355,104 @@ void PlcCommunicationService::pollNextBlock() |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void PlcCommunicationService::probeRecovery() |
|
|
|
|
|
{ |
|
|
|
|
|
if (state_ != PlcConnectionState::Faulted |
|
|
|
|
|
|| !isRecoverableTimeout(last_error_type_)) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
if (master_->state() != QModbusDevice::ConnectedState) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
if (pending_reply_ != nullptr) |
|
|
|
|
|
{ |
|
|
|
|
|
recovery_timer_.start(kRecoveryProbeIntervalMs); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
if (poll_blocks_.empty()) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
const PollBlock block = poll_blocks_.front(); |
|
|
|
|
|
const QModbusDataUnit request( |
|
|
|
|
|
registerType(block.area), block.startAddress, 1); |
|
|
|
|
|
QModbusReply *reply = master_->sendReadRequest(request, configuration_.serverAddress); |
|
|
|
|
|
if (reply == nullptr) |
|
|
|
|
|
{ |
|
|
|
|
|
handleRecoveryProbeFailure(master_->error()); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
pending_reply_ = reply; |
|
|
|
|
|
const std::uint64_t generation = connection_generation_; |
|
|
|
|
|
connect(reply, &QModbusReply::finished, |
|
|
|
|
|
this, |
|
|
|
|
|
[this, reply, generation] |
|
|
|
|
|
{ |
|
|
|
|
|
if (generation != connection_generation_) |
|
|
|
|
|
{ |
|
|
|
|
|
reply->deleteLater(); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
if (pending_reply_ == reply) |
|
|
|
|
|
{ |
|
|
|
|
|
pending_reply_ = nullptr; |
|
|
|
|
|
} |
|
|
|
|
|
const QModbusDevice::Error error = reply->error(); |
|
|
|
|
|
reply->deleteLater(); |
|
|
|
|
|
if (poll_update_pending_) |
|
|
|
|
|
{ |
|
|
|
|
|
const std::vector<RegisterAddress> addresses = pending_poll_addresses_; |
|
|
|
|
|
applyPollAddresses(addresses); |
|
|
|
|
|
} |
|
|
|
|
|
if (state_ != PlcConnectionState::Faulted |
|
|
|
|
|
|| !isRecoverableTimeout(last_error_type_)) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
if (error != QModbusDevice::NoError) |
|
|
|
|
|
{ |
|
|
|
|
|
handleRecoveryProbeFailure(error); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
restoreCommunication(); |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void PlcCommunicationService::handleRecoveryProbeFailure(QModbusDevice::Error error) |
|
|
|
|
|
{ |
|
|
|
|
|
if (state_ != PlcConnectionState::Faulted |
|
|
|
|
|
|| !isRecoverableTimeout(last_error_type_)) |
|
|
|
|
|
{ |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
if (error == QModbusDevice::ConnectionError) |
|
|
|
|
|
{ |
|
|
|
|
|
const PlcCommunicationErrorContext context{ |
|
|
|
|
|
QString::fromStdString(configuration_.portName), |
|
|
|
|
|
serial_session_opened_, |
|
|
|
|
|
received_valid_response_}; |
|
|
|
|
|
setError(classifyPlcCommunicationError(error, context)); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
recovery_timer_.start(kRecoveryProbeIntervalMs); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void PlcCommunicationService::restoreCommunication() |
|
|
|
|
|
{ |
|
|
|
|
|
received_valid_response_ = true; |
|
|
|
|
|
last_error_type_ = PlcCommunicationError::None; |
|
|
|
|
|
last_error_.clear(); |
|
|
|
|
|
rebuildPollBlocks(); |
|
|
|
|
|
setState(PlcConnectionState::Recovering); |
|
|
|
|
|
poll_timer_.start(configuration_.pollIntervalMs); |
|
|
|
|
|
pollNextBlock(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
void PlcCommunicationService::handleReadFinished(QModbusReply *reply, PollBlock block) |
|
|
void PlcCommunicationService::handleReadFinished(QModbusReply *reply, PollBlock block) |
|
|
{ |
|
|
{ |
|
|
if (state_ != PlcConnectionState::Connected) |
|
|
|
|
|
|
|
|
if (!isReadingState(state_)) |
|
|
{ |
|
|
{ |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
@@ -481,6 +599,7 @@ void PlcCommunicationService::closeSerialSession() |
|
|
++connection_generation_; |
|
|
++connection_generation_; |
|
|
disconnecting_ = true; |
|
|
disconnecting_ = true; |
|
|
poll_timer_.stop(); |
|
|
poll_timer_.stop(); |
|
|
|
|
|
recovery_timer_.stop(); |
|
|
pending_reply_ = nullptr; |
|
|
pending_reply_ = nullptr; |
|
|
poll_update_pending_ = false; |
|
|
poll_update_pending_ = false; |
|
|
pending_poll_addresses_.clear(); |
|
|
pending_poll_addresses_.clear(); |
|
|
@@ -512,6 +631,7 @@ void PlcCommunicationService::setError(const PlcCommunicationFailure &failure) |
|
|
last_error_type_ = failure.type; |
|
|
last_error_type_ = failure.type; |
|
|
last_error_ = toUtf8(failure.message); |
|
|
last_error_ = toUtf8(failure.message); |
|
|
poll_timer_.stop(); |
|
|
poll_timer_.stop(); |
|
|
|
|
|
recovery_timer_.stop(); |
|
|
updateInitialReadCompleted(false); |
|
|
updateInitialReadCompleted(false); |
|
|
const bool disconnected = failure.type == PlcCommunicationError::SerialPortOpenFailed |
|
|
const bool disconnected = failure.type == PlcCommunicationError::SerialPortOpenFailed |
|
|
|| failure.type == PlcCommunicationError::SerialConnectionLost |
|
|
|| failure.type == PlcCommunicationError::SerialConnectionLost |
|
|
@@ -527,4 +647,9 @@ void PlcCommunicationService::setError(const PlcCommunicationFailure &failure) |
|
|
{ |
|
|
{ |
|
|
error_reported_callback_(last_error_); |
|
|
error_reported_callback_(last_error_); |
|
|
} |
|
|
} |
|
|
|
|
|
if (isRecoverableTimeout(failure.type) |
|
|
|
|
|
&& master_->state() == QModbusDevice::ConnectedState) |
|
|
|
|
|
{ |
|
|
|
|
|
recovery_timer_.start(kRecoveryProbeIntervalMs); |
|
|
|
|
|
} |
|
|
} |
|
|
} |