changeset 1014:8c0134a287da GasConsumption

Add a log data event to the scrubber timer at the start of the dive and every time the timer (in minutes) is decremented. The event contains a 12 bit signed integer for the remaining scrubber duration, and two flags for scrubber warning (0x2000, <= 30 minutes remaining) and scrubber error (0x4000, <= 0 minutes remaining). (mikeller)
author heinrichsweikamp
date Sun, 11 May 2025 16:18:20 +0200 (3 months ago)
parents fa1af49319e5
children 5924a2d1d3ba
files Common/Drivers/CMSIS/Include/cmsis_gcc.h Common/Inc/data_central.h Common/Inc/settings.h Discovery/Inc/logbook.h Discovery/Inc/t3.h Discovery/Src/data_exchange_main.c Discovery/Src/logbook.c Discovery/Src/settings.c Discovery/Src/simulation.c Discovery/Src/t3.c Discovery/Src/t7.c Discovery/Src/tCCR.c Discovery/Src/tMenuCvOption.c Discovery/Src/tMenuEditCvOption.c Discovery/Src/tMenuEditPlanner.c Discovery/Src/tMenuEditXtra.c
diffstat 16 files changed, 252 insertions(+), 194 deletions(-) [+]
line wrap: on
line diff
--- a/Common/Drivers/CMSIS/Include/cmsis_gcc.h	Sat May 10 21:27:06 2025 +0200
+++ b/Common/Drivers/CMSIS/Include/cmsis_gcc.h	Sun May 11 16:18:20 2025 +0200
@@ -185,11 +185,10 @@
 
     \param [in]    topOfMainStack  Main Stack Pointer value to set
  */
-// This is deprecated, get the compiler to shut up about it
-//__attribute__( ( always_inline ) ) __STATIC_INLINE void __set_MSP(uint32_t topOfMainStack)
-//{
-//  __ASM volatile ("MSR msp, %0\n" : : "r" (topOfMainStack) : "sp");
-//}
+__attribute__( ( always_inline ) ) __STATIC_INLINE void __set_MSP(uint32_t topOfMainStack)
+{
+  __ASM volatile ("MSR msp, %0\n" : : "r" (topOfMainStack) : "sp");
+}
 
 
 /**
--- a/Common/Inc/data_central.h	Sat May 10 21:27:06 2025 +0200
+++ b/Common/Inc/data_central.h	Sun May 11 16:18:20 2025 +0200
@@ -344,6 +344,8 @@
     uint16_t info_compassHeadingUpdate;
     int16_t gnssPositionUpdate;
     SGnssCoord info_gnssPosition;
+    int16_t scrubberState;
+    uint16_t info_scrubberState;
 } SEvents;
 
 
@@ -598,4 +600,6 @@
 
 uint8_t calculateSlowExit(uint16_t* pCountDownSec, float* pExitDepthMeter, uint8_t* pColor);
 
+bool isScrubberTimerEnabled(const SSettings *settings);
+bool isScrubberTimerRunning(const SDiveState *diveState, const SSettings *settings);
 #endif // DATA_CENTRAL_H
--- a/Common/Inc/settings.h	Sat May 10 21:27:06 2025 +0200
+++ b/Common/Inc/settings.h	Sun May 11 16:18:20 2025 +0200
@@ -81,8 +81,11 @@
 
 #define MAX_VIEWPORT_MODE 		(0x7F)
 
-#define MAX_SCRUBBER_TIME 		(999u)
+#define MAX_SCRUBBER_TIME		(999u)
 #define MIN_SCRUBBER_TIME       -99
+#define SCRUBBER_WARNING_TIME   30
+#define SCRUBBER_ERROR_TIME     0
+
 #define MIN_PPO2_SP_CBAR		(40u)
 
 #define PSCR_MAX_O2_DROP		(15u)
@@ -108,7 +111,7 @@
 
 typedef enum
 {
-	SCRUB_TIMER_OFF = 0,
+	INVALID_SCRUB_TIMER_OFF = 0,
 	SCRUB_TIMER_MINUTES,
 	SCRUB_TIMER_PERCENT,
 	SCRUB_TIMER_END
@@ -258,7 +261,7 @@
 	uint8_t co2_sensor_active;									/* redefined in 0xFFFF0021 */
 	uint8_t ext_uart_protocol;									/* redefined in 0xFFFF0022 */
 
-	uint8_t scubberActiveId;									/* redefined in 0xFFFF0023 */
+	uint8_t scrubberActiveId;									/* redefined in 0xFFFF0023 */
 	SScrubberData scrubberData[2];
 	uint8_t ext_sensor_map_Obsolete[5];
 	uint8_t buttonLockActive;									/* redefined in 0xFFFF0025 */
@@ -409,4 +412,7 @@
 uint8_t isSettingsWarning();
 
 bool checkAndFixSetpointSettings(void);
+
+bool isScrubberWarning(const SScrubberData *scrubberData);
+bool isScrubberError(const SScrubberData *scrubberData);
 #endif // SETTINGS_H
--- a/Discovery/Inc/logbook.h	Sat May 10 21:27:06 2025 +0200
+++ b/Discovery/Inc/logbook.h	Sun May 11 16:18:20 2025 +0200
@@ -221,4 +221,6 @@
 uint16_t logbook_fillDummySampleBuffer(SLogbookHeader* pHeader);
 void logbook_readDummySamples(uint8_t* pTarget, uint16_t length);
 
+void logScrubberState(const SScrubberData *scrubberData);
+
 #endif /* LOGBOOK_H */
--- a/Discovery/Inc/t3.h	Sat May 10 21:27:06 2025 +0200
+++ b/Discovery/Inc/t3.h	Sun May 11 16:18:20 2025 +0200
@@ -44,6 +44,6 @@
 uint8_t t3_customview_disabled(uint8_t view);
 void t3_handleAutofocus(void);
 
