fwt_software/src/core/HelpText.cpp

140 lines
6.7 KiB
C++

#include "fgc/HelpText.h"
#include <algorithm>
#include <cctype>
namespace fgc {
namespace {
std::string lower(std::string s) {
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return static_cast<char>(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<HelpSection>& helpCatalog() {
// clang-format off
static const std::vector<HelpSection> catalog = {
{"Positioning", "Aim the gimbal. 'move' is degrees; 'steps' is raw encoder counts.", {
{"gimbal move <yaw>,<pitch>",
"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 <yaw>,<pitch>",
"Move both axes to absolute encoder COUNTS (no degree conversion).", {
"Example: gimbal steps 100000,250000"}},
{"gimbal nudge <yaw|pitch> <+/-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 <y|p> <vel>",
"Set the max slew speed (counts/s) for an axis.", {}},
{"gimbal reset [y|p] / gimbal enable|disable <y|p> / gimbal setpos <y|p> <v>",
"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 \"<command>\"",
"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 <rate>", "Set capture rate in images/second.", {}},
{"set camera fps <rate>", "Set the camera sensor frame rate.", {}},
{"set camera jxlq <dist>", "Set JPEG-XL distance (lower = higher quality).", {}},
{"set camera jxle <effort>", "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 <serial|mqtt|camera|control|all|off> [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 <topic>' expands one section.", {
"Example: help positioning / help dump"}},
{"exit", "Shut down and quit.", {}},
}},
};
// clang-format on
return catalog;
}
std::vector<std::string> renderHelp(const std::string& topic) {
std::vector<std::string> out;
const std::string q = lower(topic);
if (q.empty()) {
out.emplace_back("Available commands (type 'help <topic>' 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