24std::string simpleEthClient::resolveFirmwareInfoXml()
28 const char *env = getenv(
"YAFU_FIRMWARE_INFO");
29 if (env && env[0] !=
'\0') {
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);
39 std::vector<std::string> prefixes;
40 const char *ament = getenv(
"AMENT_PREFIX_PATH");
41 if (ament && ament[0] !=
'\0') {
43 std::stringstream ss(s);
45 while (std::getline(ss, item,
':'))
if (!item.empty()) prefixes.push_back(item);
49 char exe_buf[PATH_MAX];
50 ssize_t elen = readlink(
"/proc/self/exe", exe_buf,
sizeof(exe_buf) - 1);
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 +
"/..");
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";
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;
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;
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"
81 for (
const auto &c : repo_candidates) {
83 if (stat(c.c_str(), &st) == 0) {
84 std::cerr <<
"[Resolver] Found firmware info via in-tree development path." << std::endl;
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;
98bool simpleEthClient::recvReplyForIP(uint8_t expected_opc,
int timeout_ms, eOuprot_result_t &out_res)
101 unsigned char buf[1500];
102 socklen_t sl =
sizeof(src_);
106 ssize_t r = recvfrom(sock_, buf,
sizeof(buf), 0, (
struct sockaddr*)&src_, &sl);
108 if (errno == EAGAIN || errno == EWOULDBLOCK) {
116 if (src_.sin_addr.s_addr != dest_.sin_addr.s_addr) {
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);
128bool simpleEthClient::findFirmwareForBoard(
const std::string &boardname, std::string &out_hexpath)
130 std::string xmlpath = resolveFirmwareInfoXml();
131 if (xmlpath.empty())
return false;
132 std::ifstream
f(xmlpath);
133 if (!
f.is_open())
return false;
135 bool inboard =
false;
136 std::string foundfile;
137 while (std::getline(
f, line)) {
139 std::string s = line;
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)) {
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>")));
166 if (s.find(
"</board>") != std::string::npos)
break;
170 if (foundfile.empty())
return false;
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;
179 if (stat(candidate.c_str(), &st) == 0) {
180 out_hexpath = candidate;
184 if (stat(foundfile.c_str(), &st) == 0) {
185 out_hexpath = foundfile;
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;
201 bool in_correct_board_block =
false;
202 std::string foundfile;
204 auto tolower_copy = [](std::string s) {
205 for (
char &c : s) c =
static_cast<char>(std::tolower(
static_cast<unsigned char>(c)));
209 while (std::getline(
f, line)) {
210 std::string lower_line = tolower_copy(line);
212 if (!in_correct_board_block) {
214 std::string key =
"<board type=\"" + tolower_copy(boardname) +
"\"";
215 if (lower_line.find(key) != std::string::npos) {
216 in_correct_board_block =
true;
221 std::regex file_re(R
"(<\s*file\s*>([^<]+)</file>)");
222 if (std::regex_search(line, match, file_re)) {
223 foundfile = match[1].str();
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());
233 if (lower_line.find(
"</board>") != std::string::npos) {
240 if (foundfile.empty()) {
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;
251 if (stat(candidate.c_str(), &st) == 0) {
252 out_hexpath = candidate;
255 if (stat(foundfile.c_str(), &st) == 0) {
256 out_hexpath = foundfile;
259 out_hexpath = candidate;
263bool simpleEthClient::sendPROG_START(eOuprot_partition2prog_t partition, eOuprot_result_t &out_res)
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;
274bool simpleEthClient::sendPROG_DATA_chunk(uint32_t address,
const uint8_t *
data,
size_t len, eOuprot_result_t &out_res)
276 if (len == 0 || len > uprot_PROGmaxsize)
return false;
278 const size_t HEAD_SIZE = 7;
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);
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);
287 packet[5] =
static_cast<uint8_t
>(len & 0xFF);
288 packet[6] =
static_cast<uint8_t
>((len >> 8) & 0xFF);
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;
296bool simpleEthClient::sendPROG_END(uint16_t numberofpkts, eOuprot_result_t &out_res)
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;
311 if (sock_ < 0)
return false;
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; }
322 unsigned char buf[1500];
324 eOuprot_cmd_DISCOVER_REPLY_t discovered = {0};
329 ssize_t r = recvfrom(sock_, buf,
sizeof(buf), 0, (
struct sockaddr*)&src_, &sl);
331 if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
335 if (src_.sin_addr.s_addr != dest_.sin_addr.s_addr)
continue;
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;
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;
347 }
else if ((
size_t)r >=
sizeof(eOuprot_cmd_DISCOVER_REPLY_t)) {
348 auto *rep =
reinterpret_cast<eOuprot_cmd_DISCOVER_REPLY_t*
>(buf);
357 std::cerr <<
"No discover reply from target IP" << std::endl;
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));
371 std::cerr <<
"Cannot resolve board type string from reply" << std::endl;
374 std::cout <<
"Discovered board type: " << board_name << std::endl;
378 if (!findFirmwareForBoard(board_name, hexpath)) {
379 std::cerr <<
"Firmware entry not found for board '" << board_name <<
"' in firmware.info.xml" << std::endl;
382 std::cout <<
"Found firmware file: " << hexpath << std::endl;
385 bool inmaintenance = (discovered.processes.runningnow == eUpdater);
386 if (!inmaintenance) {
387 std::cout <<
"Board not in maintenance, requesting jump2updater..." << std::endl;
389 for (
int attempt = 0; attempt < max_retries; ++attempt) {
391 std::cerr <<
"Failed to send jump2updater, attempt " << (attempt + 1) <<
" of " << max_retries << std::endl;
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");
407 socklen_t sl =
sizeof(src_);
408 ssize_t r = recvfrom(sock_, buf,
sizeof(buf), 0, (
struct sockaddr*)&src_, &sl);
410 if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
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);
422 if (got2 && discovered.processes.runningnow == eUpdater) {
423 std::cout <<
"Board entered maintenance mode" << std::endl;
426 std::cerr <<
"Board did not enter eUpdater (maintenance) mode, attempt " << (attempt + 1) <<
" of " << max_retries << std::endl;
428 if (discovered.processes.runningnow != eUpdater) {
429 std::cerr <<
"Board failed to enter maintenance mode after " << max_retries <<
" attempts" << std::endl;
433 std::cout <<
"Board already in maintenance" << std::endl;
437 std::ifstream hexf(hexpath);
438 if (!hexf.is_open()) {
439 std::cerr <<
"Cannot open hex file: " << hexpath << std::endl;
444 eOuprot_result_t resa;
445 if (!sendPROG_START(uprot_partitionAPPLICATION, resa)) {
446 std::cerr <<
"PROG_START no reply or failed" << std::endl;
449 if (resa != uprot_RES_OK) {
450 std::cerr <<
"PROG_START returned error: " << (int)resa << std::endl;
453 std::cout <<
"PROG_START acknowledged" << std::endl;
457 uint32_t upper16 = 0;
458 std::vector<uint8_t> chunk;
459 uint32_t chunk_base = 0;
461 auto flush_chunk = [&](
bool force)->
bool {
462 if (chunk.empty())
return true;
464 const int MAX_RETRIES = 3;
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; }
476 if (!
ok)
return false;
482 while (std::getline(hexf, line)) {
483 if (line.empty())
continue;
484 if (line[0] !=
':')
continue;
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;
493 return (val(hi) << 4) | val(lo);
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));
505 if (rectype == 0x00) {
506 uint32_t absaddr = (upper16 << 16) | static_cast<uint32_t>(addr16);
511 if ((absaddr & 0xFF000000u) == 0x20000000u) {
513 if (!chunk.empty()) {
514 if (!flush_chunk(
true)) { std::cerr <<
"Failed to send data chunk\n";
return false; }
516 std::cout <<
"Skipping Intel HEX record targeted to RAM at 0x"
517 << std::hex << absaddr << std::dec <<
" size=" <<
data.size() << std::endl;
523 chunk_base = absaddr;
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;
531 for (uint8_t b :
data) chunk.push_back(b);
533 if (chunk.size() >= uprot_PROGmaxsize) {
534 if (!flush_chunk(
true)) { std::cerr <<
"Failed to send data chunk\n";
return false; }
536 }
else if (rectype == 0x01) {
538 if (!flush_chunk(
true)) { std::cerr <<
"Failed to send final data chunk\n";
return false; }
540 }
else if (rectype == 0x04) {
542 if (
data.size() >= 2) {
543 uint32_t newUpper = (
static_cast<uint32_t
>(
data[0]) << 8) |
static_cast<uint32_t
>(
data[1]);
545 if (!flush_chunk(
true)) { std::cerr <<
"Failed to send chunk before extended linear\n";
return false; }
550 if (!flush_chunk(
true)) { std::cerr <<
"Failed to send chunk at record boundary\n";
return false; }
555 if (!chunk.empty()) {
556 if (!flush_chunk(
true)) { std::cerr <<
"Failed to send trailing chunk\n";
return false; }
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;
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;
570 if (r_end != uprot_RES_OK) {
571 std::cerr <<
"PROG_END returned error " << (int)r_end << std::endl;
574 std::cout <<
"PROG_END acknowledged, chunks sent: " << chunks_sent << std::endl;
591 sock_ = ::socket(AF_INET, SOCK_DGRAM, 0);
592 if (sock_ < 0) { perror(
"socket");
return false; }
595 if (setsockopt(sock_, SOL_SOCKET, SO_REUSEADDR, &
on,
sizeof(
on)) < 0) {
596 perror(
"setsockopt(SO_REUSEADDR)");
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)");
607 if (!local_ip_env) local.sin_addr.s_addr = htonl(INADDR_ANY);
608 local.sin_port = htons(0);
609 if (bind(sock_, (
struct sockaddr*)&local,
sizeof(local)) < 0) {
615 memset(&dest_, 0,
sizeof(dest_));
616 dest_.sin_family = AF_INET;
618 dest_.sin_port = htons(3333);
619 if (inet_pton(AF_INET, ip, &dest_.sin_addr) <= 0) { perror(
"inet_pton");
closeSocket();
return false; }
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; }
672 if (sock_ >= 0) { ::close(sock_); sock_ = -1; }
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);
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;
691 if (!
sendRaw(&
cmd,
sizeof(
cmd))) { perror(
"sendto");
return false; }
692 std::cout <<
"DISCOVER sent, awaiting replies..." << std::endl;
694 unsigned char buf[1500];
697 socklen_t sl =
sizeof(src_);
698 ssize_t r = recvfrom(sock_, buf,
sizeof(buf), 0, (
struct sockaddr*)&src_, &sl);
701 if (errno == EAGAIN || errno == EWOULDBLOCK) {
702 std::cout <<
"Receive timed out, no more replies." << std::endl;
709 const char *srcip = inet_ntoa(src_.sin_addr);
710 if ((
size_t)r >=
sizeof(eOuprot_cmd_DISCOVER_REPLY2_t))
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)
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;
722 else if ((
size_t)r >=
sizeof(eOuprot_cmd_MOREINFO_REPLY_t))
724 auto *more =
reinterpret_cast<eOuprot_cmd_MOREINFO_REPLY_t*
>(buf);
725 simpleEthClient::print_discover_reply(&more->discover, srcip);
727 else if ((
size_t)r >=
sizeof(eOuprot_cmd_DISCOVER_REPLY_t))
729 auto *rep =
reinterpret_cast<eOuprot_cmd_DISCOVER_REPLY_t*
>(buf);
730 simpleEthClient::print_discover_reply(rep, srcip);
732 else if ((
size_t)r >=
sizeof(eOuprot_cmd_LEGACY_SCAN_REPLY_t))
734 auto *scan =
reinterpret_cast<eOuprot_cmd_LEGACY_SCAN_REPLY_t*
>(buf);
735 print_legacy_scan_reply(scan, srcip);
739 std::cout <<
"Ignored/unknown packet of size " << r <<
" from " << srcip <<
" -- raw:";
740 for (ssize_t i = 0; i < r; ++i)
742 printf(
" %02X", buf[i]);
744 std::cout << std::endl;
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;
758 if (!
sendRaw(&
cmd,
sizeof(
cmd))) { perror(
"sendto");
return false; }
759 std::cout <<
"DISCOVER (jump2updater=1) sent to request maintenance mode." << std::endl;
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;
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;
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;
794void simpleEthClient::print_discover_reply(
const eOuprot_cmd_DISCOVER_REPLY_t *reply,
const char *srcip)
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;
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]);
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) {
814 board_name = board_name.substr(strlen(prefix));
818 std::cout <<
"MAC: " << mac <<
" Type: " << (int)reply->boardtype;
819 if (!board_name.empty())
821 std::cout <<
" ( Board Info: " << board_name <<
" )";
823 std::cout << std::endl;
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;
830 std::cout <<
"Capabilities mask: 0x" << std::hex << reply->capabilities << std::dec << std::endl;
832 int num = std::min<int>(reply->processes.numberofthem, 3);
833 for (
int i = 0; i < num; ++i)
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;
843 if ((!board_name.empty()) && reply->boardinfo32[0] != EOUPROT_VALUE_OF_UNUSED_BYTE)
845 uint8_t len = reply->boardinfo32[0];
849 size_t copylen = std::min<size_t>(len,
sizeof(reply->boardinfo32)-1);
850 info.assign(
reinterpret_cast<const char*
>(&reply->boardinfo32[1]), copylen);
854 std::cout <<
"Board info: " <<
info << std::endl;
858 std::cout <<
"----------------------------------------" << std::endl;
862void simpleEthClient::print_legacy_scan_reply(
const eOuprot_cmd_LEGACY_SCAN_REPLY_t *scan,
const char *srcip)
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;
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;
875 memcpy(&mask, scan->ipmask,
sizeof(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;
887std::string simpleEthClient::logname(
const std::string &ip)
890 for (
char &c : s) if (c ==
'.') c =
'_';
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(
".");
897 return prefix +
"/" + s +
".log";
903 if (!
open(ip, 5.0)) {
904 log << ip <<
": open() failed: " << strerror(errno) <<
"\n";
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;
915 log << ip <<
": sendRaw(discover) failed\n";
922 unsigned char buf[1500];
924 eOuprot_cmd_DISCOVER_REPLY_t discovered = {0};
925 bool has_detailed_reply =
false;
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);
934 if (errno == EAGAIN || errno == EWOULDBLOCK) {
938 log << ip <<
": recvfrom(discover): " << strerror(errno) <<
"\n";
943 if (src_.sin_addr.s_addr != dest_.sin_addr.s_addr) {
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;
952 has_detailed_reply =
true;
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;
957 has_detailed_reply =
true;
958 }
else if ((
size_t)r >=
sizeof(eOuprot_cmd_DISCOVER_REPLY_t)) {
962 auto *rep =
reinterpret_cast<eOuprot_cmd_DISCOVER_REPLY_t*
>(buf);
971 int board_major = -1, board_minor = -1;
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";
987 std::string board_name;
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));
998 int fw_major = -1, fw_minor = -1;
1000 bool fw_found =
false;
1001 if (!board_name.empty()) {
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";
1008 log << ip <<
": firmware entry not found for board '" << board_name <<
"'\n";
1011 log << ip <<
": cannot derive board name from discover to compare versions\n";
1015 if (board_major >= 0 && fw_major >= 0) {
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";
1023 log << ip <<
": board version " << board_major <<
"." << board_minor
1024 <<
" < firmware " << fw_major <<
"." << fw_minor <<
" -> will update\n";
1027 log << ip <<
": version comparison not possible (board/fw version missing), proceeding to ensure maintenance\n";
1030 if (got && discovered.processes.runningnow == eUpdater) {
1031 log << ip <<
": already in maintenance\n";
1035 log << ip <<
": not in maintenance (initial check)\n";
1038 for (
int attempt = 0; attempt < max_retries; ++attempt) {
1039 log << ip <<
": attempt " << (attempt+1) <<
"/" << max_retries <<
" -> jump2updater\n";
1041 log << ip <<
": jump2updater send failed\n";
1044 std::this_thread::sleep_for(std::chrono::seconds(retry_delay_sec));
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";
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;
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;
1069 }
else if ((
size_t)r2 >=
sizeof(eOuprot_cmd_DISCOVER_REPLY_t)) {
1070 auto *rep =
reinterpret_cast<eOuprot_cmd_DISCOVER_REPLY_t*
>(buf);
1074 }
else if (r2 < 0 && !(errno == EAGAIN || errno == EWOULDBLOCK)) {
1075 log << ip <<
": recvfrom(discover2): " << strerror(errno) <<
"\n";
1078 if (got2 && discovered.processes.runningnow == eUpdater) {
1079 log << ip <<
": entered maintenance on attempt " << (attempt+1) <<
"\n";
1083 log << ip <<
": still not in maintenance after attempt " << (attempt+1) <<
"\n";
1086 log << ip <<
": failed to enter maintenance after " << max_retries <<
" attempts\n";
1092std::vector<std::string> simpleEthClient::parseIPsFromNetworkFile(
const char *xmlpath)
1094 std::vector<std::string> ips;
1095 std::ifstream
f(xmlpath);
1097 std::cerr <<
"parseIPsFromNetworkFile: cannot open " << xmlpath <<
"\n";
1101 std::string content((std::istreambuf_iterator<char>(
f)), std::istreambuf_iterator<char>());
1106 while ((pos = content.find(
"<!--", pos)) != std::string::npos) {
1107 size_t end = content.find(
"-->", pos + 4);
1108 if (end == std::string::npos) {
1113 content.erase(pos, (end + 3) - pos);
1117 std::set<std::string> uniq;
1120 std::regex ataddr_re(R
"(<\s*ataddress\b([^>]*)>)", std::regex::icase);
1121 std::sregex_iterator it(content.begin(), content.end(), ataddr_re), end;
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);
1127 for (; it != end; ++it) {
1128 std::string attrs = (*it)[1].str();
1130 if (std::regex_search(attrs, m, ip_attr)) {
1132 if (std::regex_search(attrs, canbus_attr) || std::regex_search(attrs, canadr_attr)) {
1135 uniq.insert(m[1].str());
1139 ips.assign(uniq.begin(), uniq.end());
1144pid_t simpleEthClient::spawn_and_log(
const std::string &exe_path,
const std::vector<std::string> &args,
const std::string &logpath,
bool append)
1147 if (pid < 0)
return -1;
1150 int flags = O_CREAT | O_WRONLY | (
append ? O_APPEND : O_TRUNC);
1151 int fd =
::open(logpath.c_str(), flags, 0644);
1153 dup2(fd, STDOUT_FILENO);
1154 dup2(fd, STDERR_FILENO);
1155 if (fd > 2) ::close(fd);
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());
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";
1182 auto ips = simpleEthClient::parseIPsFromNetworkFile(network_xml);
1184 std::cerr <<
"No IPs found in " << network_xml << std::endl;
1188 std::cout <<
"Found " << ips.size() <<
" unique IP(s) to process\n";
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; }
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);
1204 pid_t pid = simpleEthClient::spawn_and_log(exe_path, {
"prepare_ip", ip }, logfile,
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;
1209 std::vector<std::string> prepared;
1210 std::vector<std::string> not_prepared;
1211 std::vector<std::string> up_to_date;
1212 for (
size_t i = 0; i < prep_pids.size(); ++i) {
1214 pid_t w = waitpid(prep_pids[i], &status, 0);
1216 if (WIFEXITED(status)) {
1217 int code = WEXITSTATUS(status);
1219 prepared.push_back(ips[i]);
1220 }
else if (code == 2) {
1221 up_to_date.push_back(ips[i]);
1223 not_prepared.push_back(ips[i]);
1226 not_prepared.push_back(ips[i]);
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";
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";
1244 if (prepared.empty()) {
1245 if (!up_to_date.empty() && not_prepared.empty()) {
1247 std::cout <<
"\nSuccess: All boards are already up-to-date. No programming was necessary." << std::endl;
1251 std::cerr <<
"\nFinished: No boards were prepared for programming. Check logs for details on failed boards." << std::endl;
1256 std::cout <<
"\nProceeding to program " << prepared.size() <<
" board(s) that require an update...\n";
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,
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;
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) {
1271 pid_t w = waitpid(prog_pids[i], &status, 0);
1273 if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
1274 prog_ok.push_back(prepared[i]);
1276 prog_failed.push_back(prepared[i]);
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";
1286 if (!not_prepared.empty()) {
1287 std::cout <<
"Boards not prepared (skipped programming):\n";
1288 for (
auto &ip : not_prepared) std::cout <<
" " << ip <<
"\n";
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;
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,
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;
1304 std::vector<std::string> restart_ok, restart_failed;
1305 for (
size_t i = 0; i < restart_pids.size(); ++i) {
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]);
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";
1319 return prog_failed.empty() ? 0 : 3;
1328 if (argc == 3 && std::string(argv[1]) ==
"prepare_ip") {
1329 const char *ip = argv[2];
1334 if (argc == 3 && std::string(argv[1]) ==
"program_ip") {
1335 const char *ip = argv[2];
1337 if (!client.
open(ip, 5.0))
return 2;
1341 if (argc == 3 && std::string(argv[1]) ==
"restart_ip") {
1342 const char *ip = argv[2];
1344 if (!client.
open(ip, 5.0)) {
1345 std::cerr << ip <<
": open failed for restart\n";
1351 std::cerr << ip <<
": restart failed" << std::endl;
1354 std::cout << ip <<
": restart succeeded\n";
1360 std::string single = argv[1];
1361 if (single ==
"parallel_update" || single ==
"parallel_program") {
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;
1373 return inet_pton(AF_INET, s, &ina) == 1;
1376 const char *ip =
nullptr;
1377 const char *
cmd =
nullptr;
1380 }
else if (is_ip(b)) {
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";
1389 if (!client.
open(ip, 5.0))
return 1;
1391 std::string scmd(
cmd);
1392 if (scmd ==
"discover") {
1394 }
else if (scmd ==
"maintenance" || scmd ==
"jump2updater") {
1396 }
else if (scmd ==
"application" || scmd ==
"def2run_application") {
1400 }
else if (scmd ==
"restart") {
1402 }
else if (scmd ==
"blink") {
1404 }
else if (scmd ==
"program") {
1406 std::cerr <<
"Programming failed\n";
1410 std::cerr <<
"Unknown command: " << scmd << std::endl;
1416 std::cerr <<
"Usage: ./yafu <ip> <command> OR ./yafu <command> <ip>\n";
static int orchestrateParallelProgram()
bool sendRaw(const void *buf, size_t len)
int ensureMaintenance(const char *ip, int max_retries, int retry_delay_sec, std::ostream &log)
bool findFirmwareForBoardWithVersion(const std::string &boardname, std::string &out_hexpath, int &out_major, int &out_minor)
bool open(const char *ip, double rx_timeout_sec=5.0)
bool def2run_application()
static uint32_t idx[BOARD_NUM]
void append(const size_t index, shared_ptr< Epsilon_neighbours_t > en)