/*
 * 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/uiLocalisation.h"
#include "user_interface/uiUtilities.h"
#include "utils.h"
#include "interfaces/pit.h"
#include "functions/satellite.h"
#if defined(PLATFORM_MD9600) || defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
#include "interfaces/batteryAndPowerManagement.h"
#include "semphr.h"
#include "interfaces/gps.h"
#endif

static SemaphoreHandle_t battSemaphore = NULL;

#define DOUBLE_ARROW_HEIGHT 6

#define VOLTAGE_BUFFER_LEN 128
static const float BATTERY_CRITICAL_VOLTAGE = 66.7f;
static const int TEMPERATURE_CRITICAL = 500; // 50°C
static const uint8_t daysPerMonth[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
static float prevAverageBatteryVoltage = 0.0f;
static int prevTemperature = 0;
static char keypadInputDigits[17]; // HHMMSS + terminator (Displayed as HH:MM:SS, or YYYY:MM:DD or LAT.LATIT,LON.LONGI)
static int keypadInputDigitsLength = 0;
static menuStatus_t menuRadioInfosExitCode = MENU_STATUS_SUCCESS;
//static int32_t timeClockPITOffset = 0; // Time difference between PITCounter and real time.
#define VP_TIMEBUF_SIZE 9
static uint32_t hours;
static uint32_t minutes;
static uint32_t seconds;
static struct tm timeAndDate;
bool latLonIsSouthernHemisphere = false;
bool latLonIsWesternHemisphere = false;

typedef struct
{
	int32_t  buffer[VOLTAGE_BUFFER_LEN];
	int32_t *head;
	int32_t *tail;
	int32_t *end;
	bool     modified;
} voltageCircularBuffer_t;

#if defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
__attribute__((section(".ccmram")))
#else
__attribute__((section(".data.$RAM2")))
#endif
voltageCircularBuffer_t batteryVoltageHistory;

enum
{
	GRAPH_FILL = 0,
	GRAPH_LINE
};

static int displayMode = RADIO_INFOS_BATTERY_LEVEL;
static bool pureBatteryLevel = false;
static int graphStyle = GRAPH_FILL;
static int battery_stack_iter = 0;
static const int BATTERY_ITER_PUSHBACK = 20;

static void updateScreen(uiEvent_t *ev, bool forceRedraw);
static void handleEvent(uiEvent_t *ev);
static void updateVoicePrompts(bool spellIt, bool firstRun);
static uint32_t menuRadioInfosNextUpdateTime = 0;



static void circularBufferInit(voltageCircularBuffer_t *cb)
{
	cb->end = &cb->buffer[VOLTAGE_BUFFER_LEN - 1];
	cb->head = cb->buffer;
	cb->tail = cb->buffer;
	cb->modified = false;
}

static void circularBufferPushBack(voltageCircularBuffer_t *cb, const int32_t item)
{
	cb->modified = true;

	*cb->head = item;
	cb->head++;

    if(cb->head == cb->end)
    {
    	cb->head = cb->buffer;
    }

    if (cb->tail == cb->head)
    {
    	cb->tail++;

    	if(cb->tail == cb->end)
    	{
    		cb->tail = cb->buffer;
    	}
    }
}

static size_t circularBufferGetData(voltageCircularBuffer_t *cb, int32_t *data, size_t dataLen)
{
     size_t  count = 0;
     int32_t *p = cb->tail;

     while ((p != cb->head) && (count < dataLen))
     {
    	 *(data + count) = *p;

    	 p++;
    	 count++;

    	 if (p == cb->end)
    	 {
    		 p = cb->buffer;
    	 }
     }

     return count;
}

static uint32_t inputDigitsLonToFixed_10_5(void)
{
	uint32_t inPart =		((keypadInputDigits[6] - '0') * 100) +
							((keypadInputDigits[7] - '0') *  10) +
							((keypadInputDigits[8] - '0') *   1);

	uint32_t decimalPart =	((keypadInputDigits[9] - '0')  * 10000) +
							((keypadInputDigits[10] - '0') *  1000) +
							((keypadInputDigits[11] - '0') *   100) +
							((keypadInputDigits[12] - '0') *    10);

	uint32_t fixedVal = (inPart << 23) + decimalPart;
	if (latLonIsWesternHemisphere)
	{
		fixedVal |= 0x80000000;// set MSB to indicate negative number / southern hemisphere
	}
	return fixedVal;
}

static uint32_t inputDigitsLatToFixed_10_5(void)
{
	uint32_t inPart =		((keypadInputDigits[0] - '0') *  10) +
							((keypadInputDigits[1] - '0') *   1);
	uint32_t decimalPart =	((keypadInputDigits[2] - '0') * 10000) +
							((keypadInputDigits[3] - '0') *  1000) +
							((keypadInputDigits[4] - '0') *   100)+
							((keypadInputDigits[5] - '0') *    10);

	uint32_t fixedVal = (inPart << 23) + decimalPart;
	if (latLonIsSouthernHemisphere)
	{
		fixedVal |= 0x80000000;// set MSB to indicate negative number / southrern hemisphere
	}
	return fixedVal;
}

menuStatus_t menuRadioInfos(uiEvent_t *ev, bool isFirstRun)
{
	if (isFirstRun)
	{
		keypadInputDigits[0] = 0;
		keypadInputDigitsLength = 0;
		menuDataGlobal.numItems = NUM_RADIO_INFOS_MENU_ITEMS;
		displayClearBuf();
		menuDisplayTitle(currentLanguage->radio_info);
		displayRenderRows(0, 2);

		if (settingsLocationIsValid())
		{
			if (nonVolatileSettings.location.lat & 0x80000000)
			{
				latLonIsSouthernHemisphere = true;
			}
			if (nonVolatileSettings.location.lon & 0x80000000)
			{
				latLonIsWesternHemisphere = true;
			}
		}

		updateScreen(ev, true);

		updateVoicePrompts(true, true);
	}
	else
	{
		if (ev->time > menuRadioInfosNextUpdateTime)
		{
			menuRadioInfosNextUpdateTime = ev->time + 500;
			updateScreen(ev, false);// update the screen each 500ms to show any changes to the battery voltage or low battery
		}

		menuRadioInfosExitCode = MENU_STATUS_SUCCESS;

		if (ev->hasEvent)
		{
			handleEvent(ev);
		}
	}
	return menuRadioInfosExitCode;
}

static void updateScreen(uiEvent_t *ev, bool forceRedraw)
{
	static bool blink = false;
	bool renderArrowOnly = true;
	char buffer[SCREEN_LINE_BUFFER_SIZE];
#if defined(HAS_COLOURS)
	bool dblHeight = settingsIsOptionBitSet(BIT_UI_USES_DOUBLE_HEIGHT);
#else
	const bool dblHeight = false;
#endif

	switch (displayMode)
	{
		case RADIO_INFOS_BATTERY_LEVEL:
		{
			if ((prevAverageBatteryVoltage != averageBatteryVoltage) || (averageBatteryVoltage < BATTERY_CRITICAL_VOLTAGE) || pureBatteryLevel || forceRedraw)
			{
#if defined(PLATFORM_MD9600)
				int volts, mvolts;
				const int x = 72;
				const int battLevelHeight = 28;

				prevAverageBatteryVoltage = averageBatteryVoltage;
				renderArrowOnly = false;

				if (forceRedraw)
				{
					displayClearBuf();
					menuDisplayTitle(currentLanguage->battery);

					// Draw...
					// Inner body frame
					displayDrawRoundRect(x + 1, 23, 49, 34, 3, true);
					// Outer body frame
					displayDrawRoundRect(x, 22, 51, 36, 3, true);
					// Positive poles frame
					displayFillRoundRect(x + 5, 18, 9, 6, 2, true);
					displayFillRoundRect(x + 37, 18, 9, 6, 2, true);
				}
				else
				{
					// Clear voltage area
					displayFillRect(0, (((DISPLAY_SIZE_Y - (14 + FONT_SIZE_3_HEIGHT)) >> 1) + 14), x - 1, 16, true);
					// Clear level area
					displayFillRoundRect(x + 4, 26, 43, battLevelHeight, 2, false);
				}

				// Want to display instant battery voltage, not the averaged value.
				if (pureBatteryLevel)
				{
					volts = (int)(batteryVoltage / 10);
					mvolts = (int)(batteryVoltage - (volts * 10));
				}
				else
				{
					getBatteryVoltage(&volts, &mvolts);
				}

				snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%2d.%1dV", volts, mvolts);
				displayPrintAt(((x - (5 * 8)) >> 1), (((DISPLAY_SIZE_Y - (14 + FONT_SIZE_3_HEIGHT)) >> 1) + 14), buffer, FONT_SIZE_3);

				if (pureBatteryLevel == false)
				{
					uint32_t h = (uint32_t)(((averageBatteryVoltage - CUTOFF_VOLTAGE_UPPER_HYST) * battLevelHeight) / (BATTERY_MAX_VOLTAGE - CUTOFF_VOLTAGE_UPPER_HYST));
					if (h > battLevelHeight)
					{
						h = battLevelHeight;
					}

					// Draw Level
					displayFillRoundRect(x + 4, 26 + battLevelHeight - h , 43, h, 2, (averageBatteryVoltage < BATTERY_CRITICAL_VOLTAGE) ? blink : true);
				}
				else
				{
					displayPrintCore(x + (20 / 2), 26 + ((battLevelHeight - FONT_SIZE_3_HEIGHT) >> 1), "?", FONT_SIZE_3, TEXT_ALIGN_LEFT, blink);
				}

				if (voicePromptsIsPlaying() == false)
				{
					updateVoicePrompts(false, false);
				}
#else
				int volts, mvolts;
				const int x = 88;
				const int battLevelHeight = (DISPLAY_SIZE_Y - 28);

				prevAverageBatteryVoltage = averageBatteryVoltage;
				renderArrowOnly = false;

				if (forceRedraw)
				{
					displayClearBuf();
					menuDisplayTitle(currentLanguage->battery);

					displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);

					// Draw...
					// Inner body frame
					displayDrawRoundRect(x + 1, 20, 26 + DISPLAY_H_EXTRA_PIXELS, DISPLAY_SIZE_Y - 22, 3, true);
					// Outer body frame
					displayDrawRoundRect(x, 19, 28 + DISPLAY_H_EXTRA_PIXELS, DISPLAY_SIZE_Y - 20, 3, true);
					// Positive pole frame
					displayFillRoundRect(x + 9 + DISPLAY_H_OFFSET, 15, 10, 6, 2, true);
				}
				else
				{
					displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);

					// Clear voltage area
					displayFillRect(((x - (4 * 8)) >> 1), 19 + 1, (4 * 8), (DISPLAY_SIZE_Y - 20) - 4, true);
					// Clear level area
					displayFillRoundRect(x + 4, 23, 20 + DISPLAY_H_EXTRA_PIXELS, battLevelHeight, 2, false);
				}

				displayThemeResetToDefault();

				// Want to display instant battery voltage, not the averaged value.
				if (pureBatteryLevel)
				{
					volts = (int)(batteryVoltage / 10);
					mvolts = (int)(batteryVoltage - (volts * 10));
				}
				else
				{
					getBatteryVoltage(&volts, &mvolts);
				}

				snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%1d.%1dV", volts, mvolts);
				displayPrintAtDoubleHeight(((x - (4 * 8)) >> 1), 19 + 1, buffer, FONT_SIZE_3, dblHeight);

				if (pureBatteryLevel == false)
				{
					snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%d%%", getBatteryPercentage());
					displayPrintAtDoubleHeight(((x - (strlen(buffer) * 8)) >> 1), (DISPLAY_SIZE_Y - 20 - (dblHeight ? FONT_SIZE_3_HEIGHT : 0))
#if defined(PLATFORM_RD5R)
							+ 7
#endif
							, buffer, FONT_SIZE_3, dblHeight);

					uint32_t h = (uint32_t)(((averageBatteryVoltage - CUTOFF_VOLTAGE_UPPER_HYST) * battLevelHeight) / (BATTERY_MAX_VOLTAGE - CUTOFF_VOLTAGE_UPPER_HYST));
					if (h > battLevelHeight)
					{
						h = battLevelHeight;
					}

					displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
					// Draw Level
					displayFillRoundRect(x + 4, 23 + battLevelHeight - h , 20 + DISPLAY_H_EXTRA_PIXELS, h, 2, (averageBatteryVoltage < BATTERY_CRITICAL_VOLTAGE) ? blink : true);
				}
				else
				{
					displayPrintCoreDoubleHeight(x + ((20 + DISPLAY_H_EXTRA_PIXELS) / 2), 23 + ((battLevelHeight - (FONT_SIZE_3_HEIGHT * (dblHeight ? 2 : 1))) >> 1), "?", FONT_SIZE_3, TEXT_ALIGN_LEFT, blink, dblHeight);
				}

				if (voicePromptsIsPlaying() == false)
				{
					updateVoicePrompts(false, false);
				}
#endif
			}

			displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
			// Low blinking arrow
			displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 1), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 5), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 5), blink);
		}
		break;

		case RADIO_INFOS_CURRENT_TIME:
			{
				displayClearBuf();
				snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s%s", currentLanguage->time, ((nonVolatileSettings.timezone & 0x80) ? "" : " UTC"));
				menuDisplayTitle(buffer);

				if (keypadInputDigitsLength == 0)
				{
					time_t_custom t = uiDataGlobal.dateTimeSecs + ((nonVolatileSettings.timezone & 0x80) ? ((nonVolatileSettings.timezone & 0x7F) - 64) * (15 * 60) : 0);

					gmtime_r_Custom(&t, &timeAndDate);

					snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%02u:%02u:%02u", timeAndDate.tm_hour, timeAndDate.tm_min, timeAndDate.tm_sec);

					if (voicePromptsIsPlaying() == false)
					{
						updateVoicePrompts(false, false);
					}
				}
				else
				{
					strcpy(buffer,"__:__:__");
					int bufPos = 0;
					for (int i = 0; i < keypadInputDigitsLength; i++)
					{
						buffer[bufPos++] = keypadInputDigits[i];
						if ((bufPos == 2) || (bufPos == 5))
						{
							bufPos++;
						}
					}

					displayThemeApply(THEME_ITEM_FG_TEXT_INPUT, THEME_ITEM_BG);
				}

				displayPrintCenteredDoubleHeight((((DISPLAY_SIZE_Y >> 1) + MENU_HEADER_HEIGHT) - (dblHeight ? FONT_SIZE_4_HEIGHT : (FONT_SIZE_4_HEIGHT >> 1)) - DOUBLE_ARROW_HEIGHT), buffer, FONT_SIZE_4, dblHeight);
				renderArrowOnly = false;

				displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
				// Up/Down blinking arrow
				displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 1), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), blink);
				displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 5), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), blink);
			}
			break;

		case RADIO_INFOS_DATE:
			displayClearBuf();
			snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s %s", currentLanguage->date, ((nonVolatileSettings.timezone & 0x80) ? "" : "UTC"));
			menuDisplayTitle(buffer);

			if (keypadInputDigitsLength == 0)
			{
				time_t_custom t = uiDataGlobal.dateTimeSecs + ((nonVolatileSettings.timezone & 0x80) ? ((nonVolatileSettings.timezone & 0x7F) - 64) * (15 * 60) : 0);

				gmtime_r_Custom(&t, &timeAndDate);
				snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%04u-%02u-%02u", (timeAndDate.tm_year + 1900), (timeAndDate.tm_mon + 1), timeAndDate.tm_mday);

				if (voicePromptsIsPlaying() == false)
				{
					updateVoicePrompts(false, false);
				}
			}
			else
			{
				strcpy(buffer, "____-__-__");

				int bufPos = 0;
				for(int i = 0; i < keypadInputDigitsLength; i++)
				{
					buffer[bufPos++] = keypadInputDigits[i];
					if ((bufPos == 4) || (bufPos == 7))
					{
						bufPos++;
					}
				}
				displayThemeApply(THEME_ITEM_FG_TEXT_INPUT, THEME_ITEM_BG);
			}

			displayPrintCenteredDoubleHeight((((DISPLAY_SIZE_Y >> 1) + MENU_HEADER_HEIGHT) - (dblHeight ? FONT_SIZE_3_HEIGHT : (FONT_SIZE_3_HEIGHT >> 1)) - DOUBLE_ARROW_HEIGHT), buffer, FONT_SIZE_3, dblHeight);
			renderArrowOnly = false;

			displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
			// Up/Down blinking arrow
			displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 1), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), blink);
			displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 5), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), blink);
			break;

		case RADIO_INFOS_LOCATION:
			displayClearBuf();
			menuDisplayTitle(currentLanguage->location);
			{
				char maidenheadBuf[7];

				if (keypadInputDigitsLength == 0)
				{
					bool locIsValid = settingsLocationIsValid();

					buildLocationAndMaidenheadStrings(buffer, maidenheadBuf, locIsValid);

					if (locIsValid == false)
					{
						displayPrintCenteredDoubleHeight(((((DISPLAY_SIZE_Y / 3) * 2) + MENU_HEADER_HEIGHT) - (dblHeight ? FONT_SIZE_3_HEIGHT : (FONT_SIZE_3_HEIGHT >> 1)) - DOUBLE_ARROW_HEIGHT)
#if defined(PLATFORM_RD5R)
								- 4
#endif
								, currentLanguage->not_set, FONT_SIZE_3, dblHeight);
					}

					if (voicePromptsIsPlaying() == false)
					{
						updateVoicePrompts(false, false);
					}
				}
				else
				{
					int bufPos = 0;

					sprintf(buffer, "__.____%c ___.____%c",
							currentLanguageGetSymbol(latLonIsSouthernHemisphere ? SYMBOLS_SOUTH : SYMBOLS_NORTH),
							currentLanguageGetSymbol(latLonIsWesternHemisphere ? SYMBOLS_WEST : SYMBOLS_EAST));

					for(int i = 0; i < keypadInputDigitsLength; i++)
					{
						buffer[bufPos++] = keypadInputDigits[i];

						if ((bufPos == 2) || (bufPos == 12))
						{
							bufPos++;
						}
						else if (bufPos == 7)
						{
							bufPos +=2;
						}
					}

					maidenheadBuf[0] = 0;
					displayThemeApply(THEME_ITEM_FG_TEXT_INPUT, THEME_ITEM_BG);
				}

#if defined(HAS_COLOURS)
				ucFont_t fontSize = FONT_SIZE_3;
				uint16_t fontHeight = FONT_SIZE_3_HEIGHT;
#else
				ucFont_t fontSize = FONT_SIZE_1;
				uint16_t fontHeight = FONT_SIZE_1_HEIGHT;
#endif

				displayPrintCenteredDoubleHeight((((DISPLAY_SIZE_Y / 3) + MENU_HEADER_HEIGHT) - (dblHeight ? fontHeight : (fontHeight >> 1)) - DOUBLE_ARROW_HEIGHT), buffer, fontSize, dblHeight);
				displayPrintCenteredDoubleHeight(((((DISPLAY_SIZE_Y / 3) * 2) + MENU_HEADER_HEIGHT) - (dblHeight ? FONT_SIZE_3_HEIGHT : (FONT_SIZE_3_HEIGHT >> 1)) - DOUBLE_ARROW_HEIGHT)
#if defined(PLATFORM_RD5R)
						- 4
#endif
						, maidenheadBuf, FONT_SIZE_3, dblHeight);
			}
			renderArrowOnly = false;

			displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
			// Up/Down blinking arrow
			displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 1), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), blink);
			displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 5), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), blink);
			break;

		case RADIO_INFOS_TEMPERATURE_LEVEL:
		{
			int temperature = getTemperature();

			if ((prevTemperature != temperature) || (temperature > TEMPERATURE_CRITICAL) || forceRedraw)
			{
				const int x = 102 + DISPLAY_H_OFFSET;
#if defined(PLATFORM_RD5R)
				const int temperatureHeight = (DISPLAY_SIZE_Y - 34);
				const int tankVCenter = (DISPLAY_SIZE_Y - 10);
				const int tankRadius = 9;
#else
				const int temperatureHeight = (DISPLAY_SIZE_Y - 40);
				const int tankVCenter = (DISPLAY_SIZE_Y - 14);
				const int tankRadius = 11;
#endif
				prevTemperature = temperature;
				renderArrowOnly = false;

				if (forceRedraw)
				{
					displayClearBuf();
					menuDisplayTitle(currentLanguage->temperature);

					displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);

					// Body frame
					displayDrawCircleHelper(x, 20 + 2, 7, (1 | 2), true);
					displayDrawCircleHelper(x, 20 + 2, 6, (1 | 2), true);
					displaySetPixel(x, (20 + 2) - 7, true);
					displayDrawFastVLine(x - 7, 20 + 2, temperatureHeight - 1, true);
					displayDrawFastVLine(x - 6, 20 + 2, temperatureHeight - 2, true);
					displayDrawFastVLine(x + 6, 20 + 2, temperatureHeight - 2, true);
					displayDrawFastVLine(x + 7, 20 + 2, temperatureHeight - 1, true);

					displayDrawCircle(x, tankVCenter, tankRadius, true);
					displayDrawCircle(x, tankVCenter, tankRadius - 1, true);
					displayFillCircle(x, tankVCenter, tankRadius - 3, ((temperature > TEMPERATURE_CRITICAL) ? !blink : true));
					displayFillRect(x - 5, 20, 11, temperatureHeight, true);

					// H lines, min/max markers
					displayDrawFastHLine(x - (7 + 5), 20, 5, true); // MAX: 70°C
					displayDrawFastHLine(x - (7 + 5), (temperatureHeight + 20) - 1, 5, true); // MIN: 10°C
				}
				else
				{
					displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);

					// Clear temperature text area
					displayFillRect((((x - (7 + 5)) - (7 * 8)) >> 1), 20, (7 * 8), (DISPLAY_SIZE_Y - 20) - 4, true);

					// Clear thermo area
					displayFillCircle(x, tankVCenter, tankRadius - 3, ((temperature > TEMPERATURE_CRITICAL) ? !blink : true));
					displayFillRect(x - 4, 20, 9, temperatureHeight, true);
				}

				displayThemeResetToDefault();

				snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%3d.%1d%s", (temperature / 10), abs(temperature % 10), currentLanguage->celcius);
				displayPrintAtDoubleHeight((((x - (7 + 5)) - (7 * 8)) >> 1), (((DISPLAY_SIZE_Y >> 1) + MENU_HEADER_HEIGHT) - (dblHeight ? FONT_SIZE_3_HEIGHT : (FONT_SIZE_3_HEIGHT >> 1)) - DOUBLE_ARROW_HEIGHT), buffer, FONT_SIZE_3, dblHeight);

				uint32_t t = (uint32_t)((((CLAMP(temperature, 100, 700)) - 100) * temperatureHeight) / (700 - 100)); // clamp to 10..70 °C, then scale

				// Draw Level
				if (t)
				{
					displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
					displayFillRect(x - 4, 20 + temperatureHeight - t , 9, t, (temperature > TEMPERATURE_CRITICAL) ? blink : false);
				}

				if (voicePromptsIsPlaying() == false)
				{
					updateVoicePrompts(false, false);
				}
			}

			displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
			// Up/Down blinking arrow
			displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 1), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), blink);
			displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 5), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), blink);
		}
		break;

		case RADIO_INFOS_BATTERY_GRAPH:
		{
#define  CHART_WIDTH 104
			static int32_t hist[CHART_WIDTH];
			static size_t histLen = 0;
			bool newHistAvailable = false;

			// Grab history values.
			// There is a 10 ticks timeout, if it kicks in, history length will be 0, then
			// redraw will be done on the next run
			if (xSemaphoreTake(battSemaphore, (TickType_t)10) == pdTRUE)
			{
				if ((newHistAvailable = batteryVoltageHistory.modified) == true)
				{
					histLen = circularBufferGetData(&batteryVoltageHistory, hist, (sizeof(hist) / sizeof(hist[0])));
					batteryVoltageHistory.modified = false;
				}
				xSemaphoreGive(battSemaphore);
			}

			if (newHistAvailable || forceRedraw)
			{
				static const uint8_t chartX = 2 + (2 * 6) + 3 + 2 + DISPLAY_H_OFFSET;
				static const uint8_t chartY = 14 + 1 + 2;
				const int chartHeight = (DISPLAY_SIZE_Y - 26);

#if defined(PLATFORM_MD9600)
				// Min is 10V, Max is 15V
				// Pick: MIN @ 11V, MAX @ 14V
				uint32_t minVH = (uint32_t)(((110 - CUTOFF_VOLTAGE_UPPER_HYST) * chartHeight) / (BATTERY_MAX_VOLTAGE - CUTOFF_VOLTAGE_UPPER_HYST));
				uint32_t maxVH = (uint32_t)(((140 - CUTOFF_VOLTAGE_UPPER_HYST) * chartHeight) / (BATTERY_MAX_VOLTAGE - CUTOFF_VOLTAGE_UPPER_HYST));
#else
				// Min is 6.4V, Max is 8.2V
				// Pick: MIN @ 7V, MAX @ 8V
				uint32_t minVH = (uint32_t)(((70 - CUTOFF_VOLTAGE_UPPER_HYST) * chartHeight) / (BATTERY_MAX_VOLTAGE - CUTOFF_VOLTAGE_UPPER_HYST));
				uint32_t maxVH = (uint32_t)(((80 - CUTOFF_VOLTAGE_UPPER_HYST) * chartHeight) / (BATTERY_MAX_VOLTAGE - CUTOFF_VOLTAGE_UPPER_HYST));
#endif

				renderArrowOnly = false;

				// Redraw chart's axes, ticks and so on
				if (forceRedraw)
				{
					displayClearBuf();
					menuDisplayTitle(currentLanguage->battery);

					displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);

					// 2 axis chart
					displayDrawFastVLine(chartX - 3, chartY - 2, chartHeight + 2 + 3, true);
					displayDrawFastVLine(chartX - 2, chartY - 2, chartHeight + 2 + 2, true);
					displayDrawFastHLine(chartX - 3, chartY + chartHeight + 2, CHART_WIDTH + 3 + 3, true);
					displayDrawFastHLine(chartX - 2, chartY + chartHeight + 1, CHART_WIDTH + 3 + 2, true);

					// Min/Max Voltage ticks and values
					displayDrawFastHLine(chartX - 6, (chartY + chartHeight) - minVH, 3, true);
#if defined(PLATFORM_MD9600)
					displayPrintAt(DISPLAY_H_OFFSET, ((chartY + chartHeight) - minVH) - 3, "11V", FONT_SIZE_1);
					displayDrawFastHLine(chartX - 6, (chartY + chartHeight) - maxVH, 3, true);
					displayPrintAt(DISPLAY_H_OFFSET, ((chartY + chartHeight) - maxVH) - 3, "14V", FONT_SIZE_1);
#elif defined(PLATFORM_GD77) || defined(PLATFORM_GD77S) || defined(PLATFORM_DM1801) || defined(PLATFORM_DM1801A) || defined(PLATFORM_RD5R)
					displayPrintAt(chartX - 3 - 12 - 3, ((chartY + chartHeight) - minVH) - 3, "7V", FONT_SIZE_1);
					displayDrawFastHLine(chartX - 6, (chartY + chartHeight) - maxVH, 3, true);
					displayPrintAt(chartX - 3 - 12 - 3, ((chartY + chartHeight) - maxVH) - 3, "8V", FONT_SIZE_1);
#else // Other STM32 platforms
					displayPrintAtDoubleHeight(DISPLAY_H_OFFSET, ((chartY + chartHeight) - minVH) - (3 * (dblHeight ? 2 : 1)), "7V", FONT_SIZE_1, dblHeight);
					displayDrawFastHLine(chartX - 6, (chartY + chartHeight) - maxVH, 3, true);
					displayPrintAtDoubleHeight(DISPLAY_H_OFFSET, ((chartY + chartHeight) - maxVH) - (3 * (dblHeight ? 2 : 1)), "8V", FONT_SIZE_1, dblHeight);
#endif

					// Time ticks
					for (uint8_t i = 0; i < CHART_WIDTH + 2; i += 22 /* ~ 15 minutes */)
					{
						displaySetPixel(chartX + i, (chartY + chartHeight) + 3, true);
					}
				}
				else
				{
					displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
					displayFillRect(chartX, chartY, CHART_WIDTH, chartHeight, true);
				}

				// Draw chart values, according to style
				for (size_t i = 0; i < histLen; i++)
				{
					uint32_t y = (uint32_t)((((CLAMP(hist[i], CUTOFF_VOLTAGE_UPPER_HYST, BATTERY_MAX_VOLTAGE)) - CUTOFF_VOLTAGE_UPPER_HYST) *
							chartHeight) / (BATTERY_MAX_VOLTAGE - CUTOFF_VOLTAGE_UPPER_HYST));

					if (graphStyle == GRAPH_FILL)
					{
						displayDrawFastVLine(chartX + i, ((chartY + chartHeight) - y), y, true);
					}
					else
					{
						displaySetPixel(chartX + i, ((chartY + chartHeight) - y), true);
					}
				}

				// Min/Max dot lines
				for (uint8_t i = 0; i < CHART_WIDTH + 2; i++)
				{
					displaySetPixel(chartX + i, ((chartY + chartHeight) - minVH), (i % 2) ? false : true);
					displaySetPixel(chartX + i, ((chartY + chartHeight) - maxVH), (i % 2) ? false : true);
				}
			}
			else
			{
				displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
			}

			// Up blinking arrow
			displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 5), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), blink);

			if (voicePromptsIsPlaying() == false)
			{
				updateVoicePrompts(false, false);
			}
		}
		break;

		case RADIO_INFOS_UP_TIME:
		{
			displayClearBuf();
			menuDisplayTitle(currentLanguage->uptime);
			uint32_t timeInSeconds = 0;//   (ev->time + uiDataGlobal.timeClockPITOffset) / 1000;

			hours = timeInSeconds / (60 * 60);
			minutes = (timeInSeconds % (60 * 60)) / 60;

			snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%u %s", hours, currentLanguage->hours);
			displayPrintCentered((DISPLAY_SIZE_Y / 2) - 8, buffer, FONT_SIZE_3);

			snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%u %s", minutes, currentLanguage->minutes);
			displayPrintCentered(((DISPLAY_SIZE_Y * 3) / 4) - 8, buffer, FONT_SIZE_3);

			if (voicePromptsIsPlaying() == false)
			{
				updateVoicePrompts(false, false);
			}

			renderArrowOnly = false;

			displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
			// Up/Down blinking arrow
			displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 1), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), blink);
			displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 5), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), blink);
		}
		break;

		case RADIO_INFOS_TIME_ALARM:
			{
				displayClearBuf();
				menuDisplayTitle(currentLanguage->alarm_time);

				if (keypadInputDigitsLength == 0)
				{
					hours = (uiDataGlobal.SatelliteAndAlarmData.alarmTime % (60 * 60 * 24))/ (60 * 60);
					minutes = (uiDataGlobal.SatelliteAndAlarmData.alarmTime % (60 * 60)) / 60;
					seconds = (uiDataGlobal.SatelliteAndAlarmData.alarmTime % 60);
					snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%02u:%02u:%02u", hours, minutes, seconds);

					if (voicePromptsIsPlaying() == false)
					{
						updateVoicePrompts(false, false);
					}
				}
				else
				{
					strcpy(buffer,"__:__:__");
					int bufPos = 0;
					for(int i = 0; i < keypadInputDigitsLength; i++)
					{
						buffer[bufPos++] = keypadInputDigits[i];
						if ((bufPos == 2) || (bufPos == 5))
						{
							bufPos++;
						}
					}
					displayThemeApply(THEME_ITEM_FG_TEXT_INPUT, THEME_ITEM_BG);
				}

				displayPrintCentered((DISPLAY_SIZE_Y / 2) - 8, buffer, FONT_SIZE_4);
				renderArrowOnly = false;

				displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
				// Up/Down blinking arrow
				displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 1), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), blink);
				displayFillTriangle(63 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 5), 59 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), 67 + DISPLAY_H_OFFSET, (DISPLAY_SIZE_Y - 3), blink);
			}
			break;
	}

	blink = !blink;
	displayThemeResetToDefault();

	displayRenderRows((renderArrowOnly ? (DISPLAY_NUMBER_OF_ROWS - 1) : 0), DISPLAY_NUMBER_OF_ROWS);
}

