Conversation
- feat: Add new commands and UI improvements to Runcam interface
- feat: Add new commands and UI improvements to Runcam interface
There was a problem hiding this comment.
Pull request overview
Adds a Windows-focused C++ CLI utility under Runcam_6/ to exercise the RunCam serial protocol for development/diagnostics, including protocol constants/templates and usage documentation.
Changes:
- Added a menu-driven serial interface with a simple request queue + (optional) response-wait FSM and CRC handling.
- Introduced a protocol “registry” header with command/action enums and prebuilt packet templates.
- Added user documentation for building and operating the tool.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
Runcam_6/New_RunCam_interface.cpp |
Implements the CLI tool, serial comm setup, CRC, and an FSM-based send/receive loop. |
Runcam_6/New_RunCam_registry.h |
Defines protocol constants/enums and packet templates used by the interface. |
Runcam_6/README.md |
Documents features, build/run steps, and menu options for the tool. |
Runcam_6/New_RunCam_interface.exe |
Compiled binary included in the repo (should be removed from source control). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #include <thread> | ||
| #include <chrono> | ||
| #include <functional> | ||
| #include <conio.h> |
There was a problem hiding this comment.
These includes appear unused in this file (<thread>, <chrono>, <conio.h>). Removing unused headers speeds up builds and reduces the chance of platform/define collisions.
| #include <thread> | |
| #include <chrono> | |
| #include <functional> | |
| #include <conio.h> | |
| #include <functional> |
| // --- 4. PRE-CALCULATED PACKETS --- | ||
| namespace RunCamHex { | ||
| const std::vector<uint8_t> GET_INFO = {0xCC, 0x00, 0x00}; | ||
| const std::vector<uint8_t> START_REC = {0xCC, 0x01, 0x03}; | ||
| const std::vector<uint8_t> STOP_REC = {0xCC, 0x01, 0x04}; | ||
| const std::vector<uint8_t> OSD_OPEN = {0xCC, 0x04, 0x01}; | ||
| const std::vector<uint8_t> OSD_CLOSE = {0xCC, 0x04, 0x02}; | ||
| const std::vector<uint8_t> CHANGE_MODE = {RC_HEADER, CMD_CAM_CONTROL, ACT_CHANGE_MODE}; | ||
| const std::vector<uint8_t> WIFI_BTN = {RC_HEADER, CMD_CAM_CONTROL, ACT_WIFI_BTN}; | ||
| const std::vector<uint8_t> OSD_ENTER_PRESS = {RC_HEADER, CMD_5KEY_PRESS, OSD_ENTER}; | ||
| const std::vector<uint8_t> OSD_ENTER_RELEASE = {RC_HEADER, CMD_5KEY_RELEASE, OSD_ENTER}; | ||
| const std::vector<uint8_t> GET_SETTINGS = {RC_HEADER, CMD_GET_SETTINGS, 0x00}; | ||
| const std::vector<uint8_t> READ_DETAIL = {RC_HEADER, CMD_READ_DETAIL, 0x00}; | ||
| const std::vector<uint8_t> WRITE_SETTING = {RC_HEADER, CMD_WRITE_SETTING, 0x00}; |
There was a problem hiding this comment.
The packet templates are defined as std::vector objects in a header, which incurs dynamic initialization (and potentially heap allocations) in every translation unit that includes this header. Consider switching these to inline constexpr std::array<uint8_t, N> (or std::span over arrays) to avoid runtime init overhead, and also consistently use RC_HEADER instead of mixing in raw 0xCC literals.
| - `[2]` Start Record - Non Functional (At least outside OSD) | ||
| - `[3]` Stop Record - Non Functional (At least outside OSD) |
There was a problem hiding this comment.
Spelling/formatting: "Non Functional" should be hyphenated ("Non-functional") and sentence casing could be made consistent in these menu descriptions.
| - `[2]` Start Record - Non Functional (At least outside OSD) | |
| - `[3]` Stop Record - Non Functional (At least outside OSD) | |
| - `[2]` Start Record - Non-functional (at least outside OSD) | |
| - `[3]` Stop Record - Non-functional (at least outside OSD) |
| case RxState::WaitHeader: | ||
| if (byte == 0xCC) { | ||
| rxBuffer.clear(); | ||
| rxBuffer.push_back(byte); | ||
| rxState = RxState::Buffering; | ||
| } |
There was a problem hiding this comment.
The header byte is hard-coded as 0xCC here even though RC_HEADER is defined in the registry. Using the shared constant avoids divergence if the header ever changes and keeps the parser consistent with the sender.
| int x, y; | ||
| std::string text; | ||
| std::cout << "\n--- WRITE TEXT (" << std::hex << (int)textCmdID << ") ---" << std::endl; | ||
| std::cout << "X (0-29): "; std::cin >> x; | ||
| std::cout << "Y (0-15): "; std::cin >> y; | ||
| std::cin.ignore(1000, '\n'); | ||
| std::cout << "Text: "; std::getline(std::cin, text); | ||
| std::vector<uint8_t> packet = {0xCC, textCmdID, (uint8_t)text.length(), (uint8_t)x, (uint8_t)y}; | ||
| for(char c : text) packet.push_back((uint8_t)c); | ||
| cam.queueRequest(packet); |
There was a problem hiding this comment.
Input validation is missing for text write parameters: text.length() is truncated to uint8_t, and negative/out-of-range x/y values will wrap when cast to uint8_t. Please clamp/validate x and y against the documented ranges and reject/limit text lengths beyond what the protocol supports before building the packet.
|
|
||
| ## Features | ||
|
|
||
| - Send basic control commands to a RunCam (power(simulation - can not turn on/off), OSD, etc.) |
There was a problem hiding this comment.
Spelling/grammar: "can not" should be "cannot" in this bullet for readability.
| - Send basic control commands to a RunCam (power(simulation - can not turn on/off), OSD, etc.) | |
| - Send basic control commands to a RunCam (power(simulation - cannot turn on/off), OSD, etc.) |
| int timeoutMs = 500; | ||
| RxState rxState = RxState::WaitHeader; | ||
| std::vector<uint8_t> rxBuffer; | ||
| size_t currentExpectedLen = 5; |
There was a problem hiding this comment.
currentExpectedLen is hard-coded to 5 bytes, so the receive FSM will treat all responses as fixed-length. Any variable-length response (or one longer than 5 bytes) will be truncated and CRC validation will fail, causing spurious timeouts. Consider parsing the protocol's length field (once buffered) to set the expected frame length dynamically before attempting CRC validation.
| size_t currentExpectedLen = 5; | |
| size_t currentExpectedLen = 0; |
| std::cout << " [2] Start Record (0x03) - Non Functional?" << std::endl; | ||
| std::cout << " [3] Stop Record (0x04)- Non Functional?" << std::endl; | ||
| std::cout << " [4] WiFi Button (0x00)" << std::endl; | ||
| std::cout << " [5] Change Mode (0x05)" << std::endl; |
There was a problem hiding this comment.
The menu label shows Change Mode as (0x05), but the action constant used by RunCamHex::CHANGE_MODE is ACT_CHANGE_MODE = 0x02. This mismatch will confuse users when correlating menu text to actual packets; update the displayed value (or print both command and action bytes).
| std::cout << " [5] Change Mode (0x05)" << std::endl; | |
| std::cout << " [5] Change Mode (cmd 0x05, act 0x02)" << std::endl; |
| std::cout << "Enter COM Port: "; | ||
| std::string comPort; | ||
| std::cin >> comPort; | ||
| std::cin.ignore(1000, '\n'); // Clear buffer | ||
|
|
||
| if (!cam.connect("\\\\.\\COM" + comPort)) { |
There was a problem hiding this comment.
The prompt accepts an arbitrary string, but the connection path is built as \\.\\COM + input. If the user types COM3 (common expectation from the prompt), it will try \\.\\COMCOM3 and fail. Either clarify the prompt to request only the port number (e.g., "3 for COM3") or normalize inputs that already include COM/\\.\\COM.
| std::cout << "Enter COM Port: "; | |
| std::string comPort; | |
| std::cin >> comPort; | |
| std::cin.ignore(1000, '\n'); // Clear buffer | |
| if (!cam.connect("\\\\.\\COM" + comPort)) { | |
| std::cout << "Enter COM Port (e.g., 3 or COM3): "; | |
| std::string comPort; | |
| std::cin >> comPort; | |
| std::cin.ignore(1000, '\n'); // Clear buffer | |
| // Normalize COM port input so that users can enter "3", "COM3", or a full path like "\\\\.\\COM3". | |
| std::string devicePath; | |
| if (comPort.rfind("\\\\.\\", 0) == 0) { | |
| // User entered a full device path such as "\\\\.\\COM3" | |
| devicePath = comPort; | |
| } else if (comPort.rfind("COM", 0) == 0 || comPort.rfind("com", 0) == 0) { | |
| // User entered "COM3" (case-insensitive); prepend the "\\\\.\\" prefix | |
| devicePath = "\\\\.\\" + comPort; | |
| } else { | |
| // Assume user entered only the numeric part, e.g., "3" | |
| devicePath = "\\\\.\\COM" + comPort; | |
| } | |
| if (!cam.connect(devicePath)) { |
| case '8': { | ||
| int x, y; | ||
| std::string text; | ||
| std::cout << "\n--- WRITE TEXT (" << std::hex << (int)textCmdID << ") ---" << std::endl; |
There was a problem hiding this comment.
std::hex is set on this stream and never restored. Stream formatting flags persist, which can lead to later integer output unexpectedly being printed in hex; consider restoring std::dec after printing the command id (or use a local std::ostringstream).
| std::cout << "\n--- WRITE TEXT (" << std::hex << (int)textCmdID << ") ---" << std::endl; | |
| std::cout << "\n--- WRITE TEXT (" << std::hex << (int)textCmdID << std::dec << ") ---" << std::endl; |
This pull request introduces a new C++ command-line tool for interfacing with RunCam devices over a serial connection, primarily for development and testing purposes. The main additions include an (optional) finite state machine (FSM) for serial communication, a registry of protocol constants and packet templates, and comprehensive documentation for usage and features.
Core functionality and protocol implementation:
New_RunCam_interface.cppimplementing a RunCam serial interface using a finite state machine (FSM), supporting command queueing, blind mode, and basic menu-driven user interaction for sending commands to the camera.New_RunCam_registry.hcontaining protocol constants, command/action enums, and pre-defined packet templates for RunCam operations.Documentation and usability:
README.mddetailing features, usage instructions, menu options, and file structure for the new tool, improving accessibility for developers and testers.