/* * 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