Firmware update
This advanced-level tutorial shows how to implement firmware update over UAVCAN using libuavcan. The reader must be familiar with the corresponding section of the specification. Two applications will be implemented:
- Updater - this application runs an active node monitor (this topic has been covered in one of the previous tutorials),
and when a remote node responds to
uavcan.protocol.GetNodeInfo
request, the application checks if it has a newer firmware image than the node is currently running. If this is the case, the application sends a request of typeuavcan.protocol.file.BeginFirmwareUpdate
to the node. This application also implements a file server, which is another mandatory component of the firmware update process. - Updatee - this application demonstrates how to handle requests of type
uavcan.protocol.file.BeginFirmwareUpdate
, and how to download a file using the serviceuavcan.protocol.file.Read
.
Launch instructions are provided after the source code below.
It is advised to refer to a real production-used cross-platform UAVCAN bootloader implemented by PX4 development team.
Updater
#include <iostream>
#include <cstdlib>
#include <cctype>
#include <vector>
#include <algorithm>
#include <unistd.h>
#include <uavcan/uavcan.hpp>
/*
* Classes that implement high-level protocol logic
*/
#include <uavcan/protocol/firmware_update_trigger.hpp> // uavcan::FirmwareUpdateTrigger
#include <uavcan/protocol/node_info_retriever.hpp> // uavcan::NodeInfoRetriever (see tutorial "Node discovery")
/*
* We're using POSIX-dependent classes and POSIX API in this example.
* This means that the example will only work as-is on a POSIX-compliant system (e.g. Linux, NuttX),
* otherwise the said classes will have to be re-implemented.
*/
#include <uavcan_posix/basic_file_server_backend.hpp>
#include <glob.h> // POSIX glob() function
extern uavcan::ICanDriver& getCanDriver();
extern uavcan::ISystemClock& getSystemClock();
/**
* This class implements the application-specific part of uavcan::FirmwareUpdateTrigger
* via the interface uavcan::IFirmwareVersionChecker.
*
* It works as follows: uavcan::FirmwareUpdateTrigger is subscribed to node information reports from
* uavcan::NodeInfoRetriever via the interface uavcan::INodeInfoListener (learn more on node monitoring
* in the tutorial "Node discovery"). Whenever FirmwareUpdateTrigger receives information about a new node,
* it relays this information to the application via the interface uavcan::IFirmwareVersionChecker.
* The application is then expected to check, using the information provided, whether the node requires
* a firmware update, and reports the results of the check back to FirmwareUpdateTrigger. If the node requires
* an update, FirmwareUpdateTrigger will send it a request uavcan.protocol.file.BeginFirmwareUpdate; otherwise
* the node will be ignored until it restarts or re-appears on the bus. If the node did not respond to the update
* request, FirmwareUpdateTrigger will try again. If the node responded with an error, FirmwareUpdateTrigger
* will ask the application, using the same interface, whether it needs to try again.
*
* Refer to the source of uavcan::FirmwareUpdateTrigger to read more documentation.
*/
class ExampleFirmwareVersionChecker final : public uavcan::IFirmwareVersionChecker
{
/**
* This method will be invoked when the class obtains a response to GetNodeInfo request.
*
* @param node_id Node ID that this GetNodeInfo response was received from.
*
* @param node_info Actual node info structure; refer to uavcan.protocol.GetNodeInfo for details.
*
* @param out_firmware_file_path The implementation should return the firmware image path via this argument.
* Note that this path must be reachable via uavcan.protocol.file.Read service.
* Refer to @ref FileServer and @ref BasicFileServer for details.
*
* @return True - the class will begin sending update requests.
* False - the node will be ignored, no request will be sent.
*/
bool shouldRequestFirmwareUpdate(uavcan::NodeID node_id,
const uavcan::protocol::GetNodeInfo::Response& node_info,
FirmwareFilePath& out_firmware_file_path)
override
{
/*
* We need to make the decision, given the inputs, whether the node requires an update.
* This part of the logic is deeply application-specific, so the solution provided here may not work
* for some real-world applications.
*
* It is recommended to refer to the PX4 autopilot project or to the APM project for a real-world
* example.
*
* Both PX4 and APM leverage a class provided by the libuavcan's POSIX platform driver -
* uavcan_posix::FirmwareVersionChecker - which implements a generic firmware version checking algorithm.
* The algorithm works as follows:
* 1. Check if the file system has a firmware file for the given node.
* If not, exit - update will not be possible anyway.
* 2. Compare the CRC of the local firmware image for the given node with CRC of the firmware the node is
* running at the moment (the latter is available via the node info argument in this method).
* 3. Request an update if CRC don't match, otherwise do not request an update.
*
* In this example, we're using a simpler logic.
*
* Firmware file name pattern used in the example is as follows:
* <node-name>-<hw-major>.<hw-minor>-<sw-major>.<sw-minor>.<vcs-hash-hex>.uavcan.bin
*/
std::cout << "Checking firmware version of node " << int(node_id.get()) << "; node info:\n"
<< node_info << std::endl;
/*
* Looking for matching firmware files.
*/
auto files = findAvailableFirmwareFiles(node_info);
if (files.empty())
{
std::cout << "No firmware files found for this node" << std::endl;
return false;
}
std::cout << "Matching firmware files:";
for (auto x: files)
{
std::cout << "\n\t" << x << "\n" << parseFirmwareFileName(x.c_str()) << std::endl;
}
/*
* Looking for the firmware file with highest version number
*/
std::string best_file_name;
unsigned best_combined_version = 0;
for (auto x: files)
{
const auto inf = parseFirmwareFileName(x.c_str());
const unsigned combined_version = (unsigned(inf.software_version.major) << 8) + inf.software_version.minor;
if (combined_version >= best_combined_version)
{
best_combined_version = combined_version;
best_file_name = x;
}
}
std::cout << "Preferred firmware: " << best_file_name << std::endl;
const auto best_firmware_info = parseFirmwareFileName(best_file_name.c_str());
/*
* Comparing the best firmware with the actual one, requesting an update if they differ.
*/
if (best_firmware_info.software_version.major == node_info.software_version.major &&
best_firmware_info.software_version.minor == node_info.software_version.minor &&
best_firmware_info.software_version.vcs_commit == node_info.software_version.vcs_commit)
{
std::cout << "Firmware is already up-to-date" << std::endl;
return false;
}
/*
* The current implementation of FirmwareUpdateTrigger imposes a limitation on the maximum length of
* the firmware file path: it must not exceed 40 characters. This is NOT a limitation of DroneCAN itself.
*
* The firmware file name format we're currently using may procude names that exceed the length limitation,
* therefore we need to work around that. We do so by means of computing a hash (CRC64 in this example, but
* it obviously could be any other hash), and using it as the name of symlink to the firmware file.
*
* Aside from complying with the library's limitation, use of shorter lengths allows to somewhat reduce bus
* traffic resulting from file read requests from the updatee, as every request carries the name of the file.
*
* TODO: Do not use symlink if file name length does not exceed the limit.
*/
out_firmware_file_path = makeFirmwareFileSymlinkName(best_file_name.c_str(), best_file_name.length());
(void)std::remove(out_firmware_file_path.c_str());
const int symlink_res = ::symlink(best_file_name.c_str(), out_firmware_file_path.c_str());
if (symlink_res < 0)
{
std::cout << "Could not create symlink: " << symlink_res << std::endl;
return false;
}
std::cout << "Firmware file symlink: " << out_firmware_file_path.c_str() << std::endl;
return true;
}
/**
* This method will be invoked when a node responds to the update request with an error. If the request simply
* times out, this method will not be invoked.
* Note that if by the time of arrival of the response the node is already removed, this method will not be called.
*
* SPECIAL CASE: If the node responds with ERROR_IN_PROGRESS, the class will assume that further requesting
* is not needed anymore. This method will not be invoked.
*
* @param node_id Node ID that returned this error.
*
* @param error_response Contents of the error response. It contains error code and text.
*
* @param out_firmware_file_path New firmware path if a retry is needed. Note that this argument will be
* initialized with old path, so if the same path needs to be reused, this
* argument should be left unchanged.
*
* @return True - the class will continue sending update requests with new firmware path.
* False - the node will be forgotten, new requests will not be sent.
*/
bool shouldRetryFirmwareUpdate(uavcan::NodeID node_id,
const uavcan::protocol::file::BeginFirmwareUpdate::Response& error_response,
FirmwareFilePath& out_firmware_file_path)
override
{
/*
* In this implementation we cancel the update request if the node responds with an error.
*/
std::cout << "The node " << int(node_id.get()) << " has rejected the update request; file path was:\n"
<< "\t" << out_firmware_file_path.c_str()
<< "\nresponse was:\n"
<< error_response << std::endl;
return false;
}
/**
* This node is invoked when the node responds to the update request with confirmation.
* Note that if by the time of arrival of the response the node is already removed, this method will not be called.
*
* Implementation is optional; default one does nothing.
*
* @param node_id Node ID that confirmed the request.
*
* @param response Actual response.
*/
void handleFirmwareUpdateConfirmation(uavcan::NodeID node_id,
const uavcan::protocol::file::BeginFirmwareUpdate::Response& response)
override
{
std::cout << "Node " << int(node_id.get()) << " has confirmed the update request; response was:\n"
<< response << std::endl;
}
/*
* This function is specific for this example implementation.
* It computes the name of a symlink to the firmware file.
* Please see explanation above, where the function is called from.
* The implementation is made so that it can work even on a deeply embedded system.
*/
static FirmwareFilePath makeFirmwareFileSymlinkName(const char* file_name, unsigned file_name_length)
{
uavcan::DataTypeSignatureCRC hash;
hash.add(reinterpret_cast<const std::uint8_t*>(file_name), file_name_length);
auto hash_val = hash.get();
static const char Charset[] = "0123456789abcdefghijklmnopqrstuvwxyz";
static const unsigned CharsetSize = sizeof(Charset) - 1;
FirmwareFilePath out;
while (hash_val > 0)
{
out.push_back(Charset[hash_val % CharsetSize]);
hash_val /= CharsetSize;
}
out += ".bin";
return out;
}
/*
* This function is specific for this example implementation.
* It extracts the version information from firmware file name.
* The implementation is made so that it can work even on a deeply embedded system.
* Assumed format is:
* <node-name>-<hw-major>.<hw-minor>-<sw-major>.<sw-minor>.<vcs-hash-hex>.uavcan.bin
*/
static uavcan::protocol::GetNodeInfo::Response parseFirmwareFileName(const char* name)
{
// Must be static in order to avoid heap allocation
static const auto extract_uint8 = [](unsigned& pos, const char* name)
{
std::uint8_t res = 0;
pos++;
while (std::isdigit(name[pos]))
{
res = res * 10 + int(name[pos] - '0');
pos++;
}
return res;
};
uavcan::protocol::GetNodeInfo::Response res;
unsigned pos = 0;
while (name[pos] != '-')
{
res.name.push_back(name[pos]);
pos++;
}
res.hardware_version.major = extract_uint8(pos, name);
res.hardware_version.minor = extract_uint8(pos, name);
res.software_version.major = extract_uint8(pos, name);
res.software_version.minor = extract_uint8(pos, name);
pos++;
res.software_version.vcs_commit = ::strtoul(name + pos, nullptr, 16);
res.software_version.optional_field_flags = res.software_version.OPTIONAL_FIELD_FLAG_VCS_COMMIT;
return res;
}
/*
* This function is specific for this example implementation.
* It returns the firmware files available for given node info struct.
*/
static std::vector<std::string> findAvailableFirmwareFiles(const uavcan::protocol::GetNodeInfo::Response& info)
{
std::vector<std::string> glob_matches;
const std::string glob_pattern = std::string(info.name.c_str()) + "-" +
std::to_string(info.hardware_version.major) + "." +
std::to_string(info.hardware_version.minor) + "-*.uavcan.bin";
auto result = ::glob_t();
const int res = ::glob(glob_pattern.c_str(), 0, nullptr, &result);
if (res != 0)
{
::globfree(&result);
if (res == GLOB_NOMATCH)
{
return glob_matches;
}
throw std::runtime_error("Can't glob()");
}
for(unsigned i = 0; i < result.gl_pathc; ++i)
{
glob_matches.emplace_back(result.gl_pathv[i]);
}
::globfree(&result);
return glob_matches;
}
};
int main(int argc, const char** argv)
{
if (argc < 2)
{
std::cerr << "Usage: " << argv[0] << " <node-id>" << std::endl;
return 1;
}
const int self_node_id = std::stoi(argv[1]);
/*
* Initializing the node.
*
* Typically, a node that serves as a firmware updater will also implement a dynamic node ID allocator.
* Refer to the tutorial "Dynamic node ID allocation" to learn how to add a dynamic node ID allocator
* to your node using libuavcan.
*
* Also keep in mind that in most real-world applications, features dependent on blocking APIs
* (such as this firmware update feature) will have to be implemented in a secondary thread in order to not
* interfere with real-time processing of the primary thread. In the case of this firmware updater, the
* interference may be caused by relatively intensive processing and blocking calls to the file system API.
* Please refer to the dedicated tutorial to learn how to implement a multi-threaded node, then use that
* knowledge to make this example application multithreaded (consider this an excercise).
*/
uavcan::Node<16384> node(getCanDriver(), getSystemClock());
node.setNodeID(self_node_id);
node.setName("org.uavcan.tutorial.updater");
const int node_start_res = node.start();
if (node_start_res < 0)
{
throw std::runtime_error("Failed to start the node; error: " + std::to_string(node_start_res));
}
/*
* Initializing the node info retriever.
*
* We don't need it, but it will be used by the firmware version checker, which will be initialized next.
*/
uavcan::NodeInfoRetriever node_info_retriever(node);
const int retriever_res = node_info_retriever.start();
if (retriever_res < 0)
{
throw std::runtime_error("Failed to start the node info retriever: " + std::to_string(retriever_res));
}
/*
* Initializing the firmware update trigger.
*
* This class monitors the output of uavcan::NodeInfoRetriever, and using this output decides which nodes need
* to update their firmware. When a node that requires an update is detected, the class sends a service request
* uavcan.protocol.file.BeginFirmwareUpdate to it.
*
* The application-specific logic that performs the checks is implemented in the class
* ExampleFirmwareVersionChecker, defined above in this file.
*/
ExampleFirmwareVersionChecker checker;
uavcan::FirmwareUpdateTrigger trigger(node, checker);
const int trigger_res = trigger.start(node_info_retriever);
if (trigger_res < 0)
{
throw std::runtime_error("Failed to start the firmware update trigger: " + std::to_string(trigger_res));
}
/*
* Initializing the file server.
*
* It is not necessary to run the file server on the same node with the firmware update trigger
* (this is explained in the specification), but this use case is the most common, so we'll demonstrate it here.
*/
uavcan_posix::BasicFileServerBackend file_server_backend(node);
uavcan::FileServer file_server(node, file_server_backend);
const int file_server_res = file_server.start();
if (file_server_res < 0)
{
throw std::runtime_error("Failed to start the file server: " + std::to_string(file_server_res));
}
std::cout << "Started successfully" << std::endl;
/*
* Running the node normally.
* All of the work will be done in background.
*/
node.setModeOperational();
while (true)
{
const int res = node.spin(uavcan::MonotonicDuration::getInfinite());
if (res < 0)
{
std::cerr << "Transient failure: " << res << std::endl;
}
}
}
Updatee
#include <iostream>
#include <cstdlib>
#include <vector>
#include <iomanip>
#include <unistd.h>
#include <uavcan/uavcan.hpp>
/*
* Data types used.
*/
#include <uavcan/protocol/file/BeginFirmwareUpdate.hpp>
#include <uavcan/protocol/file/Read.hpp>
extern uavcan::ICanDriver& getCanDriver();
extern uavcan::ISystemClock& getSystemClock();
/**
* This class downloads a firmware image from specified location to memory.
* Download will start immediately after the object is constructed,
* and it can be cancelled by means of deleting the object.
*
* This is just a made-up example - real applications will likely behave differently, either:
* - Downloading the image using a dedicated bootloader application.
* - Downloading the image to a file, that will be deployed later.
* - Possibly something else, depending on the requirements of the application.
*/
class FirmwareLoader : private uavcan::TimerBase
{
public:
/**
* State transitions:
* InProgress ---[after background work]----> Success
* \ --> Failure
*/
enum class Status
{
InProgress,
Success,
Failure
};
private:
const uavcan::NodeID source_node_id_;
const uavcan::protocol::file::Path::FieldTypes::path source_path_;
std::vector<std::uint8_t> image_;
typedef uavcan::MethodBinder<FirmwareLoader*,
void (FirmwareLoader::*)(const uavcan::ServiceCallResult<uavcan::protocol::file::Read>&)>
ReadResponseCallback;
uavcan::ServiceClient<uavcan::protocol::file::Read, ReadResponseCallback> read_client_;
Status status_ = Status::InProgress;
void handleTimerEvent(const uavcan::TimerEvent&) final override
{
if (!read_client_.hasPendingCalls())
{
uavcan::protocol::file::Read::Request req;
req.path.path = source_path_;
req.offset = image_.size();
const int res = read_client_.call(source_node_id_, req);
if (res < 0)
{
std::cerr << "Read call failed: " << res << std::endl;
}
}
}
void handleReadResponse(const uavcan::ServiceCallResult<uavcan::protocol::file::Read>& result)
{
if (result.isSuccessful() &&
result.getResponse().error.value == 0)
{
auto& data = result.getResponse().data;
image_.insert(image_.end(), data.begin(), data.end());
if (data.size() < data.capacity()) // Termination condition
{
status_ = Status::Success;
uavcan::TimerBase::stop();
}
}
else
{
status_ = Status::Failure;
uavcan::TimerBase::stop();
}
}
public:
/**
* Download will start immediately upon construction.
* Destroy the object to cancel the process.
*/
FirmwareLoader(uavcan::INode& node,
uavcan::NodeID source_node_id,
const uavcan::protocol::file::Path::FieldTypes::path& source_path) :
uavcan::TimerBase(node),
source_node_id_(source_node_id),
source_path_(source_path),
read_client_(node)
{
image_.reserve(1024); // Arbitrary value
/*
* According to the specification, response priority equals request priority.
* Typically, file I/O should be executed at a very low priority level.
*/
read_client_.setPriority(uavcan::TransferPriority::OneHigherThanLowest);
read_client_.setCallback(ReadResponseCallback(this, &FirmwareLoader::handleReadResponse));
/*
* Rate-limiting is necessary to avoid bus congestion.
* The exact rate depends on the application's requirements and CAN bit rate.
*/
uavcan::TimerBase::startPeriodic(uavcan::MonotonicDuration::fromMSec(200));
}
/**
* This method allows to detect when the downloading is done, and whether it was successful.
*/
Status getStatus() const { return status_; }
/**
* Returns the downloaded image.
*/
const std::vector<std::uint8_t>& getImage() const { return image_; }
};
/**
* This function is used to display the downloaded image.
*/
template <typename InputIterator>
void printHexDump(InputIterator begin, const InputIterator end)
{
struct RAIIFlagsSaver
{
const std::ios::fmtflags flags_ = std::cout.flags();
~RAIIFlagsSaver() { std::cout.flags(flags_); }
} _flags_saver;
static constexpr unsigned BytesPerRow = 16;
unsigned offset = 0;
std::cout << std::hex << std::setfill('0');
do
{
std::cout << std::setw(8) << offset << " ";
offset += BytesPerRow;
{
auto it = begin;
for (unsigned i = 0; i < BytesPerRow; ++i)
{
if (i == 8)
{
std::cout << ' ';
}
if (it != end)
{
std::cout << std::setw(2) << unsigned(*it) << ' ';
++it;
}
else
{
std::cout << " ";
}
}
}
std::cout << " ";
for (unsigned i = 0; i < BytesPerRow; ++i)
{
if (begin != end)
{
std::cout << ((unsigned(*begin) >= 32U && unsigned(*begin) <= 126U) ? char(*begin) : '.');
++begin;
}
else
{
std::cout << ' ';
}
}
std::cout << std::endl;
}
while (begin != end);
}
int main(int argc, const char** argv)
{
if (argc < 2)
{
std::cerr << "Usage: " << argv[0] << " <node-id>" << std::endl;
return 1;
}
const int self_node_id = std::stoi(argv[1]);
/*
* Initializing the node.
* Hardware and software version information is paramount for firmware update process.
*/
uavcan::Node<16384> node(getCanDriver(), getSystemClock());
node.setNodeID(self_node_id);
node.setName("org.uavcan.tutorial.updatee");
uavcan::protocol::HardwareVersion hwver; // TODO initialize correct values
hwver.major = 1;
node.setHardwareVersion(hwver);
uavcan::protocol::SoftwareVersion swver; // TODO initialize correct values
swver.major = 1;
node.setSoftwareVersion(swver);
const int node_start_res = node.start();
if (node_start_res < 0)
{
throw std::runtime_error("Failed to start the node; error: " + std::to_string(node_start_res));
}
/*
* Storage for the firmware downloader object.
* Can be replaced with a smart pointer instead.
*/
uavcan::LazyConstructor<FirmwareLoader> fw_loader;
/*
* Initializing the BeginFirmwareUpdate server.
*/
uavcan::ServiceServer<uavcan::protocol::file::BeginFirmwareUpdate> bfu_server(node);
const int bfu_res = bfu_server.start(
[&fw_loader, &node]
(const uavcan::ReceivedDataStructure<uavcan::protocol::file::BeginFirmwareUpdate::Request>& req,
uavcan::protocol::file::BeginFirmwareUpdate::Response resp)
{
std::cout << "Firmware update request:\n" << req << std::endl;
if (fw_loader.isConstructed())
{
resp.error = resp.ERROR_IN_PROGRESS;
}
else
{
const auto source_node_id = (req.source_node_id == 0) ? req.getSrcNodeID() : req.source_node_id;
fw_loader.construct<uavcan::INode&, uavcan::NodeID, decltype(req.image_file_remote_path.path)>
(node, source_node_id, req.image_file_remote_path.path);
}
std::cout << "Response:\n" << resp << std::endl;
});
if (bfu_res < 0)
{
throw std::runtime_error("Failed to start the BeginFirmwareUpdate server: " + std::to_string(bfu_res));
}
/*
* Running the node normally.
* All of the work will be done in background.
*/
while (true)
{
if (fw_loader.isConstructed())
{
node.setModeSoftwareUpdate();
if (fw_loader->getStatus() != FirmwareLoader::Status::InProgress)
{
if (fw_loader->getStatus() == FirmwareLoader::Status::Success)
{
auto image = fw_loader->getImage();
std::cout << "Firmware download succeeded [" << image.size() << " bytes]" << std::endl;
printHexDump(std::begin(image), std::end(image));
// TODO: save the firmware image somewhere.
}
else
{
std::cout << "Firmware download failed" << std::endl;
// TODO: handle the error, e.g. retry download, send a log message, etc.
}
fw_loader.destroy();
}
}
else
{
node.setModeOperational();
}
const int res = node.spin(uavcan::MonotonicDuration::fromMSec(500));
if (res < 0)
{
std::cerr << "Transient failure: " << res << std::endl;
}
}
}
Running on Linux
Build the applications using the following CMake script:
cmake_minimum_required(VERSION 2.8)
project(tutorial_project)
find_library(UAVCAN_LIB uavcan REQUIRED)
set(CMAKE_CXX_FLAGS "-Wall -Wextra -pedantic -std=c++11")
# Make sure to provide correct path to 'platform_linux.cpp'! See earlier tutorials for more info.
add_executable(updater updater.cpp ${CMAKE_SOURCE_DIR}/../2._Node_initialization_and_startup/platform_linux.cpp)
target_link_libraries(updater ${UAVCAN_LIB} rt)
add_executable(updatee updatee.cpp ${CMAKE_SOURCE_DIR}/../2._Node_initialization_and_startup/platform_linux.cpp)
target_link_libraries(updatee ${UAVCAN_LIB} rt)
If the applications are started as-is, nothing interesting would happen,
because updater needs firmware files in order to issue update requests;
otherwise it will be ignoring nodes with a comment like No firmware files found for this node
.
In order to make something happen, create a file named org.uavcan.tutorial.updatee-1.0-5.0.0.uavcan.bin
(naming format is explained in the source code), fill it with some data and then start both nodes.
In this case, updater will request updatee to update its firmware, because the provided firmware file is declared to have higher firmware version number than updatee reports.
While firmware is being loaded, updatee will set its operating mode to SOFTWARE_UPDATE
,
as can be seen using UAVCAN monitor application for Linux
(refer to the Linux platform driver documentation to learn more about available applications):
Possible output of updater:
$ ./updater 1
Started successfully
Checking firmware version of node 2; node info:
status:
uptime_sec: 0
health: 0
mode: 0
sub_mode: 0
vendor_specific_status_code: 0
software_version:
major: 1
minor: 0
optional_field_flags: 0
vcs_commit: 0
image_crc: 0
hardware_version:
major: 1
minor: 0
unique_id: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
certificate_of_authenticity: ""
name: "org.uavcan.tutorial.updatee"
Matching firmware files:
org.uavcan.tutorial.updatee-1.0-5.0.0.uavcan.bin
status:
uptime_sec: 0
health: 0
mode: 0
sub_mode: 0
vendor_specific_status_code: 0
software_version:
major: 5
minor: 0
optional_field_flags: 1
vcs_commit: 0
image_crc: 0
hardware_version:
major: 1
minor: 0
unique_id: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
certificate_of_authenticity: ""
name: "org.uavcan.tutorial.updatee"
Preferred firmware: org.uavcan.tutorial.updatee-1.0-5.0.0.uavcan.bin
Firmware file symlink: 93osmbx05mzk.bin
Node 2 has confirmed the update request; response was:
error: 0
optional_error_message: ""
Possible output of updatee:
$ ./updatee 2
Firmware update request:
# Received struct ts_m=9534.001067 ts_utc=1443463579.015996 snid=1
source_node_id: 1
image_file_remote_path:
path: "93osmbx05mzk.bin"
Response:
error: 0
optional_error_message: ""
Firmware download succeeded [881 bytes]
00000000 54 68 65 20 74 72 61 67 65 64 79 20 6f 66 20 68 The tragedy of h
00000010 75 6d 61 6e 20 6c 69 66 65 2c 20 69 74 20 69 73 uman life, it is
00000020 20 6f 66 74 65 6e 20 74 68 6f 75 67 68 74 2c 20 often thought,
00000030 69 73 20 74 68 61 74 20 6f 75 72 20 6d 6f 72 74 is that our mort
00000040 61 6c 69 74 79 20 6d 65 61 6e 73 20 74 68 61 74 ality means that
00000050 20 64 65 61 74 68 20 69 73 20 74 68 65 20 6f 6e death is the on
00000060 6c 79 20 74 68 69 6e 67 20 74 68 61 74 20 77 65 ly thing that we
00000070 20 6b 6e 6f 77 0a 66 6f 72 20 73 75 72 65 20 61 know.for sure a
00000080 77 61 69 74 73 20 75 73 2e 20 54 68 65 20 73 74 waits us. The st
00000090 6f 72 79 20 6f 66 20 56 69 74 61 6c 69 61 20 74 ory of Vitalia t
000000a0 75 72 6e 73 20 74 68 69 73 20 63 6f 6e 76 65 6e urns this conven
000000b0 74 69 6f 6e 61 6c 20 77 69 73 64 6f 6d 20 6f 6e tional wisdom on
000000c0 20 69 74 73 20 68 65 61 64 20 61 6e 64 20 73 75 its head and su
000000d0 67 67 65 73 74 73 20 74 68 61 74 20 69 6d 6d 6f ggests that immo
000000e0 72 74 61 6c 69 74 79 0a 77 6f 75 6c 64 20 62 65 rtality.would be
000000f0 20 61 20 63 75 72 73 65 2e 20 57 65 20 6e 65 65 a curse. We nee
00000100 64 20 64 65 61 74 68 20 74 6f 20 67 69 76 65 20 d death to give
00000110 73 68 61 70 65 20 61 6e 64 20 6d 65 61 6e 69 6e shape and meanin
00000120 67 20 74 6f 20 6c 69 66 65 2e 20 57 69 74 68 6f g to life. Witho
00000130 75 74 20 69 74 2c 20 77 65 20 77 6f 75 6c 64 20 ut it, we would
00000140 66 69 6e 64 20 6c 69 66 65 20 70 6f 69 6e 74 6c find life pointl
00000150 65 73 73 2e 20 4f 6e 20 74 68 69 73 0a 76 69 65 ess. On this.vie
00000160 77 2c 20 69 66 20 68 65 6c 6c 20 69 73 20 65 74 w, if hell is et
00000170 65 72 6e 61 6c 20 64 61 6d 6e 61 74 69 6f 6e 2c ernal damnation,
00000180 20 74 68 65 20 65 74 65 72 6e 69 74 79 20 6f 66 the eternity of
00000190 20 6c 69 66 65 20 69 6e 20 48 61 64 65 73 20 77 life in Hades w
000001a0 6f 75 6c 64 20 62 65 20 65 6e 6f 75 67 68 20 74 ould be enough t
000001b0 6f 20 6d 61 6b 65 20 69 74 20 61 20 70 6c 61 63 o make it a plac
000001c0 65 20 6f 66 20 70 75 6e 69 73 68 6d 65 6e 74 2e e of punishment.
000001d0 0a 0a 49 74 20 69 73 20 73 75 72 70 72 69 73 69 ..It is surprisi
000001e0 6e 67 20 68 6f 77 20 66 65 77 20 70 65 6f 70 6c ng how few peopl
000001f0 65 20 77 68 6f 20 74 68 69 6e 6b 20 65 74 65 72 e who think eter
00000200 6e 61 6c 20 6c 69 66 65 20 77 6f 75 6c 64 20 62 nal life would b
00000210 65 20 64 65 73 69 72 61 62 6c 65 20 74 68 69 6e e desirable thin
00000220 6b 20 68 61 72 64 20 61 62 6f 75 74 20 77 68 61 k hard about wha
00000230 74 20 69 74 20 77 6f 75 6c 64 20 65 6e 74 61 69 t it would entai
00000240 6c 2e 20 54 68 61 74 0a 69 73 20 75 6e 64 65 72 l. That.is under
00000250 73 74 61 6e 64 61 62 6c 65 2e 20 57 68 61 74 20 standable. What
00000260 77 65 20 70 72 69 6d 61 72 69 6c 79 20 77 61 6e we primarily wan
00000270 74 20 69 73 20 73 69 6d 70 6c 79 20 6d 6f 72 65 t is simply more
00000280 20 6c 69 66 65 2e 20 54 68 65 20 65 78 61 63 74 life. The exact
00000290 20 64 75 72 61 74 69 6f 6e 20 6f 66 20 74 68 65 duration of the
000002a0 20 65 78 74 72 61 20 6c 65 61 73 65 20 69 73 20 extra lease is
000002b0 6e 6f 74 20 6f 75 72 20 70 72 69 6d 65 0a 63 6f not our prime.co
000002c0 6e 63 65 72 6e 2e 20 49 74 20 64 6f 65 73 20 73 ncern. It does s
000002d0 65 65 6d 20 74 68 61 74 20 73 65 76 65 6e 74 79 eem that seventy
000002e0 20 79 65 61 72 73 2c 20 69 66 20 77 65 e2 80 99 years, if we...
000002f0 72 65 20 6c 75 63 6b 79 2c 20 69 73 6e e2 80 99 re lucky, isn...
00000300 74 20 6c 6f 6e 67 20 65 6e 6f 75 67 68 2e 20 54 t long enough. T
00000310 68 65 72 65 20 61 72 65 20 73 6f 20 6d 61 6e 79 here are so many
00000320 20 70 6c 61 63 65 73 20 74 6f 20 73 65 65 2c 20 places to see,
00000330 73 6f 20 6d 75 63 68 0a 74 6f 20 64 6f 20 61 6e so much.to do an
00000340 64 20 65 78 70 65 72 69 65 6e 63 65 2e 20 49 66 d experience. If
00000350 20 6f 6e 6c 79 20 77 65 20 68 61 64 20 6d 6f 72 only we had mor
00000360 65 20 74 69 6d 65 20 74 6f 20 64 6f 20 69 74 21 e time to do it!
00000370 0a .