Skip to content

Commit a4a25af

Browse files
feat: flash firmware onto CAN nodes
1 parent 5969a4e commit a4a25af

File tree

4 files changed

+231
-37
lines changed

4 files changed

+231
-37
lines changed

src/drivers/uavcan/uavcan_drivers/posix/include/uavcan_posix/firmware_version_checker.hpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class FirmwareVersionChecker : public uavcan::IFirmwareVersionChecker
9494
* @return True - the class will begin sending update requests.
9595
* False - the node will be ignored, no request will be sent.
9696
*/
97-
virtual bool shouldRequestFirmwareUpdate(uavcan::NodeID,
97+
virtual bool shouldRequestFirmwareUpdate(uavcan::NodeID node_id,
9898
const uavcan::protocol::GetNodeInfo::Response &node_info,
9999
FirmwareFilePath &out_firmware_file_path)
100100
{
@@ -117,13 +117,9 @@ class FirmwareVersionChecker : public uavcan::IFirmwareVersionChecker
117117
if (n > 0 && n < (int)sizeof(bin_file_name) - 2) {
118118
char bin_file_path[MaxBasePathLength + 1];
119119

120-
// Look on Primary location
121-
122120
snprintf(bin_file_path, sizeof(bin_file_path), "%s%s",
123121
getFirmwareBasePath().c_str(), bin_file_name);
124122

125-
// We have a file path, is it a valid image
126-
127123
AppDescriptor descriptor{0};
128124

129125
bool found = getFileInfo(bin_file_path, descriptor) == 0;

src/drivers/uavcan/uavcan_main.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ UavcanNode::init(uavcan::NodeID node_id, UAVCAN_DRIVER::BusEvent &bus_events)
619619
_param_restartnode_client.setCallback(RestartNodeCallback(this, &UavcanNode::cb_restart));
620620

621621
if (uavcan_enable > 1) {
622-
_servers = new UavcanServers(_node, _node_info_retriever);
622+
_servers = new UavcanServers(_node, _node_info_retriever, *this);
623623

624624
if (_servers) {
625625
int rv = _servers->init();
@@ -742,6 +742,12 @@ UavcanNode::Run()
742742
perf_begin(_cycle_perf);
743743
perf_count(_interval_perf);
744744

745+
if (_servers != nullptr) {
746+
if (_servers->checkForNewFirmware()) {
747+
_check_fw = true;
748+
}
749+
}
750+
745751
for (auto &br : _sensor_bridges) {
746752
br->update();
747753
}

src/drivers/uavcan/uavcan_servers.cpp

Lines changed: 206 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@
5656
#include <uavcan_posix/dynamic_node_id_server/file_storage_backend.hpp>
5757
#include <uavcan_posix/firmware_version_checker.hpp>
5858

59+
static constexpr size_t FW_DB_LINE_SIZE = 256; // key(~15) + '=' + filename(NAME_MAX) + '\n'
60+
static const char FW_DB_PATH[] = UAVCAN_FIRMWARE_PATH "/FW.db";
61+
static const char FW_DB_TMP_PATH[] = UAVCAN_FIRMWARE_PATH "/FW.db.tmp";
62+
5963
/**
6064
* @file uavcan_servers.cpp
6165
*
@@ -65,12 +69,14 @@
6569
* David Sidrane <david_s5@nscdg.com>
6670
*/
6771

68-
UavcanServers::UavcanServers(uavcan::INode &node, uavcan::NodeInfoRetriever &node_info_retriever) :
72+
UavcanServers::UavcanServers(uavcan::INode &node, uavcan::NodeInfoRetriever &node_info_retriever,
73+
UavcanNode &uavcan_node) :
6974
_server_instance(node, _storage_backend, _tracer),
7075
_fileserver_backend(node),
7176
_fw_upgrade_trigger(node, _fw_version_checker),
7277
_fw_server(node, _fileserver_backend),
73-
_node_info_retriever(node_info_retriever)
78+
_node_info_retriever(node_info_retriever),
79+
_uavcan_node(uavcan_node)
7480
{
7581
}
7682

@@ -135,6 +141,7 @@ int UavcanServers::init()
135141
Check for firmware in the root directory, move it to appropriate location on
136142
the SD card, as defined by the APDesc.
137143
*/
144+
validateFwDatabase();
138145
migrateFWFromRoot(UAVCAN_FIRMWARE_PATH, UAVCAN_SD_ROOT_PATH);
139146

140147
/* Start the Node */
@@ -159,6 +166,7 @@ void UavcanServers::migrateFWFromRoot(const char *sd_path, const char *sd_root_p
159166
int rv;
160167
char srcpath[maxlen + 1];
161168
char dstpath[maxlen + 1];
169+
uavcan_posix::FirmwareVersionChecker::AppDescriptor descriptor{};
162170

163171
if (stat(sd_path, &sb) != 0 || !S_ISDIR(sb.st_mode)) {
164172
rv = mkdir(sd_path, S_IRWXU | S_IRWXG | S_IRWXO);
@@ -169,11 +177,6 @@ void UavcanServers::migrateFWFromRoot(const char *sd_path, const char *sd_root_p
169177
}
170178
}
171179

172-
// Phase 1: collect .bin filenames with directory open, then close
173-
static constexpr int MaxBinFiles = 10;
174-
char bin_names[MaxBinFiles][NAME_MAX + 1];
175-
int bin_count = 0;
176-
177180
DIR *const sd_root_dir = opendir(sd_root_path);
178181

179182
if (!sd_root_dir) {
@@ -182,7 +185,7 @@ void UavcanServers::migrateFWFromRoot(const char *sd_path, const char *sd_root_p
182185

183186
struct dirent *dev_dirent = NULL;
184187

185-
while ((dev_dirent = readdir(sd_root_dir)) != nullptr && bin_count < MaxBinFiles) {
188+
while ((dev_dirent = readdir(sd_root_dir)) != nullptr) {
186189
if (DIRENT_ISFILE(dev_dirent->d_type) && strstr(dev_dirent->d_name, ".bin") != nullptr) {
187190
size_t filename_len = strlen(dev_dirent->d_name);
188191
size_t srcpath_len = sd_root_path_len + 1 + filename_len;
@@ -192,34 +195,27 @@ void UavcanServers::migrateFWFromRoot(const char *sd_path, const char *sd_root_p
192195
continue;
193196
}
194197

195-
strncpy(bin_names[bin_count], dev_dirent->d_name, NAME_MAX);
196-
bin_names[bin_count][NAME_MAX] = '\0';
197-
bin_count++;
198-
}
199-
}
200-
201-
(void)closedir(sd_root_dir);
202-
203-
// Phase 2: process collected files with directory closed
204-
for (int i = 0; i < bin_count; i++) {
205-
uavcan_posix::FirmwareVersionChecker::AppDescriptor descriptor{0};
198+
snprintf(srcpath, sizeof(srcpath), "%s%s", sd_root_path, dev_dirent->d_name);
206199

207-
snprintf(srcpath, sizeof(srcpath), "%s%s", sd_root_path, bin_names[i]);
208-
209-
if (uavcan_posix::FirmwareVersionChecker::getFileInfo(srcpath, descriptor, 1024) != 0) {
210-
continue;
211-
}
200+
if (uavcan_posix::FirmwareVersionChecker::getFileInfo(srcpath, descriptor, 1024) != 0) {
201+
continue;
202+
}
212203

213-
if (descriptor.image_crc == 0) {
214-
continue;
215-
}
204+
if (descriptor.image_crc == 0) {
205+
continue;
206+
}
216207

217-
snprintf(dstpath, sizeof(dstpath), "%s/%d.bin", sd_path, descriptor.board_id);
208+
snprintf(dstpath, sizeof(dstpath), "%s/%d.bin", sd_path, descriptor.board_id);
218209

219-
if (copyFw(dstpath, srcpath) >= 0) {
220-
unlink(srcpath);
210+
if (copyFw(dstpath, srcpath) >= 0) {
211+
updateFwDatabase(dstpath, dev_dirent->d_name);
212+
unlink(srcpath);
213+
_new_firmware = true;
214+
}
221215
}
222216
}
217+
218+
(void)closedir(sd_root_dir);
223219
}
224220

225221
int UavcanServers::copyFw(const char *dst, const char *src)
@@ -277,3 +273,183 @@ int UavcanServers::copyFw(const char *dst, const char *src)
277273

278274
return rv;
279275
}
276+
277+
void UavcanServers::updateFwDatabase(const char *dst, const char *src_filename)
278+
{
279+
const char *key = dst + strlen(UAVCAN_FIRMWARE_PATH);
280+
281+
if (*key == '/') {
282+
key++;
283+
}
284+
285+
char new_line[FW_DB_LINE_SIZE];
286+
snprintf(new_line, sizeof(new_line), "%s=%s\n", key, src_filename);
287+
const size_t new_line_len = strlen(new_line);
288+
289+
char key_prefix[64];
290+
snprintf(key_prefix, sizeof(key_prefix), "%s=", key);
291+
const size_t key_prefix_len = strlen(key_prefix);
292+
293+
int src_fd = open(FW_DB_PATH, O_RDONLY);
294+
int dst_fd = open(FW_DB_TMP_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0666);
295+
296+
if (dst_fd < 0) {
297+
if (src_fd >= 0) { close(src_fd); }
298+
299+
PX4_WARN("updateFwDatabase: couldn't open tmp file");
300+
return;
301+
}
302+
303+
bool found = false;
304+
char line[FW_DB_LINE_SIZE];
305+
int pos = 0;
306+
char c;
307+
308+
if (src_fd >= 0) {
309+
while (read(src_fd, &c, 1) == 1) {
310+
if (c == '\n' || pos >= (int)sizeof(line) - 2) {
311+
if (c == '\n') { line[pos++] = c; }
312+
313+
line[pos] = '\0';
314+
315+
if (strncmp(line, key_prefix, key_prefix_len) == 0) {
316+
write(dst_fd, new_line, new_line_len);
317+
found = true;
318+
319+
} else {
320+
write(dst_fd, line, pos);
321+
}
322+
323+
pos = 0;
324+
325+
} else {
326+
line[pos++] = c;
327+
}
328+
}
329+
330+
// flush any trailing line without newline
331+
if (pos > 0) {
332+
line[pos] = '\0';
333+
334+
if (strncmp(line, key_prefix, key_prefix_len) == 0) {
335+
write(dst_fd, new_line, new_line_len);
336+
found = true;
337+
338+
} else {
339+
write(dst_fd, line, pos);
340+
}
341+
}
342+
343+
close(src_fd);
344+
}
345+
346+
if (!found) {
347+
write(dst_fd, new_line, new_line_len);
348+
}
349+
350+
close(dst_fd);
351+
rename(FW_DB_TMP_PATH, FW_DB_PATH);
352+
}
353+
354+
void UavcanServers::validateFwDatabase()
355+
{
356+
int src_fd = open(FW_DB_PATH, O_RDONLY);
357+
358+
if (src_fd < 0) {
359+
return; // no DB yet, nothing to validate
360+
}
361+
362+
int dst_fd = open(FW_DB_TMP_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0666);
363+
364+
if (dst_fd < 0) {
365+
close(src_fd);
366+
PX4_WARN("validateFwDatabase: couldn't open tmp file");
367+
return;
368+
}
369+
370+
bool changed = false;
371+
char line[FW_DB_LINE_SIZE];
372+
int pos = 0;
373+
char c;
374+
375+
auto process_line = [&](int len) {
376+
line[len] = '\0';
377+
const char *eq = strchr(line, '=');
378+
379+
if (eq != nullptr) {
380+
char key[64];
381+
const size_t key_len = (size_t)(eq - line);
382+
383+
if (key_len < sizeof(key)) {
384+
memcpy(key, line, key_len);
385+
key[key_len] = '\0';
386+
387+
char full_path[UAVCAN_MAX_PATH_LENGTH + 1];
388+
snprintf(full_path, sizeof(full_path), "%s/%s", UAVCAN_FIRMWARE_PATH, key);
389+
390+
struct stat sb;
391+
392+
if (stat(full_path, &sb) == 0 && S_ISREG(sb.st_mode)) {
393+
write(dst_fd, line, len);
394+
395+
} else {
396+
PX4_INFO("validateFwDatabase: removing stale entry for '%s'", key);
397+
changed = true;
398+
}
399+
}
400+
401+
} else {
402+
changed = true; // malformed — drop it
403+
}
404+
};
405+
406+
while (read(src_fd, &c, 1) == 1) {
407+
if (c == '\n' || pos >= (int)sizeof(line) - 2) {
408+
if (c == '\n') { line[pos++] = c; }
409+
410+
process_line(pos);
411+
pos = 0;
412+
413+
} else {
414+
line[pos++] = c;
415+
}
416+
}
417+
418+
if (pos > 0) {
419+
process_line(pos);
420+
}
421+
422+
close(src_fd);
423+
close(dst_fd);
424+
425+
if (changed) {
426+
rename(FW_DB_TMP_PATH, FW_DB_PATH);
427+
428+
} else {
429+
unlink(FW_DB_TMP_PATH);
430+
}
431+
}
432+
433+
void UavcanServers::onFirmwareCurrent(uavcan::NodeID node_id, uint16_t board_id)
434+
{
435+
const uint8_t nid = node_id.get();
436+
437+
if (nid >= sizeof(_configured_nodes) || _configured_nodes[nid]) {
438+
return;
439+
}
440+
441+
_configured_nodes[nid] = true;
442+
}
443+
444+
bool UavcanServers::checkForNewFirmware()
445+
{
446+
if (hrt_elapsed_time(&_last_fw_migration_check) < 30_s) {
447+
return false;
448+
}
449+
450+
_last_fw_migration_check = hrt_absolute_time();
451+
452+
_new_firmware = false;
453+
migrateFWFromRoot(UAVCAN_FIRMWARE_PATH, UAVCAN_SD_ROOT_PATH);
454+
return _new_firmware;
455+
}

src/drivers/uavcan/uavcan_servers.hpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#pragma once
3535

3636
#include <px4_platform_common/px4_config.h>
37+
#include <drivers/drv_hrt.h>
3738

3839
#include <uavcan/protocol/dynamic_node_id_server/centralized.hpp>
3940
#include <uavcan/protocol/file_server.hpp>
@@ -47,6 +48,8 @@
4748

4849
#include "uavcan_module.hpp"
4950

51+
class UavcanNode;
52+
5053
/**
5154
* @file uavcan_servers.hpp
5255
*
@@ -62,18 +65,27 @@
6265
class UavcanServers
6366
{
6467
public:
65-
UavcanServers(uavcan::INode &node, uavcan::NodeInfoRetriever &node_info_retriever);
68+
UavcanServers(uavcan::INode &node, uavcan::NodeInfoRetriever &node_info_retriever, UavcanNode &uavcan_node);
6669
~UavcanServers() = default;
6770

6871
int init();
72+
bool checkForNewFirmware();
6973

7074
bool guessIfAllDynamicNodesAreAllocated() { return _server_instance.guessIfAllDynamicNodesAreAllocated(); }
7175

7276
private:
7377

78+
void onFirmwareCurrent(uavcan::NodeID node_id, uint16_t board_id);
79+
7480
void unpackFwFromROMFS(const char *sd_path, const char *romfs_path);
7581
void migrateFWFromRoot(const char *sd_path, const char *sd_root_path);
7682
int copyFw(const char *dst, const char *src);
83+
void updateFwDatabase(const char *dst, const char *src_filename);
84+
void validateFwDatabase();
85+
86+
bool _new_firmware{false};
87+
88+
hrt_abstime _last_fw_migration_check{0};
7789

7890
uavcan_posix::dynamic_node_id_server::FileEventTracer _tracer;
7991
uavcan_posix::dynamic_node_id_server::FileStorageBackend _storage_backend;
@@ -84,4 +96,8 @@ class UavcanServers
8496
uavcan::BasicFileServer _fw_server;
8597

8698
uavcan::NodeInfoRetriever &_node_info_retriever;
99+
UavcanNode &_uavcan_node;
100+
101+
// Tracks nodes whose config has already been applied this session
102+
bool _configured_nodes[128] {};
87103
};

0 commit comments

Comments
 (0)