-int printScrubberText(char *text, size_t size, const SScrubberData *scrubberData, SSettings *settings);
+unsigned printScrubberText(char *text, size_t size, const SScrubberData scrubberData[], const SSettings *settings, bool useTwoLines);
 
 #endif /* T3_H */
--- a/Discovery/Src/data_exchange_main.c	Sat May 10 21:27:06 2025 +0200
+++ b/Discovery/Src/data_exchange_main.c	Sun May 11 16:18:20 2025 +0200
@@ -849,8 +849,7 @@
 	SDiveState *pStateReal = stateRealGetPointerWrite();
 	uint8_t idx;
 	float meter = 0;
-	SSettings *pSettings;
-	
+
 #ifdef ENABLE_EXTERNAL_PRESSURE
     float CO2Corr = 0.0;
 #endif
@@ -914,6 +913,7 @@
 		}
 	}
 
+	SSettings *pSettings = settingsGetPointer();
 	if((requestNecessary.uw != 0) && (dataIn.confirmRequest.uw != 0))
 	{
 		if(((dataIn.confirmRequest.uw) & CRBUTTON) != 0)
@@ -951,14 +951,12 @@
 
 		if(requestNecessary.ub.button == 1)	/* send button values to RTE */
 		{
-			setButtonResponsiveness(settingsGetPointer()->ButtonResponsiveness);
+			setButtonResponsiveness(pSettings->ButtonResponsiveness);
 		}
 	}
 
 	/*	uint8_t IAmStolenPleaseKillMe;
 	 */
-	pSettings = settingsGetPointer();
-
 	if(pSettings->IAmStolenPleaseKillMe > 3)
 	{
 		pSettings->salinity = 0;
@@ -1046,7 +1044,7 @@
             disableTimer();
 
 				// new 170508
-			settingsGetPointer()->bluetoothActive = 0;
+			pSettings->bluetoothActive = 0;
 			MX_Bluetooth_PowerOff();
 			//Init dive Mode
 			decoLock = DECO_CALC_init_as_is_start_of_dive;
@@ -1376,3 +1374,12 @@
 	return;
 }
 
+bool isScrubberTimerEnabled(const SSettings *settings)
+{
+	return settings->scrubberActiveId != 0x00 && isLoopMode(settings->dive_mode);
+}
+
+bool isScrubberTimerRunning(const SDiveState *diveState, const SSettings *settings)
+{
+	return isScrubberTimerEnabled(settings) && diveState->mode == MODE_DIVE && isLoopMode(diveState->diveSettings.diveMode);
+}
--- a/Discovery/Src/logbook.c	Sat May 10 21:27:06 2025 +0200
+++ b/Discovery/Src/logbook.c	Sun May 11 16:18:20 2025 +0200
@@ -67,6 +67,9 @@
 #define DEFAULT_SAMPLES	(100)	/* Number of sample data bytes in case of an broken header information */
 #define DUMMY_SAMPLES	(1000)	/* Maximum number of samples profided by a dummy dive profile */
 
+#define SCRUBBER_ERROR_FLAG 0x4000
+#define SCRUBBER_WARNING_FLAG 0x2000
+
 typedef struct /* don't forget to adjust void clear_divisor(void) */
 {
 	uint8_t temperature;
@@ -467,6 +470,10 @@
             eventByte1.ub.bit7 = 1;
             eventByte2.ub.bit2 = 1;
     }
+    if (state->events.scrubberState) {
+        eventByte1.ub.bit7 = 1;
+        eventByte2.ub.bit3 = 1;
+    }
 
     //Add EventByte 1
     if(eventByte1.uw > 0)
@@ -510,8 +517,9 @@
     }
     if (state->events.compassHeadingUpdate) {
         // New heading and type of heading
-        sample[length++] = state->events.info_compassHeadingUpdate & 0xFF;
-        sample[length++] = (state->events.info_compassHeadingUpdate & 0xFF00) >> 8;
+        pdata = (uint8_t*)&state->events.info_compassHeadingUpdate;
+        sample[length++] = *pdata++;
+        sample[length++] = *pdata++;
     }
     if (state->events.gnssPositionUpdate) {
     	pdata = (uint8_t*)&state->events.info_gnssPosition.fLon;
@@ -525,6 +533,11 @@
         sample[length++] = *pdata++;
         sample[length++] = *pdata++;
     }
