Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
308 changes: 308 additions & 0 deletions Runcam_6/New_RunCam_interface.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <windows.h>
#include <thread>
#include <chrono>
#include <functional>
#include <conio.h>
Comment on lines +6 to +9
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
#include <thread>
#include <chrono>
#include <functional>
#include <conio.h>
#include <functional>

Copilot uses AI. Check for mistakes.

#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<uint8_t> packet;
int retries;
DWORD sendTime;
bool awaitingResponse;
std::function<void(const std::vector<uint8_t>&, 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<RunCamRequest> requestQueue;
RunCamRequest currentRequest;
int maxRetries = 3;
int timeoutMs = 500;
RxState rxState = RxState::WaitHeader;
std::vector<uint8_t> rxBuffer;
size_t currentExpectedLen = 5;
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
size_t currentExpectedLen = 5;
size_t currentExpectedLen = 0;

Copilot uses AI. Check for mistakes.

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<uint8_t>& packet, std::function<void(const std::vector<uint8_t>&, 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<uint8_t>& basePacket, bool appendCRC) {
std::vector<uint8_t> 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;
}
Comment on lines +154 to +159
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
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<uint8_t>& 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)) {
Comment on lines +186 to +191
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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)) {

Copilot uses AI. Check for mistakes.
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;
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
std::cout << " [5] Change Mode (0x05)" << std::endl;
std::cout << " [5] Change Mode (cmd 0x05, act 0x02)" << std::endl;

Copilot uses AI. Check for mistakes.
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;
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
std::cout << "\n--- WRITE TEXT (" << std::hex << (int)textCmdID << ") ---" << std::endl;
std::cout << "\n--- WRITE TEXT (" << std::hex << (int)textCmdID << std::dec << ") ---" << std::endl;

Copilot uses AI. Check for mistakes.
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);
Comment on lines +255 to +264
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
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<uint8_t> packet = {0xCC, CMD_5KEY_PRESS, OSD_UP};
cam.queueRequest(packet);
break;
}
case 'a': {
std::vector<uint8_t> packet = {0xCC, CMD_5KEY_PRESS, OSD_LEFT};
cam.queueRequest(packet);
break;
}
case 's': {
std::vector<uint8_t> packet = {0xCC, CMD_5KEY_PRESS, OSD_DOWN};
cam.queueRequest(packet);
break;
}
case 'd': {
std::vector<uint8_t> 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;
}
Binary file added Runcam_6/New_RunCam_interface.exe
Binary file not shown.
57 changes: 57 additions & 0 deletions Runcam_6/New_RunCam_registry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#ifndef NEW_TEST_RUNCAM_REGISTRY_H
#define NEW_TEST_RUNCAM_REGISTRY_H

#include <cstdint>
#include <vector>

// --- 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<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};
Comment on lines +41 to +54
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
}

#endif // NEW_TEST_RUNCAM_REGISTRY_H
Loading