FreeTRX/MDUV380_firmware/application/source/user_interface/menuGPS.c
2026-07-06 08:45:33 +02:00

1299 lines
44 KiB
C

/*
* Copyright (C) 2019-2025 Roger Clark, VK3KYY / G4KYF
* Daniel Caujolle-Bert, F1RMB
*
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation,
* incorporation in a commercial product or incorporation into a product or project which allows commercial use.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "user_interface/uiGlobals.h"
#include "user_interface/menuSystem.h"
#include "user_interface/uiUtilities.h"
#include "user_interface/uiLocalisation.h"
#include "interfaces/gps.h"
#include "usb/usb_com.h"
#if defined(HAS_GPS)
static void updateScreen(bool isFirstRun, bool forceRedraw);
static void handleEvent(uiEvent_t *ev);
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
#define CURRENT_PAGE_ON_MONOCHOME(p) if (menuDataGlobal.currentItemIndex == (p))
#else
#define CURRENT_PAGE_ON_MONOCHOME(p)
#endif
#define POLAR_GRAPHICS_X_OFFSET 0
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
#if defined(PLATFORM_RD5R)
#define POLAR_GRAPHICS_Y_OFFSET 31
#define RSSI_X_OFFSET 0
#define RSSI_BARS_Y_POS 60
#define MAIDENHEAD_HDOP_Y_POS 40
#define NUM_BARS 14
#define DOT_RADIUS 1
#define MAIDENHEAD_HDOP_MARGIN 0
#define VALUES_FONT_SIZE FONT_SIZE_3
#define VALUES_FONT_SIZE_SMALL FONT_SIZE_2
#define UNITS_FONT_SIZE FONT_SIZE_1
#define UNITS_Y_OFFSET 0
#define VALUES_RD5R_V_GAP FONT_SIZE_3_HEIGHT
#define VALUES_RD5R_V_OFFSET 4
#define VALUES_FONT_WIDTH 8
#define VALUES_FONT_HEIGHT 8
#define DIRECTION_REGION_V_OFFSET 0
#define DIRECTION_REGION_H_OFFSET 0
#define CARDINALS_REGION_HEIGHT (FONT_SIZE_2_HEIGHT - 1)
#define DIRECTION_SATS_X_POS 11
#define DIRECTION_SATS_Y_POS 28
#define DIRECTION_CARDINAL_OFFSET 8
#define DIRECTION_COURSE_X_POS 75
#define DIRECTION_COURSE_Y_POS 25
#define DIRECTION_ALT_X_POS 81
#define DIRECTION_ALT_Y_POS 28
#define DIRECTION_HDOP_X_POS 42
#define DIRECTION_HDOP_Y_POS 28
#define DIRECTION_HEADING_X_POS 28
#define DIRECTION_HEADING_Y_POS 31
#define DIRECTION_HEADING_RADIUS 16
#define TOP_ARROW_OFFSET (DIRECTION_HEADING_RADIUS - 6)
#define BASE_ARROW_OFFSET (DIRECTION_HEADING_RADIUS >> 1)
#define DIRECTION_FIXTYPE_X_POS 25
#define DIRECTION_FIXTYPE_Y_POS 28
#define DIRECTION_SPEED_X_POS 65
#define DIRECTION_SPEED_Y_POS 40
#define MAX_RADIUS 15
#define MAX_RSSI_Y_PIXELS 29
#else // PLATFORM_RD5R
#define POLAR_GRAPHICS_Y_OFFSET 40
#define RSSI_X_OFFSET 0
#define RSSI_BARS_Y_POS 54
#define MAIDENHEAD_HDOP_Y_POS 48
#define NUM_BARS 14
#define DOT_RADIUS 1
#define MAIDENHEAD_HDOP_MARGIN 0
#define VALUES_FONT_SIZE FONT_SIZE_3
#define VALUES_FONT_SIZE_SMALL FONT_SIZE_2
#define UNITS_FONT_SIZE FONT_SIZE_1
#define UNITS_Y_OFFSET 6
#define VALUES_RD5R_V_GAP 0
#define VALUES_RD5R_V_OFFSET 0
#define VALUES_FONT_WIDTH 8
#define VALUES_FONT_HEIGHT 16
#define DIRECTION_REGION_V_OFFSET 2
#define DIRECTION_REGION_H_OFFSET 5
#define CARDINALS_REGION_HEIGHT (FONT_SIZE_2_HEIGHT - 1)
#define DIRECTION_SATS_X_POS 11
#define DIRECTION_SATS_Y_POS 34
#define DIRECTION_CARDINAL_OFFSET 8
#define DIRECTION_COURSE_X_POS 75
#define DIRECTION_COURSE_Y_POS 29
#define DIRECTION_ALT_X_POS 81
#define DIRECTION_ALT_Y_POS 34
#define DIRECTION_HDOP_X_POS 42
#define DIRECTION_HDOP_Y_POS 34
#define DIRECTION_HEADING_X_POS 28
#define DIRECTION_HEADING_Y_POS 39
#define DIRECTION_HEADING_RADIUS 24
#define TOP_ARROW_OFFSET (DIRECTION_HEADING_RADIUS - 7)
#define BASE_ARROW_OFFSET ((DIRECTION_HEADING_RADIUS >> 1) + 1)
#define DIRECTION_FIXTYPE_X_POS 25
#define DIRECTION_FIXTYPE_Y_POS 36
#define DIRECTION_SPEED_X_POS 65
#define DIRECTION_SPEED_Y_POS 47
#define MAX_RADIUS 22
#define MAX_RSSI_Y_PIXELS 25
#endif // PLATFORM_RD5R
#else
#define POLAR_GRAPHICS_Y_OFFSET 72
#define RSSI_X_OFFSET 2
#define RSSI_BARS_Y_POS 120
#define MAIDENHEAD_HDOP_Y_POS 32
#define NUM_BARS 17
#define DOT_RADIUS 2
#define MAIDENHEAD_HDOP_MARGIN 8
#define VALUES_FONT_SIZE FONT_SIZE_4
#define VALUES_FONT_SIZE_SMALL FONT_SIZE_3
#define UNITS_FONT_SIZE FONT_SIZE_2
#define UNITS_Y_OFFSET 18
#define VALUES_RD5R_V_GAP 0
#define VALUES_RD5R_V_OFFSET 0
#define VALUES_FONT_WIDTH 16
#define VALUES_FONT_HEIGHT 32
#define DIRECTION_REGION_V_OFFSET 6
#define DIRECTION_REGION_H_OFFSET 13
#define CARDINALS_REGION_HEIGHT FONT_SIZE_3_HEIGHT
#define DIRECTION_SATS_X_POS 122
#define DIRECTION_SATS_Y_POS 30
#define DIRECTION_CARDINAL_OFFSET 10
#define DIRECTION_COURSE_X_POS 56
#define DIRECTION_COURSE_Y_POS 31
#define DIRECTION_ALT_X_POS 12
#define DIRECTION_ALT_Y_POS 71
#define DIRECTION_HDOP_X_POS 111
#define DIRECTION_HDOP_Y_POS 71
#define DIRECTION_HEADING_X_POS 27
#define DIRECTION_HEADING_Y_POS 39
#define DIRECTION_HEADING_RADIUS 25
#define TOP_ARROW_OFFSET (DIRECTION_HEADING_RADIUS - 8)
#define BASE_ARROW_OFFSET (DIRECTION_HEADING_RADIUS >> 1)
#define DIRECTION_FIXTYPE_X_POS 25
#define DIRECTION_FIXTYPE_Y_POS 36
#define DIRECTION_SPEED_X_POS 40
#define DIRECTION_SPEED_Y_POS 98
#define MAX_RADIUS 44
#define MAX_RSSI_Y_PIXELS 59
#endif
typedef enum
{
PAGE_COORDS = 0,
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
PAGE_RSSI,
#else
PAGE_RSSI = 0,
#endif
PAGE_POLAR,
PAGE_DIRECTION, // MD9600: Sat/HDOP/Alt, MD-UV380 everything in that page
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
PAGE_DIRECTION_2, // Heading/Course
#endif
PAGES_MAX
} Pages_t;
typedef enum
{
FIELD_SATS_CLEAR = (1 << 0),
FIELD_SATS_ZERO = (1 << 1),
FIELD_COURSE_CLEAR = (1 << 2),
FIELD_COURSE_ZERO = (1 << 3),
FIELD_ALT_CLEAR = (1 << 4),
FIELD_ALT_ZERO = (1 << 5),
FIELD_HDOP_CLEAR = (1 << 6),
FIELD_HDOP_ZERO = (1 << 7),
FIELD_SPEED_CLEAR = (1 << 8),
FIELD_SPEED_ZERO = (1 << 9),
FIELD_BACKGROUND = (1 << 10),
FIELD_ALL_CLEAR = (FIELD_SATS_CLEAR | FIELD_COURSE_CLEAR | FIELD_ALT_CLEAR | FIELD_HDOP_CLEAR | FIELD_SPEED_CLEAR),
FIELD_ALL_ZERO = (FIELD_SATS_ZERO | FIELD_COURSE_ZERO | FIELD_ALT_ZERO | FIELD_HDOP_ZERO | FIELD_SPEED_ZERO)
} DirectionFieldsFlags_t;
typedef enum
{
FIXTYPE_NONE = (1 << 1),
FIXTYPE_1D = (1 << 2),
FIXTYPE_2D = (1 << 3),
FIXTYPE_3D = (1 << 4),
} DirectionFixtype_t;
static menuStatus_t menuGPSExitCode = MENU_STATUS_SUCCESS;
static uint8_t prevGPSState = 0xff;
static uint32_t updateTick = 0;
static uint16_t prevSatsInView = 0xFFFF;
static char directions[16][4];
menuStatus_t menuGPS(uiEvent_t *ev, bool isFirstRun)
{
if (isFirstRun)
{
menuDataGlobal.numItems = PAGES_MAX;
// Prepare directions from cardinals
char N = currentLanguageGetSymbol(SYMBOLS_NORTH);
char S = currentLanguageGetSymbol(SYMBOLS_SOUTH);
char E = currentLanguageGetSymbol(SYMBOLS_EAST);
char W = currentLanguageGetSymbol(SYMBOLS_WEST);
sprintf(directions[0], "%c", N);
sprintf(directions[1], "%c%c%c", N, N, E);
sprintf(directions[2], "%c%c", N, E);
sprintf(directions[3], "%c%c%c", E, N, E);
sprintf(directions[4], "%c", E);
sprintf(directions[5], "%c%c%c", E, S, E);
sprintf(directions[6], "%c%c", S, E);
sprintf(directions[7], "%c%c%c", S, S, E);
sprintf(directions[8], "%c", S);
sprintf(directions[9], "%c%c%c", S, S, W);
sprintf(directions[10], "%c%c", S, W);
sprintf(directions[11], "%c%c%c", W, S, W);
sprintf(directions[12], "%c", W);
sprintf(directions[13], "%c%c%c", W, N, W);
sprintf(directions[14], "%c%c", N, W);
sprintf(directions[15], "%c%c%c", N, N, W);
prevGPSState = 0xff;
voicePromptsInit();
voicePromptsAppendPrompt(PROMPT_SILENCE);
voicePromptsAppendLanguageString(currentLanguage->gps);
voicePromptsAppendLanguageString(currentLanguage->menu);
voicePromptsAppendPrompt(PROMPT_SILENCE);
updateScreen(true, false);
return (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS);
}
else
{
menuGPSExitCode = MENU_STATUS_SUCCESS;
if (ev->hasEvent)
{
handleEvent(ev);
}
else
{
updateTick++;
// Limits updateScreen() calling frequency rate to every second, if a new GPS info is available, GPS_STATUS_TIME_UPDATED excepted.
// The functions called by updateScreen() are also filtering and limiting the screen redrawing, depending on the displayed page.
if ((updateTick > 1000) &&
(gpsData.Status & (GPS_STATUS_FIX_UPDATED | GPS_STATUS_FIXTYPE_UPDATED | GPS_STATUS_POSITION_UPDATED |
GPS_STATUS_HDOP_UPDATED | GPS_STATUS_COURSE_UPDATED | GPS_STATUS_SPEED_UPDATED | GPS_STATUS_HEIGHT_UPDATED |
GPS_STATUS_GPS_SATS_UPDATED | GPS_STATUS_BD_SATS_UPDATED)))
{
updateTick = 0;
updateScreen(false, false);
}
}
}
return menuGPSExitCode;
}
static int displayBars(gpsSatellitesData_t *satellitesArray, uint16_t satCount, bool numbersInBold, int dispPosition, float spacing, themeItem_t fgColour, themeItem_t bgColour)
{
#if ! defined(HAS_COLOURS)
#if ! defined(PLATFORM_RD5R)
uint8_t numberFont = (numbersInBold ? FONT_SIZE_1_BOLD : FONT_SIZE_1);
#endif
#else
uint8_t numberFont = FONT_SIZE_1;
#endif
for (uint16_t i = 0; i < satCount; i++)
{
if (satellitesArray[i].RSSI > 0)
{
int xPos = RSSI_X_OFFSET + 2 + dispPosition * spacing;
if (xPos > (DISPLAY_SIZE_X - spacing + 2))
{
break;
}
// Scale down (max @ 50dB), clipping higher
int8_t rssi = SAFE_MIN(MAX_RSSI_Y_PIXELS, ((int8_t)((MAX_RSSI_Y_PIXELS * 0.020) * satellitesArray[i].RSSI)));
#if ! defined(PLATFORM_RD5R)
char buf[3];
uint16_t sNumber = (satellitesArray[i].Number % 100); // limits to 2 last digits
displayThemeApply(THEME_ITEM_FG_GPS_NUMBER, THEME_ITEM_BG);
sprintf(buf, "%d", (sNumber % 10));
displayPrintAt(xPos + 1, RSSI_BARS_Y_POS, buf, numberFont);
sprintf(buf, "%d", (sNumber / 10));
displayPrintAt(xPos + 1, RSSI_BARS_Y_POS - 8, buf, numberFont);
#endif
displayThemeApply(fgColour, bgColour);
displayFillRect(xPos, RSSI_BARS_Y_POS - 12 - 2 - rssi, spacing - 2, rssi, false);
displayThemeResetToDefault();
dispPosition++;
if (dispPosition > (NUM_BARS - 1))
{
return dispPosition;
}
}
}
return dispPosition;
}
static uint16_t getTotalNumberOfUsableSats(void)
{
uint16_t n = 0;
for (uint16_t i = 0; i < gpsData.SatsInViewGP; i++)
{
n += ((gpsData.GPSatellites[i].RSSI > 0) ? 1 : 0);
}
for (uint16_t i = 0; i < gpsData.SatsInViewBD; i++)
{
n += ((gpsData.BDSatellites[i].RSSI > 0) ? 1 : 0);
}
return n;
}
static void displayDirectionFixType(DirectionFixtype_t type)
{
uint16_t fgColour, bgColour;
displayGetForegroundAndBackgroundColours(&fgColour, &bgColour);
displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
displayFillCircle(DIRECTION_HEADING_X_POS, DIRECTION_HEADING_Y_POS, (DIRECTION_HEADING_RADIUS >> 2) + 1, ((type & FIXTYPE_NONE) != 0));
if (type & (FIXTYPE_1D | FIXTYPE_2D | FIXTYPE_3D))
{
displayDrawCircle(DIRECTION_HEADING_X_POS, DIRECTION_HEADING_Y_POS, (DIRECTION_HEADING_RADIUS >> 2) + 1, true);
}
if (type & (FIXTYPE_2D | FIXTYPE_3D))
{
displayThemeResetToDefault();
displayPrintAt(DIRECTION_FIXTYPE_X_POS, DIRECTION_FIXTYPE_Y_POS, ((type & FIXTYPE_2D) ? "2" : "3"), FONT_SIZE_2);
}
displaySetForegroundAndBackgroundColours(fgColour, bgColour);
}
static void displayDirectionInfoBackground(DirectionFieldsFlags_t flags)
{
//
// Full background redrawing
//
if (flags & FIELD_BACKGROUND)
{
// Clear background
displayFillRect(0, 16, DISPLAY_SIZE_X, (DISPLAY_SIZE_Y - 16), true);
displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
CURRENT_PAGE_ON_MONOCHOME(PAGE_DIRECTION)
{
// Sats in view
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
displayPrintAt(DIRECTION_SATS_X_POS - 3, (DIRECTION_SATS_Y_POS - 7), currentLanguage->satellite_short, FONT_SIZE_1);
displayDrawFastHLine((DIRECTION_SATS_X_POS - 7), (DIRECTION_SATS_Y_POS - 9), 5, true);
displayDrawFastVLine((DIRECTION_SATS_X_POS - 7), (DIRECTION_SATS_Y_POS - 8), FONT_SIZE_3_HEIGHT + VALUES_RD5R_V_GAP + 6, true);
displayDrawFastHLine((DIRECTION_SATS_X_POS - 7), ((DIRECTION_SATS_Y_POS - 8) + FONT_SIZE_3_HEIGHT + VALUES_RD5R_V_GAP + 6), ((2 * 8) + 7), true);
displayDrawFastVLine(((DIRECTION_SATS_X_POS - 7) + ((2 * 8) + 7)), ((DIRECTION_SATS_Y_POS - 4) + (FONT_SIZE_3_HEIGHT + VALUES_RD5R_V_GAP - 1)), 4, true);
#else
displayPrintAt(DIRECTION_SATS_X_POS, (DIRECTION_SATS_Y_POS - 3), currentLanguage->satellite_short, FONT_SIZE_2);
displayDrawFastHLine((DIRECTION_SATS_X_POS - 6), (DIRECTION_SATS_Y_POS - 5), 5, true);
displayDrawFastVLine((DIRECTION_SATS_X_POS - 6), (DIRECTION_SATS_Y_POS - 4), FONT_SIZE_4_HEIGHT, true);
displayDrawFastHLine((DIRECTION_SATS_X_POS - 6), ((DIRECTION_SATS_Y_POS - 4) + FONT_SIZE_4_HEIGHT), ((2 * 16) + 7), true);
displayDrawFastVLine(((DIRECTION_SATS_X_POS - 6) + ((2 * 16) + 7)), ((DIRECTION_SATS_Y_POS - 4) + (FONT_SIZE_4_HEIGHT - 4)), 5, true);
#endif
// Altitude
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
displayPrintAt(DIRECTION_ALT_X_POS - 3, (DIRECTION_ALT_Y_POS - 7), currentLanguage->altitude, FONT_SIZE_1);
displayPrintAt((DIRECTION_ALT_X_POS + (4 * 8) + 2), (DIRECTION_ALT_Y_POS + 6), "m", FONT_SIZE_1);
displayDrawFastHLine((DIRECTION_ALT_X_POS - 7), (DIRECTION_ALT_Y_POS - 9), 5, true);
displayDrawFastVLine((DIRECTION_ALT_X_POS - 7), (DIRECTION_ALT_Y_POS - 8), FONT_SIZE_3_HEIGHT + VALUES_RD5R_V_GAP + 6, true);
displayDrawFastHLine((DIRECTION_ALT_X_POS - 7), ((DIRECTION_ALT_Y_POS - 8) + FONT_SIZE_3_HEIGHT + VALUES_RD5R_V_GAP + 6), ((4 * 8) + 7 + 8), true);
displayDrawFastVLine(((DIRECTION_ALT_X_POS - 7) + ((4 * 8) + 7) + 8), ((DIRECTION_ALT_Y_POS - 4) + (FONT_SIZE_3_HEIGHT + VALUES_RD5R_V_GAP - 1)), 4, true);
#else
displayPrintAt(DIRECTION_ALT_X_POS, (DIRECTION_ALT_Y_POS - 3), currentLanguage->altitude, FONT_SIZE_2);
displayPrintAt((DIRECTION_ALT_X_POS + (4 * 16) + 2), (DIRECTION_ALT_Y_POS + 18), "m", FONT_SIZE_2);
displayDrawFastHLine((DIRECTION_ALT_X_POS - 6), (DIRECTION_ALT_Y_POS - 5), 5, true);
displayDrawFastVLine((DIRECTION_ALT_X_POS - 6), (DIRECTION_ALT_Y_POS - 4), FONT_SIZE_4_HEIGHT, true);
displayDrawFastHLine((DIRECTION_ALT_X_POS - 6), ((DIRECTION_ALT_Y_POS - 4) + FONT_SIZE_4_HEIGHT), ((4 * 16) + 7 + 10), true);
displayDrawFastVLine(((DIRECTION_ALT_X_POS - 6) + ((4 * 16) + 7) + 10), ((DIRECTION_ALT_Y_POS - 4) + (FONT_SIZE_4_HEIGHT - 4)), 5, true);
#endif
// HDOP
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
displayPrintAt(DIRECTION_HDOP_X_POS - 3, (DIRECTION_HDOP_Y_POS - 7), "HDOP", FONT_SIZE_1);
displayPrintAt((DIRECTION_HDOP_X_POS + (2 * 8) + 2), (DIRECTION_HDOP_Y_POS + 6), "m", FONT_SIZE_1);
displayDrawFastHLine((DIRECTION_HDOP_X_POS - 7), (DIRECTION_HDOP_Y_POS - 9), 5, true);
displayDrawFastVLine((DIRECTION_HDOP_X_POS - 7), (DIRECTION_HDOP_Y_POS - 8), FONT_SIZE_3_HEIGHT + VALUES_RD5R_V_GAP + 6, true);
displayDrawFastHLine((DIRECTION_HDOP_X_POS - 7), ((DIRECTION_HDOP_Y_POS - 8) + FONT_SIZE_3_HEIGHT + VALUES_RD5R_V_GAP + 6), ((2 * 8) + 7 + 8), true);
displayDrawFastVLine(((DIRECTION_HDOP_X_POS - 7) + ((2 * 8) + 7) + 8), ((DIRECTION_HDOP_Y_POS - 4) + (FONT_SIZE_3_HEIGHT + VALUES_RD5R_V_GAP - 1)), 4, true);
#else
displayPrintAt(DIRECTION_HDOP_X_POS, (DIRECTION_HDOP_Y_POS - 3), "HDOP", FONT_SIZE_2);
displayPrintAt((DIRECTION_HDOP_X_POS + (2 * 16) + 2), (DIRECTION_HDOP_Y_POS + 18), "m", FONT_SIZE_2);
displayDrawFastHLine((DIRECTION_HDOP_X_POS - 6), (DIRECTION_HDOP_Y_POS - 5), 5, true);
displayDrawFastVLine((DIRECTION_HDOP_X_POS - 6), (DIRECTION_HDOP_Y_POS - 4), FONT_SIZE_4_HEIGHT, true);
displayDrawFastHLine((DIRECTION_HDOP_X_POS - 6), ((DIRECTION_HDOP_Y_POS - 4) + FONT_SIZE_4_HEIGHT), ((2 * 16) + 7 + 10), true);
displayDrawFastVLine(((DIRECTION_HDOP_X_POS - 6) + ((2 * 16) + 7) + 10), ((DIRECTION_HDOP_Y_POS - 4) + (FONT_SIZE_4_HEIGHT - 4)), 5, true);
#endif
}
CURRENT_PAGE_ON_MONOCHOME(PAGE_DIRECTION_2)
{
// Course
char deg[2] = { 176, 0 };
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
displayPrintAt((DIRECTION_COURSE_X_POS + (3 * 8)), (DIRECTION_COURSE_Y_POS + 1), deg, FONT_SIZE_1);
displayFillRect(((DIRECTION_COURSE_X_POS + 12) - 12 - 5), (DIRECTION_COURSE_Y_POS - 6), 4, 2, false);
displayFillRect(((DIRECTION_COURSE_X_POS + 12) + 12 + 1), (DIRECTION_COURSE_Y_POS - 6), 4, 2, false);
displayDrawFastVLine((DIRECTION_COURSE_X_POS - 7), (((DIRECTION_COURSE_Y_POS - 3) + FONT_SIZE_3_HEIGHT + (VALUES_RD5R_V_GAP >> 1) + 2) - 4), 4, true);
displayDrawFastHLine((DIRECTION_COURSE_X_POS - 6), ((DIRECTION_COURSE_Y_POS - 3) + FONT_SIZE_3_HEIGHT + (VALUES_RD5R_V_GAP >> 1) + 1), ((3 * 8) + 7 + 7), true);
displayDrawFastVLine(((DIRECTION_COURSE_X_POS - 1) + ((3 * 8) + 7 + 2)), (DIRECTION_COURSE_Y_POS - 8), (FONT_SIZE_3_HEIGHT + (VALUES_RD5R_V_GAP >> 1) + 7), true);
displayDrawFastHLine(((DIRECTION_COURSE_X_POS - 1) + ((3 * 8) + 7 + 2) - 4), (DIRECTION_COURSE_Y_POS - 9), 5, true);
#else
displayPrintAt((DIRECTION_COURSE_X_POS + (3 * 16) + 1), (DIRECTION_COURSE_Y_POS + 5), deg, FONT_SIZE_2);
displayFillRect(((DIRECTION_COURSE_X_POS + 24) - 12 - 8), (DIRECTION_COURSE_Y_POS - 4), 7, 2, false);
displayFillRect(((DIRECTION_COURSE_X_POS + 24) + 12 + 1), (DIRECTION_COURSE_Y_POS - 4), 7, 2, false);
displayDrawFastVLine((DIRECTION_COURSE_X_POS - 2), (((DIRECTION_COURSE_Y_POS - 5) + FONT_SIZE_4_HEIGHT) - 4), 5, true);
displayDrawFastHLine((DIRECTION_COURSE_X_POS - 1), ((DIRECTION_COURSE_Y_POS - 5) + FONT_SIZE_4_HEIGHT), ((3 * 16) + 7 + 2), true);
displayDrawFastVLine(((DIRECTION_COURSE_X_POS - 1) + ((3 * 16) + 7 + 2)), (DIRECTION_COURSE_Y_POS - 5), (FONT_SIZE_4_HEIGHT + 1), true);
displayDrawFastHLine(((DIRECTION_COURSE_X_POS - 1) + ((3 * 16) + 7 + 2) - 4), (DIRECTION_COURSE_Y_POS - 6), 5, true);
#endif
// Heading
{
for (int16_t i = 0; i < 360; i += 15)
{
int16_t x1 = (int16_t)(DIRECTION_HEADING_X_POS + (DIRECTION_HEADING_RADIUS) * cos((double)(i * DEG_TO_RAD)));
int16_t y1 = (int16_t)(DIRECTION_HEADING_Y_POS + (DIRECTION_HEADING_RADIUS) * sin((double)(i * DEG_TO_RAD)));
int16_t x2 = (int16_t)(DIRECTION_HEADING_X_POS + (DIRECTION_HEADING_RADIUS - ((i % 45) ? 2 : 4)) * cos((double)(i * DEG_TO_RAD)));
int16_t y2 = (int16_t)(DIRECTION_HEADING_Y_POS + (DIRECTION_HEADING_RADIUS - ((i % 45) ? 2 : 4)) * sin((double)(i * DEG_TO_RAD)));
if (i == 270)
{
displayThemeApply(THEME_ITEM_FG_GPS_COLOUR, THEME_ITEM_BG);
displayDrawLine((x1 - 1), y1, (x2 - 1), y2, true);
}
displayDrawLine(x1, y1, x2, y2, true);
if (i == 270)
{
displayDrawLine((x1 + 1), y1, (x2 + 1), y2, true);
displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
}
}
displayDrawCircle(DIRECTION_HEADING_X_POS, DIRECTION_HEADING_Y_POS, DIRECTION_HEADING_RADIUS, true);
displayDirectionFixType(FIXTYPE_NONE);
}
// Speed
displayPrintAt((DIRECTION_SPEED_X_POS + (3 * VALUES_FONT_WIDTH) + 2), (DIRECTION_SPEED_Y_POS + UNITS_Y_OFFSET), "km/h", UNITS_FONT_SIZE);
}
displayThemeResetToDefault();
}
//
// Region clearing
//
CURRENT_PAGE_ON_MONOCHOME(PAGE_DIRECTION)
{
if (flags & (FIELD_SATS_CLEAR | FIELD_SATS_ZERO))
{
displayFillRect(DIRECTION_SATS_X_POS, (DIRECTION_SATS_Y_POS + VALUES_RD5R_V_OFFSET + DIRECTION_REGION_V_OFFSET), (2 * VALUES_FONT_WIDTH), (VALUES_FONT_HEIGHT - DIRECTION_REGION_H_OFFSET), true);
}
if (flags & (FIELD_ALT_CLEAR | FIELD_ALT_ZERO))
{
displayFillRect(DIRECTION_ALT_X_POS, (DIRECTION_ALT_Y_POS + VALUES_RD5R_V_OFFSET + DIRECTION_REGION_V_OFFSET), (4 * VALUES_FONT_WIDTH), (VALUES_FONT_HEIGHT - DIRECTION_REGION_H_OFFSET), true);
}
if (flags & (FIELD_HDOP_CLEAR | FIELD_HDOP_ZERO))
{
displayFillRect(DIRECTION_HDOP_X_POS, (DIRECTION_HDOP_Y_POS + VALUES_RD5R_V_OFFSET + DIRECTION_REGION_V_OFFSET), (2 * VALUES_FONT_WIDTH), (VALUES_FONT_HEIGHT - DIRECTION_REGION_H_OFFSET), true);
}
}
CURRENT_PAGE_ON_MONOCHOME(PAGE_DIRECTION_2)
{
if (flags & (FIELD_COURSE_CLEAR | FIELD_COURSE_ZERO))
{
displayFillRect(((DIRECTION_COURSE_X_POS + ((VALUES_FONT_WIDTH * 3) >> 1)) - 12), (DIRECTION_COURSE_Y_POS - DIRECTION_CARDINAL_OFFSET), (3 * 8), CARDINALS_REGION_HEIGHT, true);
displayFillRect(DIRECTION_COURSE_X_POS, (DIRECTION_COURSE_Y_POS + DIRECTION_REGION_V_OFFSET), (3 * VALUES_FONT_WIDTH), (VALUES_FONT_HEIGHT - DIRECTION_REGION_H_OFFSET), true);
displayFillCircle(DIRECTION_HEADING_X_POS, DIRECTION_HEADING_Y_POS, (DIRECTION_HEADING_RADIUS - 6), false);
displayDirectionFixType(FIXTYPE_NONE);
}
if (flags & (FIELD_SPEED_CLEAR | FIELD_SPEED_ZERO))
{
displayFillRect(DIRECTION_SPEED_X_POS, (DIRECTION_SPEED_Y_POS + DIRECTION_REGION_V_OFFSET), (3 * VALUES_FONT_WIDTH), (VALUES_FONT_HEIGHT - DIRECTION_REGION_H_OFFSET), true);
}
}
//
// Zeroing values
//
CURRENT_PAGE_ON_MONOCHOME(PAGE_DIRECTION)
{
if (flags & FIELD_SATS_ZERO)
{
displayPrintAt(DIRECTION_SATS_X_POS, DIRECTION_SATS_Y_POS + VALUES_RD5R_V_OFFSET, "--", VALUES_FONT_SIZE);
}
if (flags & FIELD_ALT_ZERO)
{
displayPrintAt(DIRECTION_ALT_X_POS, DIRECTION_ALT_Y_POS + VALUES_RD5R_V_OFFSET, "----", VALUES_FONT_SIZE);
}
if (flags & FIELD_HDOP_ZERO)
{
displayPrintAt(DIRECTION_HDOP_X_POS, DIRECTION_HDOP_Y_POS + VALUES_RD5R_V_OFFSET, "--", VALUES_FONT_SIZE);
}
}
CURRENT_PAGE_ON_MONOCHOME(PAGE_DIRECTION_2)
{
if (flags & FIELD_COURSE_ZERO)
{
displayPrintAt(((DIRECTION_COURSE_X_POS + ((VALUES_FONT_WIDTH * 3) >> 1)) - 12), (DIRECTION_COURSE_Y_POS - DIRECTION_CARDINAL_OFFSET), "---", VALUES_FONT_SIZE_SMALL);
displayPrintAt(DIRECTION_COURSE_X_POS, DIRECTION_COURSE_Y_POS, "---", VALUES_FONT_SIZE);
}
if (flags & FIELD_SPEED_ZERO)
{
displayPrintAt(DIRECTION_SPEED_X_POS, DIRECTION_SPEED_Y_POS, "---", VALUES_FONT_SIZE);
}
}
}
static void displayDirectionCourseZeroing(void)
{
DirectionFixtype_t ft = FIXTYPE_1D;
if (gpsData.Status & (GPS_STATUS_2D_FIX | GPS_STATUS_3D_FIX))
{
ft &= ~FIXTYPE_1D;
ft |= ((gpsData.Status & GPS_STATUS_2D_FIX) ? FIXTYPE_2D : FIXTYPE_3D);
}
displayDirectionInfoBackground(FIELD_COURSE_ZERO);
displayDirectionFixType(ft);
}
static bool displayDirectionInfo(bool isFirstRun, bool forceRedraw)
{
char buffer[SCREEN_LINE_BUFFER_SIZE];
if (gpsData.Status & GPS_STATUS_HAS_FIX)
{
uint16_t inViewAndUsableSats = getTotalNumberOfUsableSats();
if (isFirstRun || forceRedraw || (prevSatsInView != inViewAndUsableSats) ||
(gpsData.Status & (GPS_STATUS_FIX_UPDATED | GPS_STATUS_COURSE_UPDATED |
GPS_STATUS_SPEED_UPDATED | GPS_STATUS_HDOP_UPDATED | GPS_STATUS_FIXTYPE_UPDATED | GPS_STATUS_HEIGHT_UPDATED)))
{
// Draw the background
if (isFirstRun || forceRedraw)
{
displayDirectionInfoBackground(FIELD_BACKGROUND);
}
// Update, only clear screen regions
if (((isFirstRun || forceRedraw) == false) &&
((gpsData.Status & (GPS_STATUS_FIX_UPDATED | GPS_STATUS_COURSE_UPDATED |
GPS_STATUS_SPEED_UPDATED | GPS_STATUS_HDOP_UPDATED | GPS_STATUS_FIXTYPE_UPDATED | GPS_STATUS_HEIGHT_UPDATED))))
{
DirectionFieldsFlags_t f = 0;
if (gpsData.Status & GPS_STATUS_COURSE_UPDATED)
{
f |= FIELD_COURSE_CLEAR;
}
if (gpsData.Status & GPS_STATUS_SPEED_UPDATED)
{
f |= FIELD_SPEED_CLEAR;
}
if (gpsData.Status & GPS_STATUS_HDOP_UPDATED)
{
f |= FIELD_HDOP_CLEAR;
}
if (gpsData.Status & GPS_STATUS_HEIGHT_UPDATED)
{
f |= FIELD_ALT_CLEAR;
}
displayDirectionInfoBackground(f);
}
if (isFirstRun || forceRedraw || (prevSatsInView != inViewAndUsableSats))
{
CURRENT_PAGE_ON_MONOCHOME(PAGE_DIRECTION)
{
displayDirectionInfoBackground(FIELD_SATS_CLEAR);
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%2u", inViewAndUsableSats);
displayPrintAt(DIRECTION_SATS_X_POS, DIRECTION_SATS_Y_POS + VALUES_RD5R_V_OFFSET, buffer, VALUES_FONT_SIZE);
}
}
CURRENT_PAGE_ON_MONOCHOME(PAGE_DIRECTION_2)
{
if (isFirstRun || forceRedraw || (gpsData.Status & (GPS_STATUS_FIX_UPDATED | GPS_STATUS_FIXTYPE_UPDATED | GPS_STATUS_COURSE_UPDATED)))
{
DirectionFixtype_t ft = FIXTYPE_1D;
if (gpsData.Status & (GPS_STATUS_2D_FIX | GPS_STATUS_3D_FIX))
{
ft &= ~FIXTYPE_1D;
ft |= ((gpsData.Status & GPS_STATUS_2D_FIX) ? FIXTYPE_2D : FIXTYPE_3D);
}
displayDirectionFixType(ft);
}
if (isFirstRun || forceRedraw || (gpsData.Status & GPS_STATUS_COURSE_UPDATED))
{
int dir = (int)(((gpsData.CourseInHundredthDeg * 1E-2) + 11.25f) / 22.5f);
if (gpsData.SpeedInHundredthKn > GPS_SPEED_THRESHOLD_MIN)
{
int16_t x1, x2, x3, y1, y2, y3;
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s", directions[dir % 16]);
displayPrintAt(((DIRECTION_COURSE_X_POS + ((VALUES_FONT_WIDTH * 3) >> 1)) - ((strlen(buffer) * 8) >> 1)), (DIRECTION_COURSE_Y_POS - DIRECTION_CARDINAL_OFFSET), buffer, VALUES_FONT_SIZE_SMALL);
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%03u", (uint16_t)(gpsData.CourseInHundredthDeg * 1E-2));
displayPrintAt(DIRECTION_COURSE_X_POS, DIRECTION_COURSE_Y_POS, buffer, VALUES_FONT_SIZE);
displayThemeApply(THEME_ITEM_FG_GPS_COLOUR, THEME_ITEM_BG);
// Needle
float needle = ((gpsData.CourseInHundredthDeg * 1E-2) - 90.0);
if (needle > 360.0)
{
needle -= 360.0;
}
needle *= DEG_TO_RAD;
x1 = (int16_t)(DIRECTION_HEADING_X_POS + 1 + TOP_ARROW_OFFSET * cos(needle));
y1 = (int16_t)(DIRECTION_HEADING_Y_POS + 1 + TOP_ARROW_OFFSET * sin(needle));
x2 = (int16_t)(DIRECTION_HEADING_X_POS + 1 + BASE_ARROW_OFFSET * cos(needle + (30 * DEG_TO_RAD)));
y2 = (int16_t)(DIRECTION_HEADING_Y_POS + 1 + BASE_ARROW_OFFSET * sin(needle + (30 * DEG_TO_RAD)));
x3 = (int16_t)(DIRECTION_HEADING_X_POS + 1 + BASE_ARROW_OFFSET * cos(needle - (30 * DEG_TO_RAD)));
y3 = (int16_t)(DIRECTION_HEADING_Y_POS + 1 + BASE_ARROW_OFFSET * sin(needle - (30 * DEG_TO_RAD)));
displayFillTriangle(x1, y1, x2, y2, x3, y3, true);
displayThemeResetToDefault();
}
else
{
displayDirectionCourseZeroing();
}
}
if (isFirstRun || forceRedraw || (gpsData.Status & GPS_STATUS_SPEED_UPDATED))
{
uint16_t speed = (uint16_t)((gpsData.SpeedInHundredthKn * 1E-2) * KMPH_PER_KNOT);
// Speed < 1km/h and Course is not not updated => display zero course
if ((speed == 0) && ((isFirstRun || forceRedraw) == false) && ((gpsData.Status & GPS_STATUS_COURSE_UPDATED) == 0))
{
displayDirectionCourseZeroing();
}
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%03u", speed);
displayPrintAt(DIRECTION_SPEED_X_POS, DIRECTION_SPEED_Y_POS, buffer, VALUES_FONT_SIZE);
}
}
CURRENT_PAGE_ON_MONOCHOME(PAGE_DIRECTION)
{
if (isFirstRun || forceRedraw || (gpsData.Status & GPS_STATUS_HDOP_UPDATED))
{
if (gpsData.AccuracyInCm < 100)
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s", "<1");
}
else
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%2u", SAFE_MIN((gpsData.AccuracyInCm / 100), 99));
}
displayPrintAt(DIRECTION_HDOP_X_POS, DIRECTION_HDOP_Y_POS + VALUES_RD5R_V_OFFSET, buffer, VALUES_FONT_SIZE);
}
}
if (isFirstRun || forceRedraw || (gpsData.Status & GPS_STATUS_HEIGHT_UPDATED))
{
CURRENT_PAGE_ON_MONOCHOME(PAGE_DIRECTION)
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%4d", gpsData.HeightInM);
displayPrintAt(DIRECTION_ALT_X_POS, DIRECTION_ALT_Y_POS + VALUES_RD5R_V_OFFSET, buffer, VALUES_FONT_SIZE);
}
}
return true;
}
}
else
{
// |--> fix got lost
if (isFirstRun || forceRedraw || (gpsData.Status & GPS_STATUS_FIX_UPDATED))
{
displayDirectionInfoBackground((FIELD_BACKGROUND | FIELD_ALL_ZERO));
return true;
}
}
return false;
}
static bool displayRSSI(bool isFirstRun, bool forceRedraw)
{
float spacing = 9.0;
int dispPosition = 0;
if (isFirstRun || forceRedraw || (gpsData.Status & (GPS_STATUS_GPS_SATS_UPDATED | GPS_STATUS_BD_SATS_UPDATED)))
{
// Update, only clear screen regions
if (((isFirstRun || forceRedraw) == false) && (gpsData.Status & (GPS_STATUS_GPS_SATS_UPDATED | GPS_STATUS_BD_SATS_UPDATED)))
{
// Bars.
displayFillRect(0, (RSSI_BARS_Y_POS - 12 - 2 - MAX_RSSI_Y_PIXELS), DISPLAY_SIZE_X, MAX_RSSI_Y_PIXELS, true);
#if ! defined(PLATFORM_RD5R)
// Numbers
displayFillRect(0, (RSSI_BARS_Y_POS - 8), DISPLAY_SIZE_X, (8 * 2), true);
#endif
}
if (isFirstRun || forceRedraw)
{
for(int i = 0; i < NUM_BARS; i++)
{
int xPos = 2 + (i * spacing);
displayFillRect(RSSI_X_OFFSET + xPos, RSSI_BARS_Y_POS - 12 - 2, spacing - 2, 2, false);
}
}
if ((gpsData.SatsInViewGP + gpsData.SatsInViewBD) > 0)
{
dispPosition = displayBars(gpsData.GPSatellites, gpsData.SatsInViewGP, false, dispPosition, spacing, THEME_ITEM_FG_GPS_COLOUR, THEME_ITEM_BG);
displayBars(gpsData.BDSatellites, gpsData.SatsInViewBD, true, dispPosition, spacing, THEME_ITEM_FG_BD_COLOUR, THEME_ITEM_BG);
}
return true;
}
return false;
}
static void displaySatellitesPolar(gpsSatellitesData_t *satellitesArray, bool circle)
{
float az, elFactor, s, c;
int x, y;
for (int i = 0; ((i < GPS_STORAGE_MAX) && (satellitesArray[i].Number != 0)); i++)
{
if (satellitesArray[i].RSSI > 0)
{
az = (M_PI * satellitesArray[i].Az) / 180.0;
if (satellitesArray[i].El <= 0)
{
elFactor = 0;// put in the middle of the screen if there is no real elevation data
}
else
{
elFactor = (1 - (fabs(satellitesArray[i].El) / 90)) * MAX_RADIUS;
}
s = sin(az);
c = cos(az);
x = POLAR_GRAPHICS_X_OFFSET + (DISPLAY_SIZE_X / 2) + (s * elFactor);
y = POLAR_GRAPHICS_Y_OFFSET - (c * elFactor);
#if ! defined(HAS_COLOURS)
if (circle)
{
displayFillCircle(x, y, (DOT_RADIUS + 1), false);
displayDrawCircle(x, y, (DOT_RADIUS + 1), true);
}
else
#endif
{
displayFillCircle(x, y, (DOT_RADIUS + 1), true);
}
}
}
}
static bool displayPolar(bool isFirstRun, bool forceRedraw)
{
if (isFirstRun || forceRedraw || (gpsData.Status & (GPS_STATUS_GPS_SATS_UPDATED | GPS_STATUS_BD_SATS_UPDATED)))
{
displayThemeApply(THEME_ITEM_FG_POLAR_DRAWING, THEME_ITEM_BG);
// Update, only clear screen region
if (((isFirstRun || forceRedraw) == false) && (gpsData.Status & (GPS_STATUS_GPS_SATS_UPDATED | GPS_STATUS_BD_SATS_UPDATED)))
{
displayFillCircle(POLAR_GRAPHICS_X_OFFSET + (DISPLAY_SIZE_X / 2), POLAR_GRAPHICS_Y_OFFSET, MAX_RADIUS, false);
}
displayDrawCircle(POLAR_GRAPHICS_X_OFFSET + (DISPLAY_SIZE_X / 2), POLAR_GRAPHICS_Y_OFFSET, MAX_RADIUS, true);
displayDrawCircle(POLAR_GRAPHICS_X_OFFSET + (DISPLAY_SIZE_X / 2), POLAR_GRAPHICS_Y_OFFSET, (MAX_RADIUS / 3 ) * 2, true);
displayDrawCircle(POLAR_GRAPHICS_X_OFFSET + (DISPLAY_SIZE_X / 2), POLAR_GRAPHICS_Y_OFFSET, (MAX_RADIUS / 3 ), true);
#if defined(PLATFORM_RD5R)
displayDrawFastVLine(POLAR_GRAPHICS_X_OFFSET + (DISPLAY_SIZE_X / 2),
(POLAR_GRAPHICS_Y_OFFSET) - MAX_RADIUS, MAX_RADIUS * 2, true);
displayDrawFastHLine((POLAR_GRAPHICS_X_OFFSET + (DISPLAY_SIZE_X / 2)) - MAX_RADIUS,
(POLAR_GRAPHICS_Y_OFFSET), MAX_RADIUS * 2, true);
#else
displayDrawFastVLine(POLAR_GRAPHICS_X_OFFSET + (DISPLAY_SIZE_X / 2),
(POLAR_GRAPHICS_Y_OFFSET) - (MAX_RADIUS + 4), (MAX_RADIUS + 4) * 2, true);
displayDrawFastHLine((POLAR_GRAPHICS_X_OFFSET + (DISPLAY_SIZE_X / 2)) - (MAX_RADIUS + 4),
(POLAR_GRAPHICS_Y_OFFSET), (MAX_RADIUS + 4) * 2, true);
#endif
if ((gpsData.SatsInViewGP + gpsData.SatsInViewBD) > 0)
{
displayThemeApply(THEME_ITEM_FG_GPS_COLOUR, THEME_ITEM_BG);
displaySatellitesPolar(gpsData.GPSatellites, false);
displayThemeApply(THEME_ITEM_FG_BD_COLOUR, THEME_ITEM_BG);
displaySatellitesPolar(gpsData.BDSatellites, true);
}
displayThemeResetToDefault();
return true;
}
return false;
}
static bool displayCoords(bool isFirstRun, bool forceRedraw)
{
char buffer[LOCATION_TEXT_BUFFER_SIZE]; // larger screen is available on MD-UV380 and friends
if (gpsData.Status & GPS_STATUS_HAS_FIX)
{
if (isFirstRun || forceRedraw || (gpsData.Status & (GPS_STATUS_FIX_UPDATED | GPS_STATUS_POSITION_UPDATED | GPS_STATUS_HDOP_UPDATED | GPS_STATUS_HEIGHT_UPDATED)))
{
const uint16_t coords_Y_positions[] = {
#if defined(PLATFORM_RD5R)
16U, 26U
#else
16U, 32U
#endif
};
char maidenheadBuf[7];
char *p;
// Update, only clear screen regions
if (((isFirstRun || forceRedraw) == false) && (gpsData.Status & (GPS_STATUS_FIX_UPDATED | GPS_STATUS_POSITION_UPDATED | GPS_STATUS_HDOP_UPDATED | GPS_STATUS_HEIGHT_UPDATED)))
{
displayFillRect(0, (int16_t)coords_Y_positions[0], DISPLAY_SIZE_X, FONT_SIZE_3_HEIGHT, true);
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
displayFillRect(0, (int16_t)coords_Y_positions[1], DISPLAY_SIZE_X, FONT_SIZE_3_HEIGHT, true);
#endif
displayFillRect(0, MAIDENHEAD_HDOP_Y_POS, DISPLAY_SIZE_X, FONT_SIZE_3_HEIGHT, true);
}
buildLocationAndMaidenheadStrings(buffer, maidenheadBuf, true);
p = strchr(buffer, ' ');
*p = 0; // split locations Lat/Lon parts
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
// Lat: Centered
displayPrintAt(((DISPLAY_SIZE_X - (8 * 8)) >> 1), coords_Y_positions[0], buffer, FONT_SIZE_3);
// Lon: Centered
displayPrintAt(((DISPLAY_SIZE_X - (9 * 8)) >> 1), coords_Y_positions[1], (p + 1), FONT_SIZE_3);
#else
// Lat: Left
displayPrintAt(5, 16, buffer, FONT_SIZE_3);
// Lon: Right
displayPrintAt((DISPLAY_SIZE_X - ((9 * 8) + 5)), coords_Y_positions[0], (p + 1), FONT_SIZE_3);
#endif
displayPrintAt(MAIDENHEAD_HDOP_MARGIN, MAIDENHEAD_HDOP_Y_POS, maidenheadBuf, FONT_SIZE_3);
if (gpsData.AccuracyInCm < 100)
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s", "+/-<1m");
}
else
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "+/-%dm", (gpsData.AccuracyInCm / 100));
}
displayPrintAt((DISPLAY_SIZE_X - ((strlen(buffer) * 8) + MAIDENHEAD_HDOP_MARGIN)), MAIDENHEAD_HDOP_Y_POS, buffer, FONT_SIZE_3);
return true;
}
}
return false;
}
static bool displayGPSData(bool isFirstRun, bool forceRedraw)
{
char buffer[SCREEN_LINE_BUFFER_SIZE];
bool res = false;
if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) > GPS_MODE_OFF)
{
switch(menuDataGlobal.currentItemIndex)
{
case PAGE_COORDS: // with PAGE_RSSI on MD-UV380
res = displayCoords(isFirstRun, forceRedraw);
#if !(defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12))
res |= displayRSSI(isFirstRun, forceRedraw);
#endif
break;
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
case PAGE_RSSI:
res = displayRSSI(isFirstRun, forceRedraw);
break;
#endif
case PAGE_POLAR:
res = displayPolar(isFirstRun, forceRedraw);
break;
case PAGE_DIRECTION:
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
case PAGE_DIRECTION_2:
#endif
res = displayDirectionInfo(isFirstRun, forceRedraw);
break;
case PAGES_MAX:
break;
}
//
// Update the Voice Prompts
//
if (gpsData.Status & GPS_STATUS_HAS_FIX)
{
bool vpIsPlaying = voicePromptsIsPlaying();
uint16_t inViewAndUsableSats = getTotalNumberOfUsableSats();
if (((isFirstRun || vpIsPlaying) == false) &&
((prevSatsInView != inViewAndUsableSats) || (gpsData.Status & (GPS_STATUS_FIX_UPDATED | GPS_STATUS_POSITION_UPDATED | GPS_STATUS_HEIGHT_UPDATED))))
{
prevSatsInView = inViewAndUsableSats;
if (gpsData.Status & GPS_STATUS_FIX_UPDATED)
{
if (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_THRESHOLD)
{
voicePromptsInitWithOverride();
}
else
{
soundSetMelody(MELODY_ACK_BEEP);
}
}
else
{
voicePromptsInit();
}
if (!vpIsPlaying)
{
if (gpsData.Status & GPS_STATUS_HAS_POSITION)
{
char locationBuffer[LOCATION_TEXT_BUFFER_SIZE];
char maidenheadBuf[7];
buildLocationAndMaidenheadStrings(locationBuffer, maidenheadBuf, true);
voicePromptsAppendLanguageString(currentLanguage->location);
voicePromptsAppendString(locationBuffer);
voicePromptsAppendPrompt(PROMPT_SILENCE);
voicePromptsAppendPrompt(PROMPT_SILENCE);
voicePromptsAppendPrompt(PROMPT_SILENCE);
voicePromptsAppendString(maidenheadBuf);
}
if (prevSatsInView != 0xFFFF)
{
voicePromptsAppendLanguageString(currentLanguage->satellite);
voicePromptsAppendInteger(prevSatsInView);
}
if (gpsData.Status & GPS_STATUS_HAS_HEIGHT)
{
voicePromptsAppendLanguageString(currentLanguage->altitude);
voicePromptsAppendInteger(gpsData.HeightInM);
}
}
}
}
else
{
// |--> fix got lost
if (isFirstRun || forceRedraw || (gpsData.Status & GPS_STATUS_FIX_UPDATED))
{
prevSatsInView = 0xFFFF;
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
if (menuDataGlobal.currentItemIndex < PAGE_RSSI) // Do not display anything on RSSI, Polar and Direction* screens
#else
if (menuDataGlobal.currentItemIndex < PAGE_POLAR) // Do not display anything on Polar and Direction* screens
#endif
{
// Update, only clear screen regions
if (((isFirstRun || forceRedraw) == false) && (gpsData.Status & (GPS_STATUS_FIX_UPDATED)))
{
displayFillRect(0, 16, DISPLAY_SIZE_X, FONT_SIZE_3_HEIGHT, true);
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
displayFillRect(0, 32, DISPLAY_SIZE_X, FONT_SIZE_3_HEIGHT, true);
#endif
displayFillRect(0, MAIDENHEAD_HDOP_Y_POS, DISPLAY_SIZE_X, FONT_SIZE_3_HEIGHT, true);
}
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s", currentLanguage->gps_acquiring);
displayPrintCentered(16, buffer, FONT_SIZE_3);
}
if (!isFirstRun)
{
if (gpsData.Status & GPS_STATUS_FIX_UPDATED)
{
if (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_THRESHOLD)
{
voicePromptsInitWithOverride();
}
else
{
soundSetMelody(MELODY_NACK_BEEP);
}
}
else
{
voicePromptsInit();
}
}
voicePromptsAppendLanguageString(currentLanguage->gps_acquiring);
res = true;
}
}
}
else
{
if (isFirstRun || forceRedraw || (prevGPSState != SETTINGS_GPS_MODE_GET(nonVolatileSettings)))
{
if ((isFirstRun || forceRedraw) == false) // only clear if the GPS power status has changed
{
displayFillRect(0, (((DISPLAY_SIZE_Y - FONT_SIZE_3_HEIGHT) >> 1) + (FONT_SIZE_3_HEIGHT >> 1)), DISPLAY_SIZE_X, FONT_SIZE_3_HEIGHT, true);
}
if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) == GPS_MODE_OFF)
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s %s", currentLanguage->gps, currentLanguage->off);
displayThemeApply(THEME_ITEM_FG_WARNING_NOTIFICATION, THEME_ITEM_BG);
displayPrintCentered(((DISPLAY_SIZE_Y - FONT_SIZE_3_HEIGHT) >> 1) + (FONT_SIZE_3_HEIGHT >> 1), buffer, FONT_SIZE_3);
displayThemeResetToDefault();
if (forceRedraw == false)
{
voicePromptsAppendLanguageString(currentLanguage->gps);
voicePromptsAppendLanguageString(currentLanguage->off);
}
}
else
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s %s", currentLanguage->no, currentLanguage->gps);
displayThemeApply(THEME_ITEM_FG_ERROR_NOTIFICATION, THEME_ITEM_BG);
displayPrintCentered(((DISPLAY_SIZE_Y - FONT_SIZE_3_HEIGHT) >> 1) + (FONT_SIZE_3_HEIGHT >> 1), buffer, FONT_SIZE_3);
displayThemeResetToDefault();
if (forceRedraw == false)
{
voicePromptsAppendLanguageString(currentLanguage->no);
voicePromptsAppendLanguageString(currentLanguage->gps);
}
}
res = true;
}
}
// First run
if ((isFirstRun || (gpsData.Status & GPS_STATUS_FIX_UPDATED)) && (voicePromptsIsPlaying() == false))
{
promptsPlayNotAfterTx();
}
return res;
}
static void updateScreen(bool isFirstRun, bool forceRedraw)
{
bool res;
if (isFirstRun || forceRedraw)
{
char buffer[SCREEN_LINE_BUFFER_SIZE];
displayClearBuf();
menuDisplayTitle(currentLanguage->gps);
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%d/%d", (menuDataGlobal.currentItemIndex + 1), PAGES_MAX);
displayThemeApply(THEME_ITEM_FG_MENU_NAME, THEME_ITEM_BG);
displayPrintCore(0, 3, buffer, FONT_SIZE_1, TEXT_ALIGN_RIGHT, false);
displayThemeResetToDefault();
}
res = displayGPSData(isFirstRun, forceRedraw);
if (isFirstRun || forceRedraw || res)
{
displayRender();
}
// GPS power mode (Off/On/NMEA) has changed, display update is now done, store the new state.
if (prevGPSState != SETTINGS_GPS_MODE_GET(nonVolatileSettings))
{
prevGPSState = SETTINGS_GPS_MODE_GET(nonVolatileSettings);
}
// Clear all the *_UPDATED flags.
gpsData.Status &= ~(GPS_STATUS_FIX_UPDATED | GPS_STATUS_FIXTYPE_UPDATED | GPS_STATUS_POSITION_UPDATED |
GPS_STATUS_HDOP_UPDATED | GPS_STATUS_COURSE_UPDATED | GPS_STATUS_SPEED_UPDATED | GPS_STATUS_HEIGHT_UPDATED |
GPS_STATUS_GPS_SATS_UPDATED | GPS_STATUS_BD_SATS_UPDATED);
}
static void handleEvent(uiEvent_t *ev)
{
bool isDirty = false;
if (ev->events & BUTTON_EVENT)
{
if (repeatVoicePromptOnSK1(ev))
{
return;
}
}
if (ev->events & FUNCTION_EVENT)
{
if (ev->function == FUNC_REDRAW)
{
isDirty = true;
goto eventHanderExit;
}
else if (QUICKKEY_TYPE(ev->function) == QUICKKEY_MENU)
{
menuDataGlobal.currentItemIndex = QUICKKEY_ENTRYID(ev->function);
isDirty = true;
goto eventHanderExit;
}
}
if (KEYCHECK_SHORTUP(ev->keys, KEY_UP))
{
if (menuDataGlobal.currentItemIndex > PAGE_COORDS)
{
menuDataGlobal.currentItemIndex--;
isDirty = true;
}
}
else if (KEYCHECK_SHORTUP(ev->keys, KEY_DOWN))
{
if (menuDataGlobal.currentItemIndex < (PAGES_MAX - 1))
{
menuDataGlobal.currentItemIndex++;
isDirty = true;
}
}
else if ((KEYCHECK_SHORTUP(ev->keys, KEY_GREEN) && BUTTONCHECK_DOWN(ev, BUTTON_SK2))
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
// Let the operator to set the GPS power status to GPS_MODE_ON only, on longpress GREEN (A/B mic button also).
|| (KEYCHECK_LONGDOWN(ev->keys, KEY_GREEN) && (SETTINGS_GPS_MODE_GET(nonVolatileSettings) > GPS_NOT_DETECTED) && (SETTINGS_GPS_MODE_GET(nonVolatileSettings) < GPS_MODE_ON))
#endif
)
{
gpsOnUsingQuickKey(true);
}
else if (KEYCHECK_SHORTUP(ev->keys, KEY_RED) && BUTTONCHECK_DOWN(ev, BUTTON_SK2))
{
gpsOnUsingQuickKey(false);
}
else if (KEYCHECK_SHORTUP(ev->keys, KEY_RED))
{
menuSystemPopPreviousMenu();
}
else if (KEYCHECK_SHORTUP_NUMBER(ev->keys) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2)))
{
saveQuickkeyMenuIndex(ev->keys.key, menuSystemGetCurrentMenuNumber(), menuDataGlobal.currentItemIndex, 0);
}
eventHanderExit:
if (isDirty)
{
updateScreen(false, true);
}
}
#endif