Dokit
Internal development documentation
Loading...
Searching...
No Matches
dsocommand.cpp
1// SPDX-FileCopyrightText: 2022-2024 Paul Colby <git@colby.id.au>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#include "dsocommand.h"
5
7
8#include <QJsonDocument>
9#include <QJsonObject>
10
11#include <iostream>
12
13/*!
14 * \class DsoCommand
15 *
16 * The DsoCommand class implements the `dso` CLI command.
17 */
18
19/*!
20 * Construct a new DsoCommand object with \a parent.
21 */
23{
24
25}
26
28{
30 QLatin1String("mode"),
31 QLatin1String("range"),
32 };
33}
34
36{
38 QLatin1String("interval"),
39 QLatin1String("samples"),
40 QLatin1String("trigger-level"),
41 QLatin1String("trigger-mode"),
42 };
43}
44
45/*!
46 * \copybrief DeviceCommand::processOptions
47 *
48 * This implementation extends DeviceCommand::processOptions to process additional CLI options
49 * supported (or required) by this command.
50 */
52{
54 if (!errors.isEmpty()) {
55 return errors;
56 }
57
58 // Parse the (required) mode option.
59 const QString mode = parser.value(QLatin1String("mode")).trimmed().toLower();
60 if (mode.startsWith(QLatin1String("ac v")) || mode.startsWith(QLatin1String("vac"))) {
62 } else if (mode.startsWith(QLatin1String("dc v")) || mode.startsWith(QLatin1String("vdc"))) {
64 } else if (mode.startsWith(QLatin1String("ac c")) || mode.startsWith(QLatin1String("aac"))) {
66 } else if (mode.startsWith(QLatin1String("dc c")) || mode.startsWith(QLatin1String("adc"))) {
68 } else {
69 errors.append(tr("Unknown DSO mode: %1").arg(parser.value(QLatin1String("mode"))));
70 return errors;
71 }
72
73 // Parse the (required) range option.
74 QString unit;
75 {
76 const QString value = parser.value(QLatin1String("range"));
77 quint32 sensibleMinimum = 0;
78 switch (settings.mode) {
80 Q_ASSERT(false); // Not possible, since the mode parsing above never allows Idle.
81 break;
85 unit = QLatin1String("V");
86 sensibleMinimum = 50; // mV.
87 break;
91 unit = QLatin1String("A");
92 sensibleMinimum = 5; // mA.
93 break;
94 }
95 Q_ASSERT(!unit.isEmpty());
96 rangeOptionValue = parseNumber<std::milli>(value, unit, sensibleMinimum);
97 if (rangeOptionValue == 0) {
98 errors.append(tr("Invalid range value: %1").arg(value));
99 }
100 }
101
102 // Parse the trigger-level option.
103 if (parser.isSet(QLatin1String("trigger-level"))) {
104 const QString value = parser.value(QLatin1String("trigger-level"));
105 const quint32 level = parseNumber<std::micro>(value, unit);
106 if (level == 0) {
107 errors.append(tr("Invalid trigger-level value: %1").arg(value));
108 } else {
109 settings.triggerLevel = (float)(level/1'000'000.0);
110 }
111 }
112
113 // Parse the trigger-mode option.
114 if (parser.isSet(QLatin1String("trigger-mode"))) {
115 const QString triggerMode = parser.value(QLatin1String("trigger-mode")).trimmed().toLower();
116 if (triggerMode.startsWith(QLatin1String("free"))) {
118 } else if (triggerMode.startsWith(QLatin1String("ris"))) {
120 } else if (triggerMode.startsWith(QLatin1String("fall"))) {
122 } else {
123 errors.append(tr("Unknown trigger mode: %1").arg(
124 parser.value(QLatin1String("trigger-mode"))));
125 }
126 }
127
128 // Ensure that if either trigger option is present, then both are.
129 if (parser.isSet(QLatin1String("trigger-level")) !=
130 parser.isSet(QLatin1String("trigger-mode"))) {
131 errors.append(tr("If either option is provided, then both must be: trigger-level, trigger-mode"));
132 }
133
134 // Parse the interval option.
135 if (parser.isSet(QLatin1String("interval"))) {
136 const QString value = parser.value(QLatin1String("interval"));
137 const quint32 interval = parseNumber<std::micro>(value, QLatin1String("s"), 500'000);
138 if (interval == 0) {
139 errors.append(tr("Invalid interval value: %1").arg(value));
140 } else {
141 settings.samplingWindow = interval;
142 }
143 }
144
145 // Parse the samples option.
146 if (parser.isSet(QLatin1String("samples"))) {
147 const QString value = parser.value(QLatin1String("samples"));
148 const quint32 samples = parseNumber<std::ratio<1>>(value, QLatin1String("S"));
149 if (samples == 0) {
150 errors.append(tr("Invalid samples value: %1").arg(value));
151 } else if (samples > std::numeric_limits<quint16>::max()) {
152 errors.append(tr("Samples value (%1) must be no greater than %2")
153 .arg(value).arg(std::numeric_limits<quint16>::max()));
154 } else {
155 if (samples > 8192) {
156 qCWarning(lc).noquote() << tr("Pokit devices do not officially support great than 8192 samples");
157 }
158 settings.numberOfSamples = (quint16)samples;
159 }
160 }
161 return errors;
162}
163
164/*!
165 * \copybrief DeviceCommand::getService
166 *
167 * This override returns a pointer to a DsoService object.
168 */
170{
171 Q_ASSERT(device);
172 if (!service) {
173 service = device->dso();
174 Q_ASSERT(service);
177 }
178 return service;
179}
180
181/*!
182 * \copybrief DeviceCommand::serviceDetailsDiscovered
183 *
184 * This override fetches the current device's status, and outputs it in the selected format.
185 */
187{
188 DeviceCommand::serviceDetailsDiscovered(); // Just logs consistently.
191 qCInfo(lc).noquote() << tr("Sampling %1, with range %2, %Ln sample/s over %L3us", nullptr, settings.numberOfSamples)
192 .arg(DsoService::toString(settings.mode), (range.isNull()) ? QString::fromLatin1("N/A") : range)
195}
196
197/*!
198 * \var DsoCommand::minRangeFunc
199 *
200 * Pointer to function for converting #rangeOptionValue to a Pokit device's range enumerator. This function pointer
201 * is assigned during the command line parsing, but is not invoked until after the device's services are discovere,
202 * because prior to that discovery, we don't know which product (Meter vs Pro vs Clamp, etc) we're talking to and thus
203 * which enumerator list to be using.
204 *
205 * If the current mode does not support ranges (eg diode, and continuity modes), then this member will be \c nullptr.
206 *
207 * \see processOptions
208 * \see serviceDetailsDiscovered
209 */
210
211/*!
212 * Invoked when the DSO settings have been written.
213 */
215{
216 Q_ASSERT(service);
217 qCDebug(lc).noquote() << tr("Settings written; DSO has started.");
222}
223
224/*!
225 * Invoked when \a metadata has been received from the DSO.
226 */
228{
229 qCDebug(lc) << "status:" << (int)(data.status);
230 qCDebug(lc) << "scale:" << data.scale;
231 qCDebug(lc) << "mode:" << DsoService::toString(data.mode);
232 qCDebug(lc) << "range:" << service->toString(data.range, data.mode);
233 qCDebug(lc) << "samplingWindow:" << (int)data.samplingWindow;
234 qCDebug(lc) << "numberOfSamples:" << data.numberOfSamples;
235 qCDebug(lc) << "samplingRate:" << data.samplingRate << "Hz";
236 this->metadata = data;
237 this->samplesToGo = data.numberOfSamples;
238}
239
240/*!
241 * Outputs DSO \a samples in the selected ouput format.
242 */
244{
245 QString unit;
246 switch (metadata.mode) {
247 case DsoService::Mode::DcVoltage: unit = QLatin1String("Vdc"); break;
248 case DsoService::Mode::AcVoltage: unit = QLatin1String("Vac"); break;
249 case DsoService::Mode::DcCurrent: unit = QLatin1String("Adc"); break;
250 case DsoService::Mode::AcCurrent: unit = QLatin1String("Aac"); break;
251 default:
252 qCDebug(lc).noquote() << tr(R"(No known unit for mode %1 "%2".)").arg((int)metadata.mode)
254 }
256
257 for (const qint16 &sample: samples) {
258 static int sampleNumber = 0; ++sampleNumber;
259 const float value = sample * metadata.scale;
260 switch (format) {
262 for (; showCsvHeader; showCsvHeader = false) {
263 std::cout << qUtf8Printable(tr("sample_number,value,unit,range\n"));
264 }
265 std::cout << qUtf8Printable(QString::fromLatin1("%1,%2,%3,%4\n")
266 .arg(sampleNumber).arg(value).arg(unit, range));
267 break;
269 std::cout << QJsonDocument(QJsonObject{
270 { QLatin1String("value"), value },
271 { QLatin1String("unit"), unit },
272 { QLatin1String("range"), range },
274 }).toJson().toStdString();
275 break;
277 std::cout << qUtf8Printable(tr("%1 %2 %3\n").arg(sampleNumber).arg(value).arg(unit));
278 break;
279 }
280 --samplesToGo;
281 }
282 if (samplesToGo <= 0) {
283 qCInfo(lc).noquote() << tr("Finished fetching %Ln sample/s (with %L2 to remaining).",
285 if (device) disconnect(); // Will exit the application once disconnected.
286 }
287}
virtual QStringList supportedOptions(const QCommandLineParser &parser) const
Returns a list of CLI option names supported by this command.
OutputFormat format
Selected output format.
@ Text
Plain unstructured text.
@ Csv
RFC 4180 compliant CSV text.
@ Json
RFC 8259 compliant JSON text.
virtual QStringList processOptions(const QCommandLineParser &parser)
Processes the relevant options from the command line parser.
virtual QStringList requiredOptions(const QCommandLineParser &parser) const
Returns a list of CLI option names required by this command.
The AbstractPokitService class provides a common base for Pokit services classes.
std::optional< PokitProduct > pokitProduct() const
Returns the Pokit product this service is attached to.
The AbstractCommand class extends AbstractCommand to add a PokitDevice instance.
PokitDevice * device
Pokit Bluetooth device (if any) this command interracts with.
virtual void serviceDetailsDiscovered()
Handles service detail discovery events.
static quint8 minVoltageRange(const PokitProduct product, const quint32 maxValue)
t Returns the product's lowest voltage range that can measure at least up to maxValue (mV),...
static quint8 minCurrentRange(const PokitProduct product, const quint32 maxValue)
Returns the product's lowest current range that can measure at least up to maxValue (µA),...
void disconnect(int exitCode=EXIT_SUCCESS)
Disconnects the underlying Pokit device, and sets exitCode to be return to the OS once the disconnect...
DsoService::Settings settings
Settings for the Pokit device's DSO mode.
Definition dsocommand.h:33
qint32 samplesToGo
Number of samples we're expecting in the current window.
Definition dsocommand.h:38
bool showCsvHeader
Whether or not to show a header as the first line of CSV output.
Definition dsocommand.h:39
void serviceDetailsDiscovered() override
Handles service detail discovery events.
void outputSamples(const DsoService::Samples &samples)
Outputs DSO samples in the selected ouput format.
quint8(* minRangeFunc)(const PokitProduct product, const quint32 maxValue)
Pointer to function for converting rangeOptionValue to a Pokit device's range enumerator.
Definition dsocommand.h:30
QStringList supportedOptions(const QCommandLineParser &parser) const override
Returns a list of CLI option names supported by this command.
DsoCommand(QObject *const parent=nullptr)
Construct a new DsoCommand object with parent.
DsoService * service
Bluetooth service this command interracts with.
Definition dsocommand.h:32
QStringList processOptions(const QCommandLineParser &parser) override
Processes the relevant options from the command line parser.
void settingsWritten()
Invoked when the DSO settings have been written.
quint32 rangeOptionValue
The parsed value of range option.
Definition dsocommand.h:31
QStringList requiredOptions(const QCommandLineParser &parser) const override
Returns a list of CLI option names required by this command.
AbstractPokitService * getService() override
Returns a Pokit service object for the derived command class.
DsoService::Metadata metadata
Most recent DSO metadata.
Definition dsocommand.h:37
void metadataRead(const DsoService::Metadata &data)
Invoked when metadata has been received from the DSO.
bool setSettings(const Settings &settings)
Configures the Pokit device's DSO mode.
bool enableMetadataNotifications()
Enables client-side notifications of DSO metadata changes.
void metadataRead(const DsoService::Metadata &meta)
This signal is emitted when the Metadata characteristic has been read successfully.
void samplesRead(const DsoService::Samples &samples)
This signal is emitted when the Reading characteristic has been notified.
bool enableReadingNotifications()
Enables client-side notifications of DSO readings.
static QString toString(const Mode &mode)
Returns mode as a user-friendly string.
@ DcVoltage
Measure DC voltage.
@ AcCurrent
Measure AC current.
@ AcVoltage
Measure AC voltage.
@ Idle
Make device idle.
@ DcCurrent
Measure DC current.
void settingsWritten()
This signal is emitted when the Settings characteristic has been written successfully.
@ FreeRunning
Run free, without waiting for edge triggers.
@ RisingEdgeTrigger
Trigger on a rising edge.
@ FallingEdgeTrigger
Trigger on a falling edge.
DsoService * dso()
Returns a pointer to DsoService instance that uses this device's controller for access.
Declares the PokitDevice class.
bool isSet(const QString &name) const const
QString value(const QString &optionName) const const
void append(const T &value)
bool isEmpty() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
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)
bool isEmpty() const const
bool isNull() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString toLower() const const
QString trimmed() const const
Attributes included in the Metadata characterstic.
Definition dsoservice.h:84
quint32 samplingRate
Sampling rate used during last acquisition (1 to 1MHz).
Definition dsoservice.h:91
DsoStatus status
Current DSO status.
Definition dsoservice.h:85
float scale
Scale to apply to read samples.
Definition dsoservice.h:86
quint16 numberOfSamples
Number of samples acquired (1 to 8192).
Definition dsoservice.h:90
quint8 range
Range used during last acquisition.
Definition dsoservice.h:88
Mode mode
Operation mode used during last acquisition.
Definition dsoservice.h:87
quint32 samplingWindow
Sampling window (microseconds) used during last acquisition.
Definition dsoservice.h:89
Mode mode
Desired operation mode.
Definition dsoservice.h:70
quint8 range
Desired range, eg settings.range = +PokitPro::CurrentRange::AutoRange;.
Definition dsoservice.h:71
Command command
Custom operation request.
Definition dsoservice.h:68
quint32 samplingWindow
Desired sampling window in microseconds.
Definition dsoservice.h:72
float triggerLevel
Trigger threshold level in Volts or Amps, depending on mode.
Definition dsoservice.h:69
quint16 numberOfSamples
Desired number of samples to acquire.
Definition dsoservice.h:73