+    if (state->events.scrubberState) {
+        pdata = (uint8_t*)&state->events.info_scrubberState;
+        sample[length++] = *pdata++;
+        sample[length++] = *pdata++;
+    }
 
     if(divisor.temperature == 0)
     {
@@ -1321,8 +1334,23 @@
 		{
 			//InitdiveProfile
 			pSettings->totalDiveCounter++;
-			logbook_initNewdiveProfile(pStateReal,settingsGetPointer());
+			logbook_initNewdiveProfile(pStateReal, pSettings);
 			min_temperature_float_celsius = pStateReal->lifeData.temperature_celsius;
+            if (isScrubberTimerRunning(pStateReal, pSettings)) {
+                int16_t maxScrubberTime = INT16_MIN;
+                SScrubberData *longestScrubberData = NULL;
+                for (unsigned timerId = 0; timerId < 2; timerId++) {
+                    if (pSettings->scrubberActiveId & (1 << timerId)) {
+                        SScrubberData *scrubberData = &pStateReal->scrubberDataDive[timerId];
+                        if (scrubberData->TimerCur > maxScrubberTime) {
+                            maxScrubberTime = scrubberData->TimerCur;
+                            longestScrubberData = scrubberData;
+                        }
+                    }
+                }
+
+                logScrubberState(longestScrubberData);
+            }
 
 			//Write logbook sample
 			logbook_writeSample(pStateReal);
@@ -2146,5 +2174,19 @@
 	dummyReadIdx += length;
 }
 
+void logScrubberState(const SScrubberData *scrubberData)
+{
+    uint16_t scrubberState = scrubberData->TimerCur & 0x0FFF; // truncate to 12 bit
+    if (isScrubberError(scrubberData)) {
+        scrubberState |= SCRUBBER_ERROR_FLAG;
+    }
+
+    if (isScrubberWarning(scrubberData)) {
+        scrubberState |= SCRUBBER_WARNING_FLAG;
+    }
+
+    stateUsedWrite->events.scrubberState = 1;
+    stateUsedWrite->events.info_scrubberState = scrubberState;
+}
 
 /************************ (C) COPYRIGHT heinrichs weikamp *****END OF FILE****/
--- a/Discovery/Src/settings.c	Sat May 10 21:27:06 2025 +0200
+++ b/Discovery/Src/settings.c	Sun May 11 16:18:20 2025 +0200
@@ -330,7 +330,7 @@
 	.autoSetpoint = 0,
 	.scrubTimerMax_Obsolete = 0,
 	.scrubTimerCur_Obsolete = 0,
-	.scrubTimerMode = SCRUB_TIMER_OFF,
+	.scrubTimerMode = SCRUB_TIMER_MINUTES,
 	.ext_sensor_map[0] = SENSOR_OPTIC,
 	.ext_sensor_map[1] = SENSOR_OPTIC,
 	.ext_sensor_map[2] = SENSOR_OPTIC,
@@ -501,7 +501,7 @@
         // no break
     case 0xFFFF0018:
     	pSettings->cv_configuration = 0xFFFFFFFF;
-    	pSettings->cv_configuration &= pSettings->cv_configuration ^= 1 << (CVIEW_Timer);
+        pSettings->cv_configuration &= ~(1 << CVIEW_sensors | 1 << CVIEW_sensors_mV | 1 << CVIEW_Timer);
     	// no break
     case 0xFFFF0019:
     	pSettings->MotionDetection = MOTION_DETECT_OFF;
@@ -509,13 +509,13 @@
     case 0xFFFF001A:
     	/* deactivate new views => to be activated by customer */
         pSettings->cv_config_BigScreen = 0xFFFFFFFF;
-        pSettings->cv_config_BigScreen &= pSettings->cv_configuration ^= 1 << (CVIEW_T3_Navigation + LEGACY_T3_START_ID_PRE_TIMER);
-        pSettings->cv_config_BigScreen &= pSettings->cv_configuration ^= 1 << (CVIEW_T3_DepthData + LEGACY_T3_START_ID_PRE_TIMER);
+        pSettings->cv_config_BigScreen &= 1 << (CVIEW_T3_Navigation + LEGACY_T3_START_ID_PRE_TIMER);
+        pSettings->cv_config_BigScreen &= 1 << (CVIEW_T3_DepthData + LEGACY_T3_START_ID_PRE_TIMER);
         // no break
     case 0xFFFF001B:
     	pSettings->compassInertia = 0; 			/* no inertia */
     	pSettings->tX_customViewPrimaryBF = CVIEW_T3_Decostop;
-    	pSettings->cv_config_BigScreen &= pSettings->cv_configuration ^= 1 << (CVIEW_T3_DecoTTS + LEGACY_T3_START_ID_PRE_TIMER);
+        pSettings->cv_config_BigScreen &= 1 << (CVIEW_T3_DecoTTS + LEGACY_T3_START_ID_PRE_TIMER);
         // no break
     case 0xFFFF001C:
     	pSettings->viewPortMode = 0;
@@ -534,7 +534,7 @@
     	pSettings->autoSetpoint = 0;
     	pSettings->scrubTimerMax_Obsolete = 0;
     	pSettings->scrubTimerCur_Obsolete = 0;
-    	pSettings->scrubTimerMode = SCRUB_TIMER_OFF;
+        pSettings->scrubTimerMode = SCRUB_TIMER_MINUTES;
     	// no break
     case 0xFFFF001F:
     	pSettings->pscr_lung_ratio = 10;
@@ -547,7 +547,7 @@
     	pSettings->ext_uart_protocol = 0;
     	// no break;
     case 0xFFFF0022:
-    	pSettings->scubberActiveId = 0;
+        pSettings->scrubberActiveId = 0;
     	pSettings->scrubberData[0].TimerCur = pSettings->scrubTimerCur_Obsolete;
     	pSettings->scrubberData[0].TimerMax = pSettings->scrubTimerMax_Obsolete;
     	pSettings->scrubberData[0].lastDive.WeekDay = 0;
@@ -1767,11 +1767,11 @@
     	setFirstCorrection(parameterId);
     }
     parameterId++; /* 91 */
-    if(Settings.scubberActiveId > 3)	/* scrubber active is used as bitfield => two timer => 2 bits in use */
-    {
-    	Settings.scubberActiveId = 0;
-    	corrections++;
-    	setFirstCorrection(parameterId);
+    if (Settings.scrubberActiveId > 0x03) {
+        /* scrubber active is used as bitfield => two timer => 2 bits in use */
+        Settings.scrubberActiveId = 0x00;
+        corrections++;
+        setFirstCorrection(parameterId);
     }
     parameterId++; /* 92 */
     if((Settings.scrubberData[0].TimerMax > MAX_SCRUBBER_TIME) || Settings.scrubberData[0].TimerCur < MIN_SCRUBBER_TIME || Settings.scrubberData[0].TimerCur > (int16_t)MAX_SCRUBBER_TIME)
@@ -1790,9 +1790,8 @@
     	setFirstCorrection(parameterId);
     }
     parameterId++; /* 94 */
