fwt_software/include/fgc/FirmwareConfig.h

94 lines
3.8 KiB
C++

#pragma once
#include <optional>
#include <string>
#include <vector>
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<std::string> 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<NeedRequest> 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.<axis>] 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.<axis>], 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<std::string> 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<std::string> 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<std::string> 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