From d516c820416a5a61f2149429d2f1c8180ac9166f Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Thu, 5 Feb 2026 16:32:15 +0100 Subject: [PATCH] New clock page with extended functions --- lib/obp60task/PageClock.cpp | 834 ++++++++++++++++++++--------- lib/obp60task/PageClock.old | 467 ++++++++++++++++ lib/obp60task/PageClock2.new | 548 +++++++++++++++++++ lib/obp60task/PageClock3.new | 777 +++++++++++++++++++++++++++ lib/obp60task/PageClockDigital.new | 224 ++++++++ 5 files changed, 2591 insertions(+), 259 deletions(-) create mode 100644 lib/obp60task/PageClock.old create mode 100644 lib/obp60task/PageClock2.new create mode 100644 lib/obp60task/PageClock3.new create mode 100644 lib/obp60task/PageClockDigital.new diff --git a/lib/obp60task/PageClock.cpp b/lib/obp60task/PageClock.cpp index 7c7b74f..3cf097a 100644 --- a/lib/obp60task/PageClock.cpp +++ b/lib/obp60task/PageClock.cpp @@ -4,95 +4,264 @@ #include "OBP60Extensions.h" /* - * TODO mode: race timer: keys - * - prepare: set countdown to 5min - * reset: abort current countdown and start over with 5min preparation - * - 5min: key press - * - 4min: key press to sync - * - 1min: buzzer signal - * - start: buzzer signal for start + * PageClock: Clock page with + * - Analog mode (mode == 'A') + * - Digital mode (mode == 'D') + * - Countdown timer mode (mode == 'T') + * - Keys in mode analog and digital clock: + * K1: MODE (A/D/T) + * K2: POS (select field: HH / MM / SS) + * K3: + * K4: + * K5: TZ (Local/UTC) * + * Timer mode: + * - Format HH:MM:SS (24h, leading zeros) + * - Keys in timer mode: + * K1: MODE (A/D/T) + * K2: POS (select field: HH / MM / SS) + * K3: + (increment selected field) + * K4: - (decrement selected field) + * K5: RUN (start/stop countdown) + * - Selection marker: line under active field (width 2px, not wider than digits) + * - Editing only possible when timer is not running + * - When page is left, running timer continues in background using RTC time + * (on re-entry, remaining time is recalculated from RTC) */ class PageClock : public Page { -bool simulation = false; -int simtime; -bool keylock = false; + bool simulation = false; + int simtime; + bool keylock = false; #ifdef BOARD_OBP60S3 -char source = 'G'; // time source (R)TC | (G)PS | (N)TP + char source = 'G'; // time source (R)TC | (G)PS | (N)TP #endif #ifdef BOARD_OBP40S3 -char source = 'R'; // time source (R)TC | (G)PS | (N)TP + char source = 'R'; // time source (R)TC | (G)PS | (N)TP #endif -char mode = 'A'; // display mode (A)nalog | (D)igital | race (T)imer -char tz = 'L'; // time zone (L)ocal | (U)TC -double timezone = 0; // there are timezones with non int offsets, e.g. 5.5 or 5.75 -double homelat; -double homelon; -bool homevalid = false; // homelat and homelon are valid + char mode = 'A'; // display mode (A)nalog | (D)igital | race (T)imer + char tz = 'L'; // time zone (L)ocal | (U)TC + double timezone = 0; // there are timezones with non int offsets, e.g. 5.5 or 5.75 + double homelat; + double homelon; + bool homevalid = false; // homelat and homelon are valid - public: - PageClock(CommonData &common){ + // Timer state (static so it survives page switches) + static bool timerInitialized; + static bool timerRunning; + static int timerHours; + static int timerMinutes; + static int timerSeconds; + static int selectedField; // 0 = hours, 1 = minutes, 2 = seconds + static bool showSelectionMarker; + static time_t timerEndEpoch; // absolute end time based on RTC + + void setupTimerDefaults() + { + if (!timerInitialized) { + timerInitialized = true; + timerRunning = false; + timerHours = 0; + timerMinutes = 0; + timerSeconds = 0; + selectedField = 0; + showSelectionMarker = true; + timerEndEpoch = 0; + } + } + + static int clamp(int value, int minVal, int maxVal) + { + if (value < minVal) return minVal; + if (value > maxVal) return maxVal; + return value; + } + + void incrementSelected() + { + if (selectedField == 0) { + timerHours = clamp(timerHours + 1, 0, 23); + } else if (selectedField == 1) { + timerMinutes = clamp(timerMinutes + 1, 0, 59); + } else { + timerSeconds = clamp(timerSeconds + 1, 0, 59); + } + } + + void decrementSelected() + { + if (selectedField == 0) { + timerHours = clamp(timerHours - 1, 0, 23); + } else if (selectedField == 1) { + timerMinutes = clamp(timerMinutes - 1, 0, 59); + } else { + timerSeconds = clamp(timerSeconds - 1, 0, 59); + } + } + + int totalTimerSeconds() const + { + return timerHours * 3600 + timerMinutes * 60 + timerSeconds; + } + +public: + PageClock(CommonData& common) + { commonData = &common; - common.logger->logDebug(GwLog::LOG,"Instantiate PageClock"); + common.logger->logDebug(GwLog::LOG, "Instantiate PageClock"); simulation = common.config->getBool(common.config->useSimuData); timezone = common.config->getString(common.config->timeZone).toDouble(); homelat = common.config->getString(common.config->homeLAT).toDouble(); homelon = common.config->getString(common.config->homeLON).toDouble(); homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0; simtime = 38160; // time value 11:36 + setupTimerDefaults(); } - virtual void setupKeys(){ + virtual void setupKeys() + { Page::setupKeys(); - commonData->keydata[0].label = "SRC"; - commonData->keydata[1].label = "MODE"; - commonData->keydata[4].label = "TZ"; + + if (mode == 'T') { + // Timer mode: MODE, POS, +, -, RUN + commonData->keydata[0].label = "MODE"; + commonData->keydata[1].label = "POS"; + commonData->keydata[2].label = "+"; + commonData->keydata[3].label = "-"; + commonData->keydata[4].label = "RUN"; + } else { + // Clock modes: like original + commonData->keydata[0].label = "SRC"; + commonData->keydata[1].label = "MODE"; + commonData->keydata[4].label = "TZ"; + } } // Key functions - virtual int handleKey(int key){ - // Time source - if (key == 1) { - switch (source) { - case 'G': source = 'R'; break; - case 'R': source = 'G'; break; - default: source = 'G'; break; - } - return 0; + virtual int handleKey(int key) + { + setupTimerDefaults(); + + // Keylock function + if (key == 11) { // Code for keylock + keylock = !keylock; // Toggle keylock + return 0; // Commit the key } - if (key == 2) { - switch (mode) { + + if (mode == 'T') { + // Timer mode key handling + + // MODE (K1): cycle display mode A/D/T + if (key == 1) { + switch (mode) { case 'A': mode = 'D'; break; case 'D': mode = 'T'; break; case 'T': mode = 'A'; break; default: mode = 'A'; break; + } + setupKeys(); + return 0; } - return 0; + + // POS (K2): select field HH / MM / SS (only if timer not running) + if (key == 2 && !timerRunning) { + selectedField = (selectedField + 1) % 3; + showSelectionMarker = true; + return 0; + } + + // + (K3): increment selected field (only if timer not running) + if (key == 3 && !timerRunning) { + incrementSelected(); + return 0; + } + + // - (K4): decrement selected field (only if timer not running) + if (key == 4 && !timerRunning) { + decrementSelected(); + return 0; + } + + // RUN (K5): start/stop timer + if (key == 5) { + if (!timerRunning) { + // Start timer if a non-zero duration is set + int total = totalTimerSeconds(); + if (total > 0 && commonData->data.rtcValid) { + struct tm rtcCopy = commonData->data.rtcTime; + time_t nowEpoch = mktime(&rtcCopy); + timerEndEpoch = nowEpoch + total; + timerRunning = true; + showSelectionMarker = false; + } + } else { + // Stop timer: compute remaining time and keep as new setting + if (commonData->data.rtcValid) { + struct tm rtcCopy = commonData->data.rtcTime; + time_t nowEpoch = mktime(&rtcCopy); + time_t remaining = timerEndEpoch - nowEpoch; + if (remaining < 0) remaining = 0; + int rem = static_cast(remaining); + timerHours = rem / 3600; + rem -= timerHours * 3600; + timerMinutes = rem / 60; + timerSeconds = rem % 60; + } + timerRunning = false; + // marker will become visible again only after POS press + } + return 0; + } + + // In timer mode, other keys are passed through + return key; } - // Time zone: Local / UTC - if (key == 5) { - switch (tz) { - case 'L': tz = 'U'; break; - case 'U': tz = 'L'; break; - default: tz = 'L'; break; + + // Clock (A/D) modes key handling – like original PageClock + + // Time source (K1) + if (key == 1) { + switch (source) { + case 'G': source = 'R'; break; + case 'R': source = 'G'; break; + default: source = 'G'; break; } return 0; } - // Keylock function - if(key == 11){ // Code for keylock - keylock = !keylock; // Toggle keylock - return 0; // Commit the key + // MODE (K2) + if (key == 2) { + switch (mode) { + case 'A': mode = 'D'; break; + case 'D': mode = 'T'; break; + case 'T': mode = 'A'; break; + default: mode = 'A'; break; + } + setupKeys(); + return 0; } + + // Time zone: Local / UTC (K5) + if (key == 5) { + switch (tz) { + case 'L': tz = 'U'; break; + case 'U': tz = 'L'; break; + default: tz = 'L'; break; + } + return 0; + } + return key; } - int displayPage(PageData &pageData) + int displayPage(PageData& pageData) { - GwConfigHandler *config = commonData->config; - GwLog *logger = commonData->logger; + GwConfigHandler* config = commonData->config; + GwLog* logger = commonData->logger; + + setupTimerDefaults(); + setupKeys(); // ensure correct key labels for current mode static String svalue1old = ""; static String unit1old = ""; @@ -116,58 +285,57 @@ bool homevalid = false; // homelat and homelon are valid String backlightMode = config->getString(config->backlight); // Get boat values for GPS time - GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) + GwApi::BoatValue* bvalue1 = pageData.values[0]; // First element in list String name1 = bvalue1->getName().c_str(); // Value name name1 = name1.substring(0, 6); // String length limit for value name - if(simulation == false){ + if (simulation == false) { value1 = bvalue1->value; // Value as double in SI unit - } - else{ + } else { value1 = simtime++; // Simulation data for time value 11:36 in seconds } // Other simulation data see OBP60Formatter.cpp - bool valid1 = bvalue1->valid; // Valid information - String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + bool valid1 = bvalue1->valid; // Valid information + String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value - if(valid1 == true){ + if (valid1 == true) { svalue1old = svalue1; // Save old value unit1old = unit1; // Save old unit } // Get boat values for GPS date - GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue) + GwApi::BoatValue* bvalue2 = pageData.values[1]; // Second element in list String name2 = bvalue2->getName().c_str(); // Value name name2 = name2.substring(0, 6); // String length limit for value name value2 = bvalue2->value; // Value as double in SI unit - bool valid2 = bvalue2->valid; // Valid information - String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + bool valid2 = bvalue2->valid; // Valid information + String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value - if(valid2 == true){ + if (valid2 == true) { svalue2old = svalue2; // Save old value unit2old = unit2; // Save old unit } - // Get boat values for HDOP date - GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list (only one value by PageOneValue) + // Get boat values for HDOP + GwApi::BoatValue* bvalue3 = pageData.values[2]; // Third element in list String name3 = bvalue3->getName().c_str(); // Value name name3 = name3.substring(0, 6); // String length limit for value name value3 = bvalue3->value; // Value as double in SI unit - bool valid3 = bvalue3->valid; // Valid information - String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + bool valid3 = bvalue3->valid; // Valid information + String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value - if(valid3 == true){ + if (valid3 == true) { svalue3old = svalue3; // Save old value unit3old = unit3; // Save old unit } // Optical warning by limit violation (unused) - if(String(flashLED) == "Limit Violation"){ + if (String(flashLED) == "Limit Violation") { setBlinkingLED(false); setFlashLED(false); } // Logging boat values - if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement? - LOG_DEBUG(GwLog::LOG,"Drawing at PageClock, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2); + if (bvalue1 == NULL) return PAGE_OK; + LOG_DEBUG(GwLog::LOG, "Drawing at PageClock, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2); // Draw page //*********************************************************** @@ -178,23 +346,22 @@ bool homevalid = false; // homelat and homelon are valid getdisplay().setTextColor(commonData->fgcolor); time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600; - struct tm *local_tm = localtime(&tv); + struct tm* local_tm = localtime(&tv); // Show values GPS date getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setCursor(10, 65); if (holdvalues == false) { if (source == 'G') { - // GPS value - getdisplay().print(svalue2); + // GPS value + getdisplay().print(svalue2); } else if (commonData->data.rtcValid) { // RTC value - if (tz == 'L') { - getdisplay().print(formatDate(dateformat, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday)); - } - else { - getdisplay().print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday)); - } + if (tz == 'L') { + getdisplay().print(formatDate(dateformat, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday)); + } else { + getdisplay().print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday)); + } } else { getdisplay().print("---"); } @@ -205,29 +372,26 @@ bool homevalid = false; // homelat and homelon are valid getdisplay().setCursor(10, 95); getdisplay().print("Date"); // Name - // Horizintal separator left + // Horizontal separator left getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor); - // Show values GPS time + // Show values GPS time (small text bottom left) getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setCursor(10, 250); if (holdvalues == false) { if (source == 'G') { getdisplay().print(svalue1); // Value - } - else if (commonData->data.rtcValid) { - if (tz == 'L') { - getdisplay().print(formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec)); - } - else { - getdisplay().print(formatTime('s', commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec)); - } + } else if (commonData->data.rtcValid) { + if (tz == 'L') { + getdisplay().print(formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec)); + } else { + getdisplay().print(formatTime('s', commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec)); + } } else { getdisplay().print("---"); } - } - else { - getdisplay().print(svalue1old); + } else { + getdisplay().print(svalue1old); } getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(10, 220); @@ -244,19 +408,19 @@ bool homevalid = false; // homelat and homelon are valid getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setCursor(335, 65); - if(holdvalues == false) getdisplay().print(sunrise); // Value + if (holdvalues == false) getdisplay().print(sunrise); // Value else getdisplay().print(svalue5old); getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(335, 95); getdisplay().print("SunR"); // Name - // Horizintal separator right + // Horizontal separator right getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor); // Show values sunset String sunset = "---"; if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) { - sunset = String(commonData->sundata.sunsetHour) + ":" + String(commonData->sundata.sunsetMinute + 100).substring(1); + sunset = String(commonData->sundata.sunsetHour) + ":" + String(commonData->sundata.sunsetMinute + 100).substring(1); svalue6old = sunset; } else if (simulation) { sunset = String("21:03"); @@ -264,204 +428,356 @@ bool homevalid = false; // homelat and homelon are valid getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setCursor(335, 250); - if(holdvalues == false) getdisplay().print(sunset); // Value + if (holdvalues == false) getdisplay().print(sunset); // Value else getdisplay().print(svalue6old); getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(335, 220); getdisplay().print("SunS"); // Name -//******************************************************************************************* + //******************************************************************************************* - // Draw clock - int rInstrument = 110; // Radius of clock - float pi = 3.141592; + if (mode == 'T') { + // TIMER MODE: countdown timer HH:MM:SS in the center with 7-segment font - getdisplay().fillCircle(200, 150, rInstrument + 10, commonData->fgcolor); // Outer circle - getdisplay().fillCircle(200, 150, rInstrument + 7, commonData->bgcolor); // Outer circle + int dispH = timerHours; + int dispM = timerMinutes; + int dispS = timerSeconds; - for(int i=0; i<360; i=i+1) - { - // Scaling values - float x = 200 + (rInstrument-30)*sin(i/180.0*pi); // x-coordinate dots - float y = 150 - (rInstrument-30)*cos(i/180.0*pi); // y-coordinate cots - const char *ii = ""; - switch (i) - { - case 0: ii="12"; break; - case 30 : ii=""; break; - case 60 : ii=""; break; - case 90 : ii="3"; break; - case 120 : ii=""; break; - case 150 : ii=""; break; - case 180 : ii="6"; break; - case 210 : ii=""; break; - case 240 : ii=""; break; - case 270 : ii="9"; break; - case 300 : ii=""; break; - case 330 : ii=""; break; - default: break; + // Update remaining time if timer is running (based on RTC) + if (timerRunning && commonData->data.rtcValid) { + struct tm rtcCopy = commonData->data.rtcTime; + time_t nowEpoch = mktime(&rtcCopy); + time_t remaining = timerEndEpoch - nowEpoch; + if (remaining <= 0) { + remaining = 0; + timerRunning = false; + } + int rem = static_cast(remaining); + dispH = rem / 3600; + rem -= dispH * 3600; + dispM = rem / 60; + dispS = rem % 60; } - // Print text centered on position x, y - int16_t x1, y1; // Return values of getTextBounds - uint16_t w, h; // Return values of getTextBounds - getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string - getdisplay().setCursor(x-w/2, y+h/2); - if(i % 30 == 0){ - getdisplay().setFont(&Ubuntu_Bold12pt8b); - getdisplay().print(ii); + char buf[9]; // "HH:MM:SS" + snprintf(buf, sizeof(buf), "%02d:%02d:%02d", dispH, dispM, dispS); + String timeStr = String(buf); + + // Clear central area and draw large digital time + getdisplay().fillRect(0, 110, getdisplay().width(), 80, commonData->bgcolor); + + getdisplay().setFont(&DSEG7Classic_BoldItalic60pt7b); + + // Determine widths for digits and colon to position selection underline exactly + int16_t x0, y0; + uint16_t wDigit, hDigit; + uint16_t wColon, hColon; + + getdisplay().getTextBounds("00", 0, 0, &x0, &y0, &wDigit, &hDigit); + getdisplay().getTextBounds(":", 0, 0, &x0, &y0, &wColon, &hColon); + + uint16_t totalWidth = 3 * wDigit + 2 * wColon; + + int16_t baseX = (static_cast(getdisplay().width()) - static_cast(totalWidth)) / 2; + int16_t centerY = 150; + + // Draw time string centered + int16_t x1b, y1b; + uint16_t wb, hb; + getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb); + int16_t textX = (static_cast(getdisplay().width()) - static_cast(wb)) / 2; + int16_t textY = centerY + hb / 2; + + getdisplay().setCursor(textX, textY); + getdisplay().print(timeStr); + + // Selection marker (only visible when not running and POS pressed) + if (!timerRunning && showSelectionMarker) { + int16_t selX = baseX; + if (selectedField == 1) { + selX = baseX + wDigit + wColon; // minutes start + } else if (selectedField == 2) { + selX = baseX + 2 * wDigit + 2 * wColon; // seconds start + } + + int16_t underlineY = centerY + hb / 2 + 2; + getdisplay().fillRect(selX, underlineY, wDigit, 2, commonData->fgcolor); } - // Draw sub scale with dots - float sinx = 0; - float cosx = 0; - if(i % 6 == 0){ - float x1c = 200 + rInstrument*sin(i/180.0*pi); - float y1c = 150 - rInstrument*cos(i/180.0*pi); - getdisplay().fillCircle((int)x1c, (int)y1c, 2, commonData->fgcolor); - sinx=sin(i/180.0*pi); - cosx=cos(i/180.0*pi); - } - - // Draw sub scale with lines (two triangles) - if(i % 30 == 0){ - float dx=2; // Line thickness = 2*dx+1 - float xx1 = -dx; - float xx2 = +dx; - float yy1 = -(rInstrument-10); - float yy2 = -(rInstrument+10); - getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1), - 200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1), - 200+(int)(cosx*xx1-sinx*yy2),150+(int)(sinx*xx1+cosx*yy2),commonData->fgcolor); - getdisplay().fillTriangle(200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1), - 200+(int)(cosx*xx1-sinx*yy2),150+(int)(sinx*xx1+cosx*yy2), - 200+(int)(cosx*xx2-sinx*yy2),150+(int)(sinx*xx2+cosx*yy2),commonData->fgcolor); - } - } - - // Print Unit in clock - getdisplay().setFont(&Ubuntu_Bold12pt8b); - getdisplay().setCursor(175, 110); - if(holdvalues == false){ - getdisplay().print(tz == 'L' ? "LOT" : "UTC"); - } - else{ - getdisplay().print(unit2old); // date unit - } - - getdisplay().setFont(&Ubuntu_Bold8pt8b); - getdisplay().setCursor(185, 190); - if (source == 'G') { - getdisplay().print("GPS"); - } else { - getdisplay().print("RTC"); - } - - // Clock values - double hour = 0; - double minute = 0; - if (source == 'R') { - if (tz == 'L') { - time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600; - struct tm *local_tm = localtime(&tv); - minute = local_tm->tm_min; - hour = local_tm->tm_hour; + // Small indicators: timezone and source + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(180, 110); + if (holdvalues == false) { + getdisplay().print(tz == 'L' ? "LOT" : "UTC"); } else { - minute = commonData->data.rtcTime.tm_min; - hour = commonData->data.rtcTime.tm_hour; + getdisplay().print(unit2old); // date unit } - hour += minute / 60; + + getdisplay().setCursor(185, 190); + if (source == 'G') { + getdisplay().print("GPS"); + } else { + getdisplay().print("RTC"); + } + + } else if (mode == 'D') { + // DIGITAL CLOCK MODE: large 7-segment time based on GPS/RTC + + int hour24 = 0; + int minute24 = 0; + int second24 = 0; + + if (source == 'R' && commonData->data.rtcValid) { + time_t tv2 = mktime(&commonData->data.rtcTime); + if (tz == 'L') { + tv2 += static_cast(timezone * 3600); + } + struct tm* tm2 = localtime(&tv2); + hour24 = tm2->tm_hour; + minute24 = tm2->tm_min; + second24 = tm2->tm_sec; + } else { + double t = value1; + if (tz == 'L') { + t += timezone * 3600; + } + if (t >= 86400) t -= 86400; + if (t < 0) t += 86400; + hour24 = static_cast(t / 3600.0); + int rest = static_cast(t) - hour24 * 3600; + minute24 = rest / 60; + second24 = rest % 60; + } + + char buf[9]; // "HH:MM:SS" + snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour24, minute24, second24); + String timeStr = String(buf); + + getdisplay().fillRect(0, 110, getdisplay().width(), 80, commonData->bgcolor); + + getdisplay().setFont(&DSEG7Classic_BoldItalic60pt7b); + + int16_t x1b, y1b; + uint16_t wb, hb; + getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb); + + int16_t x = (static_cast(getdisplay().width()) - static_cast(wb)) / 2; + int16_t y = 150 + hb / 2; + + getdisplay().setCursor(x, y); + getdisplay().print(timeStr); + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(180, 110); + if (holdvalues == false) { + getdisplay().print(tz == 'L' ? "LOT" : "UTC"); + } else { + getdisplay().print(unit2old); // date unit + } + + getdisplay().setCursor(185, 190); + if (source == 'G') { + getdisplay().print("GPS"); + } else { + getdisplay().print("RTC"); + } + } else { - if (tz == 'L') { - value1 += timezone * 3600; + // ANALOG CLOCK MODE (mode == 'A') + + int rInstrument = 110; // Radius of clock + float pi = 3.141592; + + getdisplay().fillCircle(200, 150, rInstrument + 10, commonData->fgcolor); // Outer circle + getdisplay().fillCircle(200, 150, rInstrument + 7, commonData->bgcolor); // Outer circle + + for (int i = 0; i < 360; i = i + 1) + { + // Scaling values + float x = 200 + (rInstrument - 30) * sin(i / 180.0 * pi); // x-coordinate dots + float y = 150 - (rInstrument - 30) * cos(i / 180.0 * pi); // y-coordinate dots + const char* ii = ""; + switch (i) + { + case 0: ii = "12"; break; + case 90: ii = "3"; break; + case 180: ii = "6"; break; + case 270: ii = "9"; break; + default: break; + } + + // Print text centered on position x, y + int16_t x1c, y1c; // Return values of getTextBounds + uint16_t wc, hc; // Return values of getTextBounds + getdisplay().getTextBounds(ii, int(x), int(y), &x1c, &y1c, &wc, &hc); // Calc width of new string + getdisplay().setCursor(x - wc / 2, y + hc / 2); + if (i % 90 == 0) { + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().print(ii); + } + + // Draw sub scale with dots + float sinx = 0; + float cosx = 0; + if (i % 6 == 0) { + float x1d = 200 + rInstrument * sin(i / 180.0 * pi); + float y1d = 150 - rInstrument * cos(i / 180.0 * pi); + getdisplay().fillCircle((int)x1d, (int)y1d, 2, commonData->fgcolor); + sinx = sin(i / 180.0 * pi); + cosx = cos(i / 180.0 * pi); + } + + // Draw sub scale with lines (two triangles) + if (i % 30 == 0) { + float dx = 2; // Line thickness = 2*dx+1 + float xx1 = -dx; + float xx2 = +dx; + float yy1 = -(rInstrument - 10); + float yy2 = -(rInstrument + 10); + getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1), + 200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1), + 200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2), commonData->fgcolor); + getdisplay().fillTriangle(200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1), + 200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2), + 200 + (int)(cosx * xx2 - sinx * yy2), 150 + (int)(sinx * xx2 + cosx * yy2), commonData->fgcolor); + } } - if (value1 > 86400) {value1 -= 86400;} - if (value1 < 0) {value1 += 86400;} - hour = (value1 / 3600.0); - // minute = (hour - int(hour)) * 3600.0 / 60.0; // Analog minute pointer smooth moving - minute = int((hour - int(hour)) * 3600.0 / 60.0); // Jumping minute pointer from minute to minute - } - if (hour > 12) { - hour -= 12.0; - } - LOG_DEBUG(GwLog::DEBUG,"... PageClock, value1: %f hour: %f minute:%f", value1, hour, minute); - // Draw hour pointer - float startwidth = 8; // Start width of pointer - if(valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true){ - float sinx=sin(hour * 30.0 * pi / 180); // Hour - float cosx=cos(hour * 30.0 * pi / 180); - // Normal pointer - // Pointer as triangle with center base 2*width - float xx1 = -startwidth; - float xx2 = startwidth; - float yy1 = -startwidth; - float yy2 = -(rInstrument * 0.5); - getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1), - 200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1), - 200+(int)(cosx*0-sinx*yy2),150+(int)(sinx*0+cosx*yy2),commonData->fgcolor); - // Inverted pointer - // Pointer as triangle with center base 2*width - float endwidth = 2; // End width of pointer - float ix1 = endwidth; - float ix2 = -endwidth; - float iy1 = -(rInstrument * 0.5); - float iy2 = -endwidth; - getdisplay().fillTriangle(200+(int)(cosx*ix1-sinx*iy1),150+(int)(sinx*ix1+cosx*iy1), - 200+(int)(cosx*ix2-sinx*iy1),150+(int)(sinx*ix2+cosx*iy1), - 200+(int)(cosx*0-sinx*iy2),150+(int)(sinx*0+cosx*iy2),commonData->fgcolor); - } + // Print Unit in clock + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(175, 110); + if (holdvalues == false) { + getdisplay().print(tz == 'L' ? "LOT" : "UTC"); + } else { + getdisplay().print(unit2old); // date unit + } - // Draw minute pointer - startwidth = 8; // Start width of pointer - if(valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true){ - float sinx=sin(minute * 6.0 * pi / 180); // Minute - float cosx=cos(minute * 6.0 * pi / 180); - // Normal pointer - // Pointer as triangle with center base 2*width - float xx1 = -startwidth; - float xx2 = startwidth; - float yy1 = -startwidth; - float yy2 = -(rInstrument - 15); - getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1), - 200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1), - 200+(int)(cosx*0-sinx*yy2),150+(int)(sinx*0+cosx*yy2),commonData->fgcolor); - // Inverted pointer - // Pointer as triangle with center base 2*width - float endwidth = 2; // End width of pointer - float ix1 = endwidth; - float ix2 = -endwidth; - float iy1 = -(rInstrument - 15); - float iy2 = -endwidth; - getdisplay().fillTriangle(200+(int)(cosx*ix1-sinx*iy1),150+(int)(sinx*ix1+cosx*iy1), - 200+(int)(cosx*ix2-sinx*iy1),150+(int)(sinx*ix2+cosx*iy1), - 200+(int)(cosx*0-sinx*iy2),150+(int)(sinx*0+cosx*iy2),commonData->fgcolor); - } + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(185, 190); + if (source == 'G') { + getdisplay().print("GPS"); + } else { + getdisplay().print("RTC"); + } - // Center circle - getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor); - getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor); + // Clock values + double hour = 0; + double minute = 0; + if (source == 'R') { + if (tz == 'L') { + time_t tv2 = mktime(&commonData->data.rtcTime) + timezone * 3600; + struct tm* local_tm2 = localtime(&tv2); + minute = local_tm2->tm_min; + hour = local_tm2->tm_hour; + } else { + minute = commonData->data.rtcTime.tm_min; + hour = commonData->data.rtcTime.tm_hour; + } + hour += minute / 60; + } else { + if (tz == 'L') { + value1 += timezone * 3600; + } + if (value1 > 86400) { value1 -= 86400; } + if (value1 < 0) { value1 += 86400; } + hour = (value1 / 3600.0); + // minute = (hour - int(hour)) * 3600.0 / 60.0; // Analog minute pointer smooth moving + minute = int((hour - int(hour)) * 3600.0 / 60.0); // Jumping minute pointer from minute to minute + } + if (hour > 12) { + hour -= 12.0; + } + LOG_DEBUG(GwLog::DEBUG, "... PageClock, value1: %f hour: %f minute:%f", value1, hour, minute); + + // Draw hour pointer + float startwidth = 8; // Start width of pointer + if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) { + float sinx = sin(hour * 30.0 * pi / 180); // Hour + float cosx = cos(hour * 30.0 * pi / 180); + // Normal pointer + // Pointer as triangle with center base 2*width + float xx1 = -startwidth; + float xx2 = startwidth; + float yy1 = -startwidth; + float yy2 = -(rInstrument * 0.5); + getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1), + 200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1), + 200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor); + // Inverted pointer + // Pointer as triangle with center base 2*width + float endwidth = 2; // End width of pointer + float ix1 = endwidth; + float ix2 = -endwidth; + float iy1 = -(rInstrument * 0.5); + float iy2 = -endwidth; + getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1), + 200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1), + 200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor); + } + + // Draw minute pointer + startwidth = 8; // Start width of pointer + if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) { + float sinx = sin(minute * 6.0 * pi / 180); // Minute + float cosx = cos(minute * 6.0 * pi / 180); + // Normal pointer + // Pointer as triangle with center base 2*width + float xx1 = -startwidth; + float xx2 = startwidth; + float yy1 = -startwidth; + float yy2 = -(rInstrument - 15); + getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1), + 200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1), + 200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor); + // Inverted pointer + // Pointer as triangle with center base 2*width + float endwidth = 2; // End width of pointer + float ix1 = endwidth; + float ix2 = -endwidth; + float iy1 = -(rInstrument - 15); + float iy2 = -endwidth; + getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1), + 200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1), + 200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor); + } + + // Center circle + getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor); + getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor); + } return PAGE_UPDATE; }; }; -static Page *createPage(CommonData &common){ +// Static member definitions +bool PageClock::timerInitialized = false; +bool PageClock::timerRunning = false; +int PageClock::timerHours = 0; +int PageClock::timerMinutes = 0; +int PageClock::timerSeconds = 0; +int PageClock::selectedField = 0; +bool PageClock::showSelectionMarker = true; +time_t PageClock::timerEndEpoch = 0; + +static Page* createPage(CommonData& common) +{ return new PageClock(common); } /** * with the code below we make this page known to the PageTask * we give it a type (name) that can be selected in the config * we define which function is to be called - * and we provide the number of user parameters we expect (0 here) - * and will will provide the names of the fixed values we need + * we provide the number of user parameters we expect (0 here) + * and we provide the names of the fixed values we need */ PageDescription registerPageClock( - "Clock", // Page name - createPage, // Action - 0, // Number of bus values depends on selection in Web configuration + "Clock", // Page name + createPage, // Action + 0, // Number of bus values depends on selection in Web configuration {"GPST", "GPSD", "HDOP"}, // Bus values we need in the page - true // Show display header on/off + true // Show display header on/off ); #endif + diff --git a/lib/obp60task/PageClock.old b/lib/obp60task/PageClock.old new file mode 100644 index 0000000..7c7b74f --- /dev/null +++ b/lib/obp60task/PageClock.old @@ -0,0 +1,467 @@ +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 + +#include "Pagedata.h" +#include "OBP60Extensions.h" + +/* + * TODO mode: race timer: keys + * - prepare: set countdown to 5min + * reset: abort current countdown and start over with 5min preparation + * - 5min: key press + * - 4min: key press to sync + * - 1min: buzzer signal + * - start: buzzer signal for start + * + */ + +class PageClock : public Page +{ +bool simulation = false; +int simtime; +bool keylock = false; +#ifdef BOARD_OBP60S3 +char source = 'G'; // time source (R)TC | (G)PS | (N)TP +#endif +#ifdef BOARD_OBP40S3 +char source = 'R'; // time source (R)TC | (G)PS | (N)TP +#endif +char mode = 'A'; // display mode (A)nalog | (D)igital | race (T)imer +char tz = 'L'; // time zone (L)ocal | (U)TC +double timezone = 0; // there are timezones with non int offsets, e.g. 5.5 or 5.75 +double homelat; +double homelon; +bool homevalid = false; // homelat and homelon are valid + + public: + PageClock(CommonData &common){ + commonData = &common; + common.logger->logDebug(GwLog::LOG,"Instantiate PageClock"); + simulation = common.config->getBool(common.config->useSimuData); + timezone = common.config->getString(common.config->timeZone).toDouble(); + homelat = common.config->getString(common.config->homeLAT).toDouble(); + homelon = common.config->getString(common.config->homeLON).toDouble(); + homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0; + simtime = 38160; // time value 11:36 + } + + virtual void setupKeys(){ + Page::setupKeys(); + commonData->keydata[0].label = "SRC"; + commonData->keydata[1].label = "MODE"; + commonData->keydata[4].label = "TZ"; + } + + // Key functions + virtual int handleKey(int key){ + // Time source + if (key == 1) { + switch (source) { + case 'G': source = 'R'; break; + case 'R': source = 'G'; break; + default: source = 'G'; break; + } + return 0; + } + if (key == 2) { + switch (mode) { + case 'A': mode = 'D'; break; + case 'D': mode = 'T'; break; + case 'T': mode = 'A'; break; + default: mode = 'A'; break; + } + return 0; + } + // Time zone: Local / UTC + if (key == 5) { + switch (tz) { + case 'L': tz = 'U'; break; + case 'U': tz = 'L'; break; + default: tz = 'L'; break; + } + return 0; + } + + // Keylock function + if(key == 11){ // Code for keylock + keylock = !keylock; // Toggle keylock + return 0; // Commit the key + } + return key; + } + + int displayPage(PageData &pageData) + { + GwConfigHandler *config = commonData->config; + GwLog *logger = commonData->logger; + + static String svalue1old = ""; + static String unit1old = ""; + static String svalue2old = ""; + static String unit2old = ""; + static String svalue3old = ""; + static String unit3old = ""; + + static String svalue5old = ""; + static String svalue6old = ""; + + double value1 = 0; + double value2 = 0; + double value3 = 0; + + // Get config data + String lengthformat = config->getString(config->lengthFormat); + String dateformat = config->getString(config->dateFormat); + bool holdvalues = config->getBool(config->holdvalues); + String flashLED = config->getString(config->flashLED); + String backlightMode = config->getString(config->backlight); + + // Get boat values for GPS time + GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) + String name1 = bvalue1->getName().c_str(); // Value name + name1 = name1.substring(0, 6); // String length limit for value name + if(simulation == false){ + value1 = bvalue1->value; // Value as double in SI unit + } + else{ + value1 = simtime++; // Simulation data for time value 11:36 in seconds + } // Other simulation data see OBP60Formatter.cpp + bool valid1 = bvalue1->valid; // Valid information + String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value + if(valid1 == true){ + svalue1old = svalue1; // Save old value + unit1old = unit1; // Save old unit + } + + // Get boat values for GPS date + GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue) + String name2 = bvalue2->getName().c_str(); // Value name + name2 = name2.substring(0, 6); // String length limit for value name + value2 = bvalue2->value; // Value as double in SI unit + bool valid2 = bvalue2->valid; // Valid information + String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value + if(valid2 == true){ + svalue2old = svalue2; // Save old value + unit2old = unit2; // Save old unit + } + + // Get boat values for HDOP date + GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list (only one value by PageOneValue) + String name3 = bvalue3->getName().c_str(); // Value name + name3 = name3.substring(0, 6); // String length limit for value name + value3 = bvalue3->value; // Value as double in SI unit + bool valid3 = bvalue3->valid; // Valid information + String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value + if(valid3 == true){ + svalue3old = svalue3; // Save old value + unit3old = unit3; // Save old unit + } + + // Optical warning by limit violation (unused) + if(String(flashLED) == "Limit Violation"){ + setBlinkingLED(false); + setFlashLED(false); + } + + // Logging boat values + if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement? + LOG_DEBUG(GwLog::LOG,"Drawing at PageClock, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2); + + // Draw page + //*********************************************************** + + // Set display in partial refresh mode + getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + + getdisplay().setTextColor(commonData->fgcolor); + + time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600; + struct tm *local_tm = localtime(&tv); + + // Show values GPS date + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(10, 65); + if (holdvalues == false) { + if (source == 'G') { + // GPS value + getdisplay().print(svalue2); + } else if (commonData->data.rtcValid) { + // RTC value + if (tz == 'L') { + getdisplay().print(formatDate(dateformat, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday)); + } + else { + getdisplay().print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday)); + } + } else { + getdisplay().print("---"); + } + } else { + getdisplay().print(svalue2old); + } + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(10, 95); + getdisplay().print("Date"); // Name + + // Horizintal separator left + getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor); + + // Show values GPS time + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(10, 250); + if (holdvalues == false) { + if (source == 'G') { + getdisplay().print(svalue1); // Value + } + else if (commonData->data.rtcValid) { + if (tz == 'L') { + getdisplay().print(formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec)); + } + else { + getdisplay().print(formatTime('s', commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec)); + } + } else { + getdisplay().print("---"); + } + } + else { + getdisplay().print(svalue1old); + } + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(10, 220); + getdisplay().print("Time"); // Name + + // Show values sunrise + String sunrise = "---"; + if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) { + sunrise = String(commonData->sundata.sunriseHour) + ":" + String(commonData->sundata.sunriseMinute + 100).substring(1); + svalue5old = sunrise; + } else if (simulation) { + sunrise = String("06:42"); + } + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(335, 65); + if(holdvalues == false) getdisplay().print(sunrise); // Value + else getdisplay().print(svalue5old); + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(335, 95); + getdisplay().print("SunR"); // Name + + // Horizintal separator right + getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor); + + // Show values sunset + String sunset = "---"; + if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) { + sunset = String(commonData->sundata.sunsetHour) + ":" + String(commonData->sundata.sunsetMinute + 100).substring(1); + svalue6old = sunset; + } else if (simulation) { + sunset = String("21:03"); + } + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(335, 250); + if(holdvalues == false) getdisplay().print(sunset); // Value + else getdisplay().print(svalue6old); + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(335, 220); + getdisplay().print("SunS"); // Name + +//******************************************************************************************* + + // Draw clock + int rInstrument = 110; // Radius of clock + float pi = 3.141592; + + getdisplay().fillCircle(200, 150, rInstrument + 10, commonData->fgcolor); // Outer circle + getdisplay().fillCircle(200, 150, rInstrument + 7, commonData->bgcolor); // Outer circle + + for(int i=0; i<360; i=i+1) + { + // Scaling values + float x = 200 + (rInstrument-30)*sin(i/180.0*pi); // x-coordinate dots + float y = 150 - (rInstrument-30)*cos(i/180.0*pi); // y-coordinate cots + const char *ii = ""; + switch (i) + { + case 0: ii="12"; break; + case 30 : ii=""; break; + case 60 : ii=""; break; + case 90 : ii="3"; break; + case 120 : ii=""; break; + case 150 : ii=""; break; + case 180 : ii="6"; break; + case 210 : ii=""; break; + case 240 : ii=""; break; + case 270 : ii="9"; break; + case 300 : ii=""; break; + case 330 : ii=""; break; + default: break; + } + + // Print text centered on position x, y + int16_t x1, y1; // Return values of getTextBounds + uint16_t w, h; // Return values of getTextBounds + getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string + getdisplay().setCursor(x-w/2, y+h/2); + if(i % 30 == 0){ + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().print(ii); + } + + // Draw sub scale with dots + float sinx = 0; + float cosx = 0; + if(i % 6 == 0){ + float x1c = 200 + rInstrument*sin(i/180.0*pi); + float y1c = 150 - rInstrument*cos(i/180.0*pi); + getdisplay().fillCircle((int)x1c, (int)y1c, 2, commonData->fgcolor); + sinx=sin(i/180.0*pi); + cosx=cos(i/180.0*pi); + } + + // Draw sub scale with lines (two triangles) + if(i % 30 == 0){ + float dx=2; // Line thickness = 2*dx+1 + float xx1 = -dx; + float xx2 = +dx; + float yy1 = -(rInstrument-10); + float yy2 = -(rInstrument+10); + getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1), + 200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1), + 200+(int)(cosx*xx1-sinx*yy2),150+(int)(sinx*xx1+cosx*yy2),commonData->fgcolor); + getdisplay().fillTriangle(200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1), + 200+(int)(cosx*xx1-sinx*yy2),150+(int)(sinx*xx1+cosx*yy2), + 200+(int)(cosx*xx2-sinx*yy2),150+(int)(sinx*xx2+cosx*yy2),commonData->fgcolor); + } + } + + // Print Unit in clock + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(175, 110); + if(holdvalues == false){ + getdisplay().print(tz == 'L' ? "LOT" : "UTC"); + } + else{ + getdisplay().print(unit2old); // date unit + } + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(185, 190); + if (source == 'G') { + getdisplay().print("GPS"); + } else { + getdisplay().print("RTC"); + } + + // Clock values + double hour = 0; + double minute = 0; + if (source == 'R') { + if (tz == 'L') { + time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600; + struct tm *local_tm = localtime(&tv); + minute = local_tm->tm_min; + hour = local_tm->tm_hour; + } else { + minute = commonData->data.rtcTime.tm_min; + hour = commonData->data.rtcTime.tm_hour; + } + hour += minute / 60; + } else { + if (tz == 'L') { + value1 += timezone * 3600; + } + if (value1 > 86400) {value1 -= 86400;} + if (value1 < 0) {value1 += 86400;} + hour = (value1 / 3600.0); + // minute = (hour - int(hour)) * 3600.0 / 60.0; // Analog minute pointer smooth moving + minute = int((hour - int(hour)) * 3600.0 / 60.0); // Jumping minute pointer from minute to minute + } + if (hour > 12) { + hour -= 12.0; + } + LOG_DEBUG(GwLog::DEBUG,"... PageClock, value1: %f hour: %f minute:%f", value1, hour, minute); + + // Draw hour pointer + float startwidth = 8; // Start width of pointer + if(valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true){ + float sinx=sin(hour * 30.0 * pi / 180); // Hour + float cosx=cos(hour * 30.0 * pi / 180); + // Normal pointer + // Pointer as triangle with center base 2*width + float xx1 = -startwidth; + float xx2 = startwidth; + float yy1 = -startwidth; + float yy2 = -(rInstrument * 0.5); + getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1), + 200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1), + 200+(int)(cosx*0-sinx*yy2),150+(int)(sinx*0+cosx*yy2),commonData->fgcolor); + // Inverted pointer + // Pointer as triangle with center base 2*width + float endwidth = 2; // End width of pointer + float ix1 = endwidth; + float ix2 = -endwidth; + float iy1 = -(rInstrument * 0.5); + float iy2 = -endwidth; + getdisplay().fillTriangle(200+(int)(cosx*ix1-sinx*iy1),150+(int)(sinx*ix1+cosx*iy1), + 200+(int)(cosx*ix2-sinx*iy1),150+(int)(sinx*ix2+cosx*iy1), + 200+(int)(cosx*0-sinx*iy2),150+(int)(sinx*0+cosx*iy2),commonData->fgcolor); + } + + // Draw minute pointer + startwidth = 8; // Start width of pointer + if(valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true){ + float sinx=sin(minute * 6.0 * pi / 180); // Minute + float cosx=cos(minute * 6.0 * pi / 180); + // Normal pointer + // Pointer as triangle with center base 2*width + float xx1 = -startwidth; + float xx2 = startwidth; + float yy1 = -startwidth; + float yy2 = -(rInstrument - 15); + getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1), + 200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1), + 200+(int)(cosx*0-sinx*yy2),150+(int)(sinx*0+cosx*yy2),commonData->fgcolor); + // Inverted pointer + // Pointer as triangle with center base 2*width + float endwidth = 2; // End width of pointer + float ix1 = endwidth; + float ix2 = -endwidth; + float iy1 = -(rInstrument - 15); + float iy2 = -endwidth; + getdisplay().fillTriangle(200+(int)(cosx*ix1-sinx*iy1),150+(int)(sinx*ix1+cosx*iy1), + 200+(int)(cosx*ix2-sinx*iy1),150+(int)(sinx*ix2+cosx*iy1), + 200+(int)(cosx*0-sinx*iy2),150+(int)(sinx*0+cosx*iy2),commonData->fgcolor); + } + + // Center circle + getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor); + getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor); + + return PAGE_UPDATE; + }; +}; + +static Page *createPage(CommonData &common){ + return new PageClock(common); +} +/** + * with the code below we make this page known to the PageTask + * we give it a type (name) that can be selected in the config + * we define which function is to be called + * and we provide the number of user parameters we expect (0 here) + * and will will provide the names of the fixed values we need + */ +PageDescription registerPageClock( + "Clock", // Page name + createPage, // Action + 0, // Number of bus values depends on selection in Web configuration + {"GPST", "GPSD", "HDOP"}, // Bus values we need in the page + true // Show display header on/off +); + +#endif diff --git a/lib/obp60task/PageClock2.new b/lib/obp60task/PageClock2.new new file mode 100644 index 0000000..3e0af87 --- /dev/null +++ b/lib/obp60task/PageClock2.new @@ -0,0 +1,548 @@ +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 + +#include "Pagedata.h" +#include "OBP60Extensions.h" + +/* + * Variant of PageClock with switchable analog / digital clock display. + * mode: (A)nalog | (D)igital | race (T)imer (T not implemented yet, falls back to analog) + */ + +class PageClock2 : public Page +{ + bool simulation = false; + int simtime; + bool keylock = false; +#ifdef BOARD_OBP60S3 + char source = 'G'; // time source (R)TC | (G)PS | (N)TP +#endif +#ifdef BOARD_OBP40S3 + char source = 'R'; // time source (R)TC | (G)PS | (N)TP +#endif + char mode = 'A'; // display mode (A)nalog | (D)igital | race (T)imer + char tz = 'L'; // time zone (L)ocal | (U)TC + double timezone = 0; // there are timezones with non int offsets, e.g. 5.5 or 5.75 + double homelat; + double homelon; + bool homevalid = false; // homelat and homelon are valid + +public: + PageClock2(CommonData& common) + { + commonData = &common; + common.logger->logDebug(GwLog::LOG, "Instantiate PageClock2"); + simulation = common.config->getBool(common.config->useSimuData); + timezone = common.config->getString(common.config->timeZone).toDouble(); + homelat = common.config->getString(common.config->homeLAT).toDouble(); + homelon = common.config->getString(common.config->homeLON).toDouble(); + homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0; + simtime = 38160; // time value 11:36 + } + + virtual void setupKeys() + { + Page::setupKeys(); + commonData->keydata[0].label = "SRC"; + commonData->keydata[1].label = "MODE"; + commonData->keydata[4].label = "TZ"; + } + + // Key functions + virtual int handleKey(int key) + { + // Time source + if (key == 1) { + switch (source) { + case 'G': + source = 'R'; + break; + case 'R': + source = 'G'; + break; + default: + source = 'G'; + break; + } + return 0; + } + if (key == 2) { + switch (mode) { + case 'A': + mode = 'D'; + break; + case 'D': + mode = 'T'; + break; + case 'T': + mode = 'A'; + break; + default: + mode = 'A'; + break; + } + return 0; + } + // Time zone: Local / UTC + if (key == 5) { + switch (tz) { + case 'L': + tz = 'U'; + break; + case 'U': + tz = 'L'; + break; + default: + tz = 'L'; + break; + } + return 0; + } + + // Keylock function + if (key == 11) { // Code for keylock + keylock = !keylock; // Toggle keylock + return 0; // Commit the key + } + return key; + } + + int displayPage(PageData& pageData) + { + GwConfigHandler* config = commonData->config; + + static String svalue1old = ""; + static String unit1old = ""; + static String svalue2old = ""; + static String unit2old = ""; + static String svalue3old = ""; + static String unit3old = ""; + + static String svalue5old = ""; + static String svalue6old = ""; + + double value1 = 0; + double value2 = 0; + double value3 = 0; + + // Get config data + String lengthformat = config->getString(config->lengthFormat); + String dateformat = config->getString(config->dateFormat); + bool holdvalues = config->getBool(config->holdvalues); + String flashLED = config->getString(config->flashLED); + String backlightMode = config->getString(config->backlight); + + // Get boat values for GPS time + GwApi::BoatValue* bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) + String name1 = bvalue1->getName().c_str(); // Value name + name1 = name1.substring(0, 6); // String length limit for value name + if (simulation == false) { + value1 = bvalue1->value; // Value as double in SI unit + } else { + value1 = simtime++; // Simulation data for time value 11:36 in seconds + } // Other simulation data see OBP60Formatter.cpp + bool valid1 = bvalue1->valid; // Valid information + String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value + if (valid1 == true) { + svalue1old = svalue1; // Save old value + unit1old = unit1; // Save old unit + } + + // Get boat values for GPS date + GwApi::BoatValue* bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue) + String name2 = bvalue2->getName().c_str(); // Value name + name2 = name2.substring(0, 6); // String length limit for value name + value2 = bvalue2->value; // Value as double in SI unit + bool valid2 = bvalue2->valid; // Valid information + String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value + if (valid2 == true) { + svalue2old = svalue2; // Save old value + unit2old = unit2; // Save old unit + } + + // Get boat values for HDOP date + GwApi::BoatValue* bvalue3 = pageData.values[2]; // Third element in list (only one value by PageOneValue) + String name3 = bvalue3->getName().c_str(); // Value name + name3 = name3.substring(0, 6); // String length limit for value name + value3 = bvalue3->value; // Value as double in SI unit + bool valid3 = bvalue3->valid; // Valid information + String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value + if (valid3 == true) { + svalue3old = svalue3; // Save old value + unit3old = unit3; // Save old unit + } + + // Optical warning by limit violation (unused) + if (String(flashLED) == "Limit Violation") { + setBlinkingLED(false); + setFlashLED(false); + } + + // Logging boat values + if (bvalue1 == NULL) + return PAGE_OK; // WTF why this statement? + LOG_DEBUG(GwLog::LOG, "Drawing at PageClock2, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2); + + // Draw page + //*********************************************************** + + // Set display in partial refresh mode + getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + + getdisplay().setTextColor(commonData->fgcolor); + + time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600; + struct tm* local_tm = localtime(&tv); + + // Show values GPS date + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(10, 65); + if (holdvalues == false) { + if (source == 'G') { + // GPS value + getdisplay().print(svalue2); + } else if (commonData->data.rtcValid) { + // RTC value + if (tz == 'L') { + getdisplay().print(formatDate(dateformat, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday)); + } else { + getdisplay().print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday)); + } + } else { + getdisplay().print("---"); + } + } else { + getdisplay().print(svalue2old); + } + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(10, 95); + getdisplay().print("Date"); // Name + + // Horizintal separator left + getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor); + + // Show values GPS time (small text bottom left, same as original) + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(10, 250); + if (holdvalues == false) { + if (source == 'G') { + getdisplay().print(svalue1); // Value + } else if (commonData->data.rtcValid) { + if (tz == 'L') { + getdisplay().print(formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec)); + } else { + getdisplay().print(formatTime('s', commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec)); + } + } else { + getdisplay().print("---"); + } + } else { + getdisplay().print(svalue1old); + } + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(10, 220); + getdisplay().print("Time"); // Name + + // Show values sunrise + String sunrise = "---"; + if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) { + sunrise = String(commonData->sundata.sunriseHour) + ":" + String(commonData->sundata.sunriseMinute + 100).substring(1); + svalue5old = sunrise; + } else if (simulation) { + sunrise = String("06:42"); + } + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(335, 65); + if (holdvalues == false) + getdisplay().print(sunrise); // Value + else + getdisplay().print(svalue5old); + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(335, 95); + getdisplay().print("SunR"); // Name + + // Horizintal separator right + getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor); + + // Show values sunset + String sunset = "---"; + if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) { + sunset = String(commonData->sundata.sunsetHour) + ":" + String(commonData->sundata.sunsetMinute + 100).substring(1); + svalue6old = sunset; + } else if (simulation) { + sunset = String("21:03"); + } + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(335, 250); + if (holdvalues == false) + getdisplay().print(sunset); // Value + else + getdisplay().print(svalue6old); + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(335, 220); + getdisplay().print("SunS"); // Name + + //******************************************************************************************* + + if (mode == 'D') { + // Digital clock mode: large 7-segment time centered on display + + // Determine current time in hours/minutes/seconds (24h) + int hour24 = 0; + int minute24 = 0; + int second24 = 0; + + if (source == 'R' && commonData->data.rtcValid) { + // RTC based + time_t tv2 = mktime(&commonData->data.rtcTime); + if (tz == 'L') { + tv2 += static_cast(timezone * 3600); + } + struct tm* tm2 = localtime(&tv2); + hour24 = tm2->tm_hour; + minute24 = tm2->tm_min; + second24 = tm2->tm_sec; + } else { + // GPS / simulation based + double t = value1; + if (tz == 'L') { + t += timezone * 3600; + } + if (t >= 86400) + t -= 86400; + if (t < 0) + t += 86400; + hour24 = static_cast(t / 3600.0); + int rest = static_cast(t) - hour24 * 3600; + minute24 = rest / 60; + second24 = rest % 60; + } + + char buf[9]; // "HH:MM:SS" + '\0' + snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour24, minute24, second24); + String timeStr = String(buf); + + // Clear central area and draw large digital time + getdisplay().fillRect(0, 110, getdisplay().width(), 80, commonData->bgcolor); + + getdisplay().setFont(&DSEG7Classic_BoldItalic60pt7b); + + int16_t x1b, y1b; + uint16_t wb, hb; + getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb); + + int16_t x = (static_cast(getdisplay().width()) - static_cast(wb)) / 2; + int16_t y = 150 + hb / 2; // vertically around center (y=150) + + getdisplay().setCursor(x, y); + getdisplay().print(timeStr); + + // Small indicators inside the digital area + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(180, 110); + if (holdvalues == false) { + getdisplay().print(tz == 'L' ? "LOT" : "UTC"); + } else { + getdisplay().print(unit2old); // date unit + } + + getdisplay().setCursor(185, 190); + if (source == 'G') { + getdisplay().print("GPS"); + } else { + getdisplay().print("RTC"); + } + + } else { + // Analog clock mode (A/T) - original drawing + + int rInstrument = 110; // Radius of clock + float pi = 3.141592; + + getdisplay().fillCircle(200, 150, rInstrument + 10, commonData->fgcolor); // Outer circle + getdisplay().fillCircle(200, 150, rInstrument + 7, commonData->bgcolor); // Outer circle + + for (int i = 0; i < 360; i = i + 1) + { + // Scaling values + float x = 200 + (rInstrument - 30) * sin(i / 180.0 * pi); // x-coordinate dots + float y = 150 - (rInstrument - 30) * cos(i / 180.0 * pi); // y-coordinate dots + const char* ii = ""; + switch (i) + { + case 0: ii = "12"; break; + case 90: ii = "3"; break; + case 180: ii = "6"; break; + case 270: ii = "9"; break; + default: break; + } + + // Print text centered on position x, y + int16_t x1c, y1c; // Return values of getTextBounds + uint16_t wc, hc; // Return values of getTextBounds + getdisplay().getTextBounds(ii, int(x), int(y), &x1c, &y1c, &wc, &hc); // Calc width of new string + getdisplay().setCursor(x - wc / 2, y + hc / 2); + if (i % 90 == 0) { + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().print(ii); + } + + // Draw sub scale with dots + float sinx = 0; + float cosx = 0; + if (i % 6 == 0) { + float x1d = 200 + rInstrument * sin(i / 180.0 * pi); + float y1d = 150 - rInstrument * cos(i / 180.0 * pi); + getdisplay().fillCircle((int)x1d, (int)y1d, 2, commonData->fgcolor); + sinx = sin(i / 180.0 * pi); + cosx = cos(i / 180.0 * pi); + } + + // Draw sub scale with lines (two triangles) + if (i % 30 == 0) { + float dx = 2; // Line thickness = 2*dx+1 + float xx1 = -dx; + float xx2 = +dx; + float yy1 = -(rInstrument - 10); + float yy2 = -(rInstrument + 10); + getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1), + 200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1), + 200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2), commonData->fgcolor); + getdisplay().fillTriangle(200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1), + 200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2), + 200 + (int)(cosx * xx2 - sinx * yy2), 150 + (int)(sinx * xx2 + cosx * yy2), commonData->fgcolor); + } + } + + // Print Unit in clock + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(175, 110); + if (holdvalues == false) { + getdisplay().print(tz == 'L' ? "LOT" : "UTC"); + } else { + getdisplay().print(unit2old); // date unit + } + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(185, 190); + if (source == 'G') { + getdisplay().print("GPS"); + } else { + getdisplay().print("RTC"); + } + + // Clock values + double hour = 0; + double minute = 0; + if (source == 'R') { + if (tz == 'L') { + time_t tv2 = mktime(&commonData->data.rtcTime) + timezone * 3600; + struct tm* local_tm2 = localtime(&tv2); + minute = local_tm2->tm_min; + hour = local_tm2->tm_hour; + } else { + minute = commonData->data.rtcTime.tm_min; + hour = commonData->data.rtcTime.tm_hour; + } + hour += minute / 60; + } else { + if (tz == 'L') { + value1 += timezone * 3600; + } + if (value1 > 86400) { value1 -= 86400; } + if (value1 < 0) { value1 += 86400; } + hour = (value1 / 3600.0); + // minute = (hour - int(hour)) * 3600.0 / 60.0; // Analog minute pointer smooth moving + minute = int((hour - int(hour)) * 3600.0 / 60.0); // Jumping minute pointer from minute to minute + } + if (hour > 12) { + hour -= 12.0; + } + LOG_DEBUG(GwLog::DEBUG, "... PageClock2, value1: %f hour: %f minute:%f", value1, hour, minute); + + // Draw hour pointer + float startwidth = 8; // Start width of pointer + if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) { + float sinx = sin(hour * 30.0 * pi / 180); // Hour + float cosx = cos(hour * 30.0 * pi / 180); + // Normal pointer + // Pointer as triangle with center base 2*width + float xx1 = -startwidth; + float xx2 = startwidth; + float yy1 = -startwidth; + float yy2 = -(rInstrument * 0.5); + getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1), + 200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1), + 200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor); + // Inverted pointer + // Pointer as triangle with center base 2*width + float endwidth = 2; // End width of pointer + float ix1 = endwidth; + float ix2 = -endwidth; + float iy1 = -(rInstrument * 0.5); + float iy2 = -endwidth; + getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1), + 200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1), + 200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor); + } + + // Draw minute pointer + startwidth = 8; // Start width of pointer + if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) { + float sinx = sin(minute * 6.0 * pi / 180); // Minute + float cosx = cos(minute * 6.0 * pi / 180); + // Normal pointer + // Pointer as triangle with center base 2*width + float xx1 = -startwidth; + float xx2 = startwidth; + float yy1 = -startwidth; + float yy2 = -(rInstrument - 15); + getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1), + 200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1), + 200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor); + // Inverted pointer + // Pointer as triangle with center base 2*width + float endwidth = 2; // End width of pointer + float ix1 = endwidth; + float ix2 = -endwidth; + float iy1 = -(rInstrument - 15); + float iy2 = -endwidth; + getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1), + 200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1), + 200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor); + } + + // Center circle + getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor); + getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor); + } + + return PAGE_UPDATE; + }; +}; + +static Page* createPage(CommonData& common) +{ + return new PageClock2(common); +} +/** + * with the code below we make this page known to the PageTask + * we give it a type (name) that can be selected in the config + * we define which function is to be called + * we provide the number of user parameters we expect (0 here) + * and we provide the names of the fixed values we need + */ +PageDescription registerPageClock2( + "Clock2", // Page name + createPage, // Action + 0, // Number of bus values depends on selection in Web configuration + {"GPST", "GPSD", "HDOP"}, // Bus values we need in the page + true // Show display header on/off +); + +#endif + diff --git a/lib/obp60task/PageClock3.new b/lib/obp60task/PageClock3.new new file mode 100644 index 0000000..2814c8b --- /dev/null +++ b/lib/obp60task/PageClock3.new @@ -0,0 +1,777 @@ +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 + +#include "Pagedata.h" +#include "OBP60Extensions.h" + +/* + * PageClock3: Clock page with + * - Analog mode (mode == 'A') + * - Digital mode (mode == 'D') + * - Countdown timer mode (mode == 'T') + * + * Timer mode: + * - Format HH:MM:SS (24h, leading zeros) + * - Keys in timer mode: + * K1: MODE (A/D/T) + * K2: POS (select field: HH / MM / SS) + * K3: + (increment selected field) + * K4: - (decrement selected field) + * K5: RUN (start/stop countdown) + * - Selection marker: line under active field (width 2px, not wider than digits) + * - Editing only possible when timer is not running + * - When page is left, running timer continues in background using RTC time + * (on re-entry, remaining time is recalculated from RTC) + */ + +class PageClock3 : public Page +{ + bool simulation = false; + int simtime; + bool keylock = false; +#ifdef BOARD_OBP60S3 + char source = 'G'; // time source (R)TC | (G)PS | (N)TP +#endif +#ifdef BOARD_OBP40S3 + char source = 'R'; // time source (R)TC | (G)PS | (N)TP +#endif + char mode = 'A'; // display mode (A)nalog | (D)igital | race (T)imer + char tz = 'L'; // time zone (L)ocal | (U)TC + double timezone = 0; // there are timezones with non int offsets, e.g. 5.5 or 5.75 + double homelat; + double homelon; + bool homevalid = false; // homelat and homelon are valid + + // Timer state (static so it survives page switches) + static bool timerInitialized; + static bool timerRunning; + static int timerHours; + static int timerMinutes; + static int timerSeconds; + static int selectedField; // 0 = hours, 1 = minutes, 2 = seconds + static bool showSelectionMarker; + static time_t timerEndEpoch; // absolute end time based on RTC + + void setupTimerDefaults() + { + if (!timerInitialized) { + timerInitialized = true; + timerRunning = false; + timerHours = 0; + timerMinutes = 0; + timerSeconds = 0; + selectedField = 0; + showSelectionMarker = true; + timerEndEpoch = 0; + } + } + + static int clamp(int value, int minVal, int maxVal) + { + if (value < minVal) return minVal; + if (value > maxVal) return maxVal; + return value; + } + + void incrementSelected() + { + if (selectedField == 0) { + timerHours = clamp(timerHours + 1, 0, 23); + } else if (selectedField == 1) { + timerMinutes = clamp(timerMinutes + 1, 0, 59); + } else { + timerSeconds = clamp(timerSeconds + 1, 0, 59); + } + } + + void decrementSelected() + { + if (selectedField == 0) { + timerHours = clamp(timerHours - 1, 0, 23); + } else if (selectedField == 1) { + timerMinutes = clamp(timerMinutes - 1, 0, 59); + } else { + timerSeconds = clamp(timerSeconds - 1, 0, 59); + } + } + + int totalTimerSeconds() const + { + return timerHours * 3600 + timerMinutes * 60 + timerSeconds; + } + +public: + PageClock3(CommonData& common) + { + commonData = &common; + common.logger->logDebug(GwLog::LOG, "Instantiate PageClock3"); + simulation = common.config->getBool(common.config->useSimuData); + timezone = common.config->getString(common.config->timeZone).toDouble(); + homelat = common.config->getString(common.config->homeLAT).toDouble(); + homelon = common.config->getString(common.config->homeLON).toDouble(); + homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0; + simtime = 38160; // time value 11:36 + setupTimerDefaults(); + } + + virtual void setupKeys() + { + Page::setupKeys(); + + if (mode == 'T') { + // Timer mode: MODE, POS, +, -, RUN + commonData->keydata[0].label = "MODE"; + commonData->keydata[1].label = "POS"; + commonData->keydata[2].label = "+"; + commonData->keydata[3].label = "-"; + commonData->keydata[4].label = "RUN"; + } else { + // Clock modes: like original + commonData->keydata[0].label = "SRC"; + commonData->keydata[1].label = "MODE"; + commonData->keydata[4].label = "TZ"; + } + } + + // Key functions + virtual int handleKey(int key) + { + setupTimerDefaults(); + + // Keylock function + if (key == 11) { // Code for keylock + keylock = !keylock; // Toggle keylock + return 0; // Commit the key + } + + if (mode == 'T') { + // Timer mode key handling + + // MODE (K1): cycle display mode A/D/T + if (key == 1) { + switch (mode) { + case 'A': mode = 'D'; break; + case 'D': mode = 'T'; break; + case 'T': mode = 'A'; break; + default: mode = 'A'; break; + } + setupKeys(); + return 0; + } + + // POS (K2): select field HH / MM / SS (only if timer not running) + if (key == 2 && !timerRunning) { + selectedField = (selectedField + 1) % 3; + showSelectionMarker = true; + return 0; + } + + // + (K3): increment selected field (only if timer not running) + if (key == 3 && !timerRunning) { + incrementSelected(); + return 0; + } + + // - (K4): decrement selected field (only if timer not running) + if (key == 4 && !timerRunning) { + decrementSelected(); + return 0; + } + + // RUN (K5): start/stop timer + if (key == 5) { + if (!timerRunning) { + // Start timer if a non-zero duration is set + int total = totalTimerSeconds(); + if (total > 0 && commonData->data.rtcValid) { + struct tm rtcCopy = commonData->data.rtcTime; + time_t nowEpoch = mktime(&rtcCopy); + timerEndEpoch = nowEpoch + total; + timerRunning = true; + showSelectionMarker = false; + } + } else { + // Stop timer: compute remaining time and keep as new setting + if (commonData->data.rtcValid) { + struct tm rtcCopy = commonData->data.rtcTime; + time_t nowEpoch = mktime(&rtcCopy); + time_t remaining = timerEndEpoch - nowEpoch; + if (remaining < 0) remaining = 0; + int rem = static_cast(remaining); + timerHours = rem / 3600; + rem -= timerHours * 3600; + timerMinutes = rem / 60; + timerSeconds = rem % 60; + } + timerRunning = false; + // marker will become visible again only after POS press + } + return 0; + } + + // In timer mode, other keys are passed through + return key; + } + + // Clock (A/D) modes key handling – like original PageClock + + // Time source (K1) + if (key == 1) { + switch (source) { + case 'G': source = 'R'; break; + case 'R': source = 'G'; break; + default: source = 'G'; break; + } + return 0; + } + + // MODE (K2) + if (key == 2) { + switch (mode) { + case 'A': mode = 'D'; break; + case 'D': mode = 'T'; break; + case 'T': mode = 'A'; break; + default: mode = 'A'; break; + } + setupKeys(); + return 0; + } + + // Time zone: Local / UTC (K5) + if (key == 5) { + switch (tz) { + case 'L': tz = 'U'; break; + case 'U': tz = 'L'; break; + default: tz = 'L'; break; + } + return 0; + } + + return key; + } + + int displayPage(PageData& pageData) + { + GwConfigHandler* config = commonData->config; + GwLog* logger = commonData->logger; + + setupTimerDefaults(); + setupKeys(); // ensure correct key labels for current mode + + static String svalue1old = ""; + static String unit1old = ""; + static String svalue2old = ""; + static String unit2old = ""; + static String svalue3old = ""; + static String unit3old = ""; + + static String svalue5old = ""; + static String svalue6old = ""; + + double value1 = 0; + double value2 = 0; + double value3 = 0; + + // Get config data + String lengthformat = config->getString(config->lengthFormat); + String dateformat = config->getString(config->dateFormat); + bool holdvalues = config->getBool(config->holdvalues); + String flashLED = config->getString(config->flashLED); + String backlightMode = config->getString(config->backlight); + + // Get boat values for GPS time + GwApi::BoatValue* bvalue1 = pageData.values[0]; // First element in list + String name1 = bvalue1->getName().c_str(); // Value name + name1 = name1.substring(0, 6); // String length limit for value name + if (simulation == false) { + value1 = bvalue1->value; // Value as double in SI unit + } else { + value1 = simtime++; // Simulation data for time value 11:36 in seconds + } // Other simulation data see OBP60Formatter.cpp + bool valid1 = bvalue1->valid; // Valid information + String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value + String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value + if (valid1 == true) { + svalue1old = svalue1; // Save old value + unit1old = unit1; // Save old unit + } + + // Get boat values for GPS date + GwApi::BoatValue* bvalue2 = pageData.values[1]; // Second element in list + String name2 = bvalue2->getName().c_str(); // Value name + name2 = name2.substring(0, 6); // String length limit for value name + value2 = bvalue2->value; // Value as double in SI unit + bool valid2 = bvalue2->valid; // Valid information + String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value + String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value + if (valid2 == true) { + svalue2old = svalue2; // Save old value + unit2old = unit2; // Save old unit + } + + // Get boat values for HDOP + GwApi::BoatValue* bvalue3 = pageData.values[2]; // Third element in list + String name3 = bvalue3->getName().c_str(); // Value name + name3 = name3.substring(0, 6); // String length limit for value name + value3 = bvalue3->value; // Value as double in SI unit + bool valid3 = bvalue3->valid; // Valid information + String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value + String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value + if (valid3 == true) { + svalue3old = svalue3; // Save old value + unit3old = unit3; // Save old unit + } + + // Optical warning by limit violation (unused) + if (String(flashLED) == "Limit Violation") { + setBlinkingLED(false); + setFlashLED(false); + } + + // Logging boat values + if (bvalue1 == NULL) return PAGE_OK; + LOG_DEBUG(GwLog::LOG, "Drawing at PageClock3, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2); + + // Draw page + //*********************************************************** + + // Set display in partial refresh mode + getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + + getdisplay().setTextColor(commonData->fgcolor); + + time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600; + struct tm* local_tm = localtime(&tv); + + // Show values GPS date + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(10, 65); + if (holdvalues == false) { + if (source == 'G') { + // GPS value + getdisplay().print(svalue2); + } else if (commonData->data.rtcValid) { + // RTC value + if (tz == 'L') { + getdisplay().print(formatDate(dateformat, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday)); + } else { + getdisplay().print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday)); + } + } else { + getdisplay().print("---"); + } + } else { + getdisplay().print(svalue2old); + } + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(10, 95); + getdisplay().print("Date"); // Name + + // Horizontal separator left + getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor); + + // Show values GPS time (small text bottom left) + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(10, 250); + if (holdvalues == false) { + if (source == 'G') { + getdisplay().print(svalue1); // Value + } else if (commonData->data.rtcValid) { + if (tz == 'L') { + getdisplay().print(formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec)); + } else { + getdisplay().print(formatTime('s', commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec)); + } + } else { + getdisplay().print("---"); + } + } else { + getdisplay().print(svalue1old); + } + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(10, 220); + getdisplay().print("Time"); // Name + + // Show values sunrise + String sunrise = "---"; + if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) { + sunrise = String(commonData->sundata.sunriseHour) + ":" + String(commonData->sundata.sunriseMinute + 100).substring(1); + svalue5old = sunrise; + } else if (simulation) { + sunrise = String("06:42"); + } + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(335, 65); + if (holdvalues == false) getdisplay().print(sunrise); // Value + else getdisplay().print(svalue5old); + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(335, 95); + getdisplay().print("SunR"); // Name + + // Horizontal separator right + getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor); + + // Show values sunset + String sunset = "---"; + if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) { + sunset = String(commonData->sundata.sunsetHour) + ":" + String(commonData->sundata.sunsetMinute + 100).substring(1); + svalue6old = sunset; + } else if (simulation) { + sunset = String("21:03"); + } + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(335, 250); + if (holdvalues == false) getdisplay().print(sunset); // Value + else getdisplay().print(svalue6old); + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(335, 220); + getdisplay().print("SunS"); // Name + + //******************************************************************************************* + + if (mode == 'T') { + // TIMER MODE: countdown timer HH:MM:SS in the center with 7-segment font + + int dispH = timerHours; + int dispM = timerMinutes; + int dispS = timerSeconds; + + // Update remaining time if timer is running (based on RTC) + if (timerRunning && commonData->data.rtcValid) { + struct tm rtcCopy = commonData->data.rtcTime; + time_t nowEpoch = mktime(&rtcCopy); + time_t remaining = timerEndEpoch - nowEpoch; + if (remaining <= 0) { + remaining = 0; + timerRunning = false; + } + int rem = static_cast(remaining); + dispH = rem / 3600; + rem -= dispH * 3600; + dispM = rem / 60; + dispS = rem % 60; + } + + char buf[9]; // "HH:MM:SS" + snprintf(buf, sizeof(buf), "%02d:%02d:%02d", dispH, dispM, dispS); + String timeStr = String(buf); + + // Clear central area and draw large digital time + getdisplay().fillRect(0, 110, getdisplay().width(), 80, commonData->bgcolor); + + getdisplay().setFont(&DSEG7Classic_BoldItalic60pt7b); + + // Determine widths for digits and colon to position selection underline exactly + int16_t x0, y0; + uint16_t wDigit, hDigit; + uint16_t wColon, hColon; + + getdisplay().getTextBounds("00", 0, 0, &x0, &y0, &wDigit, &hDigit); + getdisplay().getTextBounds(":", 0, 0, &x0, &y0, &wColon, &hColon); + + uint16_t totalWidth = 3 * wDigit + 2 * wColon; + + int16_t baseX = (static_cast(getdisplay().width()) - static_cast(totalWidth)) / 2; + int16_t centerY = 150; + + // Draw time string centered + int16_t x1b, y1b; + uint16_t wb, hb; + getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb); + int16_t textX = (static_cast(getdisplay().width()) - static_cast(wb)) / 2; + int16_t textY = centerY + hb / 2; + + getdisplay().setCursor(textX, textY); + getdisplay().print(timeStr); + + // Selection marker (only visible when not running and POS pressed) + if (!timerRunning && showSelectionMarker) { + int16_t selX = baseX; + if (selectedField == 1) { + selX = baseX + wDigit + wColon; // minutes start + } else if (selectedField == 2) { + selX = baseX + 2 * wDigit + 2 * wColon; // seconds start + } + + int16_t underlineY = centerY + hb / 2 + 2; + getdisplay().fillRect(selX, underlineY, wDigit, 2, commonData->fgcolor); + } + + // Small indicators: timezone and source + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(180, 110); + if (holdvalues == false) { + getdisplay().print(tz == 'L' ? "LOT" : "UTC"); + } else { + getdisplay().print(unit2old); // date unit + } + + getdisplay().setCursor(185, 190); + if (source == 'G') { + getdisplay().print("GPS"); + } else { + getdisplay().print("RTC"); + } + + } else if (mode == 'D') { + // DIGITAL CLOCK MODE: large 7-segment time based on GPS/RTC + + int hour24 = 0; + int minute24 = 0; + int second24 = 0; + + if (source == 'R' && commonData->data.rtcValid) { + time_t tv2 = mktime(&commonData->data.rtcTime); + if (tz == 'L') { + tv2 += static_cast(timezone * 3600); + } + struct tm* tm2 = localtime(&tv2); + hour24 = tm2->tm_hour; + minute24 = tm2->tm_min; + second24 = tm2->tm_sec; + } else { + double t = value1; + if (tz == 'L') { + t += timezone * 3600; + } + if (t >= 86400) t -= 86400; + if (t < 0) t += 86400; + hour24 = static_cast(t / 3600.0); + int rest = static_cast(t) - hour24 * 3600; + minute24 = rest / 60; + second24 = rest % 60; + } + + char buf[9]; // "HH:MM:SS" + snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour24, minute24, second24); + String timeStr = String(buf); + + getdisplay().fillRect(0, 110, getdisplay().width(), 80, commonData->bgcolor); + + getdisplay().setFont(&DSEG7Classic_BoldItalic60pt7b); + + int16_t x1b, y1b; + uint16_t wb, hb; + getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb); + + int16_t x = (static_cast(getdisplay().width()) - static_cast(wb)) / 2; + int16_t y = 150 + hb / 2; + + getdisplay().setCursor(x, y); + getdisplay().print(timeStr); + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(180, 110); + if (holdvalues == false) { + getdisplay().print(tz == 'L' ? "LOT" : "UTC"); + } else { + getdisplay().print(unit2old); // date unit + } + + getdisplay().setCursor(185, 190); + if (source == 'G') { + getdisplay().print("GPS"); + } else { + getdisplay().print("RTC"); + } + + } else { + // ANALOG CLOCK MODE (mode == 'A') + + int rInstrument = 110; // Radius of clock + float pi = 3.141592; + + getdisplay().fillCircle(200, 150, rInstrument + 10, commonData->fgcolor); // Outer circle + getdisplay().fillCircle(200, 150, rInstrument + 7, commonData->bgcolor); // Outer circle + + for (int i = 0; i < 360; i = i + 1) + { + // Scaling values + float x = 200 + (rInstrument - 30) * sin(i / 180.0 * pi); // x-coordinate dots + float y = 150 - (rInstrument - 30) * cos(i / 180.0 * pi); // y-coordinate dots + const char* ii = ""; + switch (i) + { + case 0: ii = "12"; break; + case 90: ii = "3"; break; + case 180: ii = "6"; break; + case 270: ii = "9"; break; + default: break; + } + + // Print text centered on position x, y + int16_t x1c, y1c; // Return values of getTextBounds + uint16_t wc, hc; // Return values of getTextBounds + getdisplay().getTextBounds(ii, int(x), int(y), &x1c, &y1c, &wc, &hc); // Calc width of new string + getdisplay().setCursor(x - wc / 2, y + hc / 2); + if (i % 90 == 0) { + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().print(ii); + } + + // Draw sub scale with dots + float sinx = 0; + float cosx = 0; + if (i % 6 == 0) { + float x1d = 200 + rInstrument * sin(i / 180.0 * pi); + float y1d = 150 - rInstrument * cos(i / 180.0 * pi); + getdisplay().fillCircle((int)x1d, (int)y1d, 2, commonData->fgcolor); + sinx = sin(i / 180.0 * pi); + cosx = cos(i / 180.0 * pi); + } + + // Draw sub scale with lines (two triangles) + if (i % 30 == 0) { + float dx = 2; // Line thickness = 2*dx+1 + float xx1 = -dx; + float xx2 = +dx; + float yy1 = -(rInstrument - 10); + float yy2 = -(rInstrument + 10); + getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1), + 200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1), + 200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2), commonData->fgcolor); + getdisplay().fillTriangle(200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1), + 200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2), + 200 + (int)(cosx * xx2 - sinx * yy2), 150 + (int)(sinx * xx2 + cosx * yy2), commonData->fgcolor); + } + } + + // Print Unit in clock + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(175, 110); + if (holdvalues == false) { + getdisplay().print(tz == 'L' ? "LOT" : "UTC"); + } else { + getdisplay().print(unit2old); // date unit + } + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(185, 190); + if (source == 'G') { + getdisplay().print("GPS"); + } else { + getdisplay().print("RTC"); + } + + // Clock values + double hour = 0; + double minute = 0; + if (source == 'R') { + if (tz == 'L') { + time_t tv2 = mktime(&commonData->data.rtcTime) + timezone * 3600; + struct tm* local_tm2 = localtime(&tv2); + minute = local_tm2->tm_min; + hour = local_tm2->tm_hour; + } else { + minute = commonData->data.rtcTime.tm_min; + hour = commonData->data.rtcTime.tm_hour; + } + hour += minute / 60; + } else { + if (tz == 'L') { + value1 += timezone * 3600; + } + if (value1 > 86400) { value1 -= 86400; } + if (value1 < 0) { value1 += 86400; } + hour = (value1 / 3600.0); + // minute = (hour - int(hour)) * 3600.0 / 60.0; // Analog minute pointer smooth moving + minute = int((hour - int(hour)) * 3600.0 / 60.0); // Jumping minute pointer from minute to minute + } + if (hour > 12) { + hour -= 12.0; + } + LOG_DEBUG(GwLog::DEBUG, "... PageClock3, value1: %f hour: %f minute:%f", value1, hour, minute); + + // Draw hour pointer + float startwidth = 8; // Start width of pointer + if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) { + float sinx = sin(hour * 30.0 * pi / 180); // Hour + float cosx = cos(hour * 30.0 * pi / 180); + // Normal pointer + // Pointer as triangle with center base 2*width + float xx1 = -startwidth; + float xx2 = startwidth; + float yy1 = -startwidth; + float yy2 = -(rInstrument * 0.5); + getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1), + 200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1), + 200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor); + // Inverted pointer + // Pointer as triangle with center base 2*width + float endwidth = 2; // End width of pointer + float ix1 = endwidth; + float ix2 = -endwidth; + float iy1 = -(rInstrument * 0.5); + float iy2 = -endwidth; + getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1), + 200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1), + 200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor); + } + + // Draw minute pointer + startwidth = 8; // Start width of pointer + if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) { + float sinx = sin(minute * 6.0 * pi / 180); // Minute + float cosx = cos(minute * 6.0 * pi / 180); + // Normal pointer + // Pointer as triangle with center base 2*width + float xx1 = -startwidth; + float xx2 = startwidth; + float yy1 = -startwidth; + float yy2 = -(rInstrument - 15); + getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1), + 200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1), + 200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor); + // Inverted pointer + // Pointer as triangle with center base 2*width + float endwidth = 2; // End width of pointer + float ix1 = endwidth; + float ix2 = -endwidth; + float iy1 = -(rInstrument - 15); + float iy2 = -endwidth; + getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1), + 200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1), + 200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor); + } + + // Center circle + getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor); + getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor); + } + + return PAGE_UPDATE; + }; +}; + +// Static member definitions +bool PageClock3::timerInitialized = false; +bool PageClock3::timerRunning = false; +int PageClock3::timerHours = 0; +int PageClock3::timerMinutes = 0; +int PageClock3::timerSeconds = 0; +int PageClock3::selectedField = 0; +bool PageClock3::showSelectionMarker = true; +time_t PageClock3::timerEndEpoch = 0; + +static Page* createPage(CommonData& common) +{ + return new PageClock3(common); +} +/** + * with the code below we make this page known to the PageTask + * we give it a type (name) that can be selected in the config + * we define which function is to be called + * we provide the number of user parameters we expect (0 here) + * and we provide the names of the fixed values we need + */ +PageDescription registerPageClock3( + "Clock3", // Page name + createPage, // Action + 0, // Number of bus values depends on selection in Web configuration + {"GPST", "GPSD", "HDOP"}, // Bus values we need in the page + true // Show display header on/off +); + +#endif + diff --git a/lib/obp60task/PageClockDigital.new b/lib/obp60task/PageClockDigital.new new file mode 100644 index 0000000..d4a3984 --- /dev/null +++ b/lib/obp60task/PageClockDigital.new @@ -0,0 +1,224 @@ +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 + +#include "Pagedata.h" +#include "OBP60Extensions.h" + +/** + * Simple digital clock page. + * + * - Shows system time as large digital value in the center + * - Uses same data sources and configuration as PageClock (GPS / RTC, time zone) + * - Keys: + * K1: toggle time source (GPS / RTC) + * K5: toggle time zone (Local / UTC) + * K11: keylock + */ +class PageClockDigital : public Page +{ + bool simulation = false; + int simtime = 0; + char source; // time source (R)TC | (G)PS + char tz = 'L'; // time zone (L)ocal | (U)TC + double timezone = 0.0; + +public: + PageClockDigital(CommonData& common) + { + commonData = &common; + common.logger->logDebug(GwLog::LOG, "Instantiate PageClockDigital"); + + simulation = common.config->getBool(common.config->useSimuData); + timezone = common.config->getString(common.config->timeZone).toDouble(); + +#ifdef BOARD_OBP60S3 + source = 'G'; // default to GPS time on OBP60 +#endif +#ifdef BOARD_OBP40S3 + source = 'R'; // default to RTC time on OBP40 +#endif + simtime = 38160; // time value 11:36 for simulation (seconds) + } + + virtual void setupKeys() + { + Page::setupKeys(); + commonData->keydata[0].label = "SRC"; + commonData->keydata[4].label = "TZ"; + } + + // Key functions + virtual int handleKey(int key) + { + // Time source + if (key == 1) { + switch (source) { + case 'G': + source = 'R'; + break; + case 'R': + source = 'G'; + break; + default: + source = 'G'; + break; + } + return 0; + } + + // Time zone: Local / UTC + if (key == 5) { + switch (tz) { + case 'L': + tz = 'U'; + break; + case 'U': + tz = 'L'; + break; + default: + tz = 'L'; + break; + } + return 0; + } + + // Keylock function + if (key == 11) { // Code for keylock + commonData->keylock = !commonData->keylock; + return 0; // Commit the key + } + return key; + } + + int displayPage(PageData& pageData) + { + GwConfigHandler* config = commonData->config; + + static String svalueTimeOld = ""; + static String svalueDateOld = ""; + + // Get config data + bool holdvalues = config->getBool(config->holdvalues); + String flashLED = config->getString(config->flashLED); + + // Get boat values for GPS time and date (same as PageClock) + if (pageData.values.size() < 2) { + return PAGE_OK; + } + + GwApi::BoatValue* bvalueTime = pageData.values[0]; + GwApi::BoatValue* bvalueDate = pageData.values[1]; + + if (bvalueTime == nullptr || bvalueDate == nullptr) { + return PAGE_OK; + } + + double valueTime = 0; + if (!simulation) { + valueTime = bvalueTime->value; // Value as double in SI unit (seconds) + } else { + valueTime = simtime++; // Simulation data + } + bool validTime = bvalueTime->valid; + String svalueTime = formatValue(bvalueTime, *commonData).svalue; // formatted time string + if (validTime) { + svalueTimeOld = svalueTime; // Save old value + } + + bool validDate = bvalueDate->valid; + String svalueDate = formatValue(bvalueDate, *commonData).svalue; // formatted date string + if (validDate) { + svalueDateOld = svalueDate; // Save old value + } + + // Optical warning by limit violation (unused) + if (flashLED == "Limit Violation") { + setBlinkingLED(false); + setFlashLED(false); + } + + // Draw page + //*********************************************************** + + // Set display in partial refresh mode + getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + getdisplay().setTextColor(commonData->fgcolor); + + // Build time string depending on source and configuration + String timeStr = "---"; + + if (!holdvalues) { + if (source == 'G') { + // GPS value as formatted by formatter + timeStr = svalueTime; + } else if (commonData->data.rtcValid) { + // RTC value + time_t tv = mktime(&commonData->data.rtcTime); + if (tz == 'L') { + tv += static_cast(timezone * 3600); + } + struct tm* local_tm = localtime(&tv); + timeStr = formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec); + } + } else { + timeStr = svalueTimeOld; + } + + // Clear central area and draw large digital time + getdisplay().fillRect(0, 80, getdisplay().width(), 140, commonData->bgcolor); + + getdisplay().setFont(&DSEG7Classic_BoldItalic60pt7b); + + int16_t x1, y1; + uint16_t w, h; + getdisplay().getTextBounds(timeStr, 0, 0, &x1, &y1, &w, &h); + + int16_t x = (static_cast(getdisplay().width()) - static_cast(w)) / 2; + int16_t y = (static_cast(getdisplay().height()) + static_cast(h)) / 2; + + getdisplay().setCursor(x, y); + getdisplay().print(timeStr); + + // Show date in the upper left corner + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(10, 40); + getdisplay().print("Date"); + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(10, 60); + if (!holdvalues) { + getdisplay().print(svalueDate); + } else { + getdisplay().print(svalueDateOld); + } + + // Show small labels for source and timezone in the lower right corner + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(getdisplay().width() - 80, getdisplay().height() - 40); + getdisplay().print(source == 'G' ? "GPS" : "RTC"); + + getdisplay().setCursor(getdisplay().width() - 80, getdisplay().height() - 20); + getdisplay().print(tz == 'L' ? "LOC" : "UTC"); + + return PAGE_UPDATE; + }; +}; + +static Page* createPage(CommonData& common) +{ + return new PageClockDigital(common); +} + +/** + * Register page so it can be selected in the configuration. + * Uses the same fixed values as PageClock. + */ +PageDescription registerPageClockDigital( + "ClockDigital", // Page name + createPage, // Action + 0, // Number of user parameters + {"GPST", "GPSD", "HDOP"}, // Bus values we need in the page + true // Show display header on/off +); + +#endif +