changeset 805:dd7ce655db26

Adds a simple countdown timer, available as a custom view in surface and dive mode. This can be used to time safety stops, or to prebreathe a CCR (or to boil your breakfast eggs if you are so inclined). The duration of the timer is configurable from 1 second to 9:59 minutes in the System menu. The timer is started by switching to the custom view, and remaining on it until a 10 second delay has elapsed. Once the timer has started the custom view can be changed and the timer will continue running in the background. After the timer has run out 'Finished' will be shown for 10 seconds in the timer custom view, and then automatic switching of custom views (if configured) resumes. In surface mode the dive computer will not go to sleep while the timer is running, and a mini timer will be shown when the timer custom view is not showing. (mikeller)
author heinrichsweikamp
date Mon, 21 Aug 2023 17:20:07 +0200
parents 391b3d420a39
children 9e2ebfc72e8c
files Common/Inc/data_central.h Common/Inc/settings.h Discovery/Inc/t7.h Discovery/Inc/tHome.h Discovery/Inc/tStructure.h Discovery/Inc/text_multilanguage.h Discovery/Src/base.c Discovery/Src/data_central.c Discovery/Src/data_exchange_main.c Discovery/Src/settings.c Discovery/Src/simulation.c Discovery/Src/t7.c Discovery/Src/tHome.c Discovery/Src/tMenuEditCustom.c Discovery/Src/tMenuEditPlanner.c Discovery/Src/tMenuEditSystem.c Discovery/Src/tMenuSystem.c Discovery/Src/text_multilanguage.c
diffstat 18 files changed, 407 insertions(+), 42 deletions(-) [+]
line wrap: on
line diff
--- a/Common/Inc/data_central.h	Thu Aug 10 21:35:34 2023 +0200
+++ b/Common/Inc/data_central.h	Mon Aug 21 17:20:07 2023 +0200
@@ -360,6 +360,14 @@
 		CHARGER_lostConnection
 };
 
+typedef enum {
+    TIMER_STATE_OFF = 0,
+    TIMER_STATE_PRESTART,
+    TIMER_STATE_RUNNING,
+    TIMER_STATE_WAIT_FINISHED,
+    TIMER_STATE_FINISHED,
+} timerState_e;
+
 typedef struct
 {
 	SDiveSettings diveSettings;
@@ -390,6 +398,9 @@
 	uint8_t sensorErrorsRTE;
 
 	uint8_t lastKnownBatteryPercentage;
+
+    timerState_e timerState;
+    int timerStartedS;
 } 	SDiveState;
 
 
@@ -512,4 +523,6 @@
 void setCompassHeading(uint16_t heading);
 
 const SDecoinfo *getDecoInfo(void);
+
+void disableTimer(void);
 #endif // DATA_CENTRAL_H
--- a/Common/Inc/settings.h	Thu Aug 10 21:35:34 2023 +0200
+++ b/Common/Inc/settings.h	Mon Aug 21 17:20:07 2023 +0200
@@ -87,7 +87,7 @@
 
 #define UART_MAX_PROTOCOL		(2u)
 
