iCub-main
Loading...
Searching...
No Matches
yafu.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C)2025 iCub Facility - Istituto Italiano di Tecnologia
3 * Author: SATHISH KUMAR S
4 * email: sathish.subramani@iit.it
5 * github: https://github.com/sksubiit
6 * website: www.robotcub.org
7 * Permission is granted to copy, distribute, and/or modify this program
8 * under the terms of the GNU General Public License, version 2 or any
9 * later version published by the Free Software Foundation.
10 *
11 * A copy of the license can be found at
12 * http://www.robotcub.org/icub/license/gpl.txt
13 *
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
17 * License for more details
18*/
19
20#include "yafu.h"
21
22
23// new helper: resolve installed or in-tree firmware.info.xml path
24std::string simpleEthClient::resolveFirmwareInfoXml()
25{
26 // 1) Environment Variable Override (Highest Priority)
27 // This provides an explicit, reliable way to specify the file path, ideal for production.
28 const char *env = getenv("YAFU_FIRMWARE_INFO");
29 if (env && env[0] != '\0') {
30 struct stat st;
31 if (stat(env, &st) == 0) {
32 std::cerr << "[Resolver] Found firmware info via 'YAFU_FIRMWARE_INFO' environment variable." << std::endl;
33 return std::string(env);
34 }
35 }
36
37 // 2) AMENT_PREFIX_PATH / Package-Style Installed Locations
38 // This is the standard way to find files in relocatable installations (e.g., in a build/install or /opt/.. dir).
39 std::vector<std::string> prefixes;
40 const char *ament = getenv("AMENT_PREFIX_PATH");
41 if (ament && ament[0] != '\0') {
42 std::string s(ament);
43 std::stringstream ss(s);
44 std::string item;
45 while (std::getline(ss, item, ':')) if (!item.empty()) prefixes.push_back(item);
46 }
47
48 // Also check relative to the executable for local installs (e.g., bin/../share)
49 char exe_buf[PATH_MAX];
50 ssize_t elen = readlink("/proc/self/exe", exe_buf, sizeof(exe_buf) - 1);
51 if (elen > 0) {
52 exe_buf[elen] = '\0';
53 std::string exe_s(exe_buf);
54 size_t pos = exe_s.find_last_of('/');
55 if (pos != std::string::npos) {
56 std::string exe_dir = exe_s.substr(0, pos);
57 prefixes.push_back(exe_dir + "/.."); // for bin/ -> share/ layout
58 }
59 }
60
61 for (const auto &p : prefixes) {
62 std::string cand1 = p + "/share/icub-firmware/info/firmware.info.xml";
63 std::string cand2 = p + "/share/ICUB/info/firmware.info.xml";
64 struct stat st;
65 if (stat(cand1.c_str(), &st) == 0) {
66 std::cerr << "[Resolver] Found firmware info via installed prefix path (AMENT_PREFIX_PATH or exe-relative)." << std::endl;
67 return cand1;
68 }
69 if (stat(cand2.c_str(), &st) == 0) {
70 std::cerr << "[Resolver] Found firmware info via installed prefix path (AMENT_PREFIX_PATH or exe-relative)." << std::endl;
71 return cand2;
72 }
73 }
74
75 // 3) In-Tree Development Path (Last Resort for Developer Convenience)
76 // This allows developers to run the tool from the source/build tree without installing.
77 std::vector<std::string> repo_candidates = {
78 "../src/icub-firmware-build/info/firmware.info.xml",
79 "../../src/icub-firmware-build/info/firmware.info.xml"
80 };
81 for (const auto &c : repo_candidates) {
82 struct stat st;
83 if (stat(c.c_str(), &st) == 0) {
84 std::cerr << "[Resolver] Found firmware info via in-tree development path." << std::endl;
85 return c;
86 }
87 }
88
89 // If no file is found via the explicit methods above, fail clearly.
90 std::cerr << "[Resolver] ERROR: Could not find firmware.info.xml." << std::endl;
91 std::cerr << "[Resolver] Please ensure that YAFU_FIRMWARE_INFO or AMENT_PREFIX_PATH is set correctly," << std::endl;
92 std::cerr << "[Resolver] or that you are running from a valid development workspace." << std::endl;
93 return std::string(); // not found
94}
95
96
97// helper: receive a 4-byte eOuprot_cmdREPLY_t for dest_ ip and check opc
98bool simpleEthClient::recvReplyForIP(uint8_t expected_opc, int timeout_ms, eOuprot_result_t &out_res)
99{
100 (void)timeout_ms; // socket timeout already set in open()
101 unsigned char buf[1500];
102 socklen_t sl = sizeof(src_);
103
104 // loop until timeout or until we receive an expected reply from dest_
105 while (true) {
106 ssize_t r = recvfrom(sock_, buf, sizeof(buf), 0, (struct sockaddr*)&src_, &sl);
107 if (r < 0) {
108 if (errno == EAGAIN || errno == EWOULDBLOCK) {
109 // socket read timed out -> no reply
110 return false;
111 }
112 perror("recvfrom");
113 return false;
114 }
115 // ignore packets from other senders, keep waiting until timeout
116 if (src_.sin_addr.s_addr != dest_.sin_addr.s_addr) {
117 continue;
118 }
119 if ((size_t)r < sizeof(eOuprot_cmdREPLY_t)) continue;
120 auto *reply = reinterpret_cast<eOuprot_cmdREPLY_t*>(buf);
121 if (reply->opc != expected_opc) continue;
122 out_res = static_cast<eOuprot_result_t>(reply->res);
123 return true;
124 }
125}
126
127// search firmware.info.xml for <board type="boardname"> and return the file path (relative resolved)
128bool simpleEthClient::findFirmwareForBoard(const std::string &boardname, std::string &out_hexpath)
129{
130 std::string xmlpath = resolveFirmwareInfoXml();
131 if (xmlpath.empty()) return false;
132 std::ifstream f(xmlpath);
133 if (!f.is_open()) return false;
134 std::string line;
135 bool inboard = false;
136 std::string foundfile;
137 while (std::getline(f, line)) {
138 // trim whitespace
139 std::string s = line;
140 // lowercase for safer matching (board types in xml are lowercase)
141 auto tolower_copy = [](std::string t){ for(char &c: t) c = static_cast<char>(std::tolower((unsigned char)c)); return t; };
142 std::string tl = tolower_copy(s);
143 std::string key = "board type=\"";
144 size_t pos = tl.find(key);
145 if (!inboard && pos != std::string::npos) {
146 size_t start = pos + key.size();
147 size_t end = tl.find('"', start);
148 if (end != std::string::npos) {
149 std::string bt = tl.substr(start, end - start);
150 if (bt == tolower_copy(boardname)) {
151 inboard = true;
152 continue;
153 }
154 }
155 }
156 if (inboard) {
157 size_t p1 = s.find("<file>");
158 if (p1 != std::string::npos) {
159 size_t p2 = s.find("</file>", p1);
160 if (p2 != std::string::npos) {
161 foundfile = s.substr(p1 + strlen("<file>"), p2 - (p1 + strlen("<file>")));
162 break;
163 }
164 }
165 // board close without file -> stop
166 if (s.find("</board>") != std::string::npos) break;
167 }
168 }
169 f.close();
170 if (foundfile.empty()) return false;
171 // resolve relative path: xml path parent + foundfile
172 std::string xmls(xmlpath);
173 size_t slash = xmls.rfind('/');
174 std::string dir = (slash == std::string::npos) ? std::string(".") : xmls.substr(0, slash+1);
175 std::string candidate = dir + foundfile;
176 // normalize simple ../
177 // try candidate as-is
178 struct stat st;
179 if (stat(candidate.c_str(), &st) == 0) {
180 out_hexpath = candidate;
181 return true;
182 }
183 // fallback: try the raw foundfile as absolute or relative to cwd
184 if (stat(foundfile.c_str(), &st) == 0) {
185 out_hexpath = foundfile;
186 return true;
187 }
188 return false;
189}
190
191// new helper: find firmware file and optional version attribute (version="MAJOR.MINOR") in the <file> tag
192bool simpleEthClient::findFirmwareForBoardWithVersion(const std::string &boardname, std::string &out_hexpath, int &out_major, int &out_minor)
193{
194 out_major = out_minor = -1;
195 std::string xmlpath = resolveFirmwareInfoXml();
196 if (xmlpath.empty()) return false;
197 std::ifstream f(xmlpath);
198 if (!f.is_open()) return false;
199
200 std::string line;
201 bool in_correct_board_block = false;
202 std::string foundfile;
203
204 auto tolower_copy = [](std::string s) {
205 for (char &c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
206 return s;
207 };
208
209 while (std::getline(f, line)) {
210 std::string lower_line = tolower_copy(line);
211
212 if (!in_correct_board_block) {
213 // Look for the start of a board block that matches our board name
214 std::string key = "<board type=\"" + tolower_copy(boardname) + "\"";
215 if (lower_line.find(key) != std::string::npos) {
216 in_correct_board_block = true;
217 }
218 } else {
219 // We are inside the correct board block, look for file and version
220 std::smatch match;
221 std::regex file_re(R"(<\s*file\s*>([^<]+)</file>)");
222 if (std::regex_search(line, match, file_re)) {
223 foundfile = match[1].str();
224 }
225
226 std::regex version_re(R"(<\s*version\s+major\s*=\s*['"](\d+)['"]\s+minor\s*=\s*['"](\d+)['"])");
227 if (std::regex_search(line, match, version_re)) {
228 out_major = std::stoi(match[1].str());
229 out_minor = std::stoi(match[2].str());
230 }
231
232 // If we find the closing board tag, we are done with this block
233 if (lower_line.find("</board>") != std::string::npos) {
234 break;
235 }
236 }
237 }
238 f.close();
239
240 if (foundfile.empty()) {
241 return false;
242 }
243
244 // resolve relative path: xml path parent + foundfile
245 std::string xmls(xmlpath);
246 size_t slash = xmls.rfind('/');
247 std::string dir = (slash == std::string::npos) ? std::string(".") : xmls.substr(0, slash + 1);
248 std::string candidate = dir + foundfile;
249
250 struct stat st;
251 if (stat(candidate.c_str(), &st) == 0) {
252 out_hexpath = candidate;
253 return true;
254 }
255 if (stat(foundfile.c_str(), &st) == 0) {
256 out_hexpath = foundfile;
257 return true;
258 }
259 out_hexpath = candidate;
260 return true;
261}
262
263bool simpleEthClient::sendPROG_START(eOuprot_partition2prog_t partition, eOuprot_result_t &out_res)
264{
265 eOuprot_cmd_PROG_START_t cmd;
266 memset(&cmd, EOUPROT_VALUE_OF_UNUSED_BYTE, sizeof(cmd));
267 cmd.opc = uprot_OPC_PROG_START;
268 cmd.partition = static_cast<uint8_t>(partition);
269 if (!sendRaw(&cmd, sizeof(cmd))) { perror("sendto PROG_START"); return false; }
270 if (!recvReplyForIP(uprot_OPC_PROG_START, 0, out_res)) return false;
271 return true;
272}
273
274bool simpleEthClient::sendPROG_DATA_chunk(uint32_t address, const uint8_t *data, size_t len, eOuprot_result_t &out_res)
275{
276 if (len == 0 || len > uprot_PROGmaxsize) return false;
277 // prepare command in a dynamic buffer to send only HEAD+data
278 const size_t HEAD_SIZE = 7; // opc (1) + address(4) + size(2)
279 std::vector<uint8_t> packet(HEAD_SIZE + len, EOUPROT_VALUE_OF_UNUSED_BYTE);
280 packet[0] = static_cast<uint8_t>(uprot_OPC_PROG_DATA);
281 // address little-endian
282 packet[1] = static_cast<uint8_t>(address & 0xFF);
283 packet[2] = static_cast<uint8_t>((address >> 8) & 0xFF);
284 packet[3] = static_cast<uint8_t>((address >> 16) & 0xFF);
285 packet[4] = static_cast<uint8_t>((address >> 24) & 0xFF);
286 // size little-endian (2 bytes)
287 packet[5] = static_cast<uint8_t>(len & 0xFF);
288 packet[6] = static_cast<uint8_t>((len >> 8) & 0xFF);
289 // data
290 memcpy(&packet[HEAD_SIZE], data, len);
291 if (!sendRaw(packet.data(), packet.size())) { perror("sendto PROG_DATA"); return false; }
292 if (!recvReplyForIP(uprot_OPC_PROG_DATA, 0, out_res)) return false;
293 return true;
294}
295
296bool simpleEthClient::sendPROG_END(uint16_t numberofpkts, eOuprot_result_t &out_res)
297{
298 eOuprot_cmd_PROG_END_t end_cmd;
299 memset(&end_cmd, EOUPROT_VALUE_OF_UNUSED_BYTE, sizeof(end_cmd));
300 end_cmd.opc = uprot_OPC_PROG_END;
301 end_cmd.numberofpkts[0] = numberofpkts & 0xFF;
302 end_cmd.numberofpkts[1] = (numberofpkts >> 8) & 0xFF;
303 if (!sendRaw(&end_cmd, sizeof(end_cmd))) { perror("sendto PROG_END"); return false; }
304 if (!recvReplyForIP(uprot_OPC_PROG_END, 0, out_res)) return false;
305 return true;
306}
307
308// program workflow: discover -> xml lookup -> jump2updater if needed -> parse intel hex -> PROG_START/DATA/END -> def2run + restart
310{
311 if (sock_ < 0) return false;
312
313 // 1) discover the board (unicast to dest_)
314 eOuprot_cmd_DISCOVER_t cmd;
315 memset(&cmd, EOUPROT_VALUE_OF_UNUSED_BYTE, sizeof(cmd));
316 cmd.opc = uprot_OPC_LEGACY_SCAN;
317 cmd.opc2 = uprot_OPC_DISCOVER;
318 cmd.jump2updater = 0;
319 if (!sendRaw(&cmd, sizeof(cmd))) { perror("sendto discover"); return false; }
320
321 // wait for reply from dest_
322 unsigned char buf[1500];
323 bool got = false;
324 eOuprot_cmd_DISCOVER_REPLY_t discovered = {0};
325 socklen_t sl;
326 while (true)
327 {
328 sl = sizeof(src_);
329 ssize_t r = recvfrom(sock_, buf, sizeof(buf), 0, (struct sockaddr*)&src_, &sl);
330 if (r < 0) {
331 if (errno == EAGAIN || errno == EWOULDBLOCK) break;
332 perror("recvfrom");
333 return false;
334 }
335 if (src_.sin_addr.s_addr != dest_.sin_addr.s_addr) continue;
336 // pick the best available discover info
337 if ((size_t)r >= sizeof(eOuprot_cmd_DISCOVER_REPLY2_t)) {
338 auto *rep2 = reinterpret_cast<eOuprot_cmd_DISCOVER_REPLY2_t*>(buf);
339 discovered = rep2->discoveryreply;
340 got = true;
341 break;
342 } else if ((size_t)r >= sizeof(eOuprot_cmd_MOREINFO_REPLY_t)) {
343 auto *m = reinterpret_cast<eOuprot_cmd_MOREINFO_REPLY_t*>(buf);
344 discovered = m->discover;
345 got = true;
346 break;
347 } else if ((size_t)r >= sizeof(eOuprot_cmd_DISCOVER_REPLY_t)) {
348 auto *rep = reinterpret_cast<eOuprot_cmd_DISCOVER_REPLY_t*>(buf);
349 discovered = *rep;
350 got = true;
351 break;
352 } else {
353 // ignore legacy limited replies for programming (we need boardtype/process info)
354 }
355 }
356 if (!got) {
357 std::cerr << "No discover reply from target IP" << std::endl;
358 return false;
359 }
360
361 // derive board name used in firmware.info.xml (strip eobrd_ prefix if present)
362 const char *raw_board_name = eoboards_type2string2(static_cast<eObrd_type_t>(discovered.boardtype), static_cast<eObool_t>(0));
363 std::string board_name;
364 if (raw_board_name) {
365 board_name = raw_board_name;
366 const char prefix[] = "eobrd_";
367 if (board_name.rfind(prefix, 0) == 0) {
368 board_name = board_name.substr(strlen(prefix));
369 }
370 } else {
371 std::cerr << "Cannot resolve board type string from reply" << std::endl;
372 return false;
373 }
374 std::cout << "Discovered board type: " << board_name << std::endl;
375
376 // 2) find firmware file from firmware.info.xml
377 std::string hexpath;
378 if (!findFirmwareForBoard(board_name, hexpath)) {
379 std::cerr << "Firmware entry not found for board '" << board_name << "' in firmware.info.xml" << std::endl;
380 return false;
381 }
382 std::cout << "Found firmware file: " << hexpath << std::endl;
383
384 // 3) ensure maintenance mode
385 bool inmaintenance = (discovered.processes.runningnow == eUpdater);
386 if (!inmaintenance) {
387 std::cout << "Board not in maintenance, requesting jump2updater..." << std::endl;
388 int max_retries = 3;
389 for (int attempt = 0; attempt < max_retries; ++attempt) {
390 if (!jump2updater()) {
391 std::cerr << "Failed to send jump2updater, attempt " << (attempt + 1) << " of " << max_retries << std::endl;
392 continue;
393 }
394 sleep(5); // Wait longer for the board to transition
395 // Re-run discover and verify
396 eOuprot_cmd_DISCOVER_t cmd2;
397 memset(&cmd2, EOUPROT_VALUE_OF_UNUSED_BYTE, sizeof(cmd2));
398 cmd2.opc = uprot_OPC_LEGACY_SCAN;
399 cmd2.opc2 = uprot_OPC_DISCOVER;
400 cmd2.jump2updater = 0;
401 if (!sendRaw(&cmd2, sizeof(cmd2))) {
402 perror("sendto discover2");
403 return false;
404 }
405 bool got2 = false;
406 while (true) {
407 socklen_t sl = sizeof(src_);
408 ssize_t r = recvfrom(sock_, buf, sizeof(buf), 0, (struct sockaddr*)&src_, &sl);
409 if (r < 0) {
410 if (errno == EAGAIN || errno == EWOULDBLOCK) break;
411 perror("recvfrom");
412 return false;
413 }
414 if (src_.sin_addr.s_addr != dest_.sin_addr.s_addr) continue;
415 if ((size_t)r >= sizeof(eOuprot_cmd_DISCOVER_REPLY_t)) {
416 auto *rep = reinterpret_cast<eOuprot_cmd_DISCOVER_REPLY_t*>(buf);
417 discovered = *rep;
418 got2 = true;
419 break;
420 }
421 }
422 if (got2 && discovered.processes.runningnow == eUpdater) {
423 std::cout << "Board entered maintenance mode" << std::endl;
424 break;
425 }
426 std::cerr << "Board did not enter eUpdater (maintenance) mode, attempt " << (attempt + 1) << " of " << max_retries << std::endl;
427 }
428 if (discovered.processes.runningnow != eUpdater) {
429 std::cerr << "Board failed to enter maintenance mode after " << max_retries << " attempts" << std::endl;
430 return false;
431 }
432 } else {
433 std::cout << "Board already in maintenance" << std::endl;
434 }
435
436 // 4) open hex file and stream intel-hex
437 std::ifstream hexf(hexpath);
438 if (!hexf.is_open()) {
439 std::cerr << "Cannot open hex file: " << hexpath << std::endl;
440 return false;
441 }
442
443 // send PROG_START (partition = APPLICATION)
444 eOuprot_result_t resa;
445 if (!sendPROG_START(uprot_partitionAPPLICATION, resa)) {
446 std::cerr << "PROG_START no reply or failed" << std::endl;
447 return false;
448 }
449 if (resa != uprot_RES_OK) {
450 std::cerr << "PROG_START returned error: " << (int)resa << std::endl;
451 return false;
452 }
453 std::cout << "PROG_START acknowledged" << std::endl;
454
455 // parse intel hex: accumulate contiguous data up to uprot_PROGmaxsize, send chunks
456 std::string line;
457 uint32_t upper16 = 0;
458 std::vector<uint8_t> chunk;
459 uint32_t chunk_base = 0;
460 int chunks_sent = 0;
461 auto flush_chunk = [&](bool force)->bool {
462 if (chunk.empty()) return true;
463 // send with retry
464 const int MAX_RETRIES = 3;
465 eOuprot_result_t rr;
466 bool ok = false;
467 for (int attempt = 0; attempt < MAX_RETRIES; ++attempt) {
468 if (sendPROG_DATA_chunk(chunk_base, chunk.data(), chunk.size(), rr)) {
469 if (rr == uprot_RES_OK) { ok = true; break; }
470 else if (rr == uprot_RES_ERR_TRYAGAIN) { usleep(200000); continue; }
471 else { break; }
472 } else {
473 usleep(200000);
474 }
475 }
476 if (!ok) return false;
477 ++chunks_sent;
478 chunk.clear();
479 return true;
480 };
481
482 while (std::getline(hexf, line)) {
483 if (line.empty()) continue;
484 if (line[0] != ':') continue;
485 // parse
486 auto hexbyte = [](char hi, char lo)->int {
487 auto val = [](char c)->int {
488 if (c >= '0' && c <= '9') return c - '0';
489 if (c >= 'A' && c <= 'F') return c - 'A' + 10;
490 if (c >= 'a' && c <= 'f') return c - 'a' + 10;
491 return 0;
492 };
493 return (val(hi) << 4) | val(lo);
494 };
495 size_t idx = 1;
496 int bytecount = hexbyte(line[idx], line[idx+1]); idx += 2;
497 int addr16 = (hexbyte(line[idx], line[idx+1]) << 8) | hexbyte(line[idx+2], line[idx+3]); idx += 4;
498 int rectype = hexbyte(line[idx], line[idx+1]); idx += 2;
499 std::vector<uint8_t> data;
500 for (int i=0;i<bytecount;i++) {
501 int b = hexbyte(line[idx], line[idx+1]); idx += 2;
502 data.push_back(static_cast<uint8_t>(b));
503 }
504 // checksum ignored here (could be validated)
505 if (rectype == 0x00) { // data
506 uint32_t absaddr = (upper16 << 16) | static_cast<uint32_t>(addr16);
507
508 // Skip records that target RAM (non-flash). The board will drop these;
509 // avoid sending them from host (prevents extra "non flash chunk" packet).
510 // so it follows the same logic as in the case of GUI by removing non flash chunk
511 if ((absaddr & 0xFF000000u) == 0x20000000u) {
512 // flush any pending flash chunk before skipping this RAM record
513 if (!chunk.empty()) {
514 if (!flush_chunk(true)) { std::cerr << "Failed to send data chunk\n"; return false; }
515 }
516 std::cout << "Skipping Intel HEX record targeted to RAM at 0x"
517 << std::hex << absaddr << std::dec << " size=" << data.size() << std::endl;
518 continue;
519 }
520
521 // if chunk empty initialize
522 if (chunk.empty()) {
523 chunk_base = absaddr;
524 }
525 // if not contiguous or would overflow, flush
526 if (absaddr != chunk_base + chunk.size() || (chunk.size() + data.size() > uprot_PROGmaxsize)) {
527 if (!flush_chunk(true)) { std::cerr << "Failed to send data chunk\n"; return false; }
528 chunk_base = absaddr;
529 }
530 // append
531 for (uint8_t b : data) chunk.push_back(b);
532 // if chunk full flush
533 if (chunk.size() >= uprot_PROGmaxsize) {
534 if (!flush_chunk(true)) { std::cerr << "Failed to send data chunk\n"; return false; }
535 }
536 } else if (rectype == 0x01) { // EOF
537 // flush any pending data
538 if (!flush_chunk(true)) { std::cerr << "Failed to send final data chunk\n"; return false; }
539 break;
540 } else if (rectype == 0x04) { // extended linear address
541 // value is two bytes in data
542 if (data.size() >= 2) {
543 uint32_t newUpper = (static_cast<uint32_t>(data[0]) << 8) | static_cast<uint32_t>(data[1]);
544 // flush pending chunk before changing upper
545 if (!flush_chunk(true)) { std::cerr << "Failed to send chunk before extended linear\n"; return false; }
546 upper16 = newUpper;
547 }
548 } else {
549 // other record types: treat as boundary -> flush
550 if (!flush_chunk(true)) { std::cerr << "Failed to send chunk at record boundary\n"; return false; }
551 }
552 }
553
554 // in case file ended without EOF record, flush
555 if (!chunk.empty()) {
556 if (!flush_chunk(true)) { std::cerr << "Failed to send trailing chunk\n"; return false; }
557 }
558
559 hexf.close();
560
561 // send PROG_END: numberofpkts = chunks_sent + 1 (protocol requirement)
562 uint16_t numberofpkts = static_cast<uint16_t>(chunks_sent + 1);
563 std::cout << "Sending PROG_END, chunks_sent=" << chunks_sent << " numberofpkts=" << numberofpkts << std::endl;
564
565 eOuprot_result_t r_end;
566 if (!sendPROG_END(numberofpkts, r_end)) {
567 std::cerr << "PROG_END send/recv failed (no reply)" << std::endl;
568 return false;
569 }
570 if (r_end != uprot_RES_OK) {
571 std::cerr << "PROG_END returned error " << (int)r_end << std::endl;
572 return false;
573 }
574 std::cout << "PROG_END acknowledged, chunks sent: " << chunks_sent << std::endl;
575
576 // restart the board to run the new application
577 // if (!def2run_application()) {
578 // std::cerr << "Warning: def2run_application failed" << std::endl;
579 // }
580 // sleep(1);
581 // if (!restart()) {
582 // std::cerr << "Warning: restart failed" << std::endl;
583 // }
584
585 return true;
586}
587
588bool simpleEthClient::open(const char *ip, double rx_timeout_sec)
589{
590 closeSocket();
591 sock_ = ::socket(AF_INET, SOCK_DGRAM, 0);
592 if (sock_ < 0) { perror("socket"); return false; }
593
594 int on = 1;
595 if (setsockopt(sock_, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
596 perror("setsockopt(SO_REUSEADDR)");
597 }
598 struct sockaddr_in local;
599 memset(&local, 0, sizeof(local));
600 local.sin_family = AF_INET;
601 const char *local_ip_env = getenv("LOCAL_IP");
602 if (local_ip_env && inet_pton(AF_INET, local_ip_env, &local.sin_addr) <= 0) {
603 perror("inet_pton(LOCAL_IP)");
604 closeSocket();
605 return false;
606 }
607 if (!local_ip_env) local.sin_addr.s_addr = htonl(INADDR_ANY);
608 local.sin_port = htons(0); // Bind to an ephemeral port
609 if (bind(sock_, (struct sockaddr*)&local, sizeof(local)) < 0) {
610 perror("bind");
611 closeSocket();
612 return false;
613 }
614
615 memset(&dest_, 0, sizeof(dest_));
616 dest_.sin_family = AF_INET;
617 // Remote/receiver port used by the target remains 3333
618 dest_.sin_port = htons(3333);
619 if (inet_pton(AF_INET, ip, &dest_.sin_addr) <= 0) { perror("inet_pton"); closeSocket(); return false; }
620
621 struct timeval tv;
622 tv.tv_sec = static_cast<time_t>(rx_timeout_sec);
623 tv.tv_usec = static_cast<suseconds_t>((rx_timeout_sec - tv.tv_sec) * 1e6);
624 if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { perror("setsockopt"); closeSocket(); return false; }
625
626 return true;
627}
628
629// bool simpleEthClient::open(const char *ip, uint16_t port, double rx_timeout_sec)
630// {
631// closeSocket();
632// sock_ = ::socket(AF_INET, SOCK_DGRAM, 0);
633// if (sock_ < 0) { perror("socket"); return false; }
634
635// int on = 1;
636// if (setsockopt(sock_, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
637// perror("setsockopt(SO_REUSEADDR)");
638// }
639// struct sockaddr_in local;
640// memset(&local, 0, sizeof(local));
641// local.sin_family = AF_INET;
642// const char *local_ip_env = getenv("LOCAL_IP");
643// if (local_ip_env && inet_pton(AF_INET, local_ip_env, &local.sin_addr) <= 0) {
644// perror("inet_pton(LOCAL_IP)");
645// closeSocket();
646// return false;
647// }
648// if (!local_ip_env) local.sin_addr.s_addr = htonl(INADDR_ANY);
649// local.sin_port = htons(port);
650// if (bind(sock_, (struct sockaddr*)&local, sizeof(local)) < 0) {
651// perror("bind");
652// closeSocket();
653// return false;
654// }
655
656// memset(&dest_, 0, sizeof(dest_));
657// dest_.sin_family = AF_INET;
658// // Remote/receiver port used by the target remains 3333
659// dest_.sin_port = htons(3333);
660// if (inet_pton(AF_INET, ip, &dest_.sin_addr) <= 0) { perror("inet_pton"); closeSocket(); return false; }
661
662// struct timeval tv;
663// tv.tv_sec = static_cast<time_t>(rx_timeout_sec);
664// tv.tv_usec = static_cast<suseconds_t>((rx_timeout_sec - tv.tv_sec) * 1e6);
665// if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { perror("setsockopt"); closeSocket(); return false; }
666
667// return true;
668// }
669
671{
672 if (sock_ >= 0) { ::close(sock_); sock_ = -1; }
673}
674
675bool simpleEthClient::sendRaw(const void *buf, size_t len)
676{
677 if (sock_ < 0) return false;
678 ssize_t s = sendto(sock_, buf, len, 0, (struct sockaddr*)&dest_, sizeof(dest_));
679 return (s == (ssize_t)len);
680}
681
683{
684 if (sock_ < 0) return false;
685 eOuprot_cmd_DISCOVER_t cmd;
686 memset(&cmd, EOUPROT_VALUE_OF_UNUSED_BYTE, sizeof(cmd));
687 cmd.opc = uprot_OPC_LEGACY_SCAN;
688 cmd.opc2 = uprot_OPC_DISCOVER;
689 cmd.jump2updater = 0;
690
691 if (!sendRaw(&cmd, sizeof(cmd))) { perror("sendto"); return false; }
692 std::cout << "DISCOVER sent, awaiting replies..." << std::endl;
693
694 unsigned char buf[1500];
695 while (true)
696 {
697 socklen_t sl = sizeof(src_);
698 ssize_t r = recvfrom(sock_, buf, sizeof(buf), 0, (struct sockaddr*)&src_, &sl);
699 if (r < 0)
700 {
701 if (errno == EAGAIN || errno == EWOULDBLOCK) {
702 std::cout << "Receive timed out, no more replies." << std::endl;
703 break;
704 }
705 perror("recvfrom");
706 return false;
707 }
708
709 const char *srcip = inet_ntoa(src_.sin_addr);
710 if ((size_t)r >= sizeof(eOuprot_cmd_DISCOVER_REPLY2_t))
711 {
712 auto *rep2 = reinterpret_cast<eOuprot_cmd_DISCOVER_REPLY2_t*>(buf);
713 simpleEthClient::print_discover_reply(&rep2->discoveryreply, srcip);
714 for (int i = 0; i < 2; ++i)
715 {
716 const eOuprot_procinfo_t &p = rep2->extraprocs[i];
717 std::cout << " ExtraProc[" << i << "] type=" << (int)p.type
718 << " ver=" << (int)p.version.major << "." << (int)p.version.minor
719 << " rom_addr_kb=" << p.rom_addr_kb << " rom_size_kb=" << p.rom_size_kb << std::endl;
720 }
721 }
722 else if ((size_t)r >= sizeof(eOuprot_cmd_MOREINFO_REPLY_t))
723 {
724 auto *more = reinterpret_cast<eOuprot_cmd_MOREINFO_REPLY_t*>(buf);
725 simpleEthClient::print_discover_reply(&more->discover, srcip);
726 }
727 else if ((size_t)r >= sizeof(eOuprot_cmd_DISCOVER_REPLY_t))
728 {
729 auto *rep = reinterpret_cast<eOuprot_cmd_DISCOVER_REPLY_t*>(buf);
730 simpleEthClient::print_discover_reply(rep, srcip);
731 }
732 else if ((size_t)r >= sizeof(eOuprot_cmd_LEGACY_SCAN_REPLY_t))
733 {
734 auto *scan = reinterpret_cast<eOuprot_cmd_LEGACY_SCAN_REPLY_t*>(buf);
735 print_legacy_scan_reply(scan, srcip);
736 }
737 else
738 {
739 std::cout << "Ignored/unknown packet of size " << r << " from " << srcip << " -- raw:";
740 for (ssize_t i = 0; i < r; ++i)
741 {
742 printf(" %02X", buf[i]);
743 }
744 std::cout << std::endl;
745 }
746 }
747 return true;
748}
749
751{
752 eOuprot_cmd_DISCOVER_t cmd;
753 memset(&cmd, EOUPROT_VALUE_OF_UNUSED_BYTE, sizeof(cmd));
754 cmd.opc = uprot_OPC_LEGACY_SCAN;
755 cmd.opc2 = uprot_OPC_DISCOVER;
756 cmd.jump2updater = 1;
757
758 if (!sendRaw(&cmd, sizeof(cmd))) { perror("sendto"); return false; }
759 std::cout << "DISCOVER (jump2updater=1) sent to request maintenance mode." << std::endl;
760 return true;
761}
762
764{
765 eOuprot_cmd_DEF2RUN_t cmd;
766 memset(&cmd, EOUPROT_VALUE_OF_UNUSED_BYTE, sizeof(cmd));
767 cmd.opc = uprot_OPC_DEF2RUN;
768 cmd.proc = static_cast<uint8_t>(eApplication);
769 if (!sendRaw(&cmd, sizeof(cmd))) { perror("sendto"); return false; }
770 std::cout << "DEF2RUN -> Application sent." << std::endl;
771 return true;
772}
773
775{
776 eOuprot_cmd_RESTART_t cmd;
777 memset(&cmd, EOUPROT_VALUE_OF_UNUSED_BYTE, sizeof(cmd));
778 cmd.opc = uprot_OPC_RESTART;
779 if (!sendRaw(&cmd, sizeof(cmd))) { perror("sendto"); return false; }
780 std::cout << "RESTART sent." << std::endl;
781 return true;
782}
783
785{
786 eOuprot_cmd_BLINK_t cmd;
787 memset(&cmd, EOUPROT_VALUE_OF_UNUSED_BYTE, sizeof(cmd));
788 cmd.opc = uprot_OPC_BLINK;
789 if (!sendRaw(&cmd, sizeof(cmd))) { perror("sendto"); return false; }
790 std::cout << "BLINK sent." << std::endl;
791 return true;
792}
793
794void simpleEthClient::print_discover_reply(const eOuprot_cmd_DISCOVER_REPLY_t *reply, const char *srcip)
795{
796 if (!reply) return;
797 std::cout << "---- Discover reply from " << srcip << " ----" << std::endl;
798 std::cout << "Result: " << (int)reply->reply.res
799 << " ProtVer: " << (int)reply->reply.protversion
800 << " sizeofextra: " << (int)reply->reply.sizeofextra << std::endl;
801
802 char mac[18];
803 snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X",
804 reply->mac48[5], reply->mac48[4], reply->mac48[3],
805 reply->mac48[2], reply->mac48[1], reply->mac48[0]);
806
807 // try to obtain a human readable board name from the boardtype value
808 const char *raw_board_name = eoboards_type2string2(static_cast<eObrd_type_t>(reply->boardtype), static_cast<eObool_t>(0));
809 std::string board_name;
810 if (raw_board_name) {
811 board_name = raw_board_name;
812 const char prefix[] = "eobrd_";
813 if (board_name.rfind(prefix, 0) == 0) { // startswith
814 board_name = board_name.substr(strlen(prefix));
815 }
816 }
817
818 std::cout << "MAC: " << mac << " Type: " << (int)reply->boardtype;
819 if (!board_name.empty())
820 {
821 std::cout << " ( Board Info: " << board_name << " )";
822 }
823 std::cout << std::endl;
824
825 std::cout << "Running: " << (int)reply->processes.runningnow
826 << " Def2run: " << (int)reply->processes.def2run
827 << " Startup: " << (int)reply->processes.startup
828 << " NumProcesses: " << (int)reply->processes.numberofthem << std::endl;
829
830 std::cout << "Capabilities mask: 0x" << std::hex << reply->capabilities << std::dec << std::endl;
831
832 int num = std::min<int>(reply->processes.numberofthem, 3);
833 for (int i = 0; i < num; ++i)
834 {
835 const eOuprot_procinfo_t &p = reply->processes.info[i];
836 std::cout << " Process[" << i << "] type=" << (int)p.type;
837 std::cout << " ver=";
838 std::cout << (int)p.version.major << "." << (int)p.version.minor;
839 std::cout << " rom_addr_kb=" << p.rom_addr_kb << " rom_size_kb=" << p.rom_size_kb << std::endl;
840 }
841
842 // if eoboards mapping did not give a name, fall back to boardinfo32 string if present
843 if ((!board_name.empty()) && reply->boardinfo32[0] != EOUPROT_VALUE_OF_UNUSED_BYTE)
844 {
845 uint8_t len = reply->boardinfo32[0];
846 std::string info;
847 if (len > 0)
848 {
849 size_t copylen = std::min<size_t>(len, sizeof(reply->boardinfo32)-1);
850 info.assign(reinterpret_cast<const char*>(&reply->boardinfo32[1]), copylen);
851 }
852 if (!info.empty())
853 {
854 std::cout << "Board info: " << info << std::endl;
855 }
856 }
857
858 std::cout << "----------------------------------------" << std::endl;
859}
860
861
862void simpleEthClient::print_legacy_scan_reply(const eOuprot_cmd_LEGACY_SCAN_REPLY_t *scan, const char *srcip)
863{
864 if (!scan) return;
865 std::cout << "---- Legacy scan reply from " << srcip << " ----" << std::endl;
866 std::cout << "opc: " << (int)scan->opc
867 << " version: " << (int)scan->version.major << "." << (int)scan->version.minor << std::endl;
868 char mac[18] = {0};
869 snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X",
870 scan->mac48[5], scan->mac48[4], scan->mac48[3],
871 scan->mac48[2], scan->mac48[1], scan->mac48[0]);
872 std::cout << "MAC: " << mac << std::endl;
873
874 uint32_t mask = 0;
875 memcpy(&mask, scan->ipmask, sizeof(mask));
876 mask = ntohl(mask);
877 uint8_t b0 = (mask >> 24) & 0xFF;
878 uint8_t b1 = (mask >> 16) & 0xFF;
879 uint8_t b2 = (mask >> 8) & 0xFF;
880 uint8_t b3 = (mask >> 0) & 0xFF;
881 std::cout << "IP mask (dotted): " << (int)b0 << "." << (int)b1 << "." << (int)b2 << "." << (int)b3
882 << " (raw 0x" << std::hex << mask << std::dec << ")" << std::endl;
883 std::cout << "----------------------------------------" << std::endl;
884}
885
886
887std::string simpleEthClient::logname(const std::string &ip)
888{
889 std::string s = ip;
890 for (char &c : s) if (c == '.') c = '_';
891
892 // ensure logs are written to the current working directory (not exe dir)
893 char cwd_buf[PATH_MAX];
894 const char *cwd = getcwd(cwd_buf, sizeof(cwd_buf));
895 std::string prefix = (cwd && cwd[0]) ? std::string(cwd) : std::string(".");
896
897 return prefix + "/" + s + ".log";
898}
899
900// Ensure the target at `ip` is in maintenance (eUpdater). Logs progress to `log`.
901int simpleEthClient::ensureMaintenance(const char *ip, int max_retries, int retry_delay_sec, std::ostream &log)
902{
903 if (!open(ip, 5.0)) {
904 log << ip << ": open() failed: " << strerror(errno) << "\n";
905 return 1;
906 }
907
908 // initial discover (single-shot command)
909 eOuprot_cmd_DISCOVER_t cmd;
910 memset(&cmd, EOUPROT_VALUE_OF_UNUSED_BYTE, sizeof(cmd));
911 cmd.opc = uprot_OPC_LEGACY_SCAN;
912 cmd.opc2 = uprot_OPC_DISCOVER;
913 cmd.jump2updater = 0;
914 if (!sendRaw(&cmd, sizeof(cmd))) {
915 log << ip << ": sendRaw(discover) failed\n";
916 closeSocket();
917 return 1;
918 }
919
920 // --- Corrected Receive Logic ---
921 // Loop to find the most detailed discover reply available until timeout.
922 unsigned char buf[1500];
923 bool got = false;
924 eOuprot_cmd_DISCOVER_REPLY_t discovered = {0};
925 bool has_detailed_reply = false;
926
927 while (!has_detailed_reply) {
928 socklen_t sl = sizeof(src_);
929 ssize_t r = recvfrom(sock_, buf, sizeof(buf), 0, (struct sockaddr*)&src_, &sl);
930
931 if (r < 0) {
932 // If we timed out (EAGAIN/EWOULDBLOCK), just break the loop.
933 // If we already got a basic reply, we'll use it. Otherwise, it's a failure.
934 if (errno == EAGAIN || errno == EWOULDBLOCK) {
935 break;
936 }
937 // For other errors, log and fail.
938 log << ip << ": recvfrom(discover): " << strerror(errno) << "\n";
939 break;
940 }
941
942 // Ignore packets from other IPs
943 if (src_.sin_addr.s_addr != dest_.sin_addr.s_addr) {
944 continue;
945 }
946
947 // Process the packet, from most detailed to least detailed type
948 if ((size_t)r >= sizeof(eOuprot_cmd_DISCOVER_REPLY2_t)) {
949 auto *rep2 = reinterpret_cast<eOuprot_cmd_DISCOVER_REPLY2_t*>(buf);
950 discovered = rep2->discoveryreply;
951 got = true;
952 has_detailed_reply = true; // This is the best reply, we can stop listening.
953 } else if ((size_t)r >= sizeof(eOuprot_cmd_MOREINFO_REPLY_t)) {
954 auto *m = reinterpret_cast<eOuprot_cmd_MOREINFO_REPLY_t*>(buf);
955 discovered = m->discover;
956 got = true;
957 has_detailed_reply = true; // This is also a great reply, stop listening.
958 } else if ((size_t)r >= sizeof(eOuprot_cmd_DISCOVER_REPLY_t)) {
959 // This is a basic reply. We'll take it, but keep listening for a moment
960 // in case a more detailed one is coming right after.
961 if (!got) { // Only store it if we don't have a better one yet.
962 auto *rep = reinterpret_cast<eOuprot_cmd_DISCOVER_REPLY_t*>(buf);
963 discovered = *rep;
964 got = true;
965 }
966 }
967 }
968 // --- End of Corrected Receive Logic ---
969
970 // Attempt to get board's application version (if available)
971 int board_major = -1, board_minor = -1;
972 if (got) {
973 // The process table lists available firmwares. Find the one for 'eApplication'.
974 int num = std::min<int>(discovered.processes.numberofthem, 3);
975 for (int i = 0; i < num; ++i) {
976 const eOuprot_procinfo_t &p = discovered.processes.info[i];
977 if (p.type == static_cast<uint8_t>(eApplication)) {
978 board_major = static_cast<int>(p.version.major);
979 board_minor = static_cast<int>(p.version.minor);
980 log << ip << ": discovered application version " << board_major << "." << board_minor << "\n";
981 break;
982 }
983 }
984 }
985
986 // derive board name to lookup firmware entry (best-effort)
987 std::string board_name;
988 if (got) {
989 const char *raw_board_name = eoboards_type2string2(static_cast<eObrd_type_t>(discovered.boardtype), static_cast<eObool_t>(0));
990 if (raw_board_name) {
991 board_name = raw_board_name;
992 const char prefix[] = "eobrd_";
993 if (board_name.rfind(prefix, 0) == 0) board_name = board_name.substr(strlen(prefix));
994 }
995 }
996
997 // If we have both board name and discovered version, try to read firmware.info.xml version
998 int fw_major = -1, fw_minor = -1;
999 std::string hexpath;
1000 bool fw_found = false;
1001 if (!board_name.empty()) {
1002 if (findFirmwareForBoardWithVersion(board_name, hexpath, fw_major, fw_minor)) {
1003 fw_found = true;
1004 log << ip << ": firmware entry found: " << hexpath << " version="
1005 << (fw_major >= 0 ? std::to_string(fw_major) : std::string("N/A")) << "."
1006 << (fw_minor >= 0 ? std::to_string(fw_minor) : std::string("N/A")) << "\n";
1007 } else {
1008 log << ip << ": firmware entry not found for board '" << board_name << "'\n";
1009 }
1010 } else {
1011 log << ip << ": cannot derive board name from discover to compare versions\n";
1012 }
1013
1014 // If both board and firmware versions are known, compare and skip if up-to-date
1015 if (board_major >= 0 && fw_major >= 0) {
1016 // if (board_major > fw_major || (board_major == fw_major && board_minor >= fw_minor)) {
1017 if (board_major == fw_major && board_minor == fw_minor) {
1018 log << ip << ": board version " << board_major << "." << board_minor
1019 << " >= firmware " << fw_major << "." << fw_minor << " -> up-to-date, skipping programming\n";
1020 closeSocket();
1021 return 2; // up-to-date, skip programming
1022 } else {
1023 log << ip << ": board version " << board_major << "." << board_minor
1024 << " < firmware " << fw_major << "." << fw_minor << " -> will update\n";
1025 }
1026 } else {
1027 log << ip << ": version comparison not possible (board/fw version missing), proceeding to ensure maintenance\n";
1028 }
1029
1030 if (got && discovered.processes.runningnow == eUpdater) {
1031 log << ip << ": already in maintenance\n";
1032 closeSocket();
1033 return 0; // entered maintenance, needs programming
1034 }
1035 log << ip << ": not in maintenance (initial check)\n";
1036
1037 // attempt jump2updater with retries
1038 for (int attempt = 0; attempt < max_retries; ++attempt) {
1039 log << ip << ": attempt " << (attempt+1) << "/" << max_retries << " -> jump2updater\n";
1040 if (!jump2updater()) {
1041 log << ip << ": jump2updater send failed\n";
1042 }
1043 // wait for device to reboot/enter updater
1044 std::this_thread::sleep_for(std::chrono::seconds(retry_delay_sec));
1045
1046 // re-discover (single-shot)
1047 eOuprot_cmd_DISCOVER_t cmd2;
1048 memset(&cmd2, EOUPROT_VALUE_OF_UNUSED_BYTE, sizeof(cmd2));
1049 cmd2.opc = uprot_OPC_LEGACY_SCAN;
1050 cmd2.opc2 = uprot_OPC_DISCOVER;
1051 cmd2.jump2updater = 0;
1052 if (!sendRaw(&cmd2, sizeof(cmd2))) {
1053 log << ip << ": sendRaw(discover2) failed\n";
1054 continue;
1055 }
1056
1057 bool got2 = false;
1058 socklen_t sl2 = sizeof(src_);
1059 ssize_t r2 = recvfrom(sock_, buf, sizeof(buf), 0, (struct sockaddr*)&src_, &sl2);
1060 if (r2 > 0 && src_.sin_addr.s_addr == dest_.sin_addr.s_addr) {
1061 if ((size_t)r2 >= sizeof(eOuprot_cmd_DISCOVER_REPLY2_t)) {
1062 auto *rep2 = reinterpret_cast<eOuprot_cmd_DISCOVER_REPLY2_t*>(buf);
1063 discovered = rep2->discoveryreply;
1064 got2 = true;
1065 } else if ((size_t)r2 >= sizeof(eOuprot_cmd_MOREINFO_REPLY_t)) {
1066 auto *m = reinterpret_cast<eOuprot_cmd_MOREINFO_REPLY_t*>(buf);
1067 discovered = m->discover;
1068 got2 = true;
1069 } else if ((size_t)r2 >= sizeof(eOuprot_cmd_DISCOVER_REPLY_t)) {
1070 auto *rep = reinterpret_cast<eOuprot_cmd_DISCOVER_REPLY_t*>(buf);
1071 discovered = *rep;
1072 got2 = true;
1073 }
1074 } else if (r2 < 0 && !(errno == EAGAIN || errno == EWOULDBLOCK)) {
1075 log << ip << ": recvfrom(discover2): " << strerror(errno) << "\n";
1076 }
1077
1078 if (got2 && discovered.processes.runningnow == eUpdater) {
1079 log << ip << ": entered maintenance on attempt " << (attempt+1) << "\n";
1080 closeSocket();
1081 return 0; // entered maintenance
1082 }
1083 log << ip << ": still not in maintenance after attempt " << (attempt+1) << "\n";
1084 }
1085
1086 log << ip << ": failed to enter maintenance after " << max_retries << " attempts\n";
1087 closeSocket();
1088 return 1;
1089}
1090
1091// parse IPs from network xml (line oriented, inspired by findFirmwareForBoard)
1092std::vector<std::string> simpleEthClient::parseIPsFromNetworkFile(const char *xmlpath)
1093{
1094 std::vector<std::string> ips;
1095 std::ifstream f(xmlpath);
1096 if (!f.is_open()) {
1097 std::cerr << "parseIPsFromNetworkFile: cannot open " << xmlpath << "\n";
1098 return ips;
1099 }
1100
1101 std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
1102 f.close();
1103
1104 {
1105 size_t pos = 0;
1106 while ((pos = content.find("<!--", pos)) != std::string::npos) {
1107 size_t end = content.find("-->", pos + 4);
1108 if (end == std::string::npos) {
1109 // unterminated comment: erase from pos to end
1110 content.erase(pos);
1111 break;
1112 }
1113 content.erase(pos, (end + 3) - pos);
1114 }
1115 }
1116
1117 std::set<std::string> uniq;
1118
1119 // Find all <ataddress ...> tags, capture attributes block
1120 std::regex ataddr_re(R"(<\s*ataddress\b([^>]*)>)", std::regex::icase);
1121 std::sregex_iterator it(content.begin(), content.end(), ataddr_re), end;
1122
1123 std::regex ip_attr(R"(\bip\s*=\s*['"](\d{1,3}(?:\.\d{1,3}){3})['"])", std::regex::icase);
1124 std::regex canbus_attr(R"(\bcanbus\s*=)", std::regex::icase);
1125 std::regex canadr_attr(R"(\bcanadr\s*=)", std::regex::icase);
1126
1127 for (; it != end; ++it) {
1128 std::string attrs = (*it)[1].str();
1129 std::smatch m;
1130 if (std::regex_search(attrs, m, ip_attr)) {
1131 // skip if canbus or canadr attribute present (these are CAN sub-entries)
1132 if (std::regex_search(attrs, canbus_attr) || std::regex_search(attrs, canadr_attr)) {
1133 continue;
1134 }
1135 uniq.insert(m[1].str());
1136 }
1137 }
1138
1139 ips.assign(uniq.begin(), uniq.end());
1140 return ips;
1141}
1142
1143// helper to spawn the current executable with args and redirect child stdout/stderr to a logfile
1144pid_t simpleEthClient::spawn_and_log(const std::string &exe_path, const std::vector<std::string> &args, const std::string &logpath, bool append)
1145{
1146 pid_t pid = fork();
1147 if (pid < 0) return -1;
1148 if (pid == 0) {
1149 // child
1150 int flags = O_CREAT | O_WRONLY | (append ? O_APPEND : O_TRUNC);
1151 int fd = ::open(logpath.c_str(), flags, 0644);
1152 if (fd >= 0) {
1153 dup2(fd, STDOUT_FILENO);
1154 dup2(fd, STDERR_FILENO);
1155 if (fd > 2) ::close(fd);
1156 }
1157 // build argv
1158 std::vector<char*> argv;
1159 argv.reserve(args.size() + 2);
1160 argv.push_back(const_cast<char*>(exe_path.c_str()));
1161 for (const auto &a : args) argv.push_back(const_cast<char*>(a.c_str()));
1162 argv.push_back(nullptr);
1163 execv(exe_path.c_str(), argv.data());
1164 // if exec fails
1165 _exit(127);
1166 }
1167 // parent returns child's pid
1168 return pid;
1169}
1170
1171
1172// Orchestrator: parse network file, prepare boards in parallel, program prepared boards in parallel.
1174{
1175 // Require the shell script to provide the network file path via NETWORK_XML.
1176 const char *network_xml = getenv("NETWORK_XML");
1177 if (network_xml == nullptr || network_xml[0] == '\0') {
1178 std::cerr << "ERROR: NETWORK_XML environment variable not set. Aborting.\n";
1179 return 1;
1180 }
1181 //std::cout << "Using network file: " << network_xml << std::endl;
1182 auto ips = simpleEthClient::parseIPsFromNetworkFile(network_xml);
1183 if (ips.empty()) {
1184 std::cerr << "No IPs found in " << network_xml << std::endl;
1185 return 1;
1186 }
1187
1188 std::cout << "Found " << ips.size() << " unique IP(s) to process\n";
1189
1190 // find current executable path
1191 char exe_buf[PATH_MAX];
1192 ssize_t elen = readlink("/proc/self/exe", exe_buf, sizeof(exe_buf)-1);
1193 std::string exe_path;
1194 if (elen > 0) { exe_buf[elen] = '\0'; exe_path = exe_buf; }
1195 else { std::cerr << "Cannot resolve /proc/self/exe; aborting\n"; return 4; }
1196
1197 // Preparation phase (spawn processes to capture full output)
1198 std::vector<pid_t> prep_pids;
1199 std::vector<std::string> prep_logs;
1200 for (const auto &ip : ips) {
1201 std::string logfile = simpleEthClient::logname(ip);
1202 // spawn prepare helper: program will handle prepare when called with "prepare_ip"
1203 // truncate logfile so it's fresh for this run
1204 pid_t pid = simpleEthClient::spawn_and_log(exe_path, { "prepare_ip", ip }, logfile, /*append=*/false);
1205 if (pid > 0) { prep_pids.push_back(pid); prep_logs.push_back(logfile); }
1206 else std::cerr << "Failed to spawn prepare process for " << ip << std::endl;
1207 }
1208 // wait for prep processes and collect results
1209 std::vector<std::string> prepared; // need programming
1210 std::vector<std::string> not_prepared; // failed to prepare
1211 std::vector<std::string> up_to_date; // skipped (already up-to-date)
1212 for (size_t i = 0; i < prep_pids.size(); ++i) {
1213 int status = 0;
1214 pid_t w = waitpid(prep_pids[i], &status, 0);
1215 (void)w;
1216 if (WIFEXITED(status)) {
1217 int code = WEXITSTATUS(status);
1218 if (code == 0) {
1219 prepared.push_back(ips[i]); // needs programming
1220 } else if (code == 2) {
1221 up_to_date.push_back(ips[i]); // skip programming
1222 } else {
1223 not_prepared.push_back(ips[i]);
1224 }
1225 } else {
1226 not_prepared.push_back(ips[i]);
1227 }
1228 }
1229
1230 // Report preparation summary
1231 std::cout << "Preparation complete. prepared=" << prepared.size()
1232 << " up_to_date=" << up_to_date.size()
1233 << " not_prepared=" << not_prepared.size() << "\n";
1234 if (!not_prepared.empty()) {
1235 std::cout << "Not prepared boards (check logs for errors):\n";
1236 for (auto &ip : not_prepared) std::cout << " " << ip << "\n";
1237 }
1238 if (!up_to_date.empty()) {
1239 std::cout << "Up-to-date boards (skipped programming):\n";
1240 for (auto &ip : up_to_date) std::cout << " " << ip << "\n";
1241 }
1242
1243 // Check the outcome of the preparation phase and provide a clear summary.
1244 if (prepared.empty()) {
1245 if (!up_to_date.empty() && not_prepared.empty()) {
1246 // This is the ideal success case where no work was needed.
1247 std::cout << "\nSuccess: All boards are already up-to-date. No programming was necessary." << std::endl;
1248 return 0; // Return success.
1249 } else {
1250 // This is a failure case where some boards failed and none could be prepared.
1251 std::cerr << "\nFinished: No boards were prepared for programming. Check logs for details on failed boards." << std::endl;
1252 return 2; // Return an error code indicating nothing was programmed.
1253 }
1254 }
1255
1256 std::cout << "\nProceeding to program " << prepared.size() << " board(s) that require an update...\n";
1257
1258 // Programming phase (spawn processes and append to the same per-ip log)
1259 std::vector<pid_t> prog_pids;
1260 std::vector<std::string> prog_logs;
1261 for (const auto &ip : prepared) {
1262 std::string logfile = simpleEthClient::logname(ip);
1263 pid_t pid = simpleEthClient::spawn_and_log(exe_path, { "program_ip", ip }, logfile, /*append=*/true);
1264 if (pid > 0) { prog_pids.push_back(pid); prog_logs.push_back(logfile); }
1265 else std::cerr << "Failed to spawn program process for " << ip << std::endl;
1266 }
1267 std::vector<std::string> prog_ok;
1268 std::vector<std::string> prog_failed;
1269 for (size_t i = 0; i < prog_pids.size(); ++i) {
1270 int status = 0;
1271 pid_t w = waitpid(prog_pids[i], &status, 0);
1272 (void)w;
1273 if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
1274 prog_ok.push_back(prepared[i]);
1275 } else {
1276 prog_failed.push_back(prepared[i]);
1277 }
1278 }
1279
1280 // Final report
1281 std::cout << "Programming summary: success=" << prog_ok.size() << " failed=" << prog_failed.size() << "\n";
1282 if (!prog_failed.empty()) {
1283 std::cout << "Programming failed for:\n";
1284 for (auto &ip : prog_failed) std::cout << " " << ip << "\n";
1285 }
1286 if (!not_prepared.empty()) {
1287 std::cout << "Boards not prepared (skipped programming):\n";
1288 for (auto &ip : not_prepared) std::cout << " " << ip << "\n";
1289 }
1290
1291 // --- Restart phase: restart all successfully programmed boards ---
1292 if (!prog_ok.empty()) {
1293 std::cout << "Restarting successfully programmed boards (" << prog_ok.size() << ")...\n";
1294 std::vector<pid_t> restart_pids;
1295 std::vector<std::string> restart_logs;
1296 // spawn restart children (append to same per-ip log)
1297 for (const auto &ip : prog_ok) {
1298 std::string logfile = simpleEthClient::logname(ip);
1299 pid_t pid = simpleEthClient::spawn_and_log(exe_path, { "restart_ip", ip }, logfile, /*append=*/true);
1300 if (pid > 0) { restart_pids.push_back(pid); restart_logs.push_back(logfile); }
1301 else std::cerr << "Failed to spawn restart process for " << ip << std::endl;
1302 }
1303
1304 std::vector<std::string> restart_ok, restart_failed;
1305 for (size_t i = 0; i < restart_pids.size(); ++i) {
1306 int status = 0;
1307 waitpid(restart_pids[i], &status, 0);
1308 if (WIFEXITED(status) && WEXITSTATUS(status) == 0) restart_ok.push_back(prog_ok[i]);
1309 else restart_failed.push_back(prog_ok[i]);
1310 }
1311
1312 std::cout << "Restart summary: success=" << restart_ok.size() << " failed=" << restart_failed.size() << "\n";
1313 if (!restart_failed.empty()) {
1314 std::cout << "Restart failed for:\n";
1315 for (auto &ip : restart_failed) std::cout << " " << ip << "\n";
1316 }
1317 }
1318
1319 return prog_failed.empty() ? 0 : 3;
1320}
1321
1322
1323
1324
1325int main(int argc, char *argv[])
1326{
1327 // preserve existing helper child modes (exact same checks as before)
1328 if (argc == 3 && std::string(argv[1]) == "prepare_ip") {
1329 const char *ip = argv[2];
1330 simpleEthClient client;
1331 int result = client.ensureMaintenance(ip, 4, 5, std::cout);
1332 return result;
1333 }
1334 if (argc == 3 && std::string(argv[1]) == "program_ip") {
1335 const char *ip = argv[2];
1336 simpleEthClient client;
1337 if (!client.open(ip, 5.0)) return 2;
1338 bool ok = client.program();
1339 return ok ? 0 : 1;
1340 }
1341 if (argc == 3 && std::string(argv[1]) == "restart_ip") {
1342 const char *ip = argv[2];
1343 simpleEthClient client;
1344 if (!client.open(ip, 5.0)) {
1345 std::cerr << ip << ": open failed for restart\n";
1346 return 2;
1347 }
1348 client.def2run_application();
1349 sleep(1);
1350 if (!client.restart()) {
1351 std::cerr << ip << ": restart failed" << std::endl;
1352 return 1;
1353 }
1354 std::cout << ip << ": restart succeeded\n";
1355 return 0;
1356 }
1357
1358 // Support single-argument orchestrator mode (unchanged)
1359 if (argc == 2) {
1360 std::string single = argv[1];
1361 if (single == "parallel_update" || single == "parallel_program") {
1363 }
1364 }
1365
1366 // Flexible parsing: accept either "<ip> <cmd>" or "<cmd> <ip>"
1367 if (argc >= 3) {
1368 const char *a = argv[1];
1369 const char *b = argv[2];
1370 auto is_ip = [](const char *s)->bool {
1371 if (!s) return false;
1372 struct in_addr ina;
1373 return inet_pton(AF_INET, s, &ina) == 1;
1374 };
1375
1376 const char *ip = nullptr;
1377 const char *cmd = nullptr;
1378 if (is_ip(a)) {
1379 ip = a; cmd = b;
1380 } else if (is_ip(b)) {
1381 ip = b; cmd = a;
1382 } else {
1383 std::cerr << "Usage: ./yafu <ip> <command> OR ./yafu <command> <ip>\n";
1384 std::cerr << "Commands: discover, maintenance|jump2updater, application|def2run_application, restart, blink, program\n";
1385 return 1;
1386 }
1387
1388 simpleEthClient client;
1389 if (!client.open(ip, 5.0)) return 1;
1390
1391 std::string scmd(cmd);
1392 if (scmd == "discover") {
1393 client.discover();
1394 } else if (scmd == "maintenance" || scmd == "jump2updater") {
1395 client.jump2updater();
1396 } else if (scmd == "application" || scmd == "def2run_application") {
1397 client.def2run_application();
1398 sleep(1);
1399 client.restart();
1400 } else if (scmd == "restart") {
1401 client.restart();
1402 } else if (scmd == "blink") {
1403 client.blink();
1404 } else if (scmd == "program") {
1405 if (!client.program()) {
1406 std::cerr << "Programming failed\n";
1407 return 1;
1408 }
1409 } else {
1410 std::cerr << "Unknown command: " << scmd << std::endl;
1411 return 1;
1412 }
1413 return 0;
1414 }
1415
1416 std::cerr << "Usage: ./yafu <ip> <command> OR ./yafu <command> <ip>\n";
1417 return 1;
1418}
@ data
static int orchestrateParallelProgram()
Definition yafu.cpp:1173
void closeSocket()
Definition yafu.cpp:670
bool jump2updater()
Definition yafu.cpp:750
bool sendRaw(const void *buf, size_t len)
Definition yafu.cpp:675
bool discover()
Definition yafu.cpp:682
bool blink()
Definition yafu.cpp:784
int ensureMaintenance(const char *ip, int max_retries, int retry_delay_sec, std::ostream &log)
Definition yafu.cpp:901
bool program()
Definition yafu.cpp:309
bool restart()
Definition yafu.cpp:774
bool findFirmwareForBoardWithVersion(const std::string &boardname, std::string &out_hexpath, int &out_major, int &out_minor)
Definition yafu.cpp:192
bool open(const char *ip, double rx_timeout_sec=5.0)
Definition yafu.cpp:588
bool def2run_application()
Definition yafu.cpp:763
cmd
Definition dataTypes.h:30
static uint32_t idx[BOARD_NUM]
int main()
Definition main.cpp:67
void append(const size_t index, shared_ptr< Epsilon_neighbours_t > en)
grid on