diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index d23aa41..35eb1dd 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,7 +2,5 @@
-
-
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5d216cc..b6f463f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,11 +6,51 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(OpenGL_GL_PREFERENCE GLVND)
-# Define file paths for thirdparty modules
-set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/imgui)
-set(IMPLOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/implot)
+# ==============================================================================
+# ЗАГРУЗКА ЗАВИСИМОСТЕЙ ЧЕРЕЗ FETCHCONTENT (ImGui и ImPlot)
+# ==============================================================================
+include(FetchContent)
-# Find Required Packages
+# 1. Загрузка Dear ImGui
+FetchContent_Declare(
+ imgui
+ GIT_REPOSITORY https://github.com/ocornut/imgui.git
+ GIT_TAG master
+)
+FetchContent_MakeAvailable(imgui)
+
+# 2. Загрузка ImPlot
+FetchContent_Declare(
+ implot
+ GIT_REPOSITORY https://github.com/epezent/implot.git
+ GIT_TAG master
+)
+FetchContent_MakeAvailable(implot)
+
+# ==============================================================================
+# НАСТРОЙКА ИСХОДНЫХ КОДОВ БИБЛИОТЕК
+# ==============================================================================
+# Исходники ImGui
+set(IMGUI_SOURCES
+ ${imgui_SOURCE_DIR}/imgui.cpp
+ ${imgui_SOURCE_DIR}/imgui_demo.cpp
+ ${imgui_SOURCE_DIR}/imgui_draw.cpp
+ ${imgui_SOURCE_DIR}/imgui_tables.cpp
+ ${imgui_SOURCE_DIR}/imgui_widgets.cpp
+ ${imgui_SOURCE_DIR}/backends/imgui_impl_glfw.cpp
+ ${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp
+)
+
+# Исходники ImPlot
+set(IMPLOT_SOURCES
+ ${implot_SOURCE_DIR}/implot.cpp
+ ${implot_SOURCE_DIR}/implot_items.cpp
+ ${implot_SOURCE_DIR}/implot_demo.cpp
+)
+
+# ==============================================================================
+# ПОИСК СИСТЕМНЫХ ПАКЕТОВ
+# ==============================================================================
find_package(PkgConfig REQUIRED)
find_package(OpenGL REQUIRED)
pkg_check_modules(GLFW REQUIRED glfw3)
@@ -18,25 +58,9 @@ pkg_check_modules(LIBSERIALPORT REQUIRED IMPORTED_TARGET libserialport)
find_package(Protobuf REQUIRED)
find_package(gRPC REQUIRED)
-# ImGUI things
-set(IMGUI_SOURCES
- ${IMGUI_DIR}/imgui.cpp
- ${IMGUI_DIR}/imgui_demo.cpp
- ${IMGUI_DIR}/imgui_draw.cpp
- ${IMGUI_DIR}/imgui_tables.cpp
- ${IMGUI_DIR}/imgui_widgets.cpp
- ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp
- ${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp
-)
-
-set(IMPLOT_SOURCES
- ${IMPLOT_DIR}/implot.cpp
- ${IMPLOT_DIR}/implot_items.cpp
- ${IMPLOT_DIR}/implot_demo.cpp
- sources/SerialApp.cpp
- include/SerialApp.h
-)
-
+# ==============================================================================
+# ГЕНЕРАЦИЯ PROTOBUF & GRPC
+# ==============================================================================
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")
@@ -65,9 +89,14 @@ target_include_directories(proto_lib PUBLIC
${Protobuf_INCLUDE_DIRS}
)
+# ==============================================================================
+# СБОРКА ОСНОВНОГО ПРИЛОЖЕНИЯ
+# ==============================================================================
add_executable(${PROJECT_NAME} main.cpp
${IMGUI_SOURCES}
${IMPLOT_SOURCES}
+ sources/SerialApp.cpp
+ include/SerialApp.h
sources/ConnectionWindow.cpp
include/ConnectionWindow.h
sources/PayloadWindow.cpp
@@ -80,9 +109,9 @@ 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}
+ ${imgui_SOURCE_DIR}
+ ${imgui_SOURCE_DIR}/backends
+ ${implot_SOURCE_DIR}
${GLFW_INCLUDE_DIRS}
${OPENGL_INCLUDE_DIR}
)
@@ -98,3 +127,34 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
)
configure_file(style/dracula.theme ${CMAKE_CURRENT_BINARY_DIR}/dracula.theme COPYONLY)
+
+
+#Является частой практикой в больших проектах для поддержки
+#единой схемы данныхмежду сервером и клиентом.
+#Если .proto лежит в отдельном Git-репозитории на сервере
+#лучше использовать FetchContent. Он корректно кэширует файлы
+#и обновляет их только при смене тега или хэша коммита.
+#```
+#FetchContent_Declare(
+#remote_proto
+#GIT_REPOSITORY git@your-server.com:shared/protocols.git # URL вашего репозитория
+#GIT_TAG main # Ветка, тег или хэш коммита
+#)
+#FetchContent_MakeAvailable(remote_proto)
+#```
+#Теперь файл доступен по пути: ${remote_proto_SOURCE_DIR}/service.proto
+#
+#не забыть обновить команду компиляции Protobuf,
+#заменив локальные пути на пути из FetchContent:
+#```
+#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 "${remote_proto_SOURCE_DIR}" # Корневая папка для импортов
+#--plugin=protoc-gen-grpc=/usr/bin/grpc_cpp_plugin
+#"${REMOTE_PROTO_PATH}" # Сам файл
+#DEPENDS "${REMOTE_PROTO_PATH}"
+#)
+#```
diff --git a/main.cpp b/main.cpp
index c2dac3d..a019093 100644
--- a/main.cpp
+++ b/main.cpp
@@ -17,54 +17,67 @@ static void glfw_error_callback(int error, const char* description) {
fprintf(stderr, "GLFW Error %d: %s\n", error, description);
}
-// Простейшая реализация вашего сервиса gRPC (для примера)
+bool initImGUI(GLFWwindow *&window) {
+ glfwSetErrorCallback(glfw_error_callback);
+ if (!glfwInit()) {
+ return true;
+ }
+
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+
+ window = glfwCreateWindow(1280, 720, "Serial App", nullptr, nullptr);
+ if (window == nullptr) {
+ return true;
+ }
+ glfwMakeContextCurrent(window);
+ glfwSwapInterval(1);
+
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+ const ImGuiIO& io = ImGui::GetIO(); (void)io;
+ ImGui::StyleColorsDark();
+
+ ImGui_ImplGlfw_InitForOpenGL(window, true);
+ ImGui_ImplOpenGL3_Init("#version 130");
+ return false;
+}
+
+// Простейшая реализация сервиса 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 {
+ std::cout << request->name() << std::endl;
reply->set_message("Hello, " + request->name() + "!");
return grpc::Status::OK;
}
};
int main(int, char**) {
- glfwSetErrorCallback(glfw_error_callback);
- if (!glfwInit()) return 1;
+ GLFWwindow *window;
+ if (initImGUI(window)) {
+ return 1;
+ }
- 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);
-
- IMGUI_CHECKVERSION();
- ImGui::CreateContext();
- ImGuiIO& io = ImGui::GetIO(); (void)io;
- ImGui::StyleColorsDark();
-
- ImGui_ImplGlfw_InitForOpenGL(window, true);
- ImGui_ImplOpenGL3_Init("#version 130");
-
- SerialApp app;
+ SerialApp app; // frontend manager
app.initialize();
- // === НАЧАЛО ИНИЦИАЛИЗАЦИИ gRPC СЕРВЕРА ===
- std::string server_address("0.0.0.0:50051");
- MyServiceImpl grpc_service;
+ // инициализация gRPC
+ const std::string serverAddress("0.0.0.0:50051");
+ MyServiceImpl serviceImpl;
grpc::ServerBuilder builder;
- builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
- builder.RegisterService(&grpc_service);
+ builder.AddListeningPort(serverAddress, grpc::InsecureServerCredentials());
+ builder.RegisterService(&serviceImpl);
- std::unique_ptr grpc_server(builder.BuildAndStart());
- std::cout << "gRPC Server listening on " << server_address << std::endl;
+ std::unique_ptr gRPCServer(builder.BuildAndStart());
+ std::cout << "gRPC Server listening on " << serverAddress << std::endl;
- // Запускаем сервер в фоновом потоке
- std::thread grpc_thread([&grpc_server]() {
- grpc_server->Wait(); // Блокирует фоновый поток, пока сервер работает
+ // сервер стартует в фоновом БЛОКИРУЮЩЕМ потоке
+ std::thread gRPCThread([&gRPCServer] {
+ gRPCServer->Wait();
});
// =========================================
@@ -82,9 +95,9 @@ int main(int, char**) {
}
ImGui::Render();
- int display_w, display_h;
- glfwGetFramebufferSize(window, &display_w, &display_h);
- glViewport(0, 0, display_w, display_h);
+ int width, height;
+ glfwGetFramebufferSize(window, &width, &height);
+ glViewport(0, 0, width, height);
glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
glClear(GL_COLOR_BUFFER_BIT);
@@ -92,14 +105,14 @@ int main(int, char**) {
glfwSwapBuffers(window);
}
- // === КОРРЕКТНОЕ ВЫКЛЮЧЕНИЕ gRPC СЕРВЕРА ===
+ // выключение gRPC
std::cout << "Shutting down gRPC server..." << std::endl;
// Останавливаем сервер (это разблокирует метод Wait() в фоновом потоке)
- grpc_server->Shutdown();
+ gRPCServer->Shutdown();
// Обязательно дожидаемся завершения фонового потока перед выходом из main
- if (grpc_thread.joinable()) {
- grpc_thread.join();
+ if (gRPCThread.joinable()) {
+ gRPCThread.join();
}
std::cout << "gRPC server thread joined." << std::endl;
// =========================================
diff --git a/proto/serialCtrl.proto b/proto/serialCtrl.proto
new file mode 100644
index 0000000..aeb1a96
--- /dev/null
+++ b/proto/serialCtrl.proto
@@ -0,0 +1,305 @@
+/**
+ * @file serial.proto
+ * @brief Протокол обмена между GUI‑клиентом и backend‑службой
+ * для работы с последовательным (UART/RS‑232/USB‑CDC) портом.
+ *
+ * Протокол построен на базе **Protocol Buffers 3** и **gRPC**.
+ * Он покрывает все типичные задачи:
+ * - перечисление доступных портов;
+ * - открытие/закрытие порта с полным набором параметров;
+ * - отправку и приём произвольных пакетов;
+ * - ведение журнала (лог‑стрим) всех переданных/полученных байтов
+ * – удобно для отладки и верификации протокола.
+ *
+ * Файл снабжён Doxygen‑комментариями, поэтому из него можно
+ * автоматически получить красивую HTML‑/PDF‑документацию.
+ */
+syntax = "proto3";
+
+package serial;
+
+/* -------------------------------------------------------------------------- */
+/* Enums – вспомогательные типы */
+/* -------------------------------------------------------------------------- */
+
+/**
+ * @enum Parity
+ * @brief Возможные варианты чётности (parity) при работе с UART.
+ *
+ * Имена полностью совпадают с теми, что использует библиотека **pyserial**
+ * (и, в принципе, большинство C/C++‑библиотек для последовательных портов).
+ */
+enum Parity {
+ PARITY_NONE = 0; /// Без чётности.
+ PARITY_EVEN = 1; /// Чётность чётных битов.
+ PARITY_ODD = 2; /// Чётность нечётных битов.
+ PARITY_MARK = 3; /// Маркер (всегда 1).
+ PARITY_SPACE= 4; /// Пробел (всегда 0).
+}
+
+/**
+ * @enum StopBits
+ * @brief Конфигурация стоп‑битов.
+ *
+ * Значения соответствуют типичным настройкам UART‑контроллера.
+ */
+enum StopBits {
+ STOPBITS_ONE = 0; /// Один стоп‑бит.
+ STOPBITS_ONE_POINT_FIVE = 1; /// Полтора стоп‑бита (реже используется).
+ STOPBITS_TWO = 2; /// Два стоп‑бита.
+}
+
+/**
+ * @enum StatusCode
+ * @brief Универсальный набор кодов возврата для большинства RPC‑методов.
+ *
+ * При необходимости можно добавить новые коды, но текущего набора достаточно
+ * для базовых сценариев.
+ */
+enum StatusCode {
+ OK = 0; /// Операция выполнена успешно.
+ INVALID_ARGUMENT = 1; /// Переданы некорректные аргументы (например, отрицательная скорость).
+ NOT_FOUND = 2; /// Запрашиваемый объект (порт, файл и т.п.) не найден.
+ INTERNAL_ERROR = 3; /// Внутренняя ошибка сервера (исключение, недоступный ресурс).
+ NOT_CONNECTED = 4; /// Операция невозможна, т.к. порт не открыт.
+}
+
+/* -------------------------------------------------------------------------- */
+/* Сообщения – описание данных, передаваемых по gRPC */
+/* -------------------------------------------------------------------------- */
+
+/**
+ * @message SerialDevice
+ * @brief Информация об одном последовательном порте, доступном в системе.
+ *
+ * @param port_name Системное имя порта (например, «COM3», «/dev/ttyUSB0»).
+ * @param description Человекочитаемое описание, если ОС его предоставляет.
+ * @param hardware_id Строка‑идентификатор устройства (VID/PID и т.п.).
+ */
+message SerialDevice {
+ string port_name = 1;
+ string description = 2;
+ string hardware_id = 3;
+}
+
+/**
+ * @message SerialConfig
+ * @brief Полный набор параметров, необходимый для открытия последовательного порта.
+ *
+ * @param port_name Должно совпадать с `SerialDevice.port_name`.
+ * @param baud_rate Скорость в бодах (например, 9600, 115200 …).
+ * @param data_bits Количество битов данных (5‑8, обычно 8).
+ * @param parity Чётность (см. enum @ref Parity).
+ * @param stop_bits Стоп‑биты (см. enum @ref StopBits).
+ * @param packet_size Фиксированный размер полезной нагрузки пакета.
+ * 0 → размер произвольный (по умолчанию).
+ */
+message SerialConfig {
+ string port_name = 1;
+ uint32 baud_rate = 2;
+ uint32 data_bits = 3;
+ Parity parity = 4;
+ StopBits stop_bits = 5;
+ uint32 packet_size = 6;
+}
+
+/**
+ * @message ConfigResponse
+ * @brief Ответ на запрос `Open`/`Close`. Содержит статус операции и,
+ * при ошибке, человекочитаемое сообщение.
+ *
+ * @param status Код статуса (см. enum @ref StatusCode).
+ * @param error Текстовое пояснение, заполнено только если `status != OK`.
+ */
+message ConfigResponse {
+ StatusCode status = 1;
+ string error = 2;
+}
+
+/**
+ * @message Packet
+ * @brief Описание логической "пакетной" единицы, которую GUI отправляет
+ * на backend, а backend – в последовательный порт.
+ *
+ * Полезная нагрузка (`payload`) передаётся «как есть» (raw‑bytes).
+ *
+ * @param seq_id Последовательный номер пакета, генерируется клиентом.
+ * Удобен для отладки и согласования запрос‑ответ.
+ * @param payload Бинарные данные, которые действительно окажутся на линии.
+ * @param tags Необязательная карта «ключ → значение», используемая
+ * только внутри программы (не попадает в кадр UART).
+ */
+message Packet {
+ uint64 seq_id = 1;
+ bytes payload = 2;
+ map tags = 3;
+}
+
+/**
+ * @message SendResponse
+ * @brief Ответ на RPC `SendPacket`. Содержит статус, сообщение об ошибке и
+ * «эхо‑идентификатор», позволяющий клиенту убедиться, что ответ
+ * относится к конкретному запросу.
+ *
+ * @param status Код статуса (см. enum @ref StatusCode).
+ * @param error Текстовое описание ошибки (если есть).
+ * @param echo_seq_id Идентификатор пакета, пришедший в запросе.
+ */
+message SendResponse {
+ StatusCode status = 1;
+ string error = 2;
+ uint64 echo_seq_id = 3;
+}
+
+/**
+ * @message SerialLogEntry
+ * @brief Описание одного события (TX или RX) на уровне сырых байтов.
+ *
+ * Это «протокол‑верификация»: каждая запись сохраняется в журнал
+ * и может быть передана клиенту в реальном времени.
+ *
+ * @param dir Направление трафика (TX – передача, RX – приём).
+ * @param timestamp Время события в миллисекундах с начала эпохи Unix.
+ * @param raw_data Точные байты, полученные/отправленные в этом событии.
+ * @param note Необязательная строка‑комментарий (например,
+ * «checksum ok», «frame start», …).
+ */
+message SerialLogEntry {
+ /** @brief Направление трафика. */
+ enum Direction {
+ /** @brief Пакет был отправлен в порт. */
+ TX = 0;
+ /** @brief Пакет был получен из порта. */
+ RX = 1;
+ }
+ Direction dir = 1;
+ uint64 timestamp = 2;
+ bytes raw_data = 3;
+ string note = 4;
+}
+
+/**
+ * @message LogStreamRequest
+ * @brief Параметры подписки на поток журнала.
+ *
+ * @param from_start Если true – клиент получает *все* накопленные записи,
+ * иначе – только новые, появившиеся после установления
+ * соединения.
+ */
+message LogStreamRequest {
+ bool from_start = 1;
+}
+
+/**
+ * @message DeviceList
+ * @brief Обёртка, возвращающая список найденных последовательных портов.
+ *
+ * @param devices Список `SerialDevice`.
+ */
+message DeviceList {
+ repeated SerialDevice devices = 1;
+}
+
+/* -------------------------------------------------------------------------- */
+/* Service definition – набор RPC‑методов, которые реализует backend */
+/* -------------------------------------------------------------------------- */
+
+/**
+ * @service SerialService
+ * @brief gRPC‑сервис, предоставляющий весь функционал работы с
+ * последовательным портом и журналированием.
+ *
+ * Каждый метод описан ниже, вместе с его параметрами и возвращаемыми
+ * типами.
+ */
+service SerialService {
+
+ // ----------------------------------------------------------------------
+ // Device discovery
+ // ----------------------------------------------------------------------
+ /**
+ * @brief Возвращает список всех последовательных портов, обнаруженных в системе.
+ *
+ * @param request Пустое сообщение (`google.protobuf.Empty`).
+ * @return `DeviceList` – массив `SerialDevice`.
+ */
+ rpc ListDevices (google.protobuf.Empty) returns (DeviceList);
+
+ // ----------------------------------------------------------------------
+ // Connection handling
+ // ----------------------------------------------------------------------
+ /**
+ * @brief Открывает (или переоткрывает) последовательный порт с указанными
+ * параметрами.
+ *
+ * @param cfg Полный набор параметров соединения (`SerialConfig`).
+ * @return `ConfigResponse` – статус операции.
+ */
+ rpc Open (SerialConfig) returns (ConfigResponse);
+
+ /**
+ * @brief Закрывает текущий открытый порт (если он был открыт).
+ *
+ * @param request Пустое сообщение.
+ * @return `ConfigResponse` – статус операции.
+ */
+ rpc Close (google.protobuf.Empty) returns (ConfigResponse);
+
+ /**
+ * @brief Возвращает конфигурацию, с которой в данный момент открыт порт.
+ *
+ * Если порт закрыт, возвращается пустой `SerialConfig`.
+ *
+ * @param request Пустое сообщение.
+ * @return `SerialConfig`.
+ */
+ rpc GetCurrentConfig (google.protobuf.Empty) returns (SerialConfig);
+
+ // ----------------------------------------------------------------------
+ // Packet transmission
+ // ----------------------------------------------------------------------
+ /**
+ * @brief Отправка одного пакета в открытый последовательный порт.
+ *
+ * Если в `SerialConfig.packet_size` задан фиксированный размер, то
+ * длина `payload` должна строго соответствовать этому размеру.
+ *
+ * @param pkt Пакет для отправки (`Packet`).
+ * @return `SendResponse` – результат записи в порт.
+ */
+ rpc SendPacket (Packet) returns (SendResponse);
+
+ /**
+ * @brief Получить один пакет из входящего буфера.
+ *
+ * Если буфер пуст, сервер возвращает gRPC‑ошибку `NOT_FOUND`.
+ *
+ * @param request Пустое сообщение.
+ * @return `Packet` (или ошибка).
+ */
+ rpc ReceivePacket (google.protobuf.Empty) returns (Packet);
+
+ // ----------------------------------------------------------------------
+ // Logging / protocol verification
+ // ----------------------------------------------------------------------
+ /**
+ * @brief Прямо вставить произвольную запись в журнал.
+ *
+ * Полезно для отладки: можно «симулировать» приходящие данные,
+ * не взаимодействуя с реальным оборудованием.
+ *
+ * @param entry Запись журнала (`SerialLogEntry`).
+ * @return Пустое сообщение (`google.protobuf.Empty`).
+ */
+ rpc LogRaw (SerialLogEntry) returns (google.protobuf.Empty);
+
+ /**
+ * @brief Подписка на поток всех событий журнала (TX и RX).
+ *
+ * Поток остаётся открытым до тех пор, пока клиент не отменит RPC.
+ *
+ * @param req Параметры подписки (`LogStreamRequest`).
+ * @return Поток `SerialLogEntry`.
+ */
+ rpc StreamLog (LogStreamRequest) returns (stream SerialLogEntry);
+}
\ No newline at end of file