-#define FUTURE_SPARE_SIZE		(2u)		/* Applied for reuse of old, not used, scooter block (was 32 bytes)*/
+#define FUTURE_SPARE_SIZE		(0u)		/* Applied for reuse of old, not used, scooter block (was 32 bytes)*/
 
 typedef enum
 {
@@ -246,7 +246,8 @@
 	uint8_t ext_sensor_map_Obsolete[5];
 	uint8_t buttonLockActive;									/* redefined in 0xFFFF0025 */
 	int8_t compassDeclinationDeg;
-    uint8_t delaySetpointLow;                                         /* redefined in 0xFFFF0026 */
+	uint8_t delaySetpointLow;
+	uint16_t timerDurationS;										/* redefined in 0xFFFF0026 */
 	uint8_t Future_SPARE[FUTURE_SPARE_SIZE];					/* redefined in 0xFFFF0020 (old scooter Block was 32 byte)*/
 	// new in 0xFFFF0006
 	uint8_t ppo2sensors_deactivated;
--- a/Discovery/Inc/t7.h	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Inc/t7.h	Mon Aug 21 17:20:07 2023 +0200
@@ -86,4 +86,7 @@
 	 void t7c_refresh(uint32_t FramebufferStartAddress);
 */
 
+void t7_tick(void);
+
+bool t7_isTimerRunning(bool foregroundOnly);
 #endif /* T7_H */
--- a/Discovery/Inc/tHome.h	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Inc/tHome.h	Mon Aug 21 17:20:07 2023 +0200
@@ -76,6 +76,7 @@
 		CVIEW_SummaryOfLeftCorner,
 		CVIEW_Charger,
 		CVIEW_CcrSummary,
+        CVIEW_Timer,
 		CVIEW_END,
 		CVIEW_T3_Decostop,
 		CVIEW_T3_TTS,
--- a/Discovery/Inc/tStructure.h	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Inc/tStructure.h	Mon Aug 21 17:20:07 2023 +0200
@@ -304,32 +304,34 @@
 #define StMSYS1_DST			_MB(2,8,1,6,0)
 #define StMSYS1_12HR    _MB(2,8,1,7,0)
 
-#define StMSYS2_English	_MB(2,8,2,1,0)
-#define StMSYS2_German	_MB(2,8,2,2,0)
-#define StMSYS2_French	_MB(2,8,2,3,0)
-#define StMSYS2_Italian	_MB(2,8,2,4,0)
-#define StMSYS2_Espanol	_MB(2,8,2,5,0)
+#define StMSYS_Timer	_MB(2,8,2,1,0)
 
-#define StMSYS3_Units		_MB(2,8,3,1,0)
-#define StMSYS3_Colors	_MB(2,8,3,2,0)
-#define StMSYS3_Debug		_MB(2,8,3,3,0)
+#define StMSYS2_English	_MB(2,8,3,1,0)
+#define StMSYS2_German	_MB(2,8,3,2,0)
+#define StMSYS2_French	_MB(2,8,3,3,0)
+#define StMSYS2_Italian	_MB(2,8,3,4,0)
+#define StMSYS2_Espanol	_MB(2,8,3,5,0)
 
-#define StMSYS4_Info		_MB(2,8,4,1,0)
+#define StMSYS3_Units		_MB(2,8,4,1,0)
+#define StMSYS3_Colors	_MB(2,8,4,2,0)
+#define StMSYS3_Debug		_MB(2,8,4,3,0)
+
+#define StMSYS4_Info		_MB(2,8,5,1,0)
 
-#define StMSYS5_Exit			_MB(2,8,5,1,0)
-#define StMSYS5_LogbookOffset	_MB(2,8,5,7,0)
-#define StMSYS5_ResetAll		_MB(2,8,5,2,0)
-#define StMSYS5_ResetDeco		_MB(2,8,5,3,0)
-#define StMSYS5_Reboot			_MB(2,8,5,4,0)
-#define StMSYS5_Maintenance		_MB(2,8,5,5,0)
-#define StMSYS5_ResetLogbook	_MB(2,8,5,6,0)
-#define StMSYS5_SetBattCharge	_MB(2,8,5,7,0)
-#define StMSYS5_RebootRTE		_MB(2,8,5,8,0)
-#define StMSYS5_RebootMainCPU	_MB(2,8,5,9,0)
-#define StMSYS5_ScreenTest		_MB(2,8,5,10,0)
-#define StMSYS5_SetFactoryBC	_MB(2,8,5,11,0)
-#define StMSYS5_ResetBluetooth	_MB(2,8,5,12,0)
-#define StMSYS5_SetSampleIndx   _MB(2,8,5,13,0)
+#define StMSYS5_Exit			_MB(2,8,6,1,0)
+#define StMSYS5_LogbookOffset	_MB(2,8,6,7,0)
+#define StMSYS5_ResetAll		_MB(2,8,6,2,0)
+#define StMSYS5_ResetDeco		_MB(2,8,6,3,0)
+#define StMSYS5_Reboot			_MB(2,8,6,4,0)
+#define StMSYS5_Maintenance		_MB(2,8,6,5,0)
+#define StMSYS5_ResetLogbook	_MB(2,8,6,6,0)
+#define StMSYS5_SetBattCharge	_MB(2,8,6,7,0)
+#define StMSYS5_RebootRTE		_MB(2,8,6,8,0)
+#define StMSYS5_RebootMainCPU	_MB(2,8,6,9,0)
+#define StMSYS5_ScreenTest		_MB(2,8,6,10,0)
+#define StMSYS5_SetFactoryBC	_MB(2,8,6,11,0)
+#define StMSYS5_ResetBluetooth	_MB(2,8,6,12,0)
+#define StMSYS5_SetSampleIndx   _MB(2,8,6,13,0)
 
  /* PAGE 9 */
 
--- a/Discovery/Inc/text_multilanguage.h	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Inc/text_multilanguage.h	Mon Aug 21 17:20:07 2023 +0200
@@ -370,6 +370,10 @@
         TXT2BYTE_Clear,
         TXT2BYTE_Reset,
 
+        TXT2BYTE_Timer,
+        TXT2BYTE_Starting,
+        TXT2BYTE_Finished,
+
 		TXT2BYTE_END
 };
 
