Files
PX4-Autopilot/src/drivers/uavcan/uavcan_main.cpp
T
ljarvelaandljarvela 061fe4806e uavcan: add warning about CAN error counter reading issue (#26492)
Add warning message in print_info() to alert users that CAN error counter
values may increase during the function call due to internal counter reading
implementation. Users should not fully trust these counters until the
underlying issue is fixed.

Co-authored-by: ljarvela <lasse.jarvela@iceye.com>
2026-02-16 11:25:30 -09:00

1591 lines
42 KiB
C++

/****************************************************************************
*
* Copyright (c) 2014-2025 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file uavcan_main.cpp
*
* Implements basic functionality of UAVCAN node.
*
* @author Pavel Kirienko <pavel.kirienko@gmail.com>
* @author David Sidrane <david_s5@nscdg.com>
* @author Andreas Jochum <Andreas@NicaDrone.com>
*
*/
#include <px4_platform_common/px4_config.h>
#include <px4_platform_common/tasks.h>
#include <inttypes.h>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <systemlib/err.h>
#include <parameters/param.h>
#include <version/version.h>
#include <arch/chip/chip.h>
#include <uORB/topics/esc_status.h>
#include <drivers/drv_hrt.h>
#include "uavcan_module.hpp"
#include "uavcan_main.hpp"
#include <uavcan/util/templates.hpp>
#include <uavcan/protocol/param/ExecuteOpcode.hpp>
//todo:The Inclusion of file_server_backend is killing
// #include <sys/types.h> and leaving OK undefined
# define OK 0
/*
* UavcanNode
*/
UavcanNode *UavcanNode::_instance;
static UavcanNode::CanInitHelper *can = nullptr;
UavcanNode::UavcanNode(uavcan::ICanDriver &can_driver, uavcan::ISystemClock &system_clock) :
ScheduledWorkItem(MODULE_NAME, px4::wq_configurations::uavcan),
ModuleParams(nullptr),
_node(can_driver, system_clock, _pool_allocator),
#if defined(CONFIG_UAVCAN_ARMING_CONTROLLER)
_arming_status_controller(_node),
#endif
#if defined(CONFIG_UAVCAN_BEEP_CONTROLLER)
_beep_controller(_node),
#endif
#if defined(CONFIG_UAVCAN_OUTPUTS_CONTROLLER)
_esc_controller(_node),
_servo_controller(_node),
#endif
#if defined(CONFIG_UAVCAN_HARDPOINT_CONTROLLER)
_hardpoint_controller(_node),
#endif
#if defined(CONFIG_UAVCAN_SAFETY_STATE_CONTROLLER)
_safety_state_controller(_node),
#endif
#if defined(CONFIG_UAVCAN_REMOTEID_CONTROLLER)
_remoteid_controller(_node),
#endif
#if defined(CONFIG_UAVCAN_RGB_CONTROLLER)
_rgbled_controller(_node),
#endif
_log_message_controller(_node),
_time_sync_master(_node),
_time_sync_slave(_node),
_node_status_monitor(_node),
_node_info_retriever(_node),
_node_info_publisher(_node, _node_info_retriever),
_master_timer(_node),
_param_getset_client(_node),
_param_opcode_client(_node),
_param_restartnode_client(_node)
{
int res = pthread_mutex_init(&_node_mutex, nullptr);
if (res < 0) {
std::abort();
}
#if defined(CONFIG_UAVCAN_OUTPUTS_CONTROLLER)
_mixing_interface_esc.mixingOutput().setMaxTopicUpdateRate(1000000 / UavcanEscController::MAX_RATE_HZ);
_mixing_interface_servo.mixingOutput().setMaxTopicUpdateRate(1000000 / UavcanServoController::MAX_RATE_HZ);
#endif
}
UavcanNode::~UavcanNode()
{
if (_servers != nullptr) {
delete _servers;
_servers = nullptr;
}
if (_instance) {
/* tell the task we want it to go away */
_task_should_exit.store(true);
ScheduleNow();
unsigned i = 10;
do {
/* wait 5ms - it should wake every 10ms or so worst-case */
usleep(5000);
if (--i == 0) {
break;
}
} while (_instance);
}
// Removing the sensor bridges
_sensor_bridges.clear();
pthread_mutex_destroy(&_node_mutex);
perf_free(_cycle_perf);
perf_free(_interval_perf);
}
int
UavcanNode::getHardwareVersion(uavcan::protocol::HardwareVersion &hwver)
{
int rv = -1;
if (UavcanNode::instance()) {
if (!std::strncmp(px4_board_name(), "PX4_FMU_V2", 9)) {
hwver.major = 2;
} else {
; // All other values of px4_board_name() resolve to zero
}
mfguid_t mfgid = {};
board_get_mfguid(mfgid);
uavcan::copy(mfgid, mfgid + sizeof(mfgid), hwver.unique_id.begin());
rv = 0;
}
return rv;
}
int
UavcanNode::print_params(uavcan::protocol::param::GetSet::Response &resp)
{
if (resp.value.is(uavcan::protocol::param::Value::Tag::integer_value)) {
return std::printf("name: %s %" PRId64 "\n", resp.name.c_str(),
resp.value.to<uavcan::protocol::param::Value::Tag::integer_value>());
} else if (resp.value.is(uavcan::protocol::param::Value::Tag::real_value)) {
return std::printf("name: %s %.4f\n", resp.name.c_str(),
static_cast<double>(resp.value.to<uavcan::protocol::param::Value::Tag::real_value>()));
} else if (resp.value.is(uavcan::protocol::param::Value::Tag::boolean_value)) {
return std::printf("name: %s %d\n", resp.name.c_str(),
resp.value.to<uavcan::protocol::param::Value::Tag::boolean_value>());
} else if (resp.value.is(uavcan::protocol::param::Value::Tag::string_value)) {
return std::printf("name: %s '%s'\n", resp.name.c_str(),
resp.value.to<uavcan::protocol::param::Value::Tag::string_value>().c_str());
}
return -1;
}
int
UavcanNode::save_params(int remote_node_id)
{
uavcan::protocol::param::ExecuteOpcode::Request opcode_req;
opcode_req.opcode = opcode_req.OPCODE_SAVE;
uavcan::ServiceClient<uavcan::protocol::param::ExecuteOpcode, ExecuteOpcodeCallback> client(_node);
client.setCallback(ExecuteOpcodeCallback(this, &UavcanNode::cb_opcode));
_callback_success = false;
int call_res = client.call(remote_node_id, opcode_req);
if (call_res >= 0) {
while (client.hasPendingCalls()) {
usleep(10000);
}
}
if (!_callback_success) {
std::printf("Failed to save parameters: %d\n", call_res);
return -1;
}
return 0;
}
int
UavcanNode::reset_node(int remote_node_id)
{
uavcan::protocol::RestartNode::Request restart_req;
restart_req.magic_number = restart_req.MAGIC_NUMBER;
uavcan::ServiceClient<uavcan::protocol::RestartNode, RestartNodeCallback> client(_node);
client.setCallback(RestartNodeCallback(this, &UavcanNode::cb_restart));
_callback_success = false;
int call_res = client.call(remote_node_id, restart_req);
if (call_res >= 0) {
while (client.hasPendingCalls()) {
usleep(10000);
}
}
if (!call_res) {
std::printf("Failed to reset node: %d\n", remote_node_id);
return -1;
}
return 0;
}
int
UavcanNode::list_params(int remote_node_id)
{
int rv = 0;
int index = 0;
uavcan::protocol::param::GetSet::Response resp;
set_setget_response(&resp);
while (true) {
uavcan::protocol::param::GetSet::Request req;
req.index = index++;
_callback_success = false;
int call_res = get_set_param(remote_node_id, nullptr, req);
if (call_res < 0 || !_callback_success) {
std::printf("Failed to get param: %d\n", call_res);
rv = -1;
break;
}
if (resp.name.empty()) { // Empty name means no such param, which means we're finished
break;
}
print_params(resp);
}
free_setget_response();
return rv;
}
void
UavcanNode::cb_setget(const uavcan::ServiceCallResult<uavcan::protocol::param::GetSet> &result)
{
_callback_success = result.isSuccessful();
*_setget_response = result.getResponse();
}
int
UavcanNode::get_set_param(int remote_node_id, const char *name, uavcan::protocol::param::GetSet::Request &req)
{
if (name != nullptr) {
req.name = name;
}
uavcan::ServiceClient<uavcan::protocol::param::GetSet, GetSetCallback> client(_node);
client.setCallback(GetSetCallback(this, &UavcanNode::cb_setget));
_callback_success = false;
int call_res = client.call(remote_node_id, req);
if (call_res >= 0) {
while (client.hasPendingCalls()) {
usleep(10000);
}
if (!_callback_success) {
call_res = -1;
}
}
return call_res;
}
int
UavcanNode::set_param(int remote_node_id, const char *name, char *value)
{
uavcan::protocol::param::GetSet::Request req;
uavcan::protocol::param::GetSet::Response resp;
set_setget_response(&resp);
int rv = get_set_param(remote_node_id, name, req);
if (rv < 0 || resp.name.empty()) {
std::printf("Failed to retrieve param: %s\n", name);
rv = -1;
} else {
rv = 0;
req = {};
if (resp.value.is(uavcan::protocol::param::Value::Tag::integer_value)) {
int64_t i = std::strtoull(value, NULL, 10);
int64_t min = resp.min_value.to<uavcan::protocol::param::NumericValue::Tag::integer_value>();
int64_t max = resp.max_value.to<uavcan::protocol::param::NumericValue::Tag::integer_value>();
if (i >= min && i <= max) {
req.value.to<uavcan::protocol::param::Value::Tag::integer_value>() = i;
} else {
std::printf("Invalid value for: %s must be between %" PRId64 " and %" PRId64 "\n", name, min, max);
rv = -1;
}
} else if (resp.value.is(uavcan::protocol::param::Value::Tag::real_value)) {
float f = static_cast<float>(std::atof(value));
float min = resp.min_value.to<uavcan::protocol::param::NumericValue::Tag::real_value>();
float max = resp.max_value.to<uavcan::protocol::param::NumericValue::Tag::real_value>();
if (f >= min && f <= max) {
req.value.to<uavcan::protocol::param::Value::Tag::real_value>() = f;
} else {
std::printf("Invalid value for: %s must be between %.4f and %.4f\n", name, static_cast<double>(min),
static_cast<double>(max));
rv = -1;
}
} else if (resp.value.is(uavcan::protocol::param::Value::Tag::boolean_value)) {
int8_t i = (value[0] == '1' || value[0] == 't') ? 1 : 0;
req.value.to<uavcan::protocol::param::Value::Tag::boolean_value>() = i;
} else if (resp.value.is(uavcan::protocol::param::Value::Tag::string_value)) {
req.value.to<uavcan::protocol::param::Value::Tag::string_value>() = value;
}
if (rv == 0) {
rv = get_set_param(remote_node_id, name, req);
if (rv == 0) {
// commit parameter change
save_params(remote_node_id);
} else if (rv < 0 || resp.name.empty()) {
std::printf("Failed to set param: %s\n", name);
return -1;
}
return 0;
}
}
free_setget_response();
return rv;
}
int
UavcanNode::get_param(int remote_node_id, const char *name)
{
uavcan::protocol::param::GetSet::Request req;
uavcan::protocol::param::GetSet::Response resp;
set_setget_response(&resp);
int rv = get_set_param(remote_node_id, name, req);
if (rv < 0 || resp.name.empty()) {
std::printf("Failed to get param: %s\n", name);
rv = -1;
} else {
print_params(resp);
rv = 0;
}
free_setget_response();
return rv;
}
void
UavcanNode::update_params()
{
#if defined(CONFIG_UAVCAN_OUTPUTS_CONTROLLER)
_mixing_interface_esc.updateParams();
_mixing_interface_servo.updateParams();
#endif
}
int
UavcanNode::start(uavcan::NodeID node_id, uint32_t bitrate)
{
if (_instance != nullptr) {
PX4_WARN("Already started");
return -1;
}
if (can == nullptr) {
can = new CanInitHelper(board_get_can_interfaces());
if (can == nullptr) { // We don't have exceptions so bad_alloc cannot be thrown
PX4_ERR("Out of memory");
return -1;
}
}
/*
* Node init
*/
_instance = new UavcanNode(can->driver, UAVCAN_DRIVER::SystemClock::instance());
if (_instance == nullptr) {
PX4_ERR("Out of memory");
return -1;
}
_instance->ScheduleOnInterval(ScheduleIntervalMs * 1000);
#if defined(CONFIG_UAVCAN_OUTPUTS_CONTROLLER)
_instance->_mixing_interface_esc.ScheduleNow();
_instance->_mixing_interface_servo.ScheduleNow();
#endif
return OK;
}
void
UavcanNode::fill_node_info()
{
/* software version */
uavcan::protocol::SoftwareVersion swver;
// Extracting the first 8 hex digits of the git hash and converting them to int
char fw_git_short[9] = {};
std::memmove(fw_git_short, px4_firmware_version_string(), 8);
char *end = nullptr;
swver.vcs_commit = std::strtol(fw_git_short, &end, 16);
swver.optional_field_flags |= swver.OPTIONAL_FIELD_FLAG_VCS_COMMIT;
// Too verbose for normal operation
//PX4_INFO("SW version vcs_commit: 0x%08x", unsigned(swver.vcs_commit));
_node.setSoftwareVersion(swver);
/* hardware version */
uavcan::protocol::HardwareVersion hwver;
getHardwareVersion(hwver);
_node.setHardwareVersion(hwver);
}
void
UavcanNode::busevent_signal_trampoline()
{
if (_instance) {
// trigger the work queue (Note, this is called from IRQ context)
_instance->ScheduleNow();
}
}
int
UavcanNode::init(uavcan::NodeID node_id, UAVCAN_DRIVER::BusEvent &bus_events)
{
bus_events.registerSignalCallback(UavcanNode::busevent_signal_trampoline);
_node.setName("org.pixhawk.pixhawk");
_node.setNodeID(node_id);
fill_node_info();
int ret;
// UAVCAN_PUB_ARM
#if defined(CONFIG_UAVCAN_ARMING_CONTROLLER)
int32_t uavcan_pub_arm = 0;
param_get(param_find("UAVCAN_PUB_ARM"), &uavcan_pub_arm);
if (uavcan_pub_arm == 1) {
ret = _arming_status_controller.init();
if (ret < 0) {
return ret;
}
}
#endif
#if defined(CONFIG_UAVCAN_BEEP_CONTROLLER)
ret = _beep_controller.init();
if (ret < 0) {
return ret;
}
#endif
// Actuators
#if defined(CONFIG_UAVCAN_OUTPUTS_CONTROLLER)
int32_t uavcan_enable = -1;
(void)param_get(param_find("UAVCAN_ENABLE"), &uavcan_enable);
if (uavcan_enable > 2) {
ret = _esc_controller.init();
if (ret < 0) {
return ret;
}
}
#endif
#if defined(CONFIG_UAVCAN_HARDPOINT_CONTROLLER)
ret = _hardpoint_controller.init();
if (ret < 0) {
return ret;
}
#endif
#if defined(CONFIG_UAVCAN_SAFETY_STATE_CONTROLLER)
ret = _safety_state_controller.init();
if (ret < 0) {
return ret;
}
#endif
ret = _log_message_controller.init();
if (ret < 0) {
return ret;
}
#if defined(CONFIG_UAVCAN_REMOTEID_CONTROLLER)
ret = _remoteid_controller.init();
if (ret < 0) {
return ret;
}
#endif
#if defined(CONFIG_UAVCAN_RGB_CONTROLLER)
ret = _rgbled_controller.init();
if (ret < 0) {
return ret;
}
#endif
/* Start node info retriever to fetch node info from new nodes */
ret = _node_info_retriever.start();
if (ret < 0) {
PX4_ERR("NodeInfoRetriever init: %d", ret);
return ret;
}
// Sensor bridges
IUavcanSensorBridge::make_all(_node, _sensor_bridges, &_node_info_publisher);
for (const auto &br : _sensor_bridges) {
ret = br->init();
if (ret < 0) {
PX4_ERR("cannot init sensor bridge '%s' (%d)", br->get_name(), ret);
return ret;
}
PX4_DEBUG("sensor bridge '%s' init ok", br->get_name());
}
#if defined(CONFIG_UAVCAN_OUTPUTS_CONTROLLER)
_esc_controller.set_node_info_publisher(&_node_info_publisher);
#endif
/* Set up shared service clients */
_param_getset_client.setCallback(GetSetCallback(this, &UavcanNode::cb_getset));
_param_opcode_client.setCallback(ExecuteOpcodeCallback(this, &UavcanNode::cb_opcode));
_param_restartnode_client.setCallback(RestartNodeCallback(this, &UavcanNode::cb_restart));
if (uavcan_enable > 1) {
_servers = new UavcanServers(_node, _node_info_retriever);
if (_servers) {
int rv = _servers->init();
if (rv < 0) {
PX4_ERR("UavcanServers init: %d", rv);
}
}
}
// Start the Node
return _node.start();
}
void
UavcanNode::handle_time_sync(const uavcan::TimerEvent &)
{
/*
* Check whether there are higher priority masters in the network.
* If there are, we need to activate the local slave in order to sync with them.
*/
if (_time_sync_slave.isActive()) { // "Active" means that the slave tracks at least one remote master in the network
if (_node.getNodeID() < _time_sync_slave.getMasterNodeID()) {
/*
* We're the highest priority master in the network.
* We need to suppress the slave now to prevent it from picking up unwanted sync messages from
* lower priority masters.
*/
_time_sync_slave.suppress(true); // SUPPRESS
} else {
/*
* There is at least one higher priority master in the network.
* We need to allow the slave to adjust our local clock in order to be in sync.
*/
_time_sync_slave.suppress(false); // UNSUPPRESS
}
} else {
/*
* There are no other time sync masters in the network, so we're the only time source.
* The slave must be suppressed anyway to prevent it from disrupting the local clock if a new
* lower priority master suddenly appears in the network.
*/
_time_sync_slave.suppress(true);
}
/*
* Publish the sync message now, even if we're not a higher priority master.
* Other nodes will be able to pick the right master anyway.
*/
_time_sync_master.publish();
}
void
UavcanNode::Run()
{
if (!_node_init) {
// Node ID
int32_t node_id = 1;
(void)param_get(param_find("UAVCAN_NODE_ID"), &node_id);
if (node_id < 0 || node_id > uavcan::NodeID::Max || !uavcan::NodeID(node_id).isUnicast()) {
PX4_ERR("Invalid Node ID %" PRId32, node_id);
::exit(1);
}
// CAN bitrate
int32_t bitrate = 1000000;
(void)param_get(param_find("UAVCAN_BITRATE"), &bitrate);
/*
* CAN driver init
* Note that we instantiate and initialize CanInitHelper only once, because the STM32's bxCAN driver
* shipped with libuavcan does not support deinitialization.
*/
const int can_init_res = can->init(bitrate);
if (can_init_res < 0) {
PX4_ERR("CAN driver init failed %i", can_init_res);
} else {
_instance->init(node_id, can->driver.updateEvent());
_node_init = true;
}
}
pthread_mutex_lock(&_node_mutex);
if (_output_count == 0) {
// Set up the time synchronization
const int slave_init_res = _time_sync_slave.start();
if (slave_init_res < 0) {
PX4_ERR("Failed to start time_sync_slave");
ScheduleClear();
return;
}
/* When we have a system wide notion of time update (i.e the transition from the initial
* System RTC setting to the GPS) we would call UAVCAN_DRIVER::clock::setUtc() when that
* happens, but for now we use adjustUtc with a correction of the hrt so that the
* time bases are the same
*/
UAVCAN_DRIVER::clock::adjustUtc(uavcan::UtcDuration::fromUSec(hrt_absolute_time()));
_master_timer.setCallback(TimerCallback(this, &UavcanNode::handle_time_sync));
_master_timer.startPeriodic(uavcan::MonotonicDuration::fromMSec(1000));
_node_status_monitor.start();
_node.setModeOperational();
update_params();
// XXX figure out the output count
_output_count = 2;
}
perf_begin(_cycle_perf);
perf_count(_interval_perf);
for (auto &br : _sensor_bridges) {
br->update();
}
if (_check_fw) {
_check_fw = false;
_node_info_retriever.invalidateAll();
}
_node.spinOnce(); // expected to be non-blocking
publish_can_interface_statuses();
publish_node_statuses();
// check for parameter updates
if (_parameter_update_sub.updated()) {
// clear update
parameter_update_s pupdate;
_parameter_update_sub.copy(&pupdate);
// update parameters from storage
update_params();
}
// Check for parameter requests (get/set/list)
if (_param_request_sub.updated() && !_param_list_in_progress && !_param_in_progress && !_count_in_progress) {
uavcan_parameter_request_s request{};
_param_request_sub.copy(&request);
if (_param_counts[request.node_id]) {
/*
* We know how many parameters are exposed by this node, so
* process the request.
*/
if (request.message_type == uavcan_parameter_request_s::MESSAGE_TYPE_PARAM_REQUEST_READ) {
uavcan::protocol::param::GetSet::Request req;
if (request.param_index >= 0) {
req.index = request.param_index;
} else {
req.name = (char *)request.param_id;
}
int call_res = _param_getset_client.call(request.node_id, req);
if (call_res < 0) {
PX4_ERR("couldn't send GetSet: %d", call_res);
} else {
_param_in_progress = true;
_param_index = request.param_index;
}
} else if (request.message_type == uavcan_parameter_request_s::MESSAGE_TYPE_PARAM_SET) {
uavcan::protocol::param::GetSet::Request req;
if (request.param_index >= 0) {
req.index = request.param_index;
} else {
req.name = (char *)request.param_id;
}
if (request.param_type == uavcan_parameter_request_s::PARAM_TYPE_REAL32) {
req.value.to<uavcan::protocol::param::Value::Tag::real_value>() = request.real_value;
} else if (request.param_type == uavcan_parameter_request_s::PARAM_TYPE_UINT8) {
req.value.to<uavcan::protocol::param::Value::Tag::boolean_value>() = request.int_value;
} else {
req.value.to<uavcan::protocol::param::Value::Tag::integer_value>() = request.int_value;
}
// Set the dirty bit for this node
set_node_params_dirty(request.node_id);
int call_res = _param_getset_client.call(request.node_id, req);
if (call_res < 0) {
PX4_ERR("couldn't send GetSet: %d", call_res);
} else {
_param_in_progress = true;
_param_index = request.param_index;
}
} else if (request.message_type == uavcan_parameter_request_s::MESSAGE_TYPE_PARAM_REQUEST_LIST) {
// This triggers the _param_list_in_progress case below.
_param_index = 0;
_param_list_in_progress = true;
_param_list_node_id = request.node_id;
_param_list_all_nodes = false;
PX4_DEBUG("starting component-specific param list");
}
} else if (request.node_id == uavcan_parameter_request_s::NODE_ID_ALL) {
if (request.message_type == uavcan_parameter_request_s::MESSAGE_TYPE_PARAM_REQUEST_LIST) {
/*
* This triggers the _param_list_in_progress case below,
* but additionally iterates over all active nodes.
*/
_param_index = 0;
_param_list_in_progress = true;
_param_list_node_id = get_next_active_node_id(0);
_param_list_all_nodes = true;
PX4_DEBUG("starting global param list with node %hhu", _param_list_node_id);
if (_param_counts[_param_list_node_id] == 0) {
param_count(_param_list_node_id);
}
}
} else {
/*
* Need to know how many parameters this node has before we can
* continue; count them now and then process the request.
*/
param_count(request.node_id);
}
}
// Handle parameter listing index/node ID advancement
if (_param_list_in_progress && !_param_in_progress && !_count_in_progress) {
if (_param_index >= _param_counts[_param_list_node_id]) {
PX4_DEBUG("completed param list for node %hhu", _param_list_node_id);
// Reached the end of the current node's parameter set.
_param_list_in_progress = false;
if (_param_list_all_nodes) {
// We're listing all parameters for all nodes -- get the next node ID
uint8_t next_id = get_next_active_node_id(_param_list_node_id);
if (next_id < 128) {
_param_list_node_id = next_id;
/*
* If there is a next node ID, check if that node's parameters
* have been counted before. If not, do it now.
*/
if (_param_counts[_param_list_node_id] == 0) {
param_count(_param_list_node_id);
}
// Keep on listing.
_param_index = 0;
_param_list_in_progress = true;
PX4_DEBUG("started param list for node %hhu", _param_list_node_id);
}
}
}
}
// Check if we're still listing, and need to get the next parameter
if (_param_list_in_progress && !_param_in_progress && !_count_in_progress) {
// Ready to request the next value -- _param_index is incremented
// after each successful fetch by cb_getset
uavcan::protocol::param::GetSet::Request req;
req.index = _param_index;
int call_res = _param_getset_client.call(_param_list_node_id, req);
if (call_res < 0) {
_param_list_in_progress = false;
PX4_ERR("couldn't send param list GetSet: %d", call_res);
} else {
_param_in_progress = true;
}
}
if (_vcmd_sub.updated() && !_cmd_in_progress) {
bool acknowledge = false;
vehicle_command_s cmd{};
_vcmd_sub.copy(&cmd);
uint8_t cmd_ack_result = vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED;
if (cmd.command == vehicle_command_s::VEHICLE_CMD_PREFLIGHT_STORAGE) {
acknowledge = true;
int command_id = static_cast<int>(cmd.param1 + 0.5f);
PX4_DEBUG("received storage command ID %d", command_id);
switch (command_id) {
case 1: {
// Param save request
int node_id;
node_id = get_next_dirty_node_id(1);
if (node_id < 128) {
_param_save_opcode = uavcan::protocol::param::ExecuteOpcode::Request::OPCODE_SAVE;
param_opcode(node_id);
}
break;
}
case 2: {
// Command is a param erase request -- apply it to all active nodes by setting the dirty bit
_param_save_opcode = uavcan::protocol::param::ExecuteOpcode::Request::OPCODE_ERASE;
for (int i = 1; i < 128; i = get_next_active_node_id(i)) {
set_node_params_dirty(i);
}
param_opcode(get_next_dirty_node_id(1));
break;
}
}
}
if (acknowledge) {
// Acknowledge the received command
vehicle_command_ack_s ack{};
ack.command = cmd.command;
ack.result = cmd_ack_result;
ack.target_system = cmd.source_system;
ack.target_component = cmd.source_component;
ack.timestamp = hrt_absolute_time();
_command_ack_pub.publish(ack);
}
}
#if defined(CONFIG_UAVCAN_OUTPUTS_CONTROLLER)
_arming_status_controller.setActuatorTestRunning(_mixing_interface_esc.isActuatorTestRunning());
#endif
perf_end(_cycle_perf);
pthread_mutex_unlock(&_node_mutex);
if (_task_should_exit.load()) {
#if defined(CONFIG_UAVCAN_OUTPUTS_CONTROLLER)
_mixing_interface_esc.mixingOutput().unregister();
_mixing_interface_esc.ScheduleClear();
_mixing_interface_servo.mixingOutput().unregister();
_mixing_interface_servo.ScheduleClear();
#endif
ScheduleClear();
_instance = nullptr;
}
}
void UavcanNode::publish_can_interface_statuses()
{
constexpr hrt_abstime status_pub_interval = 100_ms;
const hrt_abstime now = hrt_absolute_time();
if (now - _last_can_status_pub >= status_pub_interval) {
_last_can_status_pub = now;
for (int i = 0; i < _node.getDispatcher().getCanIOManager().getCanDriver().getNumIfaces(); i++) {
if (i > UAVCAN_NUM_IFACES) {
break;
}
auto iface = _node.getDispatcher().getCanIOManager().getCanDriver().getIface(i);
if (!iface) {
continue;
}
auto iface_perf_cnt = _node.getDispatcher().getCanIOManager().getIfacePerfCounters(i);
can_interface_status_s status{
.timestamp = now,
.io_errors = iface_perf_cnt.errors,
.frames_tx = iface_perf_cnt.frames_tx,
.frames_rx = iface_perf_cnt.frames_rx,
.interface = static_cast<uint8_t>(i),
};
if (_can_status_pub_handles[i] == nullptr) {
int instance{0};
_can_status_pub_handles[i] = orb_advertise_multi(ORB_ID(can_interface_status), nullptr, &instance);
}
(void)orb_publish(ORB_ID(can_interface_status), _can_status_pub_handles[i], &status);
}
}
}
void UavcanNode::publish_node_statuses()
{
constexpr hrt_abstime status_pub_interval = 100_ms;
const hrt_abstime now = hrt_absolute_time();
if (now - _last_node_status_pub >= status_pub_interval) {
_last_node_status_pub = now;
_node_status_monitor.forEachNode([this](uavcan::NodeID node_id, uavcan::NodeStatusMonitor::NodeStatus node_status) {
auto node_id_val = node_id.get();
if (node_id_val == 0) {
return;
}
// See if we have NodeID <--> uORB_index mapped
int uorb_index = -1;
for (uint8_t i = 0; i < ORB_MULTI_MAX_INSTANCES; i++) {
if (_node_status_uorb_index_map[i] == node_id_val) {
uorb_index = i;
break;
}
}
if (uorb_index < 0) {
// use next available index
for (uint8_t i = 0; i < ORB_MULTI_MAX_INSTANCES; i++) {
if (_node_status_uorb_index_map[i] == 0) {
_node_status_uorb_index_map[i] = node_id_val;
uorb_index = i;
// advertise
PX4_INFO("advertising node_id %u on index %u", node_id_val, i);
int instance{0};
_node_status_pub_handles[i] = orb_advertise_multi(ORB_ID(dronecan_node_status), nullptr, &instance);
break;
}
}
}
if (uorb_index >= 0) {
dronecan_node_status_s status{
.timestamp = hrt_absolute_time(),
.uptime_sec = node_status.uptime_sec,
.node_id = node_id_val,
.vendor_specific_status_code = node_status.vendor_specific_status_code,
.health = node_status.health,
.mode = node_status.mode,
.sub_mode = node_status.sub_mode,
};
(void)orb_publish(ORB_ID(dronecan_node_status), _node_status_pub_handles[uorb_index], &status);
}
});
}
}
#if defined(CONFIG_UAVCAN_OUTPUTS_CONTROLLER)
bool UavcanMixingInterfaceESC::updateOutputs(uint16_t outputs[MAX_ACTUATORS], unsigned num_outputs,
unsigned num_control_groups_updated)
{
if (_esc_controller.initialized()) {
// num_outputs is the maximum possible number of outputs (8)
// output_array_size adapts to the highest output index that is mapped (4 for a quad)
// this allows for sending less CAN frames depending on what output indices are mapped
uint8_t output_array_size = 0;
for (int i = MAX_ACTUATORS - 1; i >= 0; i--) {
if (mixingOutput().isFunctionSet(i)) {
output_array_size = i + 1;
break;
}
}
_esc_controller.update_outputs(outputs, output_array_size);
}
return true;
}
void UavcanMixingInterfaceESC::Run()
{
pthread_mutex_lock(&_node_mutex);
_mixing_output.update();
_mixing_output.updateSubscriptions(false);
pthread_mutex_unlock(&_node_mutex);
}
void UavcanMixingInterfaceESC::mixerChanged()
{
int rotor_count = 0;
for (unsigned i = 0; i < MAX_ACTUATORS; ++i) {
rotor_count += _mixing_output.isFunctionSet(i);
if (i < esc_status_s::CONNECTED_ESC_MAX) {
_esc_controller.esc_status().esc[i].actuator_function = (uint8_t)_mixing_output.outputFunction(i);
}
}
_esc_controller.set_rotor_count(rotor_count);
}
bool UavcanMixingInterfaceServo::updateOutputs(uint16_t outputs[MAX_ACTUATORS], unsigned num_outputs,
unsigned num_control_groups_updated)
{
_servo_controller.update_outputs(outputs, num_outputs);
return true;
}
void UavcanMixingInterfaceServo::Run()
{
pthread_mutex_lock(&_node_mutex);
_mixing_output.update();
_mixing_output.updateSubscriptions(false);
pthread_mutex_unlock(&_node_mutex);
}
#endif
void
UavcanNode::print_info()
{
// Memory status
printf("Pool allocator status:\n");
printf("\tCapacity hard/soft: %" PRIu16 "/%" PRIu16 " blocks\n",
_pool_allocator.getBlockCapacityHardLimit(), _pool_allocator.getBlockCapacity());
printf("\tReserved: %" PRIu16 " blocks\n", _pool_allocator.getNumReservedBlocks());
printf("\tAllocated: %" PRIu16 " blocks\n", _pool_allocator.getNumAllocatedBlocks());
printf("\n");
// See https://github.com/PX4/PX4-Autopilot/issues/22871
printf("WARNING: CAN error counter values below may increase during this function call due to internal counter reading implementation.\n");
printf("Do not fully trust these counters until this issue is fixed.\n");
printf("\n");
// UAVCAN node perfcounters
printf("UAVCAN node status:\n");
printf("\tInternal failures: %" PRIu64 "\n", _node.getInternalFailureCount());
printf("\tTransfer errors: %" PRIu64 "\n", _node.getDispatcher().getTransferPerfCounter().getErrorCount());
printf("\tRX transfers: %" PRIu64 "\n", _node.getDispatcher().getTransferPerfCounter().getRxTransferCount());
printf("\tTX transfers: %" PRIu64 "\n", _node.getDispatcher().getTransferPerfCounter().getTxTransferCount());
printf("\n");
// CAN driver status
for (unsigned i = 0; i < _node.getDispatcher().getCanIOManager().getCanDriver().getNumIfaces(); i++) {
printf("CAN%u status:\n", unsigned(i + 1));
auto iface = _node.getDispatcher().getCanIOManager().getCanDriver().getIface(i);
if (iface) {
printf("\tHW errors: %" PRIu64 "\n", iface->getErrorCount());
auto iface_perf_cnt = _node.getDispatcher().getCanIOManager().getIfacePerfCounters(i);
printf("\tIO errors: %" PRIu64 "\n", iface_perf_cnt.errors);
printf("\tRX frames: %" PRIu64 "\n", iface_perf_cnt.frames_rx);
printf("\tTX frames: %" PRIu64 "\n", iface_perf_cnt.frames_tx);
}
}
printf("\n");
#if defined(CONFIG_UAVCAN_OUTPUTS_CONTROLLER)
// Print esc status if at least one channel is enabled
for (int i = 0; i < OutputModuleInterface::MAX_ACTUATORS; i++) {
if (_mixing_interface_esc.mixingOutput().isFunctionSet(i)) {
printf("ESC outputs:\n");
_mixing_interface_esc.mixingOutput().printStatus();
printf("\n");
break;
}
}
// Print servo status if at least one channel is enabled
for (int i = 0; i < OutputModuleInterface::MAX_ACTUATORS; i++) {
if (_mixing_interface_servo.mixingOutput().isFunctionSet(i)) {
printf("Servo outputs:\n");
_mixing_interface_servo.mixingOutput().printStatus();
printf("\n");
break;
}
}
#endif
// Sensor bridges
for (const auto &br : _sensor_bridges) {
printf("Sensor '%s':\n", br->get_name());
br->print_status();
printf("\n");
}
// Printing all nodes that are online
printf("Online nodes (Node ID, Health, Mode):\n");
_node_status_monitor.forEachNode([](uavcan::NodeID nid, uavcan::NodeStatusMonitor::NodeStatus ns) {
static constexpr const char *HEALTH[] = {"OK", "WARN", "ERR", "CRIT"};
static constexpr const char *MODES[] = {"OPERAT", "INIT", "MAINT", "SW_UPD", "?", "?", "?", "OFFLN"};
printf("\t% 3d %-10s %-10s\n", int(nid.get()), HEALTH[ns.health], MODES[ns.mode]);
});
printf("\n");
perf_print_counter(_cycle_perf);
perf_print_counter(_interval_perf);
}
void
UavcanNode::shrink()
{
(void)pthread_mutex_lock(&_node_mutex);
_pool_allocator.shrink();
(void)pthread_mutex_unlock(&_node_mutex);
}
void
UavcanNode::cb_getset(const uavcan::ServiceCallResult<uavcan::protocol::param::GetSet> &result)
{
if (_count_in_progress) {
/*
* Currently in parameter count mode:
* Iterate over all parameters for the node to which the request was
* originally sent, in order to find the maximum parameter ID. If a
* request fails, set the node's parameter count to zero.
*/
uint8_t node_id = result.getCallID().server_node_id.get();
if (result.isSuccessful()) {
uavcan::protocol::param::GetSet::Response resp = result.getResponse();
if (resp.name.size()) {
_count_index++;
_param_counts[node_id] = _count_index;
uavcan::protocol::param::GetSet::Request req;
req.index = _count_index;
int call_res = _param_getset_client.call(result.getCallID().server_node_id, req);
if (call_res < 0) {
_count_in_progress = false;
_count_index = 0;
PX4_ERR("couldn't send GetSet during param count: %d", call_res);
}
} else {
_count_in_progress = false;
_count_index = 0;
PX4_DEBUG("completed param count for node %hhu: %hhu", node_id, _param_counts[node_id]);
}
} else {
_param_counts[node_id] = 0;
_count_in_progress = false;
_count_index = 0;
PX4_ERR("GetSet error during param count");
}
} else {
/*
* Currently in parameter get/set mode:
* Publish a uORB uavcan_parameter_value message containing the current value
* of the parameter.
*/
if (result.isSuccessful()) {
uavcan::protocol::param::GetSet::Response param = result.getResponse();
uavcan_parameter_value_s response{};
response.node_id = result.getCallID().server_node_id.get();
strncpy(response.param_id, param.name.c_str(), sizeof(response.param_id) - 1);
response.param_id[16] = '\0';
response.param_index = _param_index;
response.param_count = _param_counts[response.node_id];
if (param.value.is(uavcan::protocol::param::Value::Tag::integer_value)) {
response.param_type = uavcan_parameter_request_s::PARAM_TYPE_INT64;
response.int_value = param.value.to<uavcan::protocol::param::Value::Tag::integer_value>();
} else if (param.value.is(uavcan::protocol::param::Value::Tag::real_value)) {
response.param_type = uavcan_parameter_request_s::PARAM_TYPE_REAL32;
response.real_value = param.value.to<uavcan::protocol::param::Value::Tag::real_value>();
} else if (param.value.is(uavcan::protocol::param::Value::Tag::boolean_value)) {
response.param_type = uavcan_parameter_request_s::PARAM_TYPE_UINT8;
response.int_value = param.value.to<uavcan::protocol::param::Value::Tag::boolean_value>();
}
_param_response_pub.publish(response);
} else {
PX4_ERR("GetSet error");
}
_param_in_progress = false;
_param_index++;
}
}
void
UavcanNode::param_count(uavcan::NodeID node_id)
{
uavcan::protocol::param::GetSet::Request req;
req.index = 0;
int call_res = _param_getset_client.call(node_id, req);
// -ErrInvalidParam is returned when no UAVCAN device is connected to the CAN bus
if ((call_res < 0) && (-uavcan::ErrInvalidParam != call_res)) {
PX4_ERR("couldn't start parameter count: %d", call_res);
} else {
_count_in_progress = true;
_count_index = 0;
PX4_DEBUG("starting param count");
}
}
void
UavcanNode::param_opcode(uavcan::NodeID node_id)
{
uavcan::protocol::param::ExecuteOpcode::Request opcode_req;
opcode_req.opcode = _param_save_opcode;
int call_res = _param_opcode_client.call(node_id, opcode_req);
if (call_res < 0) {
PX4_ERR("couldn't send ExecuteOpcode: %d", call_res);
} else {
_cmd_in_progress = true;
PX4_INFO("sent ExecuteOpcode");
}
}
void
UavcanNode::cb_opcode(const uavcan::ServiceCallResult<uavcan::protocol::param::ExecuteOpcode> &result)
{
bool success = result.isSuccessful();
uint8_t node_id = result.getCallID().server_node_id.get();
uavcan::protocol::param::ExecuteOpcode::Response resp = result.getResponse();
success &= resp.ok;
_cmd_in_progress = false;
if (!result.isSuccessful()) {
PX4_ERR("save request for node %hhu timed out.", node_id);
} else if (!result.getResponse().ok) {
PX4_ERR("save request for node %hhu rejected.", node_id);
} else {
PX4_INFO("save request for node %hhu completed OK, restarting.", node_id);
uavcan::protocol::RestartNode::Request restart_req;
restart_req.magic_number = restart_req.MAGIC_NUMBER;
int call_res = _param_restartnode_client.call(node_id, restart_req);
if (call_res < 0) {
PX4_ERR("couldn't send RestartNode: %d", call_res);
} else {
PX4_ERR("sent RestartNode");
_cmd_in_progress = true;
}
}
if (!_cmd_in_progress) {
/*
* Something went wrong, so cb_restart is never going to be called as a result of this request.
* To ensure we try to execute the opcode on all nodes that permit it, get the next dirty node
* ID and keep processing here. The dirty bit on the current node is still set, so the
* save/erase attempt will occur when the next save/erase command is received over MAVLink.
*/
node_id = get_next_dirty_node_id(node_id);
if (node_id < 128) {
param_opcode(node_id);
}
}
}
void
UavcanNode::cb_restart(const uavcan::ServiceCallResult<uavcan::protocol::RestartNode> &result)
{
bool success = result.isSuccessful();
uint8_t node_id = result.getCallID().server_node_id.get();
uavcan::protocol::RestartNode::Response resp = result.getResponse();
success &= resp.ok;
_cmd_in_progress = false;
if (success) {
PX4_DEBUG("restart request for node %hhu completed OK.", node_id);
// Clear the dirty flag
clear_node_params_dirty(node_id);
} else {
PX4_ERR("restart request for node %hhu failed.", node_id);
}
// Get the next dirty node ID and send the same command to it
node_id = get_next_dirty_node_id(node_id);
if (node_id < 128) {
param_opcode(node_id);
}
}
uint8_t
UavcanNode::get_next_active_node_id(uint8_t base)
{
base++;
for (; base < 128 && (!_node_info_retriever.isNodeKnown(base) || _node.getNodeID().get() == base); base++);
return base;
}
uint8_t
UavcanNode::get_next_dirty_node_id(uint8_t base)
{
base++;
for (; base < 128 && !are_node_params_dirty(base); base++);
return base;
}
/*
* App entry point
*/
static void print_usage()
{
PX4_INFO("usage: \n"
"\tuavcan {start|status|stop|shrink|update}\n"
"\t param [set|get|list|save] <node-id> <name> <value>|reset <node-id>");
}
extern "C" __EXPORT int uavcan_main(int argc, char *argv[])
{
if (argc < 2) {
print_usage();
::exit(1);
}
if (!std::strcmp(argv[1], "start")) {
if (UavcanNode::instance()) {
// Already running, no error
PX4_INFO("already started");
::exit(0);
}
// Node ID
int32_t node_id = 1;
(void)param_get(param_find("UAVCAN_NODE_ID"), &node_id);
if (node_id < 0 || node_id > uavcan::NodeID::Max || !uavcan::NodeID(node_id).isUnicast()) {
PX4_ERR("Invalid Node ID %" PRId32, node_id);
::exit(1);
}
// CAN bitrate
int32_t bitrate = 1000000;
(void)param_get(param_find("UAVCAN_BITRATE"), &bitrate);
// Start
PX4_INFO("Node ID %" PRIu32 ", bitrate %" PRIu32, node_id, bitrate);
return UavcanNode::start(node_id, bitrate);
}
/* commands below require the app to be started */
UavcanNode *const inst = UavcanNode::instance();
if (!inst) {
errx(1, "application not running");
}
if (!std::strcmp(argv[1], "update")) {
if (UavcanNode::instance() == nullptr) {
errx(1, "firmware server is not running");
}
UavcanNode::instance()->requestCheckAllNodesFirmwareAndUpdate();
::exit(0);
}
if (!std::strcmp(argv[1], "status") || !std::strcmp(argv[1], "info")) {
inst->print_info();
::exit(0);
}
if (!std::strcmp(argv[1], "shrink")) {
inst->shrink();
::exit(0);
}
/*
* Parameter setting commands
*
* uavcan param list <node>
* uavcan param save <node>
* uavcan param get <node> <name>
* uavcan param set <node> <name> <value>
*
*/
int node_arg = !std::strcmp(argv[1], "reset") ? 2 : 3;
if (!std::strcmp(argv[1], "param") || node_arg == 2) {
if (argc < node_arg + 1) {
errx(1, "Node id required");
}
int nodeid = atoi(argv[node_arg]);
if (nodeid == 0 || nodeid > 127 || nodeid == inst->get_node().getNodeID().get()) {
errx(1, "Invalid Node id");
}
if (node_arg == 2) {
return inst->reset_node(nodeid);
} else if (!std::strcmp(argv[2], "list")) {
return inst->list_params(nodeid);
} else if (!std::strcmp(argv[2], "save")) {
return inst->save_params(nodeid);
} else if (!std::strcmp(argv[2], "get")) {
if (argc < 5) {
errx(1, "Name required");
}
return inst->get_param(nodeid, argv[4]);
} else if (!std::strcmp(argv[2], "set")) {
if (argc < 5) {
errx(1, "Name required");
}
if (argc < 6) {
errx(1, "Value required");
}
return inst->set_param(nodeid, argv[4], argv[5]);
}
}
if (!std::strcmp(argv[1], "stop")) {
delete inst;
::exit(0);
}
print_usage();
::exit(1);
}