Motion Master 6.0.0-alpha.50
Next-generation motion control software
Loading...
Searching...
No Matches
foe_error.h
Go to the documentation of this file.
1#pragma once
2
3// ── Currently unused: a reference implementation, not yet wired into any surface. ──────────────
4//
5// Today every FieldbusDriver operation — readFile/writeFile included — reports failure as
6// std::expected<T, std::string>. That is the deliberate default (see CLAUDE.md, the no-exceptions
7// mandate): std::string is right everywhere a caller only logs, forwards, or shows the error, which
8// is every FoE caller in the tree right now (the HTTP handlers just put the text in a 500 body).
9//
10// This header exists so the *promotion path* is concrete rather than hypothetical. The moment a
11// caller must **branch** on why a FoE transfer failed — the canonical case is a firmware flasher
12// that retries a transient failure (packet desync, or a slave still warming up after BOOT) but
13// aborts immediately on a permanent one (no such file, undersized buffer) — string-matching the
14// message to decide control flow is the smell that says that surface has earned a structured error.
15// At that point readFile/writeFile change their error type from std::string to FoeError; because
16// FoeError keeps a string face (operator<<, .message, .what()), the forwarding callers that only
17// display it change by at most one word (.error() → .error().message) and nothing ripples further.
18//
19// This is the same design split the wider ecosystem settled on, and the mandate is a deliberate
20// pick of one side of it:
21// • Rust codified it as two libraries — anyhow::Error (a message you propagate and Display) for
22// leaf/application code, thiserror (typed enums you `match` on) for a library whose callers
23// branch. "std::string by default, FoeError where you branch" is that rule verbatim.
24// • Go is the same shape: fmt.Errorf("...: %w", err) (string + wrap) almost everywhere, and only
25// sentinel errors + errors.Is/errors.As at the specific call site that must distinguish a
26// cause.
27// The rejected third option is a single generic error type swept across the whole codebase: its
28// enum is either too coarse to serve the branching caller (who then string-matches anyway) or a
29// grab-bag union of every layer's failure modes (coupling with no abstraction win). So the rule is
30// "typed per surface, or a string" with nothing in between — and this type is named for its surface
31// (FoeError, not OpError) so both the type and the coupling to it stay local to FoE callers. See
32// NEXTGEN.md, Session 2026-07-17.
33//
34// The `retry` tag is the one axis worth sharing across surfaces (an SDO error would carry the same
35// Transient/Permanent distinction), so it rides *alongside* the FoE-specific kind rather than being
36// folded into it — the absl::Status insight (a universal code + operation-specific detail), sized
37// down to the one distinction callers here actually act on.
38// ──────────────────────────────────────────────────────────────────────────────────────────────
39
40#include <cstdint>
41#include <format>
42#include <ostream>
43#include <string>
44#include <string_view>
45
46namespace mm::comm {
47
52enum class Retry {
53 Transient,
54 Permanent,
55};
56
66
68inline std::string_view foeReason(FoeErrorKind kind) {
69 switch (kind) {
71 return "no response";
73 return "file not found";
75 return "buffer too small";
77 return "packet number mismatch";
79 return "FoE error";
80 }
81 return "FoE error";
82}
83
99
102struct FoeError {
105 std::string message;
106
108 const std::string& what() const { return message; }
109};
110
112inline std::ostream& operator<<(std::ostream& os, const FoeError& e) { return os << e.message; }
113
117inline FoeError makeFoeError(FoeErrorKind kind, std::string_view op, uint16_t slave,
118 std::string_view filename) {
119 return {kind, foeRetry(kind),
120 std::format("{} slave {} '{}' failed ({})", op, slave, filename, foeReason(kind))};
121}
122
123} // namespace mm::comm
Definition al_status_codes.cc:5
std::string_view foeReason(FoeErrorKind kind)
Short human-readable reason for a FoE kind (no surrounding punctuation).
Definition foe_error.h:68
FoeErrorKind
Specific File-over-EtherCAT failure kind. Transport-agnostic: a SOEM driver decodes it from a negated...
Definition foe_error.h:59
@ NoResponse
The slave answered nothing (work counter 0 — timeout or not ready).
@ FileNotFound
Firmware recognises no such filename.
@ PacketMismatch
Packet-number desync mid-transfer.
@ Protocol
Generic FoE-protocol error with no finer classification.
@ BufferTooSmall
The slave's buffer cannot hold the requested transfer.
Retry foeRetry(FoeErrorKind kind)
Retry disposition implied by a FoE kind. A missing file or an undersized buffer is a fixed condition ...
Definition foe_error.h:87
FoeError makeFoeError(FoeErrorKind kind, std::string_view op, uint16_t slave, std::string_view filename)
Builds a fully-formed FoeError from a decoded kind plus call context. Keeps the message wording ident...
Definition foe_error.h:117
std::ostream & operator<<(std::ostream &os, const FoeError &e)
Streams the message, so ASSERT_TRUE(r) << r.error() and spdlog {} work unchanged.
Definition foe_error.h:112
Retry
Retry disposition of a failed operation: whether re-issuing the identical call could plausibly succee...
Definition foe_error.h:52
@ Permanent
The identical call will fail the same way (no such file, undersized buffer).
@ Transient
A fresh attempt may succeed (protocol desync, slave not yet ready after BOOT).
A structured FoE failure. String-like where a caller only forwards it (operator<<,...
Definition foe_error.h:102
std::string message
Definition foe_error.h:105
Retry retry
Definition foe_error.h:104
FoeErrorKind kind
Definition foe_error.h:103
const std::string & what() const
Exception-shaped accessor for the message, for call sites that prefer .what().
Definition foe_error.h:108