From 65f3c7f93ddb9d854cb20933bb27ec0af71f2769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beat=20K=C3=BCng?= Date: Mon, 2 Sep 2019 19:22:19 +0200 Subject: [PATCH] dshot: add telemetry and publish esc_status message --- msg/esc_status.msg | 3 +- src/drivers/dshot/CMakeLists.txt | 3 + src/drivers/dshot/dshot.cpp | 135 +++++++++- src/drivers/dshot/module.yaml | 54 ++++ src/drivers/dshot/telemetry.cpp | 265 ++++++++++++++++++++ src/drivers/dshot/{params.c => telemetry.h} | 96 ++++--- src/lib/mixer_module/mixer_module.cpp | 20 ++ src/lib/mixer_module/mixer_module.hpp | 12 +- 8 files changed, 552 insertions(+), 36 deletions(-) create mode 100644 src/drivers/dshot/module.yaml create mode 100644 src/drivers/dshot/telemetry.cpp rename src/drivers/dshot/{params.c => telemetry.h} (54%) diff --git a/msg/esc_status.msg b/msg/esc_status.msg index 38472800f9..a12b890fb8 100644 --- a/msg/esc_status.msg +++ b/msg/esc_status.msg @@ -3,9 +3,10 @@ uint8 CONNECTED_ESC_MAX = 8 # The number of ESCs supported. Current (Q2/2013) uint8 ESC_CONNECTION_TYPE_PPM = 0 # Traditional PPM ESC uint8 ESC_CONNECTION_TYPE_SERIAL = 1 # Serial Bus connected ESC -uint8 ESC_CONNECTION_TYPE_ONESHOOT = 2 # One Shoot PPM +uint8 ESC_CONNECTION_TYPE_ONESHOT = 2 # One Shot PPM uint8 ESC_CONNECTION_TYPE_I2C = 3 # I2C uint8 ESC_CONNECTION_TYPE_CAN = 4 # CAN-Bus +uint8 ESC_CONNECTION_TYPE_DSHOT = 5 # DShot uint16 counter # incremented by the writing thread everytime new data is stored diff --git a/src/drivers/dshot/CMakeLists.txt b/src/drivers/dshot/CMakeLists.txt index 6f98950d16..493419c293 100644 --- a/src/drivers/dshot/CMakeLists.txt +++ b/src/drivers/dshot/CMakeLists.txt @@ -36,10 +36,13 @@ px4_add_module( STACK_MAIN 1200 SRCS dshot.cpp + telemetry.cpp DEPENDS arch_io_pins arch_dshot mixer mixer_module output_limit + MODULE_CONFIG + module.yaml ) diff --git a/src/drivers/dshot/dshot.cpp b/src/drivers/dshot/dshot.cpp index 6144682e52..2004ed044d 100644 --- a/src/drivers/dshot/dshot.cpp +++ b/src/drivers/dshot/dshot.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,9 @@ #include #include #include +#include + +#include "telemetry.h" using namespace time_literals; @@ -148,6 +152,8 @@ public: void updateOutputs(bool stop_motors, uint16_t outputs[MAX_ACTUATORS], unsigned num_outputs, unsigned num_control_groups_updated) override; + void mixerChanged() override; + private: enum class DShotConfig { Disabled = 0, @@ -157,8 +163,22 @@ private: DShot1200 = 1200, }; + struct Telemetry { + DShotTelemetry handler; + uORB::PublicationData esc_status_pub{ORB_ID(esc_status)}; + int last_motor_index{-1}; + }; + + void updateTelemetryNumMotors(); + void initTelemetry(const char *device); + void handleNewTelemetryData(int motor_index, const DShotTelemetry::EscData &data); + MixingOutput _mixing_output{*this, MixingOutput::SchedulingPolicy::Auto, false, false}; + Telemetry *_telemetry{nullptr}; + static char _telemetry_device[20]; + static px4::atomic_bool _request_telemetry_init; + Mode _mode{MODE_NONE}; uORB::Subscription _param_sub{ORB_ID(parameter_update)}; @@ -188,10 +208,14 @@ private: DEFINE_PARAMETERS( (ParamInt) _param_dshot_config, - (ParamFloat) _param_dshot_min + (ParamFloat) _param_dshot_min, + (ParamInt) _param_mot_pole_count ) }; +char DShotOutput::_telemetry_device[] {}; +px4::atomic_bool DShotOutput::_request_telemetry_init{false}; + DShotOutput::DShotOutput() : CDev("/dev/dshot"), OutputModuleInterface(MODULE_NAME, px4::wq_configurations::hp_default), @@ -214,6 +238,7 @@ DShotOutput::~DShotOutput() perf_free(_cycle_perf); perf_free(_cycle_interval_perf); + delete _telemetry; } int @@ -484,6 +509,79 @@ DShotOutput::update_dshot_out_state(bool on) up_dshot_arm(on); } +void DShotOutput::updateTelemetryNumMotors() +{ + if (!_telemetry) { + return; + } + + int motor_count = 0; + + if (_mixing_output.mixers()) { + motor_count = _mixing_output.mixers()->get_multirotor_count(); + } + + _telemetry->handler.setNumMotors(motor_count); +} + +void DShotOutput::initTelemetry(const char *device) +{ + if (!_telemetry) { + _telemetry = new Telemetry{}; + + if (!_telemetry) { + PX4_ERR("alloc failed"); + return; + } + } + + int ret = _telemetry->handler.init(device); + + if (ret != 0) { + PX4_ERR("telemetry init failed (%i)", ret); + } + + updateTelemetryNumMotors(); +} + +void DShotOutput::handleNewTelemetryData(int motor_index, const DShotTelemetry::EscData &data) +{ + // fill in new motor data + esc_status_s &esc_status = _telemetry->esc_status_pub.get(); + + if (motor_index < esc_status_s::CONNECTED_ESC_MAX) { + esc_status.esc_online_flags |= 1 << motor_index; + esc_status.esc[motor_index].timestamp = data.time; + esc_status.esc[motor_index].esc_rpm = ((int)data.erpm * 100) / (_param_mot_pole_count.get() / 2); + esc_status.esc[motor_index].esc_voltage = (float)data.voltage * 0.01f; + esc_status.esc[motor_index].esc_current = (float)data.current * 0.01f; + esc_status.esc[motor_index].esc_temperature = data.temperature; + // TODO: accumulate consumption and use for battery estimation + } + + // publish when motor index wraps (which is robust against motor timeouts) + if (motor_index < _telemetry->last_motor_index) { + esc_status.timestamp = hrt_absolute_time(); + esc_status.esc_connectiontype = esc_status_s::ESC_CONNECTION_TYPE_DSHOT; + esc_status.esc_count = _telemetry->handler.numMotors(); + ++esc_status.counter; + // FIXME: mark all ESC's as online, otherwise commander complains even for a single dropout + esc_status.esc_online_flags = (1 << esc_status.esc_count) - 1; + + _telemetry->esc_status_pub.update(); + + // reset esc data (in case a motor times out, so we won't send stale data) + memset(&esc_status.esc, 0, sizeof(_telemetry->esc_status_pub.get().esc)); + esc_status.esc_online_flags = 0; + } + + _telemetry->last_motor_index = motor_index; +} + +void DShotOutput::mixerChanged() +{ + updateTelemetryNumMotors(); +} void DShotOutput::updateOutputs(bool stop_motors, uint16_t outputs[MAX_ACTUATORS], unsigned num_outputs, unsigned num_control_groups_updated) @@ -494,6 +592,10 @@ void DShotOutput::updateOutputs(bool stop_motors, uint16_t outputs[MAX_ACTUATORS int requested_telemetry_index = -1; + if (_telemetry) { + requested_telemetry_index = _mixing_output.reorderedMotorIndex(_telemetry->handler.getRequestMotorIndex()); + } + if (stop_motors) { for (int i = 0; i < (int)num_outputs; i++) { @@ -534,10 +636,24 @@ DShotOutput::Run() update_dshot_out_state(outputs_on); } + if (_telemetry) { + int telem_update = _telemetry->handler.update(); + + if (telem_update >= 0) { + handleNewTelemetryData(telem_update, _telemetry->handler.latestESCData()); + } + } + if (_param_sub.updated()) { update_params(); } + // telemetry device update request? + if (_request_telemetry_init.load()) { + initTelemetry(_telemetry_device); + _request_telemetry_init.store(false); + } + // check at end of cycle (updateSubscriptions() can potentially change to a different WorkQueue thread) _mixing_output.updateSubscriptions(true); @@ -1108,6 +1224,17 @@ int DShotOutput::custom_command(int argc, char *argv[]) PortMode new_mode = PORT_MODE_UNSET; const char *verb = argv[0]; + if (!strcmp(verb, "telemetry")) { + if (argc > 1) { + // telemetry can be requested before the module is started + strncpy(_telemetry_device, argv[1], sizeof(_telemetry_device) - 1); + _telemetry_device[sizeof(_telemetry_device) - 1] = '\0'; + _request_telemetry_init.store(true); + } + + return 0; + } + /* start the FMU if not running */ if (!is_running()) { int ret = DShotOutput::task_spawn(argc, argv); @@ -1234,6 +1361,9 @@ to use DShot as ESC communication protocol instead of PWM. PRINT_MODULE_USAGE_COMMAND("mode_pwm1"); #endif + PRINT_MODULE_USAGE_COMMAND_DESCR("telemetry", "Enable Telemetry on a UART"); + PRINT_MODULE_USAGE_ARG("", "UART device", false); + PRINT_MODULE_USAGE_DEFAULT_COMMANDS(); return 0; @@ -1290,6 +1420,9 @@ int DShotOutput::print_status() perf_print_counter(_cycle_perf); perf_print_counter(_cycle_interval_perf); _mixing_output.printStatus(); + if (_telemetry) { + PX4_INFO("telemetry on: %s", _telemetry_device); + } return 0; } diff --git a/src/drivers/dshot/module.yaml b/src/drivers/dshot/module.yaml new file mode 100644 index 0000000000..a276ceda75 --- /dev/null +++ b/src/drivers/dshot/module.yaml @@ -0,0 +1,54 @@ +module_name: DShot Driver +serial_config: + - command: dshot telemetry ${SERIAL_DEV} + port_config_param: + name: DSHOT_TEL_CFG + group: DShot + +parameters: + - group: DShot + definitions: + DSHOT_CONFIG: + description: + short: Configure DShot + long: | + This enables/disables DShot. The different modes define different + speeds, for example DShot150 = 150kb/s. Not all ESCs support all modes. + + Note: this enables DShot on the FMU outputs. For boards with an IO it is the + AUX outputs. + type: enum + values: + 0: Disable (use PWM/Oneshot) + 150: DShot150 + 300: DShot300 + 600: DShot600 + 1200: DShot1200 + reboot_required: true + default: 0 + DSHOT_MIN: + description: + short: Minimum DShot Motor Output + long: | + Minimum Output Value for DShot in percent. The value depends on the ESC. Make + sure to set this high enough so that the motors are always spinning while + armed. + type: float + unit: '%' + min: 0 + max: 1 + decimal: 2 + increment: 0.01 + default: 0.055 + MOT_POLE_COUNT: # only used by dshot so far, so keep it under the dshot group + description: + short: Number of magnetic poles of the motors + long: | + Specify the number of magnetic poles of the motors. + It is required to compute the RPM value from the eRPM returned with the ESC telemetry. + + Either get the number from the motor spec sheet or count the magnets on the bell of the motor (not the stator magnets). + Typical motors for 5 inch props have 14 poles. + type: int32 + default: 14 + diff --git a/src/drivers/dshot/telemetry.cpp b/src/drivers/dshot/telemetry.cpp new file mode 100644 index 0000000000..50823a318f --- /dev/null +++ b/src/drivers/dshot/telemetry.cpp @@ -0,0 +1,265 @@ +/**************************************************************************** + * + * Copyright (c) 2019 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. + * + ****************************************************************************/ + +#include "telemetry.h" + +#include + +#include +#include +#include +#include + +using namespace time_literals; + +#define DSHOT_TELEMETRY_UART_BAUDRATE 115200 + +DShotTelemetry::~DShotTelemetry() +{ + deinit(); +} + +int DShotTelemetry::init(const char *uart_device) +{ + deinit(); + _uart_fd = ::open(uart_device, O_RDONLY | O_NOCTTY); + + if (_uart_fd < 0) { + PX4_ERR("failed to open serial port: %s err: %d", uart_device, errno); + return -errno; + } + + _current_motor_index_request = -1; + return setBaudrate(DSHOT_TELEMETRY_UART_BAUDRATE); +} + +void DShotTelemetry::deinit() +{ + if (_uart_fd >= 0) { + close(_uart_fd); + _uart_fd = -1; + } +} + +int DShotTelemetry::update() +{ + if (_uart_fd < 0) { + return -1; + } + + if (_current_motor_index_request == -1) { + // nothing in progress, start a request + _current_motor_index_request = 0; + _current_request_start = 0; + _frame_position = 0; + return -1; + } + + // read from the uart. This must be non-blocking, so check first if there is data available + int bytes_available = 0; + int ret = ioctl(_uart_fd, FIONREAD, (unsigned long)&bytes_available); + + if (ret != 0 || bytes_available <= 0) { + // no data available. Check for a timeout + const hrt_abstime now = hrt_absolute_time(); + + if (_current_request_start > 0 && now - _current_request_start > 30_ms) { + PX4_DEBUG("ESC telemetry timeout for motor %i (frame pos=%i)", _current_motor_index_request, _frame_position); + requestNextMotor(); + } + + return -1; + } + + const int buf_length = ESC_FRAME_SIZE; + uint8_t buf[buf_length]; + + int num_read = read(_uart_fd, buf, buf_length); + ret = -1; + + for (int i = 0; i < num_read; ++i) { + if (decodeByte(buf[i])) { + ret = _current_motor_index_request; + requestNextMotor(); + } + } + + return ret; +} + +bool DShotTelemetry::decodeByte(uint8_t byte) +{ + _frame_buffer[_frame_position++] = byte; + + if (_frame_position == ESC_FRAME_SIZE) { + PX4_DEBUG("got ESC frame for motor %i", _current_motor_index_request); + uint8_t checksum = crc8(_frame_buffer, ESC_FRAME_SIZE - 1); + uint8_t checksum_data = _frame_buffer[ESC_FRAME_SIZE - 1]; + + if (checksum == checksum_data) { + _latest_data.time = hrt_absolute_time(); + _latest_data.temperature = _frame_buffer[0]; + _latest_data.voltage = (_frame_buffer[1] << 8) | _frame_buffer[2]; + _latest_data.current = (_frame_buffer[3] << 8) | _frame_buffer[4]; + _latest_data.consumption = (_frame_buffer[5]) << 8 | _frame_buffer[6]; + _latest_data.erpm = (_frame_buffer[7] << 8) | _frame_buffer[8]; + PX4_DEBUG("Motor %i: temp=%i, V=%i, cur=%i, consumpt=%i, rpm=%i", _current_motor_index_request, + _latest_data.temperature, _latest_data.voltage, _latest_data.current, _latest_data.consumption, + _latest_data.erpm); + } + + return true; + } + + return false; +} + +uint8_t DShotTelemetry::updateCrc8(uint8_t crc, uint8_t crc_seed) +{ + uint8_t crc_u = crc ^ crc_seed; + + for (int i = 0; i < 8; ++i) { + crc_u = (crc_u & 0x80) ? 0x7 ^ (crc_u << 1) : (crc_u << 1); + } + + return crc_u; +} + + +uint8_t DShotTelemetry::crc8(const uint8_t *buf, uint8_t len) +{ + uint8_t crc = 0; + + for (int i = 0; i < len; ++i) { + crc = updateCrc8(buf[i], crc); + } + + return crc; +} + + +void DShotTelemetry::requestNextMotor() +{ + _current_motor_index_request = (_current_motor_index_request + 1) % _num_motors; + _current_request_start = 0; + _frame_position = 0; +} + +int DShotTelemetry::getRequestMotorIndex() +{ + if (_current_request_start != 0) { + // already in progress, do not send another request + return -1; + } + + _current_request_start = hrt_absolute_time(); + return _current_motor_index_request; +} + +int DShotTelemetry::setBaudrate(unsigned baud) +{ + int speed; + + switch (baud) { + case 9600: speed = B9600; break; + + case 19200: speed = B19200; break; + + case 38400: speed = B38400; break; + + case 57600: speed = B57600; break; + + case 115200: speed = B115200; break; + + case 230400: speed = B230400; break; + + default: + return -EINVAL; + } + + struct termios uart_config; + + int termios_state; + + /* fill the struct for the new configuration */ + tcgetattr(_uart_fd, &uart_config); + + // + // Input flags - Turn off input processing + // + // convert break to null byte, no CR to NL translation, + // no NL to CR translation, don't mark parity errors or breaks + // no input parity check, don't strip high bit off, + // no XON/XOFF software flow control + // + uart_config.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | + INLCR | PARMRK | INPCK | ISTRIP | IXON); + // + // Output flags - Turn off output processing + // + // no CR to NL translation, no NL to CR-NL translation, + // no NL to CR translation, no column 0 CR suppression, + // no Ctrl-D suppression, no fill characters, no case mapping, + // no local output processing + // + // config.c_oflag &= ~(OCRNL | ONLCR | ONLRET | + // ONOCR | ONOEOT| OFILL | OLCUC | OPOST); + uart_config.c_oflag = 0; + + // + // No line processing + // + // echo off, echo newline off, canonical mode off, + // extended input processing off, signal chars off + // + uart_config.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG); + + /* no parity, one stop bit, disable flow control */ + uart_config.c_cflag &= ~(CSTOPB | PARENB | CRTSCTS); + + /* set baud rate */ + if ((termios_state = cfsetispeed(&uart_config, speed)) < 0) { + return -errno; + } + + if ((termios_state = cfsetospeed(&uart_config, speed)) < 0) { + return -errno; + } + + if ((termios_state = tcsetattr(_uart_fd, TCSANOW, &uart_config)) < 0) { + return -errno; + } + + return 0; +} + diff --git a/src/drivers/dshot/params.c b/src/drivers/dshot/telemetry.h similarity index 54% rename from src/drivers/dshot/params.c rename to src/drivers/dshot/telemetry.h index d5f0b9708b..4373a8079d 100644 --- a/src/drivers/dshot/params.c +++ b/src/drivers/dshot/telemetry.h @@ -31,39 +31,69 @@ * ****************************************************************************/ -/** - * Configure DShot - * - * This enabled/disabled DShot. The different modes define different - * speeds, for example DShot150 = 150kb/s. Not all ESCs support all modes. - * - * Note: this enabled DShot on the FMU outputs. For boards with an IO it is the - * AUX outputs. - * - * @value 0 Disable (use PWM/Oneshot) - * @value 150 DShot150 - * @value 300 DShot300 - * @value 600 DShot600 - * @value 1200 DShot1200 - * @reboot_required true - * @group DShot - */ -PARAM_DEFINE_INT32(DSHOT_CONFIG, 0); +#pragma once -/** - * Minimum DShot Motor Output - * - * Minimum Output Value for DShot in percent. The value depends on the ESC. Make - * sure to set this high enough so that the motors are always spinning while - * armed. - * - * @unit norm - * @min 0 - * @max 1 - * @decimal 2 - * @increment 0.01 - * @group DShot - */ -PARAM_DEFINE_FLOAT(DSHOT_MIN, 0.055f); +#include +class DShotTelemetry +{ +public: + struct EscData { + hrt_abstime time; + int8_t temperature; ///< [deg C] + int16_t voltage; ///< [0.01V] + int16_t current; ///< [0.01A] + int16_t consumption; ///< [mAh] + int16_t erpm; ///< [100ERPM] + }; + ~DShotTelemetry(); + + int init(const char *uart_device); + + void deinit(); + + void setNumMotors(int num_motors) { _num_motors = num_motors; } + int numMotors() const { return _num_motors; } + + /** + * Read telemetry from the UART (non-blocking) and handle timeouts. + * @return -1 if no update, >= 0 for the motor index. Use @latestESCData() to get the data. + */ + int update(); + + /** + * Get the motor index for which telemetry should be requested. + * @return -1 if no request should be made, motor index otherwise + */ + int getRequestMotorIndex(); + + const EscData &latestESCData() const { return _latest_data; } + +private: + static constexpr int ESC_FRAME_SIZE = 10; + + /** + * set the Baudrate + * @param baud + * @return 0 on success, <0 on error + */ + int setBaudrate(unsigned baud); + + void requestNextMotor(); + + bool decodeByte(uint8_t byte); + + static inline uint8_t updateCrc8(uint8_t crc, uint8_t crc_seed); + static uint8_t crc8(const uint8_t *buf, uint8_t len); + + int _uart_fd{-1}; + int _num_motors{0}; + uint8_t _frame_buffer[ESC_FRAME_SIZE]; + int _frame_position{0}; + + EscData _latest_data; + + int _current_motor_index_request{-1}; + hrt_abstime _current_request_start{0}; +}; diff --git a/src/lib/mixer_module/mixer_module.cpp b/src/lib/mixer_module/mixer_module.cpp index c94848da24..f1cdc824fa 100644 --- a/src/lib/mixer_module/mixer_module.cpp +++ b/src/lib/mixer_module/mixer_module.cpp @@ -374,6 +374,23 @@ MixingOutput::reorderOutputs(uint16_t values[MAX_ACTUATORS]) */ } +int MixingOutput::reorderedMotorIndex(int index) +{ + if ((MotorOrdering)_param_mot_ordering.get() == MotorOrdering::Betaflight) { + switch (index) { + case 0: return 1; + + case 1: return 2; + + case 2: return 3; + + case 3: return 0; + } + } + + return index; +} + int MixingOutput::controlCallback(uintptr_t handle, uint8_t control_group, uint8_t control_index, float &input) { const MixingOutput *output = (const MixingOutput *)handle; @@ -420,6 +437,8 @@ void MixingOutput::resetMixer() _mixers = nullptr; _groups_required = 0; } + + _interface.mixerChanged(); } int MixingOutput::loadMixer(const char *buf, unsigned len) @@ -447,6 +466,7 @@ int MixingOutput::loadMixer(const char *buf, unsigned len) PX4_DEBUG("loaded mixers \n%s\n", buf); updateParams(); + _interface.mixerChanged(); return ret; } diff --git a/src/lib/mixer_module/mixer_module.hpp b/src/lib/mixer_module/mixer_module.hpp index 3b0721a78f..6c7eafc956 100644 --- a/src/lib/mixer_module/mixer_module.hpp +++ b/src/lib/mixer_module/mixer_module.hpp @@ -66,6 +66,9 @@ public: virtual void updateOutputs(bool stop_motors, uint16_t outputs[MAX_ACTUATORS], unsigned num_outputs, unsigned num_control_groups_updated) = 0; + + /** called whenever the mixer gets updated/reset */ + virtual void mixerChanged() {}; }; /** @@ -148,11 +151,18 @@ public: uint16_t &reverseOutputMask() { return _reverse_output_mask; } uint16_t &failsafeValue(int index) { return _failsafe_value[index]; } - /** Disarmed values: if ramp_up is true, then disarmedValue < minValue needs to hold */ + /** Disarmed values: disarmedValue < minValue needs to hold */ uint16_t &disarmedValue(int index) { return _disarmed_value[index]; } uint16_t &minValue(int index) { return _min_value[index]; } uint16_t &maxValue(int index) { return _max_value[index]; } + /** + * Get the motor index that maps from PX4 convention to the configured one + * @param index motor index in [0, num_motors-1] + * @return reordered motor index. When out of range, the input index is returned + */ + int reorderedMotorIndex(int index); + protected: void updateParams() override;