Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Openhantek2 #300

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Next Next commit
Use Qt3D for the scope window; Make Dso parameters/logic gui independant
New features
* Extend marker system: Allow unlimited markers and zoomviews
* Extend math channels: Dynamic math channels. Math Modes: +,-,*
* Add a self-calibration for gnd-offset/gain-limit values.

Bug fix
* Export: Always keep a pointer to the the last sampleSet. This ways exports work if the user uses a one-time trigger and want to export the visible graphs.
* Disable roll-mode for DSO6022

Documentation
* API of DsoControl documented and slightly changed.

Easier maintenance
* DsoControl: Samples are always in [-1,1] range now and user-gain/offsets are applied in post/graphgenerator.
* Qt3d is used for the scope window now. No direct shader programming for different platforms or GL versions anymore.
* Move settings related files to own subfolder
* Make GUI docks independant objects. They react on settings changes and manipulate settings or the dsocontrol object directly.
* Split HantekDsoControl into DsoControl+DsoLoop+DsoCommandQueue
* sispinbox does not allow a fixed set of values anymore. Made it over complicated and we have QComboBox for this purpose anyway.

Optimize
* Allow DSO models to not always have 9 fixed gain steps. The DSO6022 for example only has 2.
* Remove a lot of allocations in the fetch-samples/convert/show-graph hot-path. We have a PostProcessingResult pool now.
* Use the new GlScope class to draw the export graph, no dublicate code and it does look exactly like on screen (except different dimensions).
David Graeff committed Mar 9, 2018
commit 349068029c9e0efdfa953329df3e05b406b45aed
36 changes: 30 additions & 6 deletions docs/adddevice.md
Original file line number Diff line number Diff line change
@@ -6,10 +6,13 @@ We only accept new devices whoms firmware is hantek protocol compatible.
Codewise you will only need to touch files within `openhantek/src/hantekdso`.

## Firmware and usb access
The firmware goes to `openhantek/res/firmware` in the hex format. Please keep to the filename
The firmware goes to `openhantek/res/firmware` in intel hex format. Please keep to the filename
convention devicename-firmware.hex and devicename-loader.hex.
The `openhantek/res/firmwares.qrc` should list the new files.
The firmware/60-hantek.rules file needs the usb vendor/device id to add access permissions.

The firmware/60-hantek.rules file needs the usb vendor/device id to add access permissions for linux users.
MacOS users do not have access restrictions. On Windows there need to be a driver installed that
also manages the usb access.

## The hantek protocol
The hantek protocol itself is encoded in the `src/hantekprotocol` files.
@@ -24,7 +27,7 @@ You will only need to touch files within `openhantek/src/hantekdso/models`.
struct ModelDSO2090 : public DSOModel {
static const int ID = 0x2090; // Freely chooseable but unique id
ModelDSO2090();
void applyRequirements(HantekDsoControl* dsoControl) const override;
void applyRequirements(DsoCommandQueue* commandQueue) const override;
};
```
@@ -37,7 +40,7 @@ DSOModel(int ID, long vendorID, long productID, long vendorIDnoFirmware, long pr
```

* You need to find out the usb vendor id and product id for your digital oscilloscope after it has received
the firmware (for ``long vendorID``, ``long productID``) and before it has a valid firmware
the firmware (for ``long vendorID``, ``long productID``) and also before it has a valid firmware
(for ``long vendorIDnoFirmware``, ``long productIDnoFirmware``).
* The firmware token is just the devicename part of the firmware
(remember that we used `devicename-firmware.hex` and `devicename-loader.hex`).
@@ -56,7 +59,7 @@ DSOModel(int ID, long vendorID, long productID, long vendorIDnoFirmware, long pr
specification.samplerate.multi.recordLengths = {UINT_MAX, 20480, 65536};
```
4. The actual commands that are send, need to be defined as well, for instance:
4. The command prototypes that are used need to be defined, for instance:
``` c++
specification.command.control.setOffset = CONTROL_SETOFFSET;
@@ -69,7 +72,28 @@ DSOModel(int ID, long vendorID, long productID, long vendorIDnoFirmware, long pr
specification.command.bulk.setPretrigger = BulkCode::SETTRIGGERANDSAMPLERATE;
```

5. Add an instance of your class to the cpp file. The `DSOModel` constructor will register
5. You need to register the actual commands to the DsoCommandQueue object like this:

``` c++
void ModelDSO2150::applyRequirements(DsoCommandQueue *commandQueue) const {
commandQueue->addCommand(new BulkForceTrigger(), false);
commandQueue->addCommand(new BulkCaptureStart(), false);
commandQueue->addCommand(new BulkTriggerEnabled(), false);
commandQueue->addCommand(new BulkGetData(), false);
commandQueue->addCommand(new BulkGetCaptureState(), false);
commandQueue->addCommand(new BulkSetGain(), false);

commandQueue->addCommand(new BulkSetTriggerAndSamplerate(), false);
commandQueue->addCommand(new ControlSetOffset(), false);
commandQueue->addCommand(new ControlSetRelays(), false);
}
```
If you define a command prototype in (4) but do not register the actual command in (5), the application
will crash. Guaranteed!
Always create new heap objects for the command queue. The queue will clean up after itself.
6. Add an object instance of your class to the cpp file as last line. The `DSOModel` constructor will register
your new model automatically to the ModelRegistry:
```
47 changes: 35 additions & 12 deletions openhantek/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,37 +1,60 @@
project(OpenHantek CXX)

find_package(Qt5Widgets REQUIRED)
find_package(Qt5PrintSupport REQUIRED)
find_package(Qt5OpenGL REQUIRED)
find_package(OpenGL)
find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets PrintSupport 3DCore 3DExtras 3DRender 3DInput)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)

if (Qt5Widgets_VERSION VERSION_LESS 5.4.0)
message(FATAL_ERROR "Minimum supported Qt5 version is 5.4.0!")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

if (Qt5Widgets_VERSION VERSION_LESS 5.8.0)
message(FATAL_ERROR "Minimum supported Qt5 version is 5.8.0!")
endif()

# include directories
set(CMAKE_INCLUDE_CURRENT_DIR ON)
include_directories(src/ src/hantekdso src/widgets src/docks src/configdialog)
include_directories(src/ src/hantekdso src/settings) # src/widgets src/scopeview

# collect sources and other files
file(GLOB_RECURSE SRC "src/*.cpp")
file(GLOB_RECURSE HEADERS "src/*.h")
file(GLOB_RECURSE SRC_CORE
"src/hantekdso/*.cpp" "src/hantekprotocol/*.cpp" "src/hantekprotocol/*.cpp" "src/usb/*.cpp" "src/utils/*.cpp"
"src/hantekdso/*.h" "src/hantekprotocol/*.h" "src/hantekprotocol/*.h" "src/usb/*.h" "src/utils/*.h")
file(GLOB_RECURSE SRC_POST "src/post/*.cpp" "src/post/*.h")
file(GLOB_RECURSE SRC_SETTINGS "src/settings/*.cpp" "src/settings/*.h")
file(GLOB_RECURSE SRC_EXPORT "src/exporting/*.cpp" "src/exporting/*.h")
file(GLOB_RECURSE SRC_SCOPEVIEW "src/scopeview/*.cpp" "src/scopeview/*.h")
file(GLOB SRC_UI "src/main.cpp"
"src/widgets/*.cpp" "src/widgets/*.h"
"src/mainwindow.cpp" "src/mainwindow.h"
"src/configdialog/*.cpp" "src/configdialog/*.h"
"src/docks/*.cpp" "src/docks/*.h"
"src/iconfont/*.cpp" "src/iconfont/*.h"
"src/selectdevice/*.cpp" "src/selectdevice/*.h")
set(SRC ${SRC_CORE} ${SRC_POST} ${SRC_SETTINGS} ${SRC_EXPORT} ${SRC_SCOPEVIEW} ${SRC_UI})

file(GLOB_RECURSE UI "src/*.ui")
file(GLOB_RECURSE QRC "res/*.qrc")

add_custom_target(format SOURCES ".clang-format"
COMMAND "clang-format" "-style=file" "-i" "-sort-includes" ${SRC} ${HEADERS})
COMMAND "clang-format" "-style=file" "-i" "-sort-includes" ${SRC})

add_subdirectory(translations)

add_definitions(-DVERSION="${CPACK_PACKAGE_VERSION}")

# make executable
add_executable(${PROJECT_NAME} ${SRC} ${HEADERS} ${UI} ${QRC} ${TRANSLATION_BIN_FILES} ${TRANSLATION_QRC})
target_link_libraries(${PROJECT_NAME} Qt5::Widgets Qt5::PrintSupport Qt5::OpenGL ${OPENGL_LIBRARIES} )
add_executable(${PROJECT_NAME} ${SRC} ${UI} ${QRC} ${TRANSLATION_BIN_FILES} ${TRANSLATION_QRC})
target_link_libraries(${PROJECT_NAME}
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::3DCore
Qt5::3DExtras
Qt5::3DRender
Qt5::3DInput
Qt5::PrintSupport)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_range_for)
if(MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE "/W4" "/wd4251" "/wd4127" "/wd4275" "/wd4200" "/nologo" "/J" "/Zi")
14 changes: 7 additions & 7 deletions openhantek/src/configdialog/DsoConfigAnalysisPage.cpp
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

#include "DsoConfigAnalysisPage.h"

