#pragma once #include #include #include namespace fgc { // The host half of the config handshake (hardware-config-plan.md section 6). // // Today the firmware carries a full parameter table and GUESSES when the host // says nothing, so the same drivetrain value exists in two places and they can // disagree. Instead the firmware declares what it needs and refuses to move // until it has it: // // firmware -> BOOT build=abc123 // firmware -> NEED Y steps_per_rev global_scaler sw_mode enc_deviation ... // host -> SET Y steps_per_rev 1210778 // host -> SET Y global_scaler 80 // ... // // This module is the pure part: read a NEED line, and work out which SET lines // answer it. Sending them, and deciding what to do when some cannot be answered, // belongs to the caller. // A NEED line: the firmware naming the parameters it will not run without. struct NeedRequest { char axis = '?'; // 'Y' or 'P' std::vector keys; }; // Returns nullopt for anything that is not a well-formed NEED line. A NEED with // no keys is well-formed and means "this axis needs nothing" - which is how a // firmware built for fewer axes than the rig has answers for the axis it lacks. std::optional parseNeedLine(const std::string& line); // Everything the host can answer for one axis. Each group carries its own // "declared" flag because a value the host does not have must be reported as // unanswered rather than sent as a zero - a zero here is a real setting, and a // silently zeroed current or margin is a damaged rig. struct AxisConfigSource { // From [Motor.] milliamps, via TmcCurrent.h. bool have_current = false; int global_scaler = 0; int run_irun = 0; int homing_irun = 0; // From [Motor] calibration and margins. Always known - they have defaults // that predate hardware.ini and are already sent as CENTER / MARGIN. long center_count = 0; long soft_margin = 0; // From [Motor.], the mechanism's own facts. bool have_travel = false; long steps_per_rev = 0; // expected full mechanical travel, in counts bool have_deviation = false; long enc_deviation = 0; // Speeds, already converted from deg/s to VMAX by the caller (AxisSpeed.h). // 0 means the conversion was refused - an uncalibrated scale - and the key // goes unanswered rather than stopping the axis with a VMAX of zero. long normal_vmax = 0; long homing_vmax = 0; long center_vmax = 0; // Wiring facts: whether this axis has physical endstop switches on the TMC's // REFL/REFR, and whether its encoder is aligned and usable. bool have_wiring = false; unsigned sw_mode = 0; bool has_encoder = true; }; struct FirmwareConfigPlan { // Ready-to-send command lines, in the order the firmware asked for them. std::vector sets; // Keys the host was asked for and has no value for. The firmware stays in // NEEDCFG until they are answered, so this is a startup failure, not a // warning to scroll past. std::vector unanswered; // Keys that are not part of the vocabulary at all: a firmware newer than // this host. Kept separate from `unanswered` because the fix is different - // upgrade the host, rather than fill in hardware.ini. std::vector unknown; bool complete() const { return unanswered.empty() && unknown.empty(); } }; // Map one NEED request onto SET lines. `center` / `margin` are accepted as // aliases for `center_count` / `soft_margin`, because those are the names the // existing CENTER and MARGIN commands use and hand-typed sessions rely on them. FirmwareConfigPlan planFirmwareConfig(const NeedRequest& need, const AxisConfigSource& src); } // namespace fgc