-    if(Settings.scrubTimerMode > SCRUB_TIMER_END)
-    {
-    	Settings.scrubTimerMode = SCRUB_TIMER_OFF;
+    if (Settings.scrubTimerMode == INVALID_SCRUB_TIMER_OFF || Settings.scrubTimerMode > SCRUB_TIMER_END) {
+        Settings.scrubTimerMode = SCRUB_TIMER_MINUTES;
     	corrections++;
     	setFirstCorrection(parameterId);
     }
@@ -1996,7 +1995,7 @@
 
     memset(pSettings->customtext,0,60);
     sprintf(pSettings->customtext," <your name>\n <address>");
-    pSettings->cv_configuration &= (pSettings->cv_configuration ^= 1 << (CVIEW_Timer));
+    pSettings->cv_configuration &= ~(1 << CVIEW_sensors | 1 << CVIEW_sensors_mV | 1 << CVIEW_Timer);
 
     set_new_settings_missing_in_ext_flash();
     check_and_correct_settings();
@@ -3389,7 +3388,26 @@
 {
 	settingsWarning = 0;
 }
+
 inline uint8_t isSettingsWarning()
 {
 	return settingsWarning;
 }
+
+bool isScrubberError(const SScrubberData *scrubberData)
+{
+    if (scrubberData->TimerCur <= SCRUBBER_ERROR_TIME) {
+        return true;
+    }
+
+    return false;
+}
+
+bool isScrubberWarning(const SScrubberData *scrubberData)
+{
+    if (scrubberData->TimerCur <= SCRUBBER_WARNING_TIME) {
+        return true;
+    }
+
+    return false;
+}
--- a/Discovery/Src/simulation.c	Sat May 10 21:27:06 2025 +0200
+++ b/Discovery/Src/simulation.c	Sun May 11 16:18:20 2025 +0200
@@ -43,6 +43,7 @@
 #include "vpm.h"
 #include "buehlmann.h"
 #include "logbook_miniLive.h"
+#include "logbook.h"
 
 #include "configuration.h"
 
@@ -111,7 +112,8 @@
 
     stateSim.lifeData.apnea_total_max_depth_meter = 0;
 
-    memcpy(stateSim.scrubberDataDive, settingsGetPointer()->scrubberData, sizeof(stateSim.scrubberDataDive));
+    SSettings *settings = settingsGetPointer();
+    memcpy(stateSim.scrubberDataDive, settings->scrubberData, sizeof(stateSim.scrubberDataDive));
     memset(simSensmVOffset,0,sizeof(simSensmVOffset));
    	if(getReplayOffset() != 0xFFFF)
    	{
@@ -151,7 +153,6 @@
     SDiveState * pDiveState = &stateSim;
     const SDiveState * pRealState = stateRealGetPointer();
 	SSettings *pSettings;
-	uint8_t timerId = 0;
 
     static int last_second = -1;
     static _Bool two_second = 0;
@@ -245,27 +246,35 @@
     	pDiveState->lifeData.ascent_rate_meter_per_min = 0;
     }
 
-    if((pSettings->scrubTimerMode != SCRUB_TIMER_OFF) && (isLoopMode(pSettings->dive_mode)) && (pDiveState->mode == MODE_DIVE) && isLoopMode(pDiveState->diveSettings.diveMode))
-    {
-    	simScrubberTimeoutCount++;
-    	if(simScrubberTimeoutCount >= 60)		/* resolution is minutes */
-    	{
-    		simScrubberTimeoutCount = 0;
-    		for(timerId = 0; timerId < 2; timerId++)
-    		{
-    		   	if(pSettings->scubberActiveId & (1 << timerId))
-    		   	{
-					if(pDiveState->scrubberDataDive[timerId].TimerCur > MIN_SCRUBBER_TIME)
-					{
-						pDiveState->scrubberDataDive[timerId].TimerCur--;
-					}
-					translateDate(stateUsed->lifeData.dateBinaryFormat, &pDiveState->scrubberDataDive[timerId].lastDive);
-    		   	}
-    		}
-    	}
+    if (isScrubberTimerRunning(pDiveState, pSettings)) {
+        simScrubberTimeoutCount++;
+
+        if (simScrubberTimeoutCount >= 60) {
+            /* resolution is minutes */
+            simScrubberTimeoutCount = 0;
+
+            int16_t maxScrubberTime = INT16_MIN;
+            SScrubberData *longestScrubberData = NULL;
+            for (unsigned timerId = 0; timerId < 2; timerId++) {
+                if (pSettings->scrubberActiveId & (1 << timerId)) {
+                    SScrubberData *scrubberData = &pDiveState->scrubberDataDive[timerId];
+                    if (scrubberData->TimerCur > MIN_SCRUBBER_TIME) {
+                        scrubberData->TimerCur--;
+                    }
+
+                    if (scrubberData->TimerCur > maxScrubberTime) {
+                        maxScrubberTime = scrubberData->TimerCur;
+                        longestScrubberData = scrubberData;
+                    }
+
+                    translateDate(stateUsed->lifeData.dateBinaryFormat, &scrubberData->lastDive);
+                }
+            }
+
+            logScrubberState(longestScrubberData);
+        }
     }
 
-
     if(lastPressure_bar > 0)
      {
          //1 second * 60 == 1 minute, bar * 10 = meter
--- a/Discovery/Src/t3.c	Sat May 10 21:27:06 2025 +0200
+++ b/Discovery/Src/t3.c	Sun May 11 16:18:20 2025 +0200
@@ -33,6 +33,7 @@
 #include "t3.h"
 
 #include "data_exchange_main.h"
+#include "settings.h"
 #include "decom.h"
 #include "gfx_fonts.h"
 #include "math.h"
@@ -1334,14 +1335,13 @@
                 GFX_write_string(&FontT105,tXc1,text,1);
             }
 
-            if((pSettings->scrubTimerMode != SCRUB_TIMER_OFF) && isLoopMode(pSettings->dive_mode))
-            {
+            if (isScrubberTimerEnabled(pSettings)) {
                  snprintf(text,TEXTSIZE,"\032\002\f%c",TXT_ScrubTime);
                  GFX_write_string(&FontT42,tXc1,text,0);
 
                 textpointer = 0;
                 text[textpointer++] = '\002';
-                textpointer += printScrubberText(&text[textpointer], 10, stateUsed->scrubberDataDive, pSettings);
+                textpointer += printScrubberText(&text[textpointer], 10, stateUsed->scrubberDataDive, pSettings, false);
                 GFX_write_string(&FontT105,tXc1,text,1);
             }
         }
@@ -1996,41 +1996,45 @@
     return t3_selection_customview;
 }
 
-int printScrubberText(char *text, size_t size, const SScrubberData *scrubberData, SSettings *settings)
+unsigned printScrubberText(char *text, size_t size, const SScrubberData scrubberData[], const SSettings *settings, bool useTwoLines)
 {
-	uint8_t timerId = 0;
-	int16_t currentTimerMinutes = 0;
-	char colour = 0;
-	uint8_t textIndex = 0;
+    unsigned textIndex = 0;
+	for (unsigned timerId = 0; timerId < 2; timerId++) {
+		if (settings->scrubberActiveId & (1 << timerId)) {
+            const SScrubberData *currentScrubberData = &scrubberData[timerId];
+            char colour = '\020';
+            if (isScrubberError(currentScrubberData)) {
+                colour = '\025';
+            } else if (isScrubberWarning(currentScrubberData)) {
+                colour = '\024';
+            }
+
+            if (useTwoLines) {
+                textIndex += snprintf(&text[textIndex], size, "\016\016");
+            }
 
-	for(timerId = 0; timerId < 2; timerId++)
-	{
-		if(settings->scubberActiveId & (1 << timerId))
-		{
-			currentTimerMinutes = scrubberData[timerId].TimerCur;
-			colour = '\020';
-			if (currentTimerMinutes <= 0)
-			{
-				colour = '\025';
+            int16_t currentTimerMinutes = currentScrubberData->TimerCur;
+            if (settings->scrubTimerMode == SCRUB_TIMER_MINUTES || currentTimerMinutes < 0) {
+                textIndex += snprintf(&text[textIndex], size, "%c%3i'", colour, currentTimerMinutes);
+            } else {
+                if (useTwoLines) {
+                    textIndex += snprintf(&text[textIndex], size, "%c%u%%", colour, currentTimerMinutes * 100 / settings->scrubberData[timerId].TimerMax);
+                } else {
+                    textIndex += snprintf(&text[textIndex], size, "%c%u\016\016%%\017", colour, currentTimerMinutes * 100 / settings->scrubberData[timerId].TimerMax);
+                }
+            }
+
+			if (settings->scrubberActiveId == 0x03 && timerId == 0)	{
+                /* both timers are active => print separator */
+                if (useTwoLines) {
+                    textIndex += snprintf(&text[textIndex], size, "\r\n");
+                } else {
+                    textIndex += snprintf(&text[textIndex], size, "\020|");
+                }
 			}
-			else if (currentTimerMinutes <= 30)
-			{
-				colour = '\024';
-			}
-			if (settings->scrubTimerMode == SCRUB_TIMER_MINUTES || currentTimerMinutes < 0)
-			{
-				textIndex += snprintf(&text[textIndex], size, "%c%3i'", colour, currentTimerMinutes);
-			}
-			else
-			{
-				textIndex += snprintf(&text[textIndex], size, "%c%u\016\016%%\017", colour, currentTimerMinutes * 100 / settingsGetPointer()->scrubberData[timerId].TimerMax);
-			}
-			if((settings->scubberActiveId == 3) && (timerId == 0))	/* both timers are active => print separator */
-			{
-				textIndex += snprintf(&text[textIndex], size, " | ");
-			}
-		}
-	}
+        }
+    }
+
 	return textIndex;
 }
 
--- a/Discovery/Src/t7.c	Sat May 10 21:27:06 2025 +0200
+++ b/Discovery/Src/t7.c	Sun May 11 16:18:20 2025 +0200
@@ -1835,7 +1835,7 @@
     }
 
     bool showScrubberTime = false;
-    if (settings->scrubTimerMode != SCRUB_TIMER_OFF) {
+    if (isScrubberTimerEnabled(settings)) {
         numLines++;
         showScrubberTime = true;
     }
@@ -1982,19 +1982,8 @@
 
         data[dataIndex++] = '\n';
         data[dataIndex++] = '\r';
-        data[dataIndex++] = '\002';
-        if(settings->scubberActiveId == 3)	/* switch to small font size to avoid bad alignment */
-        {
-        	data[dataIndex++] = '\016';
-        	data[dataIndex++] = '\016';
-        }
-        dataIndex += printScrubberText(&data[dataIndex], 10, settings->scrubberData, settings);
-        if(settings->scubberActiveId == 3)
-        {
-        	data[dataIndex++] = ' ';
-        	data[dataIndex++] = ' ';
-        }
-
+        data[dataIndex++] = '\t';
+        dataIndex += printScrubberText(&data[dataIndex], 10, settings->scrubberData, settings, false);
     }
 
     heading[headingIndex++] = '\017';
@@ -3283,18 +3272,18 @@
     {
     	selection_custom_field++;
     }
-    if((selection_custom_field == LLC_ScrubberTime) && ((settingsGetPointer()->scrubTimerMode == SCRUB_TIMER_OFF) || (!isLoopMode(settingsGetPointer()->dive_mode))))
-    {
+    SSettings *settings = settingsGetPointer();
+    if (selection_custom_field == LLC_ScrubberTime && !isScrubberTimerEnabled(settings)) {
     	selection_custom_field++;
     }
 #ifdef ENABLE_PSCR_MODE
-    if((selection_custom_field == LCC_SimPpo2) && (settingsGetPointer()->dive_mode != DIVEMODE_PSCR))
+    if((selection_custom_field == LCC_SimPpo2) && (settings->dive_mode != DIVEMODE_PSCR))
     {
     	selection_custom_field++;
     }
 #endif
 #ifdef ENABLE_CO2_SUPPORT
-    if((selection_custom_field == LCC_CO2) && (settingsGetPointer()->co2_sensor_active == 0))
+    if((selection_custom_field == LCC_CO2) && (settings->co2_sensor_active == 0))
     {
        	selection_custom_field++;
     }
@@ -3315,9 +3304,7 @@
 
     char  headerText[10];
     char  text[TEXTSIZE];
-    char  tmpString[TEXTSIZE];
     uint8_t textpointer = 0;
-    uint8_t index = 0;
     _Bool tinyHeaderFont = 0;
     uint8_t line = 0;
 #ifdef ENABLE_BOTTLE_SENSOR
@@ -3433,26 +3420,8 @@
     	tinyHeaderFont = 1;
         headerText[2] = TXT_ScrubTime;
 
-        textpointer = printScrubberText(text, TEXTSIZE, stateUsed->scrubberDataDive, pSettings);
-        if (pSettings->scubberActiveId == 3)	/* both timer active */
-        {
-        	snprintf(tmpString,TEXTSIZE,"\016\016%s",text);
-        	for(index = 0; index < textpointer; index++)
-        	{
-        		if(tmpString[index] == '\017')	/* remove switch to normal font */
-        		{
-        			tmpString[index] = ' ';
-        		}
-        		if(tmpString[index] == '|')	/* replace separator with new line */
-        		{
-        			tmpString[index] = '\n';
-        			tmpString[index+1] = '\r';
-        			break;
-        		}
-        	}
-        	line = 1;
-        	strcpy(text,tmpString);
-        }
+        bool useTwoLines = (pSettings->scrubberActiveId == 0x03);
+        textpointer = printScrubberText(text, TEXTSIZE, stateUsed->scrubberDataDive, pSettings, useTwoLines);
 
 		break;
 #ifdef ENABLE_PSCR_MODE
@@ -4226,7 +4195,7 @@
 
     t7cY0free.WindowLineSpacing = 48;
     t7cY0free.WindowNumberOfTextLines = 6;
-    t7cY0free.WindowTab = 420;
+    t7cY0free.WindowTab = 380;
 
     // header
     textpointer = 0;
@@ -4248,10 +4217,9 @@
     text[textpointer++] = TXT_FutureTTS;
     text[textpointer++] = '\n';
     text[textpointer++] = '\r';
-    if((pSettings->scrubTimerMode != SCRUB_TIMER_OFF) && (isLoopMode(pSettings->dive_mode)))
-    {
-		text[textpointer++] = TXT_ScrubTime;
-
+    if (isScrubberTimerEnabled(pSettings)) {
+		text[textpointer++] = TXT_2BYTE;
+		text[textpointer++] = TXT2BYTE_Scrubber;
     }
     text[textpointer++] = '\017';
 	text[textpointer++] = 0;
@@ -4297,13 +4265,12 @@
     else
     	textpointer += snprintf(&text[textpointer],10,"\020%ih", (pDecoinfoFuture->output_time_to_surface_seconds + 59) / 3600);
 
-    if((pSettings->scrubTimerMode != SCRUB_TIMER_OFF) && (isLoopMode(pSettings->dive_mode)))
-    {
+    if (isScrubberTimerEnabled(pSettings)) {
         text[textpointer++] = '\n';
         text[textpointer++] = '\r';
         text[textpointer++] = '\t';
 
-        textpointer += printScrubberText(&text[textpointer], 10, stateUsed->scrubberDataDive, pSettings);
+        textpointer += printScrubberText(&text[textpointer], 10, stateUsed->scrubberDataDive, pSettings, false);
     }
     text[textpointer++] = 0;
     Gfx_colorsscheme_mod(text, 0);
--- a/Discovery/Src/tCCR.c	Sat May 10 21:27:06 2025 +0200
+++ b/Discovery/Src/tCCR.c	Sun May 11 16:18:20 2025 +0200
@@ -34,6 +34,7 @@
 #include "data_exchange.h"
 #include "check_warning.h"
 #include "configuration.h"
+#include "logbook.h"
 #include <math.h>
 
 /* Private types -------------------------------------------------------------*/
@@ -322,7 +323,6 @@
 void tCCR_tick(void)
 {
 	SSettings* pSettings = settingsGetPointer();
-	uint8_t timerId = 0;
 
 	if(pSettings->ppo2sensors_source == O2_SENSOR_SOURCE_OPTIC)
 	{
@@ -341,23 +341,30 @@
     // If we are in the simulator the counter is updated in `simulator.c`
     if (!is_stateUsedSetToSim()) {
         /* decrease scrubber timer only if we are not bailed out */
-        if((pSettings->scrubTimerMode != SCRUB_TIMER_OFF) && (isLoopMode(pSettings->dive_mode)) && (stateUsed->mode == MODE_DIVE) && isLoopMode(stateUsed->diveSettings.diveMode))
-        {
+        if (isScrubberTimerRunning(stateUsed, pSettings)) {
             ScrubberTimeoutCount++;
-            if(ScrubberTimeoutCount >= 600)		/* resolution is minutes */
-            {
+            if (ScrubberTimeoutCount >= 600) {		/* resolution is minutes */
                 ScrubberTimeoutCount = 0;
-                for(timerId = 0; timerId < 2; timerId++)
-                {
-                	if(pSettings->scubberActiveId & (1 << timerId))
-                	{
-						if(stateUsed->scrubberDataDive[timerId].TimerCur > MIN_SCRUBBER_TIME)
-						{
-							stateUsedWrite->scrubberDataDive[timerId].TimerCur--;
-						}
-						translateDate(stateUsed->lifeData.dateBinaryFormat, &stateUsedWrite->scrubberDataDive[timerId].lastDive);
-                	}
+
+                int16_t maxScrubberTime = INT16_MIN;
+                SScrubberData *longestScrubberData = NULL;
+                for (unsigned timerId = 0; timerId < 2; timerId++) {
+                    if (pSettings->scrubberActiveId & (1 << timerId)) {
+                        SScrubberData *scrubberData = &stateUsedWrite->scrubberDataDive[timerId];
+                        if (scrubberData->TimerCur > MIN_SCRUBBER_TIME) {
+                            scrubberData->TimerCur--;
+                        }
+
+                        if (scrubberData->TimerCur > maxScrubberTime) {
+                            maxScrubberTime = scrubberData->TimerCur;
+                            longestScrubberData = scrubberData;
+                        }
+
+                        translateDate(stateUsed->lifeData.dateBinaryFormat, &scrubberData->lastDive);
+                    }
                 }
+
+                logScrubberState(longestScrubberData);
             }
         }
     }
--- a/Discovery/Src/tMenuCvOption.c	Sat May 10 21:27:06 2025 +0200
+++ b/Discovery/Src/tMenuCvOption.c	Sun May 11 16:18:20 2025 +0200
@@ -67,7 +67,6 @@
     	{
     		text[textPointer++] = '\031';		/* change text color */
     	    textPointer += snprintf(&text[textPointer], 21, "%c%c\t%u:%02u \016\016[m:ss]\017", TXT_2BYTE, TXT2BYTE_Timer, data->timerDurationS / 60, data->timerDurationS % 60);
-    	    disableLine(StMOption_Timer);
             text[textPointer++] = '\020';		/* restore text color */
     	}
     	else
--- a/Discovery/Src/tMenuEditCvOption.c	Sat May 10 21:27:06 2025 +0200
+++ b/Discovery/Src/tMenuEditCvOption.c	Sun May 11 16:18:20 2025 +0200
@@ -328,6 +328,8 @@
                 tMenuEdit_newInput(editId, settings->timerDurationS / 60, settings->timerDurationS % 60, 0, 0);
             }
 
+            settings->cv_configuration |= (1 << CVIEW_Timer);
+
             return EXIT_TO_MENU;
         }
     case ACTION_BUTTON_NEXT:
--- a/Discovery/Src/tMenuEditPlanner.c	Sat May 10 21:27:06 2025 +0200
+++ b/Discovery/Src/tMenuEditPlanner.c	Sun May 11 16:18:20 2025 +0200
@@ -396,11 +396,7 @@
 
     resultPage = 0;
     pDecoinfo = simulation_decoplaner(tMplan_depth_meter, tMplan_intervall_time_minutes, tMplan_dive_time_minutes, gasChangeList);
-    simulation_evaluate_profil( &tMplan_pGasConsumption,
-    							&tMplan_Summary,
-								tMplan_depth_meter, tMplan_dive_time_minutes, tMplan_gasConsumTravel, tMplan_gasConsumDeco,
-								pDecoinfo,
-								gasChangeList);
+    simulation_evaluate_profil(&tMplan_pGasConsumption[0], &tMplan_Summary, tMplan_depth_meter, tMplan_dive_time_minutes, tMplan_gasConsumTravel, tMplan_gasConsumDeco, pDecoinfo, gasChangeList);
   //  simulation_gas_consumption(tMplan_pGasConsumption, tMplan_depth_meter, tMplan_dive_time_minutes, pDecoinfo, tMplan_gasConsumTravel, tMplan_gasConsumDeco, gasChangeList);
  //   simulation_helper_change_points(&tMplan_Summary, tMplan_depth_meter, tMplan_dive_time_minutes, pDecoinfo, gasChangeListDepth);
 
--- a/Discovery/Src/tMenuEditXtra.c	Sat May 10 21:27:06 2025 +0200
+++ b/Discovery/Src/tMenuEditXtra.c	Sun May 11 16:18:20 2025 +0200
@@ -229,7 +229,7 @@
     snprintf(&text[0], 32,"%c \002#%d",TXT_ScrubTime,scrubberMenuId);
     write_field_button(StMXTRA_ScrubTimer, 20, 780, ME_Y_LINE1,  &FontT48, text);
 
-    if(pSettings->scubberActiveId & (1 << scrubberMenuId))
+    if(pSettings->scrubberActiveId & (1 << scrubberMenuId))
     {
     	snprintf(&text[0], 32,"%c %c \002\005", TXT_ScrubTime, TXT_Active);
     }
@@ -255,9 +255,7 @@
 
     if(pSettings->scrubberData[scrubberMenuId].lastDive.WeekDay != 0)
     {
-    	snprintf(&text[0], 32,"%c%c\002       %02d.%02d.%02d", TXT_2BYTE, TXT2BYTE_SimDiveTime, 	pSettings->scrubberData[scrubberMenuId].lastDive.Date,
-																				pSettings->scrubberData[scrubberMenuId].lastDive.Month,
-																				pSettings->scrubberData[scrubberMenuId].lastDive.Year);
+        snprintf(&text[0], 32, "%c%c\002       %02d.%02d.%02d", TXT_2BYTE, TXT2BYTE_SimDiveTime, pSettings->scrubberData[scrubberMenuId].lastDive.Date, pSettings->scrubberData[scrubberMenuId].lastDive.Month, pSettings->scrubberData[scrubberMenuId].lastDive.Year);
     }
     else
     {
@@ -265,16 +263,17 @@
     }
 	write_label_var(  20, 780, ME_Y_LINE5, &FontT48, text);
 
-   	switch(pSettings->scrubTimerMode)
-    	{
-    		case SCRUB_TIMER_OFF:
-    		default: 	snprintf(&text[0], 32,"%c\002%c%c",TXT_ScrubTimeMode, TXT_2BYTE, TXT2BYTE_MoCtrlNone );
-    			break;
-    		case SCRUB_TIMER_MINUTES: snprintf(&text[0], 32,"%c\002%c",TXT_ScrubTimeMode, TXT_Minutes );
-    			break;
-    		case SCRUB_TIMER_PERCENT: snprintf(&text[0], 32,"%c\002%c",TXT_ScrubTimeMode, TXT_Percent );
-    			break;
-    	}
+    switch (pSettings->scrubTimerMode) {
+        case SCRUB_TIMER_MINUTES:
+        default:
+            snprintf(&text[0], 32,"%c\002%c",TXT_ScrubTimeMode, TXT_Minutes );
+
+            break;
+        case SCRUB_TIMER_PERCENT:
+            snprintf(&text[0], 32,"%c\002%c",TXT_ScrubTimeMode, TXT_Percent );
+
+            break;
+    }
     write_field_button(StMXTRA_ScrubTimer_OP_Mode,	 20, 780, ME_Y_LINE6,  &FontT48, text);
 
     setEvent(StMXTRA_ScrubTimer, (uint32_t)OnAction_ScrubberTimerId);
@@ -478,7 +477,6 @@
 	SSettings *pSettings;
     pSettings = settingsGetPointer();
 
-
     if(scrubberMenuId == 0)
     {
     	scrubberMenuId = 1;
@@ -498,9 +496,7 @@
 
     if(pSettings->scrubberData[scrubberMenuId].lastDive.WeekDay != 0)
     {
-    	snprintf(&text[0], 32,"%c%c\002   %02d.%02d.%02d", TXT_2BYTE, TXT2BYTE_SimDiveTime, 	pSettings->scrubberData[scrubberMenuId].lastDive.Date,
-																				pSettings->scrubberData[scrubberMenuId].lastDive.Month,
-																				pSettings->scrubberData[scrubberMenuId].lastDive.Year);
+        snprintf(&text[0], 32, "%c%c\002   %02d.%02d.%02d", TXT_2BYTE, TXT2BYTE_SimDiveTime, pSettings->scrubberData[scrubberMenuId].lastDive.Date, pSettings->scrubberData[scrubberMenuId].lastDive.Month, pSettings->scrubberData[scrubberMenuId].lastDive.Year);
     }
     else
     {
@@ -509,7 +505,7 @@
     clean_content(  20, 780, ME_Y_LINE5, &FontT48);
 	write_label_var(  20, 780, ME_Y_LINE5, &FontT48, text);
 
-    if(pSettings->scubberActiveId & (1 << scrubberMenuId))
+    if(pSettings->scrubberActiveId & (1 << scrubberMenuId))
     {
     	snprintf(&text[0], 32,"%c %c \002\005", TXT_ScrubTime, TXT_Active);
     }
@@ -589,22 +585,22 @@
 	SSettings *pSettings;
     pSettings = settingsGetPointer();
     newMode = pSettings->scrubTimerMode + 1;
-    if(newMode >= SCRUB_TIMER_END)
-    {
-   	 newMode = SCRUB_TIMER_OFF;
+    if (newMode >= SCRUB_TIMER_END) {
+        newMode = SCRUB_TIMER_MINUTES;
     }
     pSettings->scrubTimerMode = newMode;
 
-   	switch(pSettings->scrubTimerMode)
-    	{
-    		case SCRUB_TIMER_OFF:
-    		default: 	snprintf(&text[0], 32,"%c\002%c%c",TXT_ScrubTimeMode, TXT_2BYTE, TXT2BYTE_MoCtrlNone );
-    			break;
-    		case SCRUB_TIMER_MINUTES: snprintf(&text[0], 32,"%c\002%c",TXT_ScrubTimeMode, TXT_Minutes );
-    			break;
-    		case SCRUB_TIMER_PERCENT: snprintf(&text[0], 32,"%c\002%c",TXT_ScrubTimeMode, TXT_Percent );
-    			break;
-    	}
+    switch (pSettings->scrubTimerMode) {
+        case SCRUB_TIMER_MINUTES:
+        default:
+            snprintf(&text[0], 32,"%c\002%c",TXT_ScrubTimeMode, TXT_Minutes );
+
+            break;
+        case SCRUB_TIMER_PERCENT:
+            snprintf(&text[0], 32,"%c\002%c",TXT_ScrubTimeMode, TXT_Percent );
+
+            break;
+    }
     tMenuEdit_newButtonText(StMXTRA_ScrubTimer_OP_Mode, text);
 
     return UNSPECIFIC_RETURN;
@@ -616,14 +612,14 @@
 	SSettings *pSettings;
     pSettings = settingsGetPointer();
 
-    if(pSettings->scubberActiveId & (1 << scrubberMenuId))
+    if(pSettings->scrubberActiveId & (1 << scrubberMenuId))
     {
-    	pSettings->scubberActiveId &= ~(1 << scrubberMenuId);
+    	pSettings->scrubberActiveId &= ~(1 << scrubberMenuId);
     	snprintf(&text[0], 32,"%c %c \002\006", TXT_ScrubTime, TXT_Active);
     }
     else
     {
-       	pSettings->scubberActiveId |= (1 << scrubberMenuId);
+       	pSettings->scrubberActiveId |= (1 << scrubberMenuId);
        	snprintf(&text[0], 32,"%c %c \002\005", TXT_ScrubTime, TXT_Active);
     }