9#include <QJsonDocument>
65 mode.
startsWith(u
"ac v"_s) || mode.startsWith(u
"vac"_s)) {
67 }
else if (mode.startsWith(u
"dc v"_s) || mode.startsWith(u
"vdc"_s)) {
69 }
else if (mode.startsWith(u
"ac c"_s) || mode.startsWith(u
"aac"_s)) {
71 }
else if (mode.startsWith(u
"dc c"_s) || mode.startsWith(u
"adc"_s)) {
74 errors.
append(
tr(
"Unknown DSO mode: %1").arg(parser.
value(u
"mode"_s)));
82 quint32 sensibleMinimum = 0;
103 errors.
append(
tr(
"Invalid range value: %1").arg(value));
108 if (parser.
isSet(u
"trigger-level"_s)) {
110 const QString rawValue = parser.
value(u
"trigger-level"_s);
113 for (nonSpacePos = 0; (nonSpacePos < rawValue.
length()) && (rawValue.
at(nonSpacePos) == u
' '); ++nonSpacePos);
114 if ((nonSpacePos < rawValue.
length()) && (rawValue.
at(nonSpacePos) == u
'-')) {
115 absValue = rawValue.
mid(nonSpacePos+1);
119 qCDebug(lc) <<
"Trigger level" << rawValue << absValue << nonSpacePos << sign << level;
121 errors.
append(
tr(
"Invalid trigger-level value: %1").arg(rawValue));
123 settings.triggerLevel = sign * level;
124 qCDebug(lc) <<
"Trigger level" <<
settings.triggerLevel;
127 errors.
append(
tr(
"Trigger-level %1%2 is outside range ±%3%2").arg(
134 if (parser.
isSet(u
"trigger-mode"_s)) {
138 }
else if (triggerMode.
startsWith(u
"ris"_s)) {
140 }
else if (triggerMode.
startsWith(u
"fall"_s)) {
143 errors.
append(
tr(
"Unknown trigger mode: %1").arg(parser.
value(u
"trigger-mode"_s)));
148 if (parser.
isSet(u
"trigger-level"_s) != parser.
isSet(u
"trigger-mode"_s)) {
149 errors.
append(
tr(
"If either option is provided, then both must be: trigger-level, trigger-mode"));
153 if (parser.
isSet(u
"sample-rate"_s)) {
157 errors.
append(
tr(
"Invalid sample-rate value: %1").arg(value));
159 qCWarning(lc).noquote() <<
tr(
"Pokit devices do not officially support sample rates greater than 1Mhz");
164 if (parser.
isSet(u
"interval"_s)) {
168 errors.
append(
tr(
"Invalid interval value: %1").arg(value));
175 if (parser.
isSet(u
"window-size"_s)) {
179 errors.
append(
tr(
"Invalid window-size value: %1").arg(value));
180 }
else if (samples > std::numeric_limits<quint16>::max()) {
181 errors.
append(
tr(
"Window size value (%1) must be no greater than %2")
182 .arg(value).arg(std::numeric_limits<quint16>::max()));
184 settings.numberOfSamples = (quint16)samples;
186 qCWarning(lc).noquote() <<
187 tr(
"No Pokit device officially supports windows greater than %L1 samples").
arg(maxSamples);
193 if ((!parser.
isSet(u
"sample-rate"_s)) && !(parser.
isSet(u
"interval"_s) && parser.
isSet(u
"window-size"_s))) {
194 errors.
append(
tr(
"Missing required option/s: either sample-rate, or both interval and window-size"));
199 const quint32 sampleRate =
settings.numberOfSamples * 1'000'000ull /
settings.samplingWindow;
201 errors.
append(
tr(
"Windows size (%1 samples) and interval (%2ns) yield a sample rate of %3Hz, which does "
202 "not match the supplied sample-rate (%4Hz). Tip: leave one option unset to have dokit calculate the "
209 if (parser.
isSet(u
"samples"_s)) {
213 errors.
append(
tr(
"Invalid samples value: %1").arg(value));
251 Q_ASSERT_X(
false,
"DsoCommand::maxWindowSize",
"Unknown PokitProduct enum value");
264 const quint32 maxSampleRate = 1'000'000;
267 if (sampleRate > maxSampleRate) {
268 qCWarning(lc).noquote() <<
269 tr(
"The requested sample rate (%1Hz) likely exceeds the connected device's limit (%2Hz)")
270 .
arg(sampleRate).
arg(maxSampleRate);
274 qCWarning(lc).noquote() <<
275 tr(
"Requested window size (%1 samples) likely exceeds the connected device's limit (%2) samples")
280 qCDebug(lc).noquote() <<
"Both numberOfSamples and samplingWindow are set, so no need to derive either";
281 if (sampleRate != 0) {
282 const quint32 derivedRate =
settings.numberOfSamples * 1'000'000ull /
settings.samplingWindow;
283 qCDebug(lc).noquote() <<
"derivedRate" << derivedRate << sampleRate;
284 if (sampleRate != derivedRate) {
285 qCWarning(lc).noquote() <<
tr(
"Ignoring sample-rate, as interval and window-size both provided");
290 Q_ASSERT_X(sampleRate > 0,
"DsoCommand::configureWindow",
"processOptions should have rejected already");
294 qCDebug(lc).noquote() <<
tr(
"Choosing best number-of-samples for sample-rate %2Hz").
arg(sampleRate);
295 double smallestDifference = std::numeric_limits<double>::quiet_NaN();
296 for (quint32 windowSize =
maxWindowSize; windowSize > 0; --windowSize) {
297 const quint32 period = windowSize * 1'000'000ull / sampleRate;
298 const double effectiveRate = double(windowSize) * 1'000'000.0 / (double)period;
299 if (effectiveRate > maxSampleRate)
continue;
300 if (
const quint32 effectivePeriod = windowSize * 1'000'000ull / sampleRate;
301 effectivePeriod > 1'000'000)
continue;
302 const double difference = qAbs(effectiveRate - sampleRate);
305 if ((
settings.numberOfSamples == 0) || (difference < smallestDifference)) {
306 settings.numberOfSamples = windowSize;
307 smallestDifference = difference;
310 qCDebug(lc).noquote() <<
tr(
"Chose %Ln sample/s, with error ±%2Hz",
nullptr,
311 settings.numberOfSamples).
arg(smallestDifference, 0,
'f');
312 if (
settings.numberOfSamples == 0) {
313 qCCritical(lc).noquote() <<
tr(
"Failed to select a compatible window size for sample rate %1Hz").
arg(sampleRate);
318 if (
settings.numberOfSamples == 0) {
319 qCDebug(lc).noquote() <<
tr(
"Calculating number-of-samples for %1us window at %2Hz")
321 Q_ASSERT(
settings.samplingWindow != 0);
322 const auto numberOfSamples = sampleRate *
settings.samplingWindow / 1'000'000ull;
323 qCDebug(lc).noquote() <<
tr(
"Calculated %Ln sample/s",
nullptr, numberOfSamples);
324 if ((numberOfSamples == 0) || (numberOfSamples >
maxWindowSize)) {
325 qCCritical(lc).noquote() <<
tr(
"Failed to calculate a valid number of samples for a %L1us period at %2Hz")
329 settings.numberOfSamples = numberOfSamples;
330 Q_ASSERT(
settings.numberOfSamples * 1'000'000ull /
settings.samplingWindow <= sampleRate);
334 qCDebug(lc).noquote() <<
tr(
"Calculating sampling-window for %Ln sample/s at %1Hz",
nullptr,
336 Q_ASSERT(
settings.numberOfSamples != 0);
337 settings.samplingWindow =
settings.numberOfSamples * 1'000'000ull / sampleRate;
338 qCDebug(lc).noquote() <<
tr(
"Calculated %1us").
arg(
settings.samplingWindow);
340 qCCritical(lc).noquote() <<
tr(
"Failed to calculate a valid sampling window for a %L1 samples at %1Hz")
366 range.
at(range.
size()-1));
367 qCInfo(lc).noquote() <<
tr(
"Sampling %1, with range %2, at %L3Hz (%Ln sample/s over %L4us)%5",
nullptr,
settings.numberOfSamples)
371 if (!
service->enableMetadataNotifications()) {
372 qCCritical(lc).noquote() <<
tr(
"Failed to enable metadata notifications");
376 if (!
service->enableReadingNotifications()) {
377 qCCritical(lc).noquote() <<
tr(
"Failed to enable reading notifications");
404 qCDebug(lc).noquote() <<
tr(
"Settings written; DSO has started.");
412 qCDebug(lc) <<
"status:" << (int)(data.
status);
413 qCDebug(lc) <<
"scale:" << data.
scale;
418 qCDebug(lc) <<
"samplingRate:" << data.
samplingRate <<
"Hz";
435 qCDebug(lc).noquote() <<
tr(R
"(No known unit for mode %1 "%2".)").arg((int)
metadata.mode)
440 for (
const qint16 &sample: samples) {
441 static quint32 sampleNumber = 0; ++sampleNumber;
442 const float value = sample *
metadata.scale;
446 std::cout << qUtf8Printable(
tr(
"sample_number,value,unit,range\n"));
449 .arg(sampleNumber).arg(value).arg(unit, range));
453 { u
"value"_s, value },
455 { u
"range"_s, range },
457 }).toJson().toStdString();
460 std::cout << qUtf8Printable(
tr(
"%1 %2 %3\n").arg(sampleNumber).arg(value).arg(unit));
466 qCInfo(lc).noquote() <<
tr(
"Finished fetching %Ln sample/s. Disconnecting.",
nullptr, sampleNumber);
474 qCDebug(lc).noquote() <<
tr(
"Finished fetching %Ln window sample/s (with %L2 to remaining).",
virtual QStringList supportedOptions(const QCommandLineParser &parser) const
Returns a list of CLI option names supported by this command.
static QString appendSiPrefix(const double value, int precision=6)
Returns a human-readable version of value, with up-to precision decimal digits, and SI unit prefix wh...
static T parseNumber(const QString &value, const QString &unit, const T sensibleMinimum=0)
Returns value as an integer multiple of the ratio R, as number of type T.
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.
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.
static quint8 minVoltageRange(const PokitProduct product, const quint32 maxValue)
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.
static quint16 maxWindowSize(const PokitProduct product)
Returns the product's maximum sampling window size.
quint8(* minRangeFunc)(const PokitProduct product, const quint32 maxValue)
Pointer to function for converting rangeOptionValue to a Pokit device's range enumerator.
qint32 samplesToGo
Number of samples we're expecting in the current window.
bool showCsvHeader
Whether or not to show a header as the first line of CSV output.
void serviceDetailsDiscovered() override
Handles service detail discovery events.
void outputSamples(const DsoService::Samples &samples)
Outputs DSO samples in the selected output format.
static bool configureWindow(const PokitProduct product, const quint32 sampleRate, DsoService::Settings &settings)
Configures the settings.numberOfSamples and/or settings.samplingWindow, if not already set,...
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 interacts with.
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.
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.
quint32 sampleRateValue
The parsed value of the sample-rate option.
quint32 samplesValue
The parsed value of the samples option.
DsoService::Metadata metadata
Most recent DSO metadata.
void metadataRead(const DsoService::Metadata &data)
Invoked when metadata has been received from the DSO.
void metadataRead(const DsoService::Metadata &meta)
This signal is emitted when the Metadata characteristic has been read successfully.
QVector< qint16 > Samples
Raw samples from the Reading characteristic.
void settingsWritten()
This signal is emitted when the Settings characteristic has been written successfully.
static QString toString(const Command &command)
Returns command as a user-friendly string.
@ DcVoltage
Measure DC voltage.
@ AcCurrent
Measure AC current.
@ AcVoltage
Measure AC voltage.
@ DcCurrent
Measure DC current.
@ FreeRunning
Run free, without waiting for edge triggers.
@ RisingEdgeTrigger
Trigger on a rising edge.
@ FallingEdgeTrigger
Trigger on a falling edge.
void samplesRead(const DsoService::Samples &samples)
This signal is emitted when the Reading characteristic has been notified.
Declares the PokitDevice class.
PokitProduct
Pokit products known to, and supported by, the QtPokit library.
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)
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
const QChar at(int position) const const
QString fromLatin1(const char *str, int size)
bool isEmpty() const const
bool isNull() const const
QString mid(int position, int n) const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString toLower() const const
QString trimmed() const const
Declares the DOKIT_USE_STRINGLITERALS macro, and related functions.
#define DOKIT_STRING_INDEX_TYPE
Internal macro for matching the index type used by QString methods.
#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 Settings characteristic.