mirror of https://github.com/ArduPilot/ardupilot
AP_RCTelemetry: Spektrum telemetry library and telemetry abstraction
add support for temperature, battery voltage, battery current, flight pack altitiude, airspeed, attitude and compass, GPS, ESC telemetry based on BLHeli status messages and QOS packets. refactor into AP_Telemetry conditionally compile based on HAL_MINIMIZE_FEATURES don't initialize spektrum telemetry if there is no RC uart
This commit is contained in:
parent
da166b256d
commit
e3a88f686d
|
@ -134,7 +134,7 @@ void AP_RCProtocol_SRXL2::_process_byte(uint32_t timestamp_us, uint8_t byte)
|
|||
|
||||
void AP_RCProtocol_SRXL2::update(void)
|
||||
{
|
||||
#if 0 // it's not clear this is actually required
|
||||
// it's not clear this is actually required, perhaps on power loss?
|
||||
if (frontend.protocol_detected() == AP_RCProtocol::SRXL2) {
|
||||
uint32_t now = AP_HAL::millis();
|
||||
// there have been no updates since we were last called
|
||||
|
@ -144,7 +144,6 @@ void AP_RCProtocol_SRXL2::update(void)
|
|||
_last_run_ms = now;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AP_RCProtocol_SRXL2::capture_scaled_input(const uint16_t *values, bool in_failsafe, int16_t new_rssi)
|
||||
|
@ -251,6 +250,8 @@ void AP_RCProtocol_SRXL2::_change_baud_rate(uint32_t baudrate)
|
|||
#endif
|
||||
}
|
||||
|
||||
// SRXL2 library callbacks below
|
||||
|
||||
// User-provided routine to change the baud rate settings on the given UART:
|
||||
// uart - the same uint8_t value as the uart parameter passed to srxlInit()
|
||||
// baudRate - the actual baud rate (currently either 115200 or 400000)
|
||||
|
@ -298,12 +299,10 @@ void srxlReceivedChannelData(SrxlChannelData* pChannelData, bool isFailsafe)
|
|||
// Return true if you want this bind information set automatically for all other receivers on all SRXL buses.
|
||||
bool srxlOnBind(SrxlFullID device, SrxlBindData info)
|
||||
{
|
||||
// TODO: Add custom handling of bound data report here if needed
|
||||
return true;
|
||||
}
|
||||
|
||||
// User-provided callback routine to handle reception of a VTX control packet.
|
||||
void srxlOnVtx(SrxlVtxData* pVtxData)
|
||||
{
|
||||
//userProvidedHandleVtxData(pVtxData);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
Abstract Telemetry library
|
||||
*/
|
||||
|
||||
#include "AP_RCTelemetry.h"
|
||||
|
||||
#include <AP_AHRS/AP_AHRS.h>
|
||||
#include <AP_Common/AP_FWVersion.h>
|
||||
#include <GCS_MAVLink/GCS.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifdef TELEM_DEBUG
|
||||
# define debug(fmt, args...) hal.console->printf("Telem: " fmt "\n", ##args)
|
||||
#else
|
||||
# define debug(fmt, args...) do {} while(0)
|
||||
#endif
|
||||
|
||||
extern const AP_HAL::HAL& hal;
|
||||
|
||||
/*
|
||||
setup ready for passthrough telem
|
||||
*/
|
||||
bool AP_RCTelemetry::init(void)
|
||||
{
|
||||
#if !APM_BUILD_TYPE(APM_BUILD_UNKNOWN)
|
||||
// make telemetry available to GCS_MAVLINK (used to queue statustext messages from GCS_MAVLINK)
|
||||
// add firmware and frame info to message queue
|
||||
const char* _frame_string = gcs().frame_string();
|
||||
if (_frame_string == nullptr) {
|
||||
queue_message(MAV_SEVERITY_INFO, AP::fwversion().fw_string);
|
||||
} else {
|
||||
char firmware_buf[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN+1];
|
||||
snprintf(firmware_buf, sizeof(firmware_buf), "%s %s", AP::fwversion().fw_string, _frame_string);
|
||||
queue_message(MAV_SEVERITY_INFO, firmware_buf);
|
||||
}
|
||||
#endif
|
||||
setup_wfq_scheduler();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AP_RCTelemetry::update_avg_packet_rate()
|
||||
{
|
||||
uint32_t poll_now = AP_HAL::millis();
|
||||
|
||||
_scheduler.avg_packet_counter++;
|
||||
|
||||
if (poll_now - _scheduler.last_poll_timer > 1000) { //average in last 1000ms
|
||||
// initialize
|
||||
if (_scheduler.avg_packet_rate == 0) _scheduler.avg_packet_rate = _scheduler.avg_packet_counter;
|
||||
// moving average
|
||||
_scheduler.avg_packet_rate = (uint8_t)_scheduler.avg_packet_rate * 0.75f + _scheduler.avg_packet_counter * 0.25f;
|
||||
// reset
|
||||
_scheduler.last_poll_timer = poll_now;
|
||||
_scheduler.avg_packet_counter = 0;
|
||||
debug("avg packet rate %dHz, rates(Hz) %d %d %d %d %d %d %d %d", _scheduler.avg_packet_rate,
|
||||
_scheduler.packet_rate[0],
|
||||
_scheduler.packet_rate[1],
|
||||
_scheduler.packet_rate[2],
|
||||
_scheduler.packet_rate[3],
|
||||
_scheduler.packet_rate[4],
|
||||
_scheduler.packet_rate[5],
|
||||
_scheduler.packet_rate[6],
|
||||
_scheduler.packet_rate[7]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* WFQ scheduler
|
||||
*/
|
||||
void AP_RCTelemetry::run_wfq_scheduler(void)
|
||||
{
|
||||
update_avg_packet_rate();
|
||||
|
||||
uint32_t now = AP_HAL::millis();
|
||||
uint8_t max_delay_idx = 0;
|
||||
|
||||
float max_delay = 0;
|
||||
float delay = 0;
|
||||
bool packet_ready = false;
|
||||
|
||||
// build message queue for sensor_status_flags
|
||||
check_sensor_status_flags();
|
||||
// build message queue for ekf_status
|
||||
check_ekf_status();
|
||||
|
||||
// dynamic priorities
|
||||
bool queue_empty;
|
||||
{
|
||||
WITH_SEMAPHORE(_statustext.sem);
|
||||
queue_empty = !_statustext.available && _statustext.queue.empty();
|
||||
}
|
||||
|
||||
adjust_packet_weight(queue_empty);
|
||||
|
||||
// search the packet with the longest delay after the scheduled time
|
||||
for (int i=0; i<_time_slots; i++) {
|
||||
// normalize packet delay relative to packet weight
|
||||
delay = (now - _scheduler.packet_timer[i])/static_cast<float>(_scheduler.packet_weight[i]);
|
||||
// use >= so with equal delays we choose the packet with lowest priority
|
||||
// this is ensured by the packets being sorted by desc frequency
|
||||
// apply the rate limiter
|
||||
if (delay >= max_delay && ((now - _scheduler.packet_timer[i]) >= _scheduler.packet_min_period[i])) {
|
||||
packet_ready = is_packet_ready(i, queue_empty);
|
||||
|
||||
if (packet_ready) {
|
||||
max_delay = delay;
|
||||
max_delay_idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
now = AP_HAL::millis();
|
||||
#ifdef TELEM_DEBUG
|
||||
_scheduler.packet_rate[max_delay_idx] = (_scheduler.packet_rate[max_delay_idx] + 1000 / (now - _scheduler.packet_timer[max_delay_idx])) / 2;
|
||||
#endif
|
||||
_scheduler.packet_timer[max_delay_idx] = now;
|
||||
//debug("process_packet(%d): %f", max_delay_idx, max_delay);
|
||||
// send packet
|
||||
process_packet(max_delay_idx);
|
||||
}
|
||||
|
||||
/*
|
||||
* add message to message cue for transmission through link
|
||||
*/
|
||||
void AP_RCTelemetry::queue_message(MAV_SEVERITY severity, const char *text)
|
||||
{
|
||||
mavlink_statustext_t statustext{};
|
||||
|
||||
statustext.severity = severity;
|
||||
strncpy(statustext.text, text, sizeof(statustext.text));
|
||||
|
||||
// The force push will ensure comm links do not block other comm links forever if they fail.
|
||||
// If we push to a full buffer then we overwrite the oldest entry, effectively removing the
|
||||
// block but not until the buffer fills up.
|
||||
WITH_SEMAPHORE(_statustext.sem);
|
||||
_statustext.queue.push_force(statustext);
|
||||
}
|
||||
|
||||
/*
|
||||
* add sensor_status_flags information to message cue, normally passed as sys_status mavlink messages to the GCS, for transmission through FrSky link
|
||||
*/
|
||||
void AP_RCTelemetry::check_sensor_status_flags(void)
|
||||
{
|
||||
uint32_t now = AP_HAL::millis();
|
||||
|
||||
const uint32_t _sensor_status_flags = sensor_status_flags();
|
||||
|
||||
if ((now - check_sensor_status_timer) >= 5000) { // prevent repeating any system_status messages unless 5 seconds have passed
|
||||
// only one error is reported at a time (in order of preference). Same setup and displayed messages as Mission Planner.
|
||||
if ((_sensor_status_flags & MAV_SYS_STATUS_SENSOR_GPS) > 0) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Bad GPS Health");
|
||||
check_sensor_status_timer = now;
|
||||
} else if ((_sensor_status_flags & MAV_SYS_STATUS_SENSOR_3D_GYRO) > 0) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Bad Gyro Health");
|
||||
check_sensor_status_timer = now;
|
||||
} else if ((_sensor_status_flags & MAV_SYS_STATUS_SENSOR_3D_ACCEL) > 0) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Bad Accel Health");
|
||||
check_sensor_status_timer = now;
|
||||
} else if ((_sensor_status_flags & MAV_SYS_STATUS_SENSOR_3D_MAG) > 0) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Bad Compass Health");
|
||||
check_sensor_status_timer = now;
|
||||
} else if ((_sensor_status_flags & MAV_SYS_STATUS_SENSOR_ABSOLUTE_PRESSURE) > 0) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Bad Baro Health");
|
||||
check_sensor_status_timer = now;
|
||||
} else if ((_sensor_status_flags & MAV_SYS_STATUS_SENSOR_LASER_POSITION) > 0) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Bad LiDAR Health");
|
||||
check_sensor_status_timer = now;
|
||||
} else if ((_sensor_status_flags & MAV_SYS_STATUS_SENSOR_OPTICAL_FLOW) > 0) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Bad OptFlow Health");
|
||||
check_sensor_status_timer = now;
|
||||
} else if ((_sensor_status_flags & MAV_SYS_STATUS_TERRAIN) > 0) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Bad or No Terrain Data");
|
||||
check_sensor_status_timer = now;
|
||||
} else if ((_sensor_status_flags & MAV_SYS_STATUS_GEOFENCE) > 0) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Geofence Breach");
|
||||
check_sensor_status_timer = now;
|
||||
} else if ((_sensor_status_flags & MAV_SYS_STATUS_AHRS) > 0) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Bad AHRS");
|
||||
check_sensor_status_timer = now;
|
||||
} else if ((_sensor_status_flags & MAV_SYS_STATUS_SENSOR_RC_RECEIVER) > 0) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "No RC Receiver");
|
||||
check_sensor_status_timer = now;
|
||||
} else if ((_sensor_status_flags & MAV_SYS_STATUS_LOGGING) > 0) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Bad Logging");
|
||||
check_sensor_status_timer = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* add innovation variance information to message cue, normally passed as ekf_status_report mavlink messages to the GCS, for transmission through FrSky link
|
||||
*/
|
||||
void AP_RCTelemetry::check_ekf_status(void)
|
||||
{
|
||||
// get variances
|
||||
bool get_variance;
|
||||
float velVar, posVar, hgtVar, tasVar;
|
||||
Vector3f magVar;
|
||||
Vector2f offset;
|
||||
{
|
||||
AP_AHRS &_ahrs = AP::ahrs();
|
||||
WITH_SEMAPHORE(_ahrs.get_semaphore());
|
||||
get_variance = _ahrs.get_variances(velVar, posVar, hgtVar, magVar, tasVar, offset);
|
||||
}
|
||||
if (get_variance) {
|
||||
uint32_t now = AP_HAL::millis();
|
||||
if ((now - check_ekf_status_timer) >= 10000) { // prevent repeating any ekf_status message unless 10 seconds have passed
|
||||
// multiple errors can be reported at a time. Same setup as Mission Planner.
|
||||
if (velVar >= 1) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Error velocity variance");
|
||||
check_ekf_status_timer = now;
|
||||
}
|
||||
if (posVar >= 1) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Error pos horiz variance");
|
||||
check_ekf_status_timer = now;
|
||||
}
|
||||
if (hgtVar >= 1) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Error pos vert variance");
|
||||
check_ekf_status_timer = now;
|
||||
}
|
||||
if (magVar.length() >= 1) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Error compass variance");
|
||||
check_ekf_status_timer = now;
|
||||
}
|
||||
if (tasVar >= 1) {
|
||||
queue_message(MAV_SEVERITY_CRITICAL, "Error terrain alt variance");
|
||||
check_ekf_status_timer = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AP_RCTelemetry::sensor_status_flags() const
|
||||
{
|
||||
uint32_t present;
|
||||
uint32_t enabled;
|
||||
uint32_t health;
|
||||
gcs().get_sensor_status_flags(present, enabled, health);
|
||||
|
||||
return ~health & enabled & present;
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AP_HAL/AP_HAL.h>
|
||||
#include <AP_Notify/AP_Notify.h>
|
||||
#include <AP_HAL/utility/RingBuffer.h>
|
||||
|
||||
#define TELEM_PAYLOAD_STATUS_CAPACITY 5 // size of the message buffer queue (max number of messages waiting to be sent)
|
||||
|
||||
// for fair scheduler
|
||||
#define TELEM_TIME_SLOT_MAX 15
|
||||
//#define TELEM_DEBUG
|
||||
|
||||
class AP_RCTelemetry {
|
||||
public:
|
||||
AP_RCTelemetry(uint8_t time_slots) : _time_slots(time_slots) {}
|
||||
virtual ~AP_RCTelemetry() {};
|
||||
|
||||
/* Do not allow copies */
|
||||
AP_RCTelemetry(const AP_RCTelemetry &other) = delete;
|
||||
AP_RCTelemetry &operator=(const AP_RCTelemetry&) = delete;
|
||||
|
||||
// add statustext message to message queue
|
||||
void queue_message(MAV_SEVERITY severity, const char *text);
|
||||
|
||||
// update error mask of sensors and subsystems. The mask uses the
|
||||
// MAV_SYS_STATUS_* values from mavlink. If a bit is set then it
|
||||
// indicates that the sensor or subsystem is present but not
|
||||
// functioning correctly
|
||||
uint32_t sensor_status_flags() const;
|
||||
|
||||
protected:
|
||||
void run_wfq_scheduler();
|
||||
// set an entry in the scheduler table
|
||||
void set_scheduler_entry(uint8_t slot, uint32_t weight, uint32_t min_period_ms) {
|
||||
_scheduler.packet_weight[slot] = weight;
|
||||
_scheduler.packet_min_period[slot] = min_period_ms;
|
||||
}
|
||||
// add an entry to the scheduler table
|
||||
void add_scheduler_entry(uint32_t weight, uint32_t min_period_ms) {
|
||||
set_scheduler_entry(_time_slots++, weight, min_period_ms);
|
||||
}
|
||||
// setup ready for passthrough operation
|
||||
virtual bool init(void);
|
||||
|
||||
uint8_t _time_slots;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t last_poll_timer;
|
||||
uint32_t avg_packet_counter;
|
||||
uint32_t packet_timer[TELEM_TIME_SLOT_MAX];
|
||||
uint32_t packet_weight[TELEM_TIME_SLOT_MAX];
|
||||
uint32_t packet_min_period[TELEM_TIME_SLOT_MAX];
|
||||
uint8_t avg_packet_rate;
|
||||
#ifdef TELEM_DEBUG
|
||||
uint8_t packet_rate[TELEM_TIME_SLOT_MAX];
|
||||
#endif
|
||||
} _scheduler;
|
||||
|
||||
struct {
|
||||
HAL_Semaphore sem;
|
||||
ObjectBuffer<mavlink_statustext_t> queue{TELEM_PAYLOAD_STATUS_CAPACITY};
|
||||
mavlink_statustext_t next;
|
||||
bool available;
|
||||
} _statustext;
|
||||
|
||||
private:
|
||||
uint32_t check_sensor_status_timer;
|
||||
uint32_t check_ekf_status_timer;
|
||||
|
||||
// passthrough WFQ scheduler
|
||||
virtual void setup_wfq_scheduler() = 0;
|
||||
virtual bool get_next_msg_chunk(void) = 0;
|
||||
virtual bool is_packet_ready(uint8_t idx, bool queue_empty) = 0;
|
||||
virtual void process_packet(uint8_t idx) = 0;
|
||||
virtual void adjust_packet_weight(bool queue_empty) = 0;
|
||||
|
||||
void update_avg_packet_rate();
|
||||
|
||||
// methods to convert flight controller data to FrSky SPort Passthrough (OpenTX) format
|
||||
void check_sensor_status_flags(void);
|
||||
void check_ekf_status(void);
|
||||
};
|
|
@ -0,0 +1,622 @@
|
|||
/*
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
Spektrum Telemetry library, based on AP_Frsky_Telem.cpp
|
||||
See https://www.spektrumrc.com/ProdInfo/Files/SPM_Telemetry_Developers_Specs.pdf
|
||||
*/
|
||||
|
||||
#include "AP_Spektrum_Telem.h"
|
||||
#include <AP_HAL/utility/sparse-endian.h>
|
||||
#include <AP_AHRS/AP_AHRS.h>
|
||||
#include <AP_RPM/AP_RPM.h>
|
||||
#include <AP_Airspeed/AP_Airspeed.h>
|
||||
#include <AP_Compass/AP_Compass.h>
|
||||
#include <AP_BattMonitor/AP_BattMonitor.h>
|
||||
#include <AP_RangeFinder/AP_RangeFinder.h>
|
||||
#include <AP_Common/AP_FWVersion.h>
|
||||
#include <GCS_MAVLink/GCS.h>
|
||||
#include <AP_Common/Location.h>
|
||||
#include <AP_GPS/AP_GPS.h>
|
||||
#include <AP_Baro/AP_Baro.h>
|
||||
#include <AP_GPS/AP_GPS.h>
|
||||
#include <GCS_MAVLink/GCS.h>
|
||||
#include <AP_RTC/AP_RTC.h>
|
||||
#ifdef HAVE_AP_BLHELI_SUPPORT
|
||||
#include <AP_BLheli/AP_BLHeli.h>
|
||||
#endif
|
||||
#include <math.h>
|
||||
|
||||
#if HAL_SPEKTRUM_TELEM_ENABLED
|
||||
|
||||
#define MICROSEC_PER_MINUTE 60000000
|
||||
#define MAX_TEXTGEN_LEN 13
|
||||
|
||||
//#define SPKT_DEBUG
|
||||
#ifdef SPKT_DEBUG
|
||||
# define debug(fmt, args...) hal.console->printf("SPKT:" fmt "\n", ##args)
|
||||
#else
|
||||
# define debug(fmt, args...) do {} while(0)
|
||||
#endif
|
||||
|
||||
extern const AP_HAL::HAL& hal;
|
||||
|
||||
AP_Spektrum_Telem *AP_Spektrum_Telem::singleton;
|
||||
|
||||
AP_Spektrum_Telem::AP_Spektrum_Telem() : AP_RCTelemetry(0)
|
||||
{
|
||||
singleton = this;
|
||||
}
|
||||
|
||||
AP_Spektrum_Telem::~AP_Spektrum_Telem(void)
|
||||
{
|
||||
singleton = nullptr;
|
||||
}
|
||||
|
||||
bool AP_Spektrum_Telem::init(void)
|
||||
{
|
||||
// sanity check that we are using a UART for RC input
|
||||
if (!AP::serialmanager().find_serial(AP_SerialManager::SerialProtocol_RCIN, 0)) {
|
||||
return false;
|
||||
}
|
||||
return AP_RCTelemetry::init();
|
||||
}
|
||||
|
||||
/*
|
||||
setup ready for passthrough telem
|
||||
*/
|
||||
void AP_Spektrum_Telem::setup_wfq_scheduler(void)
|
||||
{
|
||||
// initialize packet weights for the WFQ scheduler
|
||||
// priority[i] = 1/_scheduler.packet_weight[i]
|
||||
// rate[i] = LinkRate * ( priority[i] / (sum(priority[1-n])) )
|
||||
|
||||
// Spektrum telemetry rate is 46Hz, so these rates must fit
|
||||
add_scheduler_entry(50, 100); // qos 10Hz
|
||||
add_scheduler_entry(50, 100); // rpm 10Hz
|
||||
add_scheduler_entry(50, 100); // text, 10Hz
|
||||
add_scheduler_entry(50, 120); // Attitude and compass 8Hz
|
||||
add_scheduler_entry(550, 280); // GPS 3Hz
|
||||
add_scheduler_entry(550, 280); // ESC 3Hz
|
||||
add_scheduler_entry(400, 250); // altitude 4Hz
|
||||
add_scheduler_entry(400, 250); // airspeed 4Hz
|
||||
add_scheduler_entry(700, 500); // GPS status 2Hz
|
||||
add_scheduler_entry(1300, 500); // batt volt 2Hz
|
||||
add_scheduler_entry(1300, 500); // batt curr 2Hz
|
||||
add_scheduler_entry(1300, 500); // batt mah 2Hz
|
||||
add_scheduler_entry(1300, 500); // temp 2Hz
|
||||
}
|
||||
|
||||
void AP_Spektrum_Telem::adjust_packet_weight(bool queue_empty)
|
||||
{
|
||||
if (!queue_empty) {
|
||||
_scheduler.packet_weight[TEXT] = 50; // messages
|
||||
} else {
|
||||
_scheduler.packet_weight[TEXT] = 5000; // messages
|
||||
}
|
||||
}
|
||||
|
||||
// WFQ scheduler
|
||||
bool AP_Spektrum_Telem::is_packet_ready(uint8_t idx, bool queue_empty)
|
||||
{
|
||||
bool packet_ready = false;
|
||||
switch (idx) {
|
||||
case TEXT:
|
||||
packet_ready = !queue_empty;
|
||||
break;
|
||||
default:
|
||||
packet_ready = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return packet_ready;
|
||||
}
|
||||
|
||||
// WFQ scheduler
|
||||
void AP_Spektrum_Telem::process_packet(uint8_t idx)
|
||||
{
|
||||
// send packet
|
||||
switch (idx) {
|
||||
case QOS: // QOS
|
||||
calc_qos();
|
||||
break;
|
||||
case RPM: // RPM
|
||||
calc_rpm();
|
||||
break;
|
||||
case TEXT: // status text
|
||||
if (repeat_msg_chunk() || get_next_msg_chunk()) {
|
||||
send_msg_chunk(_msg_chunk);
|
||||
}
|
||||
break;
|
||||
case ATTITUDE: // Attitude and compass
|
||||
calc_attandmag();
|
||||
break;
|
||||
case GPS_LOC: // GPS location
|
||||
calc_gps_location();
|
||||
break;
|
||||
case ESC: // ESC
|
||||
calc_esc();
|
||||
break;
|
||||
case ALTITUDE: // altitude
|
||||
calc_altitude();
|
||||
break;
|
||||
case AIRSPEED: // airspeed
|
||||
calc_airspeed();
|
||||
break;
|
||||
case GPS_STATUS: // GPS status
|
||||
calc_gps_status();
|
||||
break;
|
||||
case VOLTAGE: // Battery volts
|
||||
calc_batt_volts(0);
|
||||
break;
|
||||
case AMPS: // Battery current
|
||||
calc_batt_amps(0);
|
||||
break;
|
||||
case MAH: // Battery current & mah
|
||||
calc_batt_mah();
|
||||
break;
|
||||
case TEMP: // temperature
|
||||
calc_temperature(0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// whether to repeat the last texgen output
|
||||
bool AP_Spektrum_Telem::repeat_msg_chunk(void)
|
||||
{
|
||||
if (_msg_chunk.repeats == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// repeat each message chunk 3 times to ensure transmission
|
||||
// on slow links reduce the number of duplicate chunks
|
||||
uint8_t extra_chunks = 2;
|
||||
|
||||
if (_scheduler.avg_packet_rate < 20) {
|
||||
extra_chunks = 0;
|
||||
} else if (_scheduler.avg_packet_rate < 30) {
|
||||
extra_chunks = 1;
|
||||
}
|
||||
|
||||
if (_msg_chunk.repeats++ > extra_chunks) {
|
||||
_msg_chunk.repeats = 0;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// grabs one "chunk" (13 bytes) of the queued message to be transmitted
|
||||
bool AP_Spektrum_Telem::get_next_msg_chunk(void)
|
||||
{
|
||||
_msg_chunk.repeats++;
|
||||
|
||||
if (!_statustext.available) {
|
||||
WITH_SEMAPHORE(_statustext.sem);
|
||||
if (!_statustext.queue.pop(_statustext.next)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_statustext.available = true;
|
||||
|
||||
// We're going to display a new message so first clear the screen
|
||||
_msg_chunk.linenumber = 0xFF;
|
||||
_msg_chunk.char_index = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t character = 0;
|
||||
memset(_msg_chunk.chunk, 0, MAX_TEXTGEN_LEN);
|
||||
|
||||
const uint8_t message_len = sizeof(_statustext.next.text);
|
||||
// the message fits in an entire line of text
|
||||
if (message_len < MAX_TEXTGEN_LEN) {
|
||||
memcpy(_msg_chunk.chunk, _statustext.next.text, message_len);
|
||||
_msg_chunk.linenumber = 0;
|
||||
_statustext.available = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// a following part of multi-line text
|
||||
if (_msg_chunk.linenumber == 0xFF) {
|
||||
_msg_chunk.linenumber = 0;
|
||||
} else if (_msg_chunk.char_index > 0) {
|
||||
_msg_chunk.linenumber++;
|
||||
}
|
||||
|
||||
// skip leading whitespace
|
||||
while (_statustext.next.text[_msg_chunk.char_index] == ' ' && _msg_chunk.char_index < message_len) {
|
||||
_msg_chunk.char_index++;
|
||||
}
|
||||
|
||||
uint8_t space_idx = 0;
|
||||
const uint8_t begin_idx = _msg_chunk.char_index;
|
||||
// can't fit it all on one line so wrap at an appropriate place
|
||||
for (int i = 0; i < MAX_TEXTGEN_LEN && _msg_chunk.char_index < message_len; i++) {
|
||||
character = _statustext.next.text[_msg_chunk.char_index++];
|
||||
// split at the first ':'
|
||||
if (character == ':') {
|
||||
_msg_chunk.chunk[i] = 0;
|
||||
break;
|
||||
}
|
||||
// record the last space if we need to go back there
|
||||
if (character == ' ') {
|
||||
space_idx = _msg_chunk.char_index;
|
||||
}
|
||||
|
||||
_msg_chunk.chunk[i] = character;
|
||||
|
||||
if (!character) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// still not done, can we break at a word boundary?
|
||||
if (character != 0 && _msg_chunk.char_index < message_len && space_idx > 0) {
|
||||
_msg_chunk.char_index = space_idx;
|
||||
_msg_chunk.chunk[space_idx - begin_idx - 1] = 0;
|
||||
}
|
||||
|
||||
// we've reached the end of the message (string terminated by '\0' or last character of the string has been processed)
|
||||
if (character == 0 || _msg_chunk.char_index == message_len) {
|
||||
_msg_chunk.char_index = 0; // reset index to get ready to process the next message
|
||||
_statustext.available = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// prepare qos data - mandatory frame that must be sent periodically
|
||||
void AP_Spektrum_Telem::calc_qos()
|
||||
{
|
||||
_telem.qos.identifier = TELE_DEVICE_QOS;
|
||||
_telem.qos.sID = 0;
|
||||
_telem.qos.A = 0xFFFF;
|
||||
_telem.qos.B = 0xFFFF;
|
||||
_telem.qos.L = 0xFFFF;
|
||||
_telem.qos.R = 0xFFFF;
|
||||
_telem.qos.F = 0xFFFF;
|
||||
_telem.qos.H = 0xFFFF;
|
||||
_telem.qos.rxVoltage = 0xFFFF;
|
||||
_telem_pending = true;
|
||||
}
|
||||
|
||||
// prepare rpm data - B/E mandatory frame that must be sent periodically
|
||||
void AP_Spektrum_Telem::calc_rpm()
|
||||
{
|
||||
const AP_BattMonitor &_battery = AP::battery();
|
||||
|
||||
_telem.rpm.identifier = TELE_DEVICE_RPM;
|
||||
_telem.rpm.sID = 0;
|
||||
// battery voltage in centivolts, can have up to a 12S battery (4.25Vx12S = 51.0V)
|
||||
_telem.rpm.volts = htobe16(((uint16_t)roundf(_battery.voltage(0) * 100.0f)));
|
||||
_telem.rpm.temperature = htobe16(int16_t(roundf(32.0f + AP::baro().get_temperature(0) * 9.0f / 5.0f)));
|
||||
const AP_RPM *rpm = AP::rpm();
|
||||
float rpm_value;
|
||||
if (!rpm || !rpm->get_rpm(0, rpm_value) || rpm_value < 999.0f) {
|
||||
rpm_value = 999.0f;
|
||||
}
|
||||
_telem.rpm.microseconds = htobe16(uint16_t(roundf(MICROSEC_PER_MINUTE / rpm_value)));
|
||||
_telem.rpm.dBm_A = 0x7F;
|
||||
_telem.rpm.dBm_B = 0x7F;
|
||||
_telem_pending = true;
|
||||
}
|
||||
|
||||
void AP_Spektrum_Telem::send_msg_chunk(const MessageChunk& chunk)
|
||||
{
|
||||
memcpy(_telem.textgen.text, chunk.chunk, 13);
|
||||
_telem.textgen.identifier = TELE_DEVICE_TEXTGEN;
|
||||
_telem.textgen.lineNumber = chunk.linenumber;
|
||||
_telem.textgen.sID = 0;
|
||||
_telem_pending = true;
|
||||
}
|
||||
|
||||
// prepare battery data - B/E but not supported by Spektrum
|
||||
void AP_Spektrum_Telem::calc_batt_volts(uint8_t instance)
|
||||
{
|
||||
const AP_BattMonitor &_battery = AP::battery();
|
||||
|
||||
// battery voltage in centivolts, can have up to a 12S battery (4.25Vx12S = 51.0V)
|
||||
_telem.hv.volts = htobe16(uint16_t(roundf(_battery.voltage(instance) * 100.0f)));
|
||||
_telem.hv.identifier = TELE_DEVICE_VOLTAGE;
|
||||
_telem.hv.sID = 0;
|
||||
_telem_pending = true;
|
||||
}
|
||||
|
||||
// prepare battery data - B/E but not supported by Spektrum
|
||||
void AP_Spektrum_Telem::calc_batt_amps(uint8_t instance)
|
||||
{
|
||||
const AP_BattMonitor &_battery = AP::battery();
|
||||
|
||||
float current;
|
||||
if (!_battery.current_amps(current, instance)) {
|
||||
current = 0;
|
||||
}
|
||||
|
||||
// Range: +/- 150A Resolution: 300A / 2048 = 0.196791 A/count
|
||||
_telem.amps.current = htobe16(int16_t(roundf(current * 2048.0f / 300.0f)));
|
||||
_telem.amps.identifier = TELE_DEVICE_AMPS;
|
||||
_telem.amps.sID = 0;
|
||||
_telem_pending = true;
|
||||
}
|
||||
|
||||
// prepare battery data - L/E
|
||||
void AP_Spektrum_Telem::calc_batt_mah()
|
||||
{
|
||||
const AP_BattMonitor &_battery = AP::battery();
|
||||
|
||||
_telem.fpMAH.identifier = TELE_DEVICE_FP_MAH;
|
||||
_telem.fpMAH.sID = 0;
|
||||
|
||||
float current;
|
||||
if (!_battery.current_amps(current, 0)) {
|
||||
current = 0;
|
||||
}
|
||||
_telem.fpMAH.current_A = int16_t(roundf(current * 10.0f)); // Instantaneous current, 0.1A (0-3276.6A)
|
||||
|
||||
float used_mah;
|
||||
if (!_battery.consumed_mah(used_mah, 0)) {
|
||||
used_mah = 0;
|
||||
}
|
||||
_telem.fpMAH.chargeUsed_A = int16_t(roundf(used_mah)); // Integrated mAh used, 1mAh (0-32.766Ah)
|
||||
|
||||
float temp;
|
||||
if (_battery.get_temperature(temp, 0)) {
|
||||
_telem.fpMAH.temp_A = uint16_t(roundf(temp * 10.0f)); // Temperature, 0.1C (0-150C, 0x7FFF indicates not populated)
|
||||
} else {
|
||||
_telem.fpMAH.temp_A = 0x7FFF;
|
||||
}
|
||||
|
||||
if (!_battery.current_amps(current, 1)) {
|
||||
current = 0;
|
||||
}
|
||||
_telem.fpMAH.current_B = int16_t(roundf(current * 10.0f)); // Instantaneous current, 0.1A (0-3276.6A)
|
||||
|
||||
if (!_battery.consumed_mah(used_mah, 1)) {
|
||||
used_mah = 0;
|
||||
}
|
||||
_telem.fpMAH.chargeUsed_B = int16_t(roundf(used_mah)); // Integrated mAh used, 1mAh (0-32.766Ah)
|
||||
|
||||
if (_battery.get_temperature(temp, 1)) {
|
||||
_telem.fpMAH.temp_B = uint16_t(roundf(temp * 10.0f)); // Temperature, 0.1C (0-150C, 0x7FFF indicates not populated)
|
||||
} else {
|
||||
_telem.fpMAH.temp_B = 0x7FFF;
|
||||
}
|
||||
|
||||
_telem_pending = true;
|
||||
}
|
||||
|
||||
// prepare temperature data - B/E but not supported by Spektrum
|
||||
void AP_Spektrum_Telem::calc_temperature(uint8_t instance)
|
||||
{
|
||||
_telem.temp.temperature = htobe16(int16_t(roundf(32.0f + AP::baro().get_temperature(instance) * 9.0f / 5.0f)));
|
||||
_telem.temp.identifier = TELE_DEVICE_TEMPERATURE;
|
||||
_telem.temp.sID = 0;
|
||||
_telem_pending = true;
|
||||
}
|
||||
|
||||
// prepare altitude data - B/E
|
||||
void AP_Spektrum_Telem::calc_altitude()
|
||||
{
|
||||
_telem.alt.identifier = TELE_DEVICE_ALTITUDE;
|
||||
_telem.alt.sID = 0;
|
||||
|
||||
AP_AHRS &ahrs = AP::ahrs();
|
||||
WITH_SEMAPHORE(ahrs.get_semaphore());
|
||||
|
||||
float alt = 0;
|
||||
ahrs.get_relative_position_D_home(alt);
|
||||
alt = roundf(-alt * 10.0f);
|
||||
_telem.alt.altitude = htobe16(uint16_t(alt)); // .1m increments
|
||||
_max_alt = MAX(alt, _max_alt);
|
||||
_telem.alt.maxAltitude = htobe16(uint16_t(_max_alt)); // .1m increments
|
||||
_telem_pending = true;
|
||||
}
|
||||
|
||||
// prepare airspeed data - B/E
|
||||
void AP_Spektrum_Telem::calc_airspeed()
|
||||
{
|
||||
_telem.speed.identifier = TELE_DEVICE_AIRSPEED;
|
||||
_telem.speed.sID = 0;
|
||||
|
||||
AP_AHRS &ahrs = AP::ahrs();
|
||||
WITH_SEMAPHORE(ahrs.get_semaphore());
|
||||
|
||||
const AP_Airspeed *airspeed = ahrs.get_airspeed();
|
||||
float speed = 0.0f;
|
||||
if (airspeed && airspeed->healthy()) {
|
||||
speed = roundf(airspeed->get_airspeed() * 3.6);
|
||||
} else {
|
||||
speed = roundf(AP::ahrs().groundspeed() * 3.6);
|
||||
}
|
||||
_telem.speed.airspeed = htobe16(uint16_t(speed)); // 1 km/h increments
|
||||
_max_speed = MAX(speed, _max_speed);
|
||||
_telem.speed.maxAirspeed = htobe16(uint16_t(_max_speed)); // 1 km/h increments
|
||||
_telem_pending = true;
|
||||
}
|
||||
|
||||
// prepare attitude and compass data - L/E
|
||||
void AP_Spektrum_Telem::calc_attandmag(void)
|
||||
{
|
||||
_telem.attMag.identifier = TELE_DEVICE_ATTMAG;
|
||||
_telem.attMag.sID = 0;
|
||||
|
||||
AP_AHRS &_ahrs = AP::ahrs();
|
||||
WITH_SEMAPHORE(_ahrs.get_semaphore());
|
||||
|
||||
// Attitude, 3 axes. Roll is a rotation about the X Axis of the vehicle using the RHR.
|
||||
// Units are 0.1 deg - Pitch is a rotation about the Y Axis of the vehicle using the RHR.
|
||||
// Yaw is a rotation about the Z Axis of the vehicle using the RHR.
|
||||
_telem.attMag.attRoll = _ahrs.roll_sensor / 10;
|
||||
_telem.attMag.attPitch = _ahrs.pitch_sensor / 10;
|
||||
_telem.attMag.attYaw = _ahrs.yaw_sensor / 10;
|
||||
_telem.attMag.heading = (_ahrs.yaw_sensor / 10) % 3600; // Heading, 0.1deg
|
||||
|
||||
const Vector3f& field = AP::compass().get_field();
|
||||
|
||||
_telem.attMag.magX = int16_t(roundf(field.x * 10.0f)); // Units are 0.1mG
|
||||
_telem.attMag.magY = int16_t(roundf(field.y * 10.0f));
|
||||
_telem.attMag.magZ = int16_t(roundf(field.z * 10.0f));
|
||||
_telem_pending = true;
|
||||
}
|
||||
|
||||
// prepare gps location - L/E
|
||||
void AP_Spektrum_Telem::calc_gps_location()
|
||||
{
|
||||
const Location &loc = AP::gps().location(0); // use the first gps instance (same as in send_mavlink_gps_raw)
|
||||
const uint32_t u1e8 = 100000000, u1e7 = 10000000, u1e6 = 1000000, u1e5 = 100000, u1e4 = 10000;
|
||||
|
||||
_telem.gpsloc.identifier = TELE_DEVICE_GPS_LOC; // Source device = 0x16
|
||||
_telem.gpsloc.sID = 0; // Secondary ID
|
||||
uint32_t alt = (abs(loc.alt) / 10) % u1e6;
|
||||
_telem.gpsloc.altitudeLow = ((alt % u1e4 / 1000) << 12) | ((alt % 1000 / 100) << 8)
|
||||
| ((alt % 100 / 10) << 4) | (alt % 100); // BCD, meters, format 3.1 (Low order of altitude)
|
||||
|
||||
const float lat = fabsf(loc.lat / 1.0e7f); // BCD, format 4.4, Degrees * 100 + minutes, less than 100 degrees
|
||||
const float lng = fabsf(loc.lng / 1.0e7f); // BCD, format 4.4 , Degrees * 100 + minutes, flag indicates > 99 degrees
|
||||
|
||||
const uint32_t ulat = roundf((int32_t(lat) * 100.0f + (lat - int32_t(lat)) * 60.0f) * 10000.0f);
|
||||
const uint32_t ulng = roundf((int32_t(lng) * 100.0f + (lng - int32_t(lng)) * 60.0f) * 10000.0f);
|
||||
|
||||
_telem.gpsloc.latitude = ((ulat % u1e8 / u1e7) << 28) | ((ulat % u1e7 / u1e6) << 24) | ((ulat % u1e6 / u1e5) << 20) | ((ulat % u1e5 / u1e4) << 16)
|
||||
| ((ulat % u1e4 / 1000) << 12) | ((ulat % 1000 / 100) << 8) | ((ulat % 100 / 10) << 4) | (ulat % 10);
|
||||
_telem.gpsloc.longitude = ((ulng % u1e8 / u1e7) << 28) | ((ulng % u1e7 / u1e6) << 24) | ((ulng % u1e6 / u1e5) << 20) | ((ulng % u1e5 / u1e4) << 16)
|
||||
| ((ulng % u1e4 / 1000) << 12) | ((ulng % 1000 / 100) << 8) | ((ulng % 100 / 10) << 4) | (ulng % 10);
|
||||
|
||||
uint16_t course = uint16_t(roundf(AP::gps().ground_course() * 10.0f));
|
||||
_telem.gpsloc.course = ((course % u1e5 / u1e4) << 12) | ((course % u1e4 / 1000) << 8) | ((course % 1000 / 100) << 4) | (course % 100 / 10); // BCD, 3.1
|
||||
uint16_t hdop = AP::gps().get_hdop(0);
|
||||
_telem.gpsloc.HDOP = ((hdop % 1000 / 100) << 4) | (hdop % 100 / 10); // BCD, format 1.1
|
||||
_telem.gpsloc.GPSflags = 0;
|
||||
|
||||
if (AP::gps().status(0) >= AP_GPS::GPS_OK_FIX_3D) {
|
||||
_telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_3D_FIX;
|
||||
}
|
||||
if (loc.alt < 0) {
|
||||
_telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_NEGATIVE_ALT;
|
||||
}
|
||||
if ((loc.lng / 1e7) > 99) {
|
||||
_telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_LONGITUDE_GREATER_99;
|
||||
}
|
||||
if (loc.lat >= 0) {
|
||||
_telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_IS_NORTH;
|
||||
}
|
||||
if (loc.lng >= 0) {
|
||||
_telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_IS_EAST;
|
||||
}
|
||||
if (AP::gps().status(0) > AP_GPS::NO_FIX) {
|
||||
_telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_GPS_FIX_VALID;
|
||||
}
|
||||
if (AP::gps().status(0) >= AP_GPS::NO_FIX) {
|
||||
_telem.gpsloc.GPSflags |= GPS_INFO_FLAGS_GPS_DATA_RECEIVED;
|
||||
}
|
||||
_telem_pending = true;
|
||||
}
|
||||
|
||||
// prepare gps status - L/E
|
||||
void AP_Spektrum_Telem::calc_gps_status()
|
||||
{
|
||||
const Location &loc = AP::gps().location(0);
|
||||
|
||||
_telem.gpsstat.identifier = TELE_DEVICE_GPS_STATS; // Source device = 0x17
|
||||
_telem.gpsstat.sID = 0; // Secondary ID
|
||||
|
||||
uint16_t knots = roundf(AP::gps().ground_speed() * 1.94384f * 10.0f);
|
||||
_telem.gpsstat.speed = ((knots % 10000 / 1000) << 12) | ((knots % 1000 / 100) << 8) | ((knots % 100 / 10) << 4) | (knots % 10); // BCD, knots, format 3.1
|
||||
uint16_t ms;
|
||||
uint8_t h, m, s;
|
||||
AP::rtc().get_system_clock_utc(h, m, s, ms); // BCD, format HH:MM:SS.S, format 6.1
|
||||
_telem.gpsstat.UTC = ((((h / 10) << 4) | (h % 10)) << 20) | ((((m / 10) << 4) | (m % 10)) << 12) | ((((s / 10) << 4) | (s % 10)) << 4) | (ms / 100) ;
|
||||
uint8_t nsats = AP::gps().num_sats();
|
||||
_telem.gpsstat.numSats = ((nsats / 10) << 4) | (nsats % 10); // BCD, 0-99
|
||||
uint32_t alt = (abs(loc.alt) / 100000);
|
||||
_telem.gpsstat.altitudeHigh = ((alt / 10) << 4) | (alt % 10); // BCD, meters, format 2.0 (High order of altitude)
|
||||
_telem_pending = true;
|
||||
}
|
||||
|
||||
// prepare ESC information - B/E
|
||||
void AP_Spektrum_Telem::calc_esc()
|
||||
{
|
||||
#ifdef HAVE_AP_BLHELI_SUPPORT
|
||||
AP_BLHeli* blh = AP_BLHeli::get_singleton();
|
||||
|
||||
if (blh == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
AP_BLHeli::telem_data td;
|
||||
|
||||
if (!blh->get_telem_data(0, td)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_telem.esc.identifier = TELE_DEVICE_ESC; // Source device = 0x20
|
||||
_telem.esc.sID = 0; // Secondary ID
|
||||
_telem.esc.RPM = htobe16(uint16_t(roundf(blh->get_average_motor_frequency_hz() * 60))); // Electrical RPM, 10RPM (0-655340 RPM) 0xFFFF --> "No data"
|
||||
_telem.esc.voltsInput = htobe16(td.voltage); // Volts, 0.01v (0-655.34V) 0xFFFF --> "No data"
|
||||
_telem.esc.tempFET = htobe16(td.temperature * 10); // Temperature, 0.1C (0-6553.4C) 0xFFFF --> "No data"
|
||||
_telem.esc.currentMotor = htobe16(td.current); // Current, 10mA (0-655.34A) 0xFFFF --> "No data"
|
||||
_telem.esc.tempBEC = 0xFFFF; // Temperature, 0.1C (0-6553.4C) 0xFFFF --> "No data"
|
||||
_telem.esc.currentBEC = 0xFF; // BEC Current, 100mA (0-25.4A) 0xFF ----> "No data"
|
||||
_telem.esc.voltsBEC = 0xFF; // BEC Volts, 0.05V (0-12.70V) 0xFF ----> "No data"
|
||||
_telem.esc.throttle = 0xFF; // 0.5% (0-100%) 0xFF ----> "No data"
|
||||
_telem.esc.powerOut = 0xFF; // Power Output, 0.5% (0-127%) 0xFF ----> "No data"
|
||||
_telem_pending = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
fetch Spektrum data for an external transport, such as SRXL2
|
||||
*/
|
||||
bool AP_Spektrum_Telem::_get_telem_data(uint8_t* data)
|
||||
{
|
||||
memset(&_telem, 0, 16);
|
||||
run_wfq_scheduler();
|
||||
if (!_telem_pending) {
|
||||
return false;
|
||||
}
|
||||
memcpy(data, &_telem, 16);
|
||||
_telem_pending = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
fetch data for an external transport, such as SRXL2
|
||||
*/
|
||||
bool AP_Spektrum_Telem::get_telem_data(uint8_t* data)
|
||||
{
|
||||
if (!singleton && !hal.util->get_soft_armed()) {
|
||||
// if telem data is requested when we are disarmed and don't
|
||||
// yet have a AP_Spektrum_Telem object then try to allocate one
|
||||
new AP_Spektrum_Telem();
|
||||
// initialize the passthrough scheduler
|
||||
if (singleton) {
|
||||
singleton->init();
|
||||
}
|
||||
}
|
||||
if (!singleton) {
|
||||
return false;
|
||||
}
|
||||
return singleton->_get_telem_data(data);
|
||||
}
|
||||
|
||||
namespace AP {
|
||||
AP_Spektrum_Telem *spektrum_telem() {
|
||||
return AP_Spektrum_Telem::get_singleton();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AP_HAL/AP_HAL.h>
|
||||
|
||||
#ifndef HAL_SPEKTRUM_TELEM_ENABLED
|
||||
#define HAL_SPEKTRUM_TELEM_ENABLED !HAL_MINIMIZE_FEATURES
|
||||
#endif
|
||||
|
||||
#if HAL_SPEKTRUM_TELEM_ENABLED
|
||||
|
||||
#include <AP_Notify/AP_Notify.h>
|
||||
#include <AP_SerialManager/AP_SerialManager.h>
|
||||
#include <AP_HAL/utility/RingBuffer.h>
|
||||
#include "AP_RCTelemetry.h"
|
||||
|
||||
#define UINT8 uint8_t
|
||||
#define UINT16 uint16_t
|
||||
#define UINT32 uint32_t
|
||||
#define UINT64 uint64_t
|
||||
#define INT8 int8_t
|
||||
#define INT16 int16_t
|
||||
#define INT32 int32_t
|
||||
extern "C" {
|
||||
#include "spektrumTelemetrySensors.h"
|
||||
}
|
||||
#undef UINT8
|
||||
#undef UINT16
|
||||
#undef UINT32
|
||||
#undef UINT64
|
||||
#undef INT8
|
||||
#undef INT16
|
||||
#undef INT32
|
||||
|
||||
class AP_Spektrum_Telem : public AP_RCTelemetry {
|
||||
public:
|
||||
AP_Spektrum_Telem();
|
||||
~AP_Spektrum_Telem() override;
|
||||
|
||||
/* Do not allow copies */
|
||||
AP_Spektrum_Telem(const AP_Spektrum_Telem &other) = delete;
|
||||
AP_Spektrum_Telem &operator=(const AP_Spektrum_Telem&) = delete;
|
||||
|
||||
// init - perform required initialisation
|
||||
virtual bool init() override;
|
||||
|
||||
static AP_Spektrum_Telem *get_singleton(void) {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
// get next telemetry data for external consumers of SPort data
|
||||
static bool get_telem_data(uint8_t* data);
|
||||
|
||||
private:
|
||||
|
||||
enum SensorType {
|
||||
QOS,
|
||||
RPM,
|
||||
TEXT,
|
||||
ATTITUDE,
|
||||
GPS_LOC,
|
||||
ESC,
|
||||
ALTITUDE,
|
||||
AIRSPEED,
|
||||
GPS_STATUS,
|
||||
VOLTAGE,
|
||||
AMPS,
|
||||
MAH,
|
||||
TEMP,
|
||||
NUM_SENSORS
|
||||
};
|
||||
|
||||
struct MessageChunk
|
||||
{
|
||||
uint8_t chunk[13]; // a "chunk" (13 characters/bytes) at a time of the queued message to be sent
|
||||
uint8_t linenumber;
|
||||
uint8_t char_index; // index of which character to get in the message
|
||||
uint8_t repeats;
|
||||
} _msg_chunk;
|
||||
|
||||
float _max_speed = 0.0f;
|
||||
float _max_alt = 0.0f;
|
||||
|
||||
// passthrough WFQ scheduler
|
||||
// Text Generator
|
||||
bool get_next_msg_chunk(void) override;
|
||||
bool repeat_msg_chunk(void);
|
||||
void send_msg_chunk(const MessageChunk& message);
|
||||
bool is_packet_ready(uint8_t idx, bool queue_empty) override;
|
||||
void process_packet(uint8_t idx) override;
|
||||
void adjust_packet_weight(bool queue_empty) override;
|
||||
// RxV + flight log data
|
||||
void calc_qos();
|
||||
// High-Voltage sensor
|
||||
void calc_batt_volts(uint8_t instance);
|
||||
// Temperature Sensor
|
||||
void calc_temperature(uint8_t instance);
|
||||
// Amps
|
||||
void calc_batt_amps(uint8_t instance);
|
||||
// Flight Battery Capacity (Dual)
|
||||
void calc_batt_mah();
|
||||
// Altitude (Eagle Tree Sensor)
|
||||
void calc_altitude();
|
||||
// Air Speed (Eagle Tree Sensor)
|
||||
void calc_airspeed();
|
||||
// Attitude and Magnetic Compass
|
||||
void calc_attandmag();
|
||||
// GPS Location Data (Eagle Tree)
|
||||
void calc_gps_location();
|
||||
// GPS Status (Eagle Tree)
|
||||
void calc_gps_status();
|
||||
// Electronic Speed Control
|
||||
void calc_esc();
|
||||
// RPM sensor
|
||||
void calc_rpm();
|
||||
|
||||
// setup ready for passthrough operation
|
||||
void setup_wfq_scheduler(void) override;
|
||||
|
||||
// get next telemetry data for external consumers of SPort data (internal function)
|
||||
bool _get_telem_data(uint8_t* data);
|
||||
|
||||
// all Spektrum telemtry packets are big-endian!
|
||||
PACKED UN_TELEMETRY _telem;
|
||||
bool _telem_pending;
|
||||
|
||||
static AP_Spektrum_Telem *singleton;
|
||||
};
|
||||
|
||||
namespace AP {
|
||||
AP_Spektrum_Telem *spektrum_telem();
|
||||
};
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue