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
14 changes: 14 additions & 0 deletions .github/instructions/cpp-style.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ Use the `//!` style on `#endif` (not `/* NAME */`):
System / standard library headers first (angle-bracket form), then project
headers (quote form). Blank line between unrelated clusters.

Prefer `<stdint.h>` over `<cstdint>`. Use fixed-width integer types such as
`uint16_t` and `uint8_t` without the `std::` prefix.

Do not use `using namespace std;` at global scope. Keep standard-library types
that are not fixed-width integer types explicitly qualified, for example
`std::string` and `std::vector`.

## Module structure

A module containing one implementation file and one matching header may keep
both files in its module directory. When a module has, or is expected to gain,
multiple implementation or header files, place headers in `inc/` and source
files in `src/` below that module directory.

## Macro documentation

Every public or non-obvious `#define` gets a Doxygen block; align related values:
Expand Down
7 changes: 4 additions & 3 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
## Linked Task
<!-- Link to issue, task, or ticket -->
<!-- AUTO-LINKED-TASK:START -->
<!-- AUTO-LINKED-TASK:END -->

## Commits
<!-- AUTO-COMMIT-SUMMARY:START -->
<!-- AUTO-COMMIT-SUMMARY:END -->

## Author Checklist
- [ ] added a summary description of the work done to the linked task(s);
- [ ] added generated documentation;
- [ ] added a description of the work completed to the history file;
- [ ] successfully generated the documentation, and it meets expectations;
- [ ] successfully compiled the library on Linux;