DsoConfigAnalysisPage::DsoConfigAnalysisPage(DsoSettings *settings, QWidget *parent)
DsoConfigAnalysisPage::DsoConfigAnalysisPage(Settings::DsoSettings *settings, QWidget *parent)
: QWidget(parent), settings(settings) {
// Initialize lists for comboboxes
QStringList windowFunctionStrings;
@@ -15,14 +15,14 @@ DsoConfigAnalysisPage::DsoConfigAnalysisPage(DsoSettings *settings, QWidget *par
windowFunctionLabel = new QLabel(tr("Window function"));
windowFunctionComboBox = new QComboBox();
windowFunctionComboBox->addItems(windowFunctionStrings);
windowFunctionComboBox->setCurrentIndex((int)settings->post.spectrumWindow);
windowFunctionComboBox->setCurrentIndex((int)settings->post.spectrumWindow());

referenceLevelLabel = new QLabel(tr("Reference level"));
referenceLevelSpinBox = new QDoubleSpinBox();
referenceLevelSpinBox->setDecimals(1);
referenceLevelSpinBox->setMinimum(-40.0);
referenceLevelSpinBox->setMaximum(100.0);
referenceLevelSpinBox->setValue(settings->post.spectrumReference);
referenceLevelSpinBox->setValue(settings->post.spectrumReference());
referenceLevelUnitLabel = new QLabel(tr("dBm"));
referenceLevelLayout = new QHBoxLayout();
referenceLevelLayout->addWidget(referenceLevelSpinBox);
@@ -33,7 +33,7 @@ DsoConfigAnalysisPage::DsoConfigAnalysisPage(DsoSettings *settings, QWidget *par
minimumMagnitudeSpinBox->setDecimals(1);
minimumMagnitudeSpinBox->setMinimum(-40.0);
minimumMagnitudeSpinBox->setMaximum(100.0);
minimumMagnitudeSpinBox->setValue(settings->post.spectrumLimit);
minimumMagnitudeSpinBox->setValue(settings->post.spectrumLimit());
minimumMagnitudeUnitLabel = new QLabel(tr("dBm"));
minimumMagnitudeLayout = new QHBoxLayout();
minimumMagnitudeLayout->addWidget(minimumMagnitudeSpinBox);
@@ -59,7 +59,7 @@ DsoConfigAnalysisPage::DsoConfigAnalysisPage(DsoSettings *settings, QWidget *par

/// \brief Saves the new settings.
void DsoConfigAnalysisPage::saveSettings() {
settings->post.spectrumWindow = (Dso::WindowFunction)windowFunctionComboBox->currentIndex();
settings->post.spectrumReference = referenceLevelSpinBox->value();
settings->post.spectrumLimit = minimumMagnitudeSpinBox->value();
settings->post.m_spectrumWindow = (PostProcessing::WindowFunction)windowFunctionComboBox->currentIndex();
settings->post.m_spectrumReference = referenceLevelSpinBox->value();
settings->post.m_spectrumLimit = minimumMagnitudeSpinBox->value();
}
6 changes: 2 additions & 4 deletions openhantek/src/configdialog/DsoConfigAnalysisPage.h
Original file line number Diff line number Diff line change
@@ -14,20 +14,18 @@
#include <QSpinBox>
#include <QVBoxLayout>

////////////////////////////////////////////////////////////////////////////////
/// \class DsoConfigAnalysisPage configpages.h
/// \brief Config page for the data analysis.
class DsoConfigAnalysisPage : public QWidget {
Q_OBJECT

public:
DsoConfigAnalysisPage(DsoSettings *settings, QWidget *parent = 0);
DsoConfigAnalysisPage(Settings::DsoSettings *settings, QWidget *parent = 0);

public slots:
void saveSettings();

private:
DsoSettings *settings;
Settings::DsoSettings *settings;

QVBoxLayout *mainLayout;

110 changes: 64 additions & 46 deletions openhantek/src/configdialog/DsoConfigColorsPage.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
// SPDX-License-Identifier: GPL-2.0+

#include "DsoConfigColorsPage.h"
#include "widgets/colorbox.h"

DsoConfigColorsPage::DsoConfigColorsPage(DsoSettings *settings, QWidget *parent) : QWidget(parent), settings(settings) {
DsoConfigColorsPage::DsoConfigColorsPage(Settings::DsoSettings *settings, QWidget *parent)
: QWidget(parent), settings(settings) {
// Initialize elements
DsoSettingsView &colorSettings = settings->view;
Settings::View &colorSettings = settings->view;
enum { COL_LABEL = 0, COL_SCR_CHANNEL, COL_SCR_SPECTRUM, COL_PRT_CHANNEL, COL_PRT_SPECTRUM };

QVBoxLayout *mainLayout;

QGroupBox *colorsGroup;
QGridLayout *colorsLayout;

QLabel *screenColorsLabel, *printColorsLabel;
QLabel *axesLabel, *backgroundLabel, *borderLabel, *gridLabel, *markersLabel, *textLabel;
QLabel *graphLabel;
QLabel *screenChannelLabel, *screenSpectrumLabel, *printChannelLabel, *printSpectrumLabel;

// Plot Area
graphLabel = new QLabel(tr("<hr width=\"100%\"/>")); // 4*80
graphLabel->setAlignment(Qt::AlignRight);
@@ -18,28 +30,28 @@ DsoConfigColorsPage::DsoConfigColorsPage(DsoSettings *settings, QWidget *parent)
printColorsLabel->setAlignment(Qt::AlignHCenter);

axesLabel = new QLabel(tr("Axes"));
axesColorBox = new ColorBox(colorSettings.screen.axes);
printAxesColorBox = new ColorBox(colorSettings.print.axes);
axesColorBox = new ColorBox(colorSettings.screen.axes());
printAxesColorBox = new ColorBox(colorSettings.print.axes());

backgroundLabel = new QLabel(tr("Background"));
backgroundColorBox = new ColorBox(colorSettings.screen.background);
printBackgroundColorBox = new ColorBox(colorSettings.print.background);
backgroundColorBox = new ColorBox(colorSettings.screen.background());
printBackgroundColorBox = new ColorBox(colorSettings.print.background());

borderLabel = new QLabel(tr("Border"));
borderColorBox = new ColorBox(colorSettings.screen.border);
printBorderColorBox = new ColorBox(colorSettings.print.border);
borderColorBox = new ColorBox(colorSettings.screen.border());
printBorderColorBox = new ColorBox(colorSettings.print.border());

gridLabel = new QLabel(tr("Grid"));
gridColorBox = new ColorBox(colorSettings.screen.grid);
printGridColorBox = new ColorBox(colorSettings.print.grid);
gridColorBox = new ColorBox(colorSettings.screen.grid());
printGridColorBox = new ColorBox(colorSettings.print.grid());

markersLabel = new QLabel(tr("Markers"));
markersColorBox = new ColorBox(colorSettings.screen.markers);
printMarkersColorBox = new ColorBox(colorSettings.print.markers);
markersColorBox = new ColorBox(colorSettings.screen.markers());
printMarkersColorBox = new ColorBox(colorSettings.print.markers());

textLabel = new QLabel(tr("Text"));
textColorBox = new ColorBox(colorSettings.screen.text);
printTextColorBox = new ColorBox(colorSettings.print.text);
textColorBox = new ColorBox(colorSettings.screen.text());
printTextColorBox = new ColorBox(colorSettings.print.text());

// Graph category
screenChannelLabel = new QLabel(tr("Channel"));
@@ -51,14 +63,6 @@ DsoConfigColorsPage::DsoConfigColorsPage(DsoSettings *settings, QWidget *parent)
printSpectrumLabel = new QLabel(tr("Spectrum"));
printSpectrumLabel->setAlignment(Qt::AlignHCenter);

for (ChannelID channel = 0; channel < settings->scope.voltage.size(); ++channel) {
colorLabel.push_back(new QLabel(settings->scope.voltage[channel].name));
screenChannelColorBox.push_back(new ColorBox(colorSettings.screen.voltage[channel]));
screenSpectrumColorBox.push_back(new ColorBox(colorSettings.screen.spectrum[channel]));
printChannelColorBox.push_back(new ColorBox(colorSettings.print.voltage[channel]));
printSpectrumColorBox.push_back(new ColorBox(colorSettings.print.spectrum[channel]));
}

// Plot Area Layout
colorsLayout = new QGridLayout();
colorsLayout->setColumnStretch(COL_LABEL, 1);
@@ -106,12 +110,21 @@ DsoConfigColorsPage::DsoConfigColorsPage(DsoSettings *settings, QWidget *parent)
colorsLayout->addWidget(printSpectrumLabel, row, COL_PRT_SPECTRUM);
++row;

for (ChannelID channel = 0; channel < settings->scope.voltage.size(); ++channel, ++row) {
colorsLayout->addWidget(colorLabel[channel], row, COL_LABEL);
colorsLayout->addWidget(screenChannelColorBox[channel], row, COL_SCR_CHANNEL);
colorsLayout->addWidget(screenSpectrumColorBox[channel], row, COL_SCR_SPECTRUM);
colorsLayout->addWidget(printChannelColorBox[channel], row, COL_PRT_CHANNEL);
colorsLayout->addWidget(printSpectrumColorBox[channel], row, COL_PRT_SPECTRUM);
for (auto *channelSettings : settings->scope) {
ChannelColors *cc = new ChannelColors(this);
QLabel *colorLabel = new QLabel(channelSettings->name());
cc->screenChannelColorBox = new ColorBox(colorSettings.screen.voltage(channelSettings->channelID()));
cc->screenSpectrumColorBox = new ColorBox(colorSettings.screen.spectrum(channelSettings->channelID()));
cc->printChannelColorBox = new ColorBox(colorSettings.print.voltage(channelSettings->channelID()));
cc->printSpectrumColorBox = new ColorBox(colorSettings.print.spectrum(channelSettings->channelID()));
m_channelColorMap.insert(std::make_pair(channelSettings->channelID(), cc));

colorsLayout->addWidget(colorLabel, row, COL_LABEL);
colorsLayout->addWidget(cc->screenChannelColorBox, row, COL_SCR_CHANNEL);
colorsLayout->addWidget(cc->screenSpectrumColorBox, row, COL_SCR_SPECTRUM);
colorsLayout->addWidget(cc->printChannelColorBox, row, COL_PRT_CHANNEL);
colorsLayout->addWidget(cc->printSpectrumColorBox, row, COL_PRT_SPECTRUM);
++row;
}

colorsGroup = new QGroupBox(tr("Screen and Print Colors"));
@@ -127,29 +140,34 @@ DsoConfigColorsPage::DsoConfigColorsPage(DsoSettings *settings, QWidget *parent)

/// \brief Saves the new settings.
void DsoConfigColorsPage::saveSettings() {
DsoSettingsView &colorSettings = settings->view;
Settings::View &colorSettings = settings->view;

// Screen category
colorSettings.screen.axes = axesColorBox->getColor();
colorSettings.screen.background = backgroundColorBox->getColor();
colorSettings.screen.border = borderColorBox->getColor();
colorSettings.screen.grid = gridColorBox->getColor();
colorSettings.screen.markers = markersColorBox->getColor();
colorSettings.screen.text = textColorBox->getColor();
colorSettings.screen._axes = axesColorBox->getColor();
colorSettings.screen._background = backgroundColorBox->getColor();
colorSettings.screen._border = borderColorBox->getColor();
colorSettings.screen._grid = gridColorBox->getColor();
colorSettings.screen._markers = markersColorBox->getColor();
colorSettings.screen._text = textColorBox->getColor();

// Print category
colorSettings.print.axes = printAxesColorBox->getColor();
colorSettings.print.background = printBackgroundColorBox->getColor();
colorSettings.print.border = printBorderColorBox->getColor();
colorSettings.print.grid = printGridColorBox->getColor();
colorSettings.print.markers = printMarkersColorBox->getColor();
colorSettings.print.text = printTextColorBox->getColor();
colorSettings.print._axes = printAxesColorBox->getColor();
colorSettings.print._background = printBackgroundColorBox->getColor();
colorSettings.print._border = printBorderColorBox->getColor();
colorSettings.print._grid = printGridColorBox->getColor();
colorSettings.print._markers = printMarkersColorBox->getColor();
colorSettings.print._text = printTextColorBox->getColor();

// Graph category
for (ChannelID channel = 0; channel < settings->scope.voltage.size(); ++channel) {
colorSettings.screen.voltage[channel] = screenChannelColorBox[channel]->getColor();
colorSettings.screen.spectrum[channel] = screenSpectrumColorBox[channel]->getColor();
colorSettings.print.voltage[channel] = printChannelColorBox[channel]->getColor();
colorSettings.print.spectrum[channel] = printSpectrumColorBox[channel]->getColor();
for (auto &c : m_channelColorMap) {
colorSettings.screen.setVoltage(c.first, c.second->screenChannelColorBox->getColor());
colorSettings.screen.setSpectrum(c.first, c.second->screenSpectrumColorBox->getColor());
colorSettings.print.setVoltage(c.first, c.second->printChannelColorBox->getColor());
colorSettings.print.setSpectrum(c.first, c.second->printSpectrumColorBox->getColor());
}

colorSettings.screen.observer()->update();
colorSettings.print.observer()->update();
}

ChannelColors::ChannelColors(QObject *parent) : QObject(parent) {}
43 changes: 19 additions & 24 deletions openhantek/src/configdialog/DsoConfigColorsPage.h
Original file line number Diff line number Diff line change
@@ -14,41 +14,36 @@
#include <QSpinBox>
#include <QVBoxLayout>

#include "colorbox.h"
class ColorBox;

struct ChannelColors : protected QObject {
ChannelColors(QObject *parent);

ColorBox *screenChannelColorBox;
ColorBox *screenSpectrumColorBox;
ColorBox *printChannelColorBox;
ColorBox *printSpectrumColorBox;

private:
Q_OBJECT
};

////////////////////////////////////////////////////////////////////////////////
/// \class DsoConfigColorsPage configpages.h
/// \brief Config page for the colors.
class DsoConfigColorsPage : public QWidget {
Q_OBJECT

public:
DsoConfigColorsPage(DsoSettings *settings, QWidget *parent = 0);
DsoConfigColorsPage(Settings::DsoSettings *settings, QWidget *parent = 0);

public slots:
void saveSettings();

private:
DsoSettings *settings;

QVBoxLayout *mainLayout;

QGroupBox *colorsGroup;
QGridLayout *colorsLayout;

QLabel *screenColorsLabel, *printColorsLabel;
QLabel *axesLabel, *backgroundLabel, *borderLabel, *gridLabel, *markersLabel, *textLabel;
ColorBox *axesColorBox, *backgroundColorBox, *borderColorBox, *gridColorBox, *markersColorBox, *textColorBox;

ColorBox *printAxesColorBox, *printBackgroundColorBox, *printBorderColorBox, *printGridColorBox,
*printMarkersColorBox, *printTextColorBox;
Settings::DsoSettings *settings;

QLabel *graphLabel;
ColorBox *axesColorBox, *backgroundColorBox, *borderColorBox, *gridColorBox, *markersColorBox, *textColorBox,
*printAxesColorBox, *printBackgroundColorBox, *printBorderColorBox, *printGridColorBox, *printMarkersColorBox,
*printTextColorBox;

QLabel *screenChannelLabel, *screenSpectrumLabel, *printChannelLabel, *printSpectrumLabel;
std::vector<QLabel *> colorLabel;
std::vector<ColorBox *> screenChannelColorBox;
std::vector<ColorBox *> screenSpectrumColorBox;
std::vector<ColorBox *> printChannelColorBox;
std::vector<ColorBox *> printSpectrumColorBox;
std::map<ChannelID, ChannelColors *> m_channelColorMap;
};
2 changes: 1 addition & 1 deletion openhantek/src/configdialog/DsoConfigFilesPage.cpp
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

#include "DsoConfigFilesPage.h"

DsoConfigFilesPage::DsoConfigFilesPage(DsoSettings *settings, QWidget *parent) : QWidget(parent), settings(settings) {
DsoConfigFilesPage::DsoConfigFilesPage(Settings::DsoSettings *settings, QWidget *parent) : QWidget(parent), settings(settings) {
// Export group
screenColorCheckBox = new QCheckBox(tr("Export Images with Screen Colors"));
screenColorCheckBox->setChecked(settings->view.screenColorImages);
6 changes: 2 additions & 4 deletions openhantek/src/configdialog/DsoConfigFilesPage.h
Original file line number Diff line number Diff line change
@@ -15,20 +15,18 @@
#include <QSpinBox>
#include <QVBoxLayout>

////////////////////////////////////////////////////////////////////////////////
/// \class DsoConfigFilesPage configpages.h
/// \brief Config page for file loading/saving.
class DsoConfigFilesPage : public QWidget {
Q_OBJECT

public:
DsoConfigFilesPage(DsoSettings *settings, QWidget *parent = 0);
DsoConfigFilesPage(Settings::DsoSettings *settings, QWidget *parent = 0);

public slots:
void saveSettings();

private:
DsoSettings *settings;
Settings::DsoSettings *settings;

QVBoxLayout *mainLayout;

11 changes: 6 additions & 5 deletions openhantek/src/configdialog/DsoConfigScopePage.cpp
Original file line number Diff line number Diff line change
@@ -2,7 +2,8 @@

#include "DsoConfigScopePage.h"

DsoConfigScopePage::DsoConfigScopePage(DsoSettings *settings, QWidget *parent) : QWidget(parent), settings(settings) {
DsoConfigScopePage::DsoConfigScopePage(Settings::DsoSettings *settings, QWidget *parent)
: QWidget(parent), settings(settings) {
// Initialize lists for comboboxes
QStringList interpolationStrings;
interpolationStrings << tr("Off") << tr("Linear");
@@ -11,12 +12,12 @@ DsoConfigScopePage::DsoConfigScopePage(DsoSettings *settings, QWidget *parent) :
interpolationLabel = new QLabel(tr("Interpolation"));
interpolationComboBox = new QComboBox();
interpolationComboBox->addItems(interpolationStrings);
interpolationComboBox->setCurrentIndex(settings->view.interpolation);
interpolationComboBox->setCurrentIndex((unsigned)settings->view.interpolation());
digitalPhosphorDepthLabel = new QLabel(tr("Digital phosphor depth"));
digitalPhosphorDepthSpinBox = new QSpinBox();
digitalPhosphorDepthSpinBox->setMinimum(2);
digitalPhosphorDepthSpinBox->setMaximum(99);
digitalPhosphorDepthSpinBox->setValue(settings->view.digitalPhosphorDepth);
digitalPhosphorDepthSpinBox->setValue(settings->view.digitalPhosphor());

graphLayout = new QGridLayout();
graphLayout->addWidget(interpolationLabel, 1, 0);
@@ -36,6 +37,6 @@ DsoConfigScopePage::DsoConfigScopePage(DsoSettings *settings, QWidget *parent) :

/// \brief Saves the new settings.
void DsoConfigScopePage::saveSettings() {
settings->view.interpolation = (Dso::InterpolationMode)interpolationComboBox->currentIndex();
settings->view.digitalPhosphorDepth = digitalPhosphorDepthSpinBox->value();
settings->view.setInterpolation((Dso::InterpolationMode)interpolationComboBox->currentIndex());
settings->view.setDigitalPhosphor(settings->view.digitalPhosphor(), (unsigned)digitalPhosphorDepthSpinBox->value());
}
6 changes: 2 additions & 4 deletions openhantek/src/configdialog/DsoConfigScopePage.h
Original file line number Diff line number Diff line change
@@ -14,20 +14,18 @@
#include <QSpinBox>
#include <QVBoxLayout>

////////////////////////////////////////////////////////////////////////////////
/// \class DsoConfigScopePage configpages.h
/// \brief Config page for the scope screen.
class DsoConfigScopePage : public QWidget {
Q_OBJECT

public:
DsoConfigScopePage(DsoSettings *settings, QWidget *parent = 0);
DsoConfigScopePage(Settings::DsoSettings *settings, QWidget *parent = 0);

public slots:
void saveSettings();

private:
DsoSettings *settings;
Settings::DsoSettings *settings;

QVBoxLayout *mainLayout;

6 changes: 3 additions & 3 deletions openhantek/src/configdialog/configdialog.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// SPDX-License-Identifier: GPL-2.0+

/*#if defined(OS_UNIX)
#define CONFIG_PATH QDir::homePath() + "/.config/paranoiacs.net/openhantek"
#define CONFIG_PATH QDir::homePath() + "/.config/openhantek/openhantek"
#define CONFIG_FILE CONFIG_PATH "/openhantek.conf"
#elif defined(OS_DARWIN)
#define CONFIG_PATH QDir::homePath() + "/Library/Application Support/OpenHantek"
#define CONFIG_FILE CONFIG_PATH "/openhantek.plist"
#elif defined(OS_WINDOWS)
//#define CONFIG_PATH QDir::homePath() + "" // Too hard to get and this OS sucks
anyway, ignore it
#define CONFIG_FILE "HKEY_CURRENT_USER\\Software\\paranoiacs.net\\OpenHantek"
#define CONFIG_FILE "HKEY_CURRENT_USER\\Software\\OpenHantek\\OpenHantek"
#endif*/

#define CONFIG_LIST_WIDTH 128 ///< The width of the page selection widget
@@ -39,7 +39,7 @@ anyway, ignore it
/// \param settings The target settings object.
/// \param parent The parent widget.
/// \param flags Flags for the window manager.
DsoConfigDialog::DsoConfigDialog(DsoSettings *settings, QWidget *parent, Qt::WindowFlags flags)
DsoConfigDialog::DsoConfigDialog(Settings::DsoSettings *settings, QWidget *parent, Qt::WindowFlags flags)
: QDialog(parent, flags), settings(settings) {

this->setWindowTitle(tr("Settings"));
7 changes: 4 additions & 3 deletions openhantek/src/configdialog/configdialog.h
Original file line number Diff line number Diff line change
@@ -6,8 +6,9 @@ class DsoConfigAnalysisPage;
class DsoConfigColorsPage;
class DsoConfigFilesPage;
class DsoConfigScopePage;
namespace Settings {
class DsoSettings;

}
class QHBoxLayout;
class QListWidget;
class QListWidgetItem;
@@ -22,7 +23,7 @@ class DsoConfigDialog : public QDialog {
Q_OBJECT

public:
DsoConfigDialog(DsoSettings *settings, QWidget *parent = 0, Qt::WindowFlags flags = 0);
DsoConfigDialog(Settings::DsoSettings *settings, QWidget *parent = 0, Qt::WindowFlags flags = 0);
~DsoConfigDialog();

public slots:
@@ -34,7 +35,7 @@ class DsoConfigDialog : public QDialog {
private:
void createIcons();

DsoSettings *settings;
Settings::DsoSettings *settings;

QVBoxLayout *mainLayout;
QHBoxLayout *horizontalLayout;
156 changes: 156 additions & 0 deletions openhantek/src/docks/DebugDock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: GPL-2.0+

#include <QCheckBox>
#include <QCloseEvent>
#include <QComboBox>
#include <QCoreApplication>
#include <QDockWidget>
#include <QDoubleSpinBox>
#include <QLabel>
#include <QSignalBlocker>
#include <QStringListModel>

#include <cmath>

#include "DebugDock.h"
#include "dockwindows.h"
#include "hantekdso/devicesettings.h"
#include "hantekdso/enums.h"
#include "hantekdso/dsocontrol.h"
#include "hantekdso/modelspecification.h"
#include "hantekprotocol/codes.h"
#include "iconfont/QtAwesome.h"
#include "scopesettings.h"
#include "utils/debugnotify.h"
#include "utils/enumhelper.h"
#include "utils/printutils.h"
#include "widgets/sispinbox.h"

#include <QDebug>
#include <QHeaderView>
#include <QLineEdit>
#include <QPushButton>
#include <QTableView>

Q_DECLARE_METATYPE(std::vector<unsigned>)
Q_DECLARE_METATYPE(std::vector<double>)

template <typename... Args> struct SELECT {
template <typename C, typename R> static constexpr auto OVERLOAD_OF(R (C::*pmf)(Args...)) -> decltype(pmf) {
return pmf;
}
};

using namespace Debug;

DebugDock::DebugDock(DsoControl *dsocontrol, QWidget *parent, Qt::WindowFlags flags)
: QDockWidget(tr("Debug"), parent, flags) {

QWidget *dockWidget = new QWidget(this);
QVBoxLayout *dockLayout = new QVBoxLayout;

QHBoxLayout *manualCommandLayout = new QHBoxLayout;
QHBoxLayout *manualCommandLayout2 = new QHBoxLayout;
QComboBox *manualCommandType = new QComboBox(this);
QComboBox *controlCodes = new QComboBox(this);
QComboBox *bulkCodes = new QComboBox(this);
QLineEdit *commandEdit = new QLineEdit(this);
QPushButton *actionManualCommand = new QPushButton;

commandEdit->setPlaceholderText(tr("0a ca (hex values)"));

actionManualCommand->setIcon(iconFont->icon(fa::edit));
manualCommandType->addItems(QStringList() << tr("Control") << tr("Bulk"));

auto bulkMeta = QMetaEnum::fromType<Hantek::BulkCode>();
for (int i = 0; i < bulkMeta.keyCount(); ++i) {
int v = bulkMeta.value(i);
if (dsocontrol->isCommandSupported((Hantek::BulkCode)v)) bulkCodes->addItem(bulkMeta.key(i), v);
}
bulkCodes->hide();

auto controlMeta = QMetaEnum::fromType<Hantek::ControlCode>();
for (int i = 0; i < controlMeta.keyCount(); ++i) {
int v = controlMeta.value(i);
if (dsocontrol->isCommandSupported((Hantek::ControlCode)v)) controlCodes->addItem(controlMeta.key(i), v);
}

connect(manualCommandType, SELECT<int>::OVERLOAD_OF(&QComboBox::currentIndexChanged), this,
[controlCodes, bulkCodes](unsigned index) {
controlCodes->setVisible(index == 0);
bulkCodes->setVisible(index == 1);
});

manualCommandLayout->addWidget(manualCommandType);
manualCommandLayout->addWidget(controlCodes);
manualCommandLayout->addWidget(bulkCodes);
manualCommandLayout2->addWidget(commandEdit, 1);
manualCommandLayout2->addWidget(actionManualCommand);

connect(this, &DebugDock::manualCommand, dsocontrol, &DsoControl::manualCommand);

auto fnManualCommand = [this, commandEdit, manualCommandType, controlCodes, bulkCodes, dsocontrol]() {
if (commandEdit->text().trimmed().isEmpty()) return;
if (manualCommandType->currentIndex() == 1) {
if (bulkCodes->currentIndex() < 0) return;
} else if (controlCodes->currentIndex() < 0)
return;
QByteArray data(100, 0);
data.resize((int)hexParse(commandEdit->text(), (unsigned char *)data.data(), (unsigned)data.size()));
if (data.isEmpty()) return;

// Use a signal instead of a direct function call to archive thread-safety
emit manualCommand(manualCommandType->currentIndex() == 1,
(Hantek::BulkCode)bulkCodes->currentData(Qt::UserRole).toInt(),
(Hantek::ControlCode)controlCodes->currentData(Qt::UserRole).toInt(), data);
commandEdit->clear();
};

connect(actionManualCommand, &QPushButton::toggled, this, fnManualCommand);
connect(commandEdit, &QLineEdit::returnPressed, this, fnManualCommand);

QTableView *logTable = new QTableView(this);
logTable->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
logTable->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
logTable->horizontalHeader()->hide();
logTable->horizontalHeader()->setStretchLastSection(true);
logTable->setColumnWidth(0, 60);
logTable->setColumnWidth(1, 60);
logTable->verticalHeader()->hide();
m_model = new Debug::LogModel(this);
logTable->setModel(m_model);
connect(dsocontrol, &DsoControl::debugMessage, m_model, &LogModel::addEntry);

QCheckBox *showLoopLog = new QCheckBox("Verbose loop log", this);
showLoopLog->setChecked(false);
m_model->addToFilter(Debug::NotificationType::DSOLoop);
connect(showLoopLog, &QCheckBox::toggled, this, [this](bool enable) {
if (enable)
m_model->clearFilter();
else
m_model->addToFilter(Debug::NotificationType::DSOLoop);
});

QPushButton *clearLog = new QPushButton;
clearLog->setIcon(iconFont->icon(fa::remove));
connect(clearLog, &QPushButton::clicked, m_model, &LogModel::removeAll);

QHBoxLayout *clearLogLayout = new QHBoxLayout;
clearLogLayout->addWidget(new QLabel(tr("Logs"), this), 1);
clearLogLayout->addWidget(clearLog);

dockLayout->addLayout(clearLogLayout);
dockLayout->addWidget(logTable, 1);
dockLayout->addWidget(showLoopLog);
dockLayout->addWidget(new QLabel(tr("Manual command"), this));
dockLayout->addLayout(manualCommandLayout);
dockLayout->addLayout(manualCommandLayout2);
SetupDockWidget(this, dockWidget, dockLayout, QSizePolicy::Expanding);
}

/// \brief Don't close the dock, just hide it.
/// \param event The close event that should be handled.
void DebugDock::closeEvent(QCloseEvent *event) {
this->hide();
event->accept();
}
37 changes: 37 additions & 0 deletions openhantek/src/docks/DebugDock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: GPL-2.0+

#pragma once

#include <QDockWidget>
#include <QGridLayout>

#include "hantekprotocol/codes.h"
#include "utils/debugnotify.h"

namespace Settings {
class Scope;
}
class DsoControl;
namespace Debug {
class LogModel;
}

/// \brief Dock window with a log view and manual command
/// It contains the settings for the timebase and the display format.
class DebugDock : public QDockWidget {
Q_OBJECT

public:
/// \brief Initializes the horizontal axis docking window.
/// \param settings The target settings object.
/// \param parent The parent widget.
/// \param flags Flags for the window manager.
DebugDock(DsoControl *dsocontrol, QWidget *parent, Qt::WindowFlags flags = 0);

protected:
void closeEvent(QCloseEvent *event);

Debug::LogModel *m_model;
signals:
void manualCommand(bool isBulk, Hantek::BulkCode bulkCode, Hantek::ControlCode controlCode, const QByteArray &data);
};
383 changes: 209 additions & 174 deletions openhantek/src/docks/HorizontalDock.cpp

Large diffs are not rendered by default.

80 changes: 6 additions & 74 deletions openhantek/src/docks/HorizontalDock.h
Original file line number Diff line number Diff line change
@@ -7,18 +7,10 @@

#include <vector>

#include "hantekdso/enums.h"

class QLabel;
class QCheckBox;
class QComboBox;

class SiSpinBox;

struct DsoSettingsScope;

Q_DECLARE_METATYPE(std::vector<unsigned>)
Q_DECLARE_METATYPE(std::vector<double>)
namespace Settings {
class Scope;
}
class DsoControl;

/// \brief Dock window for the horizontal axis.
/// It contains the settings for the timebase and the display format.
@@ -30,69 +22,9 @@ class HorizontalDock : public QDockWidget {
/// \param settings The target settings object.
/// \param parent The parent widget.
/// \param flags Flags for the window manager.
HorizontalDock(DsoSettingsScope *scope, QWidget *parent, Qt::WindowFlags flags = 0);

/// \brief Changes the frequencybase.
/// \param frequencybase The frequencybase in hertz.
void setFrequencybase(double timebase);
/// \brief Changes the samplerate.
/// \param samplerate The samplerate in seconds.
void setSamplerate(double samplerate);
/// \brief Changes the timebase.
/// \param timebase The timebase in seconds.
double setTimebase(double timebase);
/// \brief Changes the record length if the new value is supported.
/// \param recordLength The record length in samples.
void setRecordLength(unsigned int recordLength);
/// \brief Changes the format if the new value is supported.
/// \param format The format for the horizontal axis.
/// \return Index of format-value, -1 on error.
int setFormat(Dso::GraphFormat format);
/// \brief Updates the available record lengths in the combo box.
/// \param recordLengths The available record lengths for the combo box.
void setAvailableRecordLengths(const std::vector<unsigned> &recordLengths);
/// \brief Updates the minimum and maximum of the samplerate spin box.
/// \param minimum The minimum value the spin box should accept.
/// \param maximum The minimum value the spin box should accept.
void setSamplerateLimits(double minimum, double maximum);
/// \brief Updates the mode and steps of the samplerate spin box.
/// \param mode The mode value the spin box should accept.
/// \param steps The steps value the spin box should accept.
void setSamplerateSteps(int mode, QList<double> sampleSteps);
HorizontalDock(Settings::Scope *scope, DsoControl *dsocontrol, QWidget *parent,
Qt::WindowFlags flags = 0);

protected:
void closeEvent(QCloseEvent *event);

QGridLayout *dockLayout; ///< The main layout for the dock window
QWidget *dockWidget; ///< The main widget for the dock window
QLabel *samplerateLabel; ///< The label for the samplerate spinbox
QLabel *timebaseLabel; ///< The label for the timebase spinbox
QLabel *frequencybaseLabel; ///< The label for the frequencybase spinbox
QLabel *recordLengthLabel; ///< The label for the record length combobox
QLabel *formatLabel; ///< The label for the format combobox
SiSpinBox *samplerateSiSpinBox; ///< Selects the samplerate for aquisitions
SiSpinBox *timebaseSiSpinBox; ///< Selects the timebase for voltage graphs
SiSpinBox *frequencybaseSiSpinBox; ///< Selects the frequencybase for spectrum graphs
QComboBox *recordLengthComboBox; ///< Selects the record length for aquisitions
QComboBox *formatComboBox; ///< Selects the way the sampled data is
/// interpreted and shown

DsoSettingsScope *scope; ///< The settings provided by the parent class
QList<double> timebaseSteps; ///< Steps for the timebase spinbox

QStringList formatStrings; ///< Strings for the formats

protected slots:
void frequencybaseSelected(double frequencybase);
void samplerateSelected(double samplerate);
void timebaseSelected(double timebase);
void recordLengthSelected(int index);
void formatSelected(int index);

signals:
void frequencybaseChanged(double frequencybase); ///< The frequencybase has been changed
void samplerateChanged(double samplerate); ///< The samplerate has been changed
void timebaseChanged(double timebase); ///< The timebase has been changed
void recordLengthChanged(unsigned long recordLength); ///< The recordd length has been changed
void formatChanged(Dso::GraphFormat format); ///< The viewing format has been changed
};
100 changes: 0 additions & 100 deletions openhantek/src/docks/SpectrumDock.cpp

This file was deleted.

62 changes: 0 additions & 62 deletions openhantek/src/docks/SpectrumDock.h

This file was deleted.

170 changes: 88 additions & 82 deletions openhantek/src/docks/TriggerDock.cpp
Original file line number Diff line number Diff line change
@@ -12,105 +12,111 @@
#include "TriggerDock.h"
#include "dockwindows.h"

#include "hantekdso/controlspecification.h"
#include "hantekdso/devicesettings.h"
#include "hantekdso/dsocontrol.h"
#include "hantekdso/modelspecification.h"
#include "settings.h"
#include "sispinbox.h"
#include "utils/enumhelper.h"
#include "utils/printutils.h"
#include "widgets/sispinbox.h"

TriggerDock::TriggerDock(DsoSettingsScope *scope, const Dso::ControlSpecification *spec, QWidget *parent,
Qt::WindowFlags flags)
: QDockWidget(tr("Trigger"), parent, flags), scope(scope), mSpec(spec) {
TriggerDock::TriggerDock(Settings::Scope *scope, DsoControl *dsocontrol, QWidget *parent, Qt::WindowFlags flags)
: QDockWidget(tr("Trigger"), parent, flags) {

// Initialize lists for comboboxes
for (ChannelID channel = 0; channel < mSpec->channels; ++channel)
this->sourceStandardStrings << tr("CH%1").arg(channel + 1);
for (const Dso::SpecialTriggerChannel &specialTrigger : mSpec->specialTriggerChannels)
this->sourceSpecialStrings.append(QString::fromStdString(specialTrigger.name));
QGridLayout *dockLayout; ///< The main layout for the dock window
QWidget *dockWidget; ///< The main widget for the dock window
QLabel *modeLabel; ///< The label for the trigger mode combobox
QLabel *sourceLabel; ///< The label for the trigger source combobox
QLabel *slopeLabel; ///< The label for the trigger slope combobox
QComboBox *modeComboBox; ///< Select the triggering mode
QComboBox *sourceComboBox; ///< Select the source for triggering
QComboBox *slopeComboBox; ///< Select the slope that causes triggering

auto spec = dsocontrol->deviceSettings()->spec;

// Initialize elements
this->modeLabel = new QLabel(tr("Mode"));
this->modeComboBox = new QComboBox();
for (Dso::TriggerMode mode : mSpec->triggerModes) this->modeComboBox->addItem(Dso::triggerModeString(mode));

this->slopeLabel = new QLabel(tr("Slope"));
this->slopeComboBox = new QComboBox();
for (Dso::Slope slope : Dso::SlopeEnum) this->slopeComboBox->addItem(Dso::slopeString(slope));

this->sourceLabel = new QLabel(tr("Source"));
this->sourceComboBox = new QComboBox();
this->sourceComboBox->addItems(this->sourceStandardStrings);
this->sourceComboBox->addItems(this->sourceSpecialStrings);

this->dockLayout = new QGridLayout();
this->dockLayout->setColumnMinimumWidth(0, 64);
this->dockLayout->setColumnStretch(1, 1);
this->dockLayout->addWidget(this->modeLabel, 0, 0);
this->dockLayout->addWidget(this->modeComboBox, 0, 1);
this->dockLayout->addWidget(this->sourceLabel, 1, 0);
this->dockLayout->addWidget(this->sourceComboBox, 1, 1);
this->dockLayout->addWidget(this->slopeLabel, 2, 0);
this->dockLayout->addWidget(this->slopeComboBox, 2, 1);

this->dockWidget = new QWidget();
modeLabel = new QLabel(tr("Mode"));
modeComboBox = new QComboBox();
for (Dso::TriggerMode mode : spec->triggerModes) modeComboBox->addItem(Dso::triggerModeString(mode));

slopeLabel = new QLabel(tr("Slope"));
slopeComboBox = new QComboBox();
for (Dso::Slope slope : Enum<Dso::Slope>()) slopeComboBox->addItem(Dso::slopeString(slope));

sourceLabel = new QLabel(tr("Source"));
sourceComboBox = new QComboBox();
for (auto *c : *scope)
if (!c->isMathChannel()) sourceComboBox->addItem(tr("CH%1").arg(c->channelID() + 1), (int)c->channelID());
int specialID = -1; // Assign negative (beginning with -1) ids for special channels
for (const Dso::SpecialTriggerChannel &specialTrigger : spec->specialTriggerChannels)
sourceComboBox->addItem(QString::fromStdString(specialTrigger.name), (int)specialID--);

dockLayout = new QGridLayout();
dockLayout->setColumnMinimumWidth(0, 64);
dockLayout->setColumnStretch(1, 1);
dockLayout->addWidget(modeLabel, 0, 0);
dockLayout->addWidget(modeComboBox, 0, 1);
dockLayout->addWidget(sourceLabel, 1, 0);
dockLayout->addWidget(sourceComboBox, 1, 1);
dockLayout->addWidget(slopeLabel, 2, 0);
dockLayout->addWidget(slopeComboBox, 2, 1);

dockWidget = new QWidget();
SetupDockWidget(this, dockWidget, dockLayout);

const Dso::DeviceSettings *devicesettings = dsocontrol->deviceSettings().get();

// Set values
setMode(scope->trigger.mode);
setSlope(scope->trigger.slope);
setSource(scope->trigger.special, scope->trigger.source);

// Connect signals and slots
connect(this->modeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
[this, spec](int index) {
this->scope->trigger.mode = mSpec->triggerModes[(unsigned)index];
emit modeChanged(this->scope->trigger.mode);
modeComboBox->setCurrentIndex(spec->indexOfTriggerMode(devicesettings->trigger.mode()));
slopeComboBox->setCurrentIndex((int)devicesettings->trigger.slope());
// A special channel is after all real channels
sourceComboBox->setCurrentIndex(devicesettings->trigger.special() ? (int)spec->channels
: 0 + (int)devicesettings->trigger.source());

// Connect widgets --> settings
connect(modeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
[spec, dsocontrol, modeComboBox, devicesettings](int index) {
dsocontrol->setTriggerMode(spec->triggerModes[(unsigned)index]);
QSignalBlocker blocker(modeComboBox);
modeComboBox->setCurrentIndex(spec->indexOfTriggerMode(devicesettings->trigger.mode()));
});
connect(slopeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
[dsocontrol, slopeComboBox, devicesettings](int index) {
dsocontrol->setTriggerSlope((Dso::Slope)index);
QSignalBlocker blocker(slopeComboBox);
slopeComboBox->setCurrentIndex((int)devicesettings->trigger.slope());
});
connect(this->slopeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
[this](int index) {
this->scope->trigger.slope = (Dso::Slope)index;
emit slopeChanged(this->scope->trigger.slope);
connect(sourceComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
[sourceComboBox, devicesettings, dsocontrol, spec](int index) {
int channelIndex = sourceComboBox->itemData(index, Qt::UserRole).toInt();
dsocontrol->setTriggerSource(channelIndex < 0,
channelIndex < 0 ? (unsigned)(1 + -channelIndex) : (unsigned)channelIndex);
QSignalBlocker blocker(sourceComboBox);
sourceComboBox->setCurrentIndex(devicesettings->trigger.special()
? (int)spec->channels
: 0 + (int)devicesettings->trigger.source());
});
connect(this->sourceComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
[this](int index) {
bool special = false;

if (index >= this->sourceStandardStrings.count()) {
index -= this->sourceStandardStrings.count();
special = true;
}

this->scope->trigger.source = (unsigned)index;
this->scope->trigger.special = special;
emit sourceChanged(special, (unsigned)index);
// Connect settings --> widgets
connect(&devicesettings->trigger, &Dso::Trigger::modeChanged, this, [modeComboBox, spec](Dso::TriggerMode mode) {
QSignalBlocker blocker(modeComboBox);
modeComboBox->setCurrentIndex(spec->indexOfTriggerMode(mode));
});
connect(&devicesettings->trigger, &Dso::Trigger::sourceChanged, this,
[this, sourceComboBox, spec](bool special, unsigned int id) {
QSignalBlocker blocker(sourceComboBox);
// A special channel is after all real channels
sourceComboBox->setCurrentIndex(special ? (int)spec->channels : 0 + (int)id);
});
connect(&devicesettings->trigger, &Dso::Trigger::slopeChanged, this, [slopeComboBox](Dso::Slope slope) {
QSignalBlocker blocker(slopeComboBox);
slopeComboBox->setCurrentIndex((int)slope);
});
}

/// \brief Don't close the dock, just hide it
/// \param event The close event that should be handled.
void TriggerDock::closeEvent(QCloseEvent *event) {
this->hide();
hide();

event->accept();
}

void TriggerDock::setMode(Dso::TriggerMode mode) {
int index = std::find(mSpec->triggerModes.begin(), mSpec->triggerModes.end(), mode) - mSpec->triggerModes.begin();
QSignalBlocker blocker(modeComboBox);
modeComboBox->setCurrentIndex(index);
}

void TriggerDock::setSlope(Dso::Slope slope) {
QSignalBlocker blocker(slopeComboBox);
slopeComboBox->setCurrentIndex((int)slope);
}

void TriggerDock::setSource(bool special, unsigned int id) {
if ((!special && id >= (unsigned int)this->sourceStandardStrings.count()) ||
(special && id >= (unsigned int)this->sourceSpecialStrings.count()))
return;

int index = (int)id;
if (special) index += this->sourceStandardStrings.count();
QSignalBlocker blocker(sourceComboBox);
sourceComboBox->setCurrentIndex(index);
}
45 changes: 8 additions & 37 deletions openhantek/src/docks/TriggerDock.h
Original file line number Diff line number Diff line change
@@ -2,19 +2,22 @@

#pragma once

#include <QCheckBox>
#include <QComboBox>
#include <QDockWidget>
#include <QGridLayout>
#include <QLabel>
#include <QCheckBox>
#include <QComboBox>

#include "hantekdso/enums.h"

class SiSpinBox;
struct DsoSettingsScope;
namespace Settings {
class Scope;
}
namespace Dso {
struct ControlSpecification;
struct ModelSpec;
}
class DsoControl;

/// \brief Dock window for the trigger settings.
/// It contains the settings for the trigger mode, source and slope.
@@ -27,40 +30,8 @@ class TriggerDock : public QDockWidget {
/// \param spec
/// \param parent The parent widget.
/// \param flags Flags for the window manager.
TriggerDock(DsoSettingsScope *scope, const Dso::ControlSpecification* mSpec, QWidget *parent, Qt::WindowFlags flags = 0);

/// \brief Changes the trigger mode if the new mode is supported.
/// \param mode The trigger mode.
void setMode(Dso::TriggerMode mode);

/// \brief Changes the trigger source if the new source is supported.
/// \param special true for a special channel (EXT, ...) as trigger source.
/// \param id The number of the channel, that should be used as trigger.
void setSource(bool special, unsigned int id);

/// \brief Changes the trigger slope if the new slope is supported.
/// \param slope The trigger slope.
void setSlope(Dso::Slope slope);
TriggerDock(Settings::Scope *scope, DsoControl *dsocontrol, QWidget *parent, Qt::WindowFlags flags = 0);

protected:
void closeEvent(QCloseEvent *event);

QGridLayout *dockLayout; ///< The main layout for the dock window
QWidget *dockWidget; ///< The main widget for the dock window
QLabel *modeLabel; ///< The label for the trigger mode combobox
QLabel *sourceLabel; ///< The label for the trigger source combobox
QLabel *slopeLabel; ///< The label for the trigger slope combobox
QComboBox *modeComboBox; ///< Select the triggering mode
QComboBox *sourceComboBox; ///< Select the source for triggering
QComboBox *slopeComboBox; ///< Select the slope that causes triggering

DsoSettingsScope *scope; ///< The settings provided by the parent class
const Dso::ControlSpecification* mSpec;

QStringList sourceStandardStrings; ///< Strings for the standard trigger sources
QStringList sourceSpecialStrings; ///< Strings for the special trigger sources
signals:
void modeChanged(Dso::TriggerMode); ///< The trigger mode has been changed
void sourceChanged(bool special, unsigned int id); ///< The trigger source has been changed
void slopeChanged(Dso::Slope); ///< The trigger slope has been changed
};
129 changes: 0 additions & 129 deletions openhantek/src/docks/VoltageDock.cpp

This file was deleted.

76 changes: 0 additions & 76 deletions openhantek/src/docks/VoltageDock.h

This file was deleted.

288 changes: 288 additions & 0 deletions openhantek/src/docks/VoltageOrSpectrumDock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
// SPDX-License-Identifier: GPL-2.0+

#include <QCheckBox>
#include <QCloseEvent>
#include <QComboBox>
#include <QDockWidget>
#include <QGroupBox>
#include <QLabel>
#include <QPushButton>
#include <QSignalBlocker>

#include <cmath>

#include "VoltageOrSpectrumDock.h"
#include "dockwindows.h"

#include "hantekdso/dsocontrol.h"
#include "scopesettings.h"
#include "utils/enumhelper.h"
#include "utils/printutils.h"
#include "widgets/sispinbox.h"

template <typename... Args> struct SELECT {
template <typename C, typename R> static constexpr auto OVERLOAD_OF(R (C::*pmf)(Args...)) -> decltype(pmf) {
return pmf;
}
};

VoltageOrSpectrumDock::VoltageOrSpectrumDock(bool isSpectrum, Settings::Scope *scope, DsoControl *dsocontrol,
QWidget *parent, Qt::WindowFlags flags)
: QDockWidget(isSpectrum ? tr("Spectrum") : tr("Voltage"), parent, flags), m_isSpectrum(isSpectrum) {

const Dso::DeviceSettings *deviceSettings = dsocontrol->deviceSettings().get();

// Initialize lists for comboboxes
for (Dso::Coupling c : deviceSettings->spec->couplings) couplingStrings.append(Dso::couplingString(c));
for (auto e : Enum<PostProcessing::MathMode>()) { modeStrings.append(PostProcessing::mathModeString(e)); }
magnitudeSteps = {1e0, 2e0, 3e0, 6e0, 1e1, 2e1, 3e1, 6e1, 1e2, 2e2, 3e2, 6e2};
for (const auto &magnitude : magnitudeSteps) magnitudeStrings << valueToString(magnitude, Unit::DECIBEL, 0);

QWidget *dockWidget = new QWidget();
dockLayout = new QVBoxLayout();
SetupDockWidget(this, dockWidget, dockLayout);

// Create widgets for each channel and connect to math channels changed signal to recreate if necessary
// Initialize elements
for (Settings::Channel *channel : *scope) {
if (!channel->isMathChannel()) createChannelWidgets(scope, dsocontrol, deviceSettings, channel);
}

auto btnAdd = new QPushButton(tr("Add math channel"), this);
connect(btnAdd, &QPushButton::clicked, this, [scope, deviceSettings, dsocontrol]() {
scope->addMathChannel(dsocontrol->channelUsage(), deviceSettings);
});
dockLayout->addWidget(btnAdd);

for (Settings::Channel *channel : *scope) {
if (channel->isMathChannel()) createChannelWidgets(scope, dsocontrol, deviceSettings, channel);
}

connect(scope, &Settings::Scope::mathChannelAdded, this,
[this, scope, dsocontrol, deviceSettings](Settings::Channel *channel) {
createChannelWidgets(scope, dsocontrol, deviceSettings, channel);
});
}

/// \brief Don't close the dock, just hide it
/// \param event The close event that should be handled.
void VoltageOrSpectrumDock::closeEvent(QCloseEvent *event) {
hide();
event->accept();
}

void VoltageOrSpectrumDock::setMagnitude(QComboBox *magnitudeComboBox, double magnitude) {
QSignalBlocker blocker(magnitudeComboBox);

auto indexIt = std::find(magnitudeSteps.begin(), magnitudeSteps.end(), magnitude);
if (indexIt == magnitudeSteps.end()) return;
int index = (int)std::distance(magnitudeSteps.begin(), indexIt);
magnitudeComboBox->setCurrentIndex(index);
}

void VoltageOrSpectrumDock::fillGainBox(QComboBox *gainComboBox, Settings::Scope *scope, DsoControl *dsocontrol,
Settings::Channel *channel) {
QSignalBlocker b(gainComboBox);
gainComboBox->clear();
if (scope->useHardwareGainSteps()) {
for (auto &gainStep : dsocontrol->specification()->gain)
gainComboBox->addItem(valueToString(gainStep.gain, Unit::VOLTS, 0), gainStep.gainIdentificator);
gainComboBox->setCurrentIndex((int)channel->voltage()->gainStepIndex());
} else {
int index = -1;
for (unsigned i = 0; i < gainValue.size(); ++i) {
if (channel->gain() >= gainValue[i]) index = (int)i;
gainComboBox->addItem(valueToString(gainValue[i], Unit::VOLTS, 0));
}
gainComboBox->setToolTip(
tr("Hardware Gain Index: %1").arg(findMatchingHardwareGainId(channel->gain(), dsocontrol)));
gainComboBox->setCurrentIndex(index);
}
}

int VoltageOrSpectrumDock::findMatchingHardwareGainId(double gain, DsoControl *dsocontrol) {
int matchingHardwareIndex = 0;
auto hwGains = dsocontrol->specification()->gain;
for (unsigned hIndex = 0; hIndex < hwGains.size(); ++hIndex) {
if (gain < hwGains[hIndex].gain) { break; }
matchingHardwareIndex = hIndex;
}
return matchingHardwareIndex;
}

void VoltageOrSpectrumDock::createChannelWidgets(Settings::Scope *scope, DsoControl *dsocontrol,
const Dso::DeviceSettings *deviceSettings,
Settings::Channel *channel) {

// Create a common parent, that is deleted when the dock is deleted (due to having the dock as parent)
// as well as getting deleted when the corresponding channel vanishes.
QGroupBox *channelParent = new QGroupBox(this);
channelParent->setTitle(channel->name());
channelParentWidgets.push_back(channelParent);
channelParent->setCheckable(true);
connect(channel, &QObject::destroyed, channelParent, &QObject::deleteLater);
dockLayout->addWidget(channelParent);

auto layout = new QVBoxLayout(channelParent);
channelParent->setLayout(layout);

// Spectrum properties
if (isSpectrum()) {
channelParent->setChecked(channel->spectrum()->visible());

QComboBox *magnitudeComboBox = new QComboBox(channelParent);
magnitudeComboBox->addItems(magnitudeStrings);
layout->addWidget(magnitudeComboBox);
setMagnitude(magnitudeComboBox, channel->spectrum()->magnitude());

// Connect widgets --> settings
connect(channelParent, &QGroupBox::toggled,
[scope, channel](bool checked) { channel->setSpectrumVisible(checked); });

connect(
magnitudeComboBox, SELECT<int>::OVERLOAD_OF(&QComboBox::currentIndexChanged), channelParent,
[scope, channel, this](unsigned index) { channel->spectrum()->setMagnitude(magnitudeSteps.at(index)); });
// Connect settings --> widgets
connect(channel->spectrum(), &Settings::Spectrum::magnitudeChanged, channelParent,
[this, magnitudeComboBox](const Settings::Spectrum *spectrum) {
setMagnitude(magnitudeComboBox, spectrum->magnitude());
});

connect(channel->spectrum(), &Settings::Spectrum::visibleChanged, channelParent, [channelParent](bool visible) {
QSignalBlocker blocker(channelParent);
channelParent->setChecked(visible);
});
return;
}

// Voltage properties

channelParent->setChecked(channel->visible());
// Connect widgets --> settings
connect(channelParent, &QGroupBox::toggled, channelParent,
[channel, dsocontrol](bool checked) { channel->setVoltageVisible(checked); });
// Connect settings --> widgets
connect(channel, &Settings::Channel::visibleChanged, channelParent, [channelParent](bool used) {
QSignalBlocker blocker(channelParent);
channelParent->setChecked(used);
});

auto sublayout = new QHBoxLayout; // Invert + Gain + coupling next to each other
layout->addLayout(sublayout);

/////// Invert ///////
QCheckBox *invertCheckBox = new QCheckBox(tr("INV"), channelParent);
invertCheckBox->setToolTip(tr("Invert channel on x-axes"));
invertCheckBox->setChecked(channel->inverted());
sublayout->addWidget(invertCheckBox);
connect(invertCheckBox, &QAbstractButton::toggled, channelParent,
[this, channel](bool checked) { channel->setInverted(checked); });
connect(channel, &Settings::Channel::invertedChanged, channelParent, [invertCheckBox](bool inverted) {
QSignalBlocker blocker(invertCheckBox);
invertCheckBox->setChecked(inverted);
});

/////// The voltage gain steps in V ///////
QComboBox *gainComboBox = new QComboBox(channelParent);
fillGainBox(gainComboBox, scope, dsocontrol, channel);
connect(scope, &Settings::Scope::useHardwareGainChanged, this, [this, gainComboBox, scope, dsocontrol, channel]() {
fillGainBox(gainComboBox, scope, dsocontrol, channel);
});
connect(gainComboBox, SELECT<int>::OVERLOAD_OF(&QComboBox::currentIndexChanged), channelParent,
[channel, dsocontrol, scope, this, gainComboBox](int index) {
double newGain;
if (scope->useHardwareGainSteps()) {
newGain = dsocontrol->specification()->gain[(unsigned)index].gain;
dsocontrol->setGain(channel->channelID(), (unsigned)index);
} else {
newGain = gainValue[index];
int matchingHardwareIndex = findMatchingHardwareGainId(newGain, dsocontrol);
gainComboBox->setToolTip(tr("Hardware Gain Index: %1").arg(matchingHardwareIndex));
dsocontrol->setGain(channel->channelID(), (unsigned)matchingHardwareIndex);
}
channel->setGain(newGain);
});
connect(channel->voltage(), &Dso::Channel::gainStepIndexChanged, channelParent,
[gainComboBox, scope](unsigned gainId) {
if (!scope->useHardwareGainSteps()) return; ///< Do nothing if we are not using hardware gain steps
QSignalBlocker blocker(gainComboBox);
gainComboBox->setCurrentIndex((int)gainId);
});
sublayout->addWidget(gainComboBox);

if (channel->isMathChannel()) {
Settings::MathChannel *mathChannel = static_cast<Settings::MathChannel *>(channel);
auto mathlayout = new QHBoxLayout; // Channel selection and mathmode next to each other
layout->addLayout(mathlayout);

QComboBox *mathChannel1 = new QComboBox(channelParent);
mathlayout->addWidget(mathChannel1);
QComboBox *mathModeComboBox = new QComboBox(channelParent);
mathlayout->addWidget(mathModeComboBox);
QComboBox *mathChannel2 = new QComboBox(channelParent);
mathlayout->addWidget(mathChannel2);

for (Settings::Channel *c : *scope) {
if (c->isMathChannel()) continue;
mathChannel1->addItem(c->name(), c->channelID());
mathChannel2->addItem(c->name(), c->channelID());
if (mathChannel->firstID() == c->channelID()) mathChannel1->setCurrentIndex(mathChannel1->count() - 1);
if (mathChannel->secondID() == c->channelID()) mathChannel2->setCurrentIndex(mathChannel2->count() - 1);
}

mathModeComboBox->addItems(modeStrings);
mathModeComboBox->setCurrentIndex((int)mathChannel->mathMode());

// Connect widgets --> settings
connect(mathModeComboBox, SELECT<int>::OVERLOAD_OF(&QComboBox::currentIndexChanged), channelParent,
[mathChannel](int index) { mathChannel->setMathMode((PostProcessing::MathMode)index); });
connect(mathChannel1, SELECT<int>::OVERLOAD_OF(&QComboBox::currentIndexChanged), channelParent,
[mathChannel, deviceSettings](int index) {
mathChannel->setFirstChannel((unsigned)index, deviceSettings->voltage[(unsigned)index]);
});
connect(mathChannel2, SELECT<int>::OVERLOAD_OF(&QComboBox::currentIndexChanged), channelParent,
[mathChannel, deviceSettings](int index) {
mathChannel->setSecondChannel((unsigned)index, deviceSettings->voltage[(unsigned)index]);
});
// Connect settings --> widgets
connect(mathChannel, &Settings::MathChannel::mathModeChanged, channelParent,
[mathModeComboBox](const Settings::Channel *channel) {
QSignalBlocker blocker(mathModeComboBox);
mathModeComboBox->setCurrentIndex((int)((Settings::MathChannel *)channel)->mathMode());
});
connect(mathChannel, &Settings::MathChannel::firstChannelChanged, channelParent,
[mathChannel1](ChannelID channel) {
if (channel == UINT_MAX) return;
QSignalBlocker blocker(mathChannel1);
mathChannel1->setCurrentIndex((int)channel);
});
connect(mathChannel, &Settings::MathChannel::firstChannelChanged, channelParent,
[mathChannel2](ChannelID channel) {
if (channel == UINT_MAX) return;
QSignalBlocker blocker(mathChannel2);
mathChannel2->setCurrentIndex((int)channel);
});

auto btnRemove = new QPushButton(tr("Remove"), channelParent);
connect(btnRemove, &QPushButton::clicked, channelParent,
[channel, scope]() { scope->removeMathChannel(channel->channelID()); });
layout->addWidget(btnRemove);
connect(channelParent, &QGroupBox::toggled, this, [btnRemove](bool) { btnRemove->setEnabled(true); });
btnRemove->setEnabled(true);
} else {
QComboBox *couplingComboBox = new QComboBox(channelParent);
couplingComboBox->addItems(couplingStrings);
couplingComboBox->setCurrentIndex((int)channel->voltage()->couplingIndex());
// Connect widgets --> settings
connect(
couplingComboBox, SELECT<int>::OVERLOAD_OF(&QComboBox::currentIndexChanged), channelParent,
[channel, dsocontrol](int index) { dsocontrol->setCoupling(channel->channelID(), (Dso::Coupling)index); });
// Connect settings --> widgets
connect(channel->voltage(), &Dso::Channel::couplingIndexChanged, channelParent,
[couplingComboBox](unsigned couplingIndex) {
QSignalBlocker blocker(couplingComboBox);
couplingComboBox->setCurrentIndex((int)couplingIndex);
});
sublayout->addWidget(couplingComboBox);
}
}
53 changes: 53 additions & 0 deletions openhantek/src/docks/VoltageOrSpectrumDock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-2.0+

#pragma once

#include <QCheckBox>
#include <QComboBox>
#include <QDockWidget>
#include <QGridLayout>
#include <QLabel>

#include "hantekdso/modelspecification.h"
#include "post/postprocessingsettings.h"
#include "scopesettings.h"

class SiSpinBox;
class DsoControl;

/// \brief Dock window for the voltage channel settings.
/// It contains the settings for gain and coupling for both channels and
/// allows to enable/disable the channels.
class VoltageOrSpectrumDock : public QDockWidget {
Q_OBJECT

public:
/// \brief Initializes the vertical axis docking window.
/// \param settings The target settings object.
/// \param parent The parent widget.
/// \param flags Flags for the window manager.
VoltageOrSpectrumDock(bool isSpectrum, Settings::Scope *scope, DsoControl *dsocontrol, QWidget *parent,
Qt::WindowFlags flags = 0);
inline bool isSpectrum() const { return m_isSpectrum; }

protected:
void closeEvent(QCloseEvent *event);
void createChannelWidgets(Settings::Scope *scope, DsoControl *dsocontrol,
const Dso::DeviceSettings *deviceSettings, Settings::Channel *channel);
void setMagnitude(QComboBox *magnitudeComboBox, double magnitude);
void fillGainBox(QComboBox *gainComboBox, Settings::Scope *scope, DsoControl *dsocontrol,
Settings::Channel *channel);
int findMatchingHardwareGainId(double gain,DsoControl *dsocontrol);

QVBoxLayout *dockLayout;
std::list<QWidget *> channelParentWidgets;
const bool m_isSpectrum;

/// Selectable voltage gain steps in V, if use-hardwareGainSteps is disabled
std::vector<double> gainValue = {1e-2, 2e-2, 5e-2, 1e-1, 2e-1, 5e-1, 1e0, 2e0, 5e0};

std::vector<double> magnitudeSteps; ///< The selectable magnitude steps in dB/div
QStringList magnitudeStrings; ///< String representations for the magnitude steps
QStringList couplingStrings; ///< The strings for the couplings
QStringList modeStrings; ///< The strings for the math mode
};
16 changes: 8 additions & 8 deletions openhantek/src/docks/dockwindows.cpp
Original file line number Diff line number Diff line change
@@ -8,29 +8,29 @@

#include <cmath>

#include "post/postprocessingsettings.h"
#include "dockwindows.h"
#include "hantekdso/enums.h"
#include "hantekprotocol/types.h"
#include "dockwindows.h"
#include "post/postprocessingsettings.h"

void SetupDockWidget(QDockWidget *dockWindow, QWidget *dockWidget, QLayout *layout) {
void SetupDockWidget(QDockWidget *dockWindow, QWidget *dockWidget, QLayout *layout, QSizePolicy::Policy vPolicy) {
dockWindow->setObjectName(dockWindow->windowTitle());
dockWindow->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
dockWidget->setLayout(layout);
dockWidget->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed, QSizePolicy::DefaultType));
dockWidget->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, vPolicy, QSizePolicy::DefaultType));
dockWindow->setWidget(dockWidget);
}

void registerDockMetaTypes() {
qRegisterMetaType<Dso::TriggerMode>();
qRegisterMetaType<Dso::MathMode>();
qRegisterMetaType<PostProcessing::MathMode>();
qRegisterMetaType<Dso::Slope>();
qRegisterMetaType<Dso::Coupling>();
qRegisterMetaType<Dso::GraphFormat>();
qRegisterMetaType<Dso::ChannelMode>();
qRegisterMetaType<Dso::WindowFunction>();
qRegisterMetaType<PostProcessing::WindowFunction>();
qRegisterMetaType<Dso::InterpolationMode>();
qRegisterMetaType<std::vector<unsigned> >();
qRegisterMetaType<std::vector<double> >();
qRegisterMetaType<std::vector<unsigned>>();
qRegisterMetaType<std::vector<double>>();
qRegisterMetaType<ChannelID>("ChannelID");
}
3 changes: 2 additions & 1 deletion openhantek/src/docks/dockwindows.h
Original file line number Diff line number Diff line change
@@ -6,4 +6,5 @@
#include <QLayout>

void registerDockMetaTypes();
void SetupDockWidget(QDockWidget *dockWindow, QWidget *dockWidget, QLayout *layout);
void SetupDockWidget(QDockWidget *dockWindow, QWidget *dockWidget, QLayout *layout,
QSizePolicy::Policy vPolicy = QSizePolicy::Fixed);
253 changes: 253 additions & 0 deletions openhantek/src/docks/gainAdjustDock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
// SPDX-License-Identifier: GPL-2.0+

#include <QCheckBox>
#include <QCloseEvent>
#include <QComboBox>
#include <QCoreApplication>
#include <QDockWidget>
#include <QDoubleSpinBox>
#include <QLabel>
#include <QMessageBox>
#include <QSignalBlocker>
#include <QStringListModel>

#include <cmath>

#include "gainAdjustDock.h"

#include "dockwindows.h"
#include "hantekdso/devicesettings.h"
#include "hantekdso/dsocontrol.h"
#include "hantekdso/enums.h"
#include "hantekdso/modelspecification.h"
#include "hantekprotocol/codes.h"
#include "iconfont/QtAwesome.h"
#include "post/selfcalibration.h"
#include "scopesettings.h"
#include "utils/debugnotify.h"
#include "utils/enumhelper.h"
#include "utils/printutils.h"
#include "widgets/sispinbox.h"

#include <QDebug>
#include <QHeaderView>
#include <QLineEdit>
#include <QProgressBar>
#include <QPushButton>
#include <QScrollArea>
#include <QTableView>

Q_DECLARE_METATYPE(std::vector<unsigned>)
Q_DECLARE_METATYPE(std::vector<double>)

template <typename... Args> struct SELECT {
template <typename C, typename R> static constexpr auto OVERLOAD_OF(R (C::*pmf)(Args...)) -> decltype(pmf) {
return pmf;
}
};

using namespace Debug;

GainAdjustDock::GainAdjustDock(DsoControl *dsocontrol, SelfCalibration *selfCalibration, QWidget *parent,
Qt::WindowFlags flags)
: QDockWidget(tr("Calibration"), parent, flags) {

Dso::ModelSpec *spec = const_cast<Dso::ModelSpec *>(dsocontrol->deviceSettings()->spec);

QVBoxLayout *dockLayout = new QVBoxLayout;
QScrollArea *scroll = new QScrollArea(this);
QGridLayout *grid = new QGridLayout;
QWidget *dockWidget = new QWidget(this);

QWidget *scrollWidget = new QWidget(this);
scrollWidget->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding));
grid->setSizeConstraint(QLayout::SetFixedSize);
scrollWidget->setLayout(grid);
scroll->setWidget(scrollWidget);
scroll->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding));
dockLayout->addWidget(scroll, 1);

QHBoxLayout *btns = new QHBoxLayout;
QPushButton *btnCalibrationStart = new QPushButton(tr("Self-calibration"));
btnCalibrationStart->setIcon(iconFont->icon(fa::warning));

QPushButton *btnHelp = new QPushButton();
btnHelp->setIcon(iconFont->icon(fa::info));

QPushButton *btnApply = new QPushButton(this);
btnApply->setIcon(iconFont->icon(fa::check));
btnApply->setText(tr("Apply"));
btnApply->setEnabled(false);
dockLayout->addWidget(btnApply);

///// Dialog for self calibration /////
QDialog *calibrationDialog = new QDialog(this);
calibrationDialog->setWindowTitle(tr("Self-calibration"));
calibrationDialog->setModal(true);
QVBoxLayout *dialogLayout = new QVBoxLayout(calibrationDialog);
QLabel *dialogLabel = new QLabel(calibrationDialog);
QProgressBar *dialogProgress = new QProgressBar(calibrationDialog);
dialogProgress->setRange(0, 100);
connect(selfCalibration, &SelfCalibration::progress, this,
[dialogLabel, dialogProgress](double progress, const QString &task) {
dialogProgress->setValue(progress * 100);
dialogLabel->setText(task);
});
QPushButton *btnCalibrationCancel = new QPushButton(tr("Cancel"), calibrationDialog);
dialogLayout->addWidget(dialogLabel);
dialogLayout->addWidget(dialogProgress);
dialogLayout->addWidget(btnCalibrationCancel);

QComboBox *selfCalibChannels = new QComboBox(this);
for (ChannelID c = 0; c < spec->channels; ++c) selfCalibChannels->addItem(tr("Channel %1").arg(c + 1));
selfCalibChannels->setCurrentIndex(0);

connect(btnCalibrationCancel, &QPushButton::clicked, selfCalibration, &SelfCalibration::cancel);
connect(btnCalibrationStart, &QPushButton::clicked, selfCalibration, [selfCalibration, selfCalibChannels]() {
selfCalibration->start((unsigned)selfCalibChannels->currentIndex());
});
connect(selfCalibration, &SelfCalibration::runningChanged, this,
[btnCalibrationStart, calibrationDialog, this](bool running) {
calibrationDialog->setVisible(running);
btnCalibrationStart->setDisabled(running);
if (!running) emit selfCalibrationFinished();
});

connect(btnHelp, &QPushButton::clicked, this, [this, selfCalibChannels, spec]() {
QMessageBox::information(
this, tr("Self-calibration"),
tr("Please connect the %1 probe of your oscilloscope to GND and the test signal "
"generator. Self-calibration will adjust the gain values to match the amplitude "
"of %2V. This may be inaccurate for low gain values, because of clipping in "
"the signal.\n\nThe new values are not permanent and will be discarded on exit. If the "
"new values are an improvement in your opinion, please visit our github page "
"(Help->About) and post them in a new Issue.")
.arg(selfCalibChannels->currentText())
.arg(spec->testSignalAmplitude));
});

btns->addWidget(selfCalibChannels);
btns->addWidget(btnCalibrationStart);
btns->addStretch(1);
btns->addWidget(btnHelp);

dockLayout->addLayout(btns);

int row = 0;

// Header row
QLabel *l = new QLabel(tr("Gain\nFactor*"), this);
l->setToolTip(tr("The formula is 1V=Voltage=(RawSamplePoint/gainFactor-offset)*hardwareGainVoltage to archive "
"a 1V amplitude with the DSO included test signal."));
grid->addWidget(l, row, 1);
l = new QLabel(tr("Offset\nStart"), this);
l->setToolTip(tr("Some models allow to set a hardware offset. That value is usually limited by 8, 10 or 16bits or "
"any value up to 16bits. To compute an accurate sample set, the offset range need to be known."));
grid->addWidget(l, row, 2);
grid->addWidget(new QLabel(tr("Offset\nEnd"), this), row, 3);
l = new QLabel(tr("GND\nCorrection"), this);
l->setToolTip(tr("The signal ground offset is usually auto-calibrated, but some models do not do that. "
"Adjust these values if the ground level is not correct for you."));
grid->addWidget(l, row, 4);
grid->setColumnStretch(0, 0); ///< Columns are fixed size
grid->setColumnStretch(1, 0);
grid->setColumnStretch(2, 0);
grid->setColumnStretch(3, 0);

++row;

auto *copy = new std::vector<Dso::ModelSpec::gainStepCalibration>(spec->calibration);
connect(this, &QObject::destroyed, [copy]() { delete copy; }); // delete copy when this is destroyed

QSpinBox *edit;
QDoubleSpinBox *editD;
for (unsigned gainId = 0; gainId < spec->gain.size(); ++gainId) {
for (ChannelID channelID = 0; channelID < spec->channels; ++channelID) {
auto *l = new QLabel(
tr("%1 CH%2").arg(valueToString(spec->gain[gainId].gain, Unit::VOLTS, -1)).arg(channelID + 1), this);
grid->addWidget(l, row, 0);

editD = new QDoubleSpinBox(this);
editD->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
editD->setRange(1, 2000);
editD->setSingleStep(0.1);
editD->setValue(copy->at(channelID)[gainId].voltageLimit);
editD->setToolTip(tr("Original value is: %1").arg(editD->value()));
grid->addWidget(editD, row, 1);
connect(this, &GainAdjustDock::selfCalibrationFinished, editD, [editD, spec, channelID, gainId]() {
editD->setValue(spec->calibration[channelID][gainId].voltageLimit);
});
connect(editD, SELECT<double>::OVERLOAD_OF(&QDoubleSpinBox::valueChanged), this,
[btnApply, copy, channelID, gainId](double value) {
btnApply->setEnabled(true);
(*copy)[channelID][gainId].voltageLimit = value;
});

edit = new QSpinBox(this);
edit->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
edit->setRange(0, UINT16_MAX);
edit->setSingleStep(1);
edit->setValue(copy->at(channelID)[gainId].offsetStart);
edit->setToolTip(tr("Original value is: %1").arg(edit->value()));
grid->addWidget(edit, row, 2);
connect(this, &GainAdjustDock::selfCalibrationFinished, edit, [edit, spec, channelID, gainId]() {
edit->setValue(spec->calibration[channelID][gainId].offsetStart);
});
connect(edit, SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged), this,
[btnApply, copy, channelID, gainId](int value) {
btnApply->setEnabled(true);
(*copy)[channelID][gainId].offsetStart = (unsigned short)value;
});

edit = new QSpinBox(this);
edit->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
edit->setRange(0, UINT16_MAX);
edit->setSingleStep(1);
edit->setValue(copy->at(channelID)[gainId].offsetEnd);
edit->setToolTip(tr("Original value is: %1").arg(edit->value()));
grid->addWidget(edit, row, 3);
connect(this, &GainAdjustDock::selfCalibrationFinished, edit, [edit, spec, channelID, gainId]() {
edit->setValue(spec->calibration[channelID][gainId].offsetEnd);
});
connect(edit, SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged), this,
[btnApply, copy, channelID, gainId](int value) {
btnApply->setEnabled(true);
(*copy)[channelID][gainId].offsetEnd = (unsigned short)value;
});

editD = new QDoubleSpinBox(this);
editD->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
editD->setSingleStep(0.01);
editD->setRange(-1, 1);
editD->setValue(copy->at(channelID)[gainId].offsetCorrection);
editD->setToolTip(tr("Original value is: %1").arg(editD->value()));
grid->addWidget(editD, row, 4);
connect(this, &GainAdjustDock::selfCalibrationFinished, editD, [editD, spec, channelID, gainId]() {
editD->setValue(spec->calibration[channelID][gainId].offsetCorrection);
});
connect(editD, SELECT<double>::OVERLOAD_OF(&QDoubleSpinBox::valueChanged), this,
[btnApply, copy, channelID, gainId](double value) {
btnApply->setEnabled(true);
(*copy)[channelID][gainId].offsetCorrection = value;
});

++row;
}
}

connect(btnApply, &QPushButton::clicked, this, [dsocontrol, spec, copy, btnApply]() {
spec->calibration = *copy;
for (ChannelID channelID = 0; channelID < spec->channels; ++channelID)
dsocontrol->setOffset(channelID, dsocontrol->deviceSettings()->voltage[channelID]->offset(), true);
btnApply->setEnabled(false);
});

SetupDockWidget(this, dockWidget, dockLayout, QSizePolicy::Expanding);
}

/// \brief Don't close the dock, just hide it.
/// \param event The close event that should be handled.
void GainAdjustDock::closeEvent(QCloseEvent *event) {
this->hide();
event->accept();
}
37 changes: 37 additions & 0 deletions openhantek/src/docks/gainAdjustDock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: GPL-2.0+

#pragma once

#include <QDockWidget>
#include <QGridLayout>

#include "hantekprotocol/codes.h"
#include "utils/debugnotify.h"

namespace Dso {
struct ModelSpec;
}
class DsoControl;
namespace Debug {
class LogModel;
}
class SelfCalibration;

/// \brief Dock window for adjusting the gain factor
/// It contains the settings for the timebase and the display format.
class GainAdjustDock : public QDockWidget {
Q_OBJECT

public:
/// \brief Initializes the dock
/// \param settings The target settings object.
/// \param parent The parent widget.
/// \param flags Flags for the window manager.
GainAdjustDock(DsoControl *dsocontrol, SelfCalibration *selfCalibration, QWidget *parent,
Qt::WindowFlags flags = 0);

protected:
void closeEvent(QCloseEvent *event);
signals:
void selfCalibrationFinished();
};
614 changes: 0 additions & 614 deletions openhantek/src/dsowidget.cpp

This file was deleted.

130 changes: 0 additions & 130 deletions openhantek/src/dsowidget.h

This file was deleted.

94 changes: 40 additions & 54 deletions openhantek/src/exporting/exportcsv.cpp
Original file line number Diff line number Diff line change
@@ -2,31 +2,30 @@

#include "exportcsv.h"
#include "exporterregistry.h"
#include "iconfont/QtAwesome.h"
#include "post/ppresult.h"
#include "settings.h"
#include "iconfont/QtAwesome.h"

#include <QCoreApplication>
#include <QTextStream>
#include <QFile>
#include <QFileDialog>
#include <QTextStream>

ExporterCSV::ExporterCSV() {}
namespace Exporter {
CSV::CSV() {}

void ExporterCSV::create(ExporterRegistry *registry) { this->registry = registry; data.reset(); }
QIcon CSV::icon() { return iconFont->icon(fa::filetexto); }

QIcon ExporterCSV::icon() { return iconFont->icon(fa::filetexto); }
QString CSV::name() { return QCoreApplication::tr("Export CSV"); }

QString ExporterCSV::name() { return QCoreApplication::tr("Export CSV"); }
ExporterInterface::Type CSV::type() { return Type::SnapshotExport; }

ExporterInterface::Type ExporterCSV::type() { return Type::SnapshotExport; }
float CSV::samples(const std::shared_ptr<PPresult>) { return 1; }

bool ExporterCSV::samples(const std::shared_ptr<PPresult> data) {
this->data = std::move(data);
return false;
}
bool CSV::exportNow(Registry *registry) {
auto data = registry->lastDataSet();
if (!data) return false;

bool ExporterCSV::save() {
QStringList filters;
filters << QCoreApplication::tr("Comma-Separated Values (*.csv)");

@@ -42,63 +41,49 @@ bool ExporterCSV::save() {
csvStream.setRealNumberNotation(QTextStream::FixedNotation);
csvStream.setRealNumberPrecision(10);

size_t chCount = registry->settings->scope.voltage.size();
std::vector<const SampleValues *> voltageData(size_t(chCount), nullptr);
std::vector<const SampleValues *> spectrumData(size_t(chCount), nullptr);
std::map<ChannelID, const std::vector<double> *> voltageData;
std::map<ChannelID, const std::vector<double> *> spectrumData;
size_t maxRow = 0;
bool isSpectrumUsed = false;
double timeInterval = 0;
double freqInterval = 0;

for (ChannelID channel = 0; channel < chCount; ++channel) {
if (data->data(channel)) {
if (registry->settings->scope.voltage[channel].used) {
voltageData[channel] = &(data->data(channel)->voltage);
maxRow = std::max(maxRow, voltageData[channel]->sample.size());
timeInterval = data->data(channel)->voltage.interval;
}
if (registry->settings->scope.spectrum[channel].used) {
spectrumData[channel] = &(data->data(channel)->spectrum);
maxRow = std::max(maxRow, spectrumData[channel]->sample.size());
freqInterval = data->data(channel)->spectrum.interval;
isSpectrumUsed = true;
}
const Settings::Scope *scope = &registry->settings->scope;
for (const Settings::Channel *c : *scope) {
const DataChannel *dataChannel = data->data(c->channelID());
if (!dataChannel) continue;
if (c->visible()) {
const std::vector<double> *samples = &(dataChannel->voltage.sample);
voltageData[c->channelID()] = samples;
maxRow = std::max(maxRow, samples->size());
timeInterval = dataChannel->voltage.interval;
}
if (c->spectrum()->visible()) {
const std::vector<double> *samples = &(dataChannel->spectrum.sample);
spectrumData[c->channelID()] = samples;
maxRow = std::max(maxRow, samples->size());
freqInterval = dataChannel->spectrum.interval;
}
}

// Start with channel names
csvStream << "\"t\"";
for (ChannelID channel = 0; channel < chCount; ++channel) {
if (voltageData[channel] != nullptr) { csvStream << ",\"" << registry->settings->scope.voltage[channel].name << "\""; }
}
if (isSpectrumUsed) {
csvStream << ",\"f\"";
for (ChannelID channel = 0; channel < chCount; ++channel) {
if (spectrumData[channel] != nullptr) {
csvStream << ",\"" << registry->settings->scope.spectrum[channel].name << "\"";
}
}
}
for (auto &c : voltageData) { csvStream << ",\"" << scope->channel(c.first)->name() << "\""; }
csvStream << ",\"f\"";
for (auto &c : spectrumData) { csvStream << ",\"" << scope->channel(c.first)->name() << "\""; }
csvStream << "\n";

for (unsigned int row = 0; row < maxRow; ++row) {

csvStream << timeInterval * row;
for (ChannelID channel = 0; channel < chCount; ++channel) {
if (voltageData[channel] != nullptr) {
csvStream << ",";
if (row < voltageData[channel]->sample.size()) { csvStream << voltageData[channel]->sample[row]; }
}
for (auto &c : voltageData) {
csvStream << ",";
if (row < c.second->size()) { csvStream << (*c.second)[row]; }
}

if (isSpectrumUsed) {
csvStream << "," << freqInterval * row;
for (ChannelID channel = 0; channel < chCount; ++channel) {
if (spectrumData[channel] != nullptr) {
csvStream << ",";
if (row < spectrumData[channel]->sample.size()) { csvStream << spectrumData[channel]->sample[row]; }
}
}
csvStream << "," << freqInterval * row;
for (auto &c : spectrumData) {
csvStream << ",";
if (row < c.second->size()) { csvStream << (*c.second)[row]; }
}
csvStream << "\n";
}
@@ -108,4 +93,5 @@ bool ExporterCSV::save() {
return true;
}

float ExporterCSV::progress() { return data ? 1.0f : 0; }
QKeySequence CSV::shortcut() { return QKeySequence(); }
}
18 changes: 8 additions & 10 deletions openhantek/src/exporting/exportcsv.h
Original file line number Diff line number Diff line change
@@ -3,17 +3,15 @@
#pragma once
#include "exporterinterface.h"

class ExporterCSV : public ExporterInterface
{
public:
ExporterCSV();
virtual void create(ExporterRegistry *registry) override;
namespace Exporter {
class CSV : public ExporterInterface {
public:
CSV();
virtual bool exportNow(Registry *registry) override;
virtual QIcon icon() override;
virtual QString name() override;
virtual Type type() override;
virtual bool samples(const std::shared_ptr<PPresult>data) override;
virtual bool save() override;
virtual float progress() override;
private:
std::shared_ptr<PPresult> data;
virtual float samples(const std::shared_ptr<PPresult> data) override;
virtual QKeySequence shortcut() override;
};
}
66 changes: 36 additions & 30 deletions openhantek/src/exporting/exporterinterface.h
Original file line number Diff line number Diff line change
@@ -7,25 +7,17 @@

#include <memory>

class ExporterRegistry;
class PPresult;

namespace Exporter {
class Registry;

/**
* Implement this interface and register your Exporter to the ExporterRegistry instance
* in the main routine to make an Exporter available.
*/
class ExporterInterface {
public:

/**
* Starts up this exporter. Aquires resources etc. Do not call this directly, it
* will be called by the exporter registry at some point. Release your resources in the
* destructor as usual.
* @param registry The exporter registry instance. This is used to obtain a reference
* to the settings.
*/
virtual void create(ExporterRegistry *registry) = 0;

public:
/**
* @return Return the icon representation of this exporter. Will be used in graphical
* interfaces.
@@ -38,6 +30,12 @@ class ExporterInterface {
*/
virtual QString name() = 0;

/**
* @return Return a shortcut for this exporter. Will be used in graphical
* interfaces.
*/
virtual QKeySequence shortcut() = 0;

/**
* Exporters can save only a single sample set or save data continously.
*/
@@ -50,29 +48,37 @@ class ExporterInterface {
virtual Type type() = 0;

/**
* A new sample set from the ExporterRegistry. The exporter needs to be active to receive samples.
* If it is a snapshot exporter, only one set of samples will be received.
* @return Return true if you want to receive another sample or false if you are done (progres()==1).
* A new sample set arrived at the ExporterRegistry. The exporter needs to be a continous exporter: See type().
* This method is called in the thread context of the ExporterRegistry.
*
* \return Report the used memory / reservered memory ratio here. If float>=1.0 then this exporter will be
* deactivated again and will not receive anymore sample data.
*/
virtual bool samples(const std::shared_ptr<PPresult>) = 0;
virtual float samples(const std::shared_ptr<PPresult>) = 0;

/**
* Exporter: Save your received data and perform any conversions necessary.
* This method will be called in the
* GUI thread context and can create and show dialogs if required.
* @return Return true if saving succedded otherwise false.
* Start the export process. If you are a snapshot exporter, now is the time to access the last sampleset
* from the ExporterRegistry, convert it, ask for a filename and save the data.
*
* If you are a continous exporter, you should show a dialog to the user to inform about the progress and provide
* a cancel option.
*
* This method will be called in the GUI thread context and can create and show dialogs if required.
*
* @param registry The exporter registry instance. This is used to obtain a reference
* to the settings and sample data.
* @return Return true if saving or setting everything up succedded otherwise false. If this is a continous
* exporter and false is returned, then no sample data will be send via samples(...).
*/
virtual bool save() = 0;
virtual bool exportNow(Registry *registry) = 0;

/**
* @brief The progress of receiving and processing samples. If the exporter returns 1, it will
* be called back by the GUI via the save() method.
*
* @return A number between 0..1 indicating the used capacity of this exporter. If this is a
* snapshot exporter, only 0 for "no samples processed yet" or 1 for "finished" will be returned.
* A continous exporter may report the used memory / reservered memory ratio here.
* Implement this if you are a continous exporter and want the user to be able to stop the export process
* via export checkbox in the main window.
*/
virtual float progress() = 0;
protected:
ExporterRegistry *registry;
virtual void stopContinous() {}

protected:
Registry *registry;
};
}
6 changes: 0 additions & 6 deletions openhantek/src/exporting/exporterprocessor.cpp

This file was deleted.

14 changes: 0 additions & 14 deletions openhantek/src/exporting/exporterprocessor.h

This file was deleted.

89 changes: 31 additions & 58 deletions openhantek/src/exporting/exporterregistry.cpp
Original file line number Diff line number Diff line change
@@ -4,81 +4,54 @@
#include "exporterinterface.h"

#include <algorithm>
#include <QDebug>

#include "controlspecification.h"
#include "hantekdso/modelspecification.h"
#include "post/ppresult.h"
#include "settings.h"

ExporterRegistry::ExporterRegistry(const Dso::ControlSpecification *deviceSpecification, DsoSettings *settings,
QObject *parent)
: QObject(parent), deviceSpecification(deviceSpecification), settings(settings) {}

bool ExporterRegistry::processData(std::shared_ptr<PPresult> &data, ExporterInterface *const &exporter) {
if (!exporter->samples(data)) {
waitToSaveExporters.insert(exporter);
emit exporterProgressChanged();
return true;
template <class T, class Comp, class Alloc, class Predicate>
void discard_if(std::set<T, Comp, Alloc> &c, Predicate pred) {
for (auto it{c.begin()}, end{c.end()}; it != end;) {
if (pred(*it)) {
it = c.erase(it);
} else {
++it;
}
}
return false;
}

void ExporterRegistry::addRawSamples(PPresult *d) {
if (settings->exporting.useProcessedSamples) return;
std::shared_ptr<PPresult> data(d);
enabledExporters.remove_if([&data, this](ExporterInterface *const &i) { return processData(data, i); });
}

void ExporterRegistry::input(std::shared_ptr<PPresult> data) {
if (!settings->exporting.useProcessedSamples) return;
enabledExporters.remove_if([&data, this](ExporterInterface *const &i) { return processData(data, i); });
namespace Exporter {
Registry::Registry(const Dso::ModelSpec *deviceSpecification, Settings::DsoSettings *settings, QObject *parent)
: QObject(parent), deviceSpecification(deviceSpecification), settings(settings) {
}

void ExporterRegistry::registerExporter(ExporterInterface *exporter) {
exporters.push_back(exporter);
exporter->create(this);
void Registry::input(std::shared_ptr<PPresult> data) {
m_lastDataset = data;
discard_if(continousActiveExporters,
[&data, this](ExporterInterface *const &i) { return i->samples(data) >= 1.0; });
}

void ExporterRegistry::setExporterEnabled(ExporterInterface *exporter, bool enabled) {
bool wasInList = false;
enabledExporters.remove_if([exporter, &wasInList](ExporterInterface *inlist) {
if (inlist == exporter) {
wasInList = true;
return true;
} else
return false;
});
void Registry::registerExporter(ExporterInterface *exporter) { exporters.push_back(exporter); }

if (enabled) {
// If the exporter was waiting for the user save confirmation,
// reset it instead.
auto localFind = waitToSaveExporters.find(exporter);
if (localFind != waitToSaveExporters.end()) {
waitToSaveExporters.erase(localFind);
exporter->create(this);
}
enabledExporters.push_back(exporter);
} else if (wasInList) {
// If exporter made some progress: Add to waiting for GUI list
if (exporter->progress() > 0) {
waitToSaveExporters.insert(exporter);
emit exporterProgressChanged();
} else // Reset exporter
exporter->create(this);
void Registry::exportNow(ExporterInterface *exporter) {
if (exporter->exportNow(this) && exporter->type() == ExporterInterface::Type::ContinousExport) {
auto localFind = continousActiveExporters.find(exporter);
if (localFind == continousActiveExporters.end()) { continousActiveExporters.insert(exporter); }
}
}

void ExporterRegistry::checkForWaitingExporters() {
for (ExporterInterface *exporter : waitToSaveExporters) {
if (exporter->save()) {
emit exporterStatusChanged(exporter->name(), tr("Data saved"));
} else {
emit exporterStatusChanged(exporter->name(), tr("No data exported"));
void Registry::stopContinous(ExporterInterface *exporter) {
if (exporter->type() == ExporterInterface::Type::ContinousExport) {
auto localFind = continousActiveExporters.find(exporter);
if (localFind != continousActiveExporters.end()) {
continousActiveExporters.erase(localFind);
exporter->stopContinous();
}
exporter->create(this);
}
waitToSaveExporters.clear();
}

std::vector<ExporterInterface *>::const_iterator ExporterRegistry::begin() { return exporters.begin(); }
std::vector<ExporterInterface *>::const_iterator Registry::begin() { return exporters.begin(); }

std::vector<ExporterInterface *>::const_iterator ExporterRegistry::end() { return exporters.end(); }
std::vector<ExporterInterface *>::const_iterator Registry::end() { return exporters.end(); }
}
41 changes: 21 additions & 20 deletions openhantek/src/exporting/exporterregistry.h
Original file line number Diff line number Diff line change
@@ -12,53 +12,54 @@ class Processor;
class PPresult;

// Settings forwards
namespace Settings {
class DsoSettings;
}
namespace Dso {
struct ControlSpecification;
struct ModelSpec;
}

// Exporter forwards
namespace Exporter {
class ExporterInterface;

class ExporterRegistry : public QObject {
class Registry : public QObject {
Q_OBJECT
public:
explicit ExporterRegistry(const Dso::ControlSpecification *deviceSpecification, DsoSettings *settings,
QObject *parent = nullptr);
explicit Registry(const Dso::ModelSpec *deviceSpecification, Settings::DsoSettings *settings,
QObject *parent = nullptr);

// Sample input. This will proably be performed in the post processing
// thread context. Do not open GUI dialogs or interrupt the control flow.
void addRawSamples(PPresult *data);
void input(std::shared_ptr<PPresult> data);

void registerExporter(ExporterInterface *exporter);
void setExporterEnabled(ExporterInterface *exporter, bool enabled);

void checkForWaitingExporters();
/**
* Called from the GUI thread to start an export process.
* @param exporter The exporter to use.
*/
void exportNow(ExporterInterface *exporter);

void stopContinous(ExporterInterface *exporter);

inline std::shared_ptr<PPresult> lastDataSet() const { return m_lastDataset; }

// Iterate over this class object
std::vector<ExporterInterface *>::const_iterator begin();
std::vector<ExporterInterface *>::const_iterator end();

/// Device specifications
const Dso::ControlSpecification *deviceSpecification;
const DsoSettings *settings;
const Dso::ModelSpec *deviceSpecification;
const Settings::DsoSettings *settings;

private:
/// List of all available exporters
std::vector<ExporterInterface *> exporters;
/// List of exporters that collect samples at the moment
std::list<ExporterInterface *> enabledExporters;
/// List of exporters that wait to be called back by the user to save their work
std::set<ExporterInterface *> waitToSaveExporters;
std::set<ExporterInterface *> continousActiveExporters;

/// Process data from addRawSamples() or input() in the given exporter. Add the
/// exporter to waitToSaveExporters if it finishes.
///
/// @return Return true if the exporter has finished and want to be removed from the
/// enabledExporters list.
bool processData(std::shared_ptr<PPresult> &data, ExporterInterface *const &exporter);
std::shared_ptr<PPresult> m_lastDataset;
signals:
void exporterStatusChanged(const QString &exporterName, const QString &status);
void exporterProgressChanged();
};
}
47 changes: 24 additions & 23 deletions openhantek/src/exporting/exportimage.cpp
Original file line number Diff line number Diff line change
@@ -9,61 +9,62 @@
#include "viewsettings.h"

#include <QCoreApplication>
#include <QDateTime>
#include <QFileDialog>
#include <QPrinter>
namespace Exporter {
Image::Image() {}

ExporterImage::ExporterImage() {}
QIcon Image::icon() { return iconFont->icon(fa::image); }

void ExporterImage::create(ExporterRegistry *registry) { this->registry = registry; data.reset(); }
QString Image::name() { return QCoreApplication::tr("Export Image/PDF"); }

QIcon ExporterImage::icon() { return iconFont->icon(fa::image); }
ExporterInterface::Type Image::type() { return Type::SnapshotExport; }

QString ExporterImage::name() { return QCoreApplication::tr("Export Image/PDF"); }
float Image::samples(const std::shared_ptr<PPresult>) { return 1; }

ExporterInterface::Type ExporterImage::type() { return Type::SnapshotExport; }

bool ExporterImage::samples(const std::shared_ptr<PPresult> data) {
this->data = std::move(data);
return false;
}

bool ExporterImage::save() {
bool Image::exportNow(Registry *registry) {
auto data = registry->lastDataSet();
if (!data) return false;
QStringList filters;
filters << QCoreApplication::tr("Portable Document Format (*.pdf)")
<< QCoreApplication::tr("Image (*.png *.xpm *.jpg)");
filters << QCoreApplication::tr("Image (*.png *.xpm *.jpg *.bmp)")
<< QCoreApplication::tr("Portable Document Format (*.pdf)");

QFileDialog fileDialog(nullptr, QCoreApplication::tr("Export file..."), QString(), filters.join(";;"));
fileDialog.selectFile(QDateTime::currentDateTime().toString(Qt::ISODate) + ".png");
fileDialog.setFileMode(QFileDialog::AnyFile);
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
if (fileDialog.exec() != QDialog::Accepted) return false;

bool isPdf = filters.indexOf(fileDialog.selectedNameFilter()) == 0;
bool isPdf = filters.indexOf(fileDialog.selectedNameFilter()) == 1;
const QString filename = fileDialog.selectedFiles().first();
std::unique_ptr<QPaintDevice> paintDevice;

const DsoSettingsColorValues *colorValues = &(registry->settings->view.print);
const Settings::Colors *colorValues = &(registry->settings->view.print);

if (!isPdf) {
// We need a QPixmap for image-export
QPixmap *qPixmap = new QPixmap(registry->settings->exporting.imageSize);
qPixmap->fill(colorValues->background);
qPixmap->fill(colorValues->background());
paintDevice = std::unique_ptr<QPaintDevice>(qPixmap);
} else {
// We need a QPrinter for printing, pdf- and ps-export
std::unique_ptr<QPrinter> printer = std::unique_ptr<QPrinter>(new QPrinter(QPrinter::HighResolution));
printer->setOrientation(registry->settings->view.zoom ? QPrinter::Portrait : QPrinter::Landscape);
printer->setOrientation(QPrinter::Landscape);
printer->setPageMargins(20, 20, 20, 20, QPrinter::Millimeter);
printer->setOutputFileName(fileDialog.selectedFiles().first());
printer->setOutputFileName(filename);
printer->setOutputFormat(QPrinter::PdfFormat);
paintDevice = std::move(printer);
}

if (!paintDevice) return false;

LegacyExportDrawer::exportSamples(data.get(), paintDevice.get(), registry->deviceSpecification, registry->settings,
false, colorValues);
LegacyExportDrawer::exportSamples(data, paintDevice.get(), registry->deviceSpecification, registry->settings,
colorValues);

if (!isPdf) static_cast<QPixmap *>(paintDevice.get())->save(fileDialog.selectedFiles().first());
if (!isPdf) static_cast<QPixmap *>(paintDevice.get())->save(filename);
return true;
}

float ExporterImage::progress() { return data ? 1.0f : 0; }
QKeySequence Image::shortcut() { return QKeySequence(Qt::CTRL | Qt::Key_E); }
}
18 changes: 8 additions & 10 deletions openhantek/src/exporting/exportimage.h
Original file line number Diff line number Diff line change
@@ -3,17 +3,15 @@
#pragma once
#include "exporterinterface.h"

class ExporterImage : public ExporterInterface
{
public:
ExporterImage();
virtual void create(ExporterRegistry *registry) override;
namespace Exporter {
class Image : public ExporterInterface {
public:
Image();
virtual bool exportNow(Registry *registry) override;
virtual QIcon icon() override;
virtual QString name() override;
virtual Type type() override;
virtual bool samples(const std::shared_ptr<PPresult>data) override;
virtual bool save() override;
virtual float progress() override;
private:
std::shared_ptr<PPresult> data;
virtual float samples(const std::shared_ptr<PPresult> data) override;
virtual QKeySequence shortcut() override;
};
}
31 changes: 15 additions & 16 deletions openhantek/src/exporting/exportprint.cpp
Original file line number Diff line number Diff line change
@@ -12,38 +12,37 @@
#include <QPrintDialog>
#include <QPrinter>

ExporterPrint::ExporterPrint() {}
namespace Exporter {
Print::Print() {}

void ExporterPrint::create(ExporterRegistry *registry) { this->registry = registry; data.reset(); }
QIcon Print::icon() { return iconFont->icon(fa::print); }

QIcon ExporterPrint::icon() { return iconFont->icon(fa::print); }
QString Print::name() { return QCoreApplication::tr("Print"); }

QString ExporterPrint::name() { return QCoreApplication::tr("Print"); }
ExporterInterface::Type Print::type() { return Type::SnapshotExport; }

ExporterInterface::Type ExporterPrint::type() { return Type::SnapshotExport; }
float Print::samples(const std::shared_ptr<PPresult>) { return 1; }

bool ExporterPrint::samples(const std::shared_ptr<PPresult> data) {
this->data = std::move(data);
return false;
}

bool ExporterPrint::save() {
bool Print::exportNow(Registry *registry) {
auto data = registry->lastDataSet();
if (!data) return false;
// We need a QPrinter for printing, pdf- and ps-export
std::unique_ptr<QPrinter> printer = std::unique_ptr<QPrinter>(new QPrinter(QPrinter::HighResolution));
printer->setOrientation(registry->settings->view.zoom ? QPrinter::Portrait : QPrinter::Landscape);
printer->setOrientation(QPrinter::Landscape);
printer->setPageMargins(20, 20, 20, 20, QPrinter::Millimeter);

// Show the printing dialog
QPrintDialog dialog(printer.get());
dialog.setWindowTitle(QCoreApplication::tr("Print oscillograph"));
if (dialog.exec() != QDialog::Accepted) { return false; }

const DsoSettingsColorValues *colorValues = &(registry->settings->view.print);
const Settings::Colors *colorValues = &(registry->settings->view.print);

LegacyExportDrawer::exportSamples(data.get(), printer.get(), registry->deviceSpecification, registry->settings,
true, colorValues);
LegacyExportDrawer::exportSamples(data, printer.get(), registry->deviceSpecification, registry->settings,
colorValues);

return true;
}

float ExporterPrint::progress() { return data ? 1.0f : 0; }
QKeySequence Print::shortcut() { return QKeySequence(Qt::CTRL | Qt::Key_P); }
}
18 changes: 8 additions & 10 deletions openhantek/src/exporting/exportprint.h
Original file line number Diff line number Diff line change
@@ -3,17 +3,15 @@
#pragma once
#include "exporterinterface.h"

class ExporterPrint : public ExporterInterface
{
public:
ExporterPrint();
virtual void create(ExporterRegistry *registry) override;
namespace Exporter {
class Print : public ExporterInterface {
public:
Print();
virtual bool exportNow(Registry *registry) override;
virtual QIcon icon() override;
virtual QString name() override;
virtual Type type() override;
virtual bool samples(const std::shared_ptr<PPresult>data) override;
virtual bool save() override;
virtual float progress() override;
private:
std::shared_ptr<PPresult> data;
virtual float samples(const std::shared_ptr<PPresult> data) override;
virtual QKeySequence shortcut() override;
};
}
22 changes: 22 additions & 0 deletions openhantek/src/exporting/exportsettings.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0+

#include <QApplication>
#include <QColor>
#include <QDebug>
#include <QSettings>

#include "exportsettings.h"

void Settings::DsoExportIO::read(QSettings *store, DsoExport &exporting) {
store->beginGroup("exporting");
if (store->contains("imageSize")) exporting.imageSize = store->value("imageSize").toSize();
if (store->contains("exportSizeBytes")) exporting.exportSizeBytes = store->value("exportSizeBytes").toUInt();
store->endGroup();
}

void Settings::DsoExportIO::write(QSettings *store, const DsoExport &exporting) {
store->beginGroup("exporting");
store->setValue("imageSize", exporting.imageSize);
store->setValue("exportSizeBytes", exporting.exportSizeBytes);
store->endGroup();
}
15 changes: 11 additions & 4 deletions openhantek/src/exporting/exportsettings.h
Original file line number Diff line number Diff line change
@@ -3,10 +3,17 @@
#pragma once

#include <QSize>
class QSettings;

namespace Settings {
/// \brief Holds the export options of the program.
struct DsoSettingsExport {
QSize imageSize = QSize(640, 480); ///< Size of exported images in pixels
unsigned exportSizeBytes = 1024*1024*10; ///< For exporters that save a continous stream. Default: 10 Megabytes
bool useProcessedSamples = true; ///< Export raw or processed samples
struct DsoExport {
QSize imageSize = QSize(640, 480); ///< Size of exported images in pixels
unsigned exportSizeBytes = 1024 * 1024 * 10; ///< For exporters that save a continous stream. Default: 10 Megabytes
};

struct DsoExportIO {
static void read(QSettings *io, DsoExport &exportStruct);
static void write(QSettings *io, const DsoExport &exportStruct);
};
}
401 changes: 104 additions & 297 deletions openhantek/src/exporting/legacyexportdrawer.cpp

Large diffs are not rendered by default.

32 changes: 14 additions & 18 deletions openhantek/src/exporting/legacyexportdrawer.h
Original file line number Diff line number Diff line change
@@ -2,34 +2,30 @@

#pragma once

#include "exportsettings.h"
#include <QPainter>
#include <QPrinter>
#include <QSize>
#include <memory>
#include "exportsettings.h"

namespace Settings {
class DsoSettings;
struct Colors;
}
class PPresult;
struct DsoSettingsColorValues;
namespace Dso { struct ControlSpecification; }
namespace Dso {
struct ModelSpec;
}

namespace Exporter {
/// \brief Exports the oscilloscope screen to a file or prints it.
/// TODO
/// Rewrite image exporter with OpenGL drawn grid and graphs
///
/// Sources:
/// http://doc.qt.io/qt-5/qoffscreensurface.html
/// http://doc.qt.io/qt-5/qopenglframebufferobject.html
///
/// https://dangelog.wordpress.com/2013/02/10/using-fbos-instead-of-pbuffers-in-qt-5-2/
/// TODO Grab DsoWidget instead of drawing all labels by hand
class LegacyExportDrawer {
public:
/// Draw the graphs coming from source and labels to the destination paintdevice.
static bool exportSamples(const PPresult *source, QPaintDevice* dest,
const Dso::ControlSpecification* deviceSpecification,
const DsoSettings *settings, bool isPrinter, const DsoSettingsColorValues *colorValues);

private:
static void drawGrids(QPainter &painter, const DsoSettingsColorValues *colorValues, double lineHeight, double scopeHeight,
int scopeWidth, bool isPrinter, bool zoom);
static bool exportSamples(std::shared_ptr<PPresult> source, QPaintDevice *dest,
const Dso::ModelSpec *deviceSpecification,
const Settings::DsoSettings *settings,
const Settings::Colors *colorValues);
};
}
6 changes: 3 additions & 3 deletions openhantek/src/exporting/readme.md
Original file line number Diff line number Diff line change
@@ -8,9 +8,9 @@ This directory contains exporting functionality and exporters, namely
All export classes (exportcsv, exportimage, exportprint) implement the
ExporterInterface and are registered to the ExporterRegistry in the main.cpp.

Some export classes are still using the legacyExportDrawer class to
draw the grid and paint all the labels, values and graphs.
At the moment export classes are still using the legacyExportDrawer class to
paint all the labels. It is planned to unify this with the widget based solution of DsoWidget.

# Dependency
* Files in this directory depend on the result class of the post processing directory.
* Classes in here depend on the user settings (../viewsetting.h, ../scopesetting.h)
* Classes in here depend on the user settings (../settings/)
475 changes: 0 additions & 475 deletions openhantek/src/glscope.cpp

This file was deleted.

117 changes: 0 additions & 117 deletions openhantek/src/glscope.h

This file was deleted.

81 changes: 0 additions & 81 deletions openhantek/src/glscopegraph.cpp

This file was deleted.

27 changes: 0 additions & 27 deletions openhantek/src/glscopegraph.h

This file was deleted.

30 changes: 30 additions & 0 deletions openhantek/src/hantekdso/channelusage.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-2.0+

#include "channelusage.h"

namespace Dso {

bool ChannelUsage::isUsed(ChannelID channelId) const {
QMutexLocker locker(usedMutex.get());
return m_used[channelId].size();
}

void ChannelUsage::addChannelUser(ChannelID channelId, void *object) {
QMutexLocker locker(usedMutex.get());
m_used[channelId].insert(object);
emit usedChanged(channelId, m_used[channelId].size());
}

void ChannelUsage::removeChannelUser(ChannelID channelId, void *object) {
QMutexLocker locker(usedMutex.get());
m_used[channelId].erase(object);
emit usedChanged(channelId, m_used[channelId].size());
}

unsigned ChannelUsage::countUsedChannels() const {
unsigned c = 0;
for (auto &b : m_used)
if (b.size()) ++c;
return c;
}
}
45 changes: 45 additions & 0 deletions openhantek/src/hantekdso/channelusage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0+

#pragma once

#include "hantekprotocol/types.h"
#include <QMutex>
#include <QObject>
#include <memory>
#include <set>
#include <vector>

namespace Dso {

/**
* DsoControl has an automatic channel enable mechanism, based on the usage of a hardware channel.
* You first request this ChannelUsage object from DsoControl and then add/remove channel users.
* The hardware channels will be enabled/disabled based on usage/reference count.
*/
class ChannelUsage : public QObject {
Q_OBJECT
public:
inline ChannelUsage(unsigned channels) { m_used.resize(channels); }

/// Return true if the channel is used by a voltage, spectrum graph or a math channel. This method is
/// thread-safe. HantekDsoControl should use m_used.size() instead in hot code paths.
bool isUsed(ChannelID channelId) const;
/// Add a user of this channel. As soon as the channel is used by at least one object, it will
/// be activated. This method is to be used by the scope settings setVisible methods.
/// Thread-safe.
void addChannelUser(ChannelID channelId, void *object);
/// Remove a user of this channel. As soon as the channel is not used anymore, it will be deactivated.
/// This method is to be used by the scope settings setVisible methods.
/// Thread-safe.
void removeChannelUser(ChannelID channelId, void *object);
/// Counts the currently used hardware channels.
/// This method will access shared data between DsoControl and device settings and is thread-safe.
unsigned countUsedChannels() const;

private:
mutable std::unique_ptr<QMutex> usedMutex = std::unique_ptr<QMutex>(new QMutex);
std::vector<std::set<void *>> m_used; ///< objects, that are using this channel
signals:
void usedChanged(ChannelID channelId, bool used);
};
}
19 changes: 0 additions & 19 deletions openhantek/src/hantekdso/controlsettings.cpp

This file was deleted.

66 changes: 0 additions & 66 deletions openhantek/src/hantekdso/controlsettings.h

This file was deleted.

5 changes: 0 additions & 5 deletions openhantek/src/hantekdso/controlspecification.cpp

This file was deleted.

94 changes: 0 additions & 94 deletions openhantek/src/hantekdso/controlspecification.h

This file was deleted.

136 changes: 136 additions & 0 deletions openhantek/src/hantekdso/devicesettings.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: GPL-2.0+

#include "devicesettings.h"
#include "hantekprotocol/definitions.h"

#include <QDebug>
#include <QSettings>

namespace Dso {

DeviceSettings::DeviceSettings(const ModelSpec *specification) : spec(specification) {
this->limits = &specification->normalSamplerate;
while (voltage.size() < specification->channels) voltage.push_back(new Channel);
}

void DeviceSettings::setRecordLengthId(RecordLengthID value) {
m_recordLengthId = value;
emit recordLengthChanged(m_recordLengthId);
}

void DeviceSettings::updateCurrentSamplerate(double samplerate, double timebase, unsigned fixedSamplerateIndex) {
m_samplerate.samplerate = samplerate;
m_samplerate.timebase = timebase;
m_samplerate.fixedSamperateId = fixedSamplerateIndex;
}

Samplerate &DeviceSettings::updateTarget(SamplerateSource source) {
m_samplerateSource = source;
return m_targetSamperate;
}

void Channel::setOffset(double offset, double offsetHardware) {
m_offset = offset;
m_offsetHardware = offsetHardware;
emit offsetChanged(offset);
}

void Channel::setTriggerOffset(double offset) {
m_triggerOffset = offset;
emit triggerLevelChanged(offset);
}

void Channel::setGainStepIndex(unsigned gainId) {
m_gainStepIndex = gainId;
emit gainStepIndexChanged(gainId);
}

void Channel::setCouplingIndex(unsigned couplingId) {
m_couplingIndex = couplingId;
emit couplingIndexChanged(couplingId);
}

void Trigger::setPosition(double position) {
this->m_position = position;
emit positionChanged(position);
}

void Trigger::setTriggerSource(ChannelID channel, bool specialChannel) {
m_source = channel;
m_special = specialChannel;
emit sourceChanged(specialChannel, channel);
}

void Trigger::setSlope(Slope slope) {
this->m_slope = slope;
emit slopeChanged(slope);
}

void Trigger::setMode(TriggerMode mode) {
this->m_mode = mode;
emit modeChanged(mode);
}
}

void Settings::DeviceSettingsIO::read(QSettings *io, Dso::DeviceSettings &control) {
control.m_recordLengthId = std::min(io->value("recordLengthId", control.m_recordLengthId).toUInt(),
(unsigned)control.spec->normalSamplerate.recordLengths.size() - 1);
Dso::Samplerate &localUpdateTarget = control.updateTarget(
(Dso::SamplerateSource)io->value("samplerateSource", (unsigned)control.samplerateSource()).toUInt());
localUpdateTarget.fixedSamperateId = io->value("fixedSamperateId", localUpdateTarget.fixedSamperateId).toUInt();
localUpdateTarget.samplerate = io->value("samplerate", localUpdateTarget.samplerate).toDouble();
localUpdateTarget.timebase = io->value("timebase", localUpdateTarget.timebase).toDouble();

control.trigger.m_mode = (Dso::TriggerMode)io->value("trigger.mode", (unsigned)control.trigger.m_mode).toUInt();
control.trigger.m_slope = (Dso::Slope)io->value("trigger.slope", (unsigned)control.trigger.m_slope).toUInt();
control.trigger.m_position = io->value("trigger.position", control.trigger.m_position).toDouble();
control.trigger.m_point = io->value("trigger.point", control.trigger.m_point).toUInt();
control.trigger.m_source = io->value("trigger.source", control.trigger.m_source).toUInt();
control.trigger.m_swTriggerThreshold =
io->value("trigger.swTriggerThreshold", control.trigger.m_swTriggerThreshold).toUInt();
control.trigger.m_swTriggerSampleSet =
io->value("trigger.swTriggerSampleSet", control.trigger.m_swTriggerSampleSet).toUInt();
control.trigger.m_swSampleMargin = io->value("trigger.swSampleMargin", control.trigger.m_swSampleMargin).toUInt();
control.trigger.m_special = io->value("trigger.special", control.trigger.m_special).toBool();

for (unsigned i = 0; i < control.voltage.size(); ++i) {
io->beginGroup("channel" + QString::number(i));
Dso::Channel *chan = control.voltage[i];
chan->m_couplingIndex = io->value("couplingIndex", chan->couplingIndex()).toUInt();
chan->m_gainStepIndex =
std::min(io->value("gainId", chan->gainStepIndex()).toUInt(), (unsigned)control.spec->gain.size() - 1);
chan->m_offset = io->value("offset", chan->offset()).toDouble();
chan->m_offsetHardware = io->value("offsetReal", chan->offsetHardware()).toDouble();
chan->m_triggerOffset = io->value("triggerLevel", chan->triggerLevel()).toDouble();
io->endGroup();
}
}

void Settings::DeviceSettingsIO::write(QSettings *io, const Dso::DeviceSettings &control) {
io->setValue("recordLengthId", control.m_recordLengthId);
io->setValue("samplerateSource", (unsigned)control.samplerateSource());
io->setValue("fixedSamperateId", control.target().fixedSamperateId);
io->setValue("samplerate", control.target().samplerate);
io->setValue("timebase", control.target().timebase);

io->setValue("trigger.mode", (unsigned)control.trigger.m_mode);
io->setValue("trigger.slope", (unsigned)control.trigger.m_slope);
io->setValue("trigger.position", control.trigger.m_position);
io->setValue("trigger.point", control.trigger.m_point);
io->setValue("trigger.source", control.trigger.m_source);
io->setValue("trigger.swTriggerThreshold", control.trigger.m_swTriggerThreshold);
io->setValue("trigger.swTriggerSampleSet", control.trigger.m_swTriggerSampleSet);
io->setValue("trigger.swSampleMargin", control.trigger.m_swSampleMargin);
io->setValue("trigger.special", control.trigger.m_special);

for (unsigned i = 0; i < control.voltage.size(); ++i) {
io->beginGroup("channel" + QString::number(i));
const Dso::Channel *chan = control.voltage[i];
io->setValue("couplingIndex", chan->couplingIndex());
io->setValue("gainId", chan->gainStepIndex());
io->setValue("offset", chan->offset());
io->setValue("offsetReal", chan->offsetHardware());
io->setValue("triggerLevel", chan->triggerLevel());
io->endGroup();
}
}
261 changes: 261 additions & 0 deletions openhantek/src/hantekdso/devicesettings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
// SPDX-License-Identifier: GPL-2.0+

#pragma once

/**<
* Contains all settings for a currently connected device as well as the state that this device is in.
* Changes (write access) are only allowed from within the DsoControl class. You can connect to various signals
* to be notified of changes.
*/

#include "enums.h"
#include "hantekdso/modelspecification.h"
#include "hantekprotocol/codes.h"
#include "hantekprotocol/types.h"

#include <QMutex>
#include <QMutexLocker>
#include <QObject>
#include <set>

namespace Settings {
struct DeviceSettingsIO;
}

class DsoControl;

namespace Dso {

struct ControlSamplerateLimits;

/// \brief Stores the current or target samplerate settings of the device.
struct Samplerate {
double samplerate = 1e8; ///< The target samplerate set via setSamplerate
double timebase = 1e-3; ///< The target record time set via setRecordTime
unsigned fixedSamperateId = 0; ///< The target samplerate for fixed samplerate devices set via setFixedSamplerate
};

/// \brief Stores the current trigger settings of the device.
class Trigger : public QObject {
friend struct Settings::DeviceSettingsIO;
Q_OBJECT
public:
Dso::TriggerMode mode() const { return m_mode; } ///< The trigger mode
Dso::Slope slope() const { return m_slope; } ///< The trigger slope
double position() const { return m_position; } ///< The current pretrigger position in range [0,1]
bool special() const { return m_special; } ///< true, if the trigger source is special
unsigned source() const { return m_source; } ///< The trigger source
unsigned point() const { return m_point; } ///< The trigger position in Hantek coding
unsigned swTriggerThreshold() const { return m_swTriggerThreshold; } ///< Software trigger, threshold
unsigned swTriggerSampleSet() const { return m_swTriggerSampleSet; } ///< Software trigger, sample set
unsigned swSampleMargin() const { return m_swSampleMargin; } ///< Software trigger, sample margin

void setPosition(double position);
void setPoint(unsigned point) { this->m_point = point; }
void setTriggerSource(ChannelID channel, bool specialChannel);
void setSlope(Dso::Slope slope);
void setMode(Dso::TriggerMode mode);

private:
Dso::TriggerMode m_mode = //
Dso::TriggerMode::HARDWARE_SOFTWARE; ///< The trigger mode
Dso::Slope m_slope = Dso::Slope::Positive; ///< The trigger slope
double m_position = 0.0; ///< The current pretrigger position in range [0,1]
bool m_special = false; ///< true, if the trigger source is special
unsigned m_source = 0; ///< The trigger source
unsigned m_point = 0; ///< The trigger position in Hantek coding
unsigned m_swTriggerThreshold = 7; ///< Software trigger, threshold
unsigned m_swTriggerSampleSet = 11; ///< Software trigger, sample set
unsigned m_swSampleMargin = 2000; ///< Software trigger, sample margin
signals:
void modeChanged(Dso::TriggerMode mode); ///< The trigger mode has been changed
void sourceChanged(bool special, unsigned int id); ///< The trigger source has been changed
void slopeChanged(Dso::Slope slope); ///< The trigger slope has been changed
void positionChanged(double position); ///< The trigger position has been changed
};

/// \brief Stores the current amplification settings of the device.
class Channel : public QObject {
Q_OBJECT
friend struct Settings::DeviceSettingsIO;

public:
/// The current coupling index
inline unsigned couplingIndex() const { return m_couplingIndex; }
/// The vertical resolution gain index for gain in V
inline unsigned gainStepIndex() const { return m_gainStepIndex; }
/// The current offset value in [-1,1].
inline double offset() const { return m_offset; }
/// \return Returns the hardware applied offset. For devices that do not support hardware offsets, this will be 0.
inline double offsetHardware() const { return m_offsetHardware; }
/// \return Returns the trigger level in range [0,1]
inline double triggerLevel() const { return m_triggerOffset; }

/// Get the coupling value for the specific channel
inline Dso::Coupling coupling(const Dso::ModelSpec *spec) const { return spec->couplings[m_couplingIndex]; }

/**
* @brief Sets the offset value and emit the corresponding signal. Only to be called by HantekDsoControl.
* @param offset The offset value [-1,1]
* @param offsetHardware Not necessary for math channels. The hardware applied offset. The received raw sample
* set will have this offset applied, so we need to remember and remove it later to output clean sample
* values in range [0,1].
*/
void setOffset(double offset, double offsetHardware = 0.0);

/**
* Sets the trigger level / trigger offset and emit the corresponding signal. Only to be called by HantekDsoControl.
* @param offset The offset value [-1,1]
*/
void setTriggerOffset(double offset);

/**
* @brief Sets the gain id and emit the corresponding signal. Only to be called by HantekDsoControl.
* @param gainId Gain ID. Must be in range with Dso::ModelSpec defined gain ids.
*/
void setGainStepIndex(unsigned gainId);

/**
* @brief Sets the coupling id and emit the corresponding signal. Only to be called by HantekDsoControl.
* @param couplingId Coupling ID. Must be in range with Dso::ModelSpec defined coupling ids.
*/
void setCouplingIndex(unsigned couplingId);

private:
unsigned m_couplingIndex = 0; ///< Coupling index (refers to one of the Dso::Coupling values)
unsigned m_gainStepIndex = 0; ///< The vertical resolution gain index for gain in V
double m_offset = 0.; ///< The offset for each channel [-1,1].
double m_offsetHardware = 0.0; ///< The hardware applied offset for each channel (Quantization+Min/Max considered)
Voltage m_triggerOffset; ///< The trigger level in V

signals:
void gainStepIndexChanged(unsigned gainId);
void couplingIndexChanged(unsigned couplingIndex);
void offsetChanged(double offset);
void triggerLevelChanged(double triggerLevel);
};

/// A samplerate can be set/influenced via the timebase, a samplerate value, a fixed samplerate id that refers
/// to a samplerate. We need to keep track which is the source for the current device samplerate.
enum class SamplerateSource { Duration, FixedSamplerate, Samplerrate };

/// Contains the current device settings as well as the current state of the scope device.
/// Those settings and the state are highly interactive with the {@link HantekDsoControl} class.
/// If a change to the state is made, it is propagated via the signals of this class.
class DeviceSettings : public QObject {
Q_OBJECT
friend struct Settings::DeviceSettingsIO;

public:
DeviceSettings(const ModelSpec *specification);

/// \brief Return the target samplerate, as set by the user.
/// You have access to a (samperate,record-time,fixed-samplerate-id)-tuple,
/// but only the value defined by samplerateSource() is valid.
///
/// The value is not necessarly what is applied to the hardware. To lookup the current samplerate,
/// use samplerate() instead.
inline const Samplerate &target() const { return m_targetSamperate; }
inline SamplerateSource samplerateSource() const { return m_samplerateSource; }

/// Return the current (samperate,record-time,fixed-samplerate-id)-tuple. The fixed-samplerate-id
/// is only valid, if this is a fixed samplerates model. Use spec->isFixedSamplerateDevice if in doubt.
inline const Samplerate &samplerate() const { return m_samplerate; }

/// Return true if roll-mode is enabled.
inline bool isRollMode() const { return limits->recordLengths[m_recordLengthId].recordLength == ROLL_RECORDLEN; }

/// Returns true if in fast rate mode (one channel uses all bandwith)
inline bool isFastRate() const { return limits == &spec->fastrateSamplerate; }

/// \brief Gets the record length id.
/// The id can be used to look up the record length in the model specification.
inline RecordLengthID recordLengthId() const { return m_recordLengthId; }

/// \brief Sets the record length id.
/// The id will be used to look up the record length in the model specification.
/// Called by DsoControl. Never call this out of DsoControl because the values will not be applied back.
void setRecordLengthId(RecordLengthID value);

/// Updates the (samperate,record-time,fixed-samplerate-id)-tuple.
/// Called by DsoControl. Never call this out of DsoControl because the values will not be applied back.
void updateCurrentSamplerate(double samplerate, double timebase, unsigned fixedSamplerateIndex);

/// A samplerate, recordtime or samplerate based on fixed ids can never be set alone. Each parameter
/// influences the others. This method allows to manipulate the target Samplerate structure, but you must
/// define which parameter should be the dominating one / the source for the others.
/// Called by DsoControl. Never call this out of DsoControl because the values will not be applied back.
Samplerate &updateTarget(SamplerateSource source);

/// \brief Return the hardware applied gain in V.
/// Uses the current gain step id and gain steps defined in the model specification.
inline double gain(ChannelID channel) const { return spec->gain[voltage[channel]->gainStepIndex()].gain; }

/// \brief Return the record length
/// Uses the current recordLengthId and record lengths defined in the model specification.
inline unsigned getRecordLength() const { return limits->recordLengths[m_recordLengthId].recordLength; }

/// Returns a step value meant to be used for adjusting the offset value [-1,1]. Because of quantization
/// of the offset which will be in the range of [offsetStart,offsetEnd] of calibration[channel][gainId]
/// we can't just use a pure double.
inline double offsetAdjustStep(ChannelID channel) const {
/// For non physical channels or not supported hardware offset
if (!spec->supportsOffset || channel > spec->calibration.size()) return 0.001;

const Dso::ModelSpec::GainStepCalibration &c = spec->calibration[channel][voltage[channel]->gainStepIndex()];
return 1 / (c.offsetEnd - c.offsetStart);
}

/// \brief Gets the maximum size of one packet transmitted via bulk transfer.
inline unsigned packetSize() const { return m_packetSize; }

inline unsigned getSampleCount() const {
if (isRollMode())
return packetSize();
else {
return (isFastRate()) ? getRecordLength() : getRecordLength() * spec->channels;
}
}

public:
const ModelSpec *spec;

// Device settings
std::vector<Channel *> voltage; ///< The amplification settings
Trigger trigger; ///< The trigger settings

// State variables: Those are not stored/restored
const ControlSamplerateLimits *limits; ///< The samplerate limits
unsigned int downsampler = 1; ///< The variable downsampling factor
unsigned m_packetSize = 0; ///< Device packet size

private:
// Device settings
SamplerateSource m_samplerateSource;
Samplerate m_targetSamperate; ///< The target samplerate values

// State variables: Those are not stored/restored
Samplerate m_samplerate; ///< The samplerate settings
RecordLengthID m_recordLengthId = 1; ///< The id in the record length array
signals:
/// The available samplerate range has changed
void samplerateLimitsChanged(double minimum, double maximum);
/// The available samplerate for fixed samplerate devices has changed
void fixedSampleratesChanged(const std::vector<Dso::FixedSampleRate> &sampleSteps);
/// The available record lengths. Is also emitted whenever the samplerate limits changed
void availableRecordLengthsChanged(const std::vector<Dso::RecordLength> &recordLengths);
/// The samplerate, or fixed samplerateId or recordTime has changed
void samplerateChanged(Samplerate samplerate);
/// The record length has changed
void recordLengthChanged(unsigned m_recordLengthId);
};
}
Q_DECLARE_METATYPE(Dso::Samplerate);

class QSettings;
namespace Settings {
struct DeviceSettingsIO {
static void read(QSettings *io, Dso::DeviceSettings &control);
static void write(QSettings *io, const Dso::DeviceSettings &control);
};
}
118 changes: 118 additions & 0 deletions openhantek/src/hantekdso/dsocommandqueue.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#include "dsocommandqueue.h"
#include "dsocontrol.h"
#include "usb/usbdevice.h"
#include "utils/debugnotify.h"
#include "utils/printutils.h"
#include <QDebug>

#ifdef DEBUG
#define DBGNOTIFY(x, y) emit m_control->debugMessage(x, y)
#else
#define DBGNOTIFY(x, y)
#endif

DsoCommandQueue::DsoCommandQueue(const Dso::ModelSpec *spec, USBDevice *device, DsoControl *control)
: m_commandMutex(QMutex::Recursive), m_useControlNoBulk(spec->useControlNoBulk), m_control(control),
m_device(device) {}

DsoCommandQueue::~DsoCommandQueue() {
while (firstBulkCommand) {
BulkCommand *t = firstBulkCommand->next;
delete firstBulkCommand;
firstBulkCommand = t;
}
while (firstControlCommand) {
ControlCommand *t = firstControlCommand->next;
delete firstControlCommand;
firstControlCommand = t;
}
}

void DsoCommandQueue::addCommand(BulkCommand *newCommand, bool pending) {
newCommand->pending = pending;
command[(uint8_t)newCommand->code] = newCommand;
newCommand->next = firstBulkCommand;
firstBulkCommand = newCommand;
}

void DsoCommandQueue::addCommand(ControlCommand *newCommand, bool pending) {
newCommand->pending = pending;
control[newCommand->code] = newCommand;
newCommand->next = firstControlCommand;
firstControlCommand = newCommand;
}

int DsoCommandQueue::bulkCommand(const std::vector<unsigned char> *command, int attempts) const {
// Send BeginCommand control command
int errorCode = m_device->controlWrite(&m_control->m_specification->beginCommandControl);
if (errorCode < 0) return errorCode;

// Send bulk command
return m_device->bulkWrite(command->data(), command->size(), attempts);
}

bool DsoCommandQueue::sendPendingCommands() {
int errorCode;
QMutexLocker l(&m_commandMutex);

// Send all pending control bulk commands
BulkCommand *command = m_useControlNoBulk ? nullptr : firstBulkCommand;
while (command) {
if (command->pending) {
DBGNOTIFY(QString("%1, %2")
.arg(QMetaEnum::fromType<Hantek::BulkCode>().valueToKey((int)command->code))
.arg(hexDump(command->data(), command->size())),
Debug::NotificationType::DeviceCommandSend);

errorCode = bulkCommand(command);
if (errorCode < 0) {
qWarning() << "Sending bulk command failed: " << libUsbErrorString(errorCode);
emit m_control->communicationError();
return false;
} else
command->pending = false;
}
command = command->next;
}

// Send all pending control commands
ControlCommand *controlCommand = firstControlCommand;
while (controlCommand) {
if (controlCommand->pending) {
DBGNOTIFY(QString("%1, %2")
.arg(QMetaEnum::fromType<Hantek::ControlCode>().valueToKey((int)controlCommand->code))
.arg(hexDump(controlCommand->data(), controlCommand->size())),
Debug::NotificationType::DeviceCommandSend);

errorCode = m_device->controlWrite(controlCommand);
if (errorCode < 0) {
qWarning("Sending control command %2x failed: %s", (uint8_t)controlCommand->code,
libUsbErrorString(errorCode).toLocal8Bit().data());

if (errorCode == LIBUSB_ERROR_NO_DEVICE) {
emit m_control->communicationError();
return false;
}
} else
controlCommand->pending = false;
}
controlCommand = controlCommand->next;
}
return true;
}

void DsoCommandQueue::manualCommand(bool isBulk, Hantek::BulkCode bulkCode, Hantek::ControlCode controlCode,
const QByteArray &data) {
if (!m_device->isConnected()) return;
QMutexLocker l(&m_commandMutex);

if (isBulk) {
BulkCommand *c = modifyCommand<BulkCommand>(bulkCode);
if (!c) return;
memcpy(c->data(), data.data(), std::min((size_t)data.size(), c->size()));
} else {
ControlCommand *c = modifyCommand<ControlCommand>(controlCode);
if (!c) return;
memcpy(c->data(), data.data(), std::min((size_t)data.size(), c->size()));
}
}
86 changes: 86 additions & 0 deletions openhantek/src/hantekdso/dsocommandqueue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-2.0+

#pragma once

#include <QMutex>

#include "hantekprotocol/bulkStructs.h"
#include "hantekprotocol/controlStructs.h"

class DsoControl;
class USBDevice;
namespace Dso {
struct ModelSpec;
}

/**
* Maintains a usb-bulk and usb-control command queue. To prevent hot-path runtime allocations,
* you need to add the necessary commands first (use addCommand(...)), before using them with
* getCommand() or modifyCommand().
*/
class DsoCommandQueue : public QObject {
public:
DsoCommandQueue(const Dso::ModelSpec *spec, USBDevice *device,DsoControl *control);
~DsoCommandQueue();

/**
* \brief Add a supported command. This is usually called from a model class within "models/..".
*
* If you do not add a command object and access the command via the command-code later on,
* the application will crash!
*
* @param newCommand A command object
* @param pending If true, the command will be send as soon as the dso sample-fetch loop starts.
*/
void addCommand(BulkCommand *newCommand, bool pending = false);

template <class T> T *modifyCommand(Hantek::BulkCode code) {
T *t = static_cast<T *>(command[(uint8_t)code]);
if (t) t->pending = true;
return t;
}

inline const BulkCommand *getCommand(Hantek::BulkCode code) const { return command[(uint8_t)code]; }

void addCommand(ControlCommand *newCommand, bool pending = false);

template <class T> T *modifyCommand(Hantek::ControlCode code) {
T *t = static_cast<T *>(control[(uint8_t)code]);
if (t) t->pending = true;
return t;
}

inline bool isCommandSupported(Hantek::ControlCode code) const { return control[(uint8_t)code]; }
inline bool isCommandSupported(Hantek::BulkCode code) const { return command[(uint8_t)code]; }

const ControlCommand *getCommand(Hantek::ControlCode code) const { return control[(uint8_t)code]; }

/// Send all pending control and bulk commands. Issued by the run() loop.
bool sendPendingCommands();

/// \brief Send a bulk command to the oscilloscope.
/// The hantek protocol requires to send a special control command first, this is handled by this method.
///
/// \param command The command, that should be sent.
/// \param attempts The number of attempts, that are done on timeouts.
/// \return Number of sent bytes on success, libusb error code on error.
int bulkCommand(const std::vector<unsigned char> *command, int attempts = HANTEK_ATTEMPTS) const;
public slots:
/// \brief Sends bulk/control commands directly.
/// \param data The command bytes.
/// \return See ::Dso::ErrorCode.
void manualCommand(bool isBulk, Hantek::BulkCode bulkCode, Hantek::ControlCode controlCode, const QByteArray &data);

protected:
QMutex m_commandMutex; ///< Makes command/control set-methods and enumerations thread-safe
private:
/// Pointers to bulk/control commands
BulkCommand *command[255] = {0};
BulkCommand *firstBulkCommand = nullptr;
ControlCommand *control[255] = {0};
ControlCommand *firstControlCommand = nullptr;
const bool m_useControlNoBulk;

DsoControl *m_control;
USBDevice *m_device; ///< The USB device for the oscilloscope
};
893 changes: 893 additions & 0 deletions openhantek/src/hantekdso/dsocontrol.cpp

Large diffs are not rendered by default.

244 changes: 244 additions & 0 deletions openhantek/src/hantekdso/dsocontrol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
// SPDX-License-Identifier: GPL-2.0+

#pragma once

#define NOMINMAX // disable windows.h min/max global methods
#include <limits>

#include "channelusage.h"
#include "devicesettings.h"
#include "dsocommandqueue.h"
#include "dsosamples.h"
#include "errorcodes.h"
#include "modelspecification.h"
#include "states.h"
#include "utils/debugnotify.h"
#include "utils/printutils.h"

#include "hantekprotocol/definitions.h"

#include <vector>

#include <QMetaEnum>
#include <QMutex>
#include <QStringList>
#include <QThread>
#include <QTimer>

class USBDevice;
class DsoLoop;

/// \brief The DsoControl abstraction layer for Hantek USB DSOs.
class DsoControl : public DsoCommandQueue {
Q_OBJECT
friend class DsoLoop;
friend class DsoCommandQueue;

public:
/**
* Creates a dsoControl object. The actual event loop / timer is not started.
* You can optionally create a thread and move the created object to the
* thread.
*
* You need to call updateInterval() to start the timer. This is done implicitly
* if run() is called.
*
* DSO channels are not enabled by default. To enable a channel,
* use deviceSettings->voltage[channel]->addChannelUser().
*
* @param device The usb device. No ownership is taken.
* @param deviceSettings Runtime device settings. You must provide this, but you can use
* a default constructed object. If no other consumer of the device settings exist, this
* class will clean it up.
*/
DsoControl(USBDevice *device, std::shared_ptr<Dso::DeviceSettings> deviceSettings);

/// Call this to initialize the device with the deviceSettings and start the processing, by calling run()
/// internally.
/// It is wise to move this class object to an own thread and call start() from there.
///
/// To stop processing, just destruct this object, disconnect the usbdevice object or stop the corresponding thread.
void start();

/// Return a read-only device control settings pointer. Use the set- Methods on this class to change
/// device settings. You can save device settings and restore the device state by initializing this
/// class with a loaded device settings object.
inline const std::shared_ptr<Dso::DeviceSettings> deviceSettings() const { return m_settings; }

/// Return the device specification. This is a convenience function and returns the same
/// as getDevice()->getModel()->spec().
inline const Dso::ModelSpec *specification() const { return m_specification; }

/**
* @return Returns the management object responsible for channel usage
*/
inline Dso::ChannelUsage *channelUsage() { return &m_channelUsage; }

/// \brief Get minimum samplerate for this oscilloscope.
/// \return The minimum samplerate for the current configuration in S/s.
double minSamplerate() const;

/// \brief Get maximum samplerate for this oscilloscope.
/// \return The maximum samplerate for the current configuration in S/s.
double maxSamplerate() const;

double maxSingleChannelSamplerate() const;

/// Return the associated usb device.
inline const USBDevice *getDevice() const { return device; }

inline DsoLoop *loopControl() { return m_loop.get(); }

protected: // Non-public methods
double computeTimebase(double samplerate) const;

/// Called right at the beginning to retrieve scope calibration data
Dso::ErrorCode retrieveOffsetCalibrationData();

/// \brief Gets the current state.
/// \return The current CaptureState of the oscilloscope.
std::pair<int, unsigned> retrieveCaptureState() const;

/// \brief Retrieve sample data from the oscilloscope
Dso::ErrorCode retrieveSamples(unsigned &expectedSampleCount);

/// \brief Gets the speed of the connection.
/// \return The ::ConnectionSpeed of the USB connection.
Dso::ErrorCode retrieveConnectionSpeed();

/// \brief The resulting tuple of the computeBestSamplerate() function
struct BestSamplerateResult {
unsigned downsampler = 0;
double samplerate = 0.0;
bool fastrate = false;
};
/// \brief Calculate the nearest samplerate supported by the oscilloscope.
/// \param samplerate The target samplerate, that should be met as good as possible.
/// \param maximum The target samplerate is the maximum allowed when true, the
/// minimum otherwise.
/// \return Tuple: The nearest samplerate supported, 0.0 on error and downsampling factor.
BestSamplerateResult computeBestSamplerate(double samplerate, bool maximum = false) const;

/// \brief Sets the samplerate based on the parameters calculated by
/// Control::getBestSamplerate.
/// \param downsampler The downsampling factor.
/// \param fastRate true, if one channel uses all buffers.
/// \return The downsampling factor that has been set.
unsigned updateSamplerate(unsigned downsampler, bool fastRate);

/// \brief Restore the samplerate/timebase targets after divider updates.
void restoreTargets();

/// \brief Update the minimum and maximum supported samplerate.
void notifySamplerateLimits();

/// \brief Enables/disables filtering of the given channel.
///
/// This may have influence on the sampling speed: Some scopes support a fast-mode, if only
/// a limited set of channels (usually only 1) is activated.
///
/// This method is not meant to be used directly. Use the deviceSettings to enable/disable channels,
/// by "using" them.
///
/// \param channel The channel that should be set.
/// \return See ::Dso::ErrorCode.
Dso::ErrorCode checkChannelUsage();

protected: // non-public variables
// Communication with device
USBDevice *device; ///< The USB device for the oscilloscope

// Device setup
const Dso::ModelSpec *m_specification; ///< The specifications of the device
std::shared_ptr<Dso::DeviceSettings> m_settings; ///< The current settings of the device
Dso::ChannelUsage m_channelUsage;

// Raw sample cache
std::vector<unsigned char> m_rawdata;
std::unique_ptr<DsoLoop> m_loop;

public slots:
/// \brief Sets the size of the oscilloscopes sample buffer.
/// \param index The record length index that should be set.
Dso::ErrorCode setRecordLengthByIndex(RecordLengthID size);

/// \brief Sets the samplerate of the oscilloscope.
/// \param samplerate The samplerate that should be met (S/s)
/// current samplerate. You cannot set a samplerate for a fixed samplerate device with
/// this method. Use setFixedSamplerate() instead.
Dso::ErrorCode setSamplerate(double samplerate);

/// \brief Sets the samplerate of the oscilloscope for fixed samplerate devices. Does
/// nothing on a device that supports a ranged samplerate value. Check with
/// deviceSpecification->isFixedSamplerateDevice if in doubt.
/// \param samplerrateId The samplerate id
Dso::ErrorCode setFixedSamplerate(unsigned samplerrateId);

/// \brief Sets the time duration of one aquisition by adapting the samplerate.
/// \param duration The record time duration that should be met (s)
Dso::ErrorCode setRecordTime(double duration);

/// \brief Set the coupling for the given channel.
/// \param channel The channel that should be set.
/// \param coupling The new coupling for the channel.
/// \return See ::Dso::ErrorCode.
Dso::ErrorCode setCoupling(ChannelID channel, Dso::Coupling coupling);

/// \brief Sets the gain for the given channel.
/// Get the actual gain by specification.gainSteps[hardwareGainIndex]
/// \param channel The channel that should be set.
/// \param hardwareGainIndex The gain index that refers to a gain value, defined by the device model.
/// \return See ::Dso::ErrorCode.
Dso::ErrorCode setGain(ChannelID channel, unsigned hardwareGainIndex, bool overwrite = false);

/// \brief Set the offset for the given channel.
/// Get the actual offset for the channel from devicesettings.voltage[channel].offsetReal
/// \param channel The channel that should be set.
/// \param offset The new offset value [-1.0,1.0]. Default is 0.
/// \return See ::Dso::ErrorCode.
Dso::ErrorCode setOffset(ChannelID channel, double offset, bool overwrite = false);

/// \brief Set the trigger mode.
/// \return See ::Dso::ErrorCode.
Dso::ErrorCode setTriggerMode(Dso::TriggerMode mode);

/// \brief Set the trigger source.
/// \param special true for a special channel (EXT, ...) as trigger source.
/// \param id The number of the channel, that should be used as trigger.
/// \return See ::Dso::ErrorCode.
Dso::ErrorCode setTriggerSource(bool special, ChannelID channel);

/// \brief Set the trigger level.
/// \param channel The channel that should be set.
/// \param level The new trigger offset value [-1.0,1.0]. Default is 0.
/// \return The trigger level that has been set, ::Dso::ErrorCode on error.
Dso::ErrorCode setTriggerOffset(ChannelID channel, double offset, bool overwrite = false);

/// \brief Set the trigger slope.
/// \param slope The Slope that should cause a trigger.
/// \return See ::Dso::ErrorCode.
Dso::ErrorCode setTriggerSlope(Dso::Slope slope);

/// \brief Set the pre-trigger sample range in percentage.
///
/// A sample set is longer than what is displayed on screen and only the part where the hard- or software trigger
/// found a matching signal slope is shown. To move the visible area, this method can be used by providing a
/// percentage value.
///
/// \param position The new pre-trigger position [0.0,1.0]. Default is 0.
/// \return See ::Dso::ErrorCode.
Dso::ErrorCode setPretriggerPosition(double position, bool overwrite = false);

/// \brief Forces a hardware-trigger to trigger although the condition is not met
/// Does nothing on software-trigger devices.
void forceTrigger();

signals:
void samplingStatusChanged(bool enabled); ///< The oscilloscope started/stopped sampling/waiting for trigger
void samplesAvailable(const DSOsamples *samples); ///< New sample data is available
void communicationError() const; ///< USB device error (disconnect/transfer/misbehave problem)
void debugMessage(const QString &msg, Debug::NotificationType typeEnum) const;
};

Q_DECLARE_METATYPE(DSOsamples *)
Q_DECLARE_METATYPE(std::vector<Dso::FixedSampleRate>)
388 changes: 388 additions & 0 deletions openhantek/src/hantekdso/dsoloop.cpp

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions openhantek/src/hantekdso/dsoloop.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: GPL-2.0+

#pragma once

#include "devicesettings.h"
#include "dsosamples.h"
#include "errorcodes.h"
#include "hantekdso/modelspecification.h"
#include "hantekprotocol/bulkStructs.h"
#include "hantekprotocol/controlStructs.h"
#include "hantekprotocol/definitions.h"
#include "utils/debugnotify.h"

#include <vector>

class DsoControl;

/**
* Implements the Dso logic for fetching/converting the samples at the right time.
*/
class DsoLoop : public QObject {
Q_OBJECT
public:
DsoLoop(std::shared_ptr<Dso::DeviceSettings> settings, DsoControl *control);

/// Call this to start the processing loop.
/// This method will call itself periodically from there on.
inline void run() { m_settings->isRollMode() ? runRollMode() : runStandardMode(); }

inline bool isSampling() const { return sampling; }

/// \brief If sampling is disabled, no samplesAvailable() signals are send anymore, no samples
/// are fetched from the device and no processing takes place.
/// \param enabled Enables/Disables sampling
void enableSampling(bool enabled);

/// Return the last sample set
inline const DSOsamples &getLastSamples() { return result; }

private:
void runRollMode();
void runStandardMode();

void updateInterval();

/// \brief Calculates the trigger point from the CommandGetCaptureState data.
/// \param value The data value that contains the trigger point.
/// \return The calculated trigger point for the given data.
static unsigned calculateTriggerPoint(unsigned value);

/// \brief Converts raw oscilloscope data to sample data
void convertRawDataToSamples(const std::vector<unsigned char> &rawData);

private:
int captureState = Hantek::CAPTURE_WAITING;
Hantek::RollState rollState = Hantek::RollState::STARTSAMPLING;
bool _samplingStarted = false;
Dso::TriggerMode lastTriggerMode = (Dso::TriggerMode)-1;
std::chrono::milliseconds cycleCounter = 0ms;
std::chrono::milliseconds startCycle = 0ms;
std::chrono::milliseconds cycleTime = 0ms;
bool sampling = false; ///< true, if the oscilloscope is taking samples
unsigned expectedSampleCount = 0; ///< The expected total number of samples at
/// the last check before sampling started
// Device setup
const Dso::ModelSpec *m_specification; ///< The specifications of the device
std::shared_ptr<Dso::DeviceSettings> m_settings; ///< The current settings of the device
const int m_modelID;

// Results
DSOsamples result;

DsoControl *m_control;
};
7 changes: 4 additions & 3 deletions openhantek/src/hantekdso/dsomodel.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@

// SPDX-License-Identifier: GPL-2.0+

#include "dsomodel.h"
#include "modelregistry.h"
#include "hantekdso/modelspecification.h"

DSOModel::DSOModel(int id, long vendorID, long productID, long vendorIDnoFirmware, long productIDnoFirmware,
const std::string &firmwareToken, const std::string &name,
const Dso::ControlSpecification &&specification)
const std::string &firmwareToken, const std::string &name, Dso::ModelSpec *specification)
: ID(id), vendorID(vendorID), productID(productID), vendorIDnoFirmware(vendorIDnoFirmware),
productIDnoFirmware(productIDnoFirmware), firmwareToken(firmwareToken), name(name), specification(specification) {
ModelRegistry::get()->add(this);
}

DSOModel::~DSOModel() { delete specification; }
19 changes: 10 additions & 9 deletions openhantek/src/hantekdso/dsomodel.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@

// SPDX-License-Identifier: GPL-2.0+

#pragma once

#include "controlspecification.h"
#include <list>
#include <string>

class HantekDsoControl;
class DsoCommandQueue;
namespace Dso {
struct ModelSpec;
}

/**
* @brief Describes a device
@@ -27,14 +28,14 @@ class DSOModel {
std::string firmwareToken;
std::string name; ///< User visible name. Does not need internationalisation/translation.
protected:
Dso::ControlSpecification specification;
Dso::ModelSpec *specification;

public:
/// This model may need to modify the HantekDsoControl class to work correctly
virtual void applyRequirements(HantekDsoControl *) const = 0;
/// Add available commands to the command queue object
virtual void applyRequirements(DsoCommandQueue *) const = 0;
DSOModel(int id, long vendorID, long productID, long vendorIDnoFirmware, long productIDnoFirmware,
const std::string &firmwareToken, const std::string &name, const Dso::ControlSpecification &&specification);
virtual ~DSOModel() = default;
const std::string &firmwareToken, const std::string &name, Dso::ModelSpec *specification);
virtual ~DSOModel();
/// Return the device specifications
inline const Dso::ControlSpecification *spec() const { return &specification; }
inline const Dso::ModelSpec *spec() const { return specification; }
};
42 changes: 39 additions & 3 deletions openhantek/src/hantekdso/dsosamples.h
Original file line number Diff line number Diff line change
@@ -2,14 +2,50 @@

#pragma once

#include "hantekprotocol/types.h"
#include <QReadLocker>
#include <QReadWriteLock>
#include <QWriteLocker>
#include <vector>

struct DSOsamples {
std::vector<std::vector<double>> data; ///< Pointer to input data from device
double samplerate = 0.0; ///< The samplerate of the input data
bool append = false; ///< true, if waiting data should be appended
struct ChannelSamples : public std::vector<double> {
ChannelID id;
// some statistics
double minVoltage;
double maxVoltage;
uint16_t minRaw;
uint16_t maxRaw;
};
std::vector<ChannelSamples> data; ///< Pointer to input data from device
double samplerate = 0.0; ///< The samplerate of the input data
bool append = false; ///< true, if waiting data should be appended
unsigned availableChannels = 0;
mutable QReadWriteLock lock;

DSOsamples(unsigned channels) { data.resize(channels); }
inline unsigned channelCount() const { return availableChannels; }
/**
* Clears the sample array and sets all fields. For performance reasons, we do not
* resize the channel dimension of the data array. This way all allocated resources
* are still allocated and can potentially be reused.
*
* @param channels The number of available channels. This must be lower than the channel count
* of the constructor.
* @param samplerate A samplerate
* @param append Roll mode or not
*/
inline void prepareForWrite(unsigned channels, double samplerate, bool append) {
this->samplerate = samplerate;
this->append = append;
this->availableChannels = channels;
for (ChannelSamples &v : data) {
v.id = (unsigned)-1; /// Invalid id
v.clear(); /// Clear all samples
v.maxRaw = 0;
v.minRaw = 0;
v.minVoltage = 2;
v.maxVoltage = -2;
}
}
};
154 changes: 75 additions & 79 deletions openhantek/src/hantekdso/enums.cpp
Original file line number Diff line number Diff line change
@@ -1,96 +1,92 @@
// SPDX-License-Identifier: GPL-2.0+

#include "enums.h"
#include <QCoreApplication>

namespace Dso {
Enum<Dso::TriggerMode, Dso::TriggerMode::HARDWARE_SOFTWARE, Dso::TriggerMode::SINGLE> TriggerModeEnum;
Enum<Dso::Slope, Dso::Slope::Positive, Dso::Slope::Negative> SlopeEnum;
Enum<Dso::GraphFormat, Dso::GraphFormat::TY, Dso::GraphFormat::XY> GraphFormatEnum;

/// \brief Return string representation of the given channel mode.
/// \param mode The ::ChannelMode that should be returned as string.
/// \return The string that should be used in labels etc., empty when invalid.
QString channelModeString(ChannelMode mode) {
switch (mode) {
case ChannelMode::Voltage:
return QCoreApplication::tr("Voltage");
case ChannelMode::Spectrum:
return QCoreApplication::tr("Spectrum");
}
return QString();
/// \brief Return string representation of the given channel mode.
/// \param mode The ::ChannelMode that should be returned as string.
/// \return The string that should be used in labels etc., empty when invalid.
QString channelModeString(ChannelMode mode) {
switch (mode) {
case ChannelMode::Voltage:
return QCoreApplication::tr("Voltage");
case ChannelMode::Spectrum:
return QCoreApplication::tr("Spectrum");
}
return QString();
}

/// \brief Return string representation of the given graph format.
/// \param format The ::GraphFormat that should be returned as string.
/// \return The string that should be used in labels etc.
QString graphFormatString(GraphFormat format) {
switch (format) {
case GraphFormat::TY:
return QCoreApplication::tr("T - Y");
case GraphFormat::XY:
return QCoreApplication::tr("X - Y");
}
return QString();
/// \brief Return string representation of the given graph format.
/// \param format The ::GraphFormat that should be returned as string.
/// \return The string that should be used in labels etc.
QString graphFormatString(GraphFormat format) {
switch (format) {
case GraphFormat::TY:
return QCoreApplication::tr("T - Y");
case GraphFormat::XY:
return QCoreApplication::tr("X - Y");
}
return QString();
}

/// \brief Return string representation of the given channel coupling.
/// \param coupling The ::Coupling that should be returned as string.
/// \return The string that should be used in labels etc.
QString couplingString(Coupling coupling) {
switch (coupling) {
case Coupling::AC:
return QCoreApplication::tr("AC");
case Coupling::DC:
return QCoreApplication::tr("DC");
case Coupling::GND:
return QCoreApplication::tr("GND");
}
return QString();
/// \brief Return string representation of the given channel coupling.
/// \param coupling The ::Coupling that should be returned as string.
/// \return The string that should be used in labels etc.
QString couplingString(Coupling coupling) {
switch (coupling) {
case Coupling::AC:
return QCoreApplication::tr("AC");
case Coupling::DC:
return QCoreApplication::tr("DC");
case Coupling::GND:
return QCoreApplication::tr("GND");
}
return QString();
}


/// \brief Return string representation of the given trigger mode.
/// \param mode The ::TriggerMode that should be returned as string.
/// \return The string that should be used in labels etc.
QString triggerModeString(TriggerMode mode) {
switch (mode) {
case TriggerMode::WAIT_FORCE:
return QCoreApplication::tr("Wait/Force");
case TriggerMode::HARDWARE_SOFTWARE:
return QCoreApplication::tr("Hard-/Software");
case TriggerMode::SINGLE:
return QCoreApplication::tr("Single");
}
return QString();
/// \brief Return string representation of the given trigger mode.
/// \param mode The ::TriggerMode that should be returned as string.
/// \return The string that should be used in labels etc.
QString triggerModeString(TriggerMode mode) {
switch (mode) {
case TriggerMode::WAIT_FORCE:
return QCoreApplication::tr("Wait/Force");
case TriggerMode::HARDWARE_SOFTWARE:
return QCoreApplication::tr("Hard-/Software");
case TriggerMode::SINGLE:
return QCoreApplication::tr("Single");
}
return QString();
}

/// \brief Return string representation of the given trigger slope.
/// \param slope The ::Slope that should be returned as string.
/// \return The string that should be used in labels etc.
QString slopeString(Slope slope) {
switch (slope) {
case Slope::Positive:
return QString::fromUtf8("\u2197");
case Slope::Negative:
return QString::fromUtf8("\u2198");
default:
return QString();
}
/// \brief Return string representation of the given trigger slope.
/// \param slope The ::Slope that should be returned as string.
/// \return The string that should be used in labels etc.
QString slopeString(Slope slope) {
switch (slope) {
case Slope::Positive:
return QString::fromUtf8("\u2197");
case Slope::Negative:
return QString::fromUtf8("\u2198");
}
return QString();
}

/// \brief Return string representation of the given graph interpolation mode.
/// \param interpolation The ::InterpolationMode that should be returned as
/// string.
/// \return The string that should be used in labels etc.
QString interpolationModeString(InterpolationMode interpolation) {
switch (interpolation) {
case INTERPOLATION_OFF:
return QCoreApplication::tr("Off");
case INTERPOLATION_LINEAR:
return QCoreApplication::tr("Linear");
case INTERPOLATION_SINC:
return QCoreApplication::tr("Sinc");
default:
return QString();
}
/// \brief Return string representation of the given graph interpolation mode.
/// \param interpolation The ::InterpolationMode that should be returned as
/// string.
/// \return The string that should be used in labels etc.
QString interpolationModeString(InterpolationMode interpolation) {
switch (interpolation) {
case InterpolationMode::OFF:
return QCoreApplication::tr("Off");
case InterpolationMode::LINEAR:
return QCoreApplication::tr("Linear");
case InterpolationMode::SINC:
return QCoreApplication::tr("Sinc");
}
return QString();
}
}
33 changes: 15 additions & 18 deletions openhantek/src/hantekdso/enums.h
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
// SPDX-License-Identifier: GPL-2.0+

#pragma once

#include "utils/enumclass.h"
#include <QMetaType>
#include <QString>
namespace Dso {
Q_NAMESPACE

/// \enum ChannelMode
/// \brief The channel display modes.
enum class ChannelMode {
Voltage, ///< Standard voltage view
Spectrum ///< Spectrum view
};
Q_ENUM_NS(ChannelMode)

/// \enum GraphFormat
/// \brief The possible viewing formats for the graphs on the scope.
enum GraphFormat {
enum class GraphFormat {
TY, ///< The standard mode
XY ///< CH1 on X-axis, CH2 on Y-axis
};

extern Enum<Dso::GraphFormat, Dso::GraphFormat::TY, Dso::GraphFormat::XY> GraphFormatEnum;
Q_ENUM_NS(GraphFormat)

/// \enum Coupling
/// \brief The coupling modes for the channels.
@@ -27,6 +30,7 @@ enum class Coupling {
DC, ///< No filtering
GND ///< Channel is grounded
};
Q_ENUM_NS(Coupling)

/// \enum TriggerMode
/// \brief The different triggering modes.
@@ -35,24 +39,24 @@ enum class TriggerMode {
WAIT_FORCE, ///< Automatic without trigger event
SINGLE ///< Stop after the first trigger event
};
extern Enum<Dso::TriggerMode, Dso::TriggerMode::HARDWARE_SOFTWARE, Dso::TriggerMode::SINGLE> TriggerModeEnum;
Q_ENUM_NS(TriggerMode)

/// \enum Slope
/// \brief The slope that causes a trigger.
enum class Slope : uint8_t {
Positive = 0, ///< From lower to higher voltage
Negative = 1 ///< From higher to lower voltage
};
extern Enum<Dso::Slope, Dso::Slope::Positive, Dso::Slope::Negative> SlopeEnum;
Q_ENUM_NS(Slope)

/// \enum InterpolationMode
/// \brief The different interpolation modes for the graphs.
enum InterpolationMode {
INTERPOLATION_OFF = 0, ///< Just dots for each sample
INTERPOLATION_LINEAR, ///< Sample dots connected by lines
INTERPOLATION_SINC, ///< Smooth graph through the dots
INTERPOLATION_COUNT ///< Total number of interpolation modes
enum class InterpolationMode {
OFF = 0, ///< Just dots for each sample
LINEAR, ///< Sample dots connected by lines
SINC, ///< Smooth graph through the dots
};
Q_ENUM_NS(InterpolationMode)

QString channelModeString(ChannelMode mode);
QString graphFormatString(GraphFormat format);
@@ -61,10 +65,3 @@ QString triggerModeString(TriggerMode mode);
QString slopeString(Slope slope);
QString interpolationModeString(InterpolationMode interpolation);
}

Q_DECLARE_METATYPE(Dso::TriggerMode)
Q_DECLARE_METATYPE(Dso::Slope)
Q_DECLARE_METATYPE(Dso::Coupling)
Q_DECLARE_METATYPE(Dso::GraphFormat)
Q_DECLARE_METATYPE(Dso::ChannelMode)
Q_DECLARE_METATYPE(Dso::InterpolationMode)
7 changes: 4 additions & 3 deletions openhantek/src/hantekdso/errorcodes.h
Original file line number Diff line number Diff line change
@@ -7,9 +7,10 @@ namespace Dso {
/// \brief The return codes for device control methods.
enum class ErrorCode {
NONE = 0, ///< Successful operation
CONNECTION = -1, ///< Device not connected or communication error
CONNECTION = -1, ///< Device not connected
UNSUPPORTED = -2, ///< Not supported by this device
PARAMETER = -3 ///< Parameter out of range
PARAMETER = -3, ///< Parameter out of range
UNCHANGED = -4, ///< Not applied because unchanged
UNEXPECTED = -5 ///< The device responded with an (unexpected) usb error code
};

}
1,326 changes: 0 additions & 1,326 deletions openhantek/src/hantekdso/hantekdsocontrol.cpp

This file was deleted.

296 changes: 0 additions & 296 deletions openhantek/src/hantekdso/hantekdsocontrol.h

This file was deleted.

7 changes: 4 additions & 3 deletions openhantek/src/hantekdso/modelregistry.cpp
Original file line number Diff line number Diff line change
@@ -2,9 +2,10 @@

#include "modelregistry.h"

ModelRegistry *ModelRegistry::instance = new ModelRegistry();

ModelRegistry *ModelRegistry::get() { return instance; }
ModelRegistry *ModelRegistry::get() {
static ModelRegistry *instance = new ModelRegistry();
return instance;
}

void ModelRegistry::add(DSOModel *model) { supportedModels.push_back(model); }

13 changes: 6 additions & 7 deletions openhantek/src/hantekdso/modelregistry.h
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@

// SPDX-License-Identifier: GPL-2.0+

#pragma once

#include "dsomodel.h"

class ModelRegistry {
public:
public:
static ModelRegistry *get();
void add(DSOModel* model);
const std::list<DSOModel*> models() const;
private:
static ModelRegistry* instance;
std::list<DSOModel*> supportedModels;
void add(DSOModel *model);
const std::list<DSOModel *> models() const;

private:
std::list<DSOModel *> supportedModels;
};
91 changes: 44 additions & 47 deletions openhantek/src/hantekdso/models/modelDSO2090.cpp
Original file line number Diff line number Diff line change
@@ -1,65 +1,62 @@
#include "modelDSO2090.h"
#include "hantekprotocol/bulkStructs.h"
#include "hantekprotocol/controlStructs.h"
#include "hantekdsocontrol.h"
#include "dsocommandqueue.h"
#include "modelspecification.h"

using namespace Hantek;

static ModelDSO2090 modelInstance;
static ModelDSO2090A modelInstance2;

void _applyRequirements(HantekDsoControl *dsoControl) {
dsoControl->addCommand(new BulkForceTrigger(), false);
dsoControl->addCommand(new BulkCaptureStart(), false);
dsoControl->addCommand(new BulkTriggerEnabled(), false);
dsoControl->addCommand(new BulkGetData(), false);
dsoControl->addCommand(new BulkGetCaptureState(), false);
dsoControl->addCommand(new BulkSetGain(), false);

dsoControl->addCommand(new BulkSetTriggerAndSamplerate(), false);
dsoControl->addCommand(new ControlSetOffset(), false);
dsoControl->addCommand(new ControlSetRelays(), false);
void _applyRequirements(DsoCommandQueue *commandQueue) {
commandQueue->addCommand(new BulkForceTrigger(), false);
commandQueue->addCommand(new BulkCaptureStart(), false);
commandQueue->addCommand(new BulkTriggerEnabled(), false);
commandQueue->addCommand(new BulkGetData(), false);
commandQueue->addCommand(new BulkGetCaptureState(), false);
commandQueue->addCommand(new BulkSetGain(), false);

commandQueue->addCommand(new BulkSetTriggerAndSamplerate(), false);
commandQueue->addCommand(new ControlSetOffset(), false);
commandQueue->addCommand(new ControlSetRelays(), false);
}

void initSpecifications(Dso::ControlSpecification& specification) {
specification.cmdSetRecordLength = BulkCode::SETTRIGGERANDSAMPLERATE;
specification.cmdSetChannels = BulkCode::SETTRIGGERANDSAMPLERATE;
specification.cmdSetSamplerate = BulkCode::SETTRIGGERANDSAMPLERATE;
specification.cmdSetTrigger = BulkCode::SETTRIGGERANDSAMPLERATE;
specification.cmdSetPretrigger = BulkCode::SETTRIGGERANDSAMPLERATE;

specification.samplerate.single.base = 50e6;
specification.samplerate.single.max = 50e6;
specification.samplerate.single.maxDownsampler = 131072;
specification.samplerate.single.recordLengths = {UINT_MAX, 10240, 32768};
specification.samplerate.multi.base = 100e6;
specification.samplerate.multi.max = 100e6;
specification.samplerate.multi.maxDownsampler = 131072;
specification.samplerate.multi.recordLengths = {UINT_MAX, 20480, 65536};
specification.bufferDividers = { 1000 , 1 , 1 };
specification.voltageLimit[0] = { 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 };
specification.voltageLimit[1] = { 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 };
specification.gain = { {0,0.08} , {1,0.16} , {2,0.40} , {0,0.80} ,
{1,1.60} , {2,4.00} , {0,8.00} , {1,16.00} , {2,40.00} };
specification.sampleSize = 8;
specification.specialTriggerChannels = {{"EXT", -2}, {"EXT/10", -3}};
void initSpecifications(Dso::ModelSpec *specification) {
specification->cmdSetRecordLength = BulkCode::SETTRIGGERANDSAMPLERATE;
specification->cmdSetChannels = BulkCode::SETTRIGGERANDSAMPLERATE;
specification->cmdSetSamplerate = BulkCode::SETTRIGGERANDSAMPLERATE;
specification->cmdSetTrigger = BulkCode::SETTRIGGERANDSAMPLERATE;
specification->cmdSetPretrigger = BulkCode::SETTRIGGERANDSAMPLERATE;

specification->normalSamplerate.base = 50e6;
specification->normalSamplerate.max = 50e6;
specification->normalSamplerate.maxDownsampler = 131072;
specification->normalSamplerate.recordLengths = {{UINT_MAX, 1000}, {10240, 1}, {32768, 1}};
specification->fastrateSamplerate.base = 100e6;
specification->fastrateSamplerate.max = 100e6;
specification->fastrateSamplerate.maxDownsampler = 131072;
specification->fastrateSamplerate.recordLengths = {{UINT_MAX, 1000}, {20480, 1}, {65536, 1}};
specification->calibration[0] = {{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255},
{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255},
{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}};
specification->calibration[1] = {{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255},
{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255},
{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}};
specification->gain = {{0, 0.08}, {1, 0.16}, {2, 0.40}, {0, 0.80}, {1, 1.60},
{2, 4.00}, {0, 8.00}, {1, 16.00}, {2, 40.00}};
specification->sampleSize = 8;
specification->specialTriggerChannels = {{"EXT", -2}, {"EXT/10", -3}};
}

ModelDSO2090::ModelDSO2090() : DSOModel(ID, 0x04b5, 0x2090, 0x04b4, 0x2090, "dso2090x86", "DSO-2090",
Dso::ControlSpecification(2)) {
ModelDSO2090::ModelDSO2090()
: DSOModel(ID, 0x04b5, 0x2090, 0x04b4, 0x2090, "dso2090x86", "DSO-2090", new Dso::ModelSpec(2)) {
initSpecifications(specification);
}

void ModelDSO2090::applyRequirements(HantekDsoControl *dsoControl) const {
_applyRequirements(dsoControl);
}
void ModelDSO2090::applyRequirements(DsoCommandQueue *dsoControl) const { _applyRequirements(dsoControl); }

ModelDSO2090A::ModelDSO2090A() : DSOModel(ID, 0x04b5, 0x2090, 0x04b4, 0x8613, "dso2090x86", "DSO-2090",
Dso::ControlSpecification(2)) {
ModelDSO2090A::ModelDSO2090A()
: DSOModel(ID, 0x04b5, 0x2090, 0x04b4, 0x8613, "dso2090x86", "DSO-2090", new Dso::ModelSpec(2)) {
initSpecifications(specification);
}

void ModelDSO2090A::applyRequirements(HantekDsoControl *dsoControl) const {
_applyRequirements(dsoControl);
}

void ModelDSO2090A::applyRequirements(DsoCommandQueue *dsoControl) const { _applyRequirements(dsoControl); }
7 changes: 3 additions & 4 deletions openhantek/src/hantekdso/models/modelDSO2090.h
Original file line number Diff line number Diff line change
@@ -2,18 +2,17 @@

#include "dsomodel.h"

class HantekDsoControl;
using namespace Hantek;
class DsoCommandQueue;

struct ModelDSO2090 : public DSOModel {
static const int ID = 0x2090;
ModelDSO2090();
void applyRequirements(HantekDsoControl* dsoControl) const override;
void applyRequirements(DsoCommandQueue *dsoControl) const override;
};

struct ModelDSO2090A : public DSOModel {
static const int ID = 0x2090;
ModelDSO2090A();
void applyRequirements(HantekDsoControl* dsoControl) const override;
void applyRequirements(DsoCommandQueue* dsoControl) const override;
};

72 changes: 37 additions & 35 deletions openhantek/src/hantekdso/models/modelDSO2150.cpp
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
#include "modelDSO2150.h"
#include "hantekprotocol/bulkStructs.h"
#include "hantekprotocol/controlStructs.h"
#include "hantekdsocontrol.h"
#include "dsocommandqueue.h"
#include "modelspecification.h"

using namespace Hantek;

static ModelDSO2150 modelInstance;

ModelDSO2150::ModelDSO2150() : DSOModel(ID, 0x04b5, 0x2150, 0x04b4, 0x2150, "dso2150x86", "DSO-2150",
Dso::ControlSpecification(2)) {
specification.cmdSetRecordLength = BulkCode::SETTRIGGERANDSAMPLERATE;
specification.cmdSetChannels = BulkCode::SETTRIGGERANDSAMPLERATE;
specification.cmdSetSamplerate = BulkCode::SETTRIGGERANDSAMPLERATE;
specification.cmdSetTrigger = BulkCode::SETTRIGGERANDSAMPLERATE;
specification.cmdSetPretrigger = BulkCode::SETTRIGGERANDSAMPLERATE;
ModelDSO2150::ModelDSO2150()
: DSOModel(ID, 0x04b5, 0x2150, 0x04b4, 0x2150, "dso2150x86", "DSO-2150", new Dso::ModelSpec(2)) {
specification->cmdSetRecordLength = BulkCode::SETTRIGGERANDSAMPLERATE;
specification->cmdSetChannels = BulkCode::SETTRIGGERANDSAMPLERATE;
specification->cmdSetSamplerate = BulkCode::SETTRIGGERANDSAMPLERATE;
specification->cmdSetTrigger = BulkCode::SETTRIGGERANDSAMPLERATE;
specification->cmdSetPretrigger = BulkCode::SETTRIGGERANDSAMPLERATE;

specification.samplerate.single.base = 50e6;
specification.samplerate.single.max = 75e6;
specification.samplerate.single.maxDownsampler = 131072;
specification.samplerate.single.recordLengths = {UINT_MAX, 10240, 32768};
specification.samplerate.multi.base = 100e6;
specification.samplerate.multi.max = 150e6;
specification.samplerate.multi.maxDownsampler = 131072;
specification.samplerate.multi.recordLengths = {UINT_MAX, 20480, 65536};
specification.bufferDividers = { 1000 , 1 , 1 };
specification.voltageLimit[0] = { 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 };
specification.voltageLimit[1] = { 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 };
specification.gain = { {0,0.08} , {1,0.16} , {2,0.40} , {0,0.80} ,
{1,1.60} , {2,4.00} , {0,8.00} , {1,16.00} , {2,40.00} };
specification.sampleSize = 8;
specification.specialTriggerChannels = {{"EXT", -2}, {"EXT/10", -3}};
specification->normalSamplerate.base = 50e6;
specification->normalSamplerate.max = 75e6;
specification->normalSamplerate.maxDownsampler = 131072;
specification->normalSamplerate.recordLengths = {{UINT_MAX, 1000}, {10240, 1}, {32768, 1}};
specification->fastrateSamplerate.base = 100e6;
specification->fastrateSamplerate.max = 150e6;
specification->fastrateSamplerate.maxDownsampler = 131072;
specification->fastrateSamplerate.recordLengths = {{UINT_MAX, 1000}, {20480, 1}, {65536, 1}};
specification->calibration[0] = {{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255},
{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255},
{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}};
specification->calibration[1] = {{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255},
{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255},
{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}};
specification->gain = {{0, 0.08}, {1, 0.16}, {2, 0.40}, {0, 0.80}, {1, 1.60},
{2, 4.00}, {0, 8.00}, {1, 16.00}, {2, 40.00}};
specification->sampleSize = 8;
specification->specialTriggerChannels = {{"EXT", -2}, {"EXT/10", -3}};
}

void ModelDSO2150::applyRequirements(HantekDsoControl *dsoControl) const {
dsoControl->addCommand(new BulkForceTrigger(), false);
dsoControl->addCommand(new BulkCaptureStart(), false);
dsoControl->addCommand(new BulkTriggerEnabled(), false);
dsoControl->addCommand(new BulkGetData(), false);
dsoControl->addCommand(new BulkGetCaptureState(), false);
dsoControl->addCommand(new BulkSetGain(), false);
void ModelDSO2150::applyRequirements(DsoCommandQueue *commandQueue) const {
commandQueue->addCommand(new BulkForceTrigger(), false);
commandQueue->addCommand(new BulkCaptureStart(), false);
commandQueue->addCommand(new BulkTriggerEnabled(), false);
commandQueue->addCommand(new BulkGetData(), false);
commandQueue->addCommand(new BulkGetCaptureState(), false);
commandQueue->addCommand(new BulkSetGain(), false);

dsoControl->addCommand(new BulkSetTriggerAndSamplerate(), false);
dsoControl->addCommand(new ControlSetOffset(), false);
dsoControl->addCommand(new ControlSetRelays(), false);
commandQueue->addCommand(new BulkSetTriggerAndSamplerate(), false);
commandQueue->addCommand(new ControlSetOffset(), false);
commandQueue->addCommand(new ControlSetRelays(), false);
}
5 changes: 2 additions & 3 deletions openhantek/src/hantekdso/models/modelDSO2150.h
Original file line number Diff line number Diff line change
@@ -2,11 +2,10 @@

#include "dsomodel.h"

class HantekDsoControl;
using namespace Hantek;
class DsoControl;

struct ModelDSO2150 : public DSOModel {
static const int ID = 0x2150;
ModelDSO2150();
virtual void applyRequirements(HantekDsoControl* dsoControl) const override;
virtual void applyRequirements(DsoCommandQueue* dsoControl) const override;
};
54 changes: 28 additions & 26 deletions openhantek/src/hantekdso/models/modelDSO2250.cpp
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
#include "modelDSO2250.h"
#include "hantekprotocol/bulkStructs.h"
#include "hantekprotocol/controlStructs.h"
#include "hantekdsocontrol.h"
#include "dsocommandqueue.h"
#include "modelspecification.h"

using namespace Hantek;

static ModelDSO2250 modelInstance;

ModelDSO2250::ModelDSO2250() : DSOModel(ID, 0x04b5, 0x2250, 0x04b4, 0x2250, "dso2250x86", "DSO-2250",
Dso::ControlSpecification(2)) {
specification.cmdSetRecordLength = BulkCode::DSETBUFFER;
specification.cmdSetChannels = BulkCode::BSETCHANNELS;
specification.cmdSetSamplerate = BulkCode::ESETTRIGGERORSAMPLERATE;
specification.cmdSetTrigger = BulkCode::CSETTRIGGERORSAMPLERATE;
specification.cmdSetPretrigger = BulkCode::FSETBUFFER;
ModelDSO2250::ModelDSO2250()
: DSOModel(ID, 0x04b5, 0x2250, 0x04b4, 0x2250, "dso2250x86", "DSO-2250", new Dso::ModelSpec(2)) {
specification->cmdSetRecordLength = BulkCode::DSETBUFFER;
specification->cmdSetChannels = BulkCode::BSETCHANNELS;
specification->cmdSetSamplerate = BulkCode::ESETTRIGGERORSAMPLERATE;
specification->cmdSetTrigger = BulkCode::CSETTRIGGERORSAMPLERATE;
specification->cmdSetPretrigger = BulkCode::FSETBUFFER;

specification.samplerate.single.base = 100e6;
specification.samplerate.single.max = 100e6;
specification.samplerate.single.maxDownsampler = 65536;
specification.samplerate.single.recordLengths = {UINT_MAX, 10240, 524288};
specification.samplerate.multi.base = 200e6;
specification.samplerate.multi.max = 250e6;
specification.samplerate.multi.maxDownsampler = 65536;
specification.samplerate.multi.recordLengths = {UINT_MAX, 20480, 1048576};
specification.bufferDividers = { 1000 , 1 , 1 };
specification.voltageLimit[0] = { 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 };
specification.voltageLimit[1] = { 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 };
specification.gain = { {0,0.08} , {2,0.16} , {3,0.40} , {0,0.80} ,
{2,1.60} , {3,4.00} , {0,8.00} , {2,16.00} , {3,40.00} };
specification.sampleSize = 8;
specification.specialTriggerChannels = {{"EXT", -2}};
specification->normalSamplerate.base = 100e6;
specification->normalSamplerate.max = 100e6;
specification->normalSamplerate.maxDownsampler = 65536;
specification->normalSamplerate.recordLengths = {{UINT_MAX, 1000}, {10240, 1}, {524288, 1}};
specification->fastrateSamplerate.base = 200e6;
specification->fastrateSamplerate.max = 250e6;
specification->fastrateSamplerate.maxDownsampler = 65536;
specification->fastrateSamplerate.recordLengths = {{UINT_MAX, 1000}, {20480, 1}, {1048576, 1}};
specification->calibration[0] = {{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255},
{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255},
{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}};
specification->calibration[1] = {{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255},
{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255},
{0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}, {0x0000, 0xffff, 255}};
specification->gain = {{0, 0.08}, {2, 0.16}, {3, 0.40}, {0, 0.80}, {2, 1.60},
{3, 4.00}, {0, 8.00}, {2, 16.00}, {3, 40.00}};
specification->sampleSize = 8;
specification->specialTriggerChannels = {{"EXT", -2}};
}

void ModelDSO2250::applyRequirements(HantekDsoControl *dsoControl) const {
void ModelDSO2250::applyRequirements(DsoCommandQueue *dsoControl) const {
dsoControl->addCommand(new BulkForceTrigger(), false);
dsoControl->addCommand(new BulkCaptureStart(), false);
dsoControl->addCommand(new BulkTriggerEnabled(), false);
5 changes: 2 additions & 3 deletions openhantek/src/hantekdso/models/modelDSO2250.h
Original file line number Diff line number Diff line change
@@ -2,11 +2,10 @@

#include "dsomodel.h"

class HantekDsoControl;
using namespace Hantek;
class DsoControl;

struct ModelDSO2250 : public DSOModel {
static const int ID = 0x2250;
ModelDSO2250();
void applyRequirements(HantekDsoControl* dsoControl) const override;
void applyRequirements(DsoCommandQueue* dsoControl) const override;
};
69 changes: 33 additions & 36 deletions openhantek/src/hantekdso/models/modelDSO5200.cpp
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
#include "modelDSO5200.h"
#include "hantekprotocol/bulkStructs.h"
#include "hantekprotocol/controlStructs.h"
#include "hantekdsocontrol.h"
#include "dsocommandqueue.h"
#include "modelspecification.h"

using namespace Hantek;

static ModelDSO5200 modelInstance;
static ModelDSO5200A modelInstance2;

static void initSpecifications(Dso::ControlSpecification& specification) {
specification.cmdSetRecordLength = BulkCode::DSETBUFFER;
specification.cmdSetChannels = BulkCode::ESETTRIGGERORSAMPLERATE;
specification.cmdSetSamplerate = BulkCode::CSETTRIGGERORSAMPLERATE;
specification.cmdSetTrigger = BulkCode::ESETTRIGGERORSAMPLERATE;
specification.cmdSetPretrigger = BulkCode::ESETTRIGGERORSAMPLERATE;
static void initSpecifications(Dso::ModelSpec *specification) {
specification->cmdSetRecordLength = BulkCode::DSETBUFFER;
specification->cmdSetChannels = BulkCode::ESETTRIGGERORSAMPLERATE;
specification->cmdSetSamplerate = BulkCode::CSETTRIGGERORSAMPLERATE;
specification->cmdSetTrigger = BulkCode::ESETTRIGGERORSAMPLERATE;
specification->cmdSetPretrigger = BulkCode::ESETTRIGGERORSAMPLERATE;

specification.samplerate.single.base = 100e6;
specification.samplerate.single.max = 125e6;
specification.samplerate.single.maxDownsampler = 131072;
specification.samplerate.single.recordLengths = {UINT_MAX, 10240, 14336};
specification.samplerate.multi.base = 200e6;
specification.samplerate.multi.max = 250e6;
specification.samplerate.multi.maxDownsampler = 131072;
specification.samplerate.multi.recordLengths = {UINT_MAX, 20480, 28672};
specification.bufferDividers = { 1000 , 1 , 1 };
/// \todo Use calibration data to get the DSO-5200(A) sample ranges
specification.voltageLimit[0] = { 368 , 454 , 908 , 368 , 454 , 908 , 368 , 454 , 908 };
specification.voltageLimit[1] = { 368 , 454 , 908 , 368 , 454 , 908 , 368 , 454 , 908 };
specification.gain = { {1,0.16} , {0,0.40} , {0,0.80} , {1,1.60} ,
{0,4.00} , {0,8.00} , {1,16.0} , {0,40.0} , {0,80.0} };
specification.sampleSize = 10;
specification.specialTriggerChannels = {{"EXT", -2}, {"EXT/10", -3}}; // 3, 4
specification->normalSamplerate.base = 100e6;
specification->normalSamplerate.max = 125e6;
specification->normalSamplerate.maxDownsampler = 131072;
specification->normalSamplerate.recordLengths = {{UINT_MAX, 1000}, {10240, 1}, {14336, 1}};
specification->fastrateSamplerate.base = 200e6;
specification->fastrateSamplerate.max = 250e6;
specification->fastrateSamplerate.maxDownsampler = 131072;
specification->fastrateSamplerate.recordLengths = {{UINT_MAX, 1000}, {20480, 1}, {28672, 1}};
specification->calibration[0] = {{0x0000, 0xffff, 368}, {0x0000, 0xffff, 454}, {0x0000, 0xffff, 908},
{0x0000, 0xffff, 368}, {0x0000, 0xffff, 454}, {0x0000, 0xffff, 908},
{0x0000, 0xffff, 368}, {0x0000, 0xffff, 454}, {0x0000, 0xffff, 908}};
specification->calibration[1] = {{0x0000, 0xffff, 368}, {0x0000, 0xffff, 454}, {0x0000, 0xffff, 908},
{0x0000, 0xffff, 368}, {0x0000, 0xffff, 454}, {0x0000, 0xffff, 908},
{0x0000, 0xffff, 368}, {0x0000, 0xffff, 454}, {0x0000, 0xffff, 908}};
specification->gain = {{1, 0.16}, {0, 0.40}, {0, 0.80}, {1, 1.60}, {0, 4.00},
{0, 8.00}, {1, 16.0}, {0, 40.0}, {0, 80.0}};
specification->sampleSize = 10;
specification->specialTriggerChannels = {{"EXT", -2}, {"EXT/10", -3}}; // 3, 4
}

static void _applyRequirements(HantekDsoControl *dsoControl) {
static void _applyRequirements(DsoCommandQueue *dsoControl) {
dsoControl->addCommand(new BulkForceTrigger(), false);
dsoControl->addCommand(new BulkCaptureStart(), false);
dsoControl->addCommand(new BulkTriggerEnabled(), false);
@@ -49,20 +50,16 @@ static void _applyRequirements(HantekDsoControl *dsoControl) {
dsoControl->addCommand(new ControlSetRelays(), false);
}

ModelDSO5200::ModelDSO5200() : DSOModel(ID, 0x04b5, 0x5200, 0x04b4, 0x5200, "dso5200x86", "DSO-5200",
Dso::ControlSpecification(2)) {
ModelDSO5200::ModelDSO5200()
: DSOModel(ID, 0x04b5, 0x5200, 0x04b4, 0x5200, "dso5200x86", "DSO-5200", new Dso::ModelSpec(2)) {
initSpecifications(specification);
}

void ModelDSO5200::applyRequirements(HantekDsoControl *dsoControl) const {
_applyRequirements(dsoControl);
}
void ModelDSO5200::applyRequirements(DsoCommandQueue *dsoControl) const { _applyRequirements(dsoControl); }

ModelDSO5200A::ModelDSO5200A() : DSOModel(ID, 0x04b5, 0x520a, 0x04b4, 0x520a, "dso5200ax86", "DSO-5200A",
Dso::ControlSpecification(2)) {
ModelDSO5200A::ModelDSO5200A()
: DSOModel(ID, 0x04b5, 0x520a, 0x04b4, 0x520a, "dso5200ax86", "DSO-5200A", new Dso::ModelSpec(2)) {
initSpecifications(specification);
}

void ModelDSO5200A::applyRequirements(HantekDsoControl *dsoControl) const {
_applyRequirements(dsoControl);
}
void ModelDSO5200A::applyRequirements(DsoCommandQueue *dsoControl) const { _applyRequirements(dsoControl); }
7 changes: 3 additions & 4 deletions openhantek/src/hantekdso/models/modelDSO5200.h
Original file line number Diff line number Diff line change
@@ -2,17 +2,16 @@

#include "dsomodel.h"

class HantekDsoControl;
using namespace Hantek;
class DsoControl;

struct ModelDSO5200 : public DSOModel {
static const int ID = 0x5200;
ModelDSO5200();
void applyRequirements(HantekDsoControl* dsoControl) const override;
void applyRequirements(DsoCommandQueue *dsoControl) const override;
};

struct ModelDSO5200A : public DSOModel {
static const int ID = 0x5200;
ModelDSO5200A();
void applyRequirements(HantekDsoControl* dsoControl) const override;
void applyRequirements(DsoCommandQueue* dsoControl) const override;
};
87 changes: 43 additions & 44 deletions openhantek/src/hantekdso/models/modelDSO6022.cpp
Original file line number Diff line number Diff line change
@@ -1,67 +1,66 @@
#include "modelDSO6022.h"
#include "dsocommandqueue.h"
#include "modelspecification.h"
#include "usb/usbdevice.h"
#include "hantekprotocol/controlStructs.h"
#include "hantekdsocontrol.h"

using namespace Hantek;

static ModelDSO6022BE modelInstance;
static ModelDSO6022BL modelInstance2;

static void initSpecifications(Dso::ControlSpecification& specification) {
static void initSpecifications(Dso::ModelSpec *specification) {
// 6022xx do not support any bulk commands
specification.useControlNoBulk = true;
specification.isSoftwareTriggerDevice = true;
specification.isFixedSamplerateDevice = true;
specification.supportsCaptureState = false;
specification.supportsOffset = false;
specification.supportsCouplingRelays = false;
specification->useControlNoBulk = true;
specification->isSoftwareTriggerDevice = true;
specification->isFixedSamplerateDevice = true;
specification->supportsCaptureState = false;
specification->supportsOffset = false;
specification->supportsCouplingRelays = false;
specification->supportsFastRate = false;

specification.samplerate.single.base = 1e6;
specification.samplerate.single.max = 48e6;
specification.samplerate.single.maxDownsampler = 10;
specification.samplerate.single.recordLengths = {UINT_MAX, 10240};
specification.samplerate.multi.base = 1e6;
specification.samplerate.multi.max = 48e6;
specification.samplerate.multi.maxDownsampler = 10;
specification.samplerate.multi.recordLengths = {UINT_MAX, 20480};
specification.bufferDividers = { 1000 , 1 , 1 };
specification->normalSamplerate.base = 1e6;
specification->normalSamplerate.max = 48e6;
specification->normalSamplerate.maxDownsampler = 10;
specification->normalSamplerate.recordLengths = {{10240, 1}};
specification->fastrateSamplerate.base = 1e6;
specification->fastrateSamplerate.max = 48e6;
specification->fastrateSamplerate.maxDownsampler = 10;
specification->fastrateSamplerate.recordLengths = {{20480, 1}};
// This data was based on testing and depends on Divider.
specification.voltageLimit[0] = { 25 , 51 , 103 , 206 , 412 , 196 , 392 , 784 , 1000 };
specification.voltageLimit[1] = { 25 , 51 , 103 , 206 , 412 , 196 , 392 , 784 , 1000 };
// Divider. Tested and calculated results are different!
specification.gain = { {10,0.08} , {10,0.16} , {10,0.40} , {10,0.80} ,
{10,1.60} , {2,4.00} , {2,8.00} , {2,16.00} , {1,40.00} };
specification.fixedSampleRates = { {10,1e5} , {20,2e5} , {50,5e5} , {1,1e6} , {2,2e6} , {4,4e6} , {8,8e6} ,
{16,16e6} , {24,24e6} , {48,48e6} };
specification.sampleSize = 8;
specification->calibration[0] = {{0x0000, 0xfd, 10}, {0x0000, 0xfd, 20}, {0x0000, 0xfd, 49},
{0x0000, 0xfd, 99}, {0x0000, 0xfd, 198}, {0x0000, 0xfd, 400},
{0x0000, 0xfd, 800}, {0x0000, 0xfd, 1600}};
specification->calibration[1] = {{0x0000, 0xfd, 10}, {0x0000, 0xfd, 20}, {0x0000, 0xfd, 49},
{0x0000, 0xfd, 99}, {0x0000, 0xfd, 198}, {0x0000, 0xfd, 400},
{0x0000, 0xfd, 800}, {0x0000, 0xfd, 1600}};
specification->gain = {{10, 0.08}, {10, 0.16}, {10, 0.40}, {10, 0.80},
{10, 1.60}, {2, 4.00}, {2, 8.00}, {2, 16.00}};
specification->fixedSampleRates = {{10, 1e5}, {20, 2e5}, {50, 5e5}, {1, 1e6}, {2, 2e6},
{4, 4e6}, {8, 8e6}, {16, 16e6}, {24, 24e6}, {48, 48e6}};
specification->sampleSize = 8;

specification.couplings = {Dso::Coupling::DC};
specification.triggerModes = {Dso::TriggerMode::HARDWARE_SOFTWARE, Dso::TriggerMode::SINGLE};
specification.fixedUSBinLength = 16384;
specification->couplings = {Dso::Coupling::DC};
specification->triggerModes = {Dso::TriggerMode::HARDWARE_SOFTWARE, Dso::TriggerMode::SINGLE};
specification->fixedUSBinLength = 16384;
}

void applyRequirements_(HantekDsoControl *dsoControl) {
dsoControl->addCommand(new ControlAcquireHardData());
dsoControl->addCommand(new ControlSetTimeDIV());
dsoControl->addCommand(new ControlSetVoltDIV_CH2());
dsoControl->addCommand(new ControlSetVoltDIV_CH1());
void applyRequirements_(DsoCommandQueue *dsoControl) {
dsoControl->addCommand(new ControlAcquireHardData(), false);
dsoControl->addCommand(new ControlSetTimeDIV(), false);
dsoControl->addCommand(new ControlSetVoltDIV_CH2(), false);
dsoControl->addCommand(new ControlSetVoltDIV_CH1(), false);
}

ModelDSO6022BE::ModelDSO6022BE() : DSOModel(ID, 0x04b5, 0x6022, 0x04b4, 0x6022, "dso6022be", "DSO-6022BE",
Dso::ControlSpecification(2)) {
ModelDSO6022BE::ModelDSO6022BE()
: DSOModel(ID, 0x04b5, 0x6022, 0x04b4, 0x6022, "dso6022be", "DSO-6022BE", new Dso::ModelSpec(2)) {
initSpecifications(specification);
}

void ModelDSO6022BE::applyRequirements(HantekDsoControl *dsoControl) const {
applyRequirements_(dsoControl);
}
void ModelDSO6022BE::applyRequirements(DsoCommandQueue *dsoControl) const { applyRequirements_(dsoControl); }

ModelDSO6022BL::ModelDSO6022BL() : DSOModel(ID, 0x04b5, 0x602a, 0x04b4, 0x602a, "dso6022bl", "DSO-6022BL",
Dso::ControlSpecification(2)) {
ModelDSO6022BL::ModelDSO6022BL()
: DSOModel(ID, 0x04b5, 0x602a, 0x04b4, 0x602a, "dso6022bl", "DSO-6022BL", new Dso::ModelSpec(2)) {
initSpecifications(specification);
}

void ModelDSO6022BL::applyRequirements(HantekDsoControl *dsoControl) const {
applyRequirements_(dsoControl);
}
void ModelDSO6022BL::applyRequirements(DsoCommandQueue *dsoControl) const { applyRequirements_(dsoControl); }
7 changes: 3 additions & 4 deletions openhantek/src/hantekdso/models/modelDSO6022.h
Original file line number Diff line number Diff line change
@@ -2,17 +2,16 @@

#include "dsomodel.h"

class HantekDsoControl;
using namespace Hantek;
class DsoControl;

struct ModelDSO6022BE : public DSOModel {
static const int ID = 0x6022;
ModelDSO6022BE();
virtual void applyRequirements(HantekDsoControl* dsoControl) const override;
virtual void applyRequirements(DsoCommandQueue *dsoControl) const override;
};

struct ModelDSO6022BL : public DSOModel {
static const int ID = 0x6022;
ModelDSO6022BL();
virtual void applyRequirements(HantekDsoControl* dsoControl) const override;
virtual void applyRequirements(DsoCommandQueue* dsoControl) const override;
};
5 changes: 5 additions & 0 deletions openhantek/src/hantekdso/modelspecification.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: GPL-2.0+

#include "hantekdso/modelspecification.h"

Dso::ModelSpec::ModelSpec(unsigned channels) : channels(channels) { calibration.resize(channels); }
143 changes: 143 additions & 0 deletions openhantek/src/hantekdso/modelspecification.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: GPL-2.0+

#pragma once

#include "enums.h"
#include "hantekprotocol/codes.h"
#include "hantekprotocol/controlStructs.h"
#include "hantekprotocol/definitions.h"
#include "hantekprotocol/types.h"
#include <QList>

#include <cmath>
#include <limits>

namespace Dso {
constexpr static unsigned ROLL_RECORDLEN = UINT_MAX;

struct RecordLength {
unsigned recordLength; ///< Record length, ROLL_RECORDLEN means rolling
unsigned bufferDivider; ///< Samplerate dividers for record lengths
inline RecordLength(unsigned recordLength, unsigned bufferDivider)
: recordLength(recordLength), bufferDivider(bufferDivider) {}
RecordLength() = default;
};

/// \brief Stores the samplerate limits for calculations.
struct ControlSamplerateLimits {
double base; ///< The base for sample rate calculations
double max; ///< The maximum sample rate
unsigned maxDownsampler; ///< The maximum downsampling ratio
std::vector<RecordLength> recordLengths; ///< Available record lengths

inline double minSamplerate(RecordLengthID id) const {
return base / maxDownsampler / recordLengths[id].recordLength;
}
inline double samplerate(RecordLengthID id, unsigned downsampler) const {
return base / downsampler / recordLengths[id].recordLength;
}
inline double samplerate(RecordLengthID id, double recordTime) const {
return recordLengths[id].bufferDivider / recordTime;
}

inline double maxSamplerate(RecordLengthID id) const { return max / recordLengths[id].bufferDivider; }

inline unsigned computeDownsampler(RecordLengthID id, double samplerate) const {
return unsigned(base / recordLengths[id].bufferDivider / samplerate);
}
};

struct ControlSpecificationGainLevel {
/// The index of the selected gain on the hardware
unsigned char gainIdentificator;
/// Available voltage steps in V/screenheight
double gain;
};

struct FixedSampleRate {
unsigned char id; ///< hardware id
double samplerate;
};

struct SpecialTriggerChannel {
std::string name;
int hardwareID;
};

/// \brief Stores the specifications of the currently connected device.
struct ModelSpec {
ModelSpec(unsigned channels);
const ChannelID channels;

// Interface
Hantek::BulkCode cmdSetChannels = Hantek::BulkCode::INVALID; ///< Command for setting used channels
Hantek::BulkCode cmdSetSamplerate = Hantek::BulkCode::INVALID; ///< Command for samplerate settings
Hantek::BulkCode cmdSetRecordLength = Hantek::BulkCode::INVALID; ///< Command for buffer settings
Hantek::BulkCode cmdSetTrigger = Hantek::BulkCode::INVALID; ///< Command for trigger settings
Hantek::BulkCode cmdSetPretrigger = Hantek::BulkCode::INVALID; ///< Command for pretrigger settings
Hantek::BulkCode cmdForceTrigger = Hantek::BulkCode::FORCETRIGGER; ///< Command for forcing a trigger event
Hantek::BulkCode cmdCaptureStart = Hantek::BulkCode::STARTSAMPLING; ///< Command for starting the sampling
Hantek::BulkCode cmdTriggerEnabled = Hantek::BulkCode::ENABLETRIGGER; ///< Command for enabling the trigger
Hantek::BulkCode cmdGetData = Hantek::BulkCode::GETDATA; ///< Command for retrieve sample data
Hantek::BulkCode cmdGetCaptureState = Hantek::BulkCode::GETCAPTURESTATE; ///< Command for retrieve the capture state
Hantek::BulkCode cmdSetGain = Hantek::BulkCode::SETGAIN; ///< Command for setting the gain

// Actual resolved commands based on the above interface
const Hantek::ControlBeginCommand beginCommandControl;

// Limits

/// The limits for multi channel mode
ControlSamplerateLimits normalSamplerate = {50e6, 50e6, 0, std::vector<RecordLength>()};
/// The limits for single channel mode
ControlSamplerateLimits fastrateSamplerate = {100e6, 100e6, 0, std::vector<RecordLength>()};

unsigned char sampleSize; ///< Number of bits per sample

/// For devices that support only fixed sample rates (isFixedSamplerateDevice=true)
std::vector<FixedSampleRate> fixedSampleRates;

// Calibration

struct GainStepCalibration {
double offsetCorrection = 0;
unsigned short offsetStart = 0x0000;
unsigned short offsetEnd = 0xffff;
double voltageLimit = 255.0;
inline GainStepCalibration(double offsetCorrection, unsigned short offsetStart, unsigned short offsetEnd,
double voltageLimit)
: offsetCorrection(offsetCorrection), offsetStart(offsetStart), offsetEnd(offsetEnd),
voltageLimit(voltageLimit) {}
inline GainStepCalibration(unsigned short offsetStart, unsigned short offsetEnd, double voltageLimit)
: offsetStart(offsetStart), offsetEnd(offsetEnd), voltageLimit(voltageLimit) {}
};

/// The sample values at the top of the screen
typedef std::vector<GainStepCalibration> gainStepCalibration;

std::vector<gainStepCalibration> calibration; // Per channel

/// Gain levels
std::vector<ControlSpecificationGainLevel> gain; // Usually size==HANTEK_GAIN_STEPS

// Features
std::vector<SpecialTriggerChannel> specialTriggerChannels;
std::vector<Coupling> couplings = {Dso::Coupling::DC, Dso::Coupling::AC};
std::vector<TriggerMode> triggerModes = {TriggerMode::HARDWARE_SOFTWARE, TriggerMode::WAIT_FORCE,
TriggerMode::SINGLE};
bool isFixedSamplerateDevice = false;
bool isSoftwareTriggerDevice = false;
bool useControlNoBulk = false;
bool supportsCaptureState = true;
bool supportsOffset = true;
bool supportsCouplingRelays = true;
bool supportsFastRate = true;
int fixedUSBinLength = 0;
double testSignalAmplitude = 1.0; ///< Test signal amplitude in V. Usually 1V.

inline int indexOfTriggerMode(TriggerMode mode) const {
return int(std::find(triggerModes.begin(), triggerModes.end(), mode) - triggerModes.begin());
}
};
}
Q_DECLARE_METATYPE(Dso::RecordLength);
49 changes: 33 additions & 16 deletions openhantek/src/hantekdso/readme.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
# Content
This directory contains the heart of OpenHantek, the `HantekDSOControl` class
and all model definitions.

## HantekDSOControl
The `HantekDSOControl` class manages all device settings (gain, offsets, channels, etc)
and outputs `DSOSamples` via `getLastSamples()`. Observers are notified of a new set of
available samples via the signal `samplesAvailable()`.
Current device settings are stored in the `controlsettings` field and retriveable with the
corresponding getter `getDeviceSettings()`.

`HantekDSOControl` may only contain state fields to realize the fetch samples / modify settings loop.

## Model
A model needs a `ControlSpecification`, which
describes what specific Hantek protocol commands are to be used. All known
models are specified in the subdirectory `models`.
This directory contains the heart of OpenHantek, the `DSOControl` class and all model definitions.

## DsoControl and DsoLoop

This is the core logic class that ties together the USBDevice object, the protocol parts, the sample-fetch
loop (DsoLoop) and command queue (DsoCommandQueue). Device settings can only be done through this class,
but are stored separately in the DeviceSettings object.

DsoLoop is responsible for fetching new samples from the Dso at the right time, converting it to a normalized
range [-1,1] of values and provide the result via `getLastSamples()`. Observers are notified of a new set of
available samples via the signal `samplesAvailable()`. The DsoSamples structure that is used for result retrieving
is allocated only once and will be overwritten in each loop step, it is therefore necessary to copy the result.
The structure provides a mutex to synchronize concurrent threads (usually the post processing thread and the
DsoControl thread).

## DsoCommandQueue

DsoControl inherits from DsoCommandQueue and uses the provided two command queues for usb-control and usb-bulk
commands. Commands like setGain() are not performed directly but in a batched way.
The first step of each fetch-sample loop is to send all pending/queued commands via `sendPendingCommands()`.

DsoCommandQueue requires a model to "register" all necessary commands beforehand via `addCommand(cmd)`. The command
can be altered later via `modifyCommand(id)`. Because commands are not applied directly, only the last modification
before a new loop cycle is going to begin, will actually be send to the device.

## DSO Model

A model needs a `Dso::ModelSpec` (files: modelspecification.h/cpp), which
describes what specific Hantek protocol commands are to be used and what capabilities are supported. All known
models are specified in the subdirectory `models`. A new model inherits from `DSOModel` and implements
`applyRequirements(DsoCommandQueue)`. Within this method all necessary commands are registered via
DsoCommandQueue::addCommand. In the constructor of the model, the field `specification` is used to describe the models
capabilites.

# Namespace
Relevant classes in here are in the `DSO` namespace.
5 changes: 0 additions & 5 deletions openhantek/src/hantekdso/states.h
Original file line number Diff line number Diff line change
@@ -4,8 +4,6 @@

namespace Hantek {

//////////////////////////////////////////////////////////////////////////////
/// \enum RollState
/// \brief The states of the roll cycle (Since capture state isn't valid).
enum class RollState : int {
STARTSAMPLING = 0, ///< Start sampling
@@ -16,8 +14,6 @@ enum class RollState : int {
_COUNT // Used for mod operator
};

//////////////////////////////////////////////////////////////////////////////
/// \enum CaptureState hantek/types.h
/// \brief The different capture states which the oscilloscope returns.
enum CaptureState {
CAPTURE_WAITING = 0, ///< The scope is waiting for a trigger event
@@ -27,5 +23,4 @@ enum CaptureState {
CAPTURE_READY5200 = 7, ///< Sampling data is available (DSO-5200/DSO-5200A)
CAPTURE_ERROR = 1000
};

}
2 changes: 1 addition & 1 deletion openhantek/src/hantekprotocol/bulkStructs.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0+

#include "bulkcode.h"
#include "codes.h"
#include "bulkStructs.h"
#include "definitions.h"

Original file line number Diff line number Diff line change
@@ -1,8 +1,145 @@
#pragma once

#include <QMetaEnum>
#include <inttypes.h>

namespace Hantek {
Q_NAMESPACE

/// \brief All supported control commands.

/// CONTROL_VALUE <em>[MODEL_DSO2090, MODEL_DSO2150, MODEL_DSO2250, MODEL_DSO5200, MODEL_DSO5200A, MODEL_DSO6022]</em>
/// <p>
/// The 0xa2 control read/write command gives access to a ControlValue.
/// </p>
///
/// CONTROL_GETSPEED <em>[MODEL_DSO2090, MODEL_DSO2150, MODEL_DSO2250, MODEL_DSO5200, MODEL_DSO5200A,
/// MODEL_DSO6022]</em>
/// <p>
/// The 0xb2 control read command gets the speed level of the USB
/// connection:
/// <table>
/// <tr>
/// <td>ConnectionSpeed</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// </tr>
/// </table>
/// </p>
///
/// CONTROL_BEGINCOMMAND <em>[MODEL_DSO2090, MODEL_DSO2150, MODEL_DSO2250, MODEL_DSO5200, MODEL_DSO5200A]</em>
/// <p>
/// The 0xb3 control write command is sent before any bulk command:
/// <table>
/// <tr>
/// <td>0x0f</td>
/// <td>BulkIndex</td>
/// <td>BulkIndex</td>
/// <td>BulkIndex</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// </tr>
/// </table>
/// </p>
///
/// CONTROL_SETOFFSET <em>[MODEL_DSO2090, MODEL_DSO2150, MODEL_DSO2250, MODEL_DSO5200, MODEL_DSO5200A]</em>
/// <p>
/// The 0xb4 control write command sets the channel offsets:
/// <table>
/// <tr>
/// <td>Ch1Offset[1]</td>
/// <td>Ch1Offset[0]</td>
/// <td>Ch2Offset[1]</td>
/// <td>Ch2Offset[0]</td>
/// <td>TriggerOffset[1]</td>
/// <td>TriggerOffset[0]</td>
/// </tr>
/// </table>
/// <table>
/// <tr>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// </tr>
/// </table>
/// </p>
///
/// CONTROL_SETRELAYS <em>[MODEL_DSO2090, MODEL_DSO2150, MODEL_DSO2250, MODEL_DSO5200, MODEL_DSO5200A]</em>
/// <p>
/// The 0xb5 control write command sets the internal relays:
/// <table>
/// <tr>
/// <td>0x00</td>
/// <td>0x04 ^ (Ch1Gain < 1 V)</td>
/// <td>0x08 ^ (Ch1Gain < 100 mV)</td>
/// <td>0x02 ^ (Ch1Coupling == DC)</td>
/// </tr>
/// </table>
/// <table>
/// <tr>
/// <td>0x20 ^ (Ch2Gain < 1 V)</td>
/// <td>0x40 ^ (Ch2Gain < 100 mV)</td>
/// <td>0x10 ^ (Ch2Coupling == DC)</td>
/// <td>0x01 ^ (Trigger == EXT)</td>
/// </tr>
/// </table>
/// <table>
/// <tr>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// <td>0x00</td>
/// </tr>
/// </table>
/// </p>
/// <p>
/// The limits are <= instead of < for the 10 bit models, since those
/// support voltages up to 10 V.
/// </p>
///
/// CONTROL_SETVOLTDIV_CH1 CH1 voltage div setting (6022BE/BL)
///
/// CONTROL_SETVOLTDIV_CH2 CH2 voltage div setting (6022BE/BL)
///
/// CONTROL_SETTIMEDIV Time divisor setting (6022BE/BL)
///
/// CONTROL_ACQUIIRE_HARD_DATA Request sample data (6022BE/BL)
enum class ControlCode : uint8_t {
VALUE = 0xa2,
GETSPEED = 0xb2,
BEGINCOMMAND = 0xb3,
SETOFFSET = 0xb4,
SETRELAYS = 0xb5,
SETVOLTDIV_CH1 = 0xe0,
SETVOLTDIV_CH2 = 0xe1,
SETTIMEDIV = 0xe2,
ACQUIRE_DATA = 0xe3
};
Q_ENUM_NS(ControlCode)

/// \brief All supported bulk commands.
/// Indicies given in square brackets specify byte numbers in little endian
@@ -458,8 +595,8 @@ enum class BulkCode : uint8_t {
ESETTRIGGERORSAMPLERATE = 0x0e,
FSETBUFFER = 0x0f,

GETCAPTURESTATE_RESPONSE=0xfe,
INVALID=0xff
GETCAPTURESTATE_RESPONSE = 0xfe,
INVALID = 0xff
};

Q_ENUM_NS(BulkCode)
}
34 changes: 17 additions & 17 deletions openhantek/src/hantekprotocol/controlStructs.cpp
Original file line number Diff line number Diff line change
@@ -9,32 +9,32 @@
namespace Hantek {

ControlBeginCommand::ControlBeginCommand(BulkIndex index)
: ControlCommand(Hantek::ControlCode::CONTROL_BEGINCOMMAND, 10) {
: ControlCommand(Hantek::ControlCode::BEGINCOMMAND, 10) {
data()[0] = 0x0f;
data()[1] = (uint8_t)index;
}

ControlGetSpeed::ControlGetSpeed() : ControlCommand(Hantek::ControlCode::CONTROL_GETSPEED, 10) {}
ControlGetSpeed::ControlGetSpeed() : ControlCommand(Hantek::ControlCode::GETSPEED, 10) {}

ConnectionSpeed ControlGetSpeed::getSpeed() { return (ConnectionSpeed)data()[0]; }

ControlSetOffset::ControlSetOffset() : ControlCommand(ControlCode::CONTROL_SETOFFSET, 17) {}
ControlSetOffset::ControlSetOffset() : ControlCommand(ControlCode::SETOFFSET, 17) {}

ControlSetOffset::ControlSetOffset(uint16_t channel1, uint16_t channel2, uint16_t trigger)
: ControlCommand(ControlCode::CONTROL_SETOFFSET, 17) {
this->setChannel(0, channel1);
this->setChannel(1, channel2);
this->setTrigger(trigger);
: ControlCommand(ControlCode::SETOFFSET, 17) {
this->setOffset(0, channel1);
this->setOffset(1, channel2);
this->setTriggerLevel(trigger);
}

uint16_t ControlSetOffset::getChannel(ChannelID channel) {
uint16_t ControlSetOffset::offset(ChannelID channel) {
if (channel == 0)
return ((data()[0] & 0x0f) << 8) | data()[1];
else
return ((data()[2] & 0x0f) << 8) | data()[3];
}

void ControlSetOffset::setChannel(ChannelID channel, uint16_t offset) {
void ControlSetOffset::setOffset(ChannelID channel, uint16_t offset) {
if (channel == 0) {
data()[0] = (uint8_t)(offset >> 8);
data()[1] = (uint8_t)offset;
@@ -44,16 +44,16 @@ void ControlSetOffset::setChannel(ChannelID channel, uint16_t offset) {
}
}

uint16_t ControlSetOffset::getTrigger() { return ((data()[4] & 0x0f) << 8) | data()[5]; }
uint16_t ControlSetOffset::triggerLevel() { return ((data()[4] & 0x0f) << 8) | data()[5]; }

void ControlSetOffset::setTrigger(uint16_t level) {
void ControlSetOffset::setTriggerLevel(uint16_t level) {
data()[4] = (uint8_t)(level >> 8);
data()[5] = (uint8_t)level;
}

ControlSetRelays::ControlSetRelays(bool ch1Below1V, bool ch1Below100mV, bool ch1CouplingDC, bool ch2Below1V,
bool ch2Below100mV, bool ch2CouplingDC, bool triggerExt)
: ControlCommand(ControlCode::CONTROL_SETRELAYS, 17) {
: ControlCommand(ControlCode::SETRELAYS, 17) {
this->setBelow1V(0, ch1Below1V);
this->setBelow100mV(0, ch1Below100mV);
this->setCoupling(0, ch1CouplingDC);
@@ -109,28 +109,28 @@ bool ControlSetRelays::getTrigger() { return (data()[7] & 0x01) == 0x00; }

void ControlSetRelays::setTrigger(bool ext) { data()[7] = ext ? 0xfe : 0x01; }

ControlSetVoltDIV_CH1::ControlSetVoltDIV_CH1() : ControlCommand(ControlCode::CONTROL_SETVOLTDIV_CH1, 1) {
ControlSetVoltDIV_CH1::ControlSetVoltDIV_CH1() : ControlCommand(ControlCode::SETVOLTDIV_CH1, 1) {
this->setDiv(5);
}

void ControlSetVoltDIV_CH1::setDiv(uint8_t val) { data()[0] = val; }

ControlSetVoltDIV_CH2::ControlSetVoltDIV_CH2() : ControlCommand(ControlCode::CONTROL_SETVOLTDIV_CH2, 1) {
ControlSetVoltDIV_CH2::ControlSetVoltDIV_CH2() : ControlCommand(ControlCode::SETVOLTDIV_CH2, 1) {
this->setDiv(5);
}

void ControlSetVoltDIV_CH2::setDiv(uint8_t val) { data()[0] = val; }

ControlSetTimeDIV::ControlSetTimeDIV() : ControlCommand(ControlCode::CONTROL_SETTIMEDIV, 1) { this->setDiv(1); }
ControlSetTimeDIV::ControlSetTimeDIV() : ControlCommand(ControlCode::SETTIMEDIV, 1) { this->setDiv(1); }

void ControlSetTimeDIV::setDiv(uint8_t val) { data()[0] = val; }

ControlAcquireHardData::ControlAcquireHardData() : ControlCommand(ControlCode::CONTROL_ACQUIIRE_HARD_DATA, 1) {
ControlAcquireHardData::ControlAcquireHardData() : ControlCommand(ControlCode::ACQUIRE_DATA, 1) {
data()[0] = 0x01;
}

ControlGetLimits::ControlGetLimits(size_t channels)
: ControlCommand(ControlCode::CONTROL_VALUE, 1), offsetLimit(new OffsetsPerGainStep[channels]) {
: ControlCommand(ControlCode::VALUE, 1), offsetLimit(new OffsetsPerGainStep[channels]) {
value = (uint8_t)ControlValue::VALUE_OFFSETLIMITS;
data()[0] = 0x01;
}
23 changes: 16 additions & 7 deletions openhantek/src/hantekprotocol/controlStructs.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include "controlcode.h"
#include "codes.h"
#include "controlcommand.h"
#include "types.h"
#include "usb/usbdevicedefinitions.h"
@@ -9,7 +9,6 @@
#include <memory>

namespace Hantek {
struct OffsetsPerGainStep;

/// \enum BulkIndex
/// \brief Can be set by CONTROL_BEGINCOMMAND, maybe it allows multiple commands
@@ -51,17 +50,17 @@ struct ControlSetOffset : public ControlCommand {
/// \brief Get the offset for the given channel.
/// \param channel The channel whose offset should be returned.
/// \return The channel offset value.
uint16_t getChannel(ChannelID channel);
uint16_t offset(ChannelID channel);
/// \brief Set the offset for the given channel.
/// \param channel The channel that should be set.
/// \param offset The new channel offset value.
void setChannel(ChannelID channel, uint16_t offset);
void setOffset(ChannelID channel, uint16_t offset);
/// \brief Get the trigger level.
/// \return The trigger level value.
uint16_t getTrigger();
uint16_t triggerLevel();
/// \brief Set the trigger level.
/// \param level The new trigger level value.
void setTrigger(uint16_t level);
void setTriggerLevel(uint16_t level);
};

struct ControlSetRelays : public ControlCommand {
@@ -129,8 +128,18 @@ struct ControlAcquireHardData : public ControlCommand {
};

struct ControlGetLimits : public ControlCommand {
constexpr static unsigned HANTEK_GAIN_STEPS = 9;
#pragma pack(push, 1)
struct Offset {
unsigned short start;
unsigned short end;
};
struct OffsetsPerGainStep {
Offset step[HANTEK_GAIN_STEPS];
};
#pragma pack(pop)

std::unique_ptr<OffsetsPerGainStep[]> offsetLimit;
ControlGetLimits(size_t channels);
inline uint8_t *offsetLimitData() { return (uint8_t *)offsetLimit.get(); }
};
}
140 changes: 0 additions & 140 deletions openhantek/src/hantekprotocol/controlcode.h

This file was deleted.

6 changes: 0 additions & 6 deletions openhantek/src/hantekprotocol/definitions.h
Original file line number Diff line number Diff line change
@@ -6,8 +6,6 @@
#include <QString>
#include <stdint.h>

#define HANTEK_GAIN_STEPS 9

namespace Hantek {
/// \enum UsedChannels
/// \brief The enabled channels.
@@ -37,10 +35,6 @@ struct Offset {
unsigned short end = 0xffff;
};

struct OffsetsPerGainStep {
Offset step[HANTEK_GAIN_STEPS];
};

/// \struct FilterBits
/// \brief The bits for BULK::SETFILTER.
struct FilterBits {
10 changes: 10 additions & 0 deletions openhantek/src/hantekprotocol/types.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
// SPDX-License-Identifier: GPL-2.0+

#pragma once
#include <chrono>
#include <inttypes.h>
using namespace std::literals::chrono_literals;

typedef unsigned RecordLengthID;
typedef unsigned ChannelID;

using Samples = double;
Samples operator""_S(long double); ///< User literal for samples

using Voltage = double;

using Seconds = std::chrono::duration<double>;
118 changes: 50 additions & 68 deletions openhantek/src/main.cpp
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
#include <QDebug>
#include <QLibraryInfo>
#include <QLocale>
#include <QOpenGLContext>
#include <QSurfaceFormat>
#include <QTranslator>

@@ -17,19 +18,20 @@
#include "viewconstants.h"

// DSO core logic
#include "dsocontrol.h"
#include "dsoloop.h"
#include "dsomodel.h"
#include "hantekdsocontrol.h"
#include "usb/usbdevice.h"

// Post processing
#include "post/graphgenerator.h"
#include "post/mathchannelgenerator.h"
#include "post/postprocessing.h"
#include "post/selfcalibration.h"
#include "post/spectrumgenerator.h"

// Exporter
#include "exporting/exportcsv.h"
#include "exporting/exporterprocessor.h"
#include "exporting/exporterregistry.h"
#include "exporting/exportimage.h"
#include "exporting/exportprint.h"
@@ -39,46 +41,12 @@
#include "mainwindow.h"
#include "selectdevice/selectsupporteddevice.h"

// OpenGL setup
#include "glscope.h"

#ifndef VERSION
#error "You need to run the cmake buildsystem!"
#endif

using namespace Hantek;

/// \brief Initialize the device with the current settings.
void applySettingsToDevice(HantekDsoControl *dsoControl, DsoSettingsScope *scope,
const Dso::ControlSpecification *spec) {
bool mathUsed = scope->anyUsed(spec->channels);
for (ChannelID channel = 0; channel < spec->channels; ++channel) {
dsoControl->setCoupling(channel, scope->coupling(channel, spec));
dsoControl->setGain(channel, scope->gain(channel) * DIVS_VOLTAGE);
dsoControl->setOffset(channel, (scope->voltage[channel].offset / DIVS_VOLTAGE) + 0.5);
dsoControl->setTriggerLevel(channel, scope->voltage[channel].trigger);
dsoControl->setChannelUsed(channel, mathUsed | scope->anyUsed(channel));
}

if (scope->horizontal.samplerateSource == DsoSettingsScopeHorizontal::Samplerrate)
dsoControl->setSamplerate(scope->horizontal.samplerate);
else
dsoControl->setRecordTime(scope->horizontal.timebase * DIVS_TIME);

if (dsoControl->getAvailableRecordLengths().empty())
dsoControl->setRecordLength(scope->horizontal.recordLength);
else {
auto recLenVec = dsoControl->getAvailableRecordLengths();
ptrdiff_t index = std::distance(recLenVec.begin(),
std::find(recLenVec.begin(), recLenVec.end(), scope->horizontal.recordLength));
dsoControl->setRecordLength(index < 0 ? 1 : (unsigned)index);
}
dsoControl->setTriggerMode(scope->trigger.mode);
dsoControl->setPretriggerPosition(scope->trigger.position * scope->horizontal.timebase * DIVS_TIME);
dsoControl->setTriggerSlope(scope->trigger.slope);
dsoControl->setTriggerSource(scope->trigger.special, scope->trigger.source);
}

/// \brief Initialize resources and translations and show the main window.
int main(int argc, char *argv[]) {
//////// Set application information ////////
@@ -87,9 +55,8 @@ int main(int argc, char *argv[]) {
QCoreApplication::setApplicationName("OpenHantek");
QCoreApplication::setApplicationVersion(VERSION);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
#endif
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, false);

bool useGles = false;
{
@@ -102,8 +69,22 @@ int main(int argc, char *argv[]) {
p.process(parserApp);
useGles = p.isSet(useGlesOption);
}

GlScope::fixOpenGLversion(useGles ? QSurfaceFormat::OpenGLES : QSurfaceFormat::OpenGL);
// Prefer full desktop OpenGL without fixed pipeline
QSurfaceFormat format;
format.setSamples(4); // Antia-Aliasing, Multisampling
format.setDepthBufferSize(24);
format.setStencilBufferSize(8);
format.setProfile(QSurfaceFormat::CoreProfile);
if (useGles || QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) {
format.setVersion(2, 0);
format.setRenderableType(QSurfaceFormat::OpenGLES);
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true);
} else {
format.setVersion(3, 2);
format.setRenderableType(QSurfaceFormat::OpenGL);
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, false);
}
QSurfaceFormat::setDefaultFormat(format);

QApplication openHantekApplication(argc, argv);

@@ -133,28 +114,29 @@ int main(int argc, char *argv[]) {
return -1;
}

//////// Create settings object ////////
Settings::DsoSettings settings(device->getModel()->spec());

//////// Create DSO control object and move it to a separate thread ////////
QThread dsoControlThread;
dsoControlThread.setObjectName("dsoControlThread");
HantekDsoControl dsoControl(device.get());
DsoControl dsoControl(device.get(), settings.deviceSettings);
settings.load(dsoControl.channelUsage());
dsoControl.moveToThread(&dsoControlThread);
QObject::connect(&dsoControlThread, &QThread::started, &dsoControl, &HantekDsoControl::run);
QObject::connect(&dsoControl, &HantekDsoControl::communicationError, QCoreApplication::instance(),
QObject::connect(&dsoControlThread, &QThread::started, &dsoControl, &DsoControl::start);
QObject::connect(&dsoControl, &DsoControl::communicationError, QCoreApplication::instance(),
&QCoreApplication::quit);
QObject::connect(device.get(), &USBDevice::deviceDisconnected, QCoreApplication::instance(),
&QCoreApplication::quit);

//////// Create settings object ////////
DsoSettings settings(device->getModel()->spec());
SelfCalibration selfCalibration(&dsoControl);

//////// Create exporters ////////
ExporterRegistry exportRegistry(device->getModel()->spec(), &settings);
Exporter::Registry exportRegistry(device->getModel()->spec(), &settings);

ExporterCSV exporterCSV;
ExporterImage exportImage;
ExporterPrint exportPrint;

ExporterProcessor samplesToExportRaw(&exportRegistry);
Exporter::CSV exporterCSV;
Exporter::Image exportImage;
Exporter::Print exportPrint;

exportRegistry.registerExporter(&exporterCSV);
exportRegistry.registerExporter(&exportImage);
@@ -163,37 +145,34 @@ int main(int argc, char *argv[]) {
//////// Create post processing objects ////////
QThread postProcessingThread;
postProcessingThread.setObjectName("postProcessingThread");
PostProcessing postProcessing(settings.scope.countChannels());
PostProcessing::Executor postProcessing(&settings.scope);

SpectrumGenerator spectrumGenerator(&settings.scope, &settings.post);
MathChannelGenerator mathchannelGenerator(&settings.scope, device->getModel()->spec()->channels);
GraphGenerator graphGenerator(&settings.scope, device->getModel()->spec()->isSoftwareTriggerDevice);
PostProcessing::SpectrumGenerator spectrumGenerator(&settings.scope, &settings.post);
PostProcessing::MathChannelGenerator mathchannelGenerator(&settings.scope);
PostProcessing::GraphGenerator graphGenerator(&settings.scope, settings.deviceSettings.get(),
dsoControl.channelUsage());

postProcessing.registerProcessor(&samplesToExportRaw);
postProcessing.registerProcessor(&selfCalibration);
postProcessing.registerProcessor(&mathchannelGenerator);
postProcessing.registerProcessor(&spectrumGenerator);
postProcessing.registerProcessor(&graphGenerator);

postProcessing.moveToThread(&postProcessingThread);
QObject::connect(&dsoControl, &HantekDsoControl::samplesAvailable, &postProcessing, &PostProcessing::input);
QObject::connect(&postProcessing, &PostProcessing::processingFinished, &exportRegistry, &ExporterRegistry::input,
Qt::DirectConnection);
QObject::connect(&dsoControl, &DsoControl::samplesAvailable, &postProcessing, &PostProcessing::Executor::input);
QObject::connect(&postProcessing, &PostProcessing::Executor::processingFinished, &exportRegistry,
&Exporter::Registry::input, Qt::DirectConnection);

//////// Create main window ////////
iconFont->initFontAwesome();
MainWindow openHantekMainWindow(&dsoControl, &settings, &exportRegistry);
QObject::connect(&postProcessing, &PostProcessing::processingFinished, &openHantekMainWindow,
MainWindow openHantekMainWindow(&dsoControl, &settings, &exportRegistry, &selfCalibration);
QObject::connect(&postProcessing, &PostProcessing::Executor::processingFinished, &openHantekMainWindow,
&MainWindow::showNewData);
QObject::connect(&exportRegistry, &ExporterRegistry::exporterProgressChanged, &openHantekMainWindow,
&MainWindow::exporterProgressChanged);
QObject::connect(&exportRegistry, &ExporterRegistry::exporterStatusChanged, &openHantekMainWindow,
QObject::connect(&exportRegistry, &Exporter::Registry::exporterStatusChanged, &openHantekMainWindow,
&MainWindow::exporterStatusChanged);
openHantekMainWindow.show();

applySettingsToDevice(&dsoControl, &settings.scope, device->getModel()->spec());

//////// Start DSO thread and go into GUI main loop
dsoControl.enableSampling(true);
dsoControl.loopControl()->enableSampling(true);
postProcessingThread.start();
dsoControlThread.start();
int res = openHantekApplication.exec();
@@ -205,7 +184,10 @@ int main(int argc, char *argv[]) {
postProcessingThread.quit();
postProcessingThread.wait(10000);

if (context && device != nullptr) { libusb_exit(context); }
if (context && device != nullptr) {
device.reset();
libusb_exit(context);
}

return res;
}
282 changes: 106 additions & 176 deletions openhantek/src/mainwindow.cpp

Large diffs are not rendered by default.

21 changes: 12 additions & 9 deletions openhantek/src/mainwindow.h
Original file line number Diff line number Diff line change
@@ -4,18 +4,22 @@
#include <memory>

class SpectrumGenerator;
class HantekDsoControl;
class DsoControl;
namespace Settings {
class DsoSettings;
class ExporterRegistry;
}
class DsoWidget;
class HorizontalDock;
class TriggerDock;
class SpectrumDock;
class VoltageDock;

class VoltageOrSpectrumDock;
namespace Exporter {
class Registry;
}
namespace Ui {
class MainWindow;
}
class SelfCalibration;

/// \brief The main window of the application.
/// The main window contains the classic oszilloscope-screen and the gui
@@ -24,13 +28,12 @@ class MainWindow : public QMainWindow {
Q_OBJECT

public:
explicit MainWindow(HantekDsoControl *dsoControl, DsoSettings *mSettings, ExporterRegistry *exporterRegistry,
QWidget *parent = 0);
explicit MainWindow(DsoControl *dsoControl, Settings::DsoSettings *mSettings,
Exporter::Registry *exporterRegistry, SelfCalibration *selfCalibration, QWidget *parent = 0);
~MainWindow();
public slots:
void showNewData(std::shared_ptr<PPresult> data);
void exporterStatusChanged(const QString &exporterName, const QString &status);
void exporterProgressChanged();

protected:
void closeEvent(QCloseEvent *event) override;
@@ -42,6 +45,6 @@ class MainWindow : public QMainWindow {
DsoWidget *dsoWidget;

// Settings used for the whole program
DsoSettings *mSettings;
ExporterRegistry *exporterRegistry;
Settings::DsoSettings *mSettings;
Exporter::Registry *exporterRegistry;
};
61 changes: 34 additions & 27 deletions openhantek/src/mainwindow.ui
Original file line number Diff line number Diff line change
@@ -38,8 +38,8 @@
<string>&amp;View</string>
</property>
<addaction name="actionDigital_phosphor"/>
<addaction name="actionZoom"/>
<addaction name="actionManualCommand"/>
<addaction name="actionAddMarker"/>
<addaction name="actionDocks"/>
</widget>
<widget class="QMenu" name="menuOscilloscope">
<property name="title">
@@ -77,14 +77,15 @@
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionOpen"/>
<addaction name="actionSave"/>
<addaction name="separator"/>
<addaction name="actionSampling"/>
<addaction name="separator"/>
<addaction name="actionDigital_phosphor"/>
<addaction name="actionZoom"/>
<addaction name="separator"/>
<addaction name="actionAddMarker"/>
<addaction name="actionRemoveMarker"/>
<addaction name="separator"/>
<addaction name="actionReport_an_issue"/>
</widget>
<action name="actionOpen">
<property name="text">
@@ -106,11 +107,17 @@
<property name="text">
<string>Save as ...</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+S</string>
</property>
</action>
<action name="actionExit">
<property name="text">
<string>Exit</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
<action name="actionDigital_phosphor">
<property name="checkable">
@@ -120,28 +127,13 @@
<string>Digital phosphor</string>
</property>
</action>
<action name="actionZoom">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Zoom</string>
</property>
</action>
<action name="actionDocking_windows">
<property name="text">
<string>Docking windows</string>
</property>
</action>
<action name="actionToolbars">
<property name="text">
<string>Toolbars</string>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>About</string>
</property>
<property name="shortcut">
<string>Ctrl+A</string>
</property>
</action>
<action name="actionSettings">
<property name="text">
@@ -159,12 +151,27 @@
<string>Space</string>
</property>
</action>
<action name="actionManualCommand">
<property name="checkable">
<bool>true</bool>
<action name="actionAddMarker">
<property name="text">
<string>Add marker/zoom view</string>
</property>
</action>
<action name="actionDocks">
<property name="text">
<string>Manual command</string>
<string>Docks</string>
</property>
</action>
<action name="actionReport_an_issue">
<property name="text">
<string>Report an issue</string>
</property>
</action>
<action name="actionRemoveMarker">
<property name="text">
<string>Remove Marker</string>
</property>
<property name="toolTip">
<string>Remove current marker and zoomview</string>
</property>
</action>
</widget>
70 changes: 70 additions & 0 deletions openhantek/src/post/enums.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0+

#include "postprocessingsettings.h"

#include "settings/scopesettings.h"
#include <QCoreApplication>
#include <QString>

namespace PostProcessing {

QString mathModeString(MathMode mode, const ::Settings::Channel *first, const ::Settings::Channel *second) {
switch (mode) {
case MathMode::ADD:
return QCoreApplication::tr("%1 + %2").arg(first->name(), second->name());
case MathMode::SUBSTRACT:
return QCoreApplication::tr("%1 - %2").arg(first->name(), second->name());
case MathMode::MULTIPLY:
return QCoreApplication::tr("%1 * %2").arg(first->name(), second->name());
}
return QString();
}

QString mathModeString(MathMode mode) {
switch (mode) {
case MathMode::ADD:
return "+";
case MathMode::SUBSTRACT:
return "-";
case MathMode::MULTIPLY:
return "*";
}
return QString();
}

QString windowFunctionString(WindowFunction window) {
switch (window) {
case WindowFunction::RECTANGULAR:
return QCoreApplication::tr("Rectangular");
case WindowFunction::HAMMING:
return QCoreApplication::tr("Hamming");
case WindowFunction::HANN:
return QCoreApplication::tr("Hann");
case WindowFunction::COSINE:
return QCoreApplication::tr("Cosine");
case WindowFunction::LANCZOS:
return QCoreApplication::tr("Lanczos");
case WindowFunction::BARTLETT:
return QCoreApplication::tr("Bartlett");
case WindowFunction::TRIANGULAR:
return QCoreApplication::tr("Triangular");
case WindowFunction::GAUSS:
return QCoreApplication::tr("Gauss");
case WindowFunction::BARTLETTHANN:
return QCoreApplication::tr("Bartlett-Hann");
case WindowFunction::BLACKMAN:
return QCoreApplication::tr("Blackman");
// case WindowFunction::WINDOW_KAISER:
// return QCoreApplication::tr("Kaiser");
case WindowFunction::NUTTALL:
return QCoreApplication::tr("Nuttall");
case WindowFunction::BLACKMANHARRIS:
return QCoreApplication::tr("Blackman-Harris");
case WindowFunction::BLACKMANNUTTALL:
return QCoreApplication::tr("Blackman-Nuttall");
case WindowFunction::FLATTOP:
return QCoreApplication::tr("Flat top");
}
return QString();
}
} // end namespace PostProcessing
56 changes: 56 additions & 0 deletions openhantek/src/post/enums.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: GPL-2.0+

#pragma once

#include <QMetaType>

namespace Settings {
class Channel;
}

namespace PostProcessing {
Q_NAMESPACE

/// \enum MathMode
/// \brief The different math modes for the math-channel.
enum class MathMode { ADD, SUBSTRACT, MULTIPLY };
Q_ENUM_NS(MathMode)

/// \enum WindowFunction
/// \brief The supported window functions.
/// These are needed for spectrum analysis and are applied to the sample values
/// before calculating the DFT.
enum class WindowFunction {
RECTANGULAR, ///< Rectangular window (aka Dirichlet)
HAMMING, ///< Hamming window
HANN, ///< Hann window
COSINE, ///< Cosine window (aka Sine)
LANCZOS, ///< Lanczos window (aka Sinc)
BARTLETT, ///< Bartlett window (Endpoints == 0)
TRIANGULAR, ///< Triangular window (Endpoints != 0)
GAUSS, ///< Gauss window (simga = 0.4)
BARTLETTHANN, ///< Bartlett-Hann window
BLACKMAN, ///< Blackman window (alpha = 0.16)
// KAISER, ///< Kaiser window (alpha = 3.0)
NUTTALL, ///< Nuttall window, cont. first deriv.
BLACKMANHARRIS, ///< Blackman-Harris window
BLACKMANNUTTALL, ///< Blackman-Nuttall window
FLATTOP ///< Flat top window
};
Q_ENUM_NS(WindowFunction)

/// \brief Return string representation of the given math mode.
/// \param mode The ::MathMode that should be returned as string.
/// \return The string that should be used in labels etc.
QString mathModeString(MathMode mode, const ::Settings::Channel *first, const ::Settings::Channel *second);

/// \brief Return string representation of the given math mode.
/// \param mode The ::MathMode that should be returned as string.
/// \return The string that should be used in labels etc.
QString mathModeString(MathMode mode);

/// \brief Return string representation of the given dft window function.
/// \param window The ::WindowFunction that should be returned as string.
/// \return The string that should be used in labels etc.
QString windowFunctionString(WindowFunction window);
}
174 changes: 82 additions & 92 deletions openhantek/src/post/graphgenerator.cpp
Original file line number Diff line number Diff line change
@@ -4,177 +4,167 @@
#include <QMutex>
#include <exception>

#include "hantekdso/modelspecification.h"
#include "post/graphgenerator.h"
#include "post/ppresult.h"
#include "post/softwaretrigger.h"
#include "hantekdso/controlspecification.h"
#include "scopesettings.h"
#include "utils/printutils.h"
#include "viewconstants.h"

static const SampleValues &useSpecSamplesOf(ChannelID channel, const PPresult *result,
const DsoSettingsScope *scope) {
static SampleValues emptyDefault;
if (!scope->spectrum[channel].used || !result->data(channel)) return emptyDefault;
return result->data(channel)->spectrum;
}

static const SampleValues &useVoltSamplesOf(ChannelID channel, const PPresult *result,
const DsoSettingsScope *scope) {
static SampleValues emptyDefault;
if (!scope->voltage[channel].used || !result->data(channel)) return emptyDefault;
return result->data(channel)->voltage;
}

GraphGenerator::GraphGenerator(const DsoSettingsScope *scope, bool isSoftwareTriggerDevice)
: scope(scope), isSoftwareTriggerDevice(isSoftwareTriggerDevice) {}
namespace PostProcessing {

bool GraphGenerator::isReady() const { return ready; }
GraphGenerator::GraphGenerator(const ::Settings::Scope *scope, const Dso::DeviceSettings *deviceSettings,
const Dso::ChannelUsage *channelUsage)
: m_scope(scope), m_deviceSettings(deviceSettings), m_channelUsage(channelUsage) {}

void GraphGenerator::generateGraphsTYvoltage(PPresult *result) {
unsigned preTrigSamples = 0;
unsigned postTrigSamples = 0;
unsigned swTriggerStart = 0;

// check trigger point for software trigger
if (isSoftwareTriggerDevice && scope->trigger.source < result->channelCount())
std::tie(preTrigSamples, postTrigSamples, swTriggerStart) = SoftwareTrigger::compute(result, scope);
if (m_deviceSettings->spec->isSoftwareTriggerDevice &&
m_deviceSettings->trigger.source() < m_deviceSettings->voltage.size())
std::tie(preTrigSamples, postTrigSamples, swTriggerStart) =
SoftwareTrigger::compute(result, m_deviceSettings, m_scope, m_channelUsage);
result->softwareTriggerTriggered = postTrigSamples > preTrigSamples;

result->vaChannelVoltage.resize(scope->voltage.size());
for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) {
ChannelGraph &target = result->vaChannelVoltage[channel];
const SampleValues &samples = useVoltSamplesOf(channel, result, scope);
for (DataChannel &channelData : *result) {
ChannelGraph &target = channelData.voltage.graph;
const std::vector<double> &source = channelData.voltage.sample;

// Check if this channel is used and available at the data analyzer
if (samples.sample.empty()) {
if (source.empty() || !channelData.channelSettings->visible()) {
// Delete all vector arrays
target.clear();
continue;
}
// Check if the sample count has changed
size_t sampleCount = samples.sample.size();
size_t sampleCount = source.size();
if (sampleCount > 500000) {
qWarning() << "Sample count too high!";
throw new std::runtime_error("Sample count too high!");
}
sampleCount -= (swTriggerStart - preTrigSamples);
size_t neededSize = sampleCount * 2;

// Set size directly to avoid reallocations
target.reserve(neededSize);

// What's the horizontal distance between sampling points?
float horizontalFactor = (float)(samples.interval / scope->horizontal.timebase);

// Fill vector array
std::vector<double>::const_iterator dataIterator = samples.sample.begin();
const float gain = (float)scope->gain(channel);
const float offset = (float)scope->voltage[channel].offset;
const float invert = scope->voltage[channel].inverted ? -1.0f : 1.0f;

std::advance(dataIterator, swTriggerStart - preTrigSamples);
const unsigned offSamples = unsigned(swTriggerStart - preTrigSamples);
sampleCount -= offSamples;

// Set size directly to avoid reallocations
target.resize(sampleCount);

// Data samples are in [0,1] (as long as the voltageLimits are set correctly).
// The offset needs to be applied now, as well as the gain.
const float timeFactor =
float(channelData.voltage.interval * DIVS_TIME * 2 / m_deviceSettings->samplerate().timebase);
const float offY = (float)channelData.channelSettings->voltage()->offset() * DIVS_VOLTAGE / 2;
const float offX = -DIVS_TIME / 2;
const int invert = channelData.channelSettings->inverted() ? -1.0 : 1.0;
const float gain = invert / channelData.channelSettings->gain();

#pragma omp parallel for
for (unsigned int position = 0; position < sampleCount; ++position) {
target.push_back(QVector3D(position * horizontalFactor - DIVS_TIME / 2,
(float)*(dataIterator++) / gain * invert + offset, 0.0));
const float v = (float)source[position + offSamples];
target[position] = (QVector3D(position * timeFactor + offX, v * gain + offY, 0.0));
}
}
}

void GraphGenerator::generateGraphsTYspectrum(PPresult *result) {
ready = true;
result->vaChannelSpectrum.resize(scope->spectrum.size());
for (ChannelID channel = 0; channel < scope->voltage.size(); ++channel) {
ChannelGraph &target = result->vaChannelSpectrum[channel];
const SampleValues &samples = useSpecSamplesOf(channel, result, scope);
for (DataChannel &channelData : *result) {
ChannelGraph &target = channelData.spectrum.graph;
const std::vector<double> &source = channelData.spectrum.sample;

// Check if this channel is used and available at the data analyzer
if (samples.sample.empty()) {
if (source.empty()) {
// Delete all vector arrays
target.clear();
continue;
}
// Check if the sample count has changed
size_t sampleCount = samples.sample.size();
size_t sampleCount = source.size();
if (sampleCount > 500000) {
qWarning() << "Sample count too high!";
throw new std::runtime_error("Sample count too high!");
}
size_t neededSize = sampleCount * 2;

// Set size directly to avoid reallocations
target.reserve(neededSize);
target.resize(sampleCount);

// What's the horizontal distance between sampling points?
float horizontalFactor = (float)(samples.interval / scope->horizontal.frequencybase);
const float timeFactor = (float)(channelData.spectrum.interval / m_scope->frequencybase());

// Fill vector array
std::vector<double>::const_iterator dataIterator = samples.sample.begin();
const float magnitude = (float)scope->spectrum[channel].magnitude;
const float offset = (float)scope->spectrum[channel].offset;
const float magnitude = (float)channelData.channelSettings->spectrum()->magnitude();
const float offY = (float)channelData.channelSettings->spectrum()->offset() * DIVS_VOLTAGE / 2;
const float offX = -DIVS_TIME / 2;

#pragma omp parallel for
for (unsigned int position = 0; position < sampleCount; ++position) {
target.push_back(QVector3D(position * horizontalFactor - DIVS_TIME / 2,
(float)*(dataIterator++) / magnitude + offset, 0.0));
const float v = (float)source[position];
target[position] = (QVector3D(position * timeFactor + offX, v / magnitude + offY, 0.0));
}
}
}

void GraphGenerator::process(PPresult *data) {
if (scope->horizontal.format == Dso::GraphFormat::TY) {
ready = true;
if (m_scope->format() == Dso::GraphFormat::TY) {
generateGraphsTYspectrum(data);
generateGraphsTYvoltage(data);
} else
generateGraphsXY(data, scope);
generateGraphsXY(data, m_scope);
}

void GraphGenerator::generateGraphsXY(PPresult *result, const DsoSettingsScope *scope) {
result->vaChannelVoltage.resize(scope->voltage.size());
void GraphGenerator::generateGraphsXY(PPresult *result, const ::Settings::Scope *scope) {
ChannelID xChannel;
DataChannel *lastChannel = nullptr;

// Delete all spectrum graphs
for (ChannelGraph &data : result->vaChannelSpectrum) data.clear();
for (DataChannel &channelData : *result) {
// Delete all spectrum graphs
channelData.spectrum.graph.clear();
channelData.voltage.graph.clear();

// Generate voltage graphs for pairs of channels
for (ChannelID channel = 0; channel < scope->voltage.size(); channel += 2) {
// We need pairs of channels.
if (channel + 1 == scope->voltage.size()) {
result->vaChannelVoltage[channel].clear();
// Generate voltage graphs for pairs of channels
if (!lastChannel) {
lastChannel = &channelData;
xChannel = channelData.channelID;
continue;
}

const ChannelID xChannel = channel;
const ChannelID yChannel = channel + 1;
DataChannel *thisChannel = &channelData;

const SampleValues &xSamples = useVoltSamplesOf(xChannel, result, scope);
const SampleValues &ySamples = useVoltSamplesOf(yChannel, result, scope);
ChannelGraph &target = lastChannel->voltage.graph;
const std::vector<double> &xSamples = lastChannel->voltage.sample;
const std::vector<double> &ySamples = thisChannel->voltage.sample;
const ::Settings::Channel *xSettings = lastChannel->channelSettings.get();
const ::Settings::Channel *ySettings = thisChannel->channelSettings.get();

// The channels need to be active
if (!xSamples.sample.size() || !ySamples.sample.size()) {
result->vaChannelVoltage[channel].clear();
result->vaChannelVoltage[channel + 1].clear();
if (!xSamples.size() || !ySamples.size()) {
lastChannel->voltage.graph.clear();
thisChannel->voltage.graph.clear();
continue;
}

// Check if the sample count has changed
const size_t sampleCount = std::min(xSamples.sample.size(), ySamples.sample.size());
ChannelGraph &drawLines = result->vaChannelVoltage[channel];
drawLines.reserve(sampleCount * 2);
const size_t sampleCount = std::min(xSamples.size(), ySamples.size());
target.resize(sampleCount * 2);

// Fill vector array
std::vector<double>::const_iterator xIterator = xSamples.sample.begin();
std::vector<double>::const_iterator yIterator = ySamples.sample.begin();
const double xGain = scope->gain(xChannel);
const double yGain = scope->gain(yChannel);
const double xOffset = scope->voltage[xChannel].offset;
const double yOffset = scope->voltage[yChannel].offset;
const double xInvert = scope->voltage[xChannel].inverted ? -1.0 : 1.0;
const double yInvert = scope->voltage[yChannel].inverted ? -1.0 : 1.0;

const double xGain = m_deviceSettings->spec->gain[xSettings->voltage()->gainStepIndex()].gain;
const double yGain = m_deviceSettings->spec->gain[ySettings->voltage()->gainStepIndex()].gain;
const double xOffset = ((float)xSettings->voltage()->offset() / DIVS_VOLTAGE) + 0.5f;
const double yOffset = ((float)ySettings->voltage()->offset() / DIVS_VOLTAGE) + 0.5f;
const double xInvert = xSettings->inverted() ? -1.0 : 1.0;
const double yInvert = ySettings->inverted() ? -1.0 : 1.0;

#pragma omp parallel for
for (unsigned int position = 0; position < sampleCount; ++position) {
drawLines.push_back(QVector3D((float)(*(xIterator++) / xGain * xInvert + xOffset),
(float)(*(yIterator++) / yGain * yInvert + yOffset), 0.0));
target[position] = (QVector3D((float)(xSamples[position] / xGain * xInvert + xOffset),
(float)(ySamples[position] / yGain * yInvert + yOffset), 0.0));
}

// Wait for another pair of channels
lastChannel = nullptr;
}
}
}
25 changes: 15 additions & 10 deletions openhantek/src/post/graphgenerator.h
Original file line number Diff line number Diff line change
@@ -11,32 +11,37 @@
#include "hantekprotocol/types.h"
#include "processor.h"

struct DsoSettingsScope;
namespace Settings {
class Scope;
}
class PPresult;
namespace Dso {
struct ControlSpecification;
class DeviceSettings;
class ChannelUsage;
struct ModelSpec;
}
namespace PostProcessing {

/// \brief Generates ready to be used vertex arrays
class GraphGenerator : public QObject, public Processor {
Q_OBJECT

public:
GraphGenerator(const DsoSettingsScope *scope, bool isSoftwareTriggerDevice);
void generateGraphsXY(PPresult *result, const DsoSettingsScope *scope);

bool isReady() const;
GraphGenerator(const ::Settings::Scope *m_scope, const Dso::DeviceSettings *m_deviceSettings,
const Dso::ChannelUsage *channelUsage);
void generateGraphsXY(PPresult *result, const ::Settings::Scope *m_scope);

private:
void generateGraphsTYvoltage(PPresult *result);
void generateGraphsTYspectrum(PPresult *result);

private:
bool ready = false;
const DsoSettingsScope *scope;
const bool isSoftwareTriggerDevice;
const ::Settings::Scope *m_scope;
const Dso::DeviceSettings *m_deviceSettings;
const Dso::ChannelUsage *m_channelUsage;

// Processor interface
private:
private:
virtual void process(PPresult *) override;
};
}
68 changes: 37 additions & 31 deletions openhantek/src/post/mathchannelgenerator.cpp
Original file line number Diff line number Diff line change
@@ -1,47 +1,53 @@
// SPDX-License-Identifier: GPL-2.0+

#include "mathchannelgenerator.h"
#include "scopesettings.h"
#include "post/postprocessingsettings.h"
#include "enums.h"
#include "post/postprocessingsettings.h"
#include "scopesettings.h"
#include "utils/getwithdefault.h"

MathChannelGenerator::MathChannelGenerator(const DsoSettingsScope *scope, unsigned physicalChannels)
: physicalChannels(physicalChannels), scope(scope) {}
namespace PostProcessing {

MathChannelGenerator::MathChannelGenerator(const ::Settings::Scope *scope) : scope(scope) {}

MathChannelGenerator::~MathChannelGenerator() {}

void MathChannelGenerator::process(PPresult *result) {
bool channelsHaveData = !result->data(0)->voltage.sample.empty() && !result->data(1)->voltage.sample.empty();
if (!channelsHaveData) return;

for (ChannelID channel = physicalChannels; channel < result->channelCount(); ++channel) {
DataChannel *const channelData = result->modifyData(channel);

for (std::pair<const ChannelID, const std::shared_ptr<::Settings::Channel>> item : scope->channels()) {
::Settings::Channel *channel = item.second.get();
// Math channel enabled?
if (!scope->voltage[channel].used && !scope->spectrum[channel].used) continue;
if (!channel->isMathChannel() || !channel->anyVisible()) continue;

// Set sampling interval
channelData->voltage.interval = result->data(0)->voltage.interval;
const ::Settings::MathChannel *mathChannel = static_cast<const ::Settings::MathChannel *>(channel);
if (mathChannel->firstID() == ::Settings::Channel::INVALID ||
mathChannel->secondID() == ::Settings::Channel::INVALID)
continue;

SampleValues &targetVoltage = result->addChannel(channel->channelID(), false, item.second)->voltage;
const SampleValues &firstSampleValues = result->data(mathChannel->firstID())->voltage;
const std::vector<double> &firstChannel = firstSampleValues.sample;
const std::vector<double> &secondChannel = result->data(mathChannel->secondID())->voltage.sample;

// Resize the sample vector
std::vector<double> &resultData = channelData->voltage.sample;
resultData.resize(std::min(result->data(0)->voltage.sample.size(), result->data(1)->voltage.sample.size()));
targetVoltage.interval = firstSampleValues.interval;
std::vector<double> &resultData = targetVoltage.sample;
resultData.resize(std::min(firstChannel.size(), secondChannel.size()));

// Calculate values and write them into the sample buffer
std::vector<double>::const_iterator ch1Iterator = result->data(0)->voltage.sample.begin();
std::vector<double>::const_iterator ch2Iterator = result->data(1)->voltage.sample.begin();
for (std::vector<double>::iterator it = resultData.begin(); it != resultData.end(); ++it) {
switch (Dso::getMathMode(scope->voltage[physicalChannels])) {
case Dso::MathMode::ADD_CH1_CH2:
*it = *ch1Iterator + *ch2Iterator;
break;
case Dso::MathMode::SUB_CH2_FROM_CH1:
*it = *ch1Iterator - *ch2Iterator;
break;
case Dso::MathMode::SUB_CH1_FROM_CH2:
*it = *ch2Iterator - *ch1Iterator;
break;
}
++ch1Iterator;
++ch2Iterator;
switch (mathChannel->mathMode()) {
case PostProcessing::MathMode::ADD:
// #pragma omp parallel for
for (unsigned int i = 0; i < resultData.size(); ++i) resultData[i] = firstChannel[i] + secondChannel[i];
break;
case PostProcessing::MathMode::SUBSTRACT:
// #pragma omp parallel for
for (unsigned int i = 0; i < resultData.size(); ++i) resultData[i] = firstChannel[i] - secondChannel[i];
break;
case PostProcessing::MathMode::MULTIPLY:
// #pragma omp parallel for
for (unsigned int i = 0; i < resultData.size(); ++i) resultData[i] = firstChannel[i] * secondChannel[i];
break;
}
}
}
}
13 changes: 9 additions & 4 deletions openhantek/src/post/mathchannelgenerator.h
Original file line number Diff line number Diff line change
@@ -4,16 +4,21 @@

#include "processor.h"

struct DsoSettingsScope;
namespace Settings {
class Scope;
}
class PPresult;

namespace PostProcessing {

class MathChannelGenerator : public Processor
{
public:
MathChannelGenerator(const DsoSettingsScope *scope, unsigned physicalChannels);
MathChannelGenerator(const ::Settings::Scope *scope);
virtual ~MathChannelGenerator();
virtual void process(PPresult *) override;
private:
const unsigned physicalChannels;
const DsoSettingsScope *scope;
const ::Settings::Scope *scope;
};

}
61 changes: 47 additions & 14 deletions openhantek/src/post/postprocessing.cpp
Original file line number Diff line number Diff line change
@@ -1,29 +1,62 @@
// SPDX-License-Identifier: GPL-2.0+

#include "postprocessing.h"
#include "settings/scopesettings.h"
#include <QDebug>

namespace PostProcessing {

PostProcessing::PostProcessing(unsigned channelCount) : channelCount(channelCount) {
Executor::Executor(const ::Settings::Scope *scope) : m_scope(scope) {
qRegisterMetaType<std::shared_ptr<PPresult>>();
// Call constructor for all datapool ppresults.
for (unsigned i = 0; i < DATAPOOLSIZE; ++i) { new (&resultPool[i]) PPresult(); }
}

void PostProcessing::registerProcessor(Processor *processor) { processors.push_back(processor); }
void Executor::registerProcessor(Processor *processor) { processors.push_back(processor); }

void PostProcessing::convertData(const DSOsamples *source, PPresult *destination) {
inline void convertData(const DSOsamples *source, PPresult *destination, const ::Settings::Scope *scope) {
QReadLocker locker(&source->lock);

for (ChannelID channel = 0; channel < source->data.size(); ++channel) {
const std::vector<double> &rawChannelData = source->data.at(channel);
for (unsigned index = 0; index < source->channelCount(); ++index) {
auto &v = source->data[index];

if (rawChannelData.empty()) { continue; }
if (v.id == (unsigned)-1 || v.empty()) { continue; }

DataChannel *const channelData = destination->modifyData(channel);
// We also create a new shared_ptr reference to the underlying channelsettings. This way we do not get
// suprises if the user removes a math_channel during processing.
DataChannel *const channelData = destination->addChannel(v.id, true, scope->channel(v.id));
channelData->voltage.interval = 1.0 / source->samplerate;
channelData->voltage.sample = rawChannelData;
// Data copy
channelData->voltage.sample = v;
channelData->maxVoltage = v.maxVoltage;
channelData->minVoltage = v.minVoltage;
channelData->maxRaw = v.maxRaw;
channelData->minRaw = v.minRaw;
}
}

void PostProcessing::input(const DSOsamples *data) {
currentData.reset(new PPresult(channelCount));
convertData(data, currentData.get());
for (Processor *p : processors) p->process(currentData.get());
std::shared_ptr<PPresult> res = std::move(currentData);
emit processingFinished(res);
void Executor::input(const DSOsamples *data) {
PPresult *result = nullptr;
// Find a free PPresult in the PPresult pool
for (unsigned i = 0; i < DATAPOOLSIZE; ++i) {
PPresult *p = &resultPool[i];
if (!p->inUse) {
p->softwareTriggerTriggered = false;
p->removeNonDeviceChannels();
p->inUse = true;
result = p;
break;
}
}
// Nothing found: abort
if (!result) {
qWarning() << "Sampleset skipped. Too busy!";
return;
}

convertData(data, result, m_scope);
for (Processor *p : processors) p->process(result);
// Create a shared_ptr with a custom "delete" function that resets inUse.
emit processingFinished(std::shared_ptr<PPresult>(result, [](PPresult *r) { r->inUse = false; }));
}
}
25 changes: 14 additions & 11 deletions openhantek/src/post/postprocessing.h
Original file line number Diff line number Diff line change
@@ -10,17 +10,21 @@

#include <QObject>

struct DsoSettingsScope;
namespace Settings {
class Scope;
}

namespace PostProcessing {

/**
* Manages all post processing processors. Register another processor with `registerProcessor(p)`.
* All processors, in the order of insertion, will process the input data, given by `input(data)`.
* The final result will be made available via the `processingFinished` signal.
*/
class PostProcessing : public QObject {
class Executor : public QObject {
Q_OBJECT
public:
PostProcessing(unsigned channelCount);
Executor(const ::Settings::Scope* scope);
/**
* Adds a new processor that is called when a new input arrived. The order of the processors is
* imporant. The first added processor will be called first. This class does not take ownership
@@ -29,24 +33,23 @@ class PostProcessing : public QObject {
*/
void registerProcessor(Processor *processor);


private:
/// A new `PPresult` is created for each new input. We need to know the channel size.
const unsigned channelCount;
/// The list of processors. Processors are not memory managed by this class.
std::vector<Processor *> processors;
///
std::unique_ptr<PPresult> currentData;
static void convertData(const DSOsamples *source, PPresult *destination);
enum { DATAPOOLSIZE = 10 };
/// Result pool. We want to reduce allocations/deallocations and reuse PPresult structures
PPresult resultPool[DATAPOOLSIZE];

const ::Settings::Scope* m_scope;
public slots:
/**
* Start processing new data. The actual data may be processed in another thread if you have moved
* this class object into another thread.
* @param data
*/
void input(const DSOsamples *data);
signals:
signals:
void processingFinished(std::shared_ptr<PPresult> result);
};

}
Q_DECLARE_METATYPE(std::shared_ptr<PPresult>)
77 changes: 19 additions & 58 deletions openhantek/src/post/postprocessingsettings.cpp
Original file line number Diff line number Diff line change
@@ -1,63 +1,24 @@
#include "postprocessingsettings.h"

#include <QCoreApplication>
#include <QString>
// SPDX-License-Identifier: GPL-2.0+

namespace Dso {
#include "postprocessingsettings.h"
#include "utils/enumhelper.h"

Enum<Dso::MathMode, Dso::MathMode::ADD_CH1_CH2, Dso::MathMode::SUB_CH1_FROM_CH2> MathModeEnum;
Enum<Dso::WindowFunction, Dso::WindowFunction::RECTANGULAR, Dso::WindowFunction::FLATTOP> WindowFunctionEnum;
#include <QSettings>

/// \brief Return string representation of the given math mode.
/// \param mode The ::MathMode that should be returned as string.
/// \return The string that should be used in labels etc.
QString mathModeString(MathMode mode) {
switch (mode) {
case MathMode::ADD_CH1_CH2:
return QCoreApplication::tr("CH1 + CH2");
case MathMode::SUB_CH2_FROM_CH1:
return QCoreApplication::tr("CH1 - CH2");
case MathMode::SUB_CH1_FROM_CH2:
return QCoreApplication::tr("CH2 - CH1");
}
return QString();
}
/// \brief Return string representation of the given dft window function.
/// \param window The ::WindowFunction that should be returned as string.
/// \return The string that should be used in labels etc.
QString windowFunctionString(WindowFunction window) {
switch (window) {
case WindowFunction::RECTANGULAR:
return QCoreApplication::tr("Rectangular");
case WindowFunction::HAMMING:
return QCoreApplication::tr("Hamming");
case WindowFunction::HANN:
return QCoreApplication::tr("Hann");
case WindowFunction::COSINE:
return QCoreApplication::tr("Cosine");
case WindowFunction::LANCZOS:
return QCoreApplication::tr("Lanczos");
case WindowFunction::BARTLETT:
return QCoreApplication::tr("Bartlett");
case WindowFunction::TRIANGULAR:
return QCoreApplication::tr("Triangular");
case WindowFunction::GAUSS:
return QCoreApplication::tr("Gauss");
case WindowFunction::BARTLETTHANN:
return QCoreApplication::tr("Bartlett-Hann");
case WindowFunction::BLACKMAN:
return QCoreApplication::tr("Blackman");
// case WindowFunction::WINDOW_KAISER:
// return QCoreApplication::tr("Kaiser");
case WindowFunction::NUTTALL:
return QCoreApplication::tr("Nuttall");
case WindowFunction::BLACKMANHARRIS:
return QCoreApplication::tr("Blackman-Harris");
case WindowFunction::BLACKMANNUTTALL:
return QCoreApplication::tr("Blackman-Nuttall");
case WindowFunction::FLATTOP:
return QCoreApplication::tr("Flat top");
}
return QString();
namespace PostProcessing {
void SettingsIO::read(QSettings *store, Settings &post) {
store->beginGroup("postprocessing");
post.m_spectrumLimit = store->value("imageSize", post.m_spectrumLimit).toDouble();
post.m_spectrumReference = store->value("exportSizeBytes", post.m_spectrumReference).toUInt();
post.m_spectrumWindow = loadForEnum(store, "spectrumWindow", post.m_spectrumWindow);
store->endGroup();
}

void SettingsIO::write(QSettings *store, const Settings &post) {
store->beginGroup("postprocessing");
store->setValue("spectrumLimit", post.m_spectrumLimit);
store->setValue("spectrumReference", post.m_spectrumReference);
store->setValue("spectrumWindow", enumName(post.m_spectrumWindow));
store->endGroup();
}
} // end namespace PostProcessing
63 changes: 22 additions & 41 deletions openhantek/src/post/postprocessingsettings.h
41 changes: 19 additions & 22 deletions openhantek/src/post/ppresult.cpp
74 changes: 57 additions & 17 deletions openhantek/src/post/ppresult.h
6 changes: 6 additions & 0 deletions openhantek/src/post/processor.h
2 changes: 1 addition & 1 deletion openhantek/src/post/readme.md
67 changes: 67 additions & 0 deletions openhantek/src/post/selfcalibration.cpp
38 changes: 38 additions & 0 deletions openhantek/src/post/selfcalibration.h
71 changes: 46 additions & 25 deletions openhantek/src/post/softwaretrigger.cpp
21 changes: 15 additions & 6 deletions openhantek/src/post/softwaretrigger.h
99 changes: 49 additions & 50 deletions openhantek/src/post/spectrumgenerator.cpp
23 changes: 15 additions & 8 deletions openhantek/src/post/spectrumgenerator.h
80 changes: 0 additions & 80 deletions openhantek/src/scopesettings.h

This file was deleted.

154 changes: 154 additions & 0 deletions openhantek/src/scopeview/glframe.cpp
107 changes: 107 additions & 0 deletions openhantek/src/scopeview/glframe.h
111 changes: 111 additions & 0 deletions openhantek/src/scopeview/glicon.cpp
87 changes: 87 additions & 0 deletions openhantek/src/scopeview/glicon.h
6 changes: 6 additions & 0 deletions openhantek/src/scopeview/glmousedevice.cpp
52 changes: 52 additions & 0 deletions openhantek/src/scopeview/glmousedevice.h
184 changes: 184 additions & 0 deletions openhantek/src/scopeview/glmoveresizesnap.cpp
134 changes: 134 additions & 0 deletions openhantek/src/scopeview/glmoveresizesnap.h
348 changes: 348 additions & 0 deletions openhantek/src/scopeview/glscope.cpp
216 changes: 216 additions & 0 deletions openhantek/src/scopeview/glscope.h
133 changes: 133 additions & 0 deletions openhantek/src/scopeview/glscopegraph.cpp
56 changes: 56 additions & 0 deletions openhantek/src/scopeview/glscopegraph.h
237 changes: 237 additions & 0 deletions openhantek/src/scopeview/glscopegrid.cpp
24 changes: 24 additions & 0 deletions openhantek/src/scopeview/glscopegrid.h
24 changes: 24 additions & 0 deletions openhantek/src/scopeview/glscopehover.h
127 changes: 127 additions & 0 deletions openhantek/src/scopeview/glscopezoomviewport.cpp
84 changes: 84 additions & 0 deletions openhantek/src/scopeview/glscopezoomviewport.h
267 changes: 0 additions & 267 deletions openhantek/src/settings.cpp

This file was deleted.

38 changes: 0 additions & 38 deletions openhantek/src/settings.h

This file was deleted.

71 changes: 71 additions & 0 deletions openhantek/src/settings/colorsettings.h
18 changes: 18 additions & 0 deletions openhantek/src/settings/markerandzoomsettings.cpp
62 changes: 62 additions & 0 deletions openhantek/src/settings/markerandzoomsettings.h
100 changes: 100 additions & 0 deletions openhantek/src/settings/scopechannel.cpp
84 changes: 84 additions & 0 deletions openhantek/src/settings/scopechannel.h
49 changes: 49 additions & 0 deletions openhantek/src/settings/scopemathchannel.h
146 changes: 146 additions & 0 deletions openhantek/src/settings/scopesettings.cpp
87 changes: 87 additions & 0 deletions openhantek/src/settings/scopesettings.h
68 changes: 68 additions & 0 deletions openhantek/src/settings/settings.cpp
45 changes: 45 additions & 0 deletions openhantek/src/settings/settings.h
39 changes: 39 additions & 0 deletions openhantek/src/settings/spectrum.h
165 changes: 165 additions & 0 deletions openhantek/src/settings/viewsettings.cpp
68 changes: 68 additions & 0 deletions openhantek/src/settings/viewsettings.h
33 changes: 9 additions & 24 deletions openhantek/src/usb/usbdevice.cpp
63 changes: 32 additions & 31 deletions openhantek/src/usb/usbdevice.h
9 changes: 6 additions & 3 deletions openhantek/src/usb/usbdevicedefinitions.h
66 changes: 66 additions & 0 deletions openhantek/src/utils/debugnotify.cpp
52 changes: 52 additions & 0 deletions openhantek/src/utils/debugnotify.h
47 changes: 0 additions & 47 deletions openhantek/src/utils/enumclass.h

This file was deleted.

44 changes: 44 additions & 0 deletions openhantek/src/utils/enumhelper.h
125 changes: 125 additions & 0 deletions openhantek/src/utils/getwithdefault.h
20 changes: 20 additions & 0 deletions openhantek/src/utils/observer.h
20 changes: 10 additions & 10 deletions openhantek/src/utils/printutils.cpp
4 changes: 2 additions & 2 deletions openhantek/src/utils/printutils.h
65 changes: 65 additions & 0 deletions openhantek/src/utils/scopecoordinates.h
3 changes: 3 additions & 0 deletions openhantek/src/viewconstants.h
49 changes: 0 additions & 49 deletions openhantek/src/viewsettings.h

This file was deleted.

67 changes: 18 additions & 49 deletions openhantek/src/widgets/colorbox.cpp
14 changes: 11 additions & 3 deletions openhantek/src/widgets/colorbox.h
489 changes: 489 additions & 0 deletions openhantek/src/widgets/dsowidget.cpp
135 changes: 135 additions & 0 deletions openhantek/src/widgets/dsowidget.h
592 changes: 224 additions & 368 deletions openhantek/src/widgets/levelslider.cpp
173 changes: 141 additions & 32 deletions openhantek/src/widgets/levelslider.h
196 changes: 48 additions & 148 deletions openhantek/src/widgets/sispinbox.cpp
79 changes: 55 additions & 24 deletions openhantek/src/widgets/sispinbox.h
2 changes: 1 addition & 1 deletion readme.md