--- a/Discovery/Src/base.c	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Src/base.c	Mon Aug 21 17:20:07 2023 +0200
@@ -1850,9 +1850,13 @@
 				{
 					timeout_limit_Surface_in_seconds = settingsGetPointer()->timeoutSurfacemode;
 				}
-				if(timeout_in_seconds  >= timeout_limit_Surface_in_seconds)
-				{
-					gotoSleep();
+				if (timeout_in_seconds >= timeout_limit_Surface_in_seconds) {
+                    if (t7_isTimerRunning(true)) {
+                        // Delay sleep until the timer has elapsed
+                        timeout_in_seconds = timeout_limit_Surface_in_seconds - 1;
+                    } else {
+					    gotoSleep();
+                    }
 				}
 				break;
 			case BaseMenu:
--- a/Discovery/Src/data_central.c	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Src/data_central.c	Mon Aug 21 17:20:07 2023 +0200
@@ -889,3 +889,9 @@
 
     return decoInfo;
 }
+
+
+void disableTimer(void)
+{
+    stateUsedWrite->timerState = TIMER_STATE_OFF;
+}
--- a/Discovery/Src/data_exchange_main.c	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Src/data_exchange_main.c	Mon Aug 21 17:20:07 2023 +0200
@@ -1025,6 +1025,8 @@
 			{
 				simulation_exit();
 			}
+            disableTimer();
+
 				// new 170508
 			settingsGetPointer()->bluetoothActive = 0;
 			MX_Bluetooth_PowerOff();
@@ -1047,6 +1049,8 @@
 			}
 			createDiveSettings();
 
+            disableTimer();
+
 			if(pStateReal->warnings.cnsHigh)
 			{
 				if(pStateReal->lifeData.cns >= 130)
--- a/Discovery/Src/settings.c	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Src/settings.c	Mon Aug 21 17:20:07 2023 +0200
@@ -337,6 +337,7 @@
 	.buttonLockActive = 0,
     .compassDeclinationDeg = 0,
     .delaySetpointLow = false,
+    .timerDurationS = 180,
 };
 
 /* Private function prototypes -----------------------------------------------*/
@@ -544,6 +545,9 @@
         // Disable auto setpoint to avoid a configuration warning being triggered by the new auto setpoint validation
         // This ensures that users don't lose setpoint information if it is not in the right spot for the new configuration
         pSettings->autoSetpoint = false;
+
+        pSettings->timerDurationS = pStandard->timerDurationS;
+
     	// no break;
     case 0xFFFF0026:
     	pSettings->ext_sensor_map[0] = pSettings->ext_sensor_map_Obsolete[0];
@@ -554,7 +558,8 @@
     	pSettings->ext_sensor_map[5] = SENSOR_NONE;
     	pSettings->ext_sensor_map[6] = SENSOR_NONE;
     	pSettings->ext_sensor_map[7] = SENSOR_NONE;
-    	// no break;
+
+        // no break;
     default:
         pSettings->header = pStandard->header;
         break; // no break before!!
@@ -1621,6 +1626,16 @@
         corrections++;
     }
 
