9#include <QtCore/QScopeGuard>
10#include <QtCore/QScopedValueRollback>
11#include <QtCore/QStringList>
21 for (
const unsigned rate : rates) {
22 names.append(QString::number(rate));
24 return names.join(QStringLiteral(
", "));
33 , _previous(channel._operationDeadline)
40 _channel._operationDeadline = _previous;
43void GPSDeadlineScope::_limitUntil(uint64_t untilUs)
45 _channel._operationDeadline.
untilUs = std::min(_channel._operationDeadline.
untilUs, untilUs);
54 return _runtime.nowUs();
69 _errorDetail = detail;
73QString GPSCommandChannel::_commandFailure(
const QByteArray& label,
GPSCommandOutcome outcome)
const
77 return QStringLiteral(
"%1 receiver %2 '%3'")
86void GPSCommandChannel::_describeFailure()
92 _errorDetail = _commandFailure(_command.
command, _command.
outcome);
99 ? QStringLiteral(
"The %1 receiver link cannot run at the requested baud rate").arg(receiver)
100 : QStringLiteral(
"No %1 receiver answered at %2 baud").arg(receiver,
gpsRateList(rates)));
119void GPSCommandChannel::_resetError()
122 _errorDetail.clear();
138 if (_commandCompleted) {
139 _command = _failedAttempt(std::move(step));
150 _commandCompleted =
false;
155 if (_commandCompleted) {
159 _commandCompleted =
true;
161 const auto result = _command;
162 _runtime._commandFinished(result);
168 if (
failed() || !buffer.data() || buffer.empty()) {
173 _errorDetail = result.detail;
175 static_cast<size_t>(result.bytesRead) <= buffer.size()) {
176 return result.bytesRead;
183 qCWarning(
logCategory()).noquote() << QStringLiteral(
"Receiver read failed (status %1): %2")
184 .arg(
static_cast<int>(result.status))
190GPSReceiveUpdates GPSCommandChannel::_readAndDecode(std::chrono::milliseconds timeout)
192 std::array<uint8_t, READ_CHUNK_SIZE> buffer{};
193 const int count =
read(buffer, timeout);
197 return _runtime.
consume(std::span<const uint8_t>(buffer).first(
static_cast<size_t>(count)));
206 _errorDetail.clear();
210 const uint64_t deadline =
211 std::min(_operationDeadline.
untilUs, _commandCompleted ? UINT64_MAX : _commandDeadline.
untilUs);
221 : _runtime._io.
write({
reinterpret_cast<const uint8_t*
>(bytes.data()),
static_cast<size_t>(bytes.size())},
223 const auto length = bytes.size();
224 _errorDetail = result.detail;
226 if (!_commandCompleted) {
231 result.uncertainBytes() == 0) {
247 const QString command = _commandCompleted ? QString() : QString::fromUtf8(_command.
command);
248 if (command.isEmpty()) {
249 _errorDetail = QStringLiteral(
"Could not write to the %1 receiver").arg(receiver);
250 }
else if (result.acceptedBytes > 0) {
251 _errorDetail = QStringLiteral(
"%1 receiver did not accept the full '%2' command").arg(receiver, command);
253 _errorDetail = QStringLiteral(
"Could not write '%2' to the %1 receiver").arg(receiver, command);
266GPSBaudStatus GPSCommandChannel::_setBaudrate(
unsigned baudrate)
271 _errorDetail.clear();
278 _errorDetail = QStringLiteral(
"Cannot set the receiver link to %1 baud").arg(baudrate);
288 const auto now =
nowUs();
289 const auto remaining = _operationDeadline.
untilUs > now ? _operationDeadline.
untilUs - now : 0;
290 const auto untilDeadline = std::chrono::microseconds(std::min<uint64_t>(remaining, INT64_MAX));
291 if (!_runtime._io.
clock.
wait(std::min(duration, untilDeadline))) {
293 _errorDetail.clear();
306 if (_commandCompleted) {
310 const uint64_t deadline = _operationDeadline.
untilUs;
321 if (
nowUs() >= deadline) {
330void GPSCommandChannel::_clearReply()
337 ReplyMatcher matcher)
339 const auto guard = qScopeGuard([
this] { _clearReply(); });
340 _replyMatcher = std::move(matcher);
342 return _failedAttempt(std::move(step));
351void GPSCommandChannel::_offerText(std::string_view reply)
353 const auto* text = std::get_if<GPSTextMatcher>(&_replyMatcher);
354 if (text && *text && _replyPending()) {
355 _resolveReply((*text)(reply));
359void GPSCommandChannel::_offerFrame(
const GPSFrame& frame)
361 const auto* frameReply = std::get_if<GPSFrameMatcher>(&_replyMatcher);
362 if (frameReply && *frameReply && _replyPending()) {
363 _resolveReply((*frameReply)(frame));
367void GPSCommandChannel::_offerRaw(std::span<const uint8_t> bytes)
369 if (
auto*
const* raw = std::get_if<GPSRawAckMatcher*>(&_replyMatcher)) {
370 (*raw)->append(bytes);
371 _resolveReply((*raw)->outcome());
377 if (
const auto* raw = std::get_if<GPSCommandSequence::RawReply>(&command.
reply)) {
379 return _transact(command.
step, command.
wire, &matcher);
386 for (
size_t index = 0; index < sequence.
steps.size(); ++index) {
387 const auto& command = sequence.
steps[index];
389 for (
unsigned attempt = 0; attempt < (std::max) (command.
attempts, 1U); ++attempt) {
406 if (!result.succeeded()) {
409 return result.succeeded();
415 std::vector<unsigned> order;
417 order.push_back(baud);
419 order.assign(candidates.begin(), candidates.end());
420 if (_detectedBaud != 0) {
422 std::erase(order, _detectedBaud);
423 order.insert(order.begin(), _detectedBaud);
427 for (
const unsigned candidate : order) {
428 result.baud = candidate;
434 result.linkFailed =
true;
437 result.probed.push_back(candidate);
454 return GPSReceiveUpdates{};
457 GPSReceiveUpdates handled;
458 std::array<uint8_t, READ_CHUNK_SIZE> buffer{};
467 handled |= _runtime.
consume(std::span<const uint8_t>(buffer).first(
static_cast<size_t>(count)));
499 if (_servicingControls ||
failed()) {
503 const QScopedValueRollback servicing(_servicingControls,
true);
523 return _runtime._context;
QString gpsRateList(std::span< const unsigned > rates)
rates as a diagnostic names them, such as "38400, 9600".
@ Stop
The receiver answered but is not usable, so no other rate is tried.
QString gpsRateList(std::span< const unsigned > rates)
rates as a diagnostic names them, such as "38400, 9600".
GPSBaudStatus
How the link answered a baud rate change.
std::function< GPSBaudProbe()> GPSBaudProbeFunction
Probes the receiver at the rate the link was just set to.
std::function< GPSCommandOutcome()> GPSReplyPoll
Polled after each decoded chunk, for replies a family tracks in its own decoder state; Pending keeps ...
GPSProtocolError
Sticky failure of the current receiver session; I/O and control stop until the next configure().
@ InvalidArgument
The driver requested an invalid write, or received before configuring.
@ Protocol
The receiver violated or rejected the control protocol.
@ Cancelled
A stop was requested; not a receiver or link fault.
@ ConsentRequired
Configuration needs a persistent receiver change the caller did not allow.
@ Transport
The link could not read, write, or change its baud rate.
const QLoggingCategory &(*)() GPSLogCategory
A family's logging category function, as declared by Q_DECLARE_LOGGING_CATEGORY; usable as qCWarning(...
QString gpsReceiverName(GPSType type)
The name of type's receiver family as shown to users; empty when no descriptor exists.
bool isCancelled() const
Safe to call from any thread.
void wait(std::chrono::microseconds duration)
Waits for duration, capped by the operation deadline.
void beginCommand(GPSConfigurationStep step)
void serviceControls()
Runs the family's streaming services once under SERVICE_TIMEOUT, then flushes. Nested calls do nothin...
GPSLogCategory logCategory() const
GPSBaudDetection detectBaud(std::span< const unsigned > candidates, unsigned baud, GPSBaudProbeFunction probe)
GPSReceiveUpdates receiveCycle(std::chrono::milliseconds timeout)
int read(std::span< uint8_t > buffer, std::chrono::milliseconds timeout)
Reads raw bytes without decoding them.
void receiveFor(std::chrono::milliseconds duration)
Repeats the family's full streaming receive until duration has passed or I/O fails.
GPSDecodeContext & context()
bool setBaudrate(unsigned baudrate)
void failSequence(const GPSCommandSequence::Result &result)
void failControl(GPSProtocolError failure=GPSProtocolError::Protocol)
GPSDeadlineScope deadlineScope(std::chrono::milliseconds timeout)
bool receiveUntil(std::function< bool()> done, std::chrono::milliseconds duration)
static constexpr std::chrono::milliseconds SERVICE_TIMEOUT
Budget of the streaming services that run after each receive.
GPSConfigurationEvidence completeCommand(GPSCommandOutcome outcome)
Retires the current attempt and reports it; repeated completion returns the retained evidence unchang...
GPSReceiveUpdates flush()
Flushes decoder state and delivers the resulting batch.
GPSStreamDemux & stream()
bool write(QByteArrayView bytes)
bool runRequired(const GPSCommandSequence &sequence)
Runs sequence and fails control over a failed step as failSequence() does.
GPSCommandSequence::Result runSequence(const GPSCommandSequence &sequence)
Runs sequence in order until a required step fails or I/O fails; failed attempts retry up to their co...
GPSConfigurationEvidence awaitReply(GPSReplyPoll reply)
Decodes received traffic until reply resolves the current command, its deadline expires,...
void failNoAnswer(std::span< const unsigned > rates)
bool writeCommand(GPSConfigurationStep step, QByteArrayView bytes)
Starts one command attempt under its own deadline scope and writes it; awaitReply() waits for its rep...
GPSConfigurationEvidence transact(GPSConfigurationStep step, QByteArrayView bytes)
GPSCommandOutcome failureOutcome() const
The command outcome that reports the sticky failure; Pending while healthy.
void finishCommand(GPSCommandOutcome outcome)
GPSDeadlineScope(GPSCommandChannel &channel, std::chrono::milliseconds timeout)
virtual void serviceStreaming(GPSCommandChannel &channel)
virtual GPSReceiveUpdates receive(GPSCommandChannel &channel, std::chrono::milliseconds timeout)
One streaming receive. The default runs GPSCommandChannel::receiveCycle() and then the streaming serv...
virtual bool completeReceiveCycle(GPSReceiveUpdates handled)
virtual std::chrono::milliseconds nextReadSlice(std::chrono::milliseconds timeout) const
The longest single read in a receive cycle of timeout; the cycle deadline still bounds it.
GPSFamilyProtocol & protocol()
const GPSReceiverFamily & family() const
GPSReceiveUpdates consume(std::span< const uint8_t > bytes)
Decodes bytes and delivers their batch to the observer. An empty span flushes.
GPSStreamDemux & stream()
GPSLogCategory logCategory() const
The family's logging category, or the runtime's when the family has none.
Bounded raw-byte matching for receivers whose replies need not be complete ASCII lines.
unsigned baud
The rate that was found, or the last rate attempted.
std::function< bool(std::chrono::microseconds)> wait
Waits duration on this clock; false when the wait was cancelled. Empty means a real wait.
GPSConfigurationStep step
Evidence label and per-attempt timeout. A required command that fails ends the sequence.
std::variant< GPSTextMatcher, RawReply > reply
GPSCommandOutcome outcome
The failed command's last outcome.
std::optional< size_t > failedStep
The failed required step, or the step during which a sticky failure ended the sequence.
std::vector< Command > steps
Evidence from one configuration command, not a claim of physical receiver operation.
GPSCommandOutcome outcome
uint64_t startedAtUs
When the attempt began; its deadline counts from here.
std::chrono::milliseconds timeout
static GPSDeadline after(uint64_t nowUs, std::chrono::microseconds timeout)
A negative timeout is already due at nowUs.
std::chrono::milliseconds remaining(uint64_t nowUs) const
Rounded up to whole milliseconds and capped at INT32_MAX milliseconds.
std::function< GPSReadResult(std::span< uint8_t >, GPSDeadline)> read
GPSCancelToken cancelToken
Checked before each write and baud change; reads observe it in the transport.
std::function< GPSWriteResult(std::span< const uint8_t >, GPSDeadline)> write
GPSClock clock
Without a wait, the runtime sleeps in short slices until the duration elapses or cancelToken is cance...
std::function< GPSBaudStatus(unsigned)> setBaudrate