diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4d4ad8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/proto/service.grpc.pb.cc +/proto/service.grpc.pb.h +/proto/service.pb.cc +/proto/service.pb.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ebd5af..5d216cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,9 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.20) project(serial_sample CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# 1. FIX: Tell CMake to use modern GLVND OpenGL libraries and suppress the warning set(OpenGL_GL_PREFERENCE GLVND) # Define file paths for thirdparty modules @@ -16,9 +15,10 @@ find_package(PkgConfig REQUIRED) find_package(OpenGL REQUIRED) pkg_check_modules(GLFW REQUIRED glfw3) pkg_check_modules(LIBSERIALPORT REQUIRED IMPORTED_TARGET libserialport) +find_package(Protobuf REQUIRED) +find_package(gRPC REQUIRED) - -# Collect core library source files +# ImGUI things set(IMGUI_SOURCES ${IMGUI_DIR}/imgui.cpp ${IMGUI_DIR}/imgui_demo.cpp @@ -37,6 +37,34 @@ set(IMPLOT_SOURCES include/SerialApp.h ) +set(proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/service.pb.cc") +set(proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/service.pb.h") +set(grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/service.grpc.pb.cc") +set(grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/service.grpc.pb.h") + +add_custom_command( + OUTPUT "${proto_srcs}" "${proto_hdrs}" "${grpc_srcs}" "${grpc_hdrs}" + COMMAND ${Protobuf_PROTOC_EXECUTABLE} + ARGS --grpc_out="${CMAKE_CURRENT_BINARY_DIR}" + --cpp_out="${CMAKE_CURRENT_BINARY_DIR}" + -I "${CMAKE_CURRENT_SOURCE_DIR}/proto" + --plugin=protoc-gen-grpc=/usr/bin/grpc_cpp_plugin + "${CMAKE_CURRENT_SOURCE_DIR}/proto/service.proto" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/proto/service.proto" +) + +set_source_files_properties( + "${proto_srcs}" "${proto_hdrs}" "${grpc_srcs}" "${grpc_hdrs}" + PROPERTIES GENERATED TRUE +) + +add_library(proto_lib STATIC ${proto_srcs} ${grpc_srcs} proto/service.proto) +target_link_libraries(proto_lib PUBLIC gRPC::grpc++ ${Protobuf_LIBRARIES}) +target_include_directories(proto_lib PUBLIC + ${CMAKE_CURRENT_BINARY_DIR} + ${Protobuf_INCLUDE_DIRS} +) + add_executable(${PROJECT_NAME} main.cpp ${IMGUI_SOURCES} ${IMPLOT_SOURCES} @@ -48,10 +76,10 @@ add_executable(${PROJECT_NAME} main.cpp include/LogWindow.h ) - -# Provide Header Search Paths target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_BINARY_DIR} + ${Protobuf_INCLUDE_DIRS} ${IMGUI_DIR} ${IMGUI_DIR}/backends ${IMPLOT_DIR} @@ -59,7 +87,6 @@ target_include_directories(${PROJECT_NAME} PRIVATE ${OPENGL_INCLUDE_DIR} ) -# Link Libraries target_link_libraries(${PROJECT_NAME} PRIVATE ${GLFW_LIBRARIES} OpenGL::GL @@ -67,6 +94,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE pthread dl PkgConfig::LIBSERIALPORT + proto_lib ) configure_file(style/dracula.theme ${CMAKE_CURRENT_BINARY_DIR}/dracula.theme COPYONLY) diff --git a/include/ConnectionWindow.h b/include/ConnectionWindow.h index 7465749..aaa9fbf 100644 --- a/include/ConnectionWindow.h +++ b/include/ConnectionWindow.h @@ -11,10 +11,10 @@ public: ~ConnectionWindow(); void initialize(); - void render(bool* p_open); + void render(bool* paramOpen); - bool isConnected() const { return connected; } - struct sp_port* getActivePort() const { return activePort; } + [[nodiscard]] bool isConnected() const { return connected; } + [[nodiscard]] sp_port* getActivePort() const { return activePort; } void forceLogMessage(const std::string& msg); std::string& getSharedLog() { return txLog; } diff --git a/include/LogWindow.h b/include/LogWindow.h index ee6cef9..6fc9d03 100644 --- a/include/LogWindow.h +++ b/include/LogWindow.h @@ -8,7 +8,7 @@ public: LogWindow() = default; ~LogWindow() = default; - void render(bool* paramOpen, ConnectionWindow& connectionWindow); + static void render(bool* paramOpen, ConnectionWindow& connectionWindow); }; #endif // SERIAL_SAMPLE_LOGWINDOW_H diff --git a/include/PayloadWindow.h b/include/PayloadWindow.h index 15a2772..7fb1c0b 100644 --- a/include/PayloadWindow.h +++ b/include/PayloadWindow.h @@ -15,8 +15,8 @@ public: void render(bool* p_open, ConnectionWindow& connectionWindow); private: - void sendHexPayload(const std::string& hexString, ConnectionWindow& connectionWindow); - char hexInputBuffer[256]; + static void sendHexPayload(const std::string& hexString, ConnectionWindow& connectionWindow); + char hexInputBuffer[256]{}; }; #endif // SERIAL_SAMPLE_PAYLOADWINDOW_H diff --git a/include/SerialApp.h b/include/SerialApp.h index dee72c9..c79b575 100644 --- a/include/SerialApp.h +++ b/include/SerialApp.h @@ -14,11 +14,12 @@ public: void initialize(); void renderUI(); - bool isClosing() const { return shouldClose; } + [[nodiscard]] bool isClosing() const { return shouldClose; } private: void renderMainMenuBar(); - bool loadTheme(const std::string& filepath); // Метод парсинга файла темы + + static bool loadTheme(const std::string& filepath); // Метод парсинга файла темы bool showConnectionWindow = true; bool showPayloadWindow = true; diff --git a/main.cpp b/main.cpp index 2a60316..c2dac3d 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,6 @@ #include - +#include +#include // Добавили для работы с потоками #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" @@ -7,56 +8,79 @@ #include #include "include/SerialApp.h" +#include + +// работает только после первой генерации +#include "service.grpc.pb.h" static void glfw_error_callback(int error, const char* description) { fprintf(stderr, "GLFW Error %d: %s\n", error, description); } +// Простейшая реализация вашего сервиса gRPC (для примера) +class MyServiceImpl final : public serial_sample::Greeter::Service { + grpc::Status SayHello(grpc::ServerContext* context, + const serial_sample::HelloRequest* request, + serial_sample::HelloReply* reply) override { + reply->set_message("Hello, " + request->name() + "!"); + return grpc::Status::OK; + } +}; + int main(int, char**) { glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) return 1; - // Настройка версии OpenGL (3.3 Core) glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - // Создание окна ОС GLFWwindow* window = glfwCreateWindow(1280, 720, "Serial App", nullptr, nullptr); if (window == nullptr) return 1; glfwMakeContextCurrent(window); - glfwSwapInterval(1); // Включение вертикальной синхронизации (V-Sync) + glfwSwapInterval(1); - // Инициализация контекста ImGui IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; ImGui::StyleColorsDark(); - // Инициализация платформных бэкендов внутри ImGui ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init("#version 130"); - // Создаем и инициализируем наше изолированное приложение SerialApp app; app.initialize(); - // Главный цикл приложения + // === НАЧАЛО ИНИЦИАЛИЗАЦИИ gRPC СЕРВЕРА === + std::string server_address("0.0.0.0:50051"); + MyServiceImpl grpc_service; + + grpc::ServerBuilder builder; + builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); + builder.RegisterService(&grpc_service); + + std::unique_ptr grpc_server(builder.BuildAndStart()); + std::cout << "gRPC Server listening on " << server_address << std::endl; + + // Запускаем сервер в фоновом потоке + std::thread grpc_thread([&grpc_server]() { + grpc_server->Wait(); // Блокирует фоновый поток, пока сервер работает + }); + // ========================================= + + // Главный цикл приложения (GUI поток) while (!glfwWindowShouldClose(window)) { glfwPollEvents(); - // Старт нового кадра ImGui ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); - // Рендерим наше кастомное UI app.renderUI(); if (app.isClosing()) { glfwSetWindowShouldClose(window, GLFW_TRUE); } - // Рендеринг графики ImGui::Render(); int display_w, display_h; glfwGetFramebufferSize(window, &display_w, &display_h); @@ -68,7 +92,18 @@ int main(int, char**) { glfwSwapBuffers(window); } - // Очистка ресурсов перед выходом + // === КОРРЕКТНОЕ ВЫКЛЮЧЕНИЕ gRPC СЕРВЕРА === + std::cout << "Shutting down gRPC server..." << std::endl; + // Останавливаем сервер (это разблокирует метод Wait() в фоновом потоке) + grpc_server->Shutdown(); + + // Обязательно дожидаемся завершения фонового потока перед выходом из main + if (grpc_thread.joinable()) { + grpc_thread.join(); + } + std::cout << "gRPC server thread joined." << std::endl; + // ========================================= + ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); diff --git a/proto/service.proto b/proto/service.proto new file mode 100644 index 0000000..5ac89bd --- /dev/null +++ b/proto/service.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package serial_sample; + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string message = 1; +} \ No newline at end of file diff --git a/sources/ConnectionWindow.cpp b/sources/ConnectionWindow.cpp index 2e31f62..f3f673c 100644 --- a/sources/ConnectionWindow.cpp +++ b/sources/ConnectionWindow.cpp @@ -1,6 +1,5 @@ #include "../include/ConnectionWindow.h" #include "imgui.h" -#include ConnectionWindow::ConnectionWindow() : selectedPortIdx(0), connected(false), activePort(nullptr) { @@ -27,10 +26,10 @@ void ConnectionWindow::refreshPorts() { ports.clear(); selectedPortIdx = 0; - struct sp_port** port_list; - if (sp_list_ports(&port_list) == SP_OK) { - for (int i = 0; port_list[i] != nullptr; i++) { - struct sp_port* port = port_list[i]; + sp_port** portList; + if (sp_list_ports(&portList) == SP_OK) { + for (int i = 0; portList[i] != nullptr; i++) { + const sp_port* port = portList[i]; SerialPortInfo info; const char* name = sp_get_port_name(port); info.portName = name ? name : "Unknown"; @@ -38,7 +37,7 @@ void ConnectionWindow::refreshPorts() { info.description = desc ? desc : "No Description"; ports.push_back(info); } - sp_free_port_list(port_list); + sp_free_port_list(portList); } if (ports.empty()) { @@ -50,8 +49,8 @@ void ConnectionWindow::forceLogMessage(const std::string& msg) { txLog += msg + "\n"; } -void ConnectionWindow::render(bool* p_open) { - if (!ImGui::Begin("Connection Settings", p_open)) { +void ConnectionWindow::render(bool* paramOpen) { + if (!ImGui::Begin("Connection Settings", paramOpen)) { ImGui::End(); return; } @@ -62,17 +61,17 @@ void ConnectionWindow::render(bool* p_open) { ImGui::SameLine(); // 1. Выбор COM-порта - std::string combo_preview = ports[selectedPortIdx].portName; + std::string preview = ports[selectedPortIdx].portName; if (ports[selectedPortIdx].portName != "None") { - combo_preview += " (" + ports[selectedPortIdx].description + ")"; + preview += " (" + ports[selectedPortIdx].description + ")"; } ImGui::BeginDisabled(connected); - if (ImGui::BeginCombo("Serial Port", combo_preview.c_str())) { + if (ImGui::BeginCombo("Serial Port", preview.c_str())) { for (int n = 0; n < ports.size(); n++) { - const bool is_selected = (selectedPortIdx == n); + const bool isSelected = (selectedPortIdx == n); std::string item_text = ports[n].portName + " - " + ports[n].description; - if (ImGui::Selectable(item_text.c_str(), is_selected)) { + if (ImGui::Selectable(item_text.c_str(), isSelected)) { selectedPortIdx = n; } } @@ -80,13 +79,13 @@ void ConnectionWindow::render(bool* p_open) { } // 2. Выбор Baud Rate через выпадающий список - std::string baud_preview = std::to_string(baudRates[selectedBaudIdx]); - if (ImGui::BeginCombo("Baud Rate", baud_preview.c_str())) { + const std::string baudPreview = std::to_string(baudRates[selectedBaudIdx]); + if (ImGui::BeginCombo("Baud Rate", baudPreview.c_str())) { for (int b = 0; b < baudRates.size(); b++) { - const bool is_selected = (selectedBaudIdx == b); + const bool isSelected = (selectedBaudIdx == b); std::string baud_item_text = std::to_string(baudRates[b]); - if (ImGui::Selectable(baud_item_text.c_str(), is_selected)) { + if (ImGui::Selectable(baud_item_text.c_str(), isSelected)) { selectedBaudIdx = b; } } @@ -102,15 +101,15 @@ void ConnectionWindow::render(bool* p_open) { if (sp_open(activePort, SP_MODE_READ_WRITE) == SP_OK) { // Передаем реальное значение скорости из выбранного индекса массива - int real_baud = baudRates[selectedBaudIdx]; - sp_set_baudrate(activePort, real_baud); + const int selectedBaud = baudRates[selectedBaudIdx]; + sp_set_baudrate(activePort, selectedBaud); sp_set_bits(activePort, 8); sp_set_parity(activePort, SP_PARITY_NONE); sp_set_stopbits(activePort, 1); connected = true; - forceLogMessage("[SYSTEM] Connected to " + ports[selectedPortIdx].portName + " at " + std::to_string(real_baud) + " baud."); + forceLogMessage("[SYSTEM] Connected to " + ports[selectedPortIdx].portName + " at " + std::to_string(selectedBaud) + " baud."); } else { forceLogMessage("[SYSTEM ERROR] Could not open port " + ports[selectedPortIdx].portName); sp_free_port(activePort); diff --git a/sources/PayloadWindow.cpp b/sources/PayloadWindow.cpp index 0ee0f22..58e3bb7 100644 --- a/sources/PayloadWindow.cpp +++ b/sources/PayloadWindow.cpp @@ -33,13 +33,18 @@ void PayloadWindow::sendHexPayload(const std::string& hexString, ConnectionWindo return; } - int bytes_written = sp_nonblocking_write(port, bytes.data(), bytes.size()); + const int bytesWritten = sp_nonblocking_write(port, bytes.data(), bytes.size()); - if (bytes_written >= 0) { + if (bytesWritten >= 0) { std::stringstream log_ss; - log_ss << "[TX] Sent " << bytes_written << " bytes: "; - for (size_t i = 0; i < bytes.size(); ++i) { - log_ss << std::uppercase << std::hex << std::setw(2) << std::setfill('0') << (int)bytes[i] << " "; + log_ss << "[TX] Sent " << bytesWritten << " bytes: "; + for (unsigned char byte : bytes) { + log_ss << std::uppercase + << std::hex + << std::setw(2) + << std::setfill('0') + << static_cast(byte) + << " "; } connectionWindow.forceLogMessage(log_ss.str()); } else { diff --git a/sources/SerialApp.cpp b/sources/SerialApp.cpp index a387365..b060e85 100644 --- a/sources/SerialApp.cpp +++ b/sources/SerialApp.cpp @@ -19,7 +19,9 @@ void SerialApp::initialize() { // Вспомогательная функция перевода HEX строки (например, "282a36ff") в ImVec4 static ImVec4 HexToImVec4(const std::string& hex_str) { - if (hex_str.length() < 8) return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + if (hex_str.length() < 8) { + return {1.0f, 1.0f, 1.0f, 1.0f}; + } unsigned int r, g, b, a; std::stringstream ss; @@ -28,7 +30,12 @@ static ImVec4 HexToImVec4(const std::string& hex_str) { ss << std::hex << hex_str.substr(4, 2); ss >> b; ss.clear(); ss << std::hex << hex_str.substr(6, 2); ss >> a; - return ImVec4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f); + return { + static_cast(r) / 255.0f, + static_cast(g) / 255.0f, + static_cast(b) / 255.0f, + static_cast(a) / 255.0f + }; } bool SerialApp::loadTheme(const std::string& filepath) { @@ -82,7 +89,7 @@ bool SerialApp::loadTheme(const std::string& filepath) { // Читаем HEX цвета std::string hex_val; ss >> hex_val; - if (color_map.find(key) != color_map.end()) { + if (color_map.contains(key)) { style.Colors[color_map[key]] = HexToImVec4(hex_val); } } else { @@ -110,7 +117,7 @@ void SerialApp::renderUI() { payloadWindow.render(&showPayloadWindow, connectionWindow); } if (showLogWindow) { - logWindow.render(&showLogWindow, connectionWindow); + LogWindow::render(&showLogWindow, connectionWindow); } }