Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/instructions/cpp-style.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Every `.c`/`.h`/`.cpp`/`.hpp` starts with this block (ISO 8601 dates, no
* @version 1.0.0
* @authors <author>
* @date YYYY-MM-DD
* @date @showdate "%Y-%m-%d"
*/
```

Expand Down Expand Up @@ -147,6 +148,24 @@ success, leave it at the error default otherwise.
| Enum values | `eCamelCase` | `eSoftware`, `eWatchDog` |
| Module-level statics | `lower_snake_case`| `system_clock_hz` |

Pointer and reference declarators stay next to the variable name, not the type:

```cpp
int *value_ptr;
const char *name;
std::string &value_ref;
const std::vector<int> &values;
```
The exception is the return types of functions:
```cpp
static libusb_device* findDeviceByInfo(
libusb_device **deviceList,
const SUsbDeviceInfo &deviceInfo,
const ssize_t deviceCount
);
```

The structure variables are placed taking into account alignment and the minimum amount of padding.

## Conditional compilation

Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
CMakePresets.json
CMakeUserPresets.json

# VS code user files
.vscode/*

# Python cache
__pycache__/

Expand Down
9 changes: 8 additions & 1 deletion HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,17 @@ Records key decisions, structural changes, and completed development stages.
- Rescan automatically when switching from Demo mode to Live mode.
- Verified detection with a connected Hantek DSO-2250 and a successful Release
build.
- Implemented device opening, USB interface claiming, interface release, and
handle/context cleanup for the selected Hantek DSO-2250.
- Added Connect and Disconnect controls with connection state in the status
line.
- Disconnect automatically when entering Demo mode, on device loss after a
rescan, and during application shutdown.
- Added a Linux udev rule for non-root DSO-2250 access through libusb.
- Verified the connect/disconnect lifecycle with a connected Hantek DSO-2250.

### Next USB tasks

- Connect to the selected device and claim its USB interface.
- Read endpoint data chunks in a dedicated acquisition path.
- Add buffering between USB reads and waveform processing.
- Handle timeouts, I/O errors, disconnects, and recovery states.
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,28 @@ The current application requires:
- GNU Make for builds through `make`.
- SDL2 development files;
- OpenGL development files.
- libusb-1.0 development files.

On Debian, Ubuntu, and Linux Mint:

```bash
sudo apt-get update
sudo apt-get install build-essential cmake make pkg-config libsdl2-dev libgl1-mesa-dev
sudo apt-get install build-essential cmake make pkg-config libsdl2-dev libgl1-mesa-dev libusb-1.0-0-dev
```

Dear ImGui is downloaded automatically by CMake during configuration. libusb
will be required when the USB layer is implemented.
Dear ImGui is downloaded automatically by CMake during configuration.

### Hantek DSO-2250 USB Access

Linux requires a udev rule for a regular desktop user to open the Hantek device
through libusb. Install the supplied rule, reload udev rules, then reconnect the
oscilloscope:

```bash
sudo cp usb/80-hantek-dso-2250.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm trigger
```

## Build

Expand Down
206 changes: 195 additions & 11 deletions app/main.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
/**
* @copyright @showdate "%Y " Anton Chernov. All rights reserved.
* @file main.cpp
* @version 0.2.1
* @authors Anton Chernov
* @date 2026-08-28
* @date @showdate "%Y-%m-%d"
* @par
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/********************************* Definition ********************************/
Expand Down Expand Up @@ -62,6 +75,9 @@
#include "usb/usb_device.h"

using oscilloscope::usb::EScanStatus;
using oscilloscope::usb::SUsbConnection;
using oscilloscope::usb::SUsbConnectionResult;
using oscilloscope::usb::SUsbDeviceInfo;
using oscilloscope::usb::SUsbScanResult;

/***************************** Private variables *****************************/
Expand All @@ -87,10 +103,23 @@ static void drawOscilloscopeGrid(
const ImVec2 &position,
const ImVec2 &size
);
static std::string formatUsbScanStatus(const SUsbScanResult &scanResult);
static bool isUsbDevicePresent(
const SUsbScanResult &scanResult,
const SUsbDeviceInfo &deviceInfo
);
static std::string formatUsbConnectionStatus(
const SUsbScanResult &scanResult,
const SUsbConnection &connection
);
static std::string formatUsbConnectionError(
const char *operation,
const SUsbConnectionResult &result
);
static void updateDemoMode(
bool *demoMode,
SUsbScanResult *usbScanResult,
SUsbConnection *connection,
SUsbDeviceInfo *connectedDevice,
std::string *deviceStatus
);

Expand Down Expand Up @@ -149,7 +178,12 @@ int main (void) {
int voltsPerDivision[] = {2, 2};
SUsbScanResult usbScanResult =
oscilloscope::usb::enumerateSupportedDevices();
std::string deviceStatus = formatUsbScanStatus(usbScanResult);
SUsbConnection usbConnection = {NULL, NULL, 0U, false};
SUsbDeviceInfo connectedDevice = {0U, 0U, 0U, 0U, NULL};
std::string deviceStatus = formatUsbConnectionStatus(
usbScanResult,
usbConnection
);
const char* timebases[] = {
"4 ns/div", "20 ns/div", "100 ns/div", "1 us/div", "10 us/div",
"100 us/div", "1 ms/div", "10 ms/div", "100 ms/div", "1 s/div"
Expand Down Expand Up @@ -186,6 +220,8 @@ int main (void) {
updateDemoMode(
&demoMode,
&usbScanResult,
&usbConnection,
&connectedDevice,
&deviceStatus
);
}
Expand Down Expand Up @@ -236,7 +272,85 @@ int main (void) {
}
if (ImGui::Button("Rescan devices", ImVec2(-1.0f, 32.0f))) {
usbScanResult = oscilloscope::usb::enumerateSupportedDevices();
deviceStatus = formatUsbScanStatus(usbScanResult);

if (
usbConnection.isConnected &&
!isUsbDevicePresent(usbScanResult, connectedDevice)
) {
const SUsbConnectionResult disconnectResult =
oscilloscope::usb::disconnectFromDevice(&usbConnection);

connectedDevice = {0U, 0U, 0U, 0U, NULL};
if (disconnectResult.errorMessage.empty()) {
deviceStatus = formatUsbConnectionStatus(
usbScanResult,
usbConnection
);
}
else {
deviceStatus = formatUsbConnectionError(
"Disconnect",
disconnectResult
);
}
}
else {
deviceStatus = formatUsbConnectionStatus(
usbScanResult,
usbConnection
);
}
}

if (usbConnection.isConnected) {
if (ImGui::Button("Disconnect", ImVec2(-1.0f, 32.0f))) {
const SUsbConnectionResult disconnectResult =
oscilloscope::usb::disconnectFromDevice(&usbConnection);

connectedDevice = {0U, 0U, 0U, 0U, NULL};
if (disconnectResult.errorMessage.empty()) {
deviceStatus = formatUsbConnectionStatus(
usbScanResult,
usbConnection
);
}
else {
deviceStatus = formatUsbConnectionError(
"Disconnect",
disconnectResult
);
}
}
}
else {
const bool canConnect =
!demoMode &&
(usbScanResult.status == EScanStatus::eSuccess) &&
!usbScanResult.devices.empty();

ImGui::BeginDisabled(!canConnect);
if (ImGui::Button("Connect", ImVec2(-1.0f, 32.0f))) {
const SUsbConnectionResult connectResult =
oscilloscope::usb::connectToDevice(
usbScanResult.devices.front(),
&usbConnection
);

if (connectResult.errorMessage.empty()) {
connectedDevice = usbScanResult.devices.front();
deviceStatus = formatUsbConnectionStatus(
usbScanResult,
usbConnection
);
}
else {
deviceStatus = formatUsbConnectionError(
"Connect",
connectResult
);
}
}
ImGui::EndDisabled();
}
ImGui::Separator();
ImGui::TextUnformatted("Horizontal");
Expand All @@ -258,7 +372,13 @@ int main (void) {
bool newDemoMode = demoMode;

if (ImGui::Checkbox("Demo mode", &newDemoMode)) {
updateDemoMode(&demoMode, &usbScanResult, &deviceStatus);
updateDemoMode(
&demoMode,
&usbScanResult,
&usbConnection,
&connectedDevice,
&deviceStatus
);
}
ImGui::EndChild();

Expand Down Expand Up @@ -287,22 +407,53 @@ int main (void) {
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
if (usbConnection.isConnected) {
oscilloscope::usb::disconnectFromDevice(&usbConnection);
}
SDL_GL_DeleteContext(glContext);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
/***************************** Private functions *****************************/

static std::string formatUsbScanStatus(const SUsbScanResult &scanResult) {
static bool isUsbDevicePresent(
const SUsbScanResult &scanResult,
const SUsbDeviceInfo &deviceInfo
) {
bool isPresent = false;

for (const SUsbDeviceInfo &device : scanResult.devices) {
if (
(device.vendorId == deviceInfo.vendorId) &&
(device.productId == deviceInfo.productId) &&
(device.busNumber == deviceInfo.busNumber) &&
(device.deviceAddress == deviceInfo.deviceAddress)
) {
isPresent = true;
break;
}
}

return isPresent;
}

static std::string formatUsbConnectionStatus(
const SUsbScanResult &scanResult,
const SUsbConnection &connection
) {
std::string status;

if (scanResult.status == EScanStatus::eSuccess) {
if (connection.isConnected) {
status = "Connected";
}
else if (scanResult.status == EScanStatus::eSuccess) {
if (scanResult.devices.empty()) {
status = "No supported device";
status = "Disconnected: No supported device";
}
else {
status = scanResult.devices.front().modelName;
status = "Disconnected: ";
status += scanResult.devices.front().modelName;
status += " detected";
}
}
Expand All @@ -314,16 +465,49 @@ static std::string formatUsbScanStatus(const SUsbScanResult &scanResult) {
return status;
}

static std::string formatUsbConnectionError(
const char *operation,
const SUsbConnectionResult &result
) {
std::string status = operation;

status += " error: ";
status += result.errorMessage;
return status;
}

static void updateDemoMode(
bool *demoMode,
SUsbScanResult *usbScanResult,
SUsbConnection *connection,
SUsbDeviceInfo *connectedDevice,
std::string *deviceStatus
) {
bool updateStatus = true;

*demoMode = !*demoMode;

if (!*demoMode) {
if (*demoMode) {
if (connection->isConnected) {
const SUsbConnectionResult disconnectResult =
oscilloscope::usb::disconnectFromDevice(connection);

*connectedDevice = {0U, 0U, 0U, 0U, NULL};
if (!disconnectResult.errorMessage.empty()) {
*deviceStatus = formatUsbConnectionError(
"Disconnect",
disconnectResult
);
updateStatus = false;
}
}
}
else {
*usbScanResult = oscilloscope::usb::enumerateSupportedDevices();
*deviceStatus = formatUsbScanStatus(*usbScanResult);
}

if (updateStatus) {
*deviceStatus = formatUsbConnectionStatus(*usbScanResult, *connection);
}
}

Expand Down
Loading
Loading