// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- /* Inspired by work done here https://github.com/PX4/Firmware/tree/master/src/drivers/frsky_telemetry from Stefan Rado 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 . */ /* FRSKY Telemetry library */ #include "AP_Frsky_Telem.h" extern const AP_HAL::HAL& hal; AP_Frsky_Telem::msg_t _msg; //constructor AP_Frsky_Telem::AP_Frsky_Telem(AP_AHRS &ahrs, const AP_BattMonitor &battery, const RangeFinder &rng) : _ahrs(ahrs), _battery(battery), _rng(rng), _port(NULL), _protocol(), _initialised_uart(), _crc(0), _ap(), _relative_home_altitude(0.0f), _control_sensors_timer(), _paramID(), _gps(), _passthrough(), _SPort(), _D(), _msg_chunk() {} /* * init - perform required initialisation * for Copter */ void AP_Frsky_Telem::init(const AP_SerialManager &serial_manager, const char *firmware_str, const char *frame_config_str, const uint8_t mav_type, AP_Float *fs_batt_voltage, AP_Float *fs_batt_mah, uint32_t *ap_value, uint32_t *control_sensors_present, uint32_t *control_sensors_enabled, uint32_t *control_sensors_health, int32_t *home_distance, int32_t *home_bearing) { // check for protocol configured for a serial port - only the first serial port with one of these protocols will then run (cannot have FrSky on multiple serial ports) if ((_port = serial_manager.find_serial(AP_SerialManager::SerialProtocol_FrSky_D, 0))) { _protocol = AP_SerialManager::SerialProtocol_FrSky_D; // FrSky D protocol (D-receivers) } else if ((_port = serial_manager.find_serial(AP_SerialManager::SerialProtocol_FrSky_SPort, 0))) { _protocol = AP_SerialManager::SerialProtocol_FrSky_SPort; // FrSky SPort protocol (X-receivers) } else if ((_port = serial_manager.find_serial(AP_SerialManager::SerialProtocol_FrSky_SPort_Passthrough, 0))) { _protocol = AP_SerialManager::SerialProtocol_FrSky_SPort_Passthrough; // FrSky SPort and SPort Passthrough (OpenTX) protocols (X-receivers) // add firmware and frame info to message queue char text[50]; strncpy(text, firmware_str, sizeof(text)); strncat(text, " ", 1); // add a space before adding FRAME_CONFIG_STRING strncat(text, frame_config_str, 10); // FRAME_CONFIG_STRING shouldn't be more that 10 chars (see config.h) queue_message(MAV_SEVERITY_INFO, text); // save main parameters locally _params.mav_type = mav_type; // frame type (see MAV_TYPE in Mavlink definition file common.h) _params.fs_batt_voltage = fs_batt_voltage; // failsafe battery voltage in volts _params.fs_batt_mah = fs_batt_mah; // failsafe reserve capacity in mAh _ap.value = ap_value; // ap bit-field _ap.control_sensors_present = control_sensors_present; _ap.control_sensors_enabled = control_sensors_enabled; _ap.control_sensors_health = control_sensors_health; _ap.home_distance = home_distance; _ap.home_bearing = home_bearing; } if (_port != NULL) { hal.scheduler->register_io_process(FUNCTOR_BIND_MEMBER(&AP_Frsky_Telem::tick, void)); // we don't want flow control for either protocol _port->set_flow_control(AP_HAL::UARTDriver::FLOW_CONTROL_DISABLE); } } /* * init - perform required initialisation * for Plane and Rover */ void AP_Frsky_Telem::init(const AP_SerialManager &serial_manager) { // check for protocol configured for a serial port - only the first serial port with one of these protocols will then run (cannot have FrSky on multiple serial ports) if ((_port = serial_manager.find_serial(AP_SerialManager::SerialProtocol_FrSky_D, 0))) { _protocol = AP_SerialManager::SerialProtocol_FrSky_D; // FrSky D protocol (D-receivers) } else if ((_port = serial_manager.find_serial(AP_SerialManager::SerialProtocol_FrSky_SPort, 0))) { _protocol = AP_SerialManager::SerialProtocol_FrSky_SPort; // FrSky SPort protocol (X-receivers) } if (_port != NULL) { hal.scheduler->register_io_process(FUNCTOR_BIND_MEMBER(&AP_Frsky_Telem::tick, void)); // we don't want flow control for either protocol _port->set_flow_control(AP_HAL::UARTDriver::FLOW_CONTROL_DISABLE); } } /* * send telemetry data * for FrSky SPort Passthrough (OpenTX) protocol (X-receivers) */ void AP_Frsky_Telem::send_SPort_Passthrough(void) { int16_t numc; numc = _port->available(); // check if available is negative if (numc < 0) { return; } // this is the constant for hub data frame if (_port->txspace() < 19) { return; } // keep only the last two bytes of the data found in the serial buffer, as we shouldn't respond to old poll requests uint8_t prev_byte = 0; for (int16_t i = 0; i < numc; i++) { prev_byte = _passthrough.new_byte; _passthrough.new_byte = _port->read(); } if ((prev_byte == START_STOP_SPORT) && (_passthrough.new_byte == SENSOR_ID_28)) { // byte 0x7E is the header of each poll request if (_passthrough.send_attiandrng) { // skip other data, send attitude (roll, pitch) and range only this iteration _passthrough.send_attiandrng = false; // next iteration, check if we should send something other } else { // check if there's other data to send _passthrough.send_attiandrng = true; // next iteration, send attitude b/c it needs frequent updates to remain smooth // build mavlink message queue for control_sensors flags control_sensors_check(); // if there's any message in the queue, start sending them chunk by chunk; three times each chunk if (_msg.sent_idx != _msg.queued_idx) { send_uint32(DIY_FIRST_ID, get_next_msg_chunk()); return; } // send other sensor data if it's time for them, and reset the corresponding timer if sent uint32_t now = AP_HAL::millis(); if (((now - _passthrough.timer_params) > 1000) && (!AP_Notify::flags.armed)) { send_uint32(DIY_FIRST_ID+7, calc_param()); _passthrough.timer_params = AP_HAL::millis(); return; } else if ((now - _passthrough.timer_ap_status) > 500) { send_uint32(DIY_FIRST_ID+1, calc_ap_status()); _passthrough.timer_ap_status = AP_HAL::millis(); return; } else if ((now - _passthrough.timer_batt) > 1000) { send_uint32(DIY_FIRST_ID+3, calc_batt()); _passthrough.timer_batt = AP_HAL::millis(); return; } else if ((now - _passthrough.timer_gps_status) > 1000) { send_uint32(DIY_FIRST_ID+2, calc_gps_status()); _passthrough.timer_gps_status = AP_HAL::millis(); return; } else if ((now - _passthrough.timer_home) > 500) { send_uint32(DIY_FIRST_ID+4, calc_home()); _passthrough.timer_home = AP_HAL::millis(); return; } else if ((now - _passthrough.timer_velandyaw) > 500) { send_uint32(DIY_FIRST_ID+5, calc_velandyaw()); _passthrough.timer_velandyaw = AP_HAL::millis(); return; } else if ((now - _passthrough.timer_gps_latlng) > 1000) { send_uint32(GPS_LONG_LATI_FIRST_ID, calc_gps_latlng(&_passthrough.send_latitude)); // gps latitude or longitude if (!_passthrough.send_latitude) { // we've cycled and sent one each of longitude then latitude, so reset the timer _passthrough.timer_gps_latlng = AP_HAL::millis(); } return; } else if ((now - _passthrough.timer_vario) > 500) { Vector3f velNED; if (_ahrs.get_velocity_NED(velNED)) { send_uint32(VARIO_FIRST_ID, (int32_t)roundf(-velNED.z*100)); // vertical velocity in cm/s, +ve up } _passthrough.timer_vario = AP_HAL::millis(); return; } else if ((now - _passthrough.timer_alt) > 1000) { send_uint32(ALT_FIRST_ID, (int32_t)_relative_home_altitude); // altitude in cm above home position _passthrough.timer_alt = AP_HAL::millis(); return; } else if ((now - _passthrough.timer_vfas) > 1000) { send_uint32(VFAS_FIRST_ID, (uint32_t)roundf(_battery.voltage() * 100.0f)); // battery pack voltage in volts _passthrough.timer_vfas = AP_HAL::millis(); return; } } // if nothing else needed to be sent, send attitude (roll, pitch) and range data send_uint32(DIY_FIRST_ID+6, calc_attiandrng()); } } /* * send telemetry data * for FrSky SPort protocol (X-receivers) */ void AP_Frsky_Telem::send_SPort(void) { int16_t numc; numc = _port->available(); // check if available is negative if (numc < 0) { return; } // this is the constant for hub data frame if (_port->txspace() < 19) { return; } for (int16_t i = 0; i < numc; i++) { int16_t readbyte = _port->read(); if (_SPort.sport_status == false) { if (readbyte == START_STOP_SPORT) { _SPort.sport_status = true; } } else { switch(readbyte) { case SENSOR_ID_FAS: switch (_SPort.fas_call) { case 0: send_uint32(DATA_ID_FUEL, (uint16_t)roundf(_battery.capacity_remaining_pct())); // send battery remaining break; case 1: send_uint32(DATA_ID_VFAS, (uint16_t)roundf(_battery.voltage() * 10.0f)); // send battery voltage break; case 2: send_uint32(DATA_ID_CURRENT, (uint16_t)roundf(_battery.current_amps() * 10.0f)); // send current consumption break; } if (_SPort.fas_call++ > 2) _SPort.fas_call = 0; break; case SENSOR_ID_GPS: switch (_SPort.gps_call) { case 0: calc_gps_position(); // gps data is not recalculated until all of it has been sent send_uint32(DATA_ID_GPS_LAT_BP, _gps.latdddmm); // send gps lattitude degree and minute integer part break; case 1: send_uint32(DATA_ID_GPS_LAT_AP, _gps.latmmmm); // send gps lattitude minutes decimal part break; case 2: send_uint32(DATA_ID_GPS_LAT_NS, _gps.lat_ns); // send gps North / South information break; case 3: send_uint32(DATA_ID_GPS_LONG_BP, _gps.londddmm); // send gps longitude degree and minute integer part break; case 4: send_uint32(DATA_ID_GPS_LONG_AP, _gps.lonmmmm); // send gps longitude minutes decimal part break; case 5: send_uint32(DATA_ID_GPS_LONG_EW, _gps.lon_ew); // send gps East / West information break; case 6: send_uint32(DATA_ID_GPS_SPEED_BP, _gps.speed_in_meter); // send gps speed integer part break; case 7: send_uint32(DATA_ID_GPS_SPEED_AP, _gps.speed_in_centimeter); // send gps speed decimal part break; case 8: send_uint32(DATA_ID_GPS_ALT_BP, _gps.alt_gps_meters); // send gps altitude integer part break; case 9: send_uint32(DATA_ID_GPS_ALT_AP, _gps.alt_gps_cm); // send gps altitude decimals break; case 10: send_uint32(DATA_ID_GPS_COURS_BP, (uint16_t)((_ahrs.yaw_sensor / 100) % 360)); // send heading in degree based on AHRS and not GPS break; } if (_SPort.gps_call++ > 10) _SPort.gps_call = 0; break; case SENSOR_ID_VARIO: switch (_SPort.vario_call) { case 0 : calc_nav_alt(); // nav altitude is not recalculated until all of it has been sent send_uint32(DATA_ID_BARO_ALT_BP, _gps.alt_nav_meters); // send altitude integer part break; case 1: send_uint32(DATA_ID_BARO_ALT_AP, _gps.alt_nav_cm); // send altitude decimal part break; } if (_SPort.vario_call++ > 1) _SPort.vario_call = 0; break; case SENSOR_ID_SP2UR: switch (_SPort.various_call) { case 0 : send_uint32(DATA_ID_TEMP2, (uint16_t)(_ahrs.get_gps().num_sats() * 10 + _ahrs.get_gps().status())); // send GPS status and number of satellites as num_sats*10 + status (to fit into a uint8_t) break; case 1: send_uint32(DATA_ID_TEMP1, _ap.control_mode); // send flight mode break; } if (_SPort.various_call++ > 1) _SPort.various_call = 0; break; } _SPort.sport_status = false; } } } /* * send frame1 and frame2 telemetry data * one frame (frame1) is sent every 200ms with baro alt, nb sats, batt volts and amp, control_mode * a second frame (frame2) is sent every second (1000ms) with gps position data, and ahrs.yaw_sensor heading (instead of GPS heading) * for FrSky D protocol (D-receivers) */ void AP_Frsky_Telem::send_D(void) { uint32_t now = AP_HAL::millis(); // send frame1 every 200ms if (now - _D.last_200ms_frame > 200) { _D.last_200ms_frame = now; send_uint16(DATA_ID_TEMP2, (uint16_t)(_ahrs.get_gps().num_sats() * 10 + _ahrs.get_gps().status())); // send GPS status and number of satellites as num_sats*10 + status (to fit into a uint8_t) send_uint16(DATA_ID_TEMP1, _ap.control_mode); // send flight mode send_uint16(DATA_ID_FUEL, (uint16_t)roundf(_battery.capacity_remaining_pct())); // send battery remaining send_uint16(DATA_ID_VFAS, (uint16_t)roundf(_battery.voltage() * 10.0f)); // send battery voltage send_uint16(DATA_ID_CURRENT, (uint16_t)roundf(_battery.current_amps() * 10.0f)); // send current consumption calc_nav_alt(); send_uint16(DATA_ID_BARO_ALT_BP, _gps.alt_nav_meters); // send nav altitude integer part send_uint16(DATA_ID_BARO_ALT_AP, _gps.alt_nav_cm); // send nav altitude decimal part } // send frame2 every second if (now - _D.last_1000ms_frame > 1000) { _D.last_1000ms_frame = now; send_uint16(DATA_ID_GPS_COURS_BP, (uint16_t)((_ahrs.yaw_sensor / 100) % 360)); // send heading in degree based on AHRS and not GPS calc_gps_position(); if (_ahrs.get_gps().status() >= 3) { send_uint16(DATA_ID_GPS_LAT_BP, _gps.latdddmm); // send gps lattitude degree and minute integer part send_uint16(DATA_ID_GPS_LAT_AP, _gps.latmmmm); // send gps lattitude minutes decimal part send_uint16(DATA_ID_GPS_LAT_NS, _gps.lat_ns); // send gps North / South information send_uint16(DATA_ID_GPS_LONG_BP, _gps.londddmm); // send gps longitude degree and minute integer part send_uint16(DATA_ID_GPS_LONG_AP, _gps.lonmmmm); // send gps longitude minutes decimal part send_uint16(DATA_ID_GPS_LONG_EW, _gps.lon_ew); // send gps East / West information send_uint16(DATA_ID_GPS_SPEED_BP, _gps.speed_in_meter); // send gps speed integer part send_uint16(DATA_ID_GPS_SPEED_AP, _gps.speed_in_centimeter); // send gps speed decimal part send_uint16(DATA_ID_GPS_ALT_BP, _gps.alt_gps_meters); // send gps altitude integer part send_uint16(DATA_ID_GPS_ALT_AP, _gps.alt_gps_cm); // send gps altitude decimal part } } } /* * tick - main call to send data to the receiver (called by scheduler at 1kHz) */ void AP_Frsky_Telem::tick(void) { // check UART has been initialised if (!_initialised_uart) { // initialise uart (this must be called from within tick b/c the UART begin must be called from the same thread as it is used from) if (_protocol == AP_SerialManager::SerialProtocol_FrSky_D) { // FrSky D protocol (D-receivers) _port->begin(AP_SERIALMANAGER_FRSKY_D_BAUD, AP_SERIALMANAGER_FRSKY_BUFSIZE_RX, AP_SERIALMANAGER_FRSKY_BUFSIZE_TX); } else { // FrSky SPort and SPort Passthrough (OpenTX) protocols (X-receivers) _port->begin(AP_SERIALMANAGER_FRSKY_SPORT_BAUD, AP_SERIALMANAGER_FRSKY_BUFSIZE_RX, AP_SERIALMANAGER_FRSKY_BUFSIZE_TX); } _initialised_uart = true;// true when we have detected the protocol and UART has been initialised } if (_protocol == AP_SerialManager::SerialProtocol_FrSky_D) { // FrSky D protocol (D-receivers) send_D(); } else if (_protocol == AP_SerialManager::SerialProtocol_FrSky_SPort) { // FrSky SPort protocol (X-receivers) send_SPort(); } else if (_protocol == AP_SerialManager::SerialProtocol_FrSky_SPort_Passthrough) { // FrSky SPort Passthrough (OpenTX) protocol (X-receivers) send_SPort_Passthrough(); } } /* * build up the frame's crc * for FrSky SPort protocol (X-receivers) */ void AP_Frsky_Telem::calc_crc(uint8_t byte) { _crc += byte; //0-1FF _crc += _crc >> 8; //0-100 _crc &= 0xFF; } /* * send the frame's crc at the end of the frame * for FrSky SPort protocol (X-receivers) */ void AP_Frsky_Telem::send_crc(void) { send_byte(0xFF - _crc); _crc = 0; } /* send 1 byte and do byte stuffing */ void AP_Frsky_Telem::send_byte(uint8_t byte) { if (_protocol == AP_SerialManager::SerialProtocol_FrSky_D) { // FrSky D protocol (D-receivers) if (byte == START_STOP_D) { _port->write(0x5D); _port->write(0x3E); } else if (byte == BYTESTUFF_D) { _port->write(0x5D); _port->write(0x3D); } else { _port->write(byte); } } else { // FrSky SPort protocol (X-receivers) if (byte == START_STOP_SPORT) { _port->write(0x7D); _port->write(0x5E); } else if (byte == BYTESTUFF_SPORT) { _port->write(0x7D); _port->write(0x5D); } else { _port->write(byte); } calc_crc(byte); } } /* * send one uint32 frame of FrSky data - for FrSky SPort protocol (X-receivers) */ void AP_Frsky_Telem::send_uint32(uint16_t id, uint32_t data) { send_byte(0x10); // DATA_FRAME uint8_t *bytes = (uint8_t*)&id; send_byte(bytes[0]); // LSB send_byte(bytes[1]); // MSB bytes = (uint8_t*)&data; send_byte(bytes[0]); // LSB send_byte(bytes[1]); send_byte(bytes[2]); send_byte(bytes[3]); // MSB send_crc(); } /* * send one uint16 frame of FrSky data - for FrSky D protocol (D-receivers) */ void AP_Frsky_Telem::send_uint16(uint16_t id, uint16_t data) { _port->write(START_STOP_D); // send a 0x5E start byte uint8_t *bytes = (uint8_t*)&id; send_byte(bytes[0]); bytes = (uint8_t*)&data; send_byte(bytes[0]); // LSB send_byte(bytes[1]); // MSB } /* * grabs one "chunk" (4 bytes) of the mavlink statustext message to be transmitted * for FrSky SPort Passthrough (OpenTX) protocol (X-receivers) */ uint32_t AP_Frsky_Telem::get_next_msg_chunk(void) { if (_msg_chunk.repeats == 0) { _msg_chunk.chunk = 0; uint8_t character = _msg.data[_msg.sent_idx].text[_msg_chunk.char_index++]; if (character) { _msg_chunk.chunk |= character<<24; character = _msg.data[_msg.sent_idx].text[_msg_chunk.char_index++]; if (character) { _msg_chunk.chunk |= character<<16; character = _msg.data[_msg.sent_idx].text[_msg_chunk.char_index++]; if (character) { _msg_chunk.chunk |= character<<8; character = _msg.data[_msg.sent_idx].text[_msg_chunk.char_index++]; if (character) { _msg_chunk.chunk |= character; } } } } if (!character) { // we've reached the end of the message (string terminated by '\0') _msg_chunk.char_index = 0; // add severity which is sent as the MSB of the last three bytes of the last chunk (bits 24, 16, and 8) since a character is on 7 bits _msg_chunk.chunk |= (_msg.data[_msg.sent_idx].severity & 0x4)<<21; _msg_chunk.chunk |= (_msg.data[_msg.sent_idx].severity & 0x2)<<14; _msg_chunk.chunk |= (_msg.data[_msg.sent_idx].severity & 0x1)<<7; } } _msg_chunk.repeats++; if (_msg_chunk.repeats > 2) { _msg_chunk.repeats = 0; _msg.sent_idx = (_msg.sent_idx + 1) % MSG_BUFFER_LENGTH; } return _msg_chunk.chunk; } /* * add message to message cue for transmission through FrSky link * for FrSky SPort Passthrough (OpenTX) protocol (X-receivers) */ void AP_Frsky_Telem::queue_message(MAV_SEVERITY severity, const char *text) { _msg.data[_msg.queued_idx].severity = severity; strncpy((char *)_msg.data[_msg.queued_idx].text, text, 50); _msg.queued_idx = (_msg.queued_idx + 1) % MSG_BUFFER_LENGTH; } /* * add control_sensors information to message cue, normally passed as sys_status mavlink messages to the GCS, for transmission through FrSky link * for FrSky SPort Passthrough (OpenTX) protocol (X-receivers) */ void AP_Frsky_Telem::control_sensors_check(void) { uint32_t now = AP_HAL::millis(); if ((now - _control_sensors_timer) > 5000) { // prevent repeating any system_status messages unless 5 seconds have passed uint32_t _control_sensors_flags = (*_ap.control_sensors_health ^ *_ap.control_sensors_enabled) & *_ap.control_sensors_present; // only one error is reported at a time (in order of preference). Same setup and displayed messages as Mission Planner. if ((_control_sensors_flags & MAV_SYS_STATUS_SENSOR_GPS) > 0) { queue_message(MAV_SEVERITY_CRITICAL, "Bad GPS Health"); _control_sensors_timer = now; } else if ((_control_sensors_flags & MAV_SYS_STATUS_SENSOR_3D_GYRO) > 0) { queue_message(MAV_SEVERITY_CRITICAL, "Bad Gyro Health"); _control_sensors_timer = now; } else if ((_control_sensors_flags & MAV_SYS_STATUS_SENSOR_3D_ACCEL) > 0) { queue_message(MAV_SEVERITY_CRITICAL, "Bad Accel Health"); _control_sensors_timer = now; } else if ((_control_sensors_flags & MAV_SYS_STATUS_SENSOR_3D_MAG) > 0) { queue_message(MAV_SEVERITY_CRITICAL, "Bad Compass Health"); _control_sensors_timer = now; } else if ((_control_sensors_flags & MAV_SYS_STATUS_SENSOR_ABSOLUTE_PRESSURE) > 0) { queue_message(MAV_SEVERITY_CRITICAL, "Bad Baro Health"); _control_sensors_timer = now; } else if ((_control_sensors_flags & MAV_SYS_STATUS_SENSOR_LASER_POSITION) > 0) { queue_message(MAV_SEVERITY_CRITICAL, "Bad LiDAR Health"); _control_sensors_timer = now; } else if ((_control_sensors_flags & MAV_SYS_STATUS_SENSOR_OPTICAL_FLOW) > 0) { queue_message(MAV_SEVERITY_CRITICAL, "Bad OptFlow Health"); _control_sensors_timer = now; } else if ((_control_sensors_flags & MAV_SYS_STATUS_TERRAIN) > 0) { queue_message(MAV_SEVERITY_CRITICAL, "Bad or No Terrain Data"); _control_sensors_timer = now; } else if ((_control_sensors_flags & MAV_SYS_STATUS_GEOFENCE) > 0) { queue_message(MAV_SEVERITY_CRITICAL, "Geofence Breach"); _control_sensors_timer = now; } else if ((_control_sensors_flags & MAV_SYS_STATUS_AHRS) > 0) { queue_message(MAV_SEVERITY_CRITICAL, "Bad AHRS"); _control_sensors_timer = now; } } } /* * prepare parameter data * for FrSky SPort Passthrough (OpenTX) protocol (X-receivers) */ uint32_t AP_Frsky_Telem::calc_param(void) { uint32_t param = 0; // cycle through paramIDs if (_paramID >= 4) { _paramID = 0; } _paramID++; switch(_paramID) { case 1: param = _params.mav_type; // frame type (see MAV_TYPE in Mavlink definition file common.h) break; case 2: param = (uint32_t)roundf(*_params.fs_batt_voltage * 100.0f); // battery failsafe voltage in centivolts break; case 3: param = (uint32_t)roundf(*_params.fs_batt_mah); // battery failsafe capacity in mAh break; case 4: param = (uint32_t)roundf(_battery.pack_capacity_mah()); // battery pack capacity in mAh as configured by user break; } //Reserve first 8 bits for param ID, use other 24 bits to store parameter value param = (_paramID << 24) | (param & 0xFFFFFF); return param; } /* * prepare gps latitude/longitude data * for FrSky SPort Passthrough (OpenTX) protocol (X-receivers) */ uint32_t AP_Frsky_Telem::calc_gps_latlng(bool *send_latitude) { uint32_t latlng; const Location &loc = _ahrs.get_gps().location(0); // use the first gps instance (same as in send_mavlink_gps_raw) // alternate between latitude and longitude if (*send_latitude == true) { if (loc.lat < 0) { latlng = ((abs(loc.lat)/100)*6) | 0x40000000; } else { latlng = ((abs(loc.lat)/100)*6); } *send_latitude = false; } else { if (loc.lng < 0) { latlng = ((abs(loc.lng)/100)*6) | 0xC0000000; } else { latlng = ((abs(loc.lng)/100)*6) | 0x80000000; } *send_latitude = true; } return latlng; } /* * prepare gps status data * for FrSky SPort Passthrough (OpenTX) protocol (X-receivers) */ uint32_t AP_Frsky_Telem::calc_gps_status(void) { uint32_t gps_status; // number of GPS satellites visible (limit to 15 (0xF) since the value is stored on 4 bits) gps_status = (_ahrs.get_gps().num_sats() < GPS_SATS_LIMIT) ? _ahrs.get_gps().num_sats() : GPS_SATS_LIMIT; // GPS receiver status (limit to 3 (0x3) since the value is stored on 2 bits: NO_GPS = 0, NO_FIX = 1, GPS_OK_FIX_2D = 2, GPS_OK_FIX_3D or GPS_OK_FIX_3D_DGPS or GPS_OK_FIX_3D_RTK = 3) gps_status |= ((_ahrs.get_gps().status() < GPS_STATUS_LIMIT) ? _ahrs.get_gps().status() : GPS_STATUS_LIMIT)<= 3) { Location loc = _ahrs.get_gps().location(); //get gps instance 0 lat = format_gps(fabsf(loc.lat/10000000.0f)); _gps.latdddmm = lat; _gps.latmmmm = (lat - _gps.latdddmm) * 10000; _gps.lat_ns = (loc.lat < 0) ? 'S' : 'N'; lon = format_gps(fabsf(loc.lng/10000000.0f)); _gps.londddmm = lon; _gps.lonmmmm = (lon - _gps.londddmm) * 10000; _gps.lon_ew = (loc.lng < 0) ? 'W' : 'E'; alt = loc.alt * 0.01f; _gps.alt_gps_meters = (int16_t)alt; _gps.alt_gps_cm = (alt - _gps.alt_gps_meters) * 100; speed = _ahrs.get_gps().ground_speed(); _gps.speed_in_meter = speed; _gps.speed_in_centimeter = (speed - _gps.speed_in_meter) * 100; } else { _gps.latdddmm = 0; _gps.latmmmm = 0; _gps.lat_ns = 0; _gps.londddmm = 0; _gps.lonmmmm = 0; _gps.alt_gps_meters = 0; _gps.alt_gps_cm = 0; _gps.speed_in_meter = 0; _gps.speed_in_centimeter = 0; } }