diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index c589feb..83e9ed6 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -421,32 +421,49 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa heartbeat = !heartbeat; // Date and time + String fmttype = commonData.config->getString(commonData.config->dateFormat); + String timesource = commonData.config->getString(commonData.config->timeSource); + double tz = commonData.config->getString(commonData.config->timeZone).toDouble(); getdisplay().setTextColor(commonData.fgcolor); getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(230, 15); - // Show date and time if date present - if(date->valid == true){ - String acttime = formatValue(time, commonData).svalue; - acttime = acttime.substring(0, 5); - String actdate = formatValue(date, commonData).svalue; - getdisplay().print(acttime); - getdisplay().print(" "); - getdisplay().print(actdate); - getdisplay().print(" "); - if(commonData.config->getInt(commonData.config->timeZone) == 0){ - getdisplay().print("UTC"); - } - else{ - getdisplay().print("LOT"); + if (timesource == "RTC" or timesource == "iRTC") { + // TODO take DST into account + if (commonData.data.rtcValid) { + time_t tv = mktime(&commonData.data.rtcTime) + (int)(tz * 3600); + struct tm *local_tm = localtime(&tv); + getdisplay().print(formatTime('m', local_tm->tm_hour, local_tm->tm_min, 0)); + getdisplay().print(" "); + getdisplay().print(formatDate(fmttype, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday)); + getdisplay().print(" "); + getdisplay().print(tz == 0 ? "UTC" : "LOT"); + } else { + drawTextRalign(396, 15, "RTC invalid"); } } - else{ - if(commonData.config->getBool(commonData.config->useSimuData) == true){ - getdisplay().print("12:00 01.01.2024 LOT"); + else if (timesource == "GPS") { + // Show date and time if date present + if(date->valid == true){ + String acttime = formatValue(time, commonData).svalue; + acttime = acttime.substring(0, 5); + String actdate = formatValue(date, commonData).svalue; + getdisplay().print(acttime); + getdisplay().print(" "); + getdisplay().print(actdate); + getdisplay().print(" "); + getdisplay().print(tz == 0 ? "UTC" : "LOT"); } else{ - getdisplay().print("No GPS data"); + if(commonData.config->getBool(commonData.config->useSimuData) == true){ + getdisplay().print("12:00 01.01.2024 LOT"); + } + else{ + drawTextRalign(396, 15, "No GPS data"); + } } + } // timesource == "GPS" + else { + getdisplay().print("No time source"); } } } @@ -528,8 +545,7 @@ void displayFooter(CommonData &commonData) { } // Sunset und sunrise calculation -SunData calcSunsetSunrise(GwApi *api, double time, double date, double latitude, double longitude, double timezone){ - GwLog *logger=api->getLogger(); +SunData calcSunsetSunrise(double time, double date, double latitude, double longitude, float timezone){ SunData returnset; SunRise sr; int secPerHour = 3600; @@ -543,8 +559,7 @@ SunData calcSunsetSunrise(GwApi *api, double time, double date, double latitude, if (!isnan(time) && !isnan(date) && !isnan(latitude) && !isnan(longitude) && !isnan(timezone)) { // Calculate local epoch - t = (date * secPerYear) + time; - // api->getLogger()->logDebug(GwLog::DEBUG,"... calcSun: Lat %f, Lon %f, at: %d ", latitude, longitude, t); + t = (date * secPerYear) + time; sr.calculate(latitude, longitude, t); // LAT, LON, EPOCH // Sunrise if (sr.hasRise) { @@ -567,6 +582,37 @@ SunData calcSunsetSunrise(GwApi *api, double time, double date, double latitude, return returnset; } +SunData calcSunsetSunriseRTC(struct tm *rtctime, double latitude, double longitude, float timezone) { + SunData returnset; + SunRise sr; + const int secPerHour = 3600; + const int secPerYear = 86400; + sr.hasRise = false; + sr.hasSet = false; + time_t t = mktime(rtctime) + timezone * 3600;; + time_t sunR = 0; + time_t sunS = 0; + + sr.calculate(latitude, longitude, t); // LAT, LON, EPOCH + // Sunrise + if (sr.hasRise) { + sunR = (sr.riseTime + int(timezone * secPerHour) + 30) % secPerYear; // add 30 seconds: round to minutes + returnset.sunriseHour = int (sunR / secPerHour); + returnset.sunriseMinute = int((sunR - returnset.sunriseHour * secPerHour) / 60); + } + // Sunset + if (sr.hasSet) { + sunS = (sr.setTime + int(timezone * secPerHour) + 30) % secPerYear; // add 30 seconds: round to minutes + returnset.sunsetHour = int (sunS / secPerHour); + returnset.sunsetMinute = int((sunS - returnset.sunsetHour * secPerHour) / 60); + } + // Sun control (return value by sun on sky = false, sun down = true) + if ((t >= sr.riseTime) && (t <= sr.setTime)) + returnset.sunDown = false; + else returnset.sunDown = true; + return returnset; +} + // Battery graphic with fill level void batteryGraphic(uint x, uint y, float percent, int pcolor, int bcolor){ // Show battery diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h index 4118623..b878d00 100644 --- a/lib/obp60task/OBP60Extensions.h +++ b/lib/obp60task/OBP60Extensions.h @@ -97,7 +97,8 @@ void displayTrendLow(int16_t x, int16_t y, uint16_t size, uint16_t color); void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatValue *time, GwApi::BoatValue *hdop); // Draw display header void displayFooter(CommonData &commonData); -SunData calcSunsetSunrise(GwApi *api, double time, double date, double latitude, double longitude, double timezone); // Calulate sunset and sunrise +SunData calcSunsetSunrise(double time, double date, double latitude, double longitude, float timezone); // Calulate sunset and sunrise +SunData calcSunsetSunriseRTC(struct tm *rtctime, double latitude, double longitude, float timezone); void batteryGraphic(uint x, uint y, float percent, int pcolor, int bcolor); // Battery graphic with fill level void solarGraphic(uint x, uint y, int pcolor, int bcolor); // Solar graphic diff --git a/lib/obp60task/OBP60Formater.cpp b/lib/obp60task/OBP60Formater.cpp index 54e9fb9..5b9745d 100644 --- a/lib/obp60task/OBP60Formater.cpp +++ b/lib/obp60task/OBP60Formater.cpp @@ -8,6 +8,35 @@ // simulation data // hold values by missing data +String formatDate(String fmttype, uint16_t year, uint8_t month, uint8_t day) { + char buffer[12]; + if (fmttype == "GB") { + snprintf(buffer, 12, "%02d/%02d/%04d", day , month, year); + } + else if (fmttype == "US") { + snprintf(buffer, 12, "%02d/%02d/%04d", month, day, year); + } + else if (fmttype == "ISO") { + snprintf(buffer, 12, "%04d-%02d-%02d", year, month, day); + } + else { + snprintf(buffer, 12, "%02d.%02d.%04d", day, month, year); + } + return String(buffer); +} + +String formatTime(char fmttype, uint8_t hour, uint8_t minute, uint8_t second) { + // fmttype: s: with seconds, m: only minutes + char buffer[10]; + if (fmttype == 'm') { + snprintf(buffer, 10, "%02d:%02d", hour , minute); + } + else { + snprintf(buffer, 10, "%02d:%02d:%02d", hour, minute, second); + } + return String(buffer); +} + FormatedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ GwLog *logger = commondata.logger; FormatedData result; diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index a6d112c..500389e 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -17,9 +17,11 @@ #include "ObpNmea0183.h" // Check NMEA0183 sentence for uncorrect content #include "OBP60Extensions.h" // Lib for hardware extensions #include "movingAvg.h" // Lib for moving average building +#include "time.h" // For getting NTP time +#include // Internal ESP32 RTC clock // Timer for hardware functions -Ticker Timer1(blinkingFlashLED, 500); // Satrt Timer1 for flash LED all 500ms +Ticker Timer1(blinkingFlashLED, 500); // Start Timer1 for flash LED all 500ms // Initialization for all sensors (RS232, I2C, 1Wire, IOs) //#################################################################################### @@ -150,6 +152,7 @@ void sensorTask(void *param){ // ds1388.adjust(DateTime(__DATE__, __TIME__)); // Set date and time from PC file time } RTC_ready = true; + sensors.rtcValid = true; } } @@ -366,6 +369,28 @@ void sensorTask(void *param){ GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP); GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop}; + // Internal RTC with NTP init + ESP32Time rtc(0); + if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") { + GwApi::Status status; + api->getStatus(status); + if (status.wifiClientConnected) { + const char *ntpServer = api->getConfig()->getCString(api->getConfig()->timeServer); + api->getLogger()->logDebug(GwLog::LOG,"Fetching date and time from NTP server '%s'.", ntpServer); + configTime(0, 0, ntpServer); // get time in UTC + struct tm timeinfo; + if (getLocalTime(&timeinfo)) { + api->getLogger()->logDebug(GwLog::LOG,"NTP time: %04d-%02d-%02d %02d:%02d:%02d UTC", timeinfo.tm_year+1900, timeinfo.tm_mon+1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); + rtc.setTimeStruct(timeinfo); + sensors.rtcValid = true; + } else { + api->getLogger()->logDebug(GwLog::LOG,"NTP time fetch failed!"); + } + } else { + api->getLogger()->logDebug(GwLog::LOG,"Wifi client not connected, NTP not available."); + } + } + // Sensor task loop runs with 100ms //#################################################################################### @@ -428,39 +453,45 @@ void sensorTask(void *param){ loopCounter++; } - // If GPS not ready or installed then send RTC time on bus all 500ms - if(millis() > starttime12 + 500){ + // Get current RTC date and time all 500ms + if (millis() > starttime12 + 500) { starttime12 = millis(); - if((rtcOn == "DS1388" && RTC_ready == true && GPS_ready == false) || (rtcOn == "DS1388" && RTC_ready == true && GPS_ready == true && hdop->valid == false)){ - // Convert RTC time to Unix system time - // https://de.wikipedia.org/wiki/Unixzeit - const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334}; - long unixtime = ds1388.now().get(); - uint16_t year = ds1388.now().year(); - uint8_t month = ds1388.now().month(); - uint8_t hour = ds1388.now().hour(); - uint8_t minute = ds1388.now().minute(); - uint8_t second = ds1388.now().second(); - uint8_t day = ds1388.now().day(); - uint16_t switchYear = ((year-1)-1968)/4 - ((year-1)-1900)/100 + ((year-1)-1600)/400; - long daysAt1970 = (year-1970)*365 + switchYear + daysOfYear[month-1] + day-1; - // If switch year then add one day - if ( (month>2) && (year%4==0 && (year%100!=0 || year%400==0)) ){ - daysAt1970 += 1; - } - double sysTime = (hour * 3600) + (minute * 60) + second; - if(!isnan(daysAt1970) && !isnan(sysTime)){ - sensors.rtcYear = year; // Save values in SensorData - sensors.rtcMonth = month; - sensors.rtcDay = day; - sensors.rtcHour = hour; - sensors.rtcMinute = minute; - sensors.rtcSecond = second; - // api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",year, month, day, hour, minute, second); - // api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime); - SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock); - api->sendN2kMessage(N2kMsg); + if (rtcOn == "DS1388" && RTC_ready) { + DateTime dt = ds1388.now(); + sensors.rtcTime.tm_year = dt.year() - 1900; // Save values in SensorData + sensors.rtcTime.tm_mon = dt.month() - 1; + sensors.rtcTime.tm_mday = dt.day(); + sensors.rtcTime.tm_hour = dt.hour(); + sensors.rtcTime.tm_min = dt.minute(); + sensors.rtcTime.tm_sec = dt.second(); + sensors.rtcTime.tm_isdst = 0; // Not considering daylight saving time + + // If GPS not ready or installed then send RTC time on bus + // TODO If there are other time sources on the bus there should + // be a logic not to send or to send with lower frequency + // or something totally different + if ((GPS_ready == false) || (GPS_ready == true && hdop->valid == false)) { + // TODO implement daysAt1970 and sysTime as methods of DateTime + const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334}; + uint16_t switchYear = ((dt.year()-1)-1968)/4 - ((dt.year()-1)-1900)/100 + ((dt.year()-1)-1600)/400; + long daysAt1970 = (dt.year()-1970)*365 + switchYear + daysOfYear[dt.month()-1] + dt.day()-1; + // If switch year then add one day + if ((dt.month() > 2) && (dt.year() % 4 == 0 && (dt.year() % 100 != 0 || dt.year() % 400 == 0))) { + daysAt1970 += 1; + } + // N2K sysTime is double in n2klib + double sysTime = (dt.hour() * 3600) + (dt.minute() * 60) + dt.second(); + // WHY? isnan should always fail here + //if(!isnan(daysAt1970) && !isnan(sysTime)){ + //api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec); + //api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime); + SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock); + api->sendN2kMessage(N2kMsg); + // } } + } else if (sensors.rtcValid) { + // use internal rtc feature + sensors.rtcTime = rtc.getTimeStruct(); } } diff --git a/lib/obp60task/PageClock.cpp b/lib/obp60task/PageClock.cpp index 906956a..b3cea6b 100644 --- a/lib/obp60task/PageClock.cpp +++ b/lib/obp60task/PageClock.cpp @@ -3,24 +3,83 @@ #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 simulation = false; +int simtime; +bool keylock = false; +char source = 'R'; // time source (R)TC | (G)PS | (N)TP +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){ - // Code for keylock - if(key == 11){ - commonData->keylock = !commonData->keylock; + // Time source + if (key == 1) { + if (source == 'G') { + source = 'R'; + } else { + source = 'G'; + } + return 0; + } + if (key == 2) { + if (mode == 'A') { + mode = 'D'; + } else if (mode == 'D') { + mode = 'T'; + } else { + mode = 'A'; + } + return 0; + } + // Time zone: Local / UTC + if (key == 5) { + if (tz == 'L') { + tz = 'U'; + } else { + tz = 'L'; + } + return 0; + } + + // Keylock function + if(key == 11){ // Code for keylock + keylock = !keylock; // Toggle keylock return 0; // Commit the key } return key; @@ -47,11 +106,10 @@ class PageClock : public Page // 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); - String stimezone = config->getString(config->timeZone); - double timezone = stimezone.toDouble(); // Get boat values for GPS time GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) @@ -67,7 +125,7 @@ class PageClock : public Page 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 + svalue1old = svalue1; // Save old value unit1old = unit1; // Save old unit } @@ -80,7 +138,7 @@ class PageClock : public Page 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 + svalue2old = svalue2; // Save old value unit2old = unit2; // Save old unit } @@ -93,14 +151,14 @@ class PageClock : public Page 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 + 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); + setFlashLED(false); } // Logging boat values @@ -115,11 +173,30 @@ class PageClock : public Page 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_Bold8pt7b); getdisplay().setCursor(10, 65); - if(holdvalues == false) getdisplay().print(svalue2); // Value - else getdisplay().print(svalue2old); + 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_Bold12pt7b); getdisplay().setCursor(10, 95); getdisplay().print("Date"); // Name @@ -130,15 +207,31 @@ class PageClock : public Page // Show values GPS time getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(10, 250); - if(holdvalues == false) getdisplay().print(svalue1); // Value - else getdisplay().print(svalue1old); + 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_Bold12pt7b); getdisplay().setCursor(10, 220); getdisplay().print("Time"); // Name // Show values sunrise String sunrise = "---"; - if(valid1 == true && valid2 == true && valid3 == true){ + 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) { @@ -158,7 +251,7 @@ class PageClock : public Page // Show values sunset String sunset = "---"; - if(valid1 == true && valid2 == true && valid3 == true){ + 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) { @@ -174,7 +267,7 @@ class PageClock : public Page getdisplay().print("SunS"); // Name //******************************************************************************************* - + // Draw clock int rInstrument = 110; // Radius of clock float pi = 3.141592; @@ -246,27 +339,52 @@ class PageClock : public Page getdisplay().setFont(&Ubuntu_Bold12pt7b); getdisplay().setCursor(175, 110); if(holdvalues == false){ - getdisplay().print(unit2); // Unit + getdisplay().print(tz == 'L' ? "LOT" : "UTC"); } else{ - getdisplay().print(unit2old); // Unit + getdisplay().print(unit2old); // date unit + } + + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(185, 190); + if (source == 'G') { + getdisplay().print("GPS"); + } else { + getdisplay().print("RTC"); } // Clock values double hour = 0; double minute = 0; - value1 = value1 + int(timezone*3600); - if (value1 > 86400) {value1 = value1 - 86400;} - if (value1 < 0) {value1 = value1 + 86400;} - hour = (value1 / 3600.0); - if(hour > 12) hour = hour - 12.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 (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 || holdvalues == true || simulation == true){ + 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 @@ -274,7 +392,7 @@ class PageClock : public Page float xx1 = -startwidth; float xx2 = startwidth; float yy1 = -startwidth; - float yy2 = -(rInstrument * 0.5); + 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); @@ -292,7 +410,7 @@ class PageClock : public Page // Draw minute pointer startwidth = 8; // Start width of pointer - if(valid1 == true || holdvalues == true || simulation == true){ + 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 @@ -300,7 +418,7 @@ class PageClock : public Page float xx1 = -startwidth; float xx2 = startwidth; float yy1 = -startwidth; - float yy2 = -(rInstrument - 15); + 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); diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index b979b57..65bdc67 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -43,14 +43,10 @@ typedef struct{ double airHumidity = 0; double airPressure = 0; double onewireTemp[8] = {0,0,0,0,0,0,0,0}; - double rotationAngle = 0; // Rotation angle in radiant - bool validRotAngle = false; // Valid flag magnet present for rotation sensor - int rtcYear = 0; // UTC time - int rtcMonth = 0; - int rtcDay = 0; - int rtcHour = 0; - int rtcMinute = 0; - int rtcSecond = 0; + double rotationAngle = 0; // Rotation angle in radiant + bool validRotAngle = false; // Valid flag magnet present for rotation sensor + struct tm rtcTime; // UTC time from internal RTC + bool rtcValid = false; int sunsetHour = 0; int sunsetMinute = 0; int sunriseHour = 0; @@ -169,13 +165,16 @@ class PageStruct{ PageDescription *description=NULL; }; -// Structure for formated boat values +// Standard format functions without overhead +String formatDate(String fmttype, uint16_t year, uint8_t month, uint8_t day); +String formatTime(char fmttype, uint8_t hour, uint8_t minute, uint8_t second); + +// Structure for formatted boat values typedef struct{ double value; String svalue; String unit; } FormatedData; - -// Formater for boat values +// Formatter for boat values FormatedData formatValue(GwApi::BoatValue *value, CommonData &commondata); diff --git a/lib/obp60task/config.json b/lib/obp60task/config.json index 14d313a..44db864 100644 --- a/lib/obp60task/config.json +++ b/lib/obp60task/config.json @@ -8,6 +8,17 @@ "description": "system name, used for the access point and for services", "category": "system" }, + { + "name": "timeServer", + "label": "time server", + "type": "string", + "default": "pool.ntp.org", + "description": "NTP time server. Use only one hostname or IP address", + "category": "wifi client", + "capabilities": { + "obp40": "true" + } + }, { "name": "timeZone", "label": "Time Zone", @@ -22,6 +33,34 @@ "obp60":"true" } }, + { + "name": "homeLAT", + "label": "Home latitude", + "type": "number", + "default": "", + "check": "checkMinMax", + "min": -90.0, + "max": 90.0, + "description": "Latitude of boat home location [-90.0...+90.0]", + "category": "OBP60 Settings", + "capabilities": { + "obp60":"true" + } + }, + { + "name": "homeLON", + "label": "Home longitude", + "type": "number", + "default": "", + "check": "checkMinMax", + "min": -180.0, + "max": 180.0, + "description": "Longitude of boat home location [-180.0...+180.0]", + "category": "OBP60 Settings", + "capabilities": { + "obp60":"true" + } + }, { "name": "draft", "label": "Boat Draft [m]", @@ -690,6 +729,21 @@ "obp60":"true" } }, + { + "name": "timeSource", + "label": "Status Time Source", + "type": "list", + "default": "GPS", + "description": "Data source for date and time display in status line [RTC|GPS]", + "list": [ + {"l":"Real time clock (RTC)","v":"RTC"}, + {"l":"Time via bus (GPS)","v":"GPS"} + ], + "category": "OBP60 Display", + "capabilities": { + "obp60":"true" + } + }, { "name": "refresh", "label": "Refresh", diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index 888c746..e24e9e9 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -8,6 +8,17 @@ "description": "system name, used for the access point and for services", "category": "system" }, + { + "name": "timeServer", + "label": "time server", + "type": "string", + "default": "pool.ntp.org", + "description": "NTP time server. Use only one hostname or IP address", + "category": "wifi client", + "capabilities": { + "obp40": "true" + } + }, { "name": "timeZone", "label": "Time Zone", @@ -22,6 +33,34 @@ "obp40": "true" } }, + { + "name": "homeLAT", + "label": "Home latitude", + "type": "number", + "default": "0.00000", + "check": "checkMinMax", + "min": -90.0, + "max": 90.0, + "description": "Latitude of boat home location [-90.0...+90.0]", + "category": "OBP40 Settings", + "capabilities": { + "obp40": "true" + } + }, + { + "name": "homeLON", + "label": "Home longitude", + "type": "number", + "default": "0.00000", + "check": "checkMinMax", + "min": -180.0, + "max": 180.0, + "description": "Longitude of boat home location [-180.0...+180.0]", + "category": "OBP40 Settings", + "capabilities": { + "obp40": "true" + } + }, { "name": "draft", "label": "Boat Draft [m]", @@ -705,6 +744,22 @@ "obp40": "true" } }, + { + "name": "timeSource", + "label": "Status Time Source", + "type": "list", + "default": "GPS", + "description": "Data source for date and time display in status line [RTC|iRTC|GPS]", + "list": [ + {"l":"Internal real time clock (iRTC)","v":"iRTC"}, + {"l":"External real time clock (RTC)","v":"RTC"}, + {"l":"External time via bus (GPS)","v":"GPS"} + ], + "category": "OBP40 Display", + "capabilities": { + "obp40":"true" + } + }, { "name": "refresh", "label": "Refresh", diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index d92454b..8a65aac 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -548,7 +548,7 @@ void OBP60Task(GwApi *api){ // Configuration values for main loop String gpsFix = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString(); String gpsOn=api->getConfig()->getConfigItem(api->getConfig()->useGPS,true)->asString(); - String tz = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asString(); + float tz = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asFloat(); commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight,true)->asString()); commonData.backlight.color = colorMapping(config->getConfigItem(config->blColor,true)->asString()); @@ -559,6 +559,15 @@ void OBP60Task(GwApi *api){ String cpuspeed = api->getConfig()->getConfigItem(api->getConfig()->cpuSpeed,true)->asString(); uint hdopAccuracy = uint(api->getConfig()->getConfigItem(api->getConfig()->hdopAccuracy,true)->asInt()); + double homelat = commonData.config->getString(commonData.config->homeLAT).toDouble(); + double homelon = commonData.config->getString(commonData.config->homeLON).toDouble(); + bool homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0; + if (homevalid) { + LOG_DEBUG(GwLog::LOG, "Home location set to %f : %f", homelat, homelon); + } else { + LOG_DEBUG(GwLog::LOG, "No valid home location found"); + } + // refreshmode defined in init section // Boat values for main loop @@ -708,7 +717,7 @@ void OBP60Task(GwApi *api){ starttime5 = millis(); if(time->valid == true && date->valid == true && lat->valid == true && lon->valid == true){ // Provide sundata to all pages - commonData.sundata = calcSunsetSunrise(api, time->value , date->value, lat->value, lon->value, tz.toDouble()); + commonData.sundata = calcSunsetSunrise(time->value , date->value, lat->value, lon->value, tz); // Backlight with sun control if (commonData.backlight.mode == BacklightMode::SUN) { // if(String(backlight) == "Control by Sun"){ @@ -719,6 +728,9 @@ void OBP60Task(GwApi *api){ setBacklightLED(0, COLOR_BLUE); // Backlight LEDs off (blue without britghness) } } + } else if (homevalid and commonData.data.rtcValid) { + // No gps fix but valid home location and time configured + commonData.sundata = calcSunsetSunriseRTC(&commonData.data.rtcTime, homelat, homelon, tz); } } diff --git a/lib/obp60task/platformio.ini b/lib/obp60task/platformio.ini index 2259b61..4acd552 100644 --- a/lib/obp60task/platformio.ini +++ b/lib/obp60task/platformio.ini @@ -21,6 +21,7 @@ lib_deps = ${basedeps.lib_deps} Wire SPI + ESP32time esphome/AsyncTCP-esphome@2.0.1 robtillaart/PCF8574@0.3.9 adafruit/Adafruit Unified Sensor @ 1.1.13 @@ -71,6 +72,7 @@ lib_deps = Wire SPI SD + ESP32time esphome/AsyncTCP-esphome@2.0.1 robtillaart/PCF8574@0.3.9 adafruit/Adafruit Unified Sensor @ 1.1.13 @@ -94,8 +96,8 @@ build_flags= -D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib -D BOARD_OBP40S3 #Board OBP40 V1.0 with ESP32S3 SKU:DIE07300S (CrowPanel 4.2) -D DISPLAY_GDEY042T81 #new E-Ink display from Waveshare, R10 2.2 ohm - -D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh - -D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors + #-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh + #-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors ${env.build_flags} upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27