From 3ae72c3c8204bb8ed7d86dace737ff3fcff16dd3 Mon Sep 17 00:00:00 2001 From: jacobh460 Date: Mon, 27 Apr 2026 19:32:20 -0400 Subject: [PATCH 01/10] needs testing; created an abstract Serial class and WindowsSerial class that inherits the Serial class and implements Serial functionality --- UI/include/flight_data_state.h | 11 ++-- UI/include/platform_win.h | 6 +- UI/include/serial.h | 15 +++++ UI/src/flight_data.cpp | 45 ++++----------- UI/src/platform_win.cpp | 100 ++++++++++++++++++++++++++++----- UI/src/ui.cpp | 32 ++++++++++- 6 files changed, 152 insertions(+), 57 deletions(-) create mode 100644 UI/include/serial.h diff --git a/UI/include/flight_data_state.h b/UI/include/flight_data_state.h index aa11039..0e1103e 100644 --- a/UI/include/flight_data_state.h +++ b/UI/include/flight_data_state.h @@ -1,10 +1,12 @@ #ifndef ASTRA_GS_FLIGHT_DATA_STATE #define ASTRA_GS_FLIGHT_DATA_STATE +#include "serial.h" #include #include #include #include +#include #include @@ -23,10 +25,9 @@ struct flight_data_state_t { std::vector ports; int fv_serial_idx; // serial index for flight vehicle radio int rtk_serial_idx; // serial index for GPS RTK source - bool fv_serial_port_open; // used for opening/closing ports and communicating status back to user - bool rtk_serial_port_open; // used for opening/closing ports and communicating status back to user - HANDLE fv_serial = INVALID_HANDLE_VALUE; - HANDLE rtk_serial = INVALID_HANDLE_VALUE; + + std::unique_ptr fv_serial = 0; + std::unique_ptr rtk_serial = 0; // file input mode char selected_file_path[260] = ""; @@ -35,7 +36,7 @@ struct flight_data_state_t { int file_read_progress; bool file_reading_paused; - uint64_t replay_play_start_us; // the time when the play button was last pressed + uint64_t replay_play_start_us; // the time when the play button was last pressed uint64_t replay_pause_offset_us; // the time "accumulated" before the play button was last pressed }; diff --git a/UI/include/platform_win.h b/UI/include/platform_win.h index c8205e2..d0cff79 100644 --- a/UI/include/platform_win.h +++ b/UI/include/platform_win.h @@ -6,10 +6,8 @@ void OpenFileDialog(char *path); const char *get_filename_from_path(const char *full_path); std::vector enumerate_ports(); -void open_serial_port(HANDLE *hSerial, const char *com_port); -void close_serial_port(HANDLE *hSerial); -void read_from_serial_port(HANDLE *hSerial, bool *open_flag, char *read_buf, size_t MAX_READ_LEN, int *bytes_read); -void write_to_serial_port(HANDLE *hSerial, bool *open_flag, const char *msg, size_t len, bool end_with_newline); + +Serial* open_serial_port(const char* name); unsigned long long get_time_us(); void platform_begin(); diff --git a/UI/include/serial.h b/UI/include/serial.h new file mode 100644 index 0000000..217099f --- /dev/null +++ b/UI/include/serial.h @@ -0,0 +1,15 @@ +#pragma once +#include + +class Serial { +public: +virtual bool is_open() = 0; + +// return 0 on error +virtual int write(const char* data, unsigned int len, bool end_with_newline = false) = 0; + +// return # of bytes read, -1 on error +virtual int read(char* data, unsigned int max_len) = 0; + +virtual void close() = 0; +}; \ No newline at end of file diff --git a/UI/src/flight_data.cpp b/UI/src/flight_data.cpp index d4b1c01..1a6c193 100644 --- a/UI/src/flight_data.cpp +++ b/UI/src/flight_data.cpp @@ -20,8 +20,9 @@ void deinit_flight_data() { fclose(FlightDataState.input_file); FlightDataState.input_file = NULL; } - close_serial_port(&FlightDataState.fv_serial); - close_serial_port(&FlightDataState.rtk_serial); + + FlightDataState.fv_serial.reset(nullptr); + FlightDataState.rtk_serial.reset(nullptr); } void commit_packet() { @@ -141,32 +142,11 @@ int rtk_write_pos = 0; void flight_data_periodic() { if (FlightDataState.data_input_mode == MODE_SERIAL_INPUT) { - if (FlightDataState.fv_serial_port_open && FlightDataState.fv_serial == INVALID_HANDLE_VALUE && FlightDataState.fv_serial_idx < FlightDataState.ports.size()) { - open_serial_port(&FlightDataState.fv_serial, FlightDataState.ports[FlightDataState.fv_serial_idx].portName.c_str()); - if (FlightDataState.fv_serial == INVALID_HANDLE_VALUE) { - FlightDataState.fv_serial_port_open = false; // tell the user we failed to open the port - } - } - - if (FlightDataState.rtk_serial_port_open && FlightDataState.rtk_serial == INVALID_HANDLE_VALUE && FlightDataState.rtk_serial_idx < FlightDataState.ports.size()) { - open_serial_port(&FlightDataState.rtk_serial, FlightDataState.ports[FlightDataState.rtk_serial_idx].portName.c_str()); - if (FlightDataState.rtk_serial == INVALID_HANDLE_VALUE) { - FlightDataState.rtk_serial_port_open = false; // tell the user we failed to open the port - } - } - - if (!FlightDataState.fv_serial_port_open && FlightDataState.fv_serial != INVALID_HANDLE_VALUE) { - close_serial_port(&FlightDataState.fv_serial); - } - - if (!FlightDataState.rtk_serial_port_open && FlightDataState.rtk_serial != INVALID_HANDLE_VALUE) { - close_serial_port(&FlightDataState.rtk_serial); - } // RTK forwarding - if (FlightDataState.rtk_serial_port_open && FlightDataState.fv_serial_port_open) { - int rtk_bytes_read = 0; - read_from_serial_port(&FlightDataState.rtk_serial, &FlightDataState.rtk_serial_port_open, rtk_read_buf, RTK_READ_SIZE, &rtk_bytes_read); + if (FlightDataState.rtk_serial && FlightDataState.rtk_serial->is_open()) { + int rtk_bytes_read = FlightDataState.rtk_serial->read(rtk_read_buf, RTK_READ_SIZE); + if (rtk_bytes_read > 0) { for (int i = 0; i < rtk_bytes_read; i++) { if (rtk_read_buf[i] == ESCAPE_CHAR || rtk_read_buf[i] == END_CHAR || rtk_read_buf[i] == CR_CHAR || rtk_read_buf[i] == BACKSPACE_CHAR) { @@ -177,8 +157,8 @@ void flight_data_periodic() { rtk_write_pos++; if (rtk_write_pos >= RTK_WRITE_SIZE - 1) { // escape newlines and backslashes - write_to_serial_port(&FlightDataState.fv_serial, &FlightDataState.fv_serial_port_open, "rtk ", 4, false); // send "rtk " cmd - write_to_serial_port(&FlightDataState.fv_serial, &FlightDataState.fv_serial_port_open, rtk_write_buf, rtk_write_pos, true); // send data and newline suffix + FlightDataState.rtk_serial->write("rtk ", 4); + FlightDataState.rtk_serial->write(rtk_write_buf, rtk_write_pos, true); rtk_write_pos = 0; } } @@ -186,9 +166,8 @@ void flight_data_periodic() { } // Flight data parsing - if (FlightDataState.fv_serial_port_open) { - int fv_bytes_read = 0; - read_from_serial_port(&FlightDataState.fv_serial, &FlightDataState.fv_serial_port_open, fv_read_buf, FV_SERIAL_READ_SIZE, &fv_bytes_read); + if (FlightDataState.fv_serial && FlightDataState.fv_serial->is_open()) { + int fv_bytes_read = FlightDataState.fv_serial->read(fv_read_buf, FV_SERIAL_READ_SIZE); for (int i = 0; i < fv_bytes_read; i++) { flight_command_encode(fv_read_buf[i]); } @@ -216,11 +195,11 @@ void flight_data_periodic() { } void write_serial_to_fv(const char *msg) { - if (FlightDataState.data_input_mode == MODE_SERIAL_INPUT && FlightDataState.fv_serial_port_open) { + if (FlightDataState.data_input_mode == MODE_SERIAL_INPUT && FlightDataState.fv_serial && FlightDataState.fv_serial->is_open()) { size_t len = strlen(msg); if (len != 0) { // write to the flight vehicle, end with newline - write_to_serial_port(&FlightDataState.fv_serial, &FlightDataState.fv_serial_port_open, msg, len, true); + FlightDataState.fv_serial->write(msg, len, true); } } else { diff --git a/UI/src/platform_win.cpp b/UI/src/platform_win.cpp index 87136a5..416f392 100644 --- a/UI/src/platform_win.cpp +++ b/UI/src/platform_win.cpp @@ -12,10 +12,74 @@ #include // file path management #include // IFileOpenDialog +#include "serial.h" #include uint64_t lpFrequency; +void _open_serial_port(HANDLE *hSerial, const char *com_port); +void _close_serial_port(HANDLE *hSerial); +void _read_from_serial_port(HANDLE *hSerial, bool *open_flag, char *read_buf, size_t MAX_READ_LEN, int *bytes_read); +void _write_to_serial_port(HANDLE *hSerial, bool *open_flag, const char *msg, size_t len, bool end_with_newline); + +class WindowsSerial : Serial { +private: + HANDLE handle; + bool _is_open = false; + +public: + WindowsSerial(HANDLE h) + { + handle = h; + } + + bool is_open() { + return _is_open; + } + + int write(const char *data, unsigned int len, bool end_with_newline = false) { + if (!_is_open) { + return 0; + } + _write_to_serial_port(&handle, &_is_open, data, len, end_with_newline); + if (_is_open) { + return 1; + } + return 0; + } + + int read(char *data, unsigned int max_len) { + if (!_is_open) + { + return 0; + } + int bytes_read = -1; + _read_from_serial_port(&handle, &_is_open, data, max_len, &bytes_read); + + if (!_is_open) + { + return -1; + } + return bytes_read; + } + + void close() + { + if (!_is_open) + { + return; + } + _close_serial_port(&handle); + _is_open = false; + return; + } + + ~WindowsSerial() + { + close(); + } +}; + // will fill path if file is chosen // TODO - restrict file type void OpenFileDialog(char *path) { @@ -92,7 +156,19 @@ std::vector enumerate_ports() { return ports; } -void open_serial_port(HANDLE *hSerial, const char *com_port) { +Serial* open_serial_port(const char* name) +{ + HANDLE h; + _open_serial_port(&h, name); + if (h == INVALID_HANDLE_VALUE) + { + return nullptr; + } + // is this cursed idk? don't classes store deconstructor in their vtable? + return (Serial*)new WindowsSerial(h); +} + +void _open_serial_port(HANDLE *hSerial, const char *com_port) { char full_port_name[20]; snprintf(full_port_name, sizeof(full_port_name), "\\\\.\\%s", com_port); *hSerial = CreateFileA(full_port_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); @@ -114,7 +190,7 @@ void open_serial_port(HANDLE *hSerial, const char *com_port) { if (!SetCommState(*hSerial, &dcb)) { printf("SetCommState error while opening serial port.\n"); - close_serial_port(hSerial); + _close_serial_port(hSerial); } // --- set timeouts (non-blocking with small wait) --- @@ -124,13 +200,13 @@ void open_serial_port(HANDLE *hSerial, const char *com_port) { timeouts.ReadTotalTimeoutMultiplier = 0; if (!SetCommTimeouts(*hSerial, &timeouts)) { printf("SetCommTimeouts error while opening serial port.\n"); - close_serial_port(hSerial); + _close_serial_port(hSerial); } printf("Serial port opened!\n"); } -void close_serial_port(HANDLE *hSerial) { +void _close_serial_port(HANDLE *hSerial) { if (*hSerial != INVALID_HANDLE_VALUE) { printf("Serial port closed.\n"); CloseHandle(*hSerial); @@ -138,12 +214,12 @@ void close_serial_port(HANDLE *hSerial) { *hSerial = INVALID_HANDLE_VALUE; } -void read_from_serial_port(HANDLE *hSerial, bool *open_flag, char *read_buf, size_t MAX_READ_LEN, int *bytes_read) { +void _read_from_serial_port(HANDLE *hSerial, bool *open_flag, char *read_buf, size_t MAX_READ_LEN, int *bytes_read) { DWORD dword_bytes_read = 0; if (!ReadFile(*hSerial, read_buf, MAX_READ_LEN, &dword_bytes_read, NULL)) { printf("ReadFile error - closing serial port.\n"); - close_serial_port(hSerial); + _close_serial_port(hSerial); *open_flag = false; *bytes_read = 0; return; @@ -152,11 +228,11 @@ void read_from_serial_port(HANDLE *hSerial, bool *open_flag, char *read_buf, siz *bytes_read = dword_bytes_read; } -void write_to_serial_port(HANDLE *hSerial, bool *open_flag, const char *msg, size_t len, bool end_with_newline) { +void _write_to_serial_port(HANDLE *hSerial, bool *open_flag, const char *msg, size_t len, bool end_with_newline) { DWORD bytes_written = 0; if (!WriteFile(*hSerial, msg, len, &bytes_written, NULL)) { printf("WriteFile error - closing serial port.\n"); - close_serial_port(hSerial); + _close_serial_port(hSerial); *open_flag = false; return; } @@ -166,7 +242,7 @@ void write_to_serial_port(HANDLE *hSerial, bool *open_flag, const char *msg, siz if (!WriteFile(*hSerial, &newline, 1, &bytes_written, NULL)) { printf("WriteFile error - closing serial port.\n"); - close_serial_port(hSerial); + _close_serial_port(hSerial); *open_flag = false; return; } @@ -174,15 +250,13 @@ void write_to_serial_port(HANDLE *hSerial, bool *open_flag, const char *msg, siz } // get time in microseconds, not synced to any external reference -unsigned long long get_time_us() -{ +unsigned long long get_time_us() { LARGE_INTEGER temp; QueryPerformanceCounter(&temp); return temp.QuadPart * 1000000 / lpFrequency; // todo - is potential overflow here concerning? nahhh } -void platform_begin() -{ +void platform_begin() { LARGE_INTEGER temp; QueryPerformanceFrequency(&temp); // per windows docs - this value doesn't change and can be cached lpFrequency = temp.QuadPart; diff --git a/UI/src/ui.cpp b/UI/src/ui.cpp index 78edfae..549a904 100644 --- a/UI/src/ui.cpp +++ b/UI/src/ui.cpp @@ -317,7 +317,10 @@ void data_management_panel() { ComPortInfo port = FlightDataState.ports[i]; const bool is_selected = (FlightDataState.fv_serial_idx == i); if (ImGui::Selectable(port.friendlyName.c_str(), is_selected)) + { FlightDataState.fv_serial_idx = i; + FlightDataState.fv_serial.reset(nullptr); + } // Set the initial focus when opening the combo (scrolling to selection) if (is_selected) @@ -326,7 +329,18 @@ void data_management_panel() { ImGui::EndCombo(); } ImGui::SameLine(); - ImGui::Checkbox("##fv_serial_open", &FlightDataState.fv_serial_port_open); + bool fv_serial_open = FlightDataState.fv_serial && FlightDataState.fv_serial->is_open(); + if (ImGui::Checkbox("##fv_serial_open", &fv_serial_open)) + { + if (fv_serial_open) + { + FlightDataState.fv_serial.reset(open_serial_port(FlightDataState.ports[FlightDataState.fv_serial_idx].portName.c_str())); + } + else + { + FlightDataState.fv_serial.reset(nullptr); + } + } ImGui::SameLine(); if (ImGui::Button("\uE117")) { FlightDataState.ports = enumerate_ports(); @@ -339,7 +353,10 @@ void data_management_panel() { ComPortInfo port = FlightDataState.ports[i]; const bool is_selected = (FlightDataState.rtk_serial_idx == i); if (ImGui::Selectable(port.friendlyName.c_str(), is_selected)) + { FlightDataState.rtk_serial_idx = i; + FlightDataState.rtk_serial.reset(nullptr); + } // Set the initial focus when opening the combo (scrolling to selection) if (is_selected) @@ -348,7 +365,18 @@ void data_management_panel() { ImGui::EndCombo(); } ImGui::SameLine(); - ImGui::Checkbox("##rtk_serial_open", &FlightDataState.rtk_serial_port_open); + + bool rtk_serial_open = FlightDataState.rtk_serial && FlightDataState.rtk_serial->is_open(); + if (ImGui::Checkbox("##rtk_serial_open", &rtk_serial_open)) + { + if (rtk_serial_open) { + FlightDataState.rtk_serial.reset(open_serial_port(FlightDataState.ports[FlightDataState.rtk_serial_idx].portName.c_str())); + } + else + { + FlightDataState.rtk_serial.reset(nullptr); + } + } ImGui::Dummy(ImVec2(0, 25)); From 2578d52b54b54d1bba18affd7ca73d29dcc3bb69 Mon Sep 17 00:00:00 2001 From: jacobh460 Date: Tue, 28 Apr 2026 15:08:53 -0400 Subject: [PATCH 02/10] bugfix: WindowsSerial deconstructor not being called, WindowsSerial _is_open not being set to true --- UI/include/serial.h | 2 ++ UI/src/platform_win.cpp | 3 ++- UI/src/ui.cpp | 9 +++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/UI/include/serial.h b/UI/include/serial.h index 217099f..57433b6 100644 --- a/UI/include/serial.h +++ b/UI/include/serial.h @@ -12,4 +12,6 @@ virtual int write(const char* data, unsigned int len, bool end_with_newline = fa virtual int read(char* data, unsigned int max_len) = 0; virtual void close() = 0; + +virtual ~Serial() = default; // intended to be overwritten by inheriting class }; \ No newline at end of file diff --git a/UI/src/platform_win.cpp b/UI/src/platform_win.cpp index 416f392..31e1195 100644 --- a/UI/src/platform_win.cpp +++ b/UI/src/platform_win.cpp @@ -25,12 +25,13 @@ void _write_to_serial_port(HANDLE *hSerial, bool *open_flag, const char *msg, si class WindowsSerial : Serial { private: HANDLE handle; - bool _is_open = false; + bool _is_open; public: WindowsSerial(HANDLE h) { handle = h; + _is_open = true; } bool is_open() { diff --git a/UI/src/ui.cpp b/UI/src/ui.cpp index 549a904..13a3bc1 100644 --- a/UI/src/ui.cpp +++ b/UI/src/ui.cpp @@ -319,7 +319,7 @@ void data_management_panel() { if (ImGui::Selectable(port.friendlyName.c_str(), is_selected)) { FlightDataState.fv_serial_idx = i; - FlightDataState.fv_serial.reset(nullptr); + FlightDataState.fv_serial.reset(); } // Set the initial focus when opening the combo (scrolling to selection) @@ -332,13 +332,14 @@ void data_management_panel() { bool fv_serial_open = FlightDataState.fv_serial && FlightDataState.fv_serial->is_open(); if (ImGui::Checkbox("##fv_serial_open", &fv_serial_open)) { + printf("a\n"); if (fv_serial_open) { FlightDataState.fv_serial.reset(open_serial_port(FlightDataState.ports[FlightDataState.fv_serial_idx].portName.c_str())); } else { - FlightDataState.fv_serial.reset(nullptr); + FlightDataState.fv_serial.reset(); } } ImGui::SameLine(); @@ -355,7 +356,7 @@ void data_management_panel() { if (ImGui::Selectable(port.friendlyName.c_str(), is_selected)) { FlightDataState.rtk_serial_idx = i; - FlightDataState.rtk_serial.reset(nullptr); + FlightDataState.rtk_serial.reset(); } // Set the initial focus when opening the combo (scrolling to selection) @@ -374,7 +375,7 @@ void data_management_panel() { } else { - FlightDataState.rtk_serial.reset(nullptr); + FlightDataState.rtk_serial.reset(); } } From 65d9430d30a271040a9507eef99f809d103b9b4c Mon Sep 17 00:00:00 2001 From: jacobh460 Date: Tue, 28 Apr 2026 14:47:57 -0400 Subject: [PATCH 03/10] modular PortSelector UI component --- UI/Makefile | 2 +- UI/include/flight_data_state.h | 11 ++-- UI/include/port_selector.h | 33 ++++++++++++ UI/src/flight_data.cpp | 20 +++---- UI/src/port_selector.cpp | 95 ++++++++++++++++++++++++++++++++++ UI/src/ui.cpp | 85 ++++-------------------------- 6 files changed, 156 insertions(+), 90 deletions(-) create mode 100644 UI/include/port_selector.h create mode 100644 UI/src/port_selector.cpp diff --git a/UI/Makefile b/UI/Makefile index 34b2fb9..c7e38a9 100644 --- a/UI/Makefile +++ b/UI/Makefile @@ -17,7 +17,7 @@ IMGUI_SRC := imgui\imgui.cpp \ implot3d\implot3d.cpp \ implot3d\implot3d_items.cpp -APP_SRC := main.cpp ui.cpp ui_components.cpp ui_graphs.cpp flight_data.cpp platform_win.cpp flight_commands.cpp +APP_SRC := main.cpp ui.cpp ui_components.cpp ui_graphs.cpp flight_data.cpp platform_win.cpp flight_commands.cpp port_selector.cpp SRC := $(APP_SRC) $(IMGUI_SRC) BUILD_FOLDER := build diff --git a/UI/include/flight_data_state.h b/UI/include/flight_data_state.h index 0e1103e..7f245f3 100644 --- a/UI/include/flight_data_state.h +++ b/UI/include/flight_data_state.h @@ -1,12 +1,13 @@ #ifndef ASTRA_GS_FLIGHT_DATA_STATE #define ASTRA_GS_FLIGHT_DATA_STATE +#include "port_selector.h" #include "serial.h" +#include #include #include #include #include -#include #include @@ -23,11 +24,11 @@ struct flight_data_state_t { // serial input mode std::vector ports; - int fv_serial_idx; // serial index for flight vehicle radio - int rtk_serial_idx; // serial index for GPS RTK source + int fv_serial_idx; // serial index for flight vehicle radio + int rtk_serial_idx; // serial index for GPS RTK source - std::unique_ptr fv_serial = 0; - std::unique_ptr rtk_serial = 0; + PortSelector fv_serial = PortSelector("Vehicle: ", "##fv_serial_picker", "##fv_serial_open"); + PortSelector rtk_serial = PortSelector("RTK: ", "##rtk_serial_picker", "##rtk_serial_open"); // file input mode char selected_file_path[260] = ""; diff --git a/UI/include/port_selector.h b/UI/include/port_selector.h new file mode 100644 index 0000000..1aae3e1 --- /dev/null +++ b/UI/include/port_selector.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Serial.h" + +class PortSelector { +public: + PortSelector(const char *name, const char *combo_id, const char *checkbox_id); + + void render(); + + bool is_open(); + + void close(); + + ~PortSelector(); + + // return 0 on error + int write(const char *data, unsigned int len, bool end_with_newline = false); + + // return # of bytes read, -1 on error + int read(char *data, unsigned int max_len); + +private: + Serial *active_port = nullptr; + char *_name; + char *_combo_id; + char *_checkbox_id; + int _index = 0; +}; + + +// renders a refresh button for refreshing the available ports list in FlightDataState +void render_port_refresh_button(); \ No newline at end of file diff --git a/UI/src/flight_data.cpp b/UI/src/flight_data.cpp index 1a6c193..000ec39 100644 --- a/UI/src/flight_data.cpp +++ b/UI/src/flight_data.cpp @@ -21,8 +21,8 @@ void deinit_flight_data() { FlightDataState.input_file = NULL; } - FlightDataState.fv_serial.reset(nullptr); - FlightDataState.rtk_serial.reset(nullptr); + FlightDataState.fv_serial.close(); + FlightDataState.rtk_serial.close(); } void commit_packet() { @@ -144,8 +144,8 @@ void flight_data_periodic() { if (FlightDataState.data_input_mode == MODE_SERIAL_INPUT) { // RTK forwarding - if (FlightDataState.rtk_serial && FlightDataState.rtk_serial->is_open()) { - int rtk_bytes_read = FlightDataState.rtk_serial->read(rtk_read_buf, RTK_READ_SIZE); + if (FlightDataState.rtk_serial.is_open()) { + int rtk_bytes_read = FlightDataState.rtk_serial.read(rtk_read_buf, RTK_READ_SIZE); if (rtk_bytes_read > 0) { for (int i = 0; i < rtk_bytes_read; i++) { @@ -157,8 +157,8 @@ void flight_data_periodic() { rtk_write_pos++; if (rtk_write_pos >= RTK_WRITE_SIZE - 1) { // escape newlines and backslashes - FlightDataState.rtk_serial->write("rtk ", 4); - FlightDataState.rtk_serial->write(rtk_write_buf, rtk_write_pos, true); + FlightDataState.rtk_serial.write("rtk ", 4); + FlightDataState.rtk_serial.write(rtk_write_buf, rtk_write_pos, true); rtk_write_pos = 0; } } @@ -166,8 +166,8 @@ void flight_data_periodic() { } // Flight data parsing - if (FlightDataState.fv_serial && FlightDataState.fv_serial->is_open()) { - int fv_bytes_read = FlightDataState.fv_serial->read(fv_read_buf, FV_SERIAL_READ_SIZE); + if (FlightDataState.fv_serial.is_open()) { + int fv_bytes_read = FlightDataState.fv_serial.read(fv_read_buf, FV_SERIAL_READ_SIZE); for (int i = 0; i < fv_bytes_read; i++) { flight_command_encode(fv_read_buf[i]); } @@ -195,11 +195,11 @@ void flight_data_periodic() { } void write_serial_to_fv(const char *msg) { - if (FlightDataState.data_input_mode == MODE_SERIAL_INPUT && FlightDataState.fv_serial && FlightDataState.fv_serial->is_open()) { + if (FlightDataState.data_input_mode == MODE_SERIAL_INPUT && FlightDataState.fv_serial.is_open()) { size_t len = strlen(msg); if (len != 0) { // write to the flight vehicle, end with newline - FlightDataState.fv_serial->write(msg, len, true); + FlightDataState.fv_serial.write(msg, len, true); } } else { diff --git a/UI/src/port_selector.cpp b/UI/src/port_selector.cpp new file mode 100644 index 0000000..4da8498 --- /dev/null +++ b/UI/src/port_selector.cpp @@ -0,0 +1,95 @@ +#include "port_selector.h" +#include "flight_data_state.h" +#include "imgui.h" +#include "platform_win.h" + +PortSelector::PortSelector(const char *name, const char *combo_id, const char *checkbox_id) { + // copy in case they point to volatile data + _name = (char *)malloc(strlen(name) + 1); + strcpy(_name, name); + + _combo_id = (char *)malloc(strlen(combo_id) + 1); + strcpy(_combo_id, combo_id); + + _checkbox_id = (char *)malloc(strlen(checkbox_id) + 1); + strcpy(_checkbox_id, checkbox_id); +} + +void PortSelector::render() { + // port name + ImGui::Text(_name); + ImGui::SameLine(150); + // port dropdown + + if (ImGui::BeginCombo(_combo_id, _index < FlightDataState.ports.size() ? FlightDataState.ports[_index].friendlyName.c_str() : "No Serial Ports Found")) { + for (int i = 0; i < FlightDataState.ports.size(); i++) { + ComPortInfo port = FlightDataState.ports[i]; + const bool is_selected = (_index == i); + if (ImGui::Selectable(port.friendlyName.c_str(), is_selected)) { + _index = i; + close(); + } + + // Set the initial focus when opening the combo (scrolling to selection) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + // port checkbox + ImGui::SameLine(); + + bool enabled = is_open(); + if (ImGui::Checkbox(_checkbox_id, &enabled)) { + if (!enabled) { + close(); + } else if (_index < FlightDataState.ports.size()) { + active_port = open_serial_port(FlightDataState.ports[_index].portName.c_str()); + } + } + + return; +} + +void PortSelector::close() { + delete active_port; + active_port = nullptr; +} + +bool PortSelector::is_open() { + return active_port && active_port->is_open(); +} + +// return 0 on error +int PortSelector::write(const char *data, unsigned int len, bool end_with_newline) { + if (!is_open()) { + return 0; + } + + return active_port->write(data, len, end_with_newline); +} + +// return # of bytes read, -1 on error +int PortSelector::read(char *data, unsigned int max_len) { + if (!is_open()) { + return 0; + } + return active_port->read(data, max_len); +} + +PortSelector::~PortSelector() { + free(_name); + free(_combo_id); + free(_checkbox_id); + return; +} + + +// multiple panels will eventually need this component so it's placed here +void render_port_refresh_button() +{ + if (ImGui::Button("\uE117")) { + FlightDataState.ports = enumerate_ports(); + } +} \ No newline at end of file diff --git a/UI/src/ui.cpp b/UI/src/ui.cpp index 13a3bc1..faf1f7e 100644 --- a/UI/src/ui.cpp +++ b/UI/src/ui.cpp @@ -296,88 +296,25 @@ void data_management_panel() { ImGui::SameLine(); ImGui::RadioButton("File Input (Flight Replay)", &FlightDataState.data_input_mode, 1); + if (FlightDataState.ports.size() == 0) + { + FlightDataState.ports = enumerate_ports(); + } + if (FlightDataState.data_input_mode == MODE_SERIAL_INPUT) { ImGui::PushFont(panel_header_font); ImGui::SeparatorText("Serial Monitor"); ImGui::PopFont(); - if (FlightDataState.ports.size() == 0) { - FlightDataState.ports = enumerate_ports(); // TODO - this could re-arrange the user's selections - make sure we preserve when possible - if (FlightDataState.ports.size() == 0) { - - FlightDataState.ports.push_back({"", "No Serial Ports Found"}); - } - } - - ImGui::Text("Vehicle: "); - ImGui::SameLine(150); - if (ImGui::BeginCombo("##fv_serial_picker", FlightDataState.ports[FlightDataState.fv_serial_idx].friendlyName.c_str())) { - for (int i = 0; i < FlightDataState.ports.size(); i++) { - ComPortInfo port = FlightDataState.ports[i]; - const bool is_selected = (FlightDataState.fv_serial_idx == i); - if (ImGui::Selectable(port.friendlyName.c_str(), is_selected)) - { - FlightDataState.fv_serial_idx = i; - FlightDataState.fv_serial.reset(); - } - - // Set the initial focus when opening the combo (scrolling to selection) - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - ImGui::SameLine(); - bool fv_serial_open = FlightDataState.fv_serial && FlightDataState.fv_serial->is_open(); - if (ImGui::Checkbox("##fv_serial_open", &fv_serial_open)) - { - printf("a\n"); - if (fv_serial_open) - { - FlightDataState.fv_serial.reset(open_serial_port(FlightDataState.ports[FlightDataState.fv_serial_idx].portName.c_str())); - } - else - { - FlightDataState.fv_serial.reset(); - } - } - ImGui::SameLine(); - if (ImGui::Button("\uE117")) { - FlightDataState.ports = enumerate_ports(); - } - - ImGui::Text("RTK: "); - ImGui::SameLine(150); - if (ImGui::BeginCombo("##rtk_serial_picker", FlightDataState.ports[FlightDataState.rtk_serial_idx].friendlyName.c_str())) { - for (int i = 0; i < FlightDataState.ports.size(); i++) { - ComPortInfo port = FlightDataState.ports[i]; - const bool is_selected = (FlightDataState.rtk_serial_idx == i); - if (ImGui::Selectable(port.friendlyName.c_str(), is_selected)) - { - FlightDataState.rtk_serial_idx = i; - FlightDataState.rtk_serial.reset(); - } - - // Set the initial focus when opening the combo (scrolling to selection) - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } + // render port selections + FlightDataState.fv_serial.render(); + ImGui::SameLine(); + // render port refresh button + render_port_refresh_button(); - bool rtk_serial_open = FlightDataState.rtk_serial && FlightDataState.rtk_serial->is_open(); - if (ImGui::Checkbox("##rtk_serial_open", &rtk_serial_open)) - { - if (rtk_serial_open) { - FlightDataState.rtk_serial.reset(open_serial_port(FlightDataState.ports[FlightDataState.rtk_serial_idx].portName.c_str())); - } - else - { - FlightDataState.rtk_serial.reset(); - } - } + FlightDataState.rtk_serial.render(); ImGui::Dummy(ImVec2(0, 25)); From ce3163466541831d06d7828cec3fa6c92cc309f6 Mon Sep 17 00:00:00 2001 From: jacobh460 Date: Tue, 28 Apr 2026 16:47:19 -0400 Subject: [PATCH 04/10] added tested menu for dumping flash (has been tested), added SaveFileDialog with file type options --- UI/Makefile | 2 +- UI/include/flash_dump.h | 21 ++++++ UI/include/flight_data_state.h | 1 + UI/include/platform_win.h | 5 +- UI/src/flash_dump.cpp | 120 +++++++++++++++++++++++++++++++++ UI/src/platform_win.cpp | 74 ++++++++++++++++++++ UI/src/port_selector.cpp | 5 +- UI/src/ui.cpp | 15 ++++- 8 files changed, 236 insertions(+), 7 deletions(-) create mode 100644 UI/include/flash_dump.h create mode 100644 UI/src/flash_dump.cpp diff --git a/UI/Makefile b/UI/Makefile index c7e38a9..c13cebe 100644 --- a/UI/Makefile +++ b/UI/Makefile @@ -17,7 +17,7 @@ IMGUI_SRC := imgui\imgui.cpp \ implot3d\implot3d.cpp \ implot3d\implot3d_items.cpp -APP_SRC := main.cpp ui.cpp ui_components.cpp ui_graphs.cpp flight_data.cpp platform_win.cpp flight_commands.cpp port_selector.cpp +APP_SRC := main.cpp ui.cpp ui_components.cpp ui_graphs.cpp flight_data.cpp platform_win.cpp flight_commands.cpp port_selector.cpp flash_dump.cpp SRC := $(APP_SRC) $(IMGUI_SRC) BUILD_FOLDER := build diff --git a/UI/include/flash_dump.h b/UI/include/flash_dump.h new file mode 100644 index 0000000..f77a6e6 --- /dev/null +++ b/UI/include/flash_dump.h @@ -0,0 +1,21 @@ +#pragma once + + +// abstract: +// state variable +// button for beginning dump +// - prompt user for output file path +// - sends start command over fv serial +// - dump over selected dump serial, if none is selected then dump over fv serial +// upon completion, switch to CSR window +// - pre-fill the open file path with dump output +namespace FlashDump +{ + bool is_in_progress(); + + void render(); + + // separate update and render functions exposed so that the dump can continue even if dump menu is closed + // should be called unconditionally every frame + void update(); +}; \ No newline at end of file diff --git a/UI/include/flight_data_state.h b/UI/include/flight_data_state.h index 7f245f3..7689456 100644 --- a/UI/include/flight_data_state.h +++ b/UI/include/flight_data_state.h @@ -18,6 +18,7 @@ struct ComPortInfo { #define MODE_SERIAL_INPUT 0 #define MODE_FILE_INPUT 1 +#define MODE_FLASH_DUMP 2 struct flight_data_state_t { int data_input_mode; diff --git a/UI/include/platform_win.h b/UI/include/platform_win.h index d0cff79..2854436 100644 --- a/UI/include/platform_win.h +++ b/UI/include/platform_win.h @@ -7,8 +7,11 @@ void OpenFileDialog(char *path); const char *get_filename_from_path(const char *full_path); std::vector enumerate_ports(); -Serial* open_serial_port(const char* name); +Serial *open_serial_port(const char *name); unsigned long long get_time_us(); void platform_begin(); +void SaveFileDialog(char *path); +void SaveFileDialog(char *path, const char *file_specs[], const char *spec_names[], unsigned int n_specs, unsigned int default_spec_idx); + #endif \ No newline at end of file diff --git a/UI/src/flash_dump.cpp b/UI/src/flash_dump.cpp new file mode 100644 index 0000000..aa74fe9 --- /dev/null +++ b/UI/src/flash_dump.cpp @@ -0,0 +1,120 @@ +#include "flash_dump.h" +#include "flight_data_state.h" +#include "imgui.h" +#include "platform_win.h" +#include "port_selector.h" + +namespace FlashDump { +bool _in_progress = false; +const char *result_message = nullptr; + +unsigned int bytes_downloaded = 0; + +PortSelector port_selector("Flash: ", "##dump_combo", "##dump_checkbox"); + +PortSelector &read_port = port_selector; + +FILE *fout; + +char out_path[MAX_PATH + 1]; + +const unsigned int PAGE_SIZE = 256; +uint8_t recv_buf[PAGE_SIZE + 3]; +unsigned int recv_buf_pos = 0; + +bool is_in_progress() { + return _in_progress; +} + +void finish() { + _in_progress = false; + fclose(fout); +} + +void update() { + if (!is_in_progress()) { + return; + } + + if (!read_port.is_open()) { + result_message = "ERROR: PORT CLOSED"; + finish(); + } + + recv_buf_pos += read_port.read((char *)(recv_buf + recv_buf_pos), sizeof(recv_buf) - recv_buf_pos); + + if (recv_buf_pos == sizeof(recv_buf)) { + // checksum calculation + uint8_t cs_A = 0; + uint8_t cs_B = 0; + for (int i = 0; i < PAGE_SIZE; ++i) { + cs_A += recv_buf[i]; + cs_B += cs_A; + } + + if (cs_A != recv_buf[PAGE_SIZE] || cs_B != recv_buf[PAGE_SIZE + 1]) { + result_message = "ERROR: CHECKSUM FAILED"; + finish(); + return; + } + + bytes_downloaded += PAGE_SIZE; + + fwrite(recv_buf, PAGE_SIZE, 1, fout); + + if (recv_buf[PAGE_SIZE + 2] == 'k') { + result_message = "Finished Successfully"; + finish(); + return; + } + + read_port.write("c", 1, false); + recv_buf_pos = 0; + } + + return; +} + +void render() { + if (is_in_progress()) { + // render dump progress (how many bytes downloaded) + ImGui::Text("%u BYTES DOWNLOADED", bytes_downloaded); + } else { + + // serial port selections + FlightDataState.fv_serial.render(); // fv serial selector + ImGui::SameLine(); + render_port_refresh_button(); // port refresh button + + port_selector.render(); + + if (FlightDataState.fv_serial.is_open() && ImGui::Button("Begin")) { + static const char *specs[] = {"*.bin", "*.*"}; + static const char *labels[] = {"Binary File", "All Files"}; + SaveFileDialog(out_path, specs, labels, 2, 0); + + fout = fopen(out_path, "wb"); + + if (fout) { + // begin by sending start command + const char start_cmd[] = "dump_flash"; + if (FlightDataState.fv_serial.write(start_cmd, sizeof(start_cmd) - 1, true)) { + // if send succeeded, switch to in-progress state + _in_progress = true; + recv_buf_pos = 0; + bytes_downloaded = 0; + + // if dump port hasn't been chosen, assume we are dumping over fv serial + read_port = port_selector.is_open() ? port_selector : FlightDataState.fv_serial; + } else { + fclose(fout); + } + } + } + + if (result_message) { + ImGui::Text("%s", result_message); + } + } +} +}; // namespace FlashDump \ No newline at end of file diff --git a/UI/src/platform_win.cpp b/UI/src/platform_win.cpp index 31e1195..8b6cf4f 100644 --- a/UI/src/platform_win.cpp +++ b/UI/src/platform_win.cpp @@ -121,6 +121,80 @@ void OpenFileDialog(char *path) { CoUninitialize(); } +void SaveFileDialog(char *path, const char *file_specs[], const char *spec_names[], unsigned int n_specs, unsigned int default_spec_idx) { + path[0] = '\0'; + + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + if (FAILED(hr)) { + return; + } + + IFileSaveDialog *pFileSave = nullptr; + + hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL, IID_IFileSaveDialog, reinterpret_cast(&pFileSave)); + + // convert types to array of win32 structs + COMDLG_FILTERSPEC *filter_spec = (COMDLG_FILTERSPEC *)malloc(n_specs * sizeof(*filter_spec)); + + for (int i = 0; i < n_specs; ++i) { + int n = MultiByteToWideChar(CP_UTF8, 0, spec_names[i], -1, (LPWSTR)filter_spec[i].pszName, 0); + filter_spec[i].pszName = (LPCWSTR)malloc(n * sizeof(*(filter_spec[i].pszName))); + MultiByteToWideChar(CP_UTF8, 0, spec_names[i], -1, (LPWSTR)filter_spec[i].pszName, n); + + n = MultiByteToWideChar(CP_UTF8, 0, file_specs[i], -1, (LPWSTR)filter_spec[i].pszSpec, 0); + filter_spec[i].pszSpec = (LPCWSTR)malloc(n * sizeof(*(filter_spec[i].pszSpec))); + MultiByteToWideChar(CP_UTF8, 0, file_specs[i], -1, (LPWSTR)filter_spec[i].pszSpec, n); + } + + hr = pFileSave->SetFileTypes(n_specs, filter_spec); + + if (SUCCEEDED(hr)) { + hr = pFileSave->SetFileTypeIndex(default_spec_idx); + if (SUCCEEDED(hr)) { + hr = pFileSave->SetDefaultExtension(filter_spec[default_spec_idx].pszSpec); + if (SUCCEEDED(hr)) { + hr = pFileSave->Show(NULL); + + if (SUCCEEDED(hr)) { + IShellItem *pItem; + hr = pFileSave->GetResult(&pItem); + + if (SUCCEEDED(hr)) { + PWSTR pszFilePath = nullptr; + hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath); + + if (SUCCEEDED(hr)) { + WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, path, MAX_PATH, NULL, NULL); + CoTaskMemFree(pszFilePath); + } + + pItem->Release(); + } + } + + pFileSave->Release(); + } + + CoUninitialize(); + } + } + + for (int i = 0; i < n_specs; ++i) { + free((void *)filter_spec[i].pszName); + free((void *)filter_spec[i].pszSpec); + } + + free(filter_spec); + return; +} + +void SaveFileDialog(char *path) { + static const char *default_spec[] = {"*.*"}; + static const char *default_label[] = {"any"}; + SaveFileDialog(path, default_spec, default_label, 1, 0); + return; +} + const char *get_filename_from_path(const char *full_path) { const char *filename = PathFindFileNameA(full_path); return filename; diff --git a/UI/src/port_selector.cpp b/UI/src/port_selector.cpp index 4da8498..22c08b6 100644 --- a/UI/src/port_selector.cpp +++ b/UI/src/port_selector.cpp @@ -73,9 +73,10 @@ int PortSelector::write(const char *data, unsigned int len, bool end_with_newlin // return # of bytes read, -1 on error int PortSelector::read(char *data, unsigned int max_len) { if (!is_open()) { - return 0; + return -1; } - return active_port->read(data, max_len); + int read_count = active_port->read(data, max_len); + return read_count; } PortSelector::~PortSelector() { diff --git a/UI/src/ui.cpp b/UI/src/ui.cpp index faf1f7e..c13f90f 100644 --- a/UI/src/ui.cpp +++ b/UI/src/ui.cpp @@ -7,6 +7,7 @@ #include "platform_win.h" #include "ui_components.h" #include "ui_graphs.h" +#include "flash_dump.h" bool text_box_active; @@ -292,15 +293,19 @@ char concat_msg_buf[1000]; void data_management_panel() { ImGui::Begin(DATA_MANAGEMENT_PANEL); - ImGui::RadioButton("Serial Input (Live Monitoring)", &FlightDataState.data_input_mode, 0); + ImGui::RadioButton("Serial Input (Live Monitoring)", &FlightDataState.data_input_mode, MODE_SERIAL_INPUT); ImGui::SameLine(); - ImGui::RadioButton("File Input (Flight Replay)", &FlightDataState.data_input_mode, 1); + ImGui::RadioButton("File Input (Flight Replay)", &FlightDataState.data_input_mode, MODE_FILE_INPUT); + ImGui::SameLine(); + ImGui::RadioButton("Flash", &FlightDataState.data_input_mode, MODE_FLASH_DUMP); if (FlightDataState.ports.size() == 0) { FlightDataState.ports = enumerate_ports(); } + FlashDump::update(); + if (FlightDataState.data_input_mode == MODE_SERIAL_INPUT) { ImGui::PushFont(panel_header_font); @@ -356,7 +361,7 @@ void data_management_panel() { // ImFormatStringToTempBuffer(&child_window_name, NULL, "%s/%s_%08X", g.CurrentWindow->Name, "##serial_output", ImGui::GetID("##serial_output")); // ImGuiWindow *child_window = ImGui::FindWindowByName(child_window_name); // ImGui::SetScrollY(child_window, child_window->ScrollMax.y); - } else { + } else if (FlightDataState.data_input_mode == MODE_FILE_INPUT) { ImGui::PushFont(panel_header_font); ImGui::SeparatorText("Flight Replay"); ImGui::PopFont(); @@ -397,6 +402,10 @@ void data_management_panel() { } } } + else + { + FlashDump::render(); + } ImGui::End(); } From 569c871bb11bc30f4b1beffe363d3c83cb861e2b Mon Sep 17 00:00:00 2001 From: jacobh460 Date: Tue, 28 Apr 2026 19:25:33 -0400 Subject: [PATCH 05/10] CSR takes file paths as arguments, flash dump sends first page without waiting for 'c', added CSR menu to UI that triggers a build of CSR and calls CSR executable, --- UI/Makefile | 2 +- UI/include/csr_trigger.h | 21 +++ UI/include/flight_data_state.h | 1 + UI/include/platform_win.h | 16 +++ UI/src/csr_trigger.cpp | 126 ++++++++++++++++++ UI/src/flash_dump.cpp | 7 +- UI/src/platform_win.cpp | 89 ++++++++++--- UI/src/ui.cpp | 16 ++- .../controller_state_reconstruction.cpp | 11 +- lib/trajectory_following/TrajectoryLogger.cpp | 27 ++-- 10 files changed, 280 insertions(+), 36 deletions(-) create mode 100644 UI/include/csr_trigger.h create mode 100644 UI/src/csr_trigger.cpp diff --git a/UI/Makefile b/UI/Makefile index c13cebe..83b9b93 100644 --- a/UI/Makefile +++ b/UI/Makefile @@ -17,7 +17,7 @@ IMGUI_SRC := imgui\imgui.cpp \ implot3d\implot3d.cpp \ implot3d\implot3d_items.cpp -APP_SRC := main.cpp ui.cpp ui_components.cpp ui_graphs.cpp flight_data.cpp platform_win.cpp flight_commands.cpp port_selector.cpp flash_dump.cpp +APP_SRC := main.cpp ui.cpp ui_components.cpp ui_graphs.cpp flight_data.cpp platform_win.cpp flight_commands.cpp port_selector.cpp flash_dump.cpp csr_trigger.cpp SRC := $(APP_SRC) $(IMGUI_SRC) BUILD_FOLDER := build diff --git a/UI/include/csr_trigger.h b/UI/include/csr_trigger.h new file mode 100644 index 0000000..d94ff63 --- /dev/null +++ b/UI/include/csr_trigger.h @@ -0,0 +1,21 @@ +#pragma once +#include "platform_win.h" + +// abstract: +// - file input for flight log +// - needs to trigger CSR by first calling make (to ensure csr executable is updated) +// - fail if CSR build fails +// - call csr executable +// - pass flight log file path, output file path, and optional csv file path +// - wait for csr to complete +// - fail if csr gives error code +// - switch to flight replay window and set the input file path to the csr output +namespace CSRTrigger { +extern char input_fp[MAX_PATH]; +void render(); + +bool in_progress(); + +void update(); + +}; // namespace CSRTrigger \ No newline at end of file diff --git a/UI/include/flight_data_state.h b/UI/include/flight_data_state.h index 7689456..91257c0 100644 --- a/UI/include/flight_data_state.h +++ b/UI/include/flight_data_state.h @@ -19,6 +19,7 @@ struct ComPortInfo { #define MODE_SERIAL_INPUT 0 #define MODE_FILE_INPUT 1 #define MODE_FLASH_DUMP 2 +#define MODE_CSR_TRIGGER 3 struct flight_data_state_t { int data_input_mode; diff --git a/UI/include/platform_win.h b/UI/include/platform_win.h index 2854436..ebe6eeb 100644 --- a/UI/include/platform_win.h +++ b/UI/include/platform_win.h @@ -14,4 +14,20 @@ void platform_begin(); void SaveFileDialog(char *path); void SaveFileDialog(char *path, const char *file_specs[], const char *spec_names[], unsigned int n_specs, unsigned int default_spec_idx); +// triggers a build of the csr code +void* spawn_csr_make(); + +// spawns csr process +void* spawn_csr(const char* input_fp, const char* output_fp, const char* output_csv_fp = nullptr); + + +struct Process_Status +{ + bool running; + int exit_code; +}; +Process_Status check_process_status(void *handle); + +void close_process(void* handle); + #endif \ No newline at end of file diff --git a/UI/src/csr_trigger.cpp b/UI/src/csr_trigger.cpp new file mode 100644 index 0000000..87f1a92 --- /dev/null +++ b/UI/src/csr_trigger.cpp @@ -0,0 +1,126 @@ +#include "csr_trigger.h" +#include "flight_data_state.h" +#include "imgui.h" +#include "platform_win.h" +#include "flight_data.h" + +namespace CSRTrigger { +bool _in_progress = false; + +char input_fp[MAX_PATH]{}; +char output_fp[MAX_PATH]{}; +char output_csv_fp[MAX_PATH]{}; + +bool output_csv = false; + +enum Stage { MAKE = 0, CSR = 1 }; + +Stage stage = Stage::MAKE; +void *current_handle = nullptr; +char *success_message = nullptr; + +void render() { + if (in_progress()) { + // just display a status + if (stage == Stage::MAKE) { + ImGui::Text("Building CSR..."); + } else { + ImGui::Text("Running CSR..."); + } + } else { + // select input file + if (ImGui::Button("Choose Input File")) { + OpenFileDialog(input_fp); + } + + if (input_fp[0]) { + ImGui::Text("%s", get_filename_from_path(input_fp)); + } + + // select output file + if (ImGui::Button("Choose CSR Output")) { + const char *specs[] = {"*.bin", "*.*"}; + const char *labels[] = {"Binary File", "All Files"}; + SaveFileDialog(output_fp, specs, labels, 2, 0); + } + + if (output_fp[0]) { + ImGui::Text("%s", get_filename_from_path(output_fp)); + } + + // optional - select csv output file + ImGui::Checkbox("Output CSV", &output_csv); + + if (output_csv) { + if (ImGui::Button("Choose CSV Output")) { + const char *specs[] = {"*.csv", "*.*"}; + const char *labels[] = {"Comma Separated Values File", "All Files"}; + SaveFileDialog(output_csv_fp, specs, labels, 2, 0); + } + + if (output_csv_fp[0]) { + ImGui::Text("%s", get_filename_from_path(output_csv_fp)); + } + } + + if (input_fp[0] && output_fp[0] && (!output_csv || output_csv_fp[0])) { + if (ImGui::Button("Begin CSR")) { + _in_progress = true; + + stage = Stage::MAKE; + // trigger a CSR build + current_handle = spawn_csr_make(); + } + } + + if (success_message) { + ImGui::Text("%s", success_message); + } + } +} + +bool in_progress() { + return _in_progress; +} + +void update() { + if (!in_progress()) { + return; + } + + Process_Status status = check_process_status(current_handle); + + if (status.running) { + return; + } + + close_process(current_handle); + current_handle = nullptr; + + if (stage == Stage::MAKE) { + if (status.exit_code == 0) // build succeeded + { + stage = Stage::CSR; + current_handle = spawn_csr(input_fp, output_fp, output_csv ? output_csv_fp : nullptr); + } else { + success_message = "ERROR: BUILD FAILED"; + _in_progress = false; + return; + } + } else { // stage == Stage::CSR + if (status.exit_code == 0) // csr succeeded + { + success_message = "CSR Finished Successfully"; + + FlightDataState.data_input_mode = MODE_FILE_INPUT; + strcpy(FlightDataState.selected_file_path, output_fp); + load_flight_replay(); + } else { + success_message = "ERROR: CSR FAILED"; + } + _in_progress = false; + return; + } +} + +}; // namespace CSRTrigger \ No newline at end of file diff --git a/UI/src/flash_dump.cpp b/UI/src/flash_dump.cpp index aa74fe9..23d7bc1 100644 --- a/UI/src/flash_dump.cpp +++ b/UI/src/flash_dump.cpp @@ -3,6 +3,7 @@ #include "imgui.h" #include "platform_win.h" #include "port_selector.h" +#include "csr_trigger.h" namespace FlashDump { bool _in_progress = false; @@ -16,7 +17,7 @@ PortSelector &read_port = port_selector; FILE *fout; -char out_path[MAX_PATH + 1]; +char out_path[MAX_PATH]; const unsigned int PAGE_SIZE = 256; uint8_t recv_buf[PAGE_SIZE + 3]; @@ -65,6 +66,10 @@ void update() { if (recv_buf[PAGE_SIZE + 2] == 'k') { result_message = "Finished Successfully"; finish(); + + // automatically switch to csr menu and set input path to the dump output + FlightDataState.data_input_mode = MODE_CSR_TRIGGER; + strcpy(CSRTrigger::input_fp, out_path); return; } diff --git a/UI/src/platform_win.cpp b/UI/src/platform_win.cpp index 8b6cf4f..bac1693 100644 --- a/UI/src/platform_win.cpp +++ b/UI/src/platform_win.cpp @@ -28,8 +28,7 @@ class WindowsSerial : Serial { bool _is_open; public: - WindowsSerial(HANDLE h) - { + WindowsSerial(HANDLE h) { handle = h; _is_open = true; } @@ -50,24 +49,20 @@ class WindowsSerial : Serial { } int read(char *data, unsigned int max_len) { - if (!_is_open) - { + if (!_is_open) { return 0; } int bytes_read = -1; _read_from_serial_port(&handle, &_is_open, data, max_len, &bytes_read); - if (!_is_open) - { + if (!_is_open) { return -1; } return bytes_read; } - void close() - { - if (!_is_open) - { + void close() { + if (!_is_open) { return; } _close_serial_port(&handle); @@ -75,8 +70,7 @@ class WindowsSerial : Serial { return; } - ~WindowsSerial() - { + ~WindowsSerial() { close(); } }; @@ -231,16 +225,14 @@ std::vector enumerate_ports() { return ports; } -Serial* open_serial_port(const char* name) -{ +Serial *open_serial_port(const char *name) { HANDLE h; _open_serial_port(&h, name); - if (h == INVALID_HANDLE_VALUE) - { + if (h == INVALID_HANDLE_VALUE) { return nullptr; } // is this cursed idk? don't classes store deconstructor in their vtable? - return (Serial*)new WindowsSerial(h); + return (Serial *)new WindowsSerial(h); } void _open_serial_port(HANDLE *hSerial, const char *com_port) { @@ -335,4 +327,67 @@ void platform_begin() { LARGE_INTEGER temp; QueryPerformanceFrequency(&temp); // per windows docs - this value doesn't change and can be cached lpFrequency = temp.QuadPart; +} + +void *spawn_csr_make() { + PROCESS_INFORMATION *proc_info = new PROCESS_INFORMATION; + STARTUPINFO si{}; + si.cb = sizeof(si); + + char cmd[] = "make.exe"; + bool success = CreateProcessA(nullptr, cmd, NULL, NULL, false, NORMAL_PRIORITY_CLASS | CREATE_NEW_PROCESS_GROUP, NULL, "..\\data_postprocessing", &si, proc_info); + + if (!success) { + int error = GetLastError(); + printf("Failed to start process: %d\n", error); + } + return proc_info; +} + +void *spawn_csr(const char *input_fp, const char *output_fp, const char *output_csv_fp) { + PROCESS_INFORMATION *proc_info = new PROCESS_INFORMATION; + STARTUPINFO si{}; + si.cb = sizeof(si); + + static char cmd_str[1024]; + + if (output_csv_fp) { + snprintf(cmd_str, sizeof(cmd_str), "\"..\\data_postprocessing\\app.exe\" \"%s\" \"%s\"", input_fp, output_fp); + } else { + snprintf(cmd_str, sizeof(cmd_str), "\"..\\data_postprocessing\\app.exe\" \"%s\" \"%s\" \"%s\"", input_fp, output_fp, output_csv_fp); + } + + bool success = CreateProcessA(nullptr, cmd_str, NULL, NULL, false, ABOVE_NORMAL_PRIORITY_CLASS | CREATE_NEW_PROCESS_GROUP, NULL, nullptr, &si, proc_info); + + return proc_info; +} + +Process_Status check_process_status(void *handle) { + Process_Status res{}; + PROCESS_INFORMATION *proc_info = (PROCESS_INFORMATION *)(handle); + DWORD code; + bool succeeded = GetExitCodeProcess(proc_info->hProcess, &code); + + if (!succeeded) { + printf("GetExitCodeProcess failed: %d\n", GetLastError()); + return res; + } + + if (code == STILL_ACTIVE) { + res.running = true; + return res; + } else { + res.running = false; + res.exit_code = code; + return res; + } +} + +void close_process(void *handle) { + PROCESS_INFORMATION *hproc = (PROCESS_INFORMATION *)handle; + + CloseHandle(hproc->hProcess); + CloseHandle(hproc->hThread); + delete hproc; + return; } \ No newline at end of file diff --git a/UI/src/ui.cpp b/UI/src/ui.cpp index c13f90f..622598a 100644 --- a/UI/src/ui.cpp +++ b/UI/src/ui.cpp @@ -8,6 +8,7 @@ #include "ui_components.h" #include "ui_graphs.h" #include "flash_dump.h" +#include "csr_trigger.h" bool text_box_active; @@ -298,6 +299,8 @@ void data_management_panel() { ImGui::RadioButton("File Input (Flight Replay)", &FlightDataState.data_input_mode, MODE_FILE_INPUT); ImGui::SameLine(); ImGui::RadioButton("Flash", &FlightDataState.data_input_mode, MODE_FLASH_DUMP); + ImGui::SameLine(); + ImGui::RadioButton("CSR", &FlightDataState.data_input_mode, MODE_CSR_TRIGGER); if (FlightDataState.ports.size() == 0) { @@ -305,6 +308,7 @@ void data_management_panel() { } FlashDump::update(); + CSRTrigger::update(); if (FlightDataState.data_input_mode == MODE_SERIAL_INPUT) { @@ -402,10 +406,20 @@ void data_management_panel() { } } } - else + else if (FlightDataState.data_input_mode == MODE_FLASH_DUMP) { + ImGui::PushFont(panel_header_font); + ImGui::SeparatorText("Flash Link"); + ImGui::PopFont(); FlashDump::render(); } + else + { + ImGui::PushFont(panel_header_font); + ImGui::SeparatorText("Controller State Reconstruction"); + ImGui::PopFont(); + CSRTrigger::render(); + } ImGui::End(); } diff --git a/data_postprocessing/controller_state_reconstruction.cpp b/data_postprocessing/controller_state_reconstruction.cpp index 0f0aa9c..f2800be 100644 --- a/data_postprocessing/controller_state_reconstruction.cpp +++ b/data_postprocessing/controller_state_reconstruction.cpp @@ -193,14 +193,19 @@ bool parse_log_entry(FILE *compressed_bin, FILE *reconstructed_bin) { return true; } -int main() { - FILE *compressed_bin = fopen("successful_flight_test.bin", "rb"); +int main(const int argc, const char* argv[]) { + if (argc < 3) + { + printf("Usage:\n./app.exe \n"); + return EXIT_FAILURE; + } + FILE *compressed_bin = fopen(argv[1], "rb"); if (compressed_bin == NULL) { printf("Failed to open compressed log dump."); return EXIT_FAILURE; } - FILE *reconstructed_bin = fopen("reconstructed_successful_test_flight_with_timestamps.bin", "wb"); + FILE *reconstructed_bin = fopen(argv[2], "wb"); if (reconstructed_bin == NULL) { fclose(compressed_bin); printf("Failed to create reconstructed flight log."); diff --git a/lib/trajectory_following/TrajectoryLogger.cpp b/lib/trajectory_following/TrajectoryLogger.cpp index 914aa90..62bfe91 100644 --- a/lib/trajectory_following/TrajectoryLogger.cpp +++ b/lib/trajectory_following/TrajectoryLogger.cpp @@ -133,19 +133,6 @@ void send_flash_over_serial() { uint32_t addr = 0; int idx = 0; while (1) { - // wait for serial to send a c before sending the next page - while (!USB_CommsSerial.available()) { - } - - char c; - - do { - c = USB_CommsSerial.read(); - } while (!(c == 'k' || c == 'c')); - - if (c == 'k') { - return; - } Flash::read(addr, PAGE_SIZE, last_page); @@ -176,6 +163,20 @@ void send_flash_over_serial() { idx %= PAGE_SIZE; addr += PAGE_SIZE; + + // wait for serial to send a c before sending the next page + while (!USB_CommsSerial.available()) { + } + + char c; + + do { + c = USB_CommsSerial.read(); + } while (!(c == 'k' || c == 'c')); + + if (c == 'k') { + return; + } } return; From 41ee6e561247ce03f98b22b990ef6ce5f63aec43 Mon Sep 17 00:00:00 2001 From: jacobh460 Date: Tue, 28 Apr 2026 22:09:27 -0400 Subject: [PATCH 06/10] fixes bug with serial port sometimes not being able to receive data from mcu over usb --- UI/src/platform_win.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/UI/src/platform_win.cpp b/UI/src/platform_win.cpp index bac1693..3d97f95 100644 --- a/UI/src/platform_win.cpp +++ b/UI/src/platform_win.cpp @@ -255,6 +255,16 @@ void _open_serial_port(HANDLE *hSerial, const char *com_port) { dcb.Parity = NOPARITY; dcb.StopBits = ONESTOPBIT; + dcb.fBinary = TRUE; // Must be TRUE for Win32 + dcb.fParity = FALSE; // Disable parity checking + dcb.fOutxCtsFlow = FALSE; // Disable CTS flow control (CRITICAL) + dcb.fOutxDsrFlow = FALSE; // Disable DSR flow control (CRITICAL) + dcb.fDtrControl = DTR_CONTROL_ENABLE; // Ensure DTR is on + dcb.fDsrSensitivity = FALSE; + dcb.fOutX = FALSE; // Disable XON/XOFF + dcb.fInX = FALSE; + dcb.fRtsControl = RTS_CONTROL_ENABLE; // Ensure RTS is on + if (!SetCommState(*hSerial, &dcb)) { printf("SetCommState error while opening serial port.\n"); _close_serial_port(hSerial); From 8553a16642d3f6a0863c196b782054b8a3e497ec Mon Sep 17 00:00:00 2001 From: jacobh460 Date: Tue, 28 Apr 2026 22:10:55 -0400 Subject: [PATCH 07/10] added timeout functionality in flash dump --- UI/src/flash_dump.cpp | 23 ++++++++++++++++--- lib/trajectory_following/TrajectoryLogger.cpp | 18 +++++++++++---- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/UI/src/flash_dump.cpp b/UI/src/flash_dump.cpp index 23d7bc1..466d239 100644 --- a/UI/src/flash_dump.cpp +++ b/UI/src/flash_dump.cpp @@ -1,9 +1,9 @@ #include "flash_dump.h" +#include "csr_trigger.h" #include "flight_data_state.h" #include "imgui.h" #include "platform_win.h" #include "port_selector.h" -#include "csr_trigger.h" namespace FlashDump { bool _in_progress = false; @@ -23,6 +23,8 @@ const unsigned int PAGE_SIZE = 256; uint8_t recv_buf[PAGE_SIZE + 3]; unsigned int recv_buf_pos = 0; +uint64_t last_byte_time = 0; + bool is_in_progress() { return _in_progress; } @@ -37,12 +39,26 @@ void update() { return; } + // timeout of 5s + if (get_time_us() - last_byte_time > 5'000'000) { + result_message = "ERROR: CONNECTION TIMED OUT"; + read_port.write("k", 1, false); + finish(); + return; + } + if (!read_port.is_open()) { result_message = "ERROR: PORT CLOSED"; finish(); + return; } - recv_buf_pos += read_port.read((char *)(recv_buf + recv_buf_pos), sizeof(recv_buf) - recv_buf_pos); + int n_bytes = read_port.read((char *)(recv_buf + recv_buf_pos), sizeof(recv_buf) - recv_buf_pos); + + if (n_bytes > 0) { + last_byte_time = get_time_us(); + recv_buf_pos += n_bytes; + } if (recv_buf_pos == sizeof(recv_buf)) { // checksum calculation @@ -102,7 +118,7 @@ void render() { if (fout) { // begin by sending start command - const char start_cmd[] = "dump_flash"; + const char start_cmd[] = "\ndump_flash"; if (FlightDataState.fv_serial.write(start_cmd, sizeof(start_cmd) - 1, true)) { // if send succeeded, switch to in-progress state _in_progress = true; @@ -111,6 +127,7 @@ void render() { // if dump port hasn't been chosen, assume we are dumping over fv serial read_port = port_selector.is_open() ? port_selector : FlightDataState.fv_serial; + last_byte_time = get_time_us(); } else { fclose(fout); } diff --git a/lib/trajectory_following/TrajectoryLogger.cpp b/lib/trajectory_following/TrajectoryLogger.cpp index 62bfe91..b15d19b 100644 --- a/lib/trajectory_following/TrajectoryLogger.cpp +++ b/lib/trajectory_following/TrajectoryLogger.cpp @@ -132,6 +132,12 @@ void send_flash_over_serial() { uint8_t last_page[PAGE_SIZE]; uint32_t addr = 0; int idx = 0; + + while (USB_CommsSerial.available()) // empty receive buffer just in case + { + USB_CommsSerial.read(); + } + while (1) { Flash::read(addr, PAGE_SIZE, last_page); @@ -165,13 +171,17 @@ void send_flash_over_serial() { addr += PAGE_SIZE; // wait for serial to send a c before sending the next page - while (!USB_CommsSerial.available()) { - } - - char c; + int c; + uint32_t wait_start_time = millis(); do { c = USB_CommsSerial.read(); + + // if (0){ + if (millis() - wait_start_time > 5000) { + CommsSerial.println("Error: flash dump timed out"); + return; + } } while (!(c == 'k' || c == 'c')); if (c == 'k') { From e0aaaade626f9ab31a4c6c3972c87382cb16a960 Mon Sep 17 00:00:00 2001 From: jacobh460 Date: Wed, 29 Apr 2026 17:00:56 -0400 Subject: [PATCH 08/10] better names in flash dump component --- UI/src/flash_dump.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/UI/src/flash_dump.cpp b/UI/src/flash_dump.cpp index 466d239..78f1384 100644 --- a/UI/src/flash_dump.cpp +++ b/UI/src/flash_dump.cpp @@ -11,9 +11,9 @@ const char *result_message = nullptr; unsigned int bytes_downloaded = 0; -PortSelector port_selector("Flash: ", "##dump_combo", "##dump_checkbox"); +PortSelector flash_usb_port("Flash: ", "##dump_combo", "##dump_checkbox"); -PortSelector &read_port = port_selector; +PortSelector &selected_download_port = flash_usb_port; FILE *fout; @@ -42,18 +42,18 @@ void update() { // timeout of 5s if (get_time_us() - last_byte_time > 5'000'000) { result_message = "ERROR: CONNECTION TIMED OUT"; - read_port.write("k", 1, false); + selected_download_port.write("k", 1, false); finish(); return; } - if (!read_port.is_open()) { + if (!selected_download_port.is_open()) { result_message = "ERROR: PORT CLOSED"; finish(); return; } - int n_bytes = read_port.read((char *)(recv_buf + recv_buf_pos), sizeof(recv_buf) - recv_buf_pos); + int n_bytes = selected_download_port.read((char *)(recv_buf + recv_buf_pos), sizeof(recv_buf) - recv_buf_pos); if (n_bytes > 0) { last_byte_time = get_time_us(); @@ -89,7 +89,7 @@ void update() { return; } - read_port.write("c", 1, false); + selected_download_port.write("c", 1, false); recv_buf_pos = 0; } @@ -107,7 +107,7 @@ void render() { ImGui::SameLine(); render_port_refresh_button(); // port refresh button - port_selector.render(); + flash_usb_port.render(); if (FlightDataState.fv_serial.is_open() && ImGui::Button("Begin")) { static const char *specs[] = {"*.bin", "*.*"}; @@ -126,7 +126,7 @@ void render() { bytes_downloaded = 0; // if dump port hasn't been chosen, assume we are dumping over fv serial - read_port = port_selector.is_open() ? port_selector : FlightDataState.fv_serial; + selected_download_port = flash_usb_port.is_open() ? flash_usb_port : FlightDataState.fv_serial; last_byte_time = get_time_us(); } else { fclose(fout); From 7982bdadff6f862ee267d17acc52e4db7562f733 Mon Sep 17 00:00:00 2001 From: jacobh460 Date: Wed, 29 Apr 2026 17:02:14 -0400 Subject: [PATCH 09/10] fix bug with not guarding rtk forwarding by checking that both serial ports are open and forward rtk to fc instead of rtk serial --- UI/src/flight_data.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/UI/src/flight_data.cpp b/UI/src/flight_data.cpp index 000ec39..2a87aee 100644 --- a/UI/src/flight_data.cpp +++ b/UI/src/flight_data.cpp @@ -142,9 +142,8 @@ int rtk_write_pos = 0; void flight_data_periodic() { if (FlightDataState.data_input_mode == MODE_SERIAL_INPUT) { - // RTK forwarding - if (FlightDataState.rtk_serial.is_open()) { + if (FlightDataState.rtk_serial.is_open() && FlightDataState.fv_serial.is_open()) { int rtk_bytes_read = FlightDataState.rtk_serial.read(rtk_read_buf, RTK_READ_SIZE); if (rtk_bytes_read > 0) { @@ -157,8 +156,8 @@ void flight_data_periodic() { rtk_write_pos++; if (rtk_write_pos >= RTK_WRITE_SIZE - 1) { // escape newlines and backslashes - FlightDataState.rtk_serial.write("rtk ", 4); - FlightDataState.rtk_serial.write(rtk_write_buf, rtk_write_pos, true); + FlightDataState.fv_serial.write("rtk ", 4); + FlightDataState.fv_serial.write(rtk_write_buf, rtk_write_pos, true); rtk_write_pos = 0; } } From b20c3fa7e597e12a2b224718fa2d16b8c8307319 Mon Sep 17 00:00:00 2001 From: jacobh460 Date: Wed, 29 Apr 2026 17:02:26 -0400 Subject: [PATCH 10/10] use mingw32-make.exe instead of make.exe --- UI/src/platform_win.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/src/platform_win.cpp b/UI/src/platform_win.cpp index 3d97f95..3124d89 100644 --- a/UI/src/platform_win.cpp +++ b/UI/src/platform_win.cpp @@ -344,7 +344,7 @@ void *spawn_csr_make() { STARTUPINFO si{}; si.cb = sizeof(si); - char cmd[] = "make.exe"; + char cmd[] = "mingw32-make.exe"; bool success = CreateProcessA(nullptr, cmd, NULL, NULL, false, NORMAL_PRIORITY_CLASS | CREATE_NEW_PROCESS_GROUP, NULL, "..\\data_postprocessing", &si, proc_info); if (!success) {