/** ****************************************************************************** * @addtogroup OpenPilotModules OpenPilot Modules * @{ * @addtogroup UAVOHoTTBridge UAVO to HoTT Bridge Telemetry Module * @{ * * @file uavohottbridge.c * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2017-2019. * Tau Labs, http://taulabs.org, Copyright (C) 2013-2014 * @brief sends telemery data on HoTT request * * @see The GNU Public License (GPL) Version 3 * *****************************************************************************/ /* * 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, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Additional note on redistribution: The copyright and license notices above * must be maintained in each individual source file that is a derivative work * of this source file; otherwise redistribution is prohibited. */ #include "openpilot.h" #include "hwsettings.h" #include "taskinfo.h" #include "callbackinfo.h" #include "hottbridgesettings.h" #include "attitudestate.h" #include "accelstate.h" #include "barosensor.h" #include "flightbatterystate.h" #include "flightstatus.h" #include "gyrosensor.h" #include "gpspositionsensor.h" #include "gpstime.h" #include "airspeedstate.h" #include "homelocation.h" #include "positionstate.h" #include "systemalarms.h" #include "velocitystate.h" #include "hottbridgestatus.h" #include "hottbridgesettings.h" #include "revosettings.h" #include "gpssettings.h" #include "homelocation.h" #include "objectpersistence.h" #include "pios_sensors.h" #include "uavohottbridge.h" #include "pios_board_io.h" #if defined(PIOS_INCLUDE_HOTT_BRIDGE) #if defined(PIOS_HoTT_STACK_SIZE) #define STACK_SIZE_BYTES PIOS_HoTT_STACK_SIZE #else #define STACK_SIZE_BYTES 2048 #endif #define TASK_PRIORITY CALLBACK_TASK_AUXILIARY static bool module_enabled = false; // Private variables static bool module_enabled; static struct telemetrydata *telestate; static HoTTBridgeStatusData status; // Private functions static void uavoHoTTBridgeTask(void *parameters); static uint16_t build_VARIO_message(struct hott_vario_message *msg); static uint16_t build_GPS_message(struct hott_gps_message *msg); static uint16_t build_GAM_message(struct hott_gam_message *msg); static uint16_t build_EAM_message(struct hott_eam_message *msg); static uint16_t build_ESC_message(struct hott_esc_message *msg); static bool build_TEXT_message(struct hott_text_message *msg, uint8_t page, uint8_t current_line, int8_t value_change, uint8_t step_change, bool edit_line, bool exit_menu); static char *reverse_pixels(char *line, uint8_t from_char, uint8_t to_char); static uint8_t get_page(uint8_t page, bool next); static int16_t get_new_value(int16_t current_value, int8_t value_change, uint8_t step, int16_t min, int16_t max); static uint8_t calc_checksum(uint8_t *data, uint16_t size); static uint8_t generate_warning(); static void store_settings(uint8_t page); static void update_telemetrydata(); static void convert_long2gps(int32_t value, uint8_t *dir, uword_t *min, uword_t *sec); static uint8_t scale_float2uint8(float value, float scale, float offset); static int8_t scale_float2int8(float value, float scale, float offset); static uword_t scale_float2uword(float value, float scale, float offset); /** * Module start routine automatically called after initialization routine * @return 0 when was successful */ static int32_t uavoHoTTBridgeStart(void) { status.TxPackets = 0; status.RxPackets = 0; status.TxFail = 0; status.RxFail = 0; // Start task if (module_enabled) { xTaskHandle taskHandle; xTaskCreate(uavoHoTTBridgeTask, "uavoHoTTBridge", STACK_SIZE_BYTES / 4, NULL, TASK_PRIORITY, &taskHandle); PIOS_TASK_MONITOR_RegisterTask(TASKINFO_RUNNING_UAVOHOTTBRIDGE, taskHandle); } return 0; } /** * Module initialization routine * @return 0 when initialization was successful */ static int32_t uavoHoTTBridgeInitialize(void) { if (PIOS_COM_HOTT) { module_enabled = true; // HoTT telemetry baudrate is fixed to 19200 PIOS_COM_ChangeBaud(PIOS_COM_HOTT, 19200); bool param = true; PIOS_COM_Ioctl(PIOS_COM_HOTT, PIOS_IOCTL_USART_SET_HALFDUPLEX, ¶m); HoTTBridgeStatusInitialize(); // allocate memory for telemetry data telestate = (struct telemetrydata *)pios_malloc(sizeof(*telestate)); if (telestate == NULL) { // there is not enough free memory. the module could not run. module_enabled = false; return -1; } } else { module_enabled = false; } return 0; } MODULE_INITCALL(uavoHoTTBridgeInitialize, uavoHoTTBridgeStart); /** * Main task. It does not return. */ static void uavoHoTTBridgeTask(__attribute__((unused)) void *parameters) { uint8_t rx_buffer[2]; uint8_t tx_buffer[HOTT_MAX_MESSAGE_LENGTH]; uint16_t message_size; // clear all state values memset(telestate, 0, sizeof(*telestate)); // initialize timer variables portTickType lastSysTime = xTaskGetTickCount(); // idle delay between telemetry request and answer uint32_t idledelay = IDLE_TIME; // data delay between transmitted bytes uint32_t datadelay = DATA_TIME; // work on hott telemetry. endless loop. while (1) { // clear message size on every loop before processing message_size = 0; // shift receiver buffer. make room for one byte. rx_buffer[1] = rx_buffer[0]; // wait for a byte of telemetry request in data delay interval while (PIOS_COM_ReceiveBuffer(PIOS_COM_HOTT, rx_buffer, 1, 0) == 0) { vTaskDelayUntil(&lastSysTime, datadelay / portTICK_RATE_MS); } // set start trigger point lastSysTime = xTaskGetTickCount(); // examine received data stream if (rx_buffer[1] == HOTT_BINARY_ID) { // first received byte looks like a binary request. check second received byte for a sensor id. switch (rx_buffer[0]) { case HOTT_VARIO_ID: message_size = build_VARIO_message((struct hott_vario_message *)tx_buffer); break; case HOTT_GPS_ID: message_size = build_GPS_message((struct hott_gps_message *)tx_buffer); break; case HOTT_GAM_ID: message_size = build_GAM_message((struct hott_gam_message *)tx_buffer); break; case HOTT_EAM_ID: message_size = build_EAM_message((struct hott_eam_message *)tx_buffer); break; case HOTT_ESC_ID: message_size = build_ESC_message((struct hott_esc_message *)tx_buffer); break; default: message_size = 0; } } else if (rx_buffer[1] == HOTT_TEXT_ID) { // first received byte looks like a text request. check second received byte for a valid button. uint8_t id_key = rx_buffer[0] & 0x0F; uint8_t id_sensor = rx_buffer[0] >> 4; static bool edit_line = false; bool exit_menu = false; bool is_saved = false; int8_t value_change = 0; static uint8_t step_change = 0; // define allowed edited lines for Main, GPS config, Vario, Gps, General, Electric and Esc pages uint8_t min_line[] = { 2, 2, 2, 2, 2, 2, 2 }; uint8_t max_line[] = { 6, 5, 7, 7, 7, 7, 7 }; static uint8_t page = HOTTTEXT_PAGE_MAIN; static uint8_t last_page = 0; static uint8_t current_line = 0; // select page to display from text request switch (id_sensor) { case (HOTT_VARIO_ID & 0x0f): page = HOTTTEXT_PAGE_VARIO; break; case (HOTT_GPS_ID & 0x0f): page = HOTTTEXT_PAGE_GPS; break; case (HOTT_GAM_ID & 0x0f): page = HOTTTEXT_PAGE_GENERAL; break; case (HOTT_EAM_ID & 0x0f): page = HOTTTEXT_PAGE_ELECTRIC; break; case (HOTT_ESC_ID & 0x0f): page = HOTTTEXT_PAGE_ESC; } // menu keys if (edit_line) { switch (id_key) { case HOTT_KEY_DEC: // up value_change--; break; case HOTT_KEY_INC: // down value_change++; break; case HOTT_KEY_PREV: // left if (step_change < 3) { step_change++; } break; case HOTT_KEY_NEXT: // right if (step_change > 0) { step_change--; } break; case HOTT_KEY_SET: // Set value_change = 0; step_change = 0; if (!is_saved) { store_settings(page); edit_line = false; // exit edit mode } break; default: value_change = 0; } } else { switch (id_key) { case HOTT_KEY_DEC: // up current_line--; break; case HOTT_KEY_INC: // down current_line++; break; case HOTT_KEY_PREV: // left exit_menu = (page == HOTTTEXT_PAGE_MAIN) ? true : false; page = get_page(page, false); break; case HOTT_KEY_NEXT: // right exit_menu = false; current_line = 0; page = get_page(page, true); break; case HOTT_KEY_SET: // Set edit_line = true; // enter edit mode break; } } // new page if (page != last_page) { last_page = page; current_line = 0; edit_line = false; } // keep current line between min/max limits if (current_line > max_line[page]) { current_line = max_line[page]; } if (current_line < min_line[page]) { current_line = min_line[page]; } status.RxFail = step_change; status.TxFail = value_change; is_saved = build_TEXT_message((struct hott_text_message *)tx_buffer, page, current_line, value_change, step_change, edit_line, exit_menu); message_size = sizeof(tx_buffer); if (is_saved) { // is already saved, exit edit mode edit_line = false; } } // check if a message is in the transmit buffer. if (message_size > 0) { status.RxPackets++; // check idle line before transmit. pause, then check receiver buffer vTaskDelayUntil(&lastSysTime, idledelay / portTICK_RATE_MS); if (PIOS_COM_ReceiveBuffer(PIOS_COM_HOTT, rx_buffer, 1, 0) == 0) { // nothing received means idle line. ready to transmit the requested message for (int i = 0; i < message_size; i++) { // send message content with pause between each byte PIOS_COM_SendCharNonBlocking(PIOS_COM_HOTT, tx_buffer[i]); // grab possible incoming loopback data and throw it away PIOS_COM_ReceiveBuffer(PIOS_COM_HOTT, rx_buffer, sizeof(rx_buffer), 0); vTaskDelayUntil(&lastSysTime, datadelay / portTICK_RATE_MS); } status.TxPackets++; // after transmitting the message, any loopback data needs to be cleaned up. vTaskDelayUntil(&lastSysTime, idledelay / portTICK_RATE_MS); PIOS_COM_ReceiveBuffer(PIOS_COM_HOTT, tx_buffer, message_size, 0); } else { status.RxFail++; } HoTTBridgeStatusSet(&status); } } } /** * Build requested answer messages. * \return value sets message size */ uint16_t build_VARIO_message(struct hott_vario_message *msg) { update_telemetrydata(); if (telestate->Settings.Sensor.VARIO == HOTTBRIDGESETTINGS_SENSOR_DISABLED) { return 0; } // clear message buffer memset(msg, 0, sizeof(*msg)); // message header msg->start = HOTT_START; msg->stop = HOTT_STOP; msg->sensor_id = HOTT_VARIO_ID; msg->warning = generate_warning(); msg->sensor_text_id = HOTT_VARIO_TEXT_ID; // alarm inverse bits. invert display areas on limits msg->alarm_inverse |= (telestate->Settings.Limit.MinHeight > telestate->altitude) ? VARIO_INVERT_ALT : 0; msg->alarm_inverse |= (telestate->Settings.Limit.MaxHeight < telestate->altitude) ? VARIO_INVERT_ALT : 0; msg->alarm_inverse |= (telestate->Settings.Limit.MaxHeight < telestate->altitude) ? VARIO_INVERT_MAX : 0; msg->alarm_inverse |= (telestate->Settings.Limit.MinHeight > telestate->altitude) ? VARIO_INVERT_MIN : 0; msg->alarm_inverse |= (telestate->Settings.Limit.NegDifference1 > telestate->climbrate1s) ? VARIO_INVERT_CR1S : 0; msg->alarm_inverse |= (telestate->Settings.Limit.PosDifference1 < telestate->climbrate1s) ? VARIO_INVERT_CR1S : 0; msg->alarm_inverse |= (telestate->Settings.Limit.NegDifference2 > telestate->climbrate3s) ? VARIO_INVERT_CR3S : 0; msg->alarm_inverse |= (telestate->Settings.Limit.PosDifference2 < telestate->climbrate3s) ? VARIO_INVERT_CR3S : 0; msg->alarm_inverse |= (telestate->Settings.Limit.NegDifference2 > telestate->climbrate10s) ? VARIO_INVERT_CR10S : 0; msg->alarm_inverse |= (telestate->Settings.Limit.PosDifference2 < telestate->climbrate10s) ? VARIO_INVERT_CR10S : 0; // altitude relative to ground msg->altitude = scale_float2uword(telestate->altitude, 1, OFFSET_ALTITUDE); msg->min_altitude = scale_float2uword(telestate->min_altitude, 1, OFFSET_ALTITUDE); msg->max_altitude = scale_float2uword(telestate->max_altitude, 1, OFFSET_ALTITUDE); // climbrate msg->climbrate = scale_float2uword(telestate->climbrate1s, M_TO_CM, OFFSET_CLIMBRATE); msg->climbrate3s = scale_float2uword(telestate->climbrate3s, M_TO_CM, OFFSET_CLIMBRATE); msg->climbrate10s = scale_float2uword(telestate->climbrate10s, M_TO_CM, OFFSET_CLIMBRATE); // compass msg->compass = scale_float2int8(telestate->Attitude.Yaw, DEG_TO_UINT, 0); // statusline memcpy(msg->ascii, telestate->statusline, sizeof(msg->ascii)); // free display characters msg->ascii1 = 0; msg->ascii2 = 0; msg->ascii3 = 0; msg->checksum = calc_checksum((uint8_t *)msg, sizeof(*msg)); return sizeof(*msg); } uint16_t build_GPS_message(struct hott_gps_message *msg) { update_telemetrydata(); if (telestate->Settings.Sensor.GPS == HOTTBRIDGESETTINGS_SENSOR_DISABLED) { return 0; } // clear message buffer memset(msg, 0, sizeof(*msg)); // message header msg->start = HOTT_START; msg->stop = HOTT_STOP; msg->sensor_id = HOTT_GPS_ID; msg->warning = generate_warning(); msg->sensor_text_id = HOTT_GPS_TEXT_ID; // alarm inverse bits. invert display areas on limits msg->alarm_inverse1 |= (telestate->Settings.Limit.MaxDistance < telestate->homedistance) ? GPS_INVERT_HDIST : 0; msg->alarm_inverse1 |= (telestate->Settings.Limit.MinSpeed > telestate->GPS.Groundspeed) ? GPS_INVERT_SPEED : 0; msg->alarm_inverse1 |= (telestate->Settings.Limit.MaxSpeed < telestate->GPS.Groundspeed) ? GPS_INVERT_SPEED : 0; msg->alarm_inverse1 |= (telestate->Settings.Limit.MinHeight > telestate->altitude) ? GPS_INVERT_ALT : 0; msg->alarm_inverse1 |= (telestate->Settings.Limit.MaxHeight < telestate->altitude) ? GPS_INVERT_ALT : 0; msg->alarm_inverse1 |= (telestate->Settings.Limit.NegDifference1 > telestate->climbrate1s) ? GPS_INVERT_CR1S : 0; msg->alarm_inverse1 |= (telestate->Settings.Limit.PosDifference1 < telestate->climbrate1s) ? GPS_INVERT_CR1S : 0; msg->alarm_inverse1 |= (telestate->Settings.Limit.NegDifference2 > telestate->climbrate3s) ? GPS_INVERT_CR3S : 0; msg->alarm_inverse1 |= (telestate->Settings.Limit.PosDifference2 < telestate->climbrate3s) ? GPS_INVERT_CR3S : 0; msg->alarm_inverse2 |= (telestate->SysAlarms.Alarm.GPS != SYSTEMALARMS_ALARM_OK) ? GPS_INVERT2_POS : 0; // gps direction, groundspeed and postition msg->flight_direction = scale_float2uint8(telestate->GPS.Heading, DEG_TO_UINT, 0); msg->gps_speed = scale_float2uword(telestate->GPS.Groundspeed, MS_TO_KMH, 0); convert_long2gps(telestate->GPS.Latitude, &msg->latitude_ns, &msg->latitude_min, &msg->latitude_sec); convert_long2gps(telestate->GPS.Longitude, &msg->longitude_ew, &msg->longitude_min, &msg->longitude_sec); // homelocation distance, course and state msg->distance = scale_float2uword(telestate->homedistance, 1, 0); msg->home_direction = scale_float2uint8(telestate->homecourse, DEG_TO_UINT, 0); msg->ascii5 = (telestate->Home.Set ? 'H' : '-'); // altitude relative to ground and climb rate msg->altitude = scale_float2uword(telestate->altitude, 1, OFFSET_ALTITUDE); msg->climbrate = scale_float2uword(telestate->climbrate1s, M_TO_CM, OFFSET_CLIMBRATE); msg->climbrate3s = scale_float2uint8(telestate->climbrate3s, 1, OFFSET_CLIMBRATE3S); // number of satellites,gps fix and state msg->gps_num_sat = telestate->GPS.Satellites; switch (telestate->GPS.Status) { case GPSPOSITIONSENSOR_STATUS_FIX2D: msg->gps_fix_char = '2'; break; case GPSPOSITIONSENSOR_STATUS_FIX3D: case GPSPOSITIONSENSOR_STATUS_FIX3DDGNSS: msg->gps_fix_char = '3'; break; default: msg->gps_fix_char = 0; } switch (telestate->SysAlarms.Alarm.GPS) { case SYSTEMALARMS_ALARM_UNINITIALISED: msg->ascii6 = 0; // if there is no gps, show compass flight direction msg->flight_direction = scale_float2int8((telestate->Attitude.Yaw > 0) ? telestate->Attitude.Yaw : 360 + telestate->Attitude.Yaw, DEG_TO_UINT, 0); break; case SYSTEMALARMS_ALARM_OK: msg->ascii6 = '.'; break; case SYSTEMALARMS_ALARM_WARNING: msg->ascii6 = '?'; break; case SYSTEMALARMS_ALARM_ERROR: case SYSTEMALARMS_ALARM_CRITICAL: msg->ascii6 = '!'; break; default: msg->ascii6 = 0; } // model angles msg->angle_roll = scale_float2int8(telestate->Attitude.Roll, DEG_TO_UINT, 0); msg->angle_nick = scale_float2int8(telestate->Attitude.Pitch, DEG_TO_UINT, 0); msg->angle_compass = scale_float2int8(telestate->Attitude.Yaw, DEG_TO_UINT, 0); // gps time msg->gps_hour = telestate->GPStime.Hour; msg->gps_min = telestate->GPStime.Minute; msg->gps_sec = telestate->GPStime.Second; msg->gps_msec = 0; // gps MSL (NN) altitude MSL msg->msl = scale_float2uword(telestate->GPS.Altitude, 1, 0); // free display chararacter msg->ascii4 = 0; msg->checksum = calc_checksum((uint8_t *)msg, sizeof(*msg)); return sizeof(*msg); } uint16_t build_GAM_message(struct hott_gam_message *msg) { update_telemetrydata(); if (telestate->Settings.Sensor.GAM == HOTTBRIDGESETTINGS_SENSOR_DISABLED) { return 0; } // clear message buffer memset(msg, 0, sizeof(*msg)); // message header msg->start = HOTT_START; msg->stop = HOTT_STOP; msg->sensor_id = HOTT_GAM_ID; msg->warning = generate_warning(); msg->sensor_text_id = HOTT_GAM_TEXT_ID; // alarm inverse bits. invert display areas on limits msg->alarm_inverse2 |= (telestate->Settings.Limit.MaxCurrent < telestate->Battery.Current) ? GAM_INVERT2_CURRENT : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.MinPowerVoltage > telestate->Battery.Voltage) ? GAM_INVERT2_VOLTAGE : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.MaxPowerVoltage < telestate->Battery.Voltage) ? GAM_INVERT2_VOLTAGE : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.MinHeight > telestate->altitude) ? GAM_INVERT2_ALT : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.MaxHeight < telestate->altitude) ? GAM_INVERT2_ALT : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.NegDifference1 > telestate->climbrate1s) ? GAM_INVERT2_CR1S : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.PosDifference1 < telestate->climbrate1s) ? GAM_INVERT2_CR1S : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.NegDifference2 > telestate->climbrate3s) ? GAM_INVERT2_CR3S : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.PosDifference2 < telestate->climbrate3s) ? GAM_INVERT2_CR3S : 0; // temperatures msg->temperature1 = scale_float2uint8(telestate->Gyro.temperature, 1, OFFSET_TEMPERATURE); msg->temperature2 = scale_float2uint8(telestate->Baro.Temperature, 1, OFFSET_TEMPERATURE); // altitude msg->altitude = scale_float2uword(telestate->altitude, 1, OFFSET_ALTITUDE); // climbrate msg->climbrate = scale_float2uword(telestate->climbrate1s, M_TO_CM, OFFSET_CLIMBRATE); msg->climbrate3s = scale_float2uint8(telestate->climbrate3s, 1, OFFSET_CLIMBRATE3S); // main battery float voltage = (telestate->Battery.Voltage > 0) ? telestate->Battery.Voltage : 0; float current = (telestate->Battery.Current > 0) ? telestate->Battery.Current : 0; float energy = (telestate->Battery.ConsumedEnergy > 0) ? telestate->Battery.ConsumedEnergy : 0; msg->voltage = scale_float2uword(voltage, 10, 0); msg->current = scale_float2uword(current, 10, 0); msg->capacity = scale_float2uword(energy, 0.1f, 0); // simulate individual cell voltage uint8_t cell_voltage = (telestate->Battery.Voltage > 0) ? scale_float2uint8(telestate->Battery.Voltage / telestate->Battery.NbCells, 50, 0) : 0; msg->cell1 = (telestate->Battery.NbCells >= 1) ? cell_voltage : 0; msg->cell2 = (telestate->Battery.NbCells >= 2) ? cell_voltage : 0; msg->cell3 = (telestate->Battery.NbCells >= 3) ? cell_voltage : 0; msg->cell4 = (telestate->Battery.NbCells >= 4) ? cell_voltage : 0; msg->cell5 = (telestate->Battery.NbCells >= 5) ? cell_voltage : 0; msg->cell6 = (telestate->Battery.NbCells >= 6) ? cell_voltage : 0; msg->min_cell_volt = cell_voltage; msg->min_cell_volt_num = telestate->Battery.NbCells; // apply main voltage to batt1 voltage msg->batt1_voltage = msg->voltage; // AirSpeed float airspeed = (telestate->Airspeed.TrueAirspeed > 0) ? telestate->Airspeed.TrueAirspeed : 0; msg->speed = scale_float2uword(airspeed, MS_TO_KMH, 0); // pressure kPa to 0.1Bar msg->pressure = scale_float2uint8(telestate->Baro.Pressure, 0.1f, 0); msg->checksum = calc_checksum((uint8_t *)msg, sizeof(*msg)); return sizeof(*msg); } uint16_t build_EAM_message(struct hott_eam_message *msg) { update_telemetrydata(); if (telestate->Settings.Sensor.EAM == HOTTBRIDGESETTINGS_SENSOR_DISABLED) { return 0; } // clear message buffer memset(msg, 0, sizeof(*msg)); // message header msg->start = HOTT_START; msg->stop = HOTT_STOP; msg->sensor_id = HOTT_EAM_ID; msg->warning = generate_warning(); msg->sensor_text_id = HOTT_EAM_TEXT_ID; // alarm inverse bits. invert display areas on limits msg->alarm_inverse1 |= (telestate->Settings.Limit.MaxUsedCapacity < telestate->Battery.ConsumedEnergy) ? EAM_INVERT_CAPACITY : 0; msg->alarm_inverse1 |= (telestate->Settings.Limit.MaxCurrent < telestate->Battery.Current) ? EAM_INVERT_CURRENT : 0; msg->alarm_inverse1 |= (telestate->Settings.Limit.MinPowerVoltage > telestate->Battery.Voltage) ? EAM_INVERT_VOLTAGE : 0; msg->alarm_inverse1 |= (telestate->Settings.Limit.MaxPowerVoltage < telestate->Battery.Voltage) ? EAM_INVERT_VOLTAGE : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.MinHeight > telestate->altitude) ? EAM_INVERT2_ALT : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.MaxHeight < telestate->altitude) ? EAM_INVERT2_ALT : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.NegDifference1 > telestate->climbrate1s) ? EAM_INVERT2_CR1S : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.PosDifference1 < telestate->climbrate1s) ? EAM_INVERT2_CR1S : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.NegDifference2 > telestate->climbrate3s) ? EAM_INVERT2_CR3S : 0; msg->alarm_inverse2 |= (telestate->Settings.Limit.PosDifference2 < telestate->climbrate3s) ? EAM_INVERT2_CR3S : 0; // main battery float voltage = (telestate->Battery.Voltage > 0) ? telestate->Battery.Voltage : 0; float current = (telestate->Battery.Current > 0) ? telestate->Battery.Current : 0; float energy = (telestate->Battery.ConsumedEnergy > 0) ? telestate->Battery.ConsumedEnergy : 0; msg->voltage = scale_float2uword(voltage, 10, 0); msg->current = scale_float2uword(current, 10, 0); msg->capacity = scale_float2uword(energy, 0.1f, 0); // simulate individual cell voltage uint8_t cell_voltage = (telestate->Battery.Voltage > 0) ? scale_float2uint8(telestate->Battery.Voltage / telestate->Battery.NbCells, 50, 0) : 0; msg->cell1_H = (telestate->Battery.NbCells >= 1) ? cell_voltage : 0; msg->cell2_H = (telestate->Battery.NbCells >= 2) ? cell_voltage : 0; msg->cell3_H = (telestate->Battery.NbCells >= 3) ? cell_voltage : 0; msg->cell4_H = (telestate->Battery.NbCells >= 4) ? cell_voltage : 0; msg->cell5_H = (telestate->Battery.NbCells >= 5) ? cell_voltage : 0; msg->cell6_H = (telestate->Battery.NbCells >= 6) ? cell_voltage : 0; msg->cell7_H = (telestate->Battery.NbCells >= 7) ? cell_voltage : 0; // apply main voltage to batt1 voltage msg->batt1_voltage = msg->voltage; // AirSpeed float airspeed = (telestate->Airspeed.TrueAirspeed > 0) ? telestate->Airspeed.TrueAirspeed : 0; msg->speed = scale_float2uword(airspeed, MS_TO_KMH, 0); // temperatures msg->temperature1 = scale_float2uint8(telestate->Gyro.temperature, 1, OFFSET_TEMPERATURE); msg->temperature2 = scale_float2uint8(telestate->Baro.Temperature, 1, OFFSET_TEMPERATURE); // altitude msg->altitude = scale_float2uword(telestate->altitude, 1, OFFSET_ALTITUDE); // climbrate msg->climbrate = scale_float2uword(telestate->climbrate1s, M_TO_CM, OFFSET_CLIMBRATE); msg->climbrate3s = scale_float2uint8(telestate->climbrate3s, 1, OFFSET_CLIMBRATE3S); // flight time float flighttime = (telestate->Battery.EstimatedFlightTime <= 5999) ? telestate->Battery.EstimatedFlightTime : 5999; msg->electric_min = flighttime / 60; msg->electric_sec = flighttime - 60 * msg->electric_min; msg->checksum = calc_checksum((uint8_t *)msg, sizeof(*msg)); return sizeof(*msg); } uint16_t build_ESC_message(struct hott_esc_message *msg) { update_telemetrydata(); if (telestate->Settings.Sensor.ESC == HOTTBRIDGESETTINGS_SENSOR_DISABLED) { return 0; } // clear message buffer memset(msg, 0, sizeof(*msg)); // message header msg->start = HOTT_START; msg->stop = HOTT_STOP; msg->sensor_id = HOTT_ESC_ID; msg->warning = 0; msg->sensor_text_id = HOTT_ESC_TEXT_ID; // main battery float voltage = (telestate->Battery.Voltage > 0) ? telestate->Battery.Voltage : 0; float current = (telestate->Battery.Current > 0) ? telestate->Battery.Current : 0; float max_current = (telestate->Battery.PeakCurrent > 0) ? telestate->Battery.PeakCurrent : 0; float energy = (telestate->Battery.ConsumedEnergy > 0) ? telestate->Battery.ConsumedEnergy : 0; msg->batt_voltage = scale_float2uword(voltage, 10, 0); msg->current = scale_float2uword(current, 10, 0); msg->max_current = scale_float2uword(max_current, 10, 0); msg->batt_capacity = scale_float2uword(energy, 0.1f, 0); // temperatures msg->temperatureESC = scale_float2uint8(telestate->Gyro.temperature, 1, OFFSET_TEMPERATURE); msg->max_temperatureESC = scale_float2uint8(0, 1, OFFSET_TEMPERATURE); msg->temperatureMOT = scale_float2uint8(telestate->Baro.Temperature, 1, OFFSET_TEMPERATURE); msg->max_temperatureMOT = scale_float2uint8(0, 1, OFFSET_TEMPERATURE); msg->checksum = calc_checksum((uint8_t *)msg, sizeof(*msg)); return sizeof(*msg); } bool build_TEXT_message(struct hott_text_message *msg, uint8_t page, uint8_t current_line, int8_t value_change, uint8_t step, bool edit_mode, bool exit_menu) { // clear message buffer memset(msg, 0, sizeof(*msg)); // message header msg->start = HOTT_TEXT_START; msg->stop = HOTT_STOP; msg->sensor_id = (exit_menu == true) ? 0x01 : 0x00; // exit menu / normal msg->warning = 0; HoTTBridgeSettingsSensorData sensor; HoTTBridgeSettingsLimitData alarmLimits; HoTTBridgeSettingsWarningData alarmWarning; RevoSettingsFusionAlgorithmOptions revoFusionAlgo; HomeLocationSetOptions homeSet; GPSSettingsData gpsSettings; bool storedtoflash = false; bool ekf_enabled = false; // page title snprintf(msg->text[0], HOTT_TEXT_COLUMNS, "%s", hottTextPageTitle[page]); // line 1 // compute page content switch (page) { case HOTTTEXT_PAGE_VARIO: // Vario page if (RevoSettingsHandle() != NULL) { RevoSettingsFusionAlgorithmGet(&revoFusionAlgo); } if (HoTTBridgeSettingsHandle() != NULL) { HoTTBridgeSettingsWarningGet(&alarmWarning); HoTTBridgeSettingsLimitGet(&alarmLimits); } bool edit_altitudebeep = (edit_mode && (current_line == 2)); bool edit_maxheight = (edit_mode && (current_line == 3)); bool edit_minheight = (edit_mode && (current_line == 4)); bool edit_minheight_value = (edit_mode && (current_line == 5)); bool edit_maxheight_value = (edit_mode && (current_line == 6)); if (edit_altitudebeep) { if (alarmWarning.AltitudeBeep == HOTTBRIDGESETTINGS_WARNING_DISABLED) { alarmWarning.AltitudeBeep = HOTTBRIDGESETTINGS_WARNING_ENABLED; } else { alarmWarning.AltitudeBeep = HOTTBRIDGESETTINGS_WARNING_DISABLED; } HoTTBridgeSettingsWarningSet(&alarmWarning); UAVObjSave(HoTTBridgeSettingsHandle(), 0); storedtoflash = true; } if (edit_minheight) { if (alarmWarning.MinHeight == HOTTBRIDGESETTINGS_WARNING_DISABLED) { alarmWarning.MinHeight = HOTTBRIDGESETTINGS_WARNING_ENABLED; } else { alarmWarning.MinHeight = HOTTBRIDGESETTINGS_WARNING_DISABLED; } HoTTBridgeSettingsWarningSet(&alarmWarning); UAVObjSave(HoTTBridgeSettingsHandle(), 0); storedtoflash = true; } if (edit_maxheight) { if (alarmWarning.MaxHeight == HOTTBRIDGESETTINGS_WARNING_DISABLED) { alarmWarning.MaxHeight = HOTTBRIDGESETTINGS_WARNING_ENABLED; } else { alarmWarning.MaxHeight = HOTTBRIDGESETTINGS_WARNING_DISABLED; } HoTTBridgeSettingsWarningSet(&alarmWarning); UAVObjSave(HoTTBridgeSettingsHandle(), 0); storedtoflash = true; } if (edit_minheight_value) { step = (step > 2) ? 2 : step; // -500 to 500m alarmLimits.MinHeight = get_new_value((int16_t)alarmLimits.MinHeight, value_change, step, -500, 500); HoTTBridgeSettingsLimitSet(&alarmLimits); } if (edit_maxheight_value) { step = (step > 2) ? 2 : step; // -500 to 1500m alarmLimits.MaxHeight = get_new_value((int16_t)alarmLimits.MaxHeight, value_change, step, -500, 1500); HoTTBridgeSettingsLimitSet(&alarmLimits); } char *txt_fusionalgo = ""; // check current algo status switch (revoFusionAlgo) { case REVOSETTINGS_FUSIONALGORITHM_INS13INDOOR: case REVOSETTINGS_FUSIONALGORITHM_TESTINGINSINDOORCF: txt_fusionalgo = "* EKF INDOOR NOGPS * "; ekf_enabled = true; break; case REVOSETTINGS_FUSIONALGORITHM_GPSNAVIGATIONINS13: case REVOSETTINGS_FUSIONALGORITHM_GPSNAVIGATIONINS13CF: txt_fusionalgo = " * EKF OUTDOOR GPS * "; ekf_enabled = true; break; case REVOSETTINGS_FUSIONALGORITHM_BASICCOMPLEMENTARY: case REVOSETTINGS_FUSIONALGORITHM_COMPLEMENTARYMAG: case REVOSETTINGS_FUSIONALGORITHM_COMPLEMENTARYMAGGPSOUTDOOR: default: txt_fusionalgo = " * BASIC ALGO * "; ekf_enabled = false; } if (edit_mode && (current_line == 7)) { // check if a GPS is available bool gps_ok = (telestate->SysAlarms.Alarm.GPS != SYSTEMALARMS_ALARM_UNINITIALISED) && (telestate->SysAlarms.Alarm.GPS != SYSTEMALARMS_ALARM_ERROR); if (gps_ok) { if (ekf_enabled) { revoFusionAlgo = REVOSETTINGS_FUSIONALGORITHM_BASICCOMPLEMENTARY; // move to Basic (no mag needed) } else { revoFusionAlgo = REVOSETTINGS_FUSIONALGORITHM_GPSNAVIGATIONINS13CF; // move to ekf } } else { // without GPS if (ekf_enabled) { revoFusionAlgo = REVOSETTINGS_FUSIONALGORITHM_BASICCOMPLEMENTARY; // move to Basic (no mag needed) } else { revoFusionAlgo = REVOSETTINGS_FUSIONALGORITHM_TESTINGINSINDOORCF; // move to ekf } } RevoSettingsFusionAlgorithmSet(&revoFusionAlgo); UAVObjSave(RevoSettingsHandle(), 0); storedtoflash = true; } snprintf(msg->text[1], HOTT_TEXT_COLUMNS, " Altitude speak [%1s] ", ((alarmWarning.AltitudeBeep == HOTTBRIDGESETTINGS_WARNING_DISABLED) ? " " : "*")); // line 2 snprintf(msg->text[2], HOTT_TEXT_COLUMNS, " MaxHeight warn [%1s] ", ((alarmWarning.MaxHeight == HOTTBRIDGESETTINGS_WARNING_DISABLED) ? " " : "*")); // line 3 snprintf(msg->text[3], HOTT_TEXT_COLUMNS, " MinHeight warn [%1s] ", ((alarmWarning.MinHeight == HOTTBRIDGESETTINGS_WARNING_DISABLED) ? " " : "*")); // line 4 snprintf(msg->text[4], HOTT_TEXT_COLUMNS, " Max height %4d ", (int16_t)alarmLimits.MaxHeight); // line 5 snprintf(msg->text[5], HOTT_TEXT_COLUMNS, " Min height %4d ", (int16_t)alarmLimits.MinHeight); // line 6 snprintf(msg->text[6], HOTT_TEXT_COLUMNS, " Use EKF algo [%1s] ", ((ekf_enabled) ? "*" : " ")); // line 7 snprintf(msg->text[7], HOTT_TEXT_COLUMNS, "%s", txt_fusionalgo); // line 8 if (current_line > 1) { msg->text[current_line - 1][0] = '>'; } if (edit_minheight_value) { reverse_pixels((char *)msg->text[current_line - 1], 16 + (3 - step), 20 - step); } if (edit_maxheight_value) { reverse_pixels((char *)msg->text[current_line - 1], 16 + (3 - step), 20 - step); } break; case HOTTTEXT_PAGE_GPS: // GPS page if (HoTTBridgeSettingsHandle() != NULL) { HoTTBridgeSettingsWarningGet(&alarmWarning); HoTTBridgeSettingsLimitGet(&alarmLimits); } bool edit_maxdistance = (edit_mode && (current_line == 2)); bool edit_maxspeed = (edit_mode && (current_line == 3)); bool edit_minspeed = (edit_mode && (current_line == 4)); bool edit_maxdistance_value = (edit_mode && (current_line == 5)); bool edit_maxspeed_value = (edit_mode && (current_line == 6)); bool edit_minspeed_value = (edit_mode && (current_line == 7)); if (edit_maxdistance) { if (alarmWarning.MaxDistance == HOTTBRIDGESETTINGS_WARNING_DISABLED) { alarmWarning.MaxDistance = HOTTBRIDGESETTINGS_WARNING_ENABLED; } else { alarmWarning.MaxDistance = HOTTBRIDGESETTINGS_WARNING_DISABLED; } HoTTBridgeSettingsWarningSet(&alarmWarning); UAVObjSave(HoTTBridgeSettingsHandle(), 0); storedtoflash = true; } if (edit_minspeed) { if (alarmWarning.MinSpeed == HOTTBRIDGESETTINGS_WARNING_DISABLED) { alarmWarning.MinSpeed = HOTTBRIDGESETTINGS_WARNING_ENABLED; } else { alarmWarning.MinSpeed = HOTTBRIDGESETTINGS_WARNING_DISABLED; } HoTTBridgeSettingsWarningSet(&alarmWarning); UAVObjSave(HoTTBridgeSettingsHandle(), 0); storedtoflash = true; } if (edit_maxspeed) { if (alarmWarning.MaxSpeed == HOTTBRIDGESETTINGS_WARNING_DISABLED) { alarmWarning.MaxSpeed = HOTTBRIDGESETTINGS_WARNING_ENABLED; } else { alarmWarning.MaxSpeed = HOTTBRIDGESETTINGS_WARNING_DISABLED; } HoTTBridgeSettingsWarningSet(&alarmWarning); UAVObjSave(HoTTBridgeSettingsHandle(), 0); storedtoflash = true; } if (edit_maxdistance_value) { step = (step > 3) ? 3 : step; // 10m to 9000m alarmLimits.MaxDistance = get_new_value((uint16_t)alarmLimits.MaxDistance, value_change, step, 10, 9000); HoTTBridgeSettingsLimitSet(&alarmLimits); } if (edit_maxspeed_value) { step = (step > 2) ? 2 : step; alarmLimits.MaxSpeed = get_new_value((int16_t)alarmLimits.MaxSpeed, value_change, step, 0, 600); HoTTBridgeSettingsLimitSet(&alarmLimits); } if (edit_minspeed_value) { step = (step > 2) ? 2 : step; alarmLimits.MinSpeed = get_new_value((int16_t)alarmLimits.MinSpeed, value_change, step, 0, 600); HoTTBridgeSettingsLimitSet(&alarmLimits); } snprintf(msg->text[1], HOTT_TEXT_COLUMNS, " MaxDist warn [%1s] ", ((alarmWarning.MaxDistance == HOTTBRIDGESETTINGS_WARNING_DISABLED) ? " " : "*")); // line 2 snprintf(msg->text[2], HOTT_TEXT_COLUMNS, " MaxSpeed warn [%1s] ", ((alarmWarning.MaxSpeed == HOTTBRIDGESETTINGS_WARNING_DISABLED) ? " " : "*")); // line 3 snprintf(msg->text[3], HOTT_TEXT_COLUMNS, " MinSpeed warn [%1s] ", ((alarmWarning.MinSpeed == HOTTBRIDGESETTINGS_WARNING_DISABLED) ? " " : "*")); // line 4 snprintf(msg->text[4], HOTT_TEXT_COLUMNS, " Max distance %4d ", (uint16_t)alarmLimits.MaxDistance); // line 5 snprintf(msg->text[5], HOTT_TEXT_COLUMNS, " Max speed %4d ", (int16_t)alarmLimits.MaxSpeed); // line 6 snprintf(msg->text[6], HOTT_TEXT_COLUMNS, " Min speed %4d ", (int16_t)alarmLimits.MinSpeed); // line 7 snprintf(msg->text[7], HOTT_TEXT_COLUMNS, " "); // line 8 if (current_line > 1) { msg->text[current_line - 1][0] = '>'; } if (edit_maxdistance_value) { reverse_pixels((char *)msg->text[current_line - 1], 16 + (3 - step), 20 - step); } if (edit_maxspeed_value) { reverse_pixels((char *)msg->text[current_line - 1], 16 + (3 - step), 20 - step); } if (edit_minspeed_value) { reverse_pixels((char *)msg->text[current_line - 1], 16 + (3 - step), 20 - step); } break; case HOTTTEXT_PAGE_GENERAL: // General Air page case HOTTTEXT_PAGE_ELECTRIC: // Electric Air page case HOTTTEXT_PAGE_ESC: // Esc page if (HoTTBridgeSettingsHandle() != NULL) { HoTTBridgeSettingsWarningGet(&alarmWarning); HoTTBridgeSettingsLimitGet(&alarmLimits); } bool edit_minvoltage = (edit_mode && (current_line == 2)); bool edit_maxcurrent = (edit_mode && (current_line == 3)); bool edit_maxusedcapacity = (edit_mode && (current_line == 4)); bool edit_minvoltage_value = (edit_mode && (current_line == 5)); bool edit_maxcurrent_value = (edit_mode && (current_line == 6)); bool edit_maxusedcapacity_value = (edit_mode && (current_line == 7)); if (edit_minvoltage) { if (alarmWarning.MinPowerVoltage == HOTTBRIDGESETTINGS_WARNING_DISABLED) { alarmWarning.MinPowerVoltage = HOTTBRIDGESETTINGS_WARNING_ENABLED; } else { alarmWarning.MinPowerVoltage = HOTTBRIDGESETTINGS_WARNING_DISABLED; } HoTTBridgeSettingsWarningSet(&alarmWarning); UAVObjSave(HoTTBridgeSettingsHandle(), 0); storedtoflash = true; } if (edit_maxcurrent) { if (alarmWarning.MaxCurrent == HOTTBRIDGESETTINGS_WARNING_DISABLED) { alarmWarning.MaxCurrent = HOTTBRIDGESETTINGS_WARNING_ENABLED; } else { alarmWarning.MaxCurrent = HOTTBRIDGESETTINGS_WARNING_DISABLED; } HoTTBridgeSettingsWarningSet(&alarmWarning); UAVObjSave(HoTTBridgeSettingsHandle(), 0); storedtoflash = true; } if (edit_maxusedcapacity) { if (alarmWarning.MaxUsedCapacity == HOTTBRIDGESETTINGS_WARNING_DISABLED) { alarmWarning.MaxUsedCapacity = HOTTBRIDGESETTINGS_WARNING_ENABLED; } else { alarmWarning.MaxUsedCapacity = HOTTBRIDGESETTINGS_WARNING_DISABLED; } HoTTBridgeSettingsWarningSet(&alarmWarning); UAVObjSave(HoTTBridgeSettingsHandle(), 0); storedtoflash = true; } if (edit_minvoltage_value) { step = (step > 2) ? 2 : step; // 3V to 50V alarmLimits.MinPowerVoltage = (float)(get_new_value((uint16_t)(alarmLimits.MinPowerVoltage * 10), value_change, step, 30, 500) / 10.0f); HoTTBridgeSettingsLimitSet(&alarmLimits); } if (edit_maxcurrent_value) { step = (step > 2) ? 2 : step; // 1A to 300A alarmLimits.MaxCurrent = get_new_value((uint16_t)(alarmLimits.MaxCurrent), value_change, step, 1, 300); HoTTBridgeSettingsLimitSet(&alarmLimits); } if (edit_maxusedcapacity_value) { step = (step > 3) ? 3 : step; // 100mAh to 30000mAh alarmLimits.MaxUsedCapacity = (float)(get_new_value((uint16_t)alarmLimits.MaxUsedCapacity, value_change, step, 100, 30000)); HoTTBridgeSettingsLimitSet(&alarmLimits); } snprintf(msg->text[1], HOTT_TEXT_COLUMNS, " MinVoltage warn [%1s] ", ((alarmWarning.MinPowerVoltage == HOTTBRIDGESETTINGS_WARNING_DISABLED) ? " " : "*")); // line 2 snprintf(msg->text[2], HOTT_TEXT_COLUMNS, " MaxCurrent warn [%1s] ", ((alarmWarning.MaxCurrent == HOTTBRIDGESETTINGS_WARNING_DISABLED) ? " " : "*")); // line 3 snprintf(msg->text[3], HOTT_TEXT_COLUMNS, " MaxUsedmAH warn [%1s] ", ((alarmWarning.MaxUsedCapacity == HOTTBRIDGESETTINGS_WARNING_DISABLED) ? " " : "*")); // line 4 snprintf(msg->text[4], HOTT_TEXT_COLUMNS, " Min voltage %2d.%d ", (uint16_t)(alarmLimits.MinPowerVoltage), (uint16_t)(alarmLimits.MinPowerVoltage * 10) % 10); // line 5 snprintf(msg->text[5], HOTT_TEXT_COLUMNS, " Max current %3d ", (uint16_t)alarmLimits.MaxCurrent); // line 6 snprintf(msg->text[6], HOTT_TEXT_COLUMNS, " Max used mAH %5d ", (uint16_t)alarmLimits.MaxUsedCapacity); // line 7 snprintf(msg->text[7], HOTT_TEXT_COLUMNS, " "); // line 8 if (current_line > 1) { msg->text[current_line - 1][0] = '>'; } if (edit_minvoltage_value) { if (step > 0) { step += 1; } reverse_pixels((char *)msg->text[current_line - 1], 15 + (4 - step), 20 - step); } if (edit_maxcurrent_value) { reverse_pixels((char *)msg->text[current_line - 1], 16 + (3 - step), 20 - step); } if (edit_maxusedcapacity_value) { reverse_pixels((char *)msg->text[current_line - 1], 15 + (4 - step), 20 - step); } break; case HOTTTEXT_PAGE_GPSCONFIG: // GPS config page if (GPSSettingsHandle() != NULL) { GPSSettingsGet(&gpsSettings); } if (HomeLocationHandle() != NULL) { UAVObjLoad(HomeLocationHandle(), 0); // load from flash HomeLocationSetGet(&homeSet); } bool edit_savehome = (edit_mode && (current_line == 2)); bool edit_minsat_value = (edit_mode && (current_line == 3)); bool edit_maxpdop_value = (edit_mode && (current_line == 4)); bool edit_ubxrate_value = (edit_mode && (current_line == 5)); if (edit_minsat_value) { gpsSettings.MinSatellites = get_new_value(gpsSettings.MinSatellites, value_change, 0, 4, 9); GPSSettingsSet(&gpsSettings); } if (edit_maxpdop_value) { step = (step > 2) ? 2 : step; // 1.0 to 10.0 gpsSettings.MaxPDOP = (float)(get_new_value((uint16_t)(gpsSettings.MaxPDOP * 10), value_change, step, 10, 100) / 10.0f); GPSSettingsSet(&gpsSettings); } if (edit_ubxrate_value) { gpsSettings.UbxRate = get_new_value(gpsSettings.UbxRate, value_change, 0, 1, 15); GPSSettingsSet(&gpsSettings); } char *home_set_perm = (homeSet == HOMELOCATION_SET_FALSE) ? "NEVER" : "ISSET"; snprintf(msg->text[1], HOTT_TEXT_COLUMNS, " Home status %s ", home_set_perm); // line 2 snprintf(msg->text[2], HOTT_TEXT_COLUMNS, " Min satellites %d ", gpsSettings.MinSatellites); // line 3 snprintf(msg->text[3], HOTT_TEXT_COLUMNS, " Max PDOP %2d.%d ", (uint16_t)(gpsSettings.MaxPDOP), (uint16_t)(gpsSettings.MaxPDOP * 10) % 10); // line 4 snprintf(msg->text[4], HOTT_TEXT_COLUMNS, " UBX Rate %2d ", gpsSettings.UbxRate); // line 5 snprintf(msg->text[5], HOTT_TEXT_COLUMNS, " "); // line 6 snprintf(msg->text[6], HOTT_TEXT_COLUMNS, " "); // line 7 snprintf(msg->text[7], HOTT_TEXT_COLUMNS, " "); // line 8 if (current_line > 1) { msg->text[current_line - 1][0] = '>'; } if (edit_savehome) { if (homeSet == HOMELOCATION_SET_TRUE) { homeSet = HOMELOCATION_SET_FALSE; HomeLocationSetSet(&homeSet); UAVObjSave(HomeLocationHandle(), 0); storedtoflash = true; } } if (edit_minsat_value) { reverse_pixels((char *)msg->text[current_line - 1], 19, 20); } if (edit_maxpdop_value) { if (step > 0) { step += 1; } reverse_pixels((char *)msg->text[current_line - 1], 15 + (4 - step), 20 - step); } if (edit_ubxrate_value) { reverse_pixels((char *)msg->text[current_line - 1], 18, 20); } break; default: case HOTTTEXT_PAGE_MAIN: // Main page where HoTT modules can be started if (HoTTBridgeSettingsHandle() != NULL) { HoTTBridgeSettingsSensorGet(&sensor); } if (edit_mode) { switch (current_line) { case 2: if (sensor.VARIO == HOTTBRIDGESETTINGS_SENSOR_DISABLED) { sensor.VARIO = HOTTBRIDGESETTINGS_SENSOR_ENABLED; } else { sensor.VARIO = HOTTBRIDGESETTINGS_SENSOR_DISABLED; } break; case 3: if (sensor.GPS == HOTTBRIDGESETTINGS_SENSOR_DISABLED) { sensor.GPS = HOTTBRIDGESETTINGS_SENSOR_ENABLED; } else { sensor.GPS = HOTTBRIDGESETTINGS_SENSOR_DISABLED; } break; case 4: if (sensor.EAM == HOTTBRIDGESETTINGS_SENSOR_DISABLED) { sensor.EAM = HOTTBRIDGESETTINGS_SENSOR_ENABLED; // no need to emulate General module sensor.GAM = HOTTBRIDGESETTINGS_SENSOR_DISABLED; } else { sensor.EAM = HOTTBRIDGESETTINGS_SENSOR_DISABLED; } break; case 5: if (sensor.GAM == HOTTBRIDGESETTINGS_SENSOR_DISABLED) { sensor.GAM = HOTTBRIDGESETTINGS_SENSOR_ENABLED; // no need to emulate Electric module sensor.EAM = HOTTBRIDGESETTINGS_SENSOR_DISABLED; } else { sensor.GAM = HOTTBRIDGESETTINGS_SENSOR_DISABLED; } break; case 6: if (sensor.ESC == HOTTBRIDGESETTINGS_SENSOR_DISABLED) { sensor.ESC = HOTTBRIDGESETTINGS_SENSOR_ENABLED; } else { sensor.ESC = HOTTBRIDGESETTINGS_SENSOR_DISABLED; } } HoTTBridgeSettingsSensorSet(&sensor); UAVObjSave(HoTTBridgeSettingsHandle(), 0); storedtoflash = true; } // create Main page content snprintf(msg->text[1], HOTT_TEXT_COLUMNS, " VARIO module [%1s] ", ((sensor.VARIO == HOTTBRIDGESETTINGS_SENSOR_DISABLED) ? " " : "*")); // line 2 snprintf(msg->text[2], HOTT_TEXT_COLUMNS, " GPS module [%1s] ", ((sensor.GPS == HOTTBRIDGESETTINGS_SENSOR_DISABLED) ? " " : "*")); // line 3 snprintf(msg->text[3], HOTT_TEXT_COLUMNS, " ELECTRIC module [%1s] ", ((sensor.EAM == HOTTBRIDGESETTINGS_SENSOR_DISABLED) ? " " : "*")); // line 4 snprintf(msg->text[4], HOTT_TEXT_COLUMNS, " GENERAL module [%1s] ", ((sensor.GAM == HOTTBRIDGESETTINGS_SENSOR_DISABLED) ? " " : "*")); // line 5 snprintf(msg->text[5], HOTT_TEXT_COLUMNS, " ESC module [%1s] ", ((sensor.ESC == HOTTBRIDGESETTINGS_SENSOR_DISABLED) ? " " : "*")); // line 6 snprintf(msg->text[6], HOTT_TEXT_COLUMNS, " Select module "); snprintf(msg->text[7], HOTT_TEXT_COLUMNS, " to be emulated "); if (current_line > 1) { msg->text[current_line - 1][0] = '>'; } // break; } msg->stop = HOTT_STOP; msg->checksum = calc_checksum((uint8_t *)msg, sizeof(*msg)); return storedtoflash; } /** * get next/previous page to display */ uint8_t get_page(uint8_t page, bool next) { HoTTBridgeSettingsSensorData sensor; if (HoTTBridgeSettingsHandle() != NULL) { HoTTBridgeSettingsSensorGet(&sensor); } if (next) { switch (page) { case HOTTTEXT_PAGE_MAIN: if (sensor.GPS == HOTTBRIDGESETTINGS_SENSOR_ENABLED) { page = HOTTTEXT_PAGE_GPSCONFIG; break; } case HOTTTEXT_PAGE_GPSCONFIG: if (sensor.VARIO == HOTTBRIDGESETTINGS_SENSOR_ENABLED) { page = HOTTTEXT_PAGE_VARIO; break; } case HOTTTEXT_PAGE_VARIO: if (sensor.GPS == HOTTBRIDGESETTINGS_SENSOR_ENABLED) { page = HOTTTEXT_PAGE_GPS; break; } case HOTTTEXT_PAGE_GPS: if (sensor.GAM == HOTTBRIDGESETTINGS_SENSOR_ENABLED) { page = HOTTTEXT_PAGE_GENERAL; break; } case HOTTTEXT_PAGE_GENERAL: if (sensor.EAM == HOTTBRIDGESETTINGS_SENSOR_ENABLED) { page = HOTTTEXT_PAGE_ELECTRIC; break; } case HOTTTEXT_PAGE_ELECTRIC: if (sensor.ESC == HOTTBRIDGESETTINGS_SENSOR_ENABLED) { page = HOTTTEXT_PAGE_ESC; break; } case HOTTTEXT_PAGE_ESC: break; default: page = HOTTTEXT_PAGE_MAIN; } } else { switch (page) { case HOTTTEXT_PAGE_ESC: if (sensor.EAM == HOTTBRIDGESETTINGS_SENSOR_ENABLED) { page = HOTTTEXT_PAGE_ELECTRIC; break; } case HOTTTEXT_PAGE_ELECTRIC: if (sensor.GAM == HOTTBRIDGESETTINGS_SENSOR_ENABLED) { page = HOTTTEXT_PAGE_GENERAL; break; } case HOTTTEXT_PAGE_GENERAL: if (sensor.GPS == HOTTBRIDGESETTINGS_SENSOR_ENABLED) { page = HOTTTEXT_PAGE_GPS; break; } case HOTTTEXT_PAGE_GPS: if (sensor.VARIO == HOTTBRIDGESETTINGS_SENSOR_ENABLED) { page = HOTTTEXT_PAGE_VARIO; break; } case HOTTTEXT_PAGE_VARIO: if (sensor.GPS == HOTTBRIDGESETTINGS_SENSOR_ENABLED) { page = HOTTTEXT_PAGE_GPSCONFIG; break; } case HOTTTEXT_PAGE_GPSCONFIG: page = HOTTTEXT_PAGE_MAIN; break; case HOTTTEXT_PAGE_MAIN: default: page = HOTTTEXT_PAGE_MAIN; } } return page; } /** * get new value for edited field */ int16_t get_new_value(int16_t current_value, int8_t value_change, uint8_t step, int16_t min, int16_t max) { uint16_t increment[] = { 1, 10, 100, 1000, 10000 }; int16_t new_value = 0; new_value = current_value + (value_change * increment[step]); if (new_value < min) { new_value = min; } if (new_value > max) { new_value = max; } return new_value; } /** * store settings to onboard flash */ void store_settings(uint8_t page) { switch (page) { case HOTTTEXT_PAGE_MAIN: case HOTTTEXT_PAGE_VARIO: case HOTTTEXT_PAGE_GPS: case HOTTTEXT_PAGE_GENERAL: case HOTTTEXT_PAGE_ELECTRIC: case HOTTTEXT_PAGE_ESC: UAVObjSave(HoTTBridgeSettingsHandle(), 0); break; case HOTTTEXT_PAGE_GPSCONFIG: UAVObjSave(GPSSettingsHandle(), 0); break; } } /** * update telemetry data * this is called on every telemetry request * calling interval is 200ms depending on TX * 200ms telemetry request is used as time base for timed calculations (5Hz interval) */ void update_telemetrydata() { // update all available data if (HoTTBridgeSettingsHandle() != NULL) { HoTTBridgeSettingsGet(&telestate->Settings); } if (AttitudeStateHandle() != NULL) { AttitudeStateGet(&telestate->Attitude); } if (AccelStateHandle() != NULL) { AccelStateGet(&telestate->Accel); } if (BaroSensorHandle() != NULL) { BaroSensorGet(&telestate->Baro); } if (FlightBatteryStateHandle() != NULL) { FlightBatteryStateGet(&telestate->Battery); } if (FlightStatusHandle() != NULL) { FlightStatusGet(&telestate->FlightStatus); } if (GPSPositionSensorHandle() != NULL) { GPSPositionSensorGet(&telestate->GPS); } if (AirspeedStateHandle() != NULL) { AirspeedStateGet(&telestate->Airspeed); } if (GPSTimeHandle() != NULL) { GPSTimeGet(&telestate->GPStime); } if (GyroSensorHandle() != NULL) { GyroSensorGet(&telestate->Gyro); } if (HomeLocationHandle() != NULL) { HomeLocationGet(&telestate->Home); } if (PositionStateHandle() != NULL) { PositionStateGet(&telestate->Position); } if (SystemAlarmsHandle() != NULL) { SystemAlarmsGet(&telestate->SysAlarms); } if (VelocityStateHandle() != NULL) { VelocityStateGet(&telestate->Velocity); } // send actual climbrate value to ring buffer as mm per 0.2s values uint8_t n = telestate->climbrate_pointer; // DEBUG_PRINTF(3, "Current pointer: %d\r\n", n); telestate->climbratebuffer[telestate->climbrate_pointer++] = -telestate->Velocity.Down * 200; telestate->climbrate_pointer %= climbratesize; // DEBUG_PRINTF(3, "Next pointer: %d\r\n", telestate->climbrate_pointer); bool display_G = (n > 25); // calculate avarage climbrates in meters per 1, 3 and 10 second(s) based on 200ms interval telestate->climbrate1s = 0; telestate->climbrate3s = 0; telestate->climbrate10s = 0; for (uint8_t i = 0; i < climbratesize; i++) { // DEBUG_PRINTF(3, "%d ", n); telestate->climbrate1s += (i < 5) ? telestate->climbratebuffer[n] : 0; telestate->climbrate3s += (i < 15) ? telestate->climbratebuffer[n] : 0; telestate->climbrate10s += (i < 50) ? telestate->climbratebuffer[n] : 0; n += climbratesize - 1; n %= climbratesize; } telestate->climbrate1s = telestate->climbrate1s / 1000; telestate->climbrate3s = telestate->climbrate3s / 1000; telestate->climbrate10s = telestate->climbrate10s / 1000; // set altitude offset and clear min/max values when arming if ((telestate->FlightStatus.Armed == FLIGHTSTATUS_ARMED_ARMING) || ((telestate->last_armed != FLIGHTSTATUS_ARMED_ARMED) && (telestate->FlightStatus.Armed == FLIGHTSTATUS_ARMED_ARMED))) { telestate->min_altitude = 0; telestate->max_altitude = 0; telestate->max_distance = 0; } telestate->last_armed = telestate->FlightStatus.Armed; // calculate altitude relative to start position telestate->altitude = -telestate->Position.Down; // gps home position and course telestate->homedistance = sqrtf(telestate->Position.North * telestate->Position.North + telestate->Position.East * telestate->Position.East); telestate->homecourse = acosf(-telestate->Position.North / telestate->homedistance) / 3.14159265f * 180; if (telestate->Position.East > 0) { telestate->homecourse = 360 - telestate->homecourse; } // calculate current 3D speed float speed_3d = sqrtf((telestate->Velocity.North * telestate->Velocity.North) + (telestate->Velocity.East * telestate->Velocity.East) + (telestate->Velocity.Down * telestate->Velocity.Down)) * MS_TO_KMH; // Normal Acceleration (G unit) float nz_alpha = 0.7f; telestate->current_G = ((1 - nz_alpha) * (-telestate->Accel.z / 9.81f)) + (telestate->current_G * nz_alpha); // check and set min/max values when armed // and without receiver input for standalone board used as sensor if ((telestate->FlightStatus.Armed == FLIGHTSTATUS_ARMED_ARMED) || ((telestate->SysAlarms.Alarm.Attitude == SYSTEMALARMS_ALARM_OK) && (telestate->SysAlarms.Alarm.Receiver != SYSTEMALARMS_ALARM_OK))) { if (telestate->min_altitude > telestate->altitude) { telestate->min_altitude = telestate->altitude; } if (telestate->max_altitude < telestate->altitude) { telestate->max_altitude = telestate->altitude; } if (telestate->max_distance < telestate->homedistance) { telestate->max_distance = telestate->homedistance; } if (telestate->max_speed < speed_3d) { telestate->max_speed = speed_3d; } if (telestate->max_G < telestate->current_G) { telestate->max_G = telestate->current_G; } if (telestate->min_G > telestate->current_G) { telestate->min_G = telestate->current_G; } } // statusline const char *txt_unknown = "unknown"; const char *txt_manual = "Manual"; const char *txt_stabilized1 = "Stabilized1"; const char *txt_stabilized2 = "Stabilized2"; const char *txt_stabilized3 = "Stabilized3"; const char *txt_stabilized4 = "Stabilized4"; const char *txt_stabilized5 = "Stabilized5"; const char *txt_stabilized6 = "Stabilized6"; const char *txt_positionhold = "PositionHold"; const char *txt_courselock = "CourseLock"; const char *txt_velocityroam = "VelocityRoam"; const char *txt_homeleash = "HomeLeash"; const char *txt_absoluteposition = "AbsolutePosition"; const char *txt_returntobase = "ReturnToBase"; const char *txt_land = "Land"; const char *txt_pathplanner = "PathPlanner"; const char *txt_poi = "PointOfInterest"; const char *txt_autocruise = "AutoCruise"; const char *txt_autotakeoff = "AutoTakeOff"; const char *txt_autotune = "Autotune"; const char *txt_disarmed = "Disarmed"; const char *txt_arming = "Arming"; const char *txt_armed = "Armed"; const char *txt_flightmode; switch (telestate->FlightStatus.FlightMode) { case FLIGHTSTATUS_FLIGHTMODE_MANUAL: txt_flightmode = txt_manual; break; case FLIGHTSTATUS_FLIGHTMODE_STABILIZED1: txt_flightmode = txt_stabilized1; break; case FLIGHTSTATUS_FLIGHTMODE_STABILIZED2: txt_flightmode = txt_stabilized2; break; case FLIGHTSTATUS_FLIGHTMODE_STABILIZED3: txt_flightmode = txt_stabilized3; break; case FLIGHTSTATUS_FLIGHTMODE_STABILIZED4: txt_flightmode = txt_stabilized4; break; case FLIGHTSTATUS_FLIGHTMODE_STABILIZED5: txt_flightmode = txt_stabilized5; break; case FLIGHTSTATUS_FLIGHTMODE_STABILIZED6: txt_flightmode = txt_stabilized6; break; case FLIGHTSTATUS_FLIGHTMODE_POSITIONHOLD: txt_flightmode = txt_positionhold; break; case FLIGHTSTATUS_FLIGHTMODE_COURSELOCK: txt_flightmode = txt_courselock; break; case FLIGHTSTATUS_FLIGHTMODE_VELOCITYROAM: txt_flightmode = txt_velocityroam; break; case FLIGHTSTATUS_FLIGHTMODE_HOMELEASH: txt_flightmode = txt_homeleash; break; case FLIGHTSTATUS_FLIGHTMODE_ABSOLUTEPOSITION: txt_flightmode = txt_absoluteposition; break; case FLIGHTSTATUS_FLIGHTMODE_RETURNTOBASE: txt_flightmode = txt_returntobase; break; case FLIGHTSTATUS_FLIGHTMODE_LAND: txt_flightmode = txt_land; break; case FLIGHTSTATUS_FLIGHTMODE_PATHPLANNER: txt_flightmode = txt_pathplanner; break; case FLIGHTSTATUS_FLIGHTMODE_POI: txt_flightmode = txt_poi; break; case FLIGHTSTATUS_FLIGHTMODE_AUTOCRUISE: txt_flightmode = txt_autocruise; break; case FLIGHTSTATUS_FLIGHTMODE_AUTOTAKEOFF: txt_flightmode = txt_autotakeoff; break; case FLIGHTSTATUS_FLIGHTMODE_AUTOTUNE: txt_flightmode = txt_autotune; break; default: txt_flightmode = txt_unknown; } const char *txt_armstate; switch (telestate->FlightStatus.Armed) { case FLIGHTSTATUS_ARMED_DISARMED: txt_armstate = txt_disarmed; break; case FLIGHTSTATUS_ARMED_ARMING: txt_armstate = txt_arming; break; case FLIGHTSTATUS_ARMED_ARMED: txt_armstate = txt_armed; break; default: txt_armstate = txt_unknown; } snprintf(telestate->statusline, sizeof(telestate->statusline), "%12s,%8s", txt_flightmode, txt_armstate); snprintf(telestate->statusline, sizeof(telestate->statusline), "Max %3dkmh Dst %4dm", (uint16_t)telestate->max_speed, (uint16_t)telestate->max_distance); // "Max 200kmh" // "Dst 1000m " if (display_G) { snprintf(telestate->statusline, sizeof(telestate->statusline), "MaxG %2d.%d MinG %2d.%d", (int8_t)(telestate->max_G), (int8_t)(telestate->max_G * 10) % 10, (int8_t)(telestate->min_G), (int8_t)abs(telestate->min_G * 10) % 10); } } /** * generate warning beeps or spoken announcements */ uint8_t generate_warning() { bool gps_ok = (telestate->SysAlarms.Alarm.GPS == SYSTEMALARMS_ALARM_OK); // set warning tone with hardcoded priority if ((telestate->Settings.Warning.MinSpeed == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.MinSpeed > telestate->GPS.Groundspeed * MS_TO_KMH) && gps_ok) { return HOTT_TONE_A; // minimum speed } if ((telestate->Settings.Warning.NegDifference2 == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.NegDifference2 > telestate->climbrate3s)) { return HOTT_TONE_B; // sink rate 3s } if ((telestate->Settings.Warning.NegDifference1 == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.NegDifference1 > telestate->climbrate1s)) { return HOTT_TONE_C; // sink rate 1s } if ((telestate->Settings.Warning.MaxDistance == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.MaxDistance < telestate->homedistance) && gps_ok) { return HOTT_TONE_D; // maximum distance } if ((telestate->Settings.Warning.MinSensor1Temp == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.MinSensor1Temp > telestate->Gyro.temperature)) { return HOTT_TONE_F; // minimum temperature sensor 1 } if ((telestate->Settings.Warning.MinSensor2Temp == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.MinSensor2Temp > telestate->Baro.Temperature)) { return HOTT_TONE_G; // minimum temperature sensor 2 } if ((telestate->Settings.Warning.MaxSensor1Temp == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.MaxSensor1Temp < telestate->Gyro.temperature)) { return HOTT_TONE_H; // maximum temperature sensor 1 } if ((telestate->Settings.Warning.MaxSensor2Temp == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.MaxSensor2Temp < telestate->Baro.Temperature)) { return HOTT_TONE_I; // maximum temperature sensor 2 } if ((telestate->Settings.Warning.MaxSpeed == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.MaxSpeed < telestate->GPS.Groundspeed * MS_TO_KMH) && gps_ok) { return HOTT_TONE_L; // maximum speed } if ((telestate->Settings.Warning.PosDifference2 == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.PosDifference2 > telestate->climbrate3s)) { return HOTT_TONE_M; // climb rate 3s } if ((telestate->Settings.Warning.PosDifference1 == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.PosDifference1 > telestate->climbrate1s)) { return HOTT_TONE_N; // climb rate 1s } if ((telestate->Settings.Warning.MinHeight == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.MinHeight > telestate->altitude)) { return HOTT_TONE_O; // minimum height } if ((telestate->Settings.Warning.MinPowerVoltage == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.MinPowerVoltage > telestate->Battery.Voltage)) { return HOTT_TONE_P; // minimum input voltage } if ((telestate->Settings.Warning.MaxUsedCapacity == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.MaxUsedCapacity < telestate->Battery.ConsumedEnergy)) { return HOTT_TONE_V; // capacity } if ((telestate->Settings.Warning.MaxCurrent == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.MaxCurrent < telestate->Battery.Current)) { return HOTT_TONE_W; // maximum current } if ((telestate->Settings.Warning.MaxPowerVoltage == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.MaxPowerVoltage < telestate->Battery.Voltage)) { return HOTT_TONE_X; // maximum input voltage } if ((telestate->Settings.Warning.MaxHeight == HOTTBRIDGESETTINGS_WARNING_ENABLED) && (telestate->Settings.Limit.MaxHeight < telestate->altitude)) { return HOTT_TONE_Z; // maximum height } // altitude beeps when crossing altitude limits at 20,40,60,80,100,200,400,600,800 and 1000 meters if (telestate->Settings.Warning.AltitudeBeep == HOTTBRIDGESETTINGS_WARNING_ENABLED) { // update altitude when checked for beeps float last = telestate->altitude_last; float actual = telestate->altitude; telestate->altitude_last = telestate->altitude; if (((last < 20) && (actual >= 20)) || ((last > 20) && (actual <= 20))) { return HOTT_TONE_20M; } if (((last < 40) && (actual >= 40)) || ((last > 40) && (actual <= 40))) { return HOTT_TONE_40M; } if (((last < 60) && (actual >= 60)) || ((last > 60) && (actual <= 60))) { return HOTT_TONE_60M; } if (((last < 80) && (actual >= 80)) || ((last > 80) && (actual <= 80))) { return HOTT_TONE_80M; } if (((last < 100) && (actual >= 100)) || ((last > 100) && (actual <= 100))) { return HOTT_TONE_100M; } if (((last < 200) && (actual >= 200)) || ((last > 200) && (actual <= 200))) { return HOTT_TONE_200M; } if (((last < 400) && (actual >= 400)) || ((last > 400) && (actual <= 400))) { return HOTT_TONE_400M; } if (((last < 600) && (actual >= 600)) || ((last > 600) && (actual <= 600))) { return HOTT_TONE_600M; } if (((last < 800) && (actual >= 800)) || ((last > 800) && (actual <= 800))) { return HOTT_TONE_800M; } if (((last < 1000) && (actual >= 1000)) || ((last > 1000) && (actual <= 1000))) { return HOTT_TONE_1000M; } } // there is no warning return 0; } /** * reverse pixels */ char *reverse_pixels(char *line, uint8_t from_char, uint8_t to_char) { for (int i = from_char; i < to_char; i++) { if (line[i] == 0) { line[i] = (uint8_t)(0x80 + 0x20); } else { line[i] = (0x80 + line[i]); } } return line; } /** * calculate checksum of data buffer */ uint8_t calc_checksum(uint8_t *data, uint16_t size) { uint16_t sum = 0; for (int i = 0; i < size; i++) { sum += data[i]; } return sum; } /** * scale float value with scale and offset to unsigned byte */ uint8_t scale_float2uint8(float value, float scale, float offset) { uint16_t temp = (uint16_t)roundf(value * scale + offset); uint8_t result; result = (uint8_t)temp & 0xff; return result; } /** * scale float value with scale and offset to signed byte (int8_t) */ int8_t scale_float2int8(float value, float scale, float offset) { int8_t result = (int8_t)roundf(value * scale + offset); return result; } /** * scale float value with scale and offset to word */ uword_t scale_float2uword(float value, float scale, float offset) { uint16_t temp = (uint16_t)roundf(value * scale + offset); uword_t result; result.l = (uint8_t)temp & 0xff; result.h = (uint8_t)(temp >> 8) & 0xff; return result; } /** * convert dword gps value into HoTT gps format and write result to given pointers */ void convert_long2gps(int32_t value, uint8_t *dir, uword_t *min, uword_t *sec) { // convert gps decigrad value into degrees, minutes and seconds uword_t temp; uint32_t absvalue = abs(value); uint16_t degrees = (absvalue / 10000000); uint32_t seconds = (absvalue - degrees * 10000000) * 6; uint16_t minutes = seconds / 1000000; seconds %= 1000000; seconds = seconds / 100; uint16_t degmin = degrees * 100 + minutes; // write results *dir = (value < 0) ? 1 : 0; temp.l = (uint8_t)degmin & 0xff; temp.h = (uint8_t)(degmin >> 8) & 0xff; *min = temp; temp.l = (uint8_t)seconds & 0xff; temp.h = (uint8_t)(seconds >> 8) & 0xff; *sec = temp; } #endif // PIOS_INCLUDE_HOTT_BRIDGE /** * @} * @} */