static void handleEvent(uiEvent_t *ev)
{
	if (ev->events & FUNCTION_EVENT)
	{
		if (ev->function == FUNC_REDRAW)
		{
			updateScreen(ev, true);
			return;
		}
		else if (QUICKKEY_TYPE(ev->function) == QUICKKEY_MENU)
		{
			displayMode = QUICKKEY_ENTRYID(ev->function);

			updateScreen(ev, true);
			updateVoicePrompts(true, false);
			return;
		}
	}

	if (ev->events & BUTTON_EVENT)
	{
		if (repeatVoicePromptOnSK1(ev))
		{
			return;
		}

		if ((pureBatteryLevel == false) && BUTTONCHECK_EXTRALONGDOWN(ev, BUTTON_SK2))
		{
			pureBatteryLevel = true;
			updateScreen(ev, true);
		}
		else if (pureBatteryLevel && (BUTTONCHECK_EXTRALONGDOWN(ev, BUTTON_SK2) == 0))
		{
			pureBatteryLevel = false;
			updateScreen(ev, true);
		}
	}

	if ((KEYCHECK_SHORTUP(ev->keys, KEY_GREEN) && BUTTONCHECK_DOWN(ev, BUTTON_SK2))
#if defined(PLATFORM_MD9600)
			// 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
	)
	{
		if (displayMode == RADIO_INFOS_LOCATION)
		{
#if defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
			gpsOnUsingQuickKey(true);
#endif
		}

		return;
	}

	if ((ev->keys.event & (KEY_MOD_UP | KEY_MOD_LONG)) == KEY_MOD_UP)
	{
		switch(ev->keys.key)
		{
			case KEY_GREEN:
				if (keypadInputDigitsLength == 0)
				{
					menuSystemPopPreviousMenu();
				}
				else
				{
					switch(displayMode)
					{
						case RADIO_INFOS_DATE:
						{
							// get the current date time because the time is constantly changing
							time_t_custom t = uiDataGlobal.dateTimeSecs + ((nonVolatileSettings.timezone & 0x80) ? ((nonVolatileSettings.timezone & 0x7F) - 64) * (15 * 60) : 0);
							gmtime_r_Custom(&t, &timeAndDate);// get the current date time as the date may have changed since starting to enter the time.

							timeAndDate.tm_mon		= (((keypadInputDigits[4] - '0') * 10) + (keypadInputDigits[5] - '0')) - 1;
							timeAndDate.tm_mday	= ((keypadInputDigits[6] - '0') * 10) + (keypadInputDigits[7] - '0');

							timeAndDate.tm_year = (((keypadInputDigits[0] - '0') * 1000) +
									((keypadInputDigits[1] - '0') * 100) +
									((keypadInputDigits[2] - '0') * 10) +
									((keypadInputDigits[3] - '0'))) - 1900;

							uiSetUTCDateTimeInSecs(mktime_custom(&timeAndDate) - ((nonVolatileSettings.timezone & 0x80)?((nonVolatileSettings.timezone & 0x7F) - 64) * (15 * 60):0));
#if defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
							setRtc_custom(uiDataGlobal.dateTimeSecs);
#endif
							keypadInputDigits[0] = 0;// clear digits
							keypadInputDigitsLength = 0;

							menuSatelliteScreenClearPredictions(false);

							nextKeyBeepMelody = (int16_t *)MELODY_ACK_BEEP;

							updateScreen(ev, true);
						}
							break;

						case RADIO_INFOS_LOCATION:
							if (keypadInputDigitsLength == 13)
							{
								settingsSet(nonVolatileSettings.location.lat, inputDigitsLatToFixed_10_5());
								settingsSet(nonVolatileSettings.location.lon, inputDigitsLonToFixed_10_5());

								keypadInputDigits[0] = 0;// clear digits
								keypadInputDigitsLength = 0;
								menuSatelliteScreenClearPredictions(false);
								aprsBeaconingInvalidateFixedPosition();
								aprsBeaconingSetCurrentLocation(settingsLocationGetLatitude(), settingsLocationGetLongitude());

								// Roaming is enabled, and root menu is Channel mode => trigger distance sorting.
								if ((nonVolatileSettings.roaming != ROAMING_OFF) &&
										(menuSystemGetRootMenuNumber() == UI_CHANNEL_MODE) &&
										(CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone) == false))
								{
									currentChannelData->rxFreq = 0U;
								}

								updateScreen(ev, true);
								nextKeyBeepMelody = (int16_t *)MELODY_ACK_BEEP;
							}
							else
							{
								nextKeyBeepMelody = (int16_t *)MELODY_NACK_BEEP;
							}
							break;

						default:
						{
							const int multipliers[6] = { 36000, 3600, 600, 60, 10, 1 };

							if (keypadInputDigitsLength == 6)
							{
								int newTime = 0;

								for(int i = 0; i < 6; i++)
								{
									newTime += (keypadInputDigits[i] - '0') * multipliers[i];
								}
								if (displayMode == RADIO_INFOS_CURRENT_TIME)
								{
									PIT2SecondsCounter = 0;// Stop PIT2SecondsCounter rolling over during the next operations
									time_t_custom t = uiDataGlobal.dateTimeSecs + ((nonVolatileSettings.timezone & 0x80)?((nonVolatileSettings.timezone & 0x7F) - 64) * (15 * 60):0);
									gmtime_r_Custom(&t, &timeAndDate);// get the current date time as the date may have changed since starting to enter the time.
									timeAndDate.tm_hour 	= ((keypadInputDigits[0] - '0') * 10) + (keypadInputDigits[1] - '0');
									timeAndDate.tm_min		= ((keypadInputDigits[2] - '0') * 10) + (keypadInputDigits[3] - '0');
									timeAndDate.tm_sec		= ((keypadInputDigits[4] - '0') * 10) + (keypadInputDigits[5] - '0');

									PIT2SecondsCounter = 0;//Synchronise PIT2SecondsCounter
									uiSetUTCDateTimeInSecs(mktime_custom(&timeAndDate) - ((nonVolatileSettings.timezone & 0x80)?((nonVolatileSettings.timezone & 0x7F) - 64) * (15 * 60):0));
#if defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
									setRtc_custom(uiDataGlobal.dateTimeSecs);
#endif
									menuSatelliteScreenClearPredictions(false);
								}
								else
								{
									// alarm
									uiDataGlobal.SatelliteAndAlarmData.alarmTime = newTime;

									if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
									{
										uiDataGlobal.SatelliteAndAlarmData.alarmType = ALARM_TYPE_CLOCK;
										powerOffFinalStage(true, false);
									}
								}
								keypadInputDigits[0] = 0;// clear digits
								keypadInputDigitsLength = 0;
								updateScreen(ev, true);
								nextKeyBeepMelody = (int16_t *)MELODY_ACK_BEEP;
							}
							break;
						}
					}
				}
				return;
				break;

			case KEY_RED:
				if ((displayMode == RADIO_INFOS_LOCATION) && BUTTONCHECK_DOWN(ev, BUTTON_SK2))
				{
#if defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
					gpsOnUsingQuickKey(false);
#endif
					return;
				}
				else if (((displayMode == RADIO_INFOS_CURRENT_TIME) || (displayMode == RADIO_INFOS_DATE) || (displayMode == RADIO_INFOS_LOCATION))
					&& (keypadInputDigitsLength != 0))
				{
					keypadInputDigits[0] = 0;
					keypadInputDigitsLength = 0;
					updateScreen(ev, true);
				}
				else
				{
					menuSystemPopPreviousMenu();
				}

				return;
				break;
		}
	}


	if (ev->keys.event & KEY_MOD_PRESS)
	{
		switch(ev->keys.key)
		{
			case KEY_DOWN:
				if (keypadInputDigitsLength != 0)
				{
					if (displayMode == RADIO_INFOS_LOCATION)
					{
						char buf[2] = { 0, 0 };

						if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
						{
							latLonIsWesternHemisphere = true;
							buf[0] = currentLanguageGetSymbol(latLonIsWesternHemisphere ? SYMBOLS_WEST : SYMBOLS_EAST);
						}
						else
						{
							latLonIsSouthernHemisphere = true;
							buf[0] = currentLanguageGetSymbol(latLonIsSouthernHemisphere ? SYMBOLS_SOUTH : SYMBOLS_NORTH);
						}
						voicePromptsInit();
						voicePromptsAppendString(buf);
						voicePromptsPlay();
						updateScreen(ev, false);
					}
					return;
				}
				if (displayMode < (NUM_RADIO_INFOS_MENU_ITEMS - 1))
				{
					displayMode++;
					updateScreen(ev, true);
					updateVoicePrompts(true, false);
				}
				break;

			case KEY_UP:
				if (keypadInputDigitsLength != 0)
				{
					if (displayMode == RADIO_INFOS_LOCATION)
					{
						char buf[2] = { 0, 0 };

						if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
						{
							latLonIsWesternHemisphere = false;
							buf[0] = currentLanguageGetSymbol(latLonIsWesternHemisphere ? SYMBOLS_WEST : SYMBOLS_EAST);
						}
						else
						{
							latLonIsSouthernHemisphere = false;
							buf[0] = currentLanguageGetSymbol(latLonIsSouthernHemisphere ? SYMBOLS_SOUTH : SYMBOLS_NORTH);
						}
						voicePromptsInit();
						voicePromptsAppendString(buf);
						voicePromptsPlay();
						updateScreen(ev, false);
					}
					return;
				}

				if (displayMode > RADIO_INFOS_BATTERY_LEVEL)
				{
					displayMode--;
					updateScreen(ev, true);
					updateVoicePrompts(true, false);
				}
				break;

			case KEY_LEFT:
#if defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
			case KEY_ROTARY_DECREMENT:
#endif
				switch(displayMode)
				{
					case RADIO_INFOS_BATTERY_GRAPH:
						if (graphStyle == GRAPH_LINE)
						{
							graphStyle = GRAPH_FILL;
							updateScreen(ev, true);
						}
						break;

					case RADIO_INFOS_CURRENT_TIME:
					case RADIO_INFOS_TIME_ALARM:
					case RADIO_INFOS_DATE:
					case RADIO_INFOS_LOCATION:
						{
							if (keypadInputDigitsLength > 0)
							{
								keypadInputDigits[keypadInputDigitsLength - 1] = 0;
								keypadInputDigitsLength--;
							}
							updateScreen(ev, true);
						}
						break;
				}
				break;

			case KEY_RIGHT:
#if defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
			case KEY_ROTARY_INCREMENT:
#endif
				switch(displayMode)
				{
					case RADIO_INFOS_BATTERY_GRAPH:
						if (graphStyle == GRAPH_FILL)
						{
							graphStyle = GRAPH_LINE;
							updateScreen(ev, true);
						}
						break;
				}
				break;

			default:
				if (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0) // Filtering for QuickKey
				{
					int keyval = menuGetKeypadKeyValue(ev, true);
					if (keyval != 99)
					{
						int maxDigitsOnThisScreen;
						switch(displayMode)
						{
							case RADIO_INFOS_DATE:
								maxDigitsOnThisScreen = 8;
							break;
							case RADIO_INFOS_LOCATION:
								maxDigitsOnThisScreen = 13;
								break;
							case RADIO_INFOS_CURRENT_TIME:
								maxDigitsOnThisScreen = 6;
								break;
							default:
								return;// No other screens have key entry
								break;
						}

						if (keypadInputDigitsLength < maxDigitsOnThisScreen)
						{
							const uint8_t MAX_DIGIT_VALUE[3][9] = {
									{ 2, 9, 5, 9, 5, 9, 0, 0, 0 },
									{ 2, 9, 5, 9, 5, 9, 0, 0, 0 },
									{ 2, 9, 5, 9, 5, 9, 0, 0, 0 }
							};
							const uint8_t MAX_YEAR_DIGIT_VALUES[5] = { 2, 0, 4, 9, 1 };
							int maxDigValue = 9;
							int minDigValue = 0;

							switch(displayMode)
							{
								case RADIO_INFOS_DATE:
									switch(keypadInputDigitsLength)
									{
										case 0:
										case 1:
										case 2:
										case 3:
										case 4:
											// Year digits, and first digit of the month
											maxDigValue = MAX_YEAR_DIGIT_VALUES[keypadInputDigitsLength];
											break;
										case 5:
											// second digit of the month
											maxDigValue = (keypadInputDigits[4] == '1') ? 2 : 9;
											if (keypadInputDigits[4] == '0')
											{
												minDigValue = 1;
											}
											break;
										case 6:
											// first digit of the day
											maxDigValue = daysPerMonth[(((keypadInputDigits[4] - '0') * 10) + (keypadInputDigits[5] - '0')) - 1] / 10;
											break;
										case 7:
											{
												// second digit of the day
												uint32_t month = (((keypadInputDigits[4] - '0') * 10) + (keypadInputDigits[5] - '0'));

												if (month == 2)
												{
													// handle leap years
													uint32_t year = 0;
													uint32_t multiplier = 1000;

													for(int i = 0; i < 4; i++)
													{
														year += (keypadInputDigits[i] - '0') * multiplier;
														multiplier /= 10;
													}

													if ((keypadInputDigits[6] - '0') == 2) // 2_: Feb, 28 or 29 days (leap year checking)
													{
														if ((year % 4) == 0)
														{
															maxDigValue = 9;
														}
														else
														{
															maxDigValue = 8;
														}
													}
												}
												else
												{
													if ((keypadInputDigits[6] - '0') < 3)
													{
														maxDigValue = 9;
													}
													else
													{
														maxDigValue = daysPerMonth[month - 1] % 10;
													}
												}

												if (keypadInputDigits[6] == '0')
												{
													minDigValue = 1;
												}
											}
											break;
									}

								break;
								case RADIO_INFOS_LOCATION:

									maxDigValue = 9;// default value if no override is applied below

									switch(keypadInputDigitsLength)
									{
										case 1:
										case 2:
										case 3:
										case 4:
										case 5:
											if (keypadInputDigits[0] == '9')
											{
												maxDigValue = 0;
											}
											break;
										case 6:
											maxDigValue = 1;
											break;
										case 7:
											if (keypadInputDigits[6] == '1')
											{
												maxDigValue = 8;
											}
											break;
										case 8:
										case 9:
										case 10:
										case 11:
										case 12:
											if ((keypadInputDigits[6] == '1') && (keypadInputDigits[7] == '8'))
											{
												maxDigValue = 0;
											}
											break;
									}

									break;
								case RADIO_INFOS_CURRENT_TIME:
									if (keypadInputDigitsLength == 1 && (keypadInputDigits[0] == '2'))
									{
										maxDigValue = 3;
									}
									else
									{
										maxDigValue = MAX_DIGIT_VALUE[0][keypadInputDigitsLength];
									}
									break;
							}

							if ((keyval <=  maxDigValue) && (keyval >= minDigValue))
							{
								char c[2] = {0, 0};
								c[0] = keyval + '0';

								if (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_THRESHOLD)
								{
									voicePromptsInit();
									switch(displayMode)
									{
										case RADIO_INFOS_LOCATION:
											switch(keypadInputDigitsLength)
											{
												case 0:
													if (latLonIsSouthernHemisphere)
													{
														voicePromptsAppendPrompt(PROMPT_MINUS);
													}
													break;
												case 5:
													if (latLonIsWesternHemisphere)
													{
														voicePromptsAppendPrompt(PROMPT_MINUS);
													}
													break;
											}
										break;
									}
									voicePromptsAppendString(c);

									switch(displayMode)
									{
										case RADIO_INFOS_LOCATION:
											if ((keypadInputDigitsLength == 1) || (keypadInputDigitsLength == 8))
											{
												voicePromptsAppendPrompt(PROMPT_POINT);
											}
											else if (keypadInputDigitsLength == 5)
											{
												char buf[2] = { 0, 0 };
												buf[0] = currentLanguageGetSymbol(latLonIsSouthernHemisphere ? SYMBOLS_SOUTH : SYMBOLS_NORTH);
												voicePromptsAppendString(buf);
											}
											else if (keypadInputDigitsLength == 12)
											{
												char buf[2] = { 0, 0 };
												buf[0] = currentLanguageGetSymbol(latLonIsWesternHemisphere ? SYMBOLS_WEST : SYMBOLS_EAST);
												voicePromptsAppendString(buf);
											}
										break;
									}
									voicePromptsPlay();
								}

								strcat(keypadInputDigits, c);
								keypadInputDigitsLength++;
								updateScreen(ev, true);
								nextKeyBeepMelody = (int16_t *)MELODY_KEY_BEEP;
							}
							else
							{
								if (keypadInputDigitsLength != 0)
								{
									nextKeyBeepMelody = (int16_t *)MELODY_NACK_BEEP;
								}
							}
						}
						else
						{
							nextKeyBeepMelody = (int16_t *)MELODY_NACK_BEEP;
						}
					}
				}
				break;
		}
	}


	if (KEYCHECK_SHORTUP_NUMBER(ev->keys) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2)))
	{
		saveQuickkeyMenuIndex(ev->keys.key, menuSystemGetCurrentMenuNumber(), displayMode, 0);
	}
}

