YARP CiA-402 EtherCAT Device 0.6.0
YARP device plugin for EtherCAT CiA-402 drives
Loading...
Searching...
No Matches
StoreHomePosition.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Fondazione Istituto Italiano di Tecnologia (IIT)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#include <chrono>
5#include <ctime>
6#include <fstream>
7#include <iomanip>
8#include <iostream>
9#include <sstream>
10#include <thread>
11#include <vector>
12
13#include <toml++/toml.h>
14
17#include <CiA402/LogComponent.h>
18#include <CiA402/TimeUtils.h>
19
21#include <yarp/os/LogStream.h>
22#include <yarp/os/ResourceFinder.h>
23
24using namespace CiA402;
25using namespace std::chrono_literals;
26
27// ---- CiA-402 / Synapticon indices ----
28static constexpr uint16_t IDX_CONTROLWORD = 0x6040; // uint16
29static constexpr uint16_t IDX_STATUSWORD = 0x6041; // uint16
30static constexpr uint16_t IDX_OPMODE = 0x6060; // int8 : 6 = Homing
31static constexpr uint16_t IDX_POSITION_ACT = 0x6064; // int32 : for logging
32static constexpr uint16_t IDX_HOMING_METHOD = 0x6098; // int8 : 35/37
33static constexpr uint16_t IDX_HOME_OFFSET = 0x607C; // int32
34static constexpr uint16_t IDX_STORE_PARAMS = 0x1010; // uint32: :01 = 'evas'
35
36static constexpr uint16_t IDX_HOME_VENDOR = 0x2005; // Synapticon: :01 Home, :02 Restore-on-load
37
38
39
40// ---- Statusword helpers ----
41static inline bool swHomingAttained(uint16_t sw)
42{
43 return (sw & (1u << 12)) != 0;
44}
45static inline bool swHomingError(uint16_t sw)
46{
47 return (sw & (1u << 13)) != 0;
48}
49
51{
52public:
53 bool run(const std::string& ifname,
54 int8_t hm,
55 int32_t hoff,
56 int timeoutMs,
57 bool restore,
58 const std::string& tomlPath)
59 {
60 // Initialize EtherCAT master
61 yCInfo(CIA402, "StoreHome37: initializing EtherCAT on %s", ifname.c_str());
62 const auto rc = mgr.init(ifname);
64 {
65 yCError(CIA402, "StoreHome37: init failed on %s (rc=%d)", ifname.c_str(), int(rc));
66 return false;
67 }
68
69 // Discover slaves (1-based indices in SOEM)
70 std::vector<int> slaves;
71 for (int s = 1;; ++s)
72 {
73 auto name = mgr.getName(s);
74 if (name.empty())
75 break;
76 yCInfo(CIA402, "StoreHome3537: found slave %d: %s", s, name.c_str());
77 slaves.push_back(s);
78 }
79 if (slaves.empty())
80 {
81 yCError(CIA402, "StoreHome3537: no slaves found");
82 return false;
83 }
84
85 bool allOk = true;
86 for (int s : slaves)
87 {
88 allOk = allOk && this->homeAndPersistSingle(s, hm, hoff, timeoutMs, restore);
89 }
90
91 // After calibration, collect encoder data and write TOML
92 if (allOk)
93 {
94 allOk = this->writeEncoderToml(slaves, tomlPath);
95 }
96
97 return allOk;
98 }
99
100private:
101 bool writeEncoderToml(const std::vector<int>& slaves, const std::string& tomlPath)
102 {
103 toml::table root;
104
105 for (int s : slaves)
106 {
107 const std::string slaveName = "slave_" + std::to_string(s);
108 toml::table slaveTable;
109
110 // Read encoder resolutions (counts per revolution)
111 uint32_t enc1Res = 0;
112 uint32_t enc2Res = 0;
113 (void)mgr.readSDO<uint32_t>(s, IDX_ENC1_CONFIG, 0x03, enc1Res);
114 (void)mgr.readSDO<uint32_t>(s, IDX_ENC2_CONFIG, 0x03, enc2Res);
115
116 // Read encoder 1 feedback (0x2111)
117 uint32_t enc1RawPos = 0; // 0x2111:01 — raw position (UDINT)
118 int32_t enc1AdjPos = 0; // 0x2111:02 — adjusted position (DINT)
119 (void)mgr.readSDO<uint32_t>(s, IDX_ENC1_DATA, 0x01, enc1RawPos);
120 (void)mgr.readSDO<int32_t>(s, IDX_ENC1_DATA, 0x02, enc1AdjPos);
121
122 // Read encoder 2 feedback (0x2113)
123 uint32_t enc2RawPos = 0; // 0x2113:01 — raw position (UDINT)
124 int32_t enc2AdjPos = 0; // 0x2113:02 — adjusted position (DINT)
125 (void)mgr.readSDO<uint32_t>(s, IDX_ENC2_DATA, 0x01, enc2RawPos);
126 (void)mgr.readSDO<int32_t>(s, IDX_ENC2_DATA, 0x02, enc2AdjPos);
127
128 // Compute degrees from adjusted position: degrees = adj * (360.0 / resolution)
129 const double enc1ResInv = enc1Res ? (1.0 / static_cast<double>(enc1Res)) : 0.0;
130 const double enc2ResInv = enc2Res ? (1.0 / static_cast<double>(enc2Res)) : 0.0;
131 const double enc1AdjDeg = static_cast<double>(enc1AdjPos) * enc1ResInv * 360.0;
132 const double enc2AdjDeg = static_cast<double>(enc2AdjPos) * enc2ResInv * 360.0;
133
134 // Compute degrees from raw position
135 const double enc1RawDeg = static_cast<double>(enc1RawPos) * enc1ResInv * 360.0;
136 const double enc2RawDeg = static_cast<double>(enc2RawPos) * enc2ResInv * 360.0;
137
138 // Store encoder 1 data
139 toml::table enc1Table;
140 enc1Table.insert("raw_position", static_cast<int64_t>(enc1RawPos));
141 enc1Table.insert("raw_position_degrees", enc1RawDeg);
142 enc1Table.insert("adjusted_position", static_cast<int64_t>(enc1AdjPos));
143 enc1Table.insert("adjusted_position_degrees", enc1AdjDeg);
144 enc1Table.insert("counts_per_revolution", static_cast<int64_t>(enc1Res));
145 enc1Table.insert("raw_to_degrees_factor", enc1Res ? (360.0 / static_cast<double>(enc1Res)) : 0.0);
146 slaveTable.insert("encoder1", enc1Table);
147
148 // Store encoder 2 data
149 toml::table enc2Table;
150 enc2Table.insert("raw_position", static_cast<int64_t>(enc2RawPos));
151 enc2Table.insert("raw_position_degrees", enc2RawDeg);
152 enc2Table.insert("adjusted_position", static_cast<int64_t>(enc2AdjPos));
153 enc2Table.insert("adjusted_position_degrees", enc2AdjDeg);
154 enc2Table.insert("counts_per_revolution", static_cast<int64_t>(enc2Res));
155 enc2Table.insert("raw_to_degrees_factor", enc2Res ? (360.0 / static_cast<double>(enc2Res)) : 0.0);
156 slaveTable.insert("encoder2", enc2Table);
157
158 // Also store device name
159 slaveTable.insert("name", mgr.getName(s));
160
161 root.insert(slaveName, slaveTable);
162
163 yCInfo(CIA402,
164 "s%02d: enc1 raw=%u adj=%d adj_deg=%.6f (res=%u), "
165 "enc2 raw=%u adj=%d adj_deg=%.6f (res=%u)",
166 s,
167 enc1RawPos,
168 enc1AdjPos,
169 enc1AdjDeg,
170 enc1Res,
171 enc2RawPos,
172 enc2AdjPos,
173 enc2AdjDeg,
174 enc2Res);
175 }
176
177 // Write TOML to file
178 std::ofstream ofs(tomlPath);
179 if (!ofs.is_open())
180 {
181 yCError(CIA402, "StoreHome37: cannot open %s for writing", tomlPath.c_str());
182 return false;
183 }
184 ofs << root;
185 ofs.close();
186 yCInfo(CIA402, "StoreHome37: encoder data written to %s", tomlPath.c_str());
187 return true;
188 }
189
190 bool homeAndPersistSingle(int s, int8_t hm, int32_t hoff, int timeoutMs, bool restore)
191 {
192 // -- 1) OpMode = Homing (6)
193 {
194 const int8_t op = 6;
195 if (mgr.writeSDO<int8_t>(s, IDX_OPMODE, 0x00, op) != EthercatManager::Error::NoError)
196 {
197 yCError(CIA402, "s%02d: write 0x6060=6 failed", s);
198 return false;
199 }
200 }
201
202 // -- 2) 0x6098 = 37 (or 35)
203 if (hm != 35 && hm != 37)
204 {
205 yCWarning(CIA402, "s%02d: invalid homing method %d, using 37", s, int(hm));
206 hm = 37;
207 }
208 if (mgr.writeSDO<int8_t>(s, IDX_HOMING_METHOD, 0x00, hm) != EthercatManager::Error::NoError)
209 {
210 yCError(CIA402, "s%02d: write 0x6098=%d failed", s, int(hm));
211 return false;
212 }
213
214 // -- 3) Optional extra home offset
215 if (hoff != 0)
216 {
217 if (mgr.writeSDO<int32_t>(s, IDX_HOME_OFFSET, 0x00, hoff)
219 {
220 yCError(CIA402, "s%02d: write 0x607C=%d failed", s, hoff);
221 return false;
222 }
223 }
224
225 // -- 4) Start homing: toggle Controlword bit4 (mode-specific "start")
226 {
227 uint16_t cw = 0;
228 (void)mgr.readSDO<uint16_t>(s, IDX_CONTROLWORD, 0x00, cw); // best-effort read
229 const uint16_t cwLow = static_cast<uint16_t>(cw & ~(1u << 4));
230 const uint16_t cwHigh = static_cast<uint16_t>(cwLow | (1u << 4));
231 if (mgr.writeSDO<uint16_t>(s, IDX_CONTROLWORD, 0x00, cwLow)
233 || mgr.writeSDO<uint16_t>(s, IDX_CONTROLWORD, 0x00, cwHigh)
235 {
236 yCError(CIA402, "s%02d: toggle 0x6040 bit4 failed", s);
237 return false;
238 }
239 }
240
241 // -- 5) Poll 0x6041: bit12 attained / bit13 error
242 const auto t0 = std::chrono::steady_clock::now();
243 while (true)
244 {
245 uint16_t sw = 0;
246 if (mgr.readSDO<uint16_t>(s, IDX_STATUSWORD, 0x00, sw)
248 {
249 yCError(CIA402, "s%02d: read 0x6041 failed", s);
250 return false;
251 }
252 if (swHomingError(sw))
253 {
254 yCError(CIA402, "s%02d: homing error (0x6041=0x%04X)", s, sw);
255 return false;
256 }
257 if (swHomingAttained(sw))
258 {
259 int32_t pos = 0;
260 (void)mgr.readSDO<int32_t>(s, IDX_POSITION_ACT, 0x00, pos);
261 yCInfo(CIA402, "s%02d: homing attained (pos=%d, sw=0x%04X)", s, pos, sw);
262 break;
263 }
266 .count()
267 > timeoutMs)
268 {
269 yCError(CIA402, "s%02d: timeout waiting homing attained", s);
270 return false;
271 }
273 }
274
275 // -- 6) (Optional) read vendor Home Position 0x2005:01 for logging
276 int32_t homeVal = 0;
277 (void)mgr.readSDO<int32_t>(s, IDX_HOME_VENDOR, 0x01, homeVal);
278 yCInfo(CIA402, "s%02d: vendor Home (0x2005:01) = %d", s, homeVal);
279
280 // -- 7) Set restore-on-startup flag per request (0x2005:02)
281 {
282 const uint8_t flag = restore ? uint8_t{1} : uint8_t{0};
283 if (mgr.writeSDO<uint8_t>(s, IDX_HOME_VENDOR, 0x02, flag)
285 {
286 yCError(CIA402, "s%02d: write 0x2005:02=%u failed", s, flag);
287 return false;
288 }
289 }
290
291 // -- 8) Save to flash: 0x1010:01 = 'evas' (0x65766173)
292 {
293 constexpr uint32_t EVAS = 0x65766173u;
294 if (mgr.writeSDO<uint32_t>(s, IDX_STORE_PARAMS, 0x01, EVAS)
296 {
297 yCError(CIA402, "s%02d: save 0x1010:01='evas' failed", s);
298 return false;
299 }
300 yCInfo(CIA402,
301 "s%02d: configuration saved (home persisted, restoreOnStartup=%s)",
302 s,
303 restore ? "true" : "false");
304 }
305
306 return true;
307 }
308
309 EthercatManager mgr;
310};
311
313{
314 m_impl = std::make_unique<Impl>();
315}
316
317StoreHome37::~StoreHome37() = default;
318
319bool StoreHome37::run(yarp::os::ResourceFinder& rf)
320{
321 const std::string ifname = rf.check("ifname") ? rf.find("ifname").asString()
322 : std::string("eth0");
323 int methodTmp = 37;
324 if (rf.check("method"))
325 {
326 methodTmp = rf.find("method").asInt32();
327 }
328 const int8_t method = static_cast<int8_t>(methodTmp);
329
330 int32_t homeOffset = 0;
331 if (rf.check("home-offset"))
332 {
333 homeOffset = rf.find("home-offset").asInt32();
334 }
335
336 int timeoutMs = 2000;
337 if (rf.check("timeout-ms"))
338 {
339 timeoutMs = rf.find("timeout-ms").asInt32();
340 }
341
342 bool restoreOnBoot = true;
343 if (rf.check("restore-on-boot"))
344 {
345 // Accept bool or int
346 if (rf.find("restore-on-boot").isBool())
347 {
348 restoreOnBoot = rf.find("restore-on-boot").asBool();
349 } else
350 {
351 restoreOnBoot = (rf.find("restore-on-boot").asInt32() != 0);
352 }
353 }
354
355 yCInfo(CIA402,
356 "StoreHome37: ifname=%s method=%d home-offset=%d timeout-ms=%d restore-on-boot=%s",
357 ifname.c_str(),
358 int(method),
359 homeOffset,
360 timeoutMs,
361 restoreOnBoot ? "true" : "false");
362 std::string tomlPath;
363 if (rf.check("toml-output"))
364 {
365 tomlPath = rf.find("toml-output").asString();
366 } else
367 {
368 // Generate default filename with current date and time
369 const auto now = std::chrono::system_clock::now();
371 const std::tm tm = getLocalTime(t);
373 oss << "joint_calibration_"
374 << std::put_time(&tm, "%Y_%m_%d_%H_%M_%S")
375 << ".toml";
376 tomlPath = oss.str();
377 }
378
379 yCInfo(CIA402, "Do you want to proceed? (press ENTER to continue)");
381
382 return m_impl->run(ifname, method, homeOffset, timeoutMs, restoreOnBoot, tomlPath);
383}
static constexpr uint16_t IDX_STORE_PARAMS
static bool swHomingAttained(uint16_t sw)
static constexpr uint16_t IDX_POSITION_ACT
static constexpr uint16_t IDX_HOME_VENDOR
static constexpr uint16_t IDX_OPMODE
static bool swHomingError(uint16_t sw)
static constexpr uint16_t IDX_HOME_OFFSET
static constexpr uint16_t IDX_HOMING_METHOD
static constexpr uint16_t IDX_CONTROLWORD
static constexpr uint16_t IDX_STATUSWORD
T c_str(T... args)
std::string getName(int slaveIndex) const noexcept
Get the name of a slave device.
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.
~StoreHome37()
Destructor.
StoreHome37()
Default constructor.
bool run(yarp::os::ResourceFinder &rf)
Run the full homing and persistence procedure on all slaves, then write encoder data.
bool run(const std::string &ifname, int8_t hm, int32_t hoff, int timeoutMs, bool restore, const std::string &tomlPath)
T count(T... args)
T duration_cast(T... args)
T empty(T... args)
T find(T... args)
T max(T... args)
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)
Definition TimeUtils.h:12
static constexpr uint16_t IDX_ENC2_DATA
:01 = raw position, :02 = adjusted position
T push_back(T... args)
T put_time(T... args)
T sleep_for(T... args)
T str(T... args)
T to_string(T... args)