/**************************************************************************** * * 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 * @author David Sidrane * @author Andreas Jochum * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "uavcan_module.hpp" #include "uavcan_main.hpp" #include #include //todo:The Inclusion of file_server_backend is killing // #include 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()); } 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(resp.value.to())); } 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()); } 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().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 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 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 &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 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(); int64_t max = resp.max_value.to(); if (i >= min && i <= max) { req.value.to() = 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(std::atof(value)); float min = resp.min_value.to(); float max = resp.max_value.to(); if (f >= min && f <= max) { req.value.to() = f; } else { std::printf("Invalid value for: %s must be between %.4f and %.4f\n", name, static_cast(min), static_cast(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() = i; } else if (resp.value.is(uavcan::protocol::param::Value::Tag::string_value)) { req.value.to() = 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::strtoul(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() = request.real_value; } else if (request.param_type == uavcan_parameter_request_s::PARAM_TYPE_UINT8) { req.value.to() = request.int_value; } else { req.value.to() = 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(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(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(float 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(float 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 &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(); } 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(); } 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(); } _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 &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 &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] |reset "); } 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 * uavcan param save * uavcan param get * uavcan param set * */ 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); }