diff --git a/Runcam_6/New_RunCam_interface.cpp b/Runcam_6/New_RunCam_interface.cpp new file mode 100644 index 0000000..a95fb46 --- /dev/null +++ b/Runcam_6/New_RunCam_interface.cpp @@ -0,0 +1,308 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "New_RunCam_registry.h" + +// --- CRC8 DVB-S2 Calculator --- +uint8_t crc8_dvb_s2(const uint8_t* data, size_t len) { + uint8_t crc = 0x00; + for (size_t i = 0; i < len; ++i) { + crc ^= data[i]; + for (int j = 0; j < 8; ++j) { + if (crc & 0x80) crc = (crc << 1) ^ 0xD5; + else crc <<= 1; + } + } + return crc; +} + +// --- FSM States --- +enum class RunCamState { Idle, WaitingResponse, Success, Error, Timeout, Disconnected }; +enum class RxState { WaitHeader, Buffering }; + +// --- Request Context --- +struct RunCamRequest { + std::vector packet; + int retries; + DWORD sendTime; + bool awaitingResponse; + std::function&, bool)> callback; +}; + +// --- RunCam Serial Class --- +class RunCamSerialFSM { +public: + bool blindMode = true; // DEFAULT ON - Send commands without waiting for response, assume success. + +private: + HANDLE hSerial = INVALID_HANDLE_VALUE; + RunCamState state = RunCamState::Idle; + std::queue requestQueue; + RunCamRequest currentRequest; + int maxRetries = 3; + int timeoutMs = 500; + RxState rxState = RxState::WaitHeader; + std::vector rxBuffer; + size_t currentExpectedLen = 5; + +public: + bool connect(const std::string& portName) { + hSerial = CreateFileA(portName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + if (hSerial == INVALID_HANDLE_VALUE) return false; + + DCB dcb = {0}; + dcb.DCBlength = sizeof(dcb); + GetCommState(hSerial, &dcb); + dcb.BaudRate = CBR_115200; + dcb.ByteSize = 8; + dcb.StopBits = ONESTOPBIT; + dcb.Parity = NOPARITY; + SetCommState(hSerial, &dcb); + + COMMTIMEOUTS timeouts = {0}; + timeouts.ReadIntervalTimeout = 20; + timeouts.ReadTotalTimeoutConstant = 50; + timeouts.WriteTotalTimeoutConstant = 50; + SetCommTimeouts(hSerial, &timeouts); + + state = RunCamState::Idle; + return true; + } + + void disconnect() { + if (hSerial != INVALID_HANDLE_VALUE) CloseHandle(hSerial); + hSerial = INVALID_HANDLE_VALUE; + state = RunCamState::Disconnected; + } + + void queueRequest(const std::vector& packet, std::function&, bool)> cb = nullptr) { + RunCamRequest req; + req.packet = packet; + req.retries = 0; + req.sendTime = 0; + req.awaitingResponse = false; + req.callback = cb; + requestQueue.push(req); + } + + void processFSM() { + switch (state) { + case RunCamState::Idle: + if (!requestQueue.empty()) { + currentRequest = requestQueue.front(); + requestQueue.pop(); + + // Flush and Send + rxState = RxState::WaitHeader; + rxBuffer.clear(); + PurgeComm(hSerial, PURGE_RXCLEAR | PURGE_RXABORT); + sendCommand(currentRequest.packet, true); + currentRequest.sendTime = GetTickCount(); + + if (blindMode) { + Sleep(50); // Tiny physical delay + state = RunCamState::Success; + if (currentRequest.callback) currentRequest.callback({}, true); + } else { + state = RunCamState::WaitingResponse; + } + } + break; + + case RunCamState::WaitingResponse: + if (GetTickCount() - currentRequest.sendTime > timeoutMs) { + state = RunCamState::Timeout; // Simple timeout for now + if (currentRequest.callback) currentRequest.callback({}, false); + } else if (processIncomingBytes()) { + state = RunCamState::Success; + if (currentRequest.callback) currentRequest.callback(rxBuffer, true); + } + break; + + case RunCamState::Success: + case RunCamState::Timeout: + case RunCamState::Error: + state = RunCamState::Idle; + break; + default: break; + } + } + +private: + void sendCommand(const std::vector& basePacket, bool appendCRC) { + std::vector packet = basePacket; + if (appendCRC) { + uint8_t crc = crc8_dvb_s2(packet.data(), packet.size()); + packet.push_back(crc); + } + DWORD bytesWritten; + WriteFile(hSerial, packet.data(), packet.size(), &bytesWritten, NULL); + } + + bool processIncomingBytes() { + uint8_t byte; + DWORD read; + while (ReadFile(hSerial, &byte, 1, &read, NULL) && read > 0) { + switch (rxState) { + case RxState::WaitHeader: + if (byte == 0xCC) { + rxBuffer.clear(); + rxBuffer.push_back(byte); + rxState = RxState::Buffering; + } + break; + case RxState::Buffering: + rxBuffer.push_back(byte); + if (rxBuffer.size() >= currentExpectedLen) { + rxState = RxState::WaitHeader; + return validateCRC(rxBuffer); + } + break; + } + } + return false; + } + + bool validateCRC(const std::vector& data) { + if (data.size() < 2) return false; + uint8_t crc = data.back(); + return crc8_dvb_s2(data.data(), data.size() - 1) == crc; + } +}; + +// --- MAIN MENU INTERFACE --- +int main() { + RunCamSerialFSM cam; + cam.blindMode = true; + + std::cout << "--- RUNCAM CONTROLLER (BLIND FSM) ---" << 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)) { + std::cerr << "ERROR: Check COM port!" << std::endl; + return 1; + } + + uint8_t textCmdID = 0x22; // Default to Horizontal (Can be toggled) + bool running = true; + + while (running) { + system("cls"); + std::cout << "--------------------------------" << std::endl; + std::cout << "--- SOAR RUNCAM CONTROLLER ---" << std::endl; + std::cout << "--------------------------------" << std::endl; + std::cout << "" << std::endl; + std::cout << " [1] Power Toggle (0x01)" << std::endl; + 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; + std::cout << " [9] Get Info (0x00)" << std::endl; + std::cout << "[10] Get Settings (0x10)" << std::endl; + std::cout << "[11] Read Detail (0x11)" << std::endl; + std::cout << "[12] Write Setting (0x13)" << std::endl; + std::cout << "--------------------------------" << std::endl; + std::cout << " [6] OSD Menu OPEN (Locks Camera!)" << std::endl; + std::cout << " [7] OSD Menu CLOSE (Unlocks Camera)" << std::endl; + std::cout << " [8] Write Text (Requires Open [6])" << std::endl; + std::cout << "[13] OSD ENTER PRESS" << std::endl; + std::cout << "[14] OSD ENTER RELEASE" << std::endl; + std::cout << "-------- OSD NAVIGATION --------" << std::endl; + std::cout << " [w] OSD UP" << std::endl; + std::cout << " [a] OSD LEFT" << std::endl; + std::cout << " [s] OSD DOWN" << std::endl; + std::cout << " [d] OSD RIGHT" << std::endl; + std::cout << "--------------------------------" << std::endl; + std::cout << " [p] Protocol Toggle (Current: " << (textCmdID == 0x22 ? "0x22 Horizontal" : "0x23 Vertical") << ")" << std::endl; + std::cout << " [x] Exit" << std::endl; + + std::cout << "\n Command >> "; + std::string input; + std::getline(std::cin, input); + char key = input.empty() ? 0 : input[0]; + + if (input == "10") key = 'g'; + if (input == "11") key = 'h'; + if (input == "12") key = 'i'; + if (input == "13") key = 'j'; + if (input == "14") key = 'k'; + + switch (key) { + case '1': cam.queueRequest({0xCC, CMD_CAM_CONTROL, ACT_POWER_BTN}); break; + case '2': cam.queueRequest(RunCamHex::START_REC); break; + case '3': cam.queueRequest(RunCamHex::STOP_REC); break; + case '4': cam.queueRequest(RunCamHex::WIFI_BTN); break; + case '5': cam.queueRequest(RunCamHex::CHANGE_MODE); break; + case '6': + std::cout << "Opening OSD..." << std::endl; + cam.queueRequest(RunCamHex::OSD_OPEN); + break; + case '7': + std::cout << "Closing OSD..." << std::endl; + cam.queueRequest(RunCamHex::OSD_CLOSE); + break; + case '8': { + 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 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); + break; + } + case '9': cam.queueRequest(RunCamHex::GET_INFO); break; + case 'g': cam.queueRequest(RunCamHex::GET_SETTINGS); break; + case 'h': cam.queueRequest(RunCamHex::READ_DETAIL); break; + case 'i': cam.queueRequest(RunCamHex::WRITE_SETTING); break; + case 'j': cam.queueRequest(RunCamHex::OSD_ENTER_PRESS); break; + case 'k': cam.queueRequest(RunCamHex::OSD_ENTER_RELEASE); break; + case 'w': { + std::vector packet = {0xCC, CMD_5KEY_PRESS, OSD_UP}; + cam.queueRequest(packet); + break; + } + case 'a': { + std::vector packet = {0xCC, CMD_5KEY_PRESS, OSD_LEFT}; + cam.queueRequest(packet); + break; + } + case 's': { + std::vector packet = {0xCC, CMD_5KEY_PRESS, OSD_DOWN}; + cam.queueRequest(packet); + break; + } + case 'd': { + std::vector packet = {0xCC, CMD_5KEY_PRESS, OSD_RIGHT}; + cam.queueRequest(packet); + break; + } + case 'p': + textCmdID = (textCmdID == 0x22) ? 0x23 : 0x22; + break; + case 'x': running = false; break; + } + + std::cout << " [Sending...]"; + for(int i=0; i<5; i++) { + cam.processFSM(); + Sleep(20); + } + } + + cam.disconnect(); + return 0; +} \ No newline at end of file diff --git a/Runcam_6/New_RunCam_interface.exe b/Runcam_6/New_RunCam_interface.exe new file mode 100644 index 0000000..d0a7d2e Binary files /dev/null and b/Runcam_6/New_RunCam_interface.exe differ diff --git a/Runcam_6/New_RunCam_registry.h b/Runcam_6/New_RunCam_registry.h new file mode 100644 index 0000000..325f60e --- /dev/null +++ b/Runcam_6/New_RunCam_registry.h @@ -0,0 +1,57 @@ +#ifndef NEW_TEST_RUNCAM_REGISTRY_H +#define NEW_TEST_RUNCAM_REGISTRY_H + +#include +#include + +// --- PROTOCOL CONSTANTS --- +const uint8_t RC_HEADER = 0xCC; + +// --- 1. COMMAND IDs --- +enum CommandID : uint8_t { + CMD_GET_INFO = 0x00, + CMD_CAM_CONTROL = 0x01, + CMD_5KEY_PRESS = 0x02, + CMD_5KEY_RELEASE = 0x03, + CMD_OSD_HANDSHAKE = 0x04, + CMD_GET_SETTINGS = 0x10, + CMD_READ_DETAIL = 0x11, + CMD_WRITE_SETTING = 0x13, + CMD_WRITE_STRING = 0x22 +}; + +// --- 2. CAMERA CONTROL ACTIONS (Payloads for CMD 0x01) --- +enum ControlAction : uint8_t { + ACT_WIFI_BTN = 0x00, + ACT_POWER_BTN = 0x01, + ACT_CHANGE_MODE = 0x02, + ACT_START_REC = 0x03, + ACT_STOP_REC = 0x04 +}; + +// --- 3. 5-KEY OSD ACTIONS (Payloads for CMD 0x02) --- +enum OSDAction : uint8_t { + OSD_ENTER = 0x01, + OSD_LEFT = 0x02, + OSD_RIGHT = 0x03, + OSD_UP = 0x04, + OSD_DOWN = 0x05 +}; + +// --- 4. PRE-CALCULATED PACKETS --- +namespace RunCamHex { + const std::vector GET_INFO = {0xCC, 0x00, 0x00}; + const std::vector START_REC = {0xCC, 0x01, 0x03}; + const std::vector STOP_REC = {0xCC, 0x01, 0x04}; + const std::vector OSD_OPEN = {0xCC, 0x04, 0x01}; + const std::vector OSD_CLOSE = {0xCC, 0x04, 0x02}; + const std::vector CHANGE_MODE = {RC_HEADER, CMD_CAM_CONTROL, ACT_CHANGE_MODE}; + const std::vector WIFI_BTN = {RC_HEADER, CMD_CAM_CONTROL, ACT_WIFI_BTN}; + const std::vector OSD_ENTER_PRESS = {RC_HEADER, CMD_5KEY_PRESS, OSD_ENTER}; + const std::vector OSD_ENTER_RELEASE = {RC_HEADER, CMD_5KEY_RELEASE, OSD_ENTER}; + const std::vector GET_SETTINGS = {RC_HEADER, CMD_GET_SETTINGS, 0x00}; + const std::vector READ_DETAIL = {RC_HEADER, CMD_READ_DETAIL, 0x00}; + const std::vector WRITE_SETTING = {RC_HEADER, CMD_WRITE_SETTING, 0x00}; +} + +#endif // NEW_TEST_RUNCAM_REGISTRY_H diff --git a/Runcam_6/README.md b/Runcam_6/README.md new file mode 100644 index 0000000..53c9d94 --- /dev/null +++ b/Runcam_6/README.md @@ -0,0 +1,67 @@ + +# RunCam Serial Interface (Testing Tool) + +This directory contains a C++ command-line tool for testing and controlling RunCam devices over a serial (UART/COM) connection. It is intended for development and diagnostic purposes mainly. + +## Features + +- Send basic control commands to a RunCam (power(simulation - can not turn on/off), OSD, etc.) +- Write custom text to the OSD +- Simple finite state machine (FSM) for command queueing +- Supports "blind mode" (no response required) + +## Usage + +1. **Build the Project** + +- Open the project in your C++ IDE or use a command-line compiler (e.g., MSVC, MinGW). +- Ensure you are building on **Windows** (uses Win32 serial APIs). + +2. **Connect the RunCam** + +- Attach the RunCam to your PC via a USB-to-serial adapter. +- Note the COM port number (e.g., `COM3`). + +3. **Run the Interface** + +- Launch the compiled executable. +- When prompted, enter the COM port **number** (e.g., `3` for `COM3`). + +4. **Menu Options** + +### Use the keyboard to select commands: + +- `[1]` Power Toggle +- `[2]` Start Record - Non Functional (At least outside OSD) +- `[3]` Stop Record - Non Functional (At least outside OSD) +- `[4]` WiFi Button +- `[5]` Change Mode +- ----- OSD and settings ----- +- `[6]` OSD Menu OPEN (locks camera) +- `[7]` OSD Menu CLOSE (unlocks camera) +- `[8]` Write Text (requires OSD open) +- `[9]` Get Info +- `[10]` Get Settings +- `[11]` Read Detail +- `[12]` Write Setting +- ----- OSD Navigation ----- +- `[13]` OSD ENTER PRESS +- `[14]` OSD ENTER RELEASE +- `[w]` OSD UP +- `[a]` OSD LEFT +- `[s]` OSD DOWN +- `[d]` OSD RIGHT +- `[p]` Toggle protocol for text (0x22/0x23) +- `[x]` Exit + +5. **Notes** + +- The tool defaults to "blind mode" (commands are sent without waiting for a response). +- For text writing, you will be prompted for X/Y coordinates and the text string. +- OSD must be open to write text. +- This tool is for **testing and development**. + +## File Overview + +- `New_RunCam_interface.cpp` — Main interface and FSM logic +- `New_RunCam_registry.h` — Protocol constants and packet templates