#include "fgc/HelpText.h" #include #include namespace fgc { namespace { std::string lower(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); return s; } // First whitespace-delimited token of a syntax string (the command verb), lowercased. std::string verbOf(const std::string& syntax) { auto end = syntax.find_first_of(" \t"); return lower(syntax.substr(0, end == std::string::npos ? syntax.size() : end)); } } // namespace const std::vector& helpCatalog() { // clang-format off static const std::vector catalog = { {"Positioning", "Aim the gimbal. 'move' is degrees; 'steps' is raw encoder counts.", { {"gimbal move ,", "Point the gimbal at an absolute heading/elevation in DEGREES.", { "Converts degrees to encoder counts (operator-calibrated) and sends a", "two-axis move so both axes start together; soft-clamped to travel limits.", "Example: gimbal move 30,-10 (yaw 30 deg, pitch -10 deg)"}}, {"gimbal steps ,", "Move both axes to absolute encoder COUNTS (no degree conversion).", { "Example: gimbal steps 100000,250000"}}, {"gimbal nudge <+/-pct>", "Relative step move by a percent of the axis travel.", { "Arrow keys do this: Left/Right = yaw -/+5%, Up/Down = pitch +/-10%.", "Example: gimbal nudge yaw -5"}}, {"gimbal home [y|p]", "Run the endstop-finding home sequence (both axes, or one).", { "Example: gimbal home / gimbal home y"}}, {"gimbal stop [y|p|all]", "Stop motion immediately (also cancels a running calibration).", {}}, {"gimbal speed ", "Set the max slew speed (counts/s) for an axis.", {}}, {"gimbal reset [y|p] / gimbal enable|disable / gimbal setpos ", "Other firmware motor controls.", {}}, }}, {"Diagnostics", "Inspect, self-test and calibrate the gimbal.", { {"gimbal dump", "Request a full firmware state dump and show it here.", { "Captured DUMP BEGIN..END block (build, uptime, reset cause, per-axis", "TMC5160 registers); shown in the gimbal 'g' expanded view.", "Equivalent offline tool: ./firmware/dump.sh"}}, {"gimbal diag [y|p|all]", "Run the firmware motor self-test; results stream to the LOG + a logfile.", { "Each axis is swept at several speeds/directions (DG lines). A PASS/FAIL", "summary is logged and saved to logs/diag_*.log. Axes must be homed first."}}, {"gimbal calib", "Calibrate steps<->degrees using the IMU (auto-homes; ~minutes).", { "Needs the IMU. Homes first if needed, calibrates PITCH at the first yaw", "position, moves pitch to 0, switches the IMU to a no-mag XKF profile", "(persists on the device), runs no-rotation + heading reset to zero yaw", "drift, then calibrates YAW. Applies the fit to the session and writes", "logs/calib_*.log. 'gimbal stop' cancels."}}, {"gimbal status", "Ask the firmware to emit one telemetry (ST) line now.", {}}, {"gimbal raw \"\"", "Low-level: send the quoted text to the firmware verbatim (a newline is", { "added automatically). Bypasses all wrappers/unit conversion for direct", "firmware control — use with care; sends exactly what you type.", "Example: gimbal raw \"MOVE 100000,250000\" / gimbal raw \"DUMP\""}}, }}, {"Capture", "Control image capture and encoding.", { {"start", "Begin the capture scan.", {}}, {"stop", "Halt the capture scan.", {}}, {"set fps ", "Set capture rate in images/second.", {}}, {"set camera fps ", "Set the camera sensor frame rate.", {}}, {"set camera jxlq ", "Set JPEG-XL distance (lower = higher quality).", {}}, {"set camera jxle ", "Set JPEG-XL encode effort.", {}}, {"set camera display <0|1>", "Toggle the local display window.", {}}, }}, {"Logging", "Control console/log verbosity.", { {"debug", "Toggle debug-level logging on/off.", {}}, {"trace [on|off]", "Toggle verbatim wire-trace categories.", { "Example: trace serial on / trace off"}}, }}, {"Session", "Help and exit.", { {"refresh", "Re-read live device state (TUI: press 'r').", { "Re-queries the IMU configuration (XKF profile, output settings) and", "requests a fresh firmware dump, updating the 'i' and 'g' expanded views.", "Use after changing the XKF profile externally. Briefly pauses the IMU", "stream while it re-enters the device's Config state."}}, {"help [topic]", "Show this reference; 'help ' expands one section.", { "Example: help positioning / help dump"}}, {"exit", "Shut down and quit.", {}}, }}, }; // clang-format on return catalog; } std::vector renderHelp(const std::string& topic) { std::vector out; const std::string q = lower(topic); if (q.empty()) { out.emplace_back("Available commands (type 'help ' for detail, e.g. 'help goto'):"); for (const auto& sec : helpCatalog()) { out.emplace_back(""); out.emplace_back("== " + sec.title + " == " + sec.blurb); for (const auto& e : sec.entries) out.emplace_back(" " + e.syntax + " - " + e.summary); } return out; } // Topic mode: match a section by title, or an entry by command verb. bool matched = false; for (const auto& sec : helpCatalog()) { const bool sec_match = lower(sec.title).find(q) != std::string::npos; for (const auto& e : sec.entries) { if (!sec_match && verbOf(e.syntax) != q) continue; matched = true; out.emplace_back(e.syntax); out.emplace_back(" " + e.summary); for (const auto& d : e.detail) out.emplace_back(" " + d); out.emplace_back(""); } } if (!matched) out.emplace_back("No help topic matching '" + topic + "'. Try 'help'."); return out; } } // namespace fgc