void menuRadioInfosInit(void)
{
	battSemaphore = xSemaphoreCreateMutex();

	if (battSemaphore == NULL)
	{
		while(true); // Something better maybe ?
	}

	circularBufferInit(&batteryVoltageHistory);
}

// called every 2000 ticks
void menuRadioInfosPushBackVoltage(int32_t voltage)
{
	// Store value each 40k ticks
	if ((battery_stack_iter == 0) || (battery_stack_iter > BATTERY_ITER_PUSHBACK))
	{
		if (xSemaphoreTake(battSemaphore, (TickType_t)10) == pdTRUE)
		{
			circularBufferPushBack(&batteryVoltageHistory, voltage);
			xSemaphoreGive(battSemaphore);
		}

		battery_stack_iter = 0;
	}

	battery_stack_iter++;
}

static void updateVoicePrompts(bool spellIt, bool firstRun)
{
	if (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_THRESHOLD)
	{
		char buffer[LOCATION_TEXT_BUFFER_SIZE];

		voicePromptsInit();

		if (firstRun)
		{
			voicePromptsAppendPrompt(PROMPT_SILENCE);
			voicePromptsAppendLanguageString(currentLanguage->radio_info);
			voicePromptsAppendLanguageString(currentLanguage->menu);
			voicePromptsAppendPrompt(PROMPT_SILENCE);
		}

		switch (displayMode)
		{
			case RADIO_INFOS_BATTERY_LEVEL:
			case RADIO_INFOS_BATTERY_GRAPH:
			{
				int volts, mvolts;

				voicePromptsAppendLanguageString(currentLanguage->battery);
				getBatteryVoltage(&volts,  &mvolts);
				snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, " %1d.%1d", volts, mvolts);
				voicePromptsAppendString(buffer);
				voicePromptsAppendPrompt(PROMPT_VOLTS);
				voicePromptsAppendInteger(getBatteryPercentage());
				voicePromptsAppendPrompt(PROMPT_PERCENT);
			}
			break;
			case RADIO_INFOS_TEMPERATURE_LEVEL:
			{
				int temperature = getTemperature();

				voicePromptsAppendLanguageString(currentLanguage->temperature);
				snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%d.%1d", (temperature / 10), (temperature % 10));
				voicePromptsAppendString(buffer);
				voicePromptsAppendLanguageString(currentLanguage->celcius);
			}
			break;
			case RADIO_INFOS_CURRENT_TIME:
				voicePromptsAppendLanguageString(currentLanguage->time);
				if (!(nonVolatileSettings.timezone & 0x80))
				{
					voicePromptsAppendString("UTC");
				}
				snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%02u %02u%02u", timeAndDate.tm_hour, timeAndDate.tm_min, timeAndDate.tm_sec);
				voicePromptsAppendString(buffer);
			break;
			case RADIO_INFOS_LOCATION:
				voicePromptsAppendLanguageString(currentLanguage->location);
				if (settingsLocationIsValid())
				{
					char maidenheadBuf[7];

					buildLocationAndMaidenheadStrings(buffer, maidenheadBuf, true);
					voicePromptsAppendString(buffer);
					voicePromptsAppendPrompt(PROMPT_SILENCE);
					voicePromptsAppendPrompt(PROMPT_SILENCE);
					voicePromptsAppendPrompt(PROMPT_SILENCE);
					voicePromptsAppendString(maidenheadBuf);
				}
				else
				{
					voicePromptsAppendLanguageString(currentLanguage->not_set);
				}
			break;
			case RADIO_INFOS_DATE:
				voicePromptsAppendLanguageString(currentLanguage->date);
				snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%04u %02u %02u", (timeAndDate.tm_year + 1900),(timeAndDate.tm_mon + 1),timeAndDate.tm_mday);
				voicePromptsAppendString(buffer);
			break;
			case RADIO_INFOS_UP_TIME:
				voicePromptsAppendLanguageString(currentLanguage->uptime);
				snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%u", hours);
				voicePromptsAppendString(buffer);
				voicePromptsAppendLanguageString(currentLanguage->hours);
				snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%u", minutes);
				voicePromptsAppendString(buffer);
				voicePromptsAppendLanguageString(currentLanguage->minutes);
			break;
		}

		if (spellIt)
		{
			promptsPlayNotAfterTx();
		}
	}
}

