protobuf server

This commit is contained in:
Ivan I. Ovchinnikov
2026-07-28 20:46:41 +03:00
parent 5a702fe0f8
commit 8c4d40b66a
11 changed files with 150 additions and 56 deletions
+4
View File
@@ -0,0 +1,4 @@
/proto/service.grpc.pb.cc
/proto/service.grpc.pb.h
/proto/service.pb.cc
/proto/service.pb.h
+35 -7
View File
@@ -1,10 +1,9 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.20)
project(serial_sample CXX) project(serial_sample CXX)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) 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) set(OpenGL_GL_PREFERENCE GLVND)
# Define file paths for thirdparty modules # Define file paths for thirdparty modules
@@ -16,9 +15,10 @@ find_package(PkgConfig REQUIRED)
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
pkg_check_modules(GLFW REQUIRED glfw3) pkg_check_modules(GLFW REQUIRED glfw3)
pkg_check_modules(LIBSERIALPORT REQUIRED IMPORTED_TARGET libserialport) pkg_check_modules(LIBSERIALPORT REQUIRED IMPORTED_TARGET libserialport)
find_package(Protobuf REQUIRED)
find_package(gRPC REQUIRED)
# ImGUI things
# Collect core library source files
set(IMGUI_SOURCES set(IMGUI_SOURCES
${IMGUI_DIR}/imgui.cpp ${IMGUI_DIR}/imgui.cpp
${IMGUI_DIR}/imgui_demo.cpp ${IMGUI_DIR}/imgui_demo.cpp
@@ -37,6 +37,34 @@ set(IMPLOT_SOURCES
include/SerialApp.h 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 add_executable(${PROJECT_NAME} main.cpp
${IMGUI_SOURCES} ${IMGUI_SOURCES}
${IMPLOT_SOURCES} ${IMPLOT_SOURCES}
@@ -48,10 +76,10 @@ add_executable(${PROJECT_NAME} main.cpp
include/LogWindow.h include/LogWindow.h
) )
# Provide Header Search Paths
target_include_directories(${PROJECT_NAME} PRIVATE target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_BINARY_DIR}
${Protobuf_INCLUDE_DIRS}
${IMGUI_DIR} ${IMGUI_DIR}
${IMGUI_DIR}/backends ${IMGUI_DIR}/backends
${IMPLOT_DIR} ${IMPLOT_DIR}
@@ -59,7 +87,6 @@ target_include_directories(${PROJECT_NAME} PRIVATE
${OPENGL_INCLUDE_DIR} ${OPENGL_INCLUDE_DIR}
) )
# Link Libraries
target_link_libraries(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE
${GLFW_LIBRARIES} ${GLFW_LIBRARIES}
OpenGL::GL OpenGL::GL
@@ -67,6 +94,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
pthread pthread
dl dl
PkgConfig::LIBSERIALPORT PkgConfig::LIBSERIALPORT
proto_lib
) )
configure_file(style/dracula.theme ${CMAKE_CURRENT_BINARY_DIR}/dracula.theme COPYONLY) configure_file(style/dracula.theme ${CMAKE_CURRENT_BINARY_DIR}/dracula.theme COPYONLY)
+3 -3
View File
@@ -11,10 +11,10 @@ public:
~ConnectionWindow(); ~ConnectionWindow();
void initialize(); void initialize();
void render(bool* p_open); void render(bool* paramOpen);
bool isConnected() const { return connected; } [[nodiscard]] bool isConnected() const { return connected; }
struct sp_port* getActivePort() const { return activePort; } [[nodiscard]] sp_port* getActivePort() const { return activePort; }
void forceLogMessage(const std::string& msg); void forceLogMessage(const std::string& msg);
std::string& getSharedLog() { return txLog; } std::string& getSharedLog() { return txLog; }
+1 -1
View File
@@ -8,7 +8,7 @@ public:
LogWindow() = default; LogWindow() = default;
~LogWindow() = default; ~LogWindow() = default;
void render(bool* paramOpen, ConnectionWindow& connectionWindow); static void render(bool* paramOpen, ConnectionWindow& connectionWindow);
}; };
#endif // SERIAL_SAMPLE_LOGWINDOW_H #endif // SERIAL_SAMPLE_LOGWINDOW_H
+2 -2
View File
@@ -15,8 +15,8 @@ public:
void render(bool* p_open, ConnectionWindow& connectionWindow); void render(bool* p_open, ConnectionWindow& connectionWindow);
private: private:
void sendHexPayload(const std::string& hexString, ConnectionWindow& connectionWindow); static void sendHexPayload(const std::string& hexString, ConnectionWindow& connectionWindow);
char hexInputBuffer[256]; char hexInputBuffer[256]{};
}; };
#endif // SERIAL_SAMPLE_PAYLOADWINDOW_H #endif // SERIAL_SAMPLE_PAYLOADWINDOW_H
+3 -2
View File
@@ -14,11 +14,12 @@ public:
void initialize(); void initialize();
void renderUI(); void renderUI();
bool isClosing() const { return shouldClose; } [[nodiscard]] bool isClosing() const { return shouldClose; }
private: private:
void renderMainMenuBar(); void renderMainMenuBar();
bool loadTheme(const std::string& filepath); // Метод парсинга файла темы
static bool loadTheme(const std::string& filepath); // Метод парсинга файла темы
bool showConnectionWindow = true; bool showConnectionWindow = true;
bool showPayloadWindow = true; bool showPayloadWindow = true;
+47 -12
View File
@@ -1,5 +1,6 @@
#include <iostream> #include <iostream>
#include <memory>
#include <thread> // Добавили для работы с потоками
#include "imgui.h" #include "imgui.h"
#include "imgui_impl_glfw.h" #include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h" #include "imgui_impl_opengl3.h"
@@ -7,56 +8,79 @@
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include "include/SerialApp.h" #include "include/SerialApp.h"
#include <grpcpp/grpcpp.h>
// работает только после первой генерации
#include "service.grpc.pb.h"
static void glfw_error_callback(int error, const char* description) { static void glfw_error_callback(int error, const char* description) {
fprintf(stderr, "GLFW Error %d: %s\n", error, 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**) { int main(int, char**) {
glfwSetErrorCallback(glfw_error_callback); glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()) return 1; if (!glfwInit()) return 1;
// Настройка версии OpenGL (3.3 Core)
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Создание окна ОС
GLFWwindow* window = glfwCreateWindow(1280, 720, "Serial App", nullptr, nullptr); GLFWwindow* window = glfwCreateWindow(1280, 720, "Serial App", nullptr, nullptr);
if (window == nullptr) return 1; if (window == nullptr) return 1;
glfwMakeContextCurrent(window); glfwMakeContextCurrent(window);
glfwSwapInterval(1); // Включение вертикальной синхронизации (V-Sync) glfwSwapInterval(1);
// Инициализация контекста ImGui
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
ImGui::CreateContext(); ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io; ImGuiIO& io = ImGui::GetIO(); (void)io;
ImGui::StyleColorsDark(); ImGui::StyleColorsDark();
// Инициализация платформных бэкендов внутри ImGui
ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 130"); ImGui_ImplOpenGL3_Init("#version 130");
// Создаем и инициализируем наше изолированное приложение
SerialApp app; SerialApp app;
app.initialize(); 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> 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)) { while (!glfwWindowShouldClose(window)) {
glfwPollEvents(); glfwPollEvents();
// Старт нового кадра ImGui
ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame(); ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
// Рендерим наше кастомное UI
app.renderUI(); app.renderUI();
if (app.isClosing()) { if (app.isClosing()) {
glfwSetWindowShouldClose(window, GLFW_TRUE); glfwSetWindowShouldClose(window, GLFW_TRUE);
} }
// Рендеринг графики
ImGui::Render(); ImGui::Render();
int display_w, display_h; int display_w, display_h;
glfwGetFramebufferSize(window, &display_w, &display_h); glfwGetFramebufferSize(window, &display_w, &display_h);
@@ -68,7 +92,18 @@ int main(int, char**) {
glfwSwapBuffers(window); 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_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown(); ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext(); ImGui::DestroyContext();
+15
View File
@@ -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;
}
+19 -20
View File
@@ -1,6 +1,5 @@
#include "../include/ConnectionWindow.h" #include "../include/ConnectionWindow.h"
#include "imgui.h" #include "imgui.h"
#include <cstdio>
ConnectionWindow::ConnectionWindow() ConnectionWindow::ConnectionWindow()
: selectedPortIdx(0), connected(false), activePort(nullptr) { : selectedPortIdx(0), connected(false), activePort(nullptr) {
@@ -27,10 +26,10 @@ void ConnectionWindow::refreshPorts() {
ports.clear(); ports.clear();
selectedPortIdx = 0; selectedPortIdx = 0;
struct sp_port** port_list; sp_port** portList;
if (sp_list_ports(&port_list) == SP_OK) { if (sp_list_ports(&portList) == SP_OK) {
for (int i = 0; port_list[i] != nullptr; i++) { for (int i = 0; portList[i] != nullptr; i++) {
struct sp_port* port = port_list[i]; const sp_port* port = portList[i];
SerialPortInfo info; SerialPortInfo info;
const char* name = sp_get_port_name(port); const char* name = sp_get_port_name(port);
info.portName = name ? name : "Unknown"; info.portName = name ? name : "Unknown";
@@ -38,7 +37,7 @@ void ConnectionWindow::refreshPorts() {
info.description = desc ? desc : "No Description"; info.description = desc ? desc : "No Description";
ports.push_back(info); ports.push_back(info);
} }
sp_free_port_list(port_list); sp_free_port_list(portList);
} }
if (ports.empty()) { if (ports.empty()) {
@@ -50,8 +49,8 @@ void ConnectionWindow::forceLogMessage(const std::string& msg) {
txLog += msg + "\n"; txLog += msg + "\n";
} }
void ConnectionWindow::render(bool* p_open) { void ConnectionWindow::render(bool* paramOpen) {
if (!ImGui::Begin("Connection Settings", p_open)) { if (!ImGui::Begin("Connection Settings", paramOpen)) {
ImGui::End(); ImGui::End();
return; return;
} }
@@ -62,17 +61,17 @@ void ConnectionWindow::render(bool* p_open) {
ImGui::SameLine(); ImGui::SameLine();
// 1. Выбор COM-порта // 1. Выбор COM-порта
std::string combo_preview = ports[selectedPortIdx].portName; std::string preview = ports[selectedPortIdx].portName;
if (ports[selectedPortIdx].portName != "None") { if (ports[selectedPortIdx].portName != "None") {
combo_preview += " (" + ports[selectedPortIdx].description + ")"; preview += " (" + ports[selectedPortIdx].description + ")";
} }
ImGui::BeginDisabled(connected); 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++) { 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; 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; selectedPortIdx = n;
} }
} }
@@ -80,13 +79,13 @@ void ConnectionWindow::render(bool* p_open) {
} }
// 2. Выбор Baud Rate через выпадающий список // 2. Выбор Baud Rate через выпадающий список
std::string baud_preview = std::to_string(baudRates[selectedBaudIdx]); const std::string baudPreview = std::to_string(baudRates[selectedBaudIdx]);
if (ImGui::BeginCombo("Baud Rate", baud_preview.c_str())) { if (ImGui::BeginCombo("Baud Rate", baudPreview.c_str())) {
for (int b = 0; b < baudRates.size(); b++) { 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]); 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; selectedBaudIdx = b;
} }
} }
@@ -102,15 +101,15 @@ void ConnectionWindow::render(bool* p_open) {
if (sp_open(activePort, SP_MODE_READ_WRITE) == SP_OK) { if (sp_open(activePort, SP_MODE_READ_WRITE) == SP_OK) {
// Передаем реальное значение скорости из выбранного индекса массива // Передаем реальное значение скорости из выбранного индекса массива
int real_baud = baudRates[selectedBaudIdx]; const int selectedBaud = baudRates[selectedBaudIdx];
sp_set_baudrate(activePort, real_baud); sp_set_baudrate(activePort, selectedBaud);
sp_set_bits(activePort, 8); sp_set_bits(activePort, 8);
sp_set_parity(activePort, SP_PARITY_NONE); sp_set_parity(activePort, SP_PARITY_NONE);
sp_set_stopbits(activePort, 1); sp_set_stopbits(activePort, 1);
connected = true; 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 { } else {
forceLogMessage("[SYSTEM ERROR] Could not open port " + ports[selectedPortIdx].portName); forceLogMessage("[SYSTEM ERROR] Could not open port " + ports[selectedPortIdx].portName);
sp_free_port(activePort); sp_free_port(activePort);
+10 -5
View File
@@ -33,13 +33,18 @@ void PayloadWindow::sendHexPayload(const std::string& hexString, ConnectionWindo
return; 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; std::stringstream log_ss;
log_ss << "[TX] Sent " << bytes_written << " bytes: "; log_ss << "[TX] Sent " << bytesWritten << " bytes: ";
for (size_t i = 0; i < bytes.size(); ++i) { for (unsigned char byte : bytes) {
log_ss << std::uppercase << std::hex << std::setw(2) << std::setfill('0') << (int)bytes[i] << " "; log_ss << std::uppercase
<< std::hex
<< std::setw(2)
<< std::setfill('0')
<< static_cast<int>(byte)
<< " ";
} }
connectionWindow.forceLogMessage(log_ss.str()); connectionWindow.forceLogMessage(log_ss.str());
} else { } else {
+11 -4
View File
@@ -19,7 +19,9 @@ void SerialApp::initialize() {
// Вспомогательная функция перевода HEX строки (например, "282a36ff") в ImVec4 // Вспомогательная функция перевода HEX строки (например, "282a36ff") в ImVec4
static ImVec4 HexToImVec4(const std::string& hex_str) { 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; unsigned int r, g, b, a;
std::stringstream ss; 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(4, 2); ss >> b; ss.clear();
ss << std::hex << hex_str.substr(6, 2); ss >> a; 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<float>(r) / 255.0f,
static_cast<float>(g) / 255.0f,
static_cast<float>(b) / 255.0f,
static_cast<float>(a) / 255.0f
};
} }
bool SerialApp::loadTheme(const std::string& filepath) { bool SerialApp::loadTheme(const std::string& filepath) {
@@ -82,7 +89,7 @@ bool SerialApp::loadTheme(const std::string& filepath) {
// Читаем HEX цвета // Читаем HEX цвета
std::string hex_val; std::string hex_val;
ss >> 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); style.Colors[color_map[key]] = HexToImVec4(hex_val);
} }
} else { } else {
@@ -110,7 +117,7 @@ void SerialApp::renderUI() {
payloadWindow.render(&showPayloadWindow, connectionWindow); payloadWindow.render(&showPayloadWindow, connectionWindow);
} }
if (showLogWindow) { if (showLogWindow) {
logWindow.render(&showLogWindow, connectionWindow); LogWindow::render(&showLogWindow, connectionWindow);
} }
} }