## Testing
- [ ] not run
- [ ] `./linux_build.sh -c -d`
- [ ] `make debug`
- [ ] manual UI check

Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/pr-commit-summary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
PR_TITLE: ${{ github.event.pull_request.title }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
shell: python
run: |
import os
Expand All @@ -49,18 +51,37 @@ jobs:

pr_number = os.environ['PR_NUMBER']
repo = os.environ['REPO']
pr_title = os.environ['PR_TITLE']
head_ref = os.environ['HEAD_REF']

body = subprocess.check_output(
['gh', 'api', f'repos/{repo}/pulls/{pr_number}', '--jq', '.body // ""'],
text=True,
) or ''

linked_task_start = '<!-- AUTO-LINKED-TASK:START -->'
linked_task_end = '<!-- AUTO-LINKED-TASK:END -->'
start_marker = '<!-- AUTO-COMMIT-SUMMARY:START -->'
end_marker = '<!-- AUTO-COMMIT-SUMMARY:END -->'

commit_lines = Path('/tmp/pr_commits.txt').read_text(encoding='utf-8').strip()
summary_content = f"{commit_lines}"

task_match = re.search(
r'(?im)^\s*(?:closes|fixes|resolves)\s+#(\d+)\b',
body,
)
if task_match is None:
task_match = re.search(r'(?<!\w)#(\d+)\b', pr_title)
if task_match is None:
task_match = re.match(r'(\d+)(?:[-_/]|$)', head_ref)
if task_match is None:
task_match = re.search(r'(?<!\w)#(\d+)\b', commit_lines)

linked_task_content = ''
if task_match is not None:
linked_task_content = f'Closes #{task_match.group(1)}'

marker_pattern = re.compile(
rf'{re.escape(start_marker)}.*?{re.escape(end_marker)}',
flags=re.S,
Expand All @@ -76,6 +97,20 @@ jobs:
cleaned = body.rstrip()
updated = cleaned + '\n\n' + replacement + '\n' if cleaned else replacement + '\n'

linked_task_pattern = re.compile(
rf'{re.escape(linked_task_start)}.*?{re.escape(linked_task_end)}',
flags=re.S,
)
linked_task_replacement = (
f'{linked_task_start}\n{linked_task_content}\n{linked_task_end}'
)

if linked_task_pattern.search(updated):
updated = linked_task_pattern.sub(
lambda _match: linked_task_replacement,
updated,
)

subprocess.run(
[
'gh', 'api',
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ __pycache__/
[Dd]ebug
[Rr]elease

# Documentation output
docs/out/

# Additional ignored files
OldQtCode/
WorkingDocs
Expand Down
12 changes: 10 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ FetchContent_MakeAvailable(imgui)

find_package(OpenGL REQUIRED)
find_package(SDL2 REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBUSB REQUIRED IMPORTED_TARGET libusb-1.0)

set(APP_NAME run)

Expand All @@ -30,6 +32,7 @@ set(CMAKE_CXX_FLAGS_RELEASE "-O2")

set(SOURCES_LIST
app/main.cpp
usb/usb_device.cpp
${imgui_SOURCE_DIR}/imgui.cpp
${imgui_SOURCE_DIR}/imgui_draw.cpp
${imgui_SOURCE_DIR}/imgui_tables.cpp
Expand All @@ -39,7 +42,7 @@ set(SOURCES_LIST
)

set(HEADERS_LIST

usb/usb_device.h
)

if(CMAKE_BUILD_TYPE MATCHES "Debug")
Expand All @@ -57,9 +60,14 @@ endif()
add_executable(${APP_NAME} ${SOURCES_LIST})

target_include_directories(${APP_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${imgui_SOURCE_DIR}
${imgui_SOURCE_DIR}/backends
)

target_link_libraries(${APP_NAME} PRIVATE SDL2::SDL2 OpenGL::GL)
target_link_libraries(${APP_NAME} PRIVATE
SDL2::SDL2
OpenGL::GL
PkgConfig::LIBUSB
)

23 changes: 19 additions & 4 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,22 @@ Records key decisions, structural changes, and completed development stages.
- Updated `.gitignore` with minor repository housekeeping changes.
- Verified the Release build after the UI layout changes.

### Next milestone

- Stage 3 - USB Layer: add libusb, implement Hantek device detection,
connection lifecycle, endpoint reads, buffering, and safe error states.
### Stage 3 - USB detection and connection

- Added `libusb-1.0` as a required CMake dependency.
- Implemented supported-device enumeration through libusb.
- Added the Hantek DSO-2250 device signature: VID `0x04B4`, PID `0x2250`.
- Scan every connected USB device and retain all supported-device matches with
their bus number, address, VID/PID, and model name.
- Added a `Rescan devices` control and status-line reporting for detected
devices and libusb errors.
- Rescan automatically when switching from Demo mode to Live mode.
- Verified detection with a connected Hantek DSO-2250 and a successful Release
build.

### 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.
76 changes: 71 additions & 5 deletions app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* @version 0.2.0
* @authors Anton Chernov
* @date 2026-08-28
* @date @showdate "%m/%d/%Y"
* @date @showdate "%Y-%m-%d"
*/

/********************************* Definition ********************************/
Expand Down Expand Up @@ -50,6 +50,7 @@

/******************************* Included files ******************************/
#include <stdio.h>
#include <string>

#include <SDL.h>
#include <SDL_opengl.h>
Expand All @@ -58,6 +59,11 @@
#include <imgui_impl_opengl3.h>
#include <imgui_impl_sdl2.h>

#include "usb/usb_device.h"

using oscilloscope::usb::EScanStatus;
using oscilloscope::usb::SUsbScanResult;

/***************************** Private variables *****************************/

#ifdef __GNUC__ // GCC/MinGW only
Expand All @@ -81,6 +87,12 @@ static void drawOscilloscopeGrid(
const ImVec2 &position,
const ImVec2 &size
);
static std::string formatUsbScanStatus(const SUsbScanResult &scanResult);
static void updateDemoMode(
bool *demoMode,
SUsbScanResult *usbScanResult,
std::string *deviceStatus
);

/********************* Application Programming Interface *********************/

Expand Down Expand Up @@ -135,6 +147,9 @@ int main (void) {
bool channelEnabled[] = {true, true};
int timebase = 6;
int voltsPerDivision[] = {2, 2};
SUsbScanResult usbScanResult =
oscilloscope::usb::enumerateSupportedDevices();
std::string deviceStatus = formatUsbScanStatus(usbScanResult);
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 @@ -165,7 +180,15 @@ int main (void) {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("View")) {
ImGui::MenuItem("Demo mode", NULL, &demoMode);
bool newDemoMode = demoMode;

if (ImGui::MenuItem("Demo mode", NULL, &newDemoMode)) {
updateDemoMode(
&demoMode,
&usbScanResult,
&deviceStatus
);
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
Expand Down Expand Up @@ -211,7 +234,10 @@ int main (void) {
) {
acquisitionRunning = !acquisitionRunning;
}
ImGui::Checkbox("Demo mode", &demoMode);
if (ImGui::Button("Rescan devices", ImVec2(-1.0f, 32.0f))) {
usbScanResult = oscilloscope::usb::enumerateSupportedDevices();
deviceStatus = formatUsbScanStatus(usbScanResult);
}
ImGui::Separator();
ImGui::TextUnformatted("Horizontal");
ImGui::Combo("Timebase", &timebase, timebases, IM_ARRAYSIZE(timebases));
Expand All @@ -228,13 +254,20 @@ int main (void) {
);
ImGui::PopID();
}
ImGui::Separator();
bool newDemoMode = demoMode;

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

ImGui::SetCursorScreenPos(statusPosition);
ImGui::Text(
"%s | %s | CH1 %s | CH2 %s",
"%s | %s | %s | CH1 %s | CH2 %s",
acquisitionRunning ? "Acquiring" : "Stopped",
demoMode ? "Demo" : "No device",
demoMode ? "Demo mode" : "Live mode",
deviceStatus.c_str(),
channelEnabled[0] ? "on" : "off",
channelEnabled[1] ? "on" : "off"
);
Expand All @@ -261,6 +294,39 @@ int main (void) {
}
/***************************** Private functions *****************************/

static std::string formatUsbScanStatus(const SUsbScanResult &scanResult) {
std::string status;

if (scanResult.status == EScanStatus::eSuccess) {
if (scanResult.devices.empty()) {
status = "No supported device";
}
else {
status = scanResult.devices.front().modelName;
status += " detected";
}
}
else {
status = "USB error: ";
status += scanResult.errorMessage;
}

return status;
}

static void updateDemoMode(
bool *demoMode,
SUsbScanResult *usbScanResult,
std::string *deviceStatus
) {
*demoMode = !*demoMode;

if (!*demoMode) {
*usbScanResult = oscilloscope::usb::enumerateSupportedDevices();
*deviceStatus = formatUsbScanStatus(*usbScanResult);
}
}

static void drawOscilloscopeGrid(ImDrawList* drawList, const ImVec2& position,
const ImVec2& size) {
const ImU32 majorColor = IM_COL32(42, 75, 94, 255);
Expand Down
Empty file removed docs/.gitkeep
Empty file.
Loading
Loading