diff --git a/libraries/AP_Hott_Telem/AP_Hott_Telem.cpp b/libraries/AP_Hott_Telem/AP_Hott_Telem.cpp
new file mode 100644
index 0000000000..bd54277351
--- /dev/null
+++ b/libraries/AP_Hott_Telem/AP_Hott_Telem.cpp
@@ -0,0 +1,455 @@
+/*
+ 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 .
+*/
+
+/*
+ Graupner Hott Telemetry library
+ Hott telemetry runs at 19200 8N1 on a non-inverted half-duplex UART
+
+ With thanks to Graupner and betaflight
+*/
+
+#include "AP_Hott_Telem.h"
+
+#if HAL_HOTT_TELEM_ENABLED
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define PROT_BINARY 0x80
+#define PROT_ID_GAM 0x8D
+#define PROT_ID_EAM 0x8E
+#define PROT_ID_GPS 0x8A
+#define PROT_ID_VARIO 0x89
+
+#define BYTE_DELAY_FIRST_US 4000
+#define BYTE_DELAY_US 1200
+
+extern const AP_HAL::HAL& hal;
+
+AP_Hott_Telem *AP_Hott_Telem::singleton;
+
+AP_Hott_Telem::AP_Hott_Telem(void)
+{
+#if CONFIG_HAL_BOARD == HAL_BOARD_SITL
+ if (singleton != nullptr) {
+ AP_HAL::panic("AP_Hott_Telem must be singleton");
+ }
+#endif
+ singleton = this;
+}
+
+/*
+ * initialise uart
+ */
+void AP_Hott_Telem::init()
+{
+ const AP_SerialManager &serial_manager = AP::serialmanager();
+
+ uart = serial_manager.find_serial(AP_SerialManager::SerialProtocol_Hott, 0);
+ if (uart) {
+ // register thread
+ if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_Hott_Telem::loop, void),
+ "Hott",
+ 1024, AP_HAL::Scheduler::PRIORITY_BOOST, 1)) {
+ hal.console->printf("Failed to create Hott thread\n");
+ }
+ }
+}
+
+/*
+ send EAM (Electric Air Model)
+ */
+void AP_Hott_Telem::send_EAM(void)
+{
+ // EAM message
+ struct PACKED {
+ uint8_t start_byte = 0x7C; //#01 start uint8_t
+ uint8_t eam_sensor_id = 0x8E;//#02 EAM sensort id. constat value 0x8e
+ uint8_t warning_beeps;
+ uint8_t sensor_id = 0xE0;
+ uint16_t alarms; //#05 alarm bitmask. Value is displayed inverted
+ uint8_t cell_low[7]; //#07 cell voltage lower value. 0.02V steps, 124=2.48V
+ uint8_t cell_high[7]; //#14 cell voltage high value. 0.02V steps, 124=2.48V
+ uint16_t batt1_voltage; //#21 battery 1 voltage in 100mv steps
+ uint16_t batt2_voltage; //#23 battery 2 voltage in 100mv steps
+ uint8_t temp1; //#25 Temperature sensor 1. 20=0C, 46=26C - offset of 20.
+ uint8_t temp2; //#26 temperature sensor 2
+ uint16_t altitude; //#27 Attitude unit: meters. Value of 500 = 0m
+ uint16_t current; //#29 Current in 0.1A steps
+ uint16_t main_voltage; //#31 Main power voltage (drive) in 0.1V steps
+ uint16_t batt_used; //#33 used battery capacity in 10mAh steps
+ uint16_t climbrate; //#35 climb rate in 0.01m/s. Value of 30000 = 0.00 m/s
+ uint8_t climbrate3s; //#37 climbrate in m/3sec. Value of 120 = 0m/3sec
+ uint16_t rpm; //#38 RPM. Steps: 10 rev/min
+ uint8_t electric_min; //#40 Electric minutes. Time starts when motor current is > 3 A
+ uint8_t electric_sec; //#41
+ uint16_t speed; //#42 speed in km/h. Steps 1km/h
+ uint8_t stop_byte = 0x7D; //#44 stop
+ } msg {};
+
+ const AP_BattMonitor &battery = AP::battery();
+ if (battery.num_instances() > 0) {
+ msg.batt1_voltage = uint16_t(battery.voltage(0) * 10);
+ }
+ if (battery.num_instances() > 1) {
+ msg.batt2_voltage = uint16_t(battery.voltage(1) * 10);
+ }
+ float current;
+ if (battery.current_amps(current)) {
+ msg.current = uint16_t(current * 10);
+ }
+ msg.main_voltage = uint16_t(battery.voltage() * 10);
+ float used_mah;
+ if (battery.consumed_mah(used_mah)) {
+ msg.batt_used = used_mah * 0.1;
+ }
+
+ const AP_Baro &baro = AP::baro();
+ msg.temp1 = uint8_t(baro.get_temperature(0) + 20.5);
+ if (baro.healthy(1)) {
+ msg.temp2 = uint8_t(baro.get_temperature(1) + 20.5);
+ }
+
+ AP_AHRS &ahrs = AP::ahrs();
+ float alt = 0;
+ Vector3f vel;
+ {
+ WITH_SEMAPHORE(ahrs.get_semaphore());
+ ahrs.get_relative_position_D_home(alt);
+ alt = -alt;
+ IGNORE_RETURN(ahrs.get_velocity_NED(vel));
+ }
+ msg.altitude = uint16_t(500.5 + alt);
+
+ msg.climbrate = uint16_t(30000.5 + vel.z * -100);
+ msg.climbrate3s = 120 + vel.z * -3;
+
+ const AP_RPM *rpm = AP::rpm();
+ if (rpm && rpm->enabled(0)) {
+ msg.rpm = rpm->get_rpm(0) / 10;
+ }
+
+ AP_Stats *stats = AP::stats();
+ if (stats) {
+ uint32_t t = stats->get_flight_time_s();
+ msg.electric_min = t / 60U;
+ msg.electric_sec = t % 60U;
+ }
+
+ AP_Airspeed *airspeed = AP_Airspeed::get_singleton();
+ if (airspeed && airspeed->healthy()) {
+ msg.speed = uint16_t(airspeed->get_airspeed() * 3.6 + 0.5);
+ } else {
+ WITH_SEMAPHORE(ahrs.get_semaphore());
+ msg.speed = uint16_t(ahrs.groundspeed() * 3.6 + 0.5);
+ }
+
+ send_packet((const uint8_t *)&msg, sizeof(msg));
+}
+
+/*
+ convert from a GPS lat/lon in decimal degrees to degrees plus decimal minutes
+ */
+void AP_Hott_Telem::GPS_to_DDM(float decimal, uint8_t &sign, uint16_t &dm, uint16_t &sec) const
+{
+ sign = decimal>=0?0:1;
+ decimal = fabsf(decimal);
+ uint8_t deg = uint16_t(decimal);
+ uint8_t min = uint16_t((decimal - deg) * 60);
+ dm = deg*100 + min;
+ sec = (decimal - (deg + min/60.0)) * 60 * 10000 + 0.5;
+}
+
+/*
+ send GPS packet
+ */
+void AP_Hott_Telem::send_GPS(void)
+{
+ // GPS message
+ struct PACKED {
+ uint8_t start_byte = 0x7c; //#01 constant value 0x7c
+ uint8_t gps_sensor_id = 0x8a; //#02 constant value 0x8a
+ uint8_t warning_beeps; //#03
+ uint8_t sensor_id = 0xA0; //#04 constant (?) value 0xa0
+ uint16_t alarm; //#05
+ uint8_t flight_direction; //#07 flight direction in 2 degreees/step (1 = 2degrees);
+ uint16_t gps_speed_kmh; //#08 km/h
+ uint8_t pos_NS; //#10 north = 0, south = 1
+ uint16_t pos_NS_dm; //#11 degree minutes
+ uint16_t pos_NS_sec; //#13 position seconds
+ uint8_t pos_EW; //#15 east = 0, west = 1
+ uint16_t pos_EW_dm; //#16 degree minutes
+ uint16_t pos_EW_sec; //#18 position seconds
+ uint16_t home_distance; //#20 meters
+ uint16_t altitude; //#22 meters. Value of 500 = 0m
+ uint16_t climbrate; //#24 m/s 0.01m/s resolution. Value of 30000 = 0.00 m/s
+ uint8_t climbrate3s; //#26 climbrate in m/3s resolution, value of 120 = 0 m/3s
+ uint8_t gps_satelites; //#27 sat count
+ uint8_t gps_fix_char; //#28 GPS fix character. display, 'D' = DGPS, '2' = 2D, '3' = 3D, '-' = no fix
+ uint8_t home_direction; //#29 direction from starting point to Model position (2 degree steps)
+ int16_t vel_north; //#30 velocity north mm/s
+ uint8_t speed_acc; //#32 speed accuracy cm/s
+ uint8_t gps_time_h; //#33 UTC time hours
+ uint8_t gps_time_m; //#34 UTC time minutes
+ uint8_t gps_time_s; //#35 UTC time seconds
+ uint8_t gps_time_hs; //#36 UTC time 0.01s units
+ int16_t vel_east; //#37 velocity north mm/s
+ uint8_t horiz_acc; //#39 horizontal accuracy
+ char free_char[3]; //#40 displayed to right of home
+ uint8_t version = 1; //#43 0: GPS Graupner #33600, 1: ArduPilot
+ uint8_t stop_byte = 0x7d; //#44
+ } msg {};
+
+ AP_GPS &gps = AP::gps();
+ Location loc;
+
+ {
+ WITH_SEMAPHORE(gps.get_semaphore());
+ loc = gps.location();
+ msg.flight_direction = uint16_t(gps.ground_course() * 0.5 + 0.5);
+ msg.gps_speed_kmh = uint16_t(gps.ground_speed() * 3.6 + 0.5);
+ float sacc, hacc;
+ if (gps.speed_accuracy(sacc)) {
+ msg.speed_acc = sacc * 100 + 0.5;
+ }
+ if (gps.horizontal_accuracy(hacc)) {
+ msg.horiz_acc = hacc * 100 + 0.5;
+ }
+ msg.gps_satelites = gps.num_sats();
+ }
+
+ float lat = loc.lat * 1.0e-7;
+ float lon = loc.lng * 1.0e-7;
+
+ uint16_t dm, sec;
+ GPS_to_DDM(lat, msg.pos_NS, dm, sec);
+ msg.pos_NS_dm = dm;
+ msg.pos_NS_sec = sec;
+
+ GPS_to_DDM(lon, msg.pos_EW, dm, sec);
+ msg.pos_EW_dm = dm;
+ msg.pos_EW_sec = sec;
+
+ AP_AHRS &ahrs = AP::ahrs();
+ Vector2f home_vec;
+ float alt = 0;
+ Vector3f vel;
+ {
+ WITH_SEMAPHORE(ahrs.get_semaphore());
+ if (ahrs.get_relative_position_NE_home(home_vec)) {
+ msg.home_distance = home_vec.length();
+ }
+ ahrs.get_relative_position_D_home(alt);
+ alt = -alt;
+ IGNORE_RETURN(ahrs.get_velocity_NED(vel));
+ }
+
+ msg.climbrate = uint16_t(30000.5 + vel.z * -100);
+ msg.climbrate3s = 120 + vel.z * -3;
+ msg.vel_north = vel.x * 1000 + 0.5;
+ msg.vel_east = vel.y * 1000 + 0.5;
+ msg.altitude = uint16_t(500.5 + alt);
+
+ switch (gps.status()) {
+ case AP_GPS::NO_GPS:
+ case AP_GPS::NO_FIX:
+ msg.gps_fix_char = '-';
+ break;
+ case AP_GPS::GPS_OK_FIX_2D:
+ msg.gps_fix_char = '2';
+ break;
+ default:
+ msg.gps_fix_char = '3';
+ break;
+ }
+
+ msg.home_direction = degrees(atan2f(home_vec.y, home_vec.x)) * 0.5 + 0.5;
+
+ AP_RTC &rtc = AP::rtc();
+ {
+ WITH_SEMAPHORE(rtc.get_semaphore());
+ uint16_t ms;
+ rtc.get_system_clock_utc(msg.gps_time_h, msg.gps_time_m, msg.gps_time_s, ms);
+ }
+
+ send_packet((const uint8_t *)&msg, sizeof(msg));
+}
+
+/*
+ send Vario
+ */
+void AP_Hott_Telem::send_Vario(void)
+{
+ // Vario message
+ struct PACKED {
+ uint8_t start_byte = 0x7C; //#01 start uint8_t
+ uint8_t vario_id = 0x89; //#02 ID
+ uint8_t warning_beeps; //#03 warnings
+ uint8_t sensor_id = 0x90; //#04 sensor ID
+ uint8_t inv_status; //#05 status
+ uint16_t altitude; //#06 Attitude meters. Value of 500 = 0m
+ uint16_t altitude_max; //#08 Attitude max meters. Value of 500 = 0m
+ uint16_t altitude_min; //#10 Attitude min meters. Value of 500 = 0m
+ uint16_t climbrate; //#12 climb rate in 0.01m/s. Value of 30000 = 0.00 m/s
+ uint16_t climbrate3s; //#14 climb rate in meters per 3s Value of 30000 = 0.00 m/s
+ uint16_t climbrate10s; //#16 climb rate in meters per 10s. Value of 30000 = 0.00 m/s
+ char text[3][7]; //#18 #Text display
+ char ascii3[3]; //#39 3 extra characters
+ uint8_t yaw; //#42 yaw in 2 degree units, 0 = north
+ uint8_t version = 1; //#43 protocol version
+ uint8_t stop_byte = 0x7D; //#44 stop
+ } msg {};
+
+ AP_AHRS &ahrs = AP::ahrs();
+ Vector3f vel;
+ float alt = 0;
+ {
+ WITH_SEMAPHORE(ahrs.get_semaphore());
+ ahrs.get_relative_position_D_home(alt);
+ alt = -alt;
+ IGNORE_RETURN(ahrs.get_velocity_NED(vel));
+ msg.yaw = wrap_360_cd(ahrs.yaw_sensor) * 0.005;
+ }
+
+ min_alt = MIN(alt, min_alt);
+ max_alt = MAX(alt, max_alt);
+
+ msg.altitude = uint16_t(500.5 + alt);
+ msg.altitude_max = uint16_t(500.5 + max_alt);
+ msg.altitude_min = uint16_t(500.5 + min_alt);
+
+ msg.climbrate = 30000.5 + vel.z * -100;
+ msg.climbrate3s = 30000.5 + vel.z * -100*3;
+ msg.climbrate10s = 30000.5 + vel.z * -100*10;
+
+ AP_Notify *notify = AP_Notify::get_singleton();
+ char fltmode[5] {};
+ if (notify) {
+ strncpy(fltmode, notify->get_flight_mode_str(), sizeof(fltmode));
+ strncpy(msg.text[0], fltmode, sizeof(msg.text[0]));
+ }
+ if (hal.util->get_soft_armed()) {
+ strncpy(msg.text[1], "ARMED", sizeof(msg.text[1]));
+ if (strncmp(fltmode, "AUTO", sizeof(fltmode)) == 0) {
+ const AP_Mission *mission = AP::mission();
+ if (mission) {
+ char wp[10] {};
+ snprintf(wp, sizeof(wp), "WP %3u", mission->get_current_nav_index());
+ memcpy(msg.text[2], wp, sizeof(msg.text[2]));
+ }
+ }
+ } else {
+ strncpy(msg.text[1], "DISARM", sizeof(msg.text[1]));
+ if (AP_Notify::flags.pre_arm_check) {
+ strncpy(msg.text[2], "CK:PASS", sizeof(msg.text[2])+1);
+ } else {
+ strncpy(msg.text[2], "CK:FAIL", sizeof(msg.text[2])+1);
+ }
+ }
+
+ send_packet((const uint8_t *)&msg, sizeof(msg));
+}
+
+/*
+ send a packet out
+ */
+void AP_Hott_Telem::send_packet(const uint8_t *b, uint8_t len)
+{
+ // initial delay
+ hal.scheduler->delay_microseconds(BYTE_DELAY_FIRST_US);
+ uint8_t crc = 0;
+ while (len) {
+ uint8_t ob = *b;
+ if (uart->write(ob) == 1) {
+ len--;
+ crc += ob;
+ b++;
+ hal.scheduler->delay_microseconds(BYTE_DELAY_US);
+ } else {
+ hal.scheduler->delay_microseconds(100);
+ }
+ }
+ uart->write(crc);
+
+ // discard any bytes received during the send
+ hal.scheduler->delay_microseconds(BYTE_DELAY_US*2);
+ while (uart->available() != 0) {
+ uart->read();
+ hal.scheduler->delay_microseconds(100);
+ }
+}
+
+/*
+ thread to process requests
+ */
+void AP_Hott_Telem::loop(void)
+{
+ uart->begin(19200, 10, 10);
+ uart->set_unbuffered_writes(true);
+ uart->set_blocking_writes(true);
+
+ while (true) {
+ hal.scheduler->delay_microseconds(1500);
+ uint32_t n = uart->available();
+ if (n < 2) {
+ // wait for 2 bytes
+ continue;
+ }
+ if (n > 2) {
+ while (n--) {
+ uart->read();
+ hal.scheduler->delay_microseconds(100);
+ }
+ continue;
+ }
+
+ const uint8_t prot_type = uart->read();
+ const uint8_t sensor_id = uart->read();
+ if (prot_type != PROT_BINARY) {
+ // only do binary protocol for now
+ continue;
+ }
+
+ switch (sensor_id) {
+ case PROT_ID_EAM:
+ send_EAM();
+ break;
+ case PROT_ID_GPS:
+ send_GPS();
+ break;
+ case PROT_ID_VARIO:
+ send_Vario();
+ break;
+ }
+ }
+}
+
+namespace AP {
+ AP_Hott_Telem *hott_telem() {
+ return AP_Hott_Telem::get_singleton();
+ }
+};
+
+#endif // HAL_HOTT_TELEM_ENABLED
diff --git a/libraries/AP_Hott_Telem/AP_Hott_Telem.h b/libraries/AP_Hott_Telem/AP_Hott_Telem.h
new file mode 100644
index 0000000000..606c4bc5a5
--- /dev/null
+++ b/libraries/AP_Hott_Telem/AP_Hott_Telem.h
@@ -0,0 +1,58 @@
+/*
+ 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 .
+*/
+#pragma once
+
+#include
+#include
+
+#ifndef HAL_HOTT_TELEM_ENABLED
+#define HAL_HOTT_TELEM_ENABLED !HAL_MINIMIZE_FEATURES
+#endif
+
+#if HAL_HOTT_TELEM_ENABLED
+class AP_Hott_Telem {
+public:
+ AP_Hott_Telem();
+
+ /* Do not allow copies */
+ AP_Hott_Telem(const AP_Hott_Telem &other) = delete;
+ AP_Hott_Telem &operator=(const AP_Hott_Telem&) = delete;
+
+ static AP_Hott_Telem *get_singleton(void) {
+ return singleton;
+ }
+
+ // initialise
+ void init(void);
+
+private:
+ static AP_Hott_Telem *singleton;
+
+ AP_HAL::UARTDriver *uart;
+
+ void loop(void);
+ void send_EAM();
+ void send_GPS();
+ void send_Vario();
+ void send_packet(const uint8_t *b, uint8_t len);
+ void GPS_to_DDM(float decimal, uint8_t &sign, uint16_t &dm, uint16_t &sec) const;
+
+ float min_alt, max_alt;
+};
+
+namespace AP {
+ AP_Hott_Telem *hott_telem();
+};
+#endif // HAL_HOTT_TELEM_ENABLED