12#include <toml++/toml.h>
20#include <yarp/os/LogStream.h>
21#include <yarp/os/ResourceFinder.h>
82 const auto* encTbl = tbl[key].as_table();
86 auto warnMissing = [&](
const char* field) {
88 "CheckEncoderCalibration: missing key '%s' in [%s], using default value",
93 if (
auto v = (*encTbl)[
"raw_position"].value<int64_t>())
96 warnMissing(
"raw_position");
97 if (
auto v = (*encTbl)[
"raw_position_degrees"].value<double>())
100 warnMissing(
"raw_position_degrees");
101 if (
auto v = (*encTbl)[
"adjusted_position"].value<int64_t>())
104 warnMissing(
"adjusted_position");
105 if (
auto v = (*encTbl)[
"adjusted_position_degrees"].value<double>())
108 warnMissing(
"adjusted_position_degrees");
109 if (
auto v = (*encTbl)[
"counts_per_revolution"].value<int64_t>())
112 warnMissing(
"counts_per_revolution");
113 if (
auto v = (*encTbl)[
"raw_to_degrees_factor"].value<double>())
116 warnMissing(
"raw_to_degrees_factor");
128 uint32_t resolution = 0;
132 if (mgr.
readSDO<uint32_t>(slave, idxConfig, 0x03, resolution)
135 yCWarning(CIA402,
"s%02d: failed to read resolution from 0x%04X:03", slave, idxConfig);
139 yCWarning(CIA402,
"s%02d: failed to read raw position from 0x%04X:01", slave, idxData);
143 yCWarning(CIA402,
"s%02d: failed to read adjusted position from 0x%04X:02", slave, idxData);
150 const double resInv = resolution ? (1.0 /
static_cast<double>(resolution)) : 0.0;
153 out.
rawToDegreesFactor = resolution ? (360.0 /
static_cast<double>(resolution)) : 0.0;
164 os <<
"#### " << title <<
"\n\n";
166 os <<
"| Metric | Reference (TOML) | Current (Live) | Delta |\n";
167 os <<
"|:-------|-----------------:|---------------:|------:|\n";
170 const int64_t dRaw = live.
rawPosition - ref.rawPosition;
171 os <<
"| Raw position (counts) | "
172 <<
fmtInt64(ref.rawPosition) <<
" | "
178 os <<
"| Raw position (deg) | "
179 <<
fmtDouble(ref.rawPositionDegrees) <<
" | "
185 os <<
"| Adjusted position (counts) | "
186 <<
fmtInt64(ref.adjustedPosition) <<
" | "
192 os <<
"| Adjusted position (deg) | "
193 <<
fmtDouble(ref.adjustedPositionDegrees) <<
" | "
199 os <<
"| Counts per revolution | "
200 <<
fmtInt64(ref.countsPerRevolution) <<
" | "
206 os <<
"| Raw-to-degrees factor | "
207 <<
fmtDouble(ref.rawToDegreesFactor) <<
" | "
225 "CheckEncoderCalibration: file not found: %s",
234 refRoot = toml::parse_file(tomlPath);
235 }
catch (
const toml::parse_error& err)
238 "CheckEncoderCalibration: failed to parse %s: %s",
243 yCInfo(CIA402,
"CheckEncoderCalibration: loaded reference TOML from %s", tomlPath.
c_str());
246 yCInfo(CIA402,
"CheckEncoderCalibration: initializing EtherCAT on %s", ifname.
c_str());
247 const auto rc = mgr.init(ifname);
251 "CheckEncoderCalibration: init failed on %s (rc=%d)",
259 for (
int s = 1;; ++s)
261 auto name = mgr.getName(s);
264 yCInfo(CIA402,
"CheckEncoderCalibration: found slave %d: %s", s, name.c_str());
269 yCError(CIA402,
"CheckEncoderCalibration: no slaves found");
280 const auto* slaveTbl = refRoot[slaveKey].as_table();
285 "CheckEncoderCalibration: no entry '%s' in TOML, skipping slave %d",
295 if (
auto v = (*slaveTbl)[
"name"].value<std::string>())
298 rpt.
name = mgr.getName(s);
304 "CheckEncoderCalibration: missing encoder1 in TOML for %s",
310 "CheckEncoderCalibration: missing encoder2 in TOML for %s",
322 allOk = this->writeMarkdownReport(reports, reportPath, tomlPath);
336 "CheckEncoderCalibration: cannot open %s for writing",
348 ofs <<
"# Encoder Calibration Check Report\n\n";
351 ofs <<
"|:--|:--|\n";
352 ofs <<
"| **Date** | " << tsStr.
str() <<
" |\n";
353 ofs <<
"| **Reference TOML** | `" << tomlPath <<
"` |\n";
354 ofs <<
"| **Slaves checked** | " << reports.
size() <<
" |\n";
358 ofs <<
"## Summary\n\n";
359 ofs <<
"| Slave | Name | Enc1 Adj Δ (deg) | Enc2 Adj Δ (deg) |\n";
360 ofs <<
"|:-----:|:-----|----------------------:|-----------------------:|\n";
362 for (
const auto& rpt : reports)
365 = rpt.liveEnc1.adjustedPositionDegrees - rpt.refEnc1.adjustedPositionDegrees;
367 = rpt.liveEnc2.adjustedPositionDegrees - rpt.refEnc2.adjustedPositionDegrees;
368 ofs <<
"| " << rpt.slaveIndex <<
" | " << rpt.name <<
" | " <<
fmtDeltaDouble(d1)
374 for (
const auto& rpt : reports)
376 ofs <<
"### Slave " << rpt.slaveIndex <<
" — " << rpt.name <<
"\n\n";
384 ofs <<
"*Report generated by `yarp-cia402-check-encoder-calibration`*\n";
389 "CheckEncoderCalibration: error closing report file %s",
394 yCInfo(CIA402,
"CheckEncoderCalibration: report written to %s", reportPath.
c_str());
403 m_impl = std::make_unique<Impl>();
411 = rf.check(
"ifname") ? rf.
find(
"ifname").asString() :
std::string(
"eth0");
414 if (!rf.check(
"toml-input"))
417 "CheckEncoderCalibration: missing required parameter 'toml-input' "
418 "(path to the reference TOML file from store-home-position)");
425 if (rf.check(
"report-output"))
427 reportPath = rf.
find(
"report-output").asString();
435 oss <<
"encoder_calibration_check_" <<
std::put_time(&tm,
"%Y_%m_%d_%H_%M_%S") <<
".md";
436 reportPath = oss.
str();
440 "CheckEncoderCalibration: ifname=%s toml-input=%s report-output=%s",
445 return m_impl->run(ifname, tomlPath, reportPath);
static bool readEncoderFromToml(const toml::table &tbl, const std::string &key, EncoderChannelData &out)
static void writeEncoderTable(std::ostream &os, const std::string &title, const EncoderChannelData &ref, const EncoderChannelData &live)
static std::string fmtDeltaInt64(int64_t v)
static std::string fmtInt64(int64_t v)
static std::string fmtDeltaDouble(double v, int prec=6)
static std::string fmtDouble(double v, int prec=6)
static bool readEncoderFromSlave(EthercatManager &mgr, int slave, uint16_t idxConfig, uint16_t idxData, EncoderChannelData &out)
bool run(const std::string &ifname, const std::string &tomlPath, const std::string &reportPath)
~CheckEncoderCalibration()
Destructor.
CheckEncoderCalibration()
Default constructor.
bool run(yarp::os::ResourceFinder &rf)
Run the full encoder check and produce a Markdown report.
Minimal EtherCAT master built on SOEM for CiA-402 devices.
Error readSDO(int slaveIndex, uint16_t idx, uint8_t subIdx, T &out) noexcept
Read an SDO value from a slave (blocking call).
@ NoError
Operation completed successfully.
static constexpr uint16_t IDX_ENC2_CONFIG
:03 = resolution (counts/rev)
static constexpr uint16_t IDX_ENC1_DATA
:01 = raw position, :02 = adjusted position
static constexpr uint16_t IDX_ENC1_CONFIG
:03 = resolution (counts/rev)
std::tm getLocalTime(const std::time_t &t)
static constexpr uint16_t IDX_ENC2_DATA
:01 = raw position, :02 = adjusted position
T setprecision(T... args)
int64_t countsPerRevolution
double rawPositionDegrees
double adjustedPositionDegrees
double rawToDegreesFactor
EncoderChannelData liveEnc2
EncoderChannelData refEnc2
EncoderChannelData refEnc1
EncoderChannelData liveEnc1