Add dynamic distance formatting for OSD elements

Optionally format distance as meters/kilometers or feet/miles based on overal distance. For metric, distance will be displayed as:

0-999m: display meters
1000-9999m: display as kilometers with 2 decimals (ie. 1.23K)
10000m and above: display as kilometers with 1 decimal (ie. 10.5K)

Similar for imperial units except that will display feet up to 1 mile (5280ft), miles with 2 decimals from 1-9.99 miles, and with 1 decimal over 10 miles.

Configured with `osd_dynamic_distance_units = OFF | ON`. If off (the default) then the behavior is unchanged and the distance will be only displayed in meters or feet depending on the units selection.

There are currently no OSD font symbols for kilometers or miles so "K" and "M" are used at this time.
This commit is contained in:
Bruce Luckcuck 2019-06-22 14:57:53 -04:00
parent ecb67fcc09
commit 865515113e
7 changed files with 107 additions and 9 deletions

View File

@ -1130,6 +1130,7 @@ const clivalue_t valueTable[] = {
// PG_OSD_CONFIG
#ifdef USE_OSD
{ "osd_units", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_UNIT }, PG_OSD_CONFIG, offsetof(osdConfig_t, units) },
{ "osd_dynamic_distance_units", VAR_UINT8 | MASTER_VALUE | MODE_LOOKUP, .config.lookup = { TABLE_OFF_ON }, PG_OSD_CONFIG, offsetof(osdConfig_t, dynamic_distance_units) },
// Please try to keep the OSD warnings in the same order as presented in the Configurator.
// This makes it easier for the user to relate the CLI output as warnings are in the same relative

View File

@ -22,6 +22,7 @@
#pragma once
//Misc
#define SYM_NONE 0x00
#define SYM_END_OF_FONT 0xFF
#define SYM_BLANK 0x20
#define SYM_HYPHEN 0x2D
@ -47,10 +48,12 @@
// Unit Icons (Metric)
#define SYM_M 0x0C
#define SYM_KM 0x4B // 'K'
#define SYM_C 0x0E
// Unit Icons (Imperial)
#define SYM_FT 0x0F
#define SYM_MILES 0x4D // 'M'
#define SYM_F 0x0D
// Heading Graphics

View File

