Dokit
Internal development documentation
Loading...
Searching...
No Matches
loggerfetchcommand.cpp
1// SPDX-FileCopyrightText: 2022-2026 Paul Colby <git@colby.id.au>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#include "loggerfetchcommand.h"
6
8
9#include <QDateTime>
10#include <QJsonDocument>
11#include <QJsonObject>
12
13#include <iostream>
14
15// Qt 6.5.0 added new QDateTime::fromSecsSinceEpoch() and fromMSecsSinceEpoch()
16// overloads, then Qt 6.6.0 deprecated some of of the older ones.
17#if (QT_VERSION < QT_VERSION_CHECK(6, 5, 0))
18 #define DOKIT_QT_UTC Qt::UTC
19#else
20 #include <QTimeZone>
21 #define DOKIT_QT_UTC QTimeZone::UTC
22#endif
23
25
26/*!
27 * \class LoggerFetchCommand
28 *
29 * The LoggerFetchCommand class implements the `logger` CLI command.
30 */
31
32/*!
33 * Construct a new LoggerFetchCommand object with \a parent.
34 */
39
40/*!
41 * \copybrief DeviceCommand::getService
42 *
43 * This override returns a pointer to a DataLoggerService object.
44 */
56
57/*!
58 * \copybrief DeviceCommand::serviceDetailsDiscovered
59 *
60 * This override fetches the current device's status, and outputs it in the selected format.
61 */
63{
64 DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
65 qCInfo(lc).noquote() << tr("Fetching logger samples...");
66 if (!service->enableMetadataNotifications()) {
67 qCCritical(lc).noquote() << tr("Failed to enable metadata notifications");
68 disconnect(EXIT_FAILURE);
69 return;
70 }
71 if (!service->enableReadingNotifications()) {
72 qCCritical(lc).noquote() << tr("Failed to enable reading notifications");
73 disconnect(EXIT_FAILURE);
74 return;
75 }
76 service->fetchSamples();
77}
78
79/*!
80 * Invoked when \a metadata has been received from the data logger.
81 */
83{
84 qCDebug(lc) << "status:" << (int)(data.status);
85 qCDebug(lc) << "scale:" << data.scale;
86 qCDebug(lc) << "mode:" << DataLoggerService::toString(data.mode) << (quint8)data.mode;
87 qCDebug(lc) << "range:" << service->toString(data.range, data.mode) << data.range;
88 qCDebug(lc) << "updateInterval:" << (int)data.updateInterval;
89 qCDebug(lc) << "numberOfSamples:" << data.numberOfSamples;
90 qCDebug(lc) << "timestamp:" << data.timestamp << QDateTime::fromSecsSinceEpoch(data.timestamp, DOKIT_QT_UTC);
91 this->metadata = data;
92 this->samplesToGo = data.numberOfSamples;
93 this->timestamp = (quint64)data.timestamp * (quint64)1000;
94 qCInfo(lc).noquote() << tr("Fetching %Ln logger sample/s...", nullptr, data.numberOfSamples);
95}
96
97/*!
98 * Outputs logger \a samples in the selected output format.
99 */
101{
102 QString unit;
103 switch (metadata.mode) {
104 case DataLoggerService::Mode::DcVoltage: unit = u"Vdc"_s; break;
105 case DataLoggerService::Mode::AcVoltage: unit = u"Vac"_s; break;
106 case DataLoggerService::Mode::DcCurrent: unit = u"Adc"_s; break;
107 case DataLoggerService::Mode::AcCurrent: unit = u"Aac"_s; break;
109 default:
110 qCDebug(lc).noquote() << tr(R"(No known unit for mode %1 "%2".)").arg((int)metadata.mode)
112 }
113 const QString range = service->toString(metadata.range, metadata.mode);
114
115 for (const qint16 &sample: samples) {
116 const QString timeString = (metadata.timestamp == 0) ? QString::number(timestamp)
118 const float value = sample * metadata.scale;
119 switch (format) {
121 for (; showCsvHeader; showCsvHeader = false) {
122 std::cout << qUtf8Printable(tr("timestamp,value,unit,range\n"));
123 }
124 std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4\n")
125 .arg(timeString).arg(value).arg(unit, range));
126 break;
127 case OutputFormat::Json: {
128 QJsonObject object{
129 { u"timestamp"_s, timeString },
130 { u"value"_s, value },
131 { u"unit"_s, unit },
132 { u"mode"_s, DataLoggerService::toString(metadata.mode) },
133 };
134 if (!range.isEmpty()) {
135 object.insert(u"range"_s, range);
136 }
137 std::cout << QJsonDocument(object).toJson().toStdString();
138 } break;
140 std::cout << qUtf8Printable(tr("%1 %2 %3\n").arg(timeString).arg(value).arg(unit));
141 break;
142 }
143 timestamp += metadata.updateInterval;
144 --samplesToGo;
145 }
146 if (samplesToGo <= 0) {
147 qCInfo(lc).noquote() << tr("Finished fetching %Ln sample/s (with %L1 remaining).",
148 nullptr, metadata.numberOfSamples).arg(samplesToGo);
149 if (device) disconnect(); // Will exit the application once disconnected.
150 }
151}
OutputFormat format
Selected output format.
@ Text
Plain unstructured text.
@ Csv
RFC 4180 compliant CSV text.
@ Json
RFC 8259 compliant JSON text.
The AbstractPokitService class provides a common base for Pokit services classes.
QVector< qint16 > Samples
Raw samples from the Reading characteristic.
static QString toString(const Mode &mode)
Returns mode as a user-friendly string.
void samplesRead(const DataLoggerService::Samples &samples)
This signal is emitted when the Reading characteristic has been notified.
void metadataRead(const DataLoggerService::Metadata &meta)
This signal is emitted when the Metadata characteristic has been read successfully.
@ DcVoltage
Measure DC voltage.
@ AcCurrent
Measure AC current.
@ AcVoltage
Measure AC voltage.
@ Temperature
Measure temperature.
@ DcCurrent
Measure DC current.
PokitDevice * device
Pokit Bluetooth device (if any) this command interacts with.
DeviceCommand(QObject *const parent=nullptr)
Construct a new DeviceCommand object with parent.
virtual void serviceDetailsDiscovered()
Handles service detail discovery events.
void disconnect(int exitCode=EXIT_SUCCESS)
Disconnects the underlying Pokit device, and sets exitCode to be return to the OS once the disconnect...
qint32 samplesToGo
Number of samples we're still expecting to receive.
DataLoggerService::Metadata metadata
Most recent data logging metadata.
DataLoggerService * service
Bluetooth service this command interacts with.
bool showCsvHeader
Whether or not to show a header as the first line of CSV output.
void outputSamples(const DataLoggerService::Samples &samples)
Outputs logger samples in the selected output format.
AbstractPokitService * getService() override
Returns a Pokit service object for the derived command class.
LoggerFetchCommand(QObject *const parent=nullptr)
Construct a new LoggerFetchCommand object with parent.
void serviceDetailsDiscovered() override
Handles service detail discovery events.
quint64 timestamp
Current sample's epoch milliseconds timestamp.
void metadataRead(const DataLoggerService::Metadata &data)
Invoked when metadata has been received from the data logger.
Declares the PokitDevice class.
std::string toStdString() const const
QDateTime fromMSecsSinceEpoch(qint64 msecs)
QDateTime fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offsetSeconds)
QString toString(Qt::DateFormat format) const const
QByteArray toJson() const const
QObject(QObject *parent)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString fromLatin1(const char *str, int size)
QString fromUtf8(const char *str, int size)
bool isEmpty() const const
QString number(int n, int base)
ISODateWithMs
Declares the DOKIT_USE_STRINGLITERALS macro, and related functions.
#define DOKIT_USE_STRINGLITERALS
Internal macro for using either official Qt string literals (added in Qt 6.4), or our own equivalent ...
Attributes included in the Metadata characteristic.
quint16 numberOfSamples
Number of samples acquired (1 to 6192).
quint32 timestamp
Timestamp stored at the beginning of the logging session.
float scale
Scale to apply to read samples.
LoggerStatus status
Current data logger status.
quint32 updateInterval
Current logging interval in milliseconds.
Mode mode
Current operation mode.