Fixed bug nudging befor calibration is run
This commit is contained in:
parent
25e39e09f2
commit
e721ac74c2
|
|
@ -217,9 +217,9 @@ away in the log. It only appears once something has run.
|
|||
|
||||
When a `gimbal calib` completes it is applied to the live session and the strip shows a highlighted
|
||||
yes/no prompt — **`Save this calibration to config as the new default? (y / n)`**. Press **`y`** to
|
||||
write the fitted `[Motor]` `*_counts_per_deg` / `*_zero_count` back into the `config.ini` the program
|
||||
was launched with (replacing those keys in place, preserving everything else) so it persists across
|
||||
restarts; press **`n`** to keep it for this session only. The keys are active only while the prompt is
|
||||
write the full `[Motor]` per-axis map — `*_counts_per_deg`, `*_zero_count`, and `*_min_deg`/`*_max_deg`
|
||||
— back into the `config.ini` the program was launched with (replacing those keys in place, preserving
|
||||
everything else) so it persists across restarts; press **`n`** to keep it for this session only. The keys are active only while the prompt is
|
||||
showing. (Until you answer `y`, calibration remains session-only — see
|
||||
[known-issues.md](known-issues.md).)
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,13 @@ doctest unit-test suite (`ctest`).
|
|||
- **`gimbal calib` persistence**: the fitted `counts_per_deg`/`zero_count` are always applied to the live
|
||||
session (display, manual moves, MQTT heading, **and** the capture scheduler) and written to
|
||||
`logs/calib_*.log`. In the **TUI**, the activity strip then prompts `Save … as the new default? (y/n)`
|
||||
— `y` writes them into `[Motor]` in `config.ini` (persists across restarts), `n` keeps them
|
||||
— `y` writes the full per-axis `[Motor]` map (`*_counts_per_deg`, `*_zero_count`,
|
||||
`*_min_deg`/`*_max_deg`) into `config.ini` (persists across restarts), `n` keeps them
|
||||
session-only. In the **headless** console there is no prompt, so calibration stays session-only there;
|
||||
copy the logged values into `[Motor]` by hand to keep them.
|
||||
- **`gimbal nudge`** moves a fixed fraction of the **homed** endstop-to-endstop travel (from the
|
||||
firmware dump), so it is independent of the degrees↔counts calibration. If no dump has been captured
|
||||
yet (not homed / no `gimbal dump`), nudge requests one and does nothing that press — it never falls
|
||||
back to the configured degree clamps (which, if `*_min_deg`/`*_max_deg` were unset, produced absurd
|
||||
±100000° steps).
|
||||
- DHT11 temperature/humidity is still a Sensors-panel placeholder (the IMU half is integrated).
|
||||
|
|
|
|||
|
|
@ -530,15 +530,17 @@ struct Application::Impl {
|
|||
return static_cast<bool>(iss >> a >> b);
|
||||
}
|
||||
|
||||
// Soft-limit travel (counts) for an axis from the last dump, else the
|
||||
// configured degree range converted to counts. Returns 0 if unknown.
|
||||
long axisRangeCounts(char axis) {
|
||||
// Homed physical travel (counts) for an axis: the endstop-to-endstop span from
|
||||
// the last firmware dump. Returns 0 if no dump has been captured yet (caller
|
||||
// should request one). Deliberately does NOT fall back to the configured degree
|
||||
// range × counts_per_deg — that conflates calibration with physical travel and,
|
||||
// when min/max are unset, yields an absurd span.
|
||||
long homedTravelCounts(char axis) {
|
||||
DumpData d = parseDump(motor->lastDump());
|
||||
if (d.valid)
|
||||
for (const auto& ax : d.axes)
|
||||
if (ax.axis == axis) return std::labs(ax.lim_pos - ax.lim_neg);
|
||||
const AxisMap& m = (axis == 'Y') ? cfg.geometry.yaw : cfg.geometry.pitch;
|
||||
return std::labs(m.toCounts(m.max_deg) - m.toCounts(m.min_deg));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// `gimbal <subcommand>` — unified, lowercase motor control. `move` is degrees
|
||||
|
|
@ -579,8 +581,16 @@ struct Application::Impl {
|
|||
char axis = (tok[2][0] == 'p' || tok[2][0] == 'P') ? 'P' : 'Y';
|
||||
double pct = 0;
|
||||
try { pct = std::stod(tok[3]); } catch (...) { LOG_WARN << "gimbal nudge: bad percent"; return; }
|
||||
long range = axisRangeCounts(axis);
|
||||
if (range <= 0) { LOG_WARN << "gimbal nudge: travel unknown; run gimbal home/dump first"; motor->sendCommand("DUMP"); return; }
|
||||
// Nudge is a fraction of the *homed* physical travel (endstop-to-endstop
|
||||
// from the firmware dump), so it is independent of calibration and never
|
||||
// a fraction of the configured degree clamps. If no dump is available
|
||||
// yet, request one and bail rather than guessing.
|
||||
long range = homedTravelCounts(axis);
|
||||
if (range <= 0) {
|
||||
LOG_WARN << "gimbal nudge: homed travel unknown; home or 'gimbal dump' first";
|
||||
motor->sendCommand("DUMP");
|
||||
return;
|
||||
}
|
||||
MotorTelemetry tel = motor->telemetry();
|
||||
long cur = (axis == 'Y') ? tel.yaw.xenc : tel.pitch.xenc;
|
||||
long target = cur + static_cast<long>(pct / 100.0 * range);
|
||||
|
|
|
|||
|
|
@ -219,11 +219,19 @@ bool saveMotorCalibration(const std::string& path, const Geometry& geo) {
|
|||
std::snprintf(b, sizeof(b), "%.10g", v);
|
||||
return std::string(b);
|
||||
};
|
||||
// Persist the full per-axis map, not just the fitted counts_per_deg/zero_count:
|
||||
// min_deg/max_deg are part of the geometry and consumers (e.g. travel-range
|
||||
// estimates) rely on them, so writing them keeps the [Motor] block complete and
|
||||
// self-consistent rather than depending on pre-existing lines.
|
||||
const std::vector<std::pair<std::string, std::string>> kv = {
|
||||
{"yaw_counts_per_deg", num(geo.yaw.counts_per_deg)},
|
||||
{"yaw_zero_count", std::to_string(geo.yaw.zero_count)},
|
||||
{"yaw_min_deg", num(geo.yaw.min_deg)},
|
||||
{"yaw_max_deg", num(geo.yaw.max_deg)},
|
||||
{"pitch_counts_per_deg", num(geo.pitch.counts_per_deg)},
|
||||
{"pitch_zero_count", std::to_string(geo.pitch.zero_count)},
|
||||
{"pitch_min_deg", num(geo.pitch.min_deg)},
|
||||
{"pitch_max_deg", num(geo.pitch.max_deg)},
|
||||
};
|
||||
const std::string updated = updateIniSectionKeys(buf.str(), "Motor", kv);
|
||||
|
||||
|
|
|
|||
|
|
@ -615,12 +615,12 @@ void TuiUi::uiLoop() {
|
|||
}
|
||||
if (overlay != Overlay::None && e == Event::Escape) { overlay = Overlay::None; return true; }
|
||||
// Arrow keys nudge the gimbal in steps (only when no overlay is open):
|
||||
// Left/Right = yaw -/+5%, Up/Down = pitch +/-10% of travel.
|
||||
// Left/Right = yaw -/+5%, Up/Down = pitch -/+10% of travel.
|
||||
if (overlay == Overlay::None && sink_) {
|
||||
if (e == Event::ArrowLeft) { sink_("gimbal nudge yaw -5"); return true; }
|
||||
if (e == Event::ArrowRight) { sink_("gimbal nudge yaw 5"); return true; }
|
||||
if (e == Event::ArrowUp) { sink_("gimbal nudge pitch 10"); return true; }
|
||||
if (e == Event::ArrowDown) { sink_("gimbal nudge pitch -10"); return true; }
|
||||
if (e == Event::ArrowUp) { sink_("gimbal nudge pitch -10"); return true; }
|
||||
if (e == Event::ArrowDown) { sink_("gimbal nudge pitch 10"); return true; }
|
||||
}
|
||||
if (!e.is_character()) return false;
|
||||
const std::string& c = e.character();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
#include "fgc/Config.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
|
||||
using namespace fgc;
|
||||
|
||||
|
|
@ -78,3 +80,41 @@ TEST_CASE("updateIniSectionKeys appends keys missing from the section") {
|
|||
// re-parsing must see the appended key under [Motor]
|
||||
CHECK(out.find("pitch_zero_count = 7") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("saveMotorCalibration round-trips the full per-axis map") {
|
||||
// Write a starting config, save a calibrated Geometry into it, reload, and
|
||||
// confirm every [Motor] parameter (incl. min/max_deg) survives, while another
|
||||
// section is left untouched.
|
||||
const std::string path = "test_calib_roundtrip.ini";
|
||||
{
|
||||
std::ofstream f(path, std::ios::trunc);
|
||||
f << "[General]\ntower_name = rig7\n\n"
|
||||
"[Motor]\n"
|
||||
"yaw_counts_per_deg = 1.0\n"
|
||||
"yaw_zero_count = 0\n"
|
||||
"yaw_min_deg = -90\n"
|
||||
"yaw_max_deg = 90\n"
|
||||
"pitch_counts_per_deg = 1.0\n"
|
||||
"pitch_zero_count = 0\n"
|
||||
"pitch_min_deg = 0\n"
|
||||
"pitch_max_deg = 60\n";
|
||||
}
|
||||
|
||||
Geometry geo;
|
||||
geo.yaw = {983.33, 500123, -88.0, 92.0};
|
||||
geo.pitch = {8333.33, -4200, -2.0, 61.5};
|
||||
REQUIRE(saveMotorCalibration(path, geo));
|
||||
|
||||
AppConfig cfg = ConfigLoader::loadFromFile(path);
|
||||
CHECK(cfg.general.tower_name == "rig7"); // unrelated section preserved
|
||||
CHECK(cfg.geometry.yaw.counts_per_deg == doctest::Approx(983.33));
|
||||
CHECK(cfg.geometry.yaw.zero_count == 500123);
|
||||
CHECK(cfg.geometry.yaw.min_deg == doctest::Approx(-88.0));
|
||||
CHECK(cfg.geometry.yaw.max_deg == doctest::Approx(92.0));
|
||||
CHECK(cfg.geometry.pitch.counts_per_deg == doctest::Approx(8333.33));
|
||||
CHECK(cfg.geometry.pitch.zero_count == -4200);
|
||||
CHECK(cfg.geometry.pitch.min_deg == doctest::Approx(-2.0));
|
||||
CHECK(cfg.geometry.pitch.max_deg == doctest::Approx(61.5));
|
||||
|
||||
std::remove(path.c_str());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue