diff --git a/include/fgc/ui/UiSnapshot.h b/include/fgc/ui/UiSnapshot.h index 06b38f3..fdc7a90 100644 --- a/include/fgc/ui/UiSnapshot.h +++ b/include/fgc/ui/UiSnapshot.h @@ -289,11 +289,22 @@ struct CalibAxisView { double r2 = 0; int n = 0; }; +// The degrees<->counts map an axis is actually using right now: from config.ini +// at startup, replaced in memory by a fit or a homing derivation. Distinct from +// CalibAxisView, which describes the last FIT and only exists once one has run. +struct AxisMapView { + double counts_per_deg = 0; + double deg_at_center = 0; +}; + struct CalibResultView { - bool has = false; + bool has = false; // has a fit run THIS SESSION? long long ts_ms = 0; bool pitch_present = false; CalibAxisView yaw, pitch; + // Always populated. "No fit this session" is not the same as "uncalibrated": + // the working calibration usually comes from config.ini and predates the run. + AxisMapView live_yaw, live_pitch; }; // Worst-case encoder tracking error from the last DIAG run, per axis, for the diff --git a/src/core/Application.cpp b/src/core/Application.cpp index c885752..2587f65 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -907,6 +907,9 @@ struct Application::Impl { // --- Activity strip + last calibration --- s.calib = last_calib_view; s.calib.pitch_present = s.gimbal.pitch_present || s.calib.pitch_present; + // The map in force right now, whether or not anything was fitted today. + s.calib.live_yaw = {cfg.geometry.yaw.counts_per_deg, cfg.geometry.yaw.deg_at_center}; + s.calib.live_pitch = {cfg.geometry.pitch.counts_per_deg, cfg.geometry.pitch.deg_at_center}; s.diag = last_diag_view; s.test = last_test_view; fillActivity(s); @@ -1040,7 +1043,11 @@ struct Application::Impl { } clear_homing{homing_active_}; motor->sendCommand("ENABLE Y"); - motor->sendCommand("ENABLE P"); + // Only address axes this tower actually has. A yaw-only rig answers + // "ERR axis not present" to every pitch command, which arrives in the log + // as a row of warnings at every startup - noise that trains an operator to + // ignore exactly the channel that reports real faults. + if (cfg.hardware.hasAxis("pitch")) motor->sendCommand("ENABLE P"); // Tell the firmware where to anchor the homed midpoint and how much // clearance to keep from each endstop, BEFORE homing: finishHoming() uses // the centre count to re-origin, so a late send would apply to the next @@ -1050,7 +1057,10 @@ struct Application::Impl { // Confirm the current landed BEFORE driving into anything. This is the // only check standing between a stale firmware and an endstop that cannot // survive full torque. - if (yaw_current_.ok || pitch_current_.ok) { + // Only when a reduction is actually depended on. An axis running at one + // current for everything has nothing to lose if the command went astray, + // and the check costs a DUMP round trip whose output floods the log. + if (cfg.drivetrain.yaw.reducesForHoming() || cfg.drivetrain.pitch.reducesForHoming()) { if (auto dd = freshDump(std::chrono::seconds(3))) { if (!verifyCurrentAdoption(*dd)) return; } else { @@ -1122,7 +1132,7 @@ struct Application::Impl { } // Production move speeds for subsequent MOVE commands. motor->sendCommand("SPEED Y 50000"); - motor->sendCommand("SPEED P 150000"); + if (cfg.hardware.hasAxis("pitch")) motor->sendCommand("SPEED P 150000"); LOG_INFO << "Gimbal init finished"; } @@ -1208,9 +1218,11 @@ struct Application::Impl { // clampTarget() keeps clear of each hard limit. void sendAnchorConfig() { motor->sendCommand("CENTER Y " + std::to_string(cfg.geometry.yaw.center_count)); - motor->sendCommand("CENTER P " + std::to_string(cfg.geometry.pitch.center_count)); motor->sendCommand("MARGIN Y " + std::to_string(cfg.yaw_soft_margin_counts)); - motor->sendCommand("MARGIN P " + std::to_string(cfg.pitch_soft_margin_counts)); + if (cfg.hardware.hasAxis("pitch")) { + motor->sendCommand("CENTER P " + std::to_string(cfg.geometry.pitch.center_count)); + motor->sendCommand("MARGIN P " + std::to_string(cfg.pitch_soft_margin_counts)); + } // Motor current belongs here too: it governs homing torque, so it has to // land before the search, not after it. auto sendCurrent = [&](char axis, const TmcCurrentPlan& p) {