@ -302,6 +302,7 @@ void pgResetFn_osdConfig(osdConfig_t *osdConfig)
osdConfig->profile[i][0] = '\0';
}
osdConfig->rssi_dbm_alarm = 60;
osdConfig->dynamic_distance_units = false;
}
static void osdDrawLogo(int x, int y)
@ -437,8 +438,6 @@ static void osdUpdateStats(void)
#ifdef USE_GPS
if (STATE(GPS_FIX) && STATE(GPS_FIX_HOME)) {
value = GPS_distanceToHome;
if (stats.max_distance < GPS_distanceToHome) {
stats.max_distance = GPS_distanceToHome;
}
@ -570,7 +569,7 @@ static bool osdDisplayStat(int statistic, uint8_t displayRow)
case OSD_STAT_MAX_DISTANCE:
if (featureIsEnabled(FEATURE_GPS)) {
tfp_sprintf(buff, "%d%c", osdGetMetersToSelectedUnit(stats.max_distance), osdGetMetersToSelectedUnitSymbol());
osdFormatDistanceString(buff, stats.max_distance, SYM_NONE);
osdDisplayStatisticLabel(displayRow, "MAX DISTANCE", buff);
return true;
}
@ -578,8 +577,8 @@ static bool osdDisplayStat(int statistic, uint8_t displayRow)
case OSD_STAT_FLIGHT_DISTANCE:
if (featureIsEnabled(FEATURE_GPS)) {
const uint32_t distanceFlown = GPS_distanceFlownInCm / 100;
tfp_sprintf(buff, "%d%c", osdGetMetersToSelectedUnit(distanceFlown), osdGetMetersToSelectedUnitSymbol());
const int distanceFlown = GPS_distanceFlownInCm / 100;
osdFormatDistanceString(buff, distanceFlown, SYM_NONE);
osdDisplayStatisticLabel(displayRow, "FLIGHT DISTANCE", buff);
return true;
}

View File

@ -257,6 +257,7 @@ typedef struct osdConfig_s {
char profile[OSD_PROFILE_COUNT][OSD_PROFILE_NAME_LENGTH + 1];
uint16_t link_quality_alarm;
uint8_t rssi_dbm_alarm;
uint8_t dynamic_distance_units; // Whether to change from meters to km or ft to miles automatically
} osdConfig_t;
PG_DECLARE(osdConfig_t, osdConfig);

View File

@ -240,6 +240,44 @@ static void osdFormatCoordinate(char *buff, char sym, int32_t val)
}
#endif // USE_GPS
void osdFormatDistanceString(char *result, int distance, char leadingSymbol)
{
const int convertedDistance = osdGetMetersToSelectedUnit(distance);
char unitSymbol;
char unitSymbolExtended;
int unitTransition;
char buff[10];
result[0] = leadingSymbol;
if (leadingSymbol != SYM_NONE) {
result[1] = 0;
}
switch (osdConfig()->units) {
case OSD_UNIT_IMPERIAL:
unitTransition = 5280;
unitSymbol = SYM_FT;
unitSymbolExtended = SYM_MILES;
break;
default:
unitTransition = 1000;
unitSymbol = SYM_M;
unitSymbolExtended = SYM_KM;
break;
}
if (convertedDistance < unitTransition || !osdConfig()->dynamic_distance_units) {
tfp_sprintf(buff, "%d%c", convertedDistance, unitSymbol);
} else {
const int displayDistance = convertedDistance * 100 / unitTransition;
if (displayDistance >= 1000) { // >= 10 miles or km - 1 decimal place
tfp_sprintf(buff, "%d.%d%c", displayDistance / 100, (displayDistance / 10) % 10, unitSymbolExtended);
} else { // < 10 miles or km - 2 decimal places
tfp_sprintf(buff, "%d.%02d%c", displayDistance / 100, displayDistance % 100, unitSymbolExtended);
}
}
strcat(result, buff);
}
static void osdFormatPID(char * buff, const char * label, const pidf_t * pid)
{
tfp_sprintf(buff, "%s %3d %3d %3d", label, pid->P, pid->I, pid->D);
@ -764,8 +802,7 @@ static void osdElementGForce(osdElementParms_t *element)
static void osdElementGpsFlightDistance(osdElementParms_t *element)
{
if (STATE(GPS_FIX) && STATE(GPS_FIX_HOME)) {
const int32_t distance = osdGetMetersToSelectedUnit(GPS_distanceFlownInCm / 100);
tfp_sprintf(element->buff, "%c%d%c", SYM_TOTAL_DISTANCE, distance, osdGetMetersToSelectedUnitSymbol());
osdFormatDistanceString(element->buff, GPS_distanceFlownInCm / 100, SYM_TOTAL_DISTANCE);
} else {
// We use this symbol when we don't have a FIX
tfp_sprintf(element->buff, "%c%c", SYM_TOTAL_DISTANCE, SYM_HYPHEN);
@ -793,8 +830,7 @@ static void osdElementGpsHomeDirection(osdElementParms_t *element)
static void osdElementGpsHomeDistance(osdElementParms_t *element)
{
if (STATE(GPS_FIX) && STATE(GPS_FIX_HOME)) {
const int32_t distance = osdGetMetersToSelectedUnit(GPS_distanceToHome);
tfp_sprintf(element->buff, "%c%d%c", SYM_HOMEFLAG, distance, osdGetMetersToSelectedUnitSymbol());
osdFormatDistanceString(element->buff, GPS_distanceToHome, SYM_HOMEFLAG);
} else {
element->buff[0] = SYM_HOMEFLAG;
// We use this symbol when we don't have a FIX

View File

@ -36,6 +36,7 @@ typedef struct osdElementParms_s {
typedef void (*osdElementDrawFn)(osdElementParms_t *element);
int osdConvertTemperatureToSelectedUnit(int tempInDegreesCelcius);
void osdFormatDistanceString(char *result, int distance, char leadingSymbol);
bool osdFormatRtcDateTime(char *buffer);
void osdFormatTime(char * buff, osd_timer_precision_e precision, timeUs_t time);
void osdFormatTimer(char *buff, bool showSymbol, bool usePrecision, int timerIndex);

View File

@ -461,6 +461,63 @@ TEST(OsdTest, TestStatsMetric)
displayPortTestBufferSubstring(2, row++, "MIN RSSI : 25%%");
}
/*
* Tests the calculation of statistics with metric unit output.
* (essentially an abridged version of the previous test
*/
TEST(OsdTest, TestStatsMetricDistanceUnits)
{
// given
// using metric unit system
osdConfigMutable()->units = OSD_UNIT_METRIC;
osdConfigMutable()->dynamic_distance_units = true;
// set timer 1 configuration to tenths precision
osdConfigMutable()->timers[OSD_TIMER_1] = OSD_TIMER(OSD_TIMER_SRC_TOTAL_ARMED, OSD_TIMER_PREC_TENTHS, 0);
// and
// default state values are set
setDefaultSimulationState();
// when
// the craft is armed
doTestArm();
// and
// these conditions occur during flight (simplified to less assignments than previous test)
rssi = 256;
gpsSol.groundSpeed = 800;
GPS_distanceToHome = 1150;
GPS_distanceFlownInCm = 1050000;
simulationBatteryVoltage = 1470;
simulationAltitude = 200;
simulationTime += 1e6;
osdRefresh(simulationTime);
osdRefresh(simulationTime);
simulationBatteryVoltage = 1520;
simulationTime += 1e6;
osdRefresh(simulationTime);
// and
// the craft is disarmed
doTestDisarm();
// then
// statistics screen should display the following
int row = 3;
displayPortTestBufferSubstring(2, row++, "2017-11-19 10:12:");
displayPortTestBufferSubstring(2, row++, "TOTAL ARM : 00:10.0");
displayPortTestBufferSubstring(2, row++, "LAST ARM : 00:02");
displayPortTestBufferSubstring(2, row++, "MAX ALTITUDE : 2.0%c", SYM_M);
displayPortTestBufferSubstring(2, row++, "MAX SPEED : 28");
displayPortTestBufferSubstring(2, row++, "MAX DISTANCE : 1.15%c", SYM_KM);
displayPortTestBufferSubstring(2, row++, "FLIGHT DISTANCE : 10.5%c", SYM_KM);
displayPortTestBufferSubstring(2, row++, "MIN BATTERY : 14.70%c", SYM_VOLT);
displayPortTestBufferSubstring(2, row++, "END BATTERY : 15.20%c", SYM_VOLT);
displayPortTestBufferSubstring(2, row++, "MIN RSSI : 25%%");
}
/*
* Tests activation of alarms and element flashing.
*/