+    if (Settings.timerDurationS > 599) {
+        Settings.timerDurationS = 599;
+
+        corrections++;
+    } else if (Settings.timerDurationS < 1) {
+        Settings.timerDurationS = 1;
+
+        corrections++;
+    }
+
     if(corrections)
     {
     	settingsWarning = 1;
--- a/Discovery/Src/simulation.c	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Src/simulation.c	Mon Aug 21 17:20:07 2023 +0200
@@ -111,6 +111,9 @@
 void simulation_exit(void)
 {
     timer_Stopwatch_Stop();
+
+    disableTimer();
+
     set_stateUsedToReal();
 }
 
--- a/Discovery/Src/t7.c	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Src/t7.c	Mon Aug 21 17:20:07 2023 +0200
@@ -28,7 +28,6 @@
 
 /* Includes ------------------------------------------------------------------*/
 #include <stdlib.h>
-#include <stdbool.h>
 
 #include "t7.h"
 #include "t3.h"
@@ -48,6 +47,8 @@
 #include "base.h"
 #include "tMenuEditSetpoint.h"
 
+#define TIMER_ACTION_DELAY_S 10
+
 /* Private function prototypes -----------------------------------------------*/
 
 void t7_refresh_surface(void);
@@ -130,6 +131,7 @@
     CVIEW_sensors_mV,
     CVIEW_EADTime,
     CVIEW_SummaryOfLeftCorner,
+    CVIEW_Timer,
     CVIEW_noneOrDebug,
     CVIEW_END,
     CVIEW_END
@@ -146,6 +148,7 @@
     CVIEW_sensors_mV,
 	CVIEW_Charger,
     CVIEW_CcrSummary,
+    CVIEW_Timer,
     CVIEW_END
 };
 
@@ -998,6 +1001,12 @@
         GFX_write_string(&FontT48,&t7surfaceR,text,1);
     }
 
+    if (stateUsed->timerState == TIMER_STATE_RUNNING && selection_customview != CVIEW_Timer) {
+        int timerRemainingS = pSettings->timerDurationS - (current_second() - stateUsed->timerStartedS);
+        snprintf(text, 20, "\001%u:%02u", timerRemainingS / 60, timerRemainingS % 60);
+        GFX_write_string(&FontT54, &t7surfaceR, text, 8);
+    }
+
     /* beta version */
     if( firmwareDataGetPointer()->versionBeta )
     {
@@ -1973,6 +1982,163 @@
 }
 
 
+static void setTimerPrestart(int startTimeS)
+{
+    stateUsedWrite->timerState = TIMER_STATE_PRESTART;
+    stateUsedWrite->timerStartedS = startTimeS;
+}
+
+
+static void setTimerFinished(int startTimeS)
+{
+    stateUsedWrite->timerState = TIMER_STATE_FINISHED;
+    stateUsedWrite->timerStartedS = startTimeS;
+}
+
+
+static void updateTimer(SSettings *settings, int nowS, bool switchedToTimerView)
+{
+    int timerElapsedS = nowS - stateUsed->timerStartedS;
+
+    if (stateUsed->timerState && timerElapsedS < 0) {
+        disableTimer();
+    } else {
+        switch (stateUsed->timerState) {
+        case TIMER_STATE_OFF:
+            if (switchedToTimerView) {
+                setTimerPrestart(nowS);
+            }
+
+            break;
+        case TIMER_STATE_PRESTART:
+            if (timerElapsedS <= TIMER_ACTION_DELAY_S) {
+                if (switchedToTimerView) {
+                    setTimerPrestart(nowS);
+                }
+            } else {
+                if (selection_customview == CVIEW_Timer) {
+                    stateUsedWrite->timerState = TIMER_STATE_RUNNING;
+                    stateUsedWrite->timerStartedS = stateUsed->timerStartedS + TIMER_ACTION_DELAY_S;
+                } else {
+                    disableTimer();
+                }
+            }
+
+            break;
+        case TIMER_STATE_RUNNING:
+            if (timerElapsedS >= settings->timerDurationS) {
+                if (selection_customview == CVIEW_Timer) {
+                    setTimerFinished(stateUsed->timerStartedS + settings->timerDurationS);
+                } else {
+                    stateUsedWrite->timerState = TIMER_STATE_WAIT_FINISHED;
+                }
+            }
+
+            break;
+        case TIMER_STATE_WAIT_FINISHED:
+            if (switchedToTimerView) {
+                setTimerFinished(nowS);
+            }
+
+            break;
+        case TIMER_STATE_FINISHED:
+            if (timerElapsedS <= TIMER_ACTION_DELAY_S) {
+                if (switchedToTimerView) {
+                    setTimerPrestart(stateUsed->timerStartedS);
+                }
+            } else {
+                disableTimer();
+            }
+
+            break;
+        }
+    }
+}
+
+
+bool t7_isTimerRunning(bool includeBackground)
+{
+    return stateUsed->timerState && (selection_customview == CVIEW_Timer || (includeBackground && stateUsed->timerState == TIMER_STATE_RUNNING));
+}
+
+
+static void showTimer(SSettings *settings, int nowS)
+{
+    char heading[32];
+    unsigned headingIndex = 0;
+
+    char data[32];
+    unsigned dataIndex = 0;
+
+    heading[headingIndex++] = '\032';
+    heading[headingIndex++] = '\016';
+    heading[headingIndex++] = '\016';
+
+    data[dataIndex++] = '\t';
+
+    int timerRemainingS = settings->timerDurationS;
+    switch (stateUsed->timerState) {
+    case TIMER_STATE_RUNNING:
+        timerRemainingS = settings->timerDurationS - (nowS - stateUsed->timerStartedS);
+
+        break;
+    case TIMER_STATE_PRESTART:
+    case TIMER_STATE_FINISHED:
+        if (stateUsed->timerState == TIMER_STATE_PRESTART) {
+            heading[headingIndex++] = TXT_2BYTE;
+            heading[headingIndex++] = TXT2BYTE_Starting;
+        } else {
+            heading[headingIndex++] = TXT_2BYTE;
+            heading[headingIndex++] = TXT2BYTE_Finished;
+
+            timerRemainingS = 0;
+        }
+
+        dataIndex += snprintf(&data[dataIndex], 10, "\020%u", TIMER_ACTION_DELAY_S - (nowS - stateUsed->timerStartedS));
+
+        break;
+    default:
+
+        break;
+    }
+
+    char timer[16];
+    snprintf(timer, 10, "\001\020%u:%02u", timerRemainingS / 60, timerRemainingS % 60);
+
+    heading[headingIndex++] = 0;
+
+    data[dataIndex++] = 0;
+
+    t7cY0free.WindowLineSpacing = 48;
+    t7cY0free.WindowNumberOfTextLines = 6;
+    t7cY0free.WindowTab = 375;
+
+    t7cY0free.WindowY0 = t7cC.WindowY0 - 10;
+    if (!settings->FlipDisplay) {
+        t7cY0free.WindowX0 += 10;
+        t7cY0free.WindowY0 += 10;
+        t7cY0free.WindowY1 = 355;
+        GFX_write_string(&FontT24, &t7cY0free, heading, 1);
+        t7cY0free.WindowX0 -= 10;
+        t7cY0free.WindowY0 -= 10;
+    } else {
+        t7cY0free.WindowY1 -= 10;
+        t7cY0free.WindowX1 -= 10;
+        GFX_write_string(&FontT24, &t7cY0free, heading, 1);
+        t7cY0free.WindowY1 += 10;
+        t7cY0free.WindowX1 += 10;
+    }
+
+    t7_colorscheme_mod(data);
+
+    GFX_write_string(&FontT42, &t7cY0free, data, 1);
+
+    t7_colorscheme_mod(timer);
+
+    GFX_write_string(&FontT105, &t7cY0free, timer, 4);
+}
+
+
 void t7_refresh_customview(void)
 {
 	static uint8_t last_customview = CVIEW_END;
@@ -2015,7 +2181,6 @@
 		{
 			t7_change_customview(ACTION_BUTTON_ENTER);
 		}
-		last_customview = selection_customview;
 	}
     switch(selection_customview)
     {
@@ -2513,11 +2678,23 @@
         t7_CcrSummary(pSettings);
 
         break;
+    case CVIEW_Timer:
+        snprintf(text, 100, "\032\f\001%c%c", TXT_2BYTE, TXT2BYTE_Timer);
+        GFX_write_string(&FontT42, &t7cH, text, 0);
+
+        int nowS = current_second();
+
+        updateTimer(pSettings, nowS, last_customview != CVIEW_Timer);
+
+        showTimer(pSettings, nowS);
+
+        break;
     }
+
+    last_customview = selection_customview;
 }
 
 
-
 /* DIVE MODE
  */
 void t7_refresh_divemode(void)
@@ -4483,7 +4660,17 @@
 
 }
 
+
 bool t7_isCompassShowing(void)
 {
     return selection_customview == CVIEW_Compass || selection_custom_field == LLC_Compass;
 }
+
+
+void t7_tick(void)
+{
+    SSettings *settings = settingsGetPointer();
+
+    int nowS = current_second();
+    updateTimer(settings, nowS, false);
+}
--- a/Discovery/Src/tHome.c	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Src/tHome.c	Mon Aug 21 17:20:07 2023 +0200
@@ -59,7 +59,7 @@
 static uint16_t tHome_tick_count_field;
 static uint16_t tHome_tick_count_o2sens;
 
-const uint8_t cv_changelist[] = {CVIEW_Compass, CVIEW_SummaryOfLeftCorner, CVIEW_Tissues, CVIEW_Profile, CVIEW_EADTime, CVIEW_Gaslist, CVIEW_noneOrDebug, CVIEW_Decolist,CVIEW_sensors,CVIEW_sensors_mV, CVIEW_END};
+const uint8_t cv_changelist[] = {CVIEW_Compass, CVIEW_SummaryOfLeftCorner, CVIEW_Tissues, CVIEW_Profile, CVIEW_EADTime, CVIEW_Gaslist, CVIEW_noneOrDebug, CVIEW_Decolist, CVIEW_sensors,CVIEW_sensors_mV, CVIEW_Timer, CVIEW_END};
 const uint8_t cv_changelist_BS[] = {CVIEW_T3_Decostop, CVIEW_sensors, CVIEW_Compass, CVIEW_T3_MaxDepth,CVIEW_T3_StopWatch, CVIEW_T3_TTS, CVIEW_T3_GasList, CVIEW_T3_ppO2andGas, CVIEW_noneOrDebug,
 									CVIEW_T3_Navigation, CVIEW_T3_DepthData, CVIEW_T3_DecoTTS,
 #ifdef ENABLE_T3_PROFILE_VIEW
@@ -638,8 +638,7 @@
         if(tHome_tick_count_cview > (cview *10))
         {
             tHome_tick_count_cview = 0;
-            if(settingsGetPointer()->design == 7)
-            {
+            if (settingsGetPointer()->design == 7 && !t7_isTimerRunning(false)) {
                 t7_set_customview_to_primary();
             }
             if(settingsGetPointer()->design == 3)
@@ -658,6 +657,8 @@
     		t7_select_customview(CVIEW_sensors);
     	}
     }
+
+    t7_tick();
 }
 
 
--- a/Discovery/Src/tMenuEditCustom.c	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Src/tMenuEditCustom.c	Mon Aug 21 17:20:07 2023 +0200
@@ -493,6 +493,9 @@
     case CVIEW_SummaryOfLeftCorner:
         text = TXT2BYTE_Summary;
         break;
+    case CVIEW_Timer:
+        text = TXT2BYTE_Timer;
+        break;
     case CVIEW_noneOrDebug:
     	text = TXT2BYTE_DispNoneDbg;
         break;
@@ -581,6 +584,9 @@
         newValue = CVIEW_SummaryOfLeftCorner;
         break;
     case CVIEW_SummaryOfLeftCorner:
+        newValue = CVIEW_Timer;
+        break;
+    case CVIEW_Timer:
         newValue = CVIEW_noneOrDebug;
         break;
     case CVIEW_noneOrDebug:
--- a/Discovery/Src/tMenuEditPlanner.c	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Src/tMenuEditPlanner.c	Mon Aug 21 17:20:07 2023 +0200
@@ -86,6 +86,9 @@
             settingsGetPointer()->bluetoothActive = 0;
             MX_Bluetooth_PowerOff();
         }
+
+        disableTimer();
+
         simulation_start(tMplan_depth_meter, tMplan_dive_time_minutes);
         exitMenuEdit_to_Home();
         break;
--- a/Discovery/Src/tMenuEditSystem.c	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Src/tMenuEditSystem.c	Mon Aug 21 17:20:07 2023 +0200
@@ -101,6 +101,87 @@
 
 /* Exported functions --------------------------------------------------------*/
 
+static uint8_t OnAction_Timer(uint32_t editId, uint8_t blockNumber, uint8_t digitNumber, uint8_t digitContent, uint8_t action)
+{
+    SSettings *settings = settingsGetPointer();
+    uint8_t digitContentNew;
+    switch (action) {
+    case ACTION_BUTTON_ENTER:
+
+        return digitContent;
+    case ACTION_BUTTON_ENTER_FINAL:
+        {
+            uint32_t timerM;
+            uint32_t timerS;
+            evaluateNewString(editId, &timerM, &timerS, 0, 0);
+            if (timerM > 9) {
+                timerM = 9;
+            }
+            if (timerS > 59) {
+                timerS = 59;
+            }
+
+            uint16_t timerDurationS = 60 * timerM + timerS;
+
+            if (timerDurationS < 1) {
+                timerDurationS = 1;
+            }
+
+            if (timerDurationS != settings->timerDurationS) {
+                settings->timerDurationS = timerDurationS;
+
+                disableTimer();
+
+                tMenuEdit_newInput(editId, settings->timerDurationS / 60, settings->timerDurationS % 60, 0, 0);
+            }
+
+            return EXIT_TO_MENU;
+        }
+    case ACTION_BUTTON_NEXT:
+        digitContentNew = digitContent + 1;
+        if ((blockNumber == 1 && digitNumber == 0 && digitContentNew > '5') || digitContentNew > '9') {
+            digitContentNew = '0';
+        }
+
+        return digitContentNew;
+    case ACTION_BUTTON_BACK:
+        digitContentNew = digitContent - 1;
+        if (digitContentNew < '0') {
+            if (blockNumber == 1 && digitNumber == 0) {
+                digitContentNew = '5';
+            } else {
+                digitContentNew = '9';
+            }
+        }
+
+        return digitContentNew;
+    }
+
+    return EXIT_TO_MENU;
+}
+
+
+static void openEdit_Timer(void)
+{
+    SSettings *settings = settingsGetPointer();
+
+    char text[32];
+    snprintf(text, 32, "\001%c%c", TXT_2BYTE, TXT2BYTE_Timer);
+    write_topline(text);
+
+    uint16_t yPos = ME_Y_LINE_BASE + get_globalState_Menu_Line() * ME_Y_LINE_STEP;
+    snprintf(text, 32, "%c%c", TXT_2BYTE, TXT2BYTE_Timer);
+    write_label_var(30, 299, yPos, &FontT48, text);
+    write_field_udigit(StMSYS_Timer, 300, 392, yPos, &FontT48, "#:##", settings->timerDurationS / 60, settings->timerDurationS % 60, 0, 0);
+    write_label_var(393, 800, yPos, &FontT48, "\016\016 [m:ss]\017");
+
+    write_buttonTextline(TXT2BYTE_ButtonMinus, TXT2BYTE_ButtonEnter, TXT2BYTE_ButtonPlus);
+
+    setEvent(StMSYS_Timer, (uint32_t)OnAction_Timer);
+    startEdit();
+}
+
+
 void openEdit_System(uint8_t line)
 {
     set_globalState_Menu_Line(line);
@@ -115,15 +196,18 @@
             openEdit_DateTime();
         break;
         case 2:
+            openEdit_Timer();
+        break;
+        case 3:
             openEdit_Language();
         break;
-        case 3:
+        case 4:
             openEdit_Design();
         break;
-        case 4:
+        case 5:
             openEdit_Information();
         break;
-        case 5:
+        case 6:
             openEdit_Reset();
         break;
 /*
--- a/Discovery/Src/tMenuSystem.c	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Src/tMenuSystem.c	Mon Aug 21 17:20:07 2023 +0200
@@ -164,7 +164,13 @@
         textPointer += 2;
     }
 
-    if((line == 0) || (line == 2))
+    if (line == 0 || line == 2) {
+        textPointer += snprintf(&text[textPointer], 21, "%c%c\t%u:%02u \016\016[m:ss]\017\n\r", TXT_2BYTE, TXT2BYTE_Timer, data->timerDurationS / 60, data->timerDurationS % 60);
+    } else {
+        textPointer += snprintf(&text[textPointer], 3, "\n\r");
+    }
+
+    if((line == 0) || (line == 3))
     {
         text[textPointer++] = TXT_Language;
         text[textPointer++] = '\t';
@@ -179,7 +185,7 @@
         textPointer += 2;
     }
 
-    if((line == 0) || (line == 3))
+    if((line == 0) || (line == 4))
     {
         text[textPointer++] = TXT_2BYTE;
         text[textPointer++] = TXT2BYTE_Layout;
@@ -252,7 +258,7 @@
         textPointer += 2;
     }
 
-    if((line == 0) || (line == 4))
+    if((line == 0) || (line == 5))
     {
         text[textPointer++] = TXT_Information;
         text[textPointer++] = '\t';
@@ -267,7 +273,7 @@
     strcpy(&text[textPointer],"\n\r");
     textPointer += 2;
 
-    if((line == 0) || (line == 5))
+    if((line == 0) || (line == 6))
     {
         text[textPointer++] = TXT_2BYTE;
         text[textPointer++] = TXT2BYTE_ResetMenu;
--- a/Discovery/Src/text_multilanguage.c	Thu Aug 10 21:35:34 2023 +0200
+++ b/Discovery/Src/text_multilanguage.c	Mon Aug 21 17:20:07 2023 +0200
@@ -1883,6 +1883,24 @@
 static uint8_t text_IT_Custom[] = "Custom";
 static uint8_t text_ES_Custom[] = "Custom";
 
+static uint8_t text_EN_Timer[] = "Timer";
+static uint8_t text_DE_Timer[] = "Timer";
+static uint8_t text_FR_Timer[] = "Minuteur";
+static uint8_t text_IT_Timer[] = "Timer";
+static uint8_t text_ES_Timer[] = "Temporizador";
+
+static uint8_t text_EN_Starting[] = "Start in";
+static uint8_t text_DE_Starting[] = "Startet in";
+static uint8_t text_FR_Starting[] = "Démarre en";
+static uint8_t text_IT_Starting[] = "Inizio in";
+static uint8_t text_ES_Starting[] = "Comienza en";
+
+static uint8_t text_EN_Finished[] = "Finished";
+static uint8_t text_DE_Finished[] = "Beendet";
+static uint8_t text_FR_Finished[] = "Fini";
+static uint8_t text_IT_Finished[] = "Finito";
+static uint8_t text_ES_Finished[] = "Terminado";
+
 /* Lookup Table -------------------------------------------------------------*/
 
 const tText text_array[] =
@@ -2167,4 +2185,8 @@
 	{(uint8_t)TXT2BYTE_Set, 	{text_EN_Set, text_DE_Set, text_FR_Set, text_IT_Set, text_ES_Set}},
 	{(uint8_t)TXT2BYTE_Clear, 	{text_EN_Clear, text_DE_Clear, text_FR_Clear, text_IT_Clear, text_ES_Clear}},
 	{(uint8_t)TXT2BYTE_Reset, 	{text_EN_Reset, text_DE_Reset, text_FR_Reset, text_IT_Reset, text_ES_Reset}},
+
+	{(uint8_t)TXT2BYTE_Timer, 	{text_EN_Timer, text_DE_Timer, text_FR_Timer, text_IT_Timer, text_ES_Timer}},
+	{(uint8_t)TXT2BYTE_Starting, 	{text_EN_Starting, text_DE_Starting, text_FR_Starting, text_IT_Starting, text_ES_Starting}},
+	{(uint8_t)TXT2BYTE_Finished, 	{text_EN_Finished, text_DE_Finished, text_FR_Finished, text_IT_Finished, text_ES_Finished}},
 };