From ebf6e62389501cd607e49b3d04149395eb444c78 Mon Sep 17 00:00:00 2001 From: Manfred Radmacher <99966818+ManfredRad@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:57:06 +0200 Subject: [PATCH 1/2] PageCompass A new page to show heading data or course over ground in a layout inspoired by an analog compass. The compass signal shown can be altered by the left key. In addition a second signal can be shown as well from the following choice: HDM, HDT, COG, STW, SOG, DBS. This signal can also be changed by the second key. --- lib/obp60task/PageCompass.cpp | 262 ++++++++++++++++++++++++++++++++++ lib/obp60task/config.json | 194 ++----------------------- lib/obp60task/gen_set.py | 3 +- 3 files changed, 275 insertions(+), 184 deletions(-) create mode 100644 lib/obp60task/PageCompass.cpp diff --git a/lib/obp60task/PageCompass.cpp b/lib/obp60task/PageCompass.cpp new file mode 100644 index 0000000..003577c --- /dev/null +++ b/lib/obp60task/PageCompass.cpp @@ -0,0 +1,262 @@ +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 + +#include "Pagedata.h" +#include "OBP60Extensions.h" + +// these constants have to match the declaration below in : +// PageDescription registerPageCompass( +// {"COG","HDT", "HDM"}, // Bus values we need in the page +const int HowManyValues = 6; + +const int AverageValues = 4; + +const int ShowHDM = 0; +const int ShowHDT = 1; +const int ShowCOG = 2; +const int ShowSTW = 3; +const int ShowSOG = 4; +const int ShowDBS = 5; + +const int Compass_X0 = 200; // center point of compass band +const int Compass_Y0 = 220; // position of compass lines +const int Compass_LineLength = 22; // length of compass lines +const float Compass_LineDelta = 8.0; // compass band: 1deg = 5 Pixels, 10deg = 50 Pixels + +class PageCompass : public Page +{ + int WhichDataCompass = ShowHDM; + int WhichDataDisplay = ShowHDM; + + public: + PageCompass(CommonData &common){ + commonData = &common; + common.logger->logDebug(GwLog::LOG,"Instantiate PageCompass"); + } + + virtual void setupKeys(){ + Page::setupKeys(); + commonData->keydata[0].label = "CMP"; + commonData->keydata[1].label = "SRC"; + } + + virtual int handleKey(int key){ + // Code for keylock + + if ( key == 1 ) { + WhichDataCompass += 1; + if ( WhichDataCompass > ShowCOG) + WhichDataCompass = ShowHDM; + return 0; + } + if ( key == 2 ) { + WhichDataDisplay += 1; + if ( WhichDataDisplay > ShowDBS) + WhichDataDisplay = ShowHDM; + } + + if(key == 11){ + commonData->keylock = !commonData->keylock; + return 0; // Commit the key + } + return key; + } + + virtual void displayPage(PageData &pageData){ + GwConfigHandler *config = commonData->config; + GwLog *logger = commonData->logger; + + + // Old values for hold function + static String OldDataText[HowManyValues] = {"", "", "","", "", ""}; + static String OldDataUnits[HowManyValues] = {"", "", "","", "", ""}; + + // Get config data + String lengthformat = config->getString(config->lengthFormat); + // bool simulation = config->getBool(config->useSimuData); + bool holdvalues = config->getBool(config->holdvalues); + String flashLED = config->getString(config->flashLED); + String backlightMode = config->getString(config->backlight); + + GwApi::BoatValue *bvalue; + String DataName[HowManyValues]; + double DataValue[HowManyValues]; + bool DataValid[HowManyValues]; + String DataText[HowManyValues]; + String DataUnits[HowManyValues]; + String DataFormat[HowManyValues]; + FormatedData TheFormattedData; + + for (int i = 0; i < HowManyValues; i++){ + bvalue = pageData.values[i]; + TheFormattedData = formatValue(bvalue, *commonData); + DataName[i] = xdrDelete(bvalue->getName()); + DataName[i] = DataName[i].substring(0, 6); // String length limit for value name + DataUnits[i] = formatValue(bvalue, *commonData).unit; + DataText[i] = TheFormattedData.svalue; // Formatted value as string including unit conversion and switching decimal places + DataValue[i] = TheFormattedData.value; // Value as double in SI unit + DataValid[i] = bvalue->valid; + DataFormat[i] = bvalue->getFormat(); // Unit of value + LOG_DEBUG(GwLog::LOG,"Drawing at PageCompass: %d %s %f %s %s", i, DataName[i], DataValue[i], DataFormat[i], DataText[i] ); + } + + // Optical warning by limit violation (unused) + if(String(flashLED) == "Limit Violation"){ + setBlinkingLED(false); + setFlashLED(false); + } + + if (bvalue == NULL) return; + + //*********************************************************** + + // Set display in partial refresh mode + getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + getdisplay().setTextColor(commonData->fgcolor); + + // Horizontal line 3 pix top & bottom + // print Data on top half + getdisplay().fillRect(0, 23, 400, 3, commonData->fgcolor); + getdisplay().setFont(&Ubuntu_Bold20pt7b); + getdisplay().setCursor(10, 70); + getdisplay().print(DataName[WhichDataDisplay]); // Page name + // Show unit + getdisplay().setFont(&Ubuntu_Bold12pt7b); + getdisplay().setCursor(10, 120); + getdisplay().print(DataUnits[WhichDataDisplay]); + getdisplay().setCursor(200, 120); + getdisplay().setFont(&DSEG7Classic_BoldItalic42pt7b); + + if(holdvalues == false){ + getdisplay().print(DataText[WhichDataDisplay]); // Real value as formated string + } + else{ + getdisplay().print(OldDataText[WhichDataDisplay]); // Old value as formated string + } + if(DataValid[WhichDataDisplay] == true){ + OldDataText[WhichDataDisplay] = DataText[WhichDataDisplay]; // Save the old value + OldDataUnits[WhichDataDisplay] = DataUnits[WhichDataDisplay]; // Save the old unit + } + // now draw compass band + // get the data + double TheAngle = DataValue[WhichDataCompass]; + static double AvgAngle = 0; + AvgAngle = ( AvgAngle * AverageValues + TheAngle ) / (AverageValues + 1 ); + + int TheTrend = round( ( TheAngle - AvgAngle) * 180.0 / M_PI ); + + static const int bsize = 30; + char buffer[bsize+1]; + buffer[0]=0; + + getdisplay().setFont(&Ubuntu_Bold16pt7b); + getdisplay().setCursor(10, Compass_Y0-60); + getdisplay().print(DataName[WhichDataCompass]); // Page name + + + // draw compass base line and pointer + getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor); + getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor); +// draw trendlines + for ( int i = 1; i < abs(TheTrend) / 2; i++){ + int x1; + if ( TheTrend < 0 ) + x1 = Compass_X0 + 20 * i; + else + x1 = Compass_X0 - 20 * ( i + 1 ); + + getdisplay().fillRect(x1, Compass_Y0 -60, 10, 6, commonData->fgcolor); + } +// central line + satellite lines + double NextSector = round(TheAngle / ( M_PI / 9 )) * ( M_PI / 9 ); // get the next 20degree value + double Offset = - ( NextSector - TheAngle); // offest of the center line compared to TheAngle in Radian + + int Delta_X = int ( Offset * 180.0 / M_PI * Compass_LineDelta ); + for ( int i = 0; i <=4; i++ ) + { + int x0; + x0 = Compass_X0 + Delta_X + 2 * i * 5 * Compass_LineDelta; + getdisplay().fillRect(x0-1, Compass_Y0 - 2 * Compass_LineLength,3, 2 * Compass_LineLength, commonData->fgcolor); + x0 = Compass_X0 + Delta_X + ( 2 * i + 1 ) * 5 * Compass_LineDelta; + getdisplay().fillRect(x0-1, Compass_Y0 - Compass_LineLength,3, Compass_LineLength, commonData->fgcolor); + + x0 = Compass_X0 + Delta_X - 2 * i * 5 * Compass_LineDelta; + getdisplay().fillRect(x0-1, Compass_Y0 - 2 * Compass_LineLength,3, 2 * Compass_LineLength, commonData->fgcolor); + x0 = Compass_X0 + Delta_X - ( 2 * i + 1 ) * 5 * Compass_LineDelta; + getdisplay().fillRect(x0-1, Compass_Y0 - Compass_LineLength,3, Compass_LineLength, commonData->fgcolor); + } + + getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor); + // add the numbers to the compass band + int x0; + float AngleToDisplay = NextSector * 180.0 / M_PI; + + x0 = Compass_X0 + Delta_X; + getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); + + do { + getdisplay().setCursor(x0 - 40, Compass_Y0 + 40); + snprintf(buffer,bsize,"%03.0f", AngleToDisplay); + getdisplay().print(buffer); + AngleToDisplay += 20; + if ( AngleToDisplay >= 360.0 ) + AngleToDisplay -= 360.0; + x0 -= 4 * 5 * Compass_LineDelta; + } while ( x0 >= 0 - 60 ); + + AngleToDisplay = NextSector * 180.0 / M_PI - 20; + if ( AngleToDisplay < 0 ) + AngleToDisplay += 360.0; + + x0 = Compass_X0 + Delta_X + 4 * 5 * Compass_LineDelta; + do { + getdisplay().setCursor(x0 - 40, Compass_Y0 + 40); + snprintf(buffer,bsize,"%03.0f", AngleToDisplay); + // quick and dirty way to prevent wrapping text in next line + if ( ( x0 - 40 ) > 380 ) + buffer[0] = 0; + else if ( ( x0 - 40 ) > 355 ) + buffer[1] = 0; + else if ( ( x0 - 40 ) > 325 ) + buffer[2] = 0; + + getdisplay().print(buffer); + + AngleToDisplay -= 20; + if ( AngleToDisplay < 0 ) + AngleToDisplay += 360.0; + x0 += 4 * 5 * Compass_LineDelta; + } while (x0 < ( 400 - 20 -40 ) ); + + // static int x_test = 320; + // x_test += 2; + + // snprintf(buffer,bsize,"%03d", x_test); + // getdisplay().setCursor(x_test, Compass_Y0 - 60); + // getdisplay().print(buffer); + // if ( x_test > 390) + // x_test = 320; + + // Update display + getdisplay().nextPage(); // Partial update (fast) + + }; + + }; +static Page *createPage(CommonData &common){ + return new PageCompass(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 + * this will be number of BoatValue pointers in pageData.values + */ +PageDescription registerPageCompass( + "Compass", // Page name + createPage, // Action + 0, // Number of bus values depends on selection in Web configuration + {"HDM","HDT", "COG", "STW", "SOG", "DBS"}, // Bus values we need in the page + true // Show display header on/off +); + +#endif diff --git a/lib/obp60task/config.json b/lib/obp60task/config.json index c1801af..6bd401a 100644 --- a/lib/obp60task/config.json +++ b/lib/obp60task/config.json @@ -686,189 +686,6 @@ "obp60":"true" } }, - { - "name": "calInstance1", - "label": "Calibration Data Instance 1", - "type": "list", - "default": "---", - "description": "Data instance for calibration", - "list": [ - "---", - "AWA", - "AWS", - "DBT", - "HDM", - "PRPOS", - "RPOS", - "STW", - "TWA", - "TWS", - "TWD", - "WTemp" - ], - "category": "OBP60 Calibrations", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "calOffset1", - "label": "Data Instance 1 Calibration Offset", - "type": "number", - "default": "0.00", - "description": "Offset for data instance 1", - "category": "OBP60 Calibrations", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "calSlope1", - "label": "Data Instance 1 Calibration Slope", - "type": "number", - "default": "1.00", - "description": "Slope for data instance 1", - "category": "OBP60 Calibrations", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "calSmooth1", - "label": "Data Instance 1 Smoothing", - "type": "number", - "default": "0", - "check": "checkMinMax", - "min": 0, - "max": 10, - "description": "Smoothing factor for data instance 1", - "category": "OBP60 Calibrations", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "calInstance2", - "label": "Calibration Data Instance 2", - "type": "list", - "default": "---", - "description": "Data instance for calibration", - "list": [ - "---", - "AWA", - "AWS", - "DBT", - "HDM", - "PRPOS", - "RPOS", - "STW", - "TWA", - "TWS", - "TWD", - "WTemp" - ], - "category": "OBP60 Calibrations", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "calOffset2", - "label": "Data Instance 2 Calibration Offset", - "type": "number", - "default": "0.00", - "description": "Offset for data instance 2", - "category": "OBP60 Calibrations", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "calSlope2", - "label": "Data Instance 2 Calibration Slope", - "type": "number", - "default": "1.00", - "description": "Slope for data instance 2", - "category": "OBP60 Calibrations", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "calSmooth2", - "label": "Data Instance 2 Smoothing", - "type": "number", - "default": "0", - "check": "checkMinMax", - "min": 0, - "max": 10, - "description": "Smoothing factor for data instance 2", - "category": "OBP60 Calibrations", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "calInstance3", - "label": "Calibration Data Instance 3", - "type": "list", - "default": "---", - "description": "Data instance for calibration", - "list": [ - "---", - "AWA", - "AWS", - "DBT", - "HDM", - "PRPOS", - "RPOS", - "STW", - "TWA", - "TWS", - "TWD", - "WTemp" - ], - "category": "OBP60 Calibrations", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "calOffset3", - "label": "Data Instance 3 Calibration Offset", - "type": "number", - "default": "0.00", - "description": "Offset for data instance 3", - "category": "OBP60 Calibrations", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "calSlope3", - "label": "Data Instance 3 Calibration Slope", - "type": "number", - "default": "1.00", - "description": "Slope for data instance 3", - "category": "OBP60 Calibrations", - "capabilities": { - "obp60":"true" - } - }, - { - "name": "calSmooth3", - "label": "Data Instance 3 Smoothing", - "type": "number", - "default": "0", - "check": "checkMinMax", - "min": 0, - "max": 10, - "description": "Smoothing factor for data instance 3", - "category": "OBP60 Calibrations", - "capabilities": { - "obp60":"true" - } - }, { "name": "display", "label": "Display Mode", @@ -1152,6 +969,7 @@ } }, + { "name": "page1type", "label": "Type", @@ -1163,6 +981,7 @@ "Battery", "Battery2", "Clock", + "Compass", "DST810", "Fluid", "FourValues", @@ -1442,6 +1261,7 @@ "Battery", "Battery2", "Clock", + "Compass", "DST810", "Fluid", "FourValues", @@ -1718,6 +1538,7 @@ "Battery", "Battery2", "Clock", + "Compass", "DST810", "Fluid", "FourValues", @@ -1991,6 +1812,7 @@ "Battery", "Battery2", "Clock", + "Compass", "DST810", "Fluid", "FourValues", @@ -2261,6 +2083,7 @@ "Battery", "Battery2", "Clock", + "Compass", "DST810", "Fluid", "FourValues", @@ -2528,6 +2351,7 @@ "Battery", "Battery2", "Clock", + "Compass", "DST810", "Fluid", "FourValues", @@ -2792,6 +2616,7 @@ "Battery", "Battery2", "Clock", + "Compass", "DST810", "Fluid", "FourValues", @@ -3053,6 +2878,7 @@ "Battery", "Battery2", "Clock", + "Compass", "DST810", "Fluid", "FourValues", @@ -3311,6 +3137,7 @@ "Battery", "Battery2", "Clock", + "Compass", "DST810", "Fluid", "FourValues", @@ -3566,6 +3393,7 @@ "Battery", "Battery2", "Clock", + "Compass", "DST810", "Fluid", "FourValues", diff --git a/lib/obp60task/gen_set.py b/lib/obp60task/gen_set.py index 69809fa..71485fa 100755 --- a/lib/obp60task/gen_set.py +++ b/lib/obp60task/gen_set.py @@ -15,6 +15,7 @@ no_of_fields_per_page = { "Battery": 0, "BME280": 0, "Clock": 0, + "Compass" : 0, "DST810": 0, "Fluid": 1, "FourValues2": 4, @@ -24,6 +25,7 @@ no_of_fields_per_page = { "OneValue": 1, "RollPitch": 2, "RudderPosition": 0, + "SixValues" : 6, "Solar": 0, "ThreeValues": 3, "TwoValues": 2, @@ -31,7 +33,6 @@ no_of_fields_per_page = { "WhitePage": 0, "WindRose": 0, "WindRoseFlex": 6, - "SixValues" : 6, } # No changes needed beyond this point From 4b5aa7ff4be24a80e7e629d69839ca5854c47df3 Mon Sep 17 00:00:00 2001 From: Manfred Radmacher <99966818+ManfredRad@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:05:33 +0200 Subject: [PATCH 2/2] Pagecompass update obp60rtask.cpp updated to handle pagecompass --- obp60task.cpp | 853 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 853 insertions(+) create mode 100644 obp60task.cpp diff --git a/obp60task.cpp b/obp60task.cpp new file mode 100644 index 0000000..a364dc8 --- /dev/null +++ b/obp60task.cpp @@ -0,0 +1,853 @@ +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 +#include "obp60task.h" +#include "Pagedata.h" // Data exchange for pages +#include "OBP60Hardware.h" // PIN definitions +#include // I2C connections +#include // MCP23017 extension Port +#include // NMEA2000 +#include +#include // NMEA0183 +#include +#include +#include // GxEPD2 lib for b/w E-Ink displays +#include "OBP60Extensions.h" // Functions lib for extension board +#include "OBP60Keypad.h" // Functions for keypad + +#ifdef BOARD_OBP40S3 +#include "driver/rtc_io.h" // Needs for weakup from deep sleep +#include // SD-Card access +#include +#include +#endif + +// True type character sets includes +// See OBP60ExtensionPort.cpp + +// Pictures +//#include GxEPD_BitmapExamples // Example picture +#include "MFD_OBP60_400x300_sw.h" // MFD with logo +#include "Logo_OBP_400x300_sw.h" // OBP Logo +#include "images/unknown.xbm" // unknown page indicator +#include "OBP60QRWiFi.h" // Functions lib for WiFi QR code +#include "OBPSensorTask.h" // Functions lib for sensor data + + +// Global vars +bool initComplete = false; // Initialization complete +int taskRunCounter = 0; // Task couter for loop section + +// Hardware initialization before start all services +//#################################################################################### +void OBP60Init(GwApi *api){ + + GwLog *logger = api->getLogger(); + GwConfigHandler *config = api->getConfig(); + + // Set a new device name and hidden the original name in the main config + String devicename = api->getConfig()->getConfigItem(api->getConfig()->deviceName,true)->asString(); + api->getConfig()->setValue(GwConfigDefinitions::systemName, devicename, GwConfigInterface::ConfigType::HIDDEN); + + api->getLogger()->logDebug(GwLog::LOG,"obp60init running"); + + // Check I2C devices + + + // Init hardware + hardwareInit(api); + + // Init power rail 5.0V + String powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString(); + api->getLogger()->logDebug(GwLog::DEBUG,"Power Mode is: %s", powermode.c_str()); + if(powermode == "Max Power" || powermode == "Only 5.0V"){ + #ifdef HARDWARE_V21 + setPortPin(OBP_POWER_50, true); // Power on 5.0V rail + #endif + #ifdef BOARD_OBP40S3 + setPortPin(OBP_POWER_EPD, true);// Power on ePaper display + setPortPin(OBP_POWER_SD, true); // Power on SD card + #endif + } + else{ + #ifdef HARDWARE_V21 + setPortPin(OBP_POWER_50, false); // Power off 5.0V rail + #endif + #ifdef BOARD_OBP40S3 + setPortPin(OBP_POWER_EPD, false);// Power off ePaper display + setPortPin(OBP_POWER_SD, false); // Power off SD card + #endif + } + + #ifdef BOARD_OBP40S3 + String sdcard = config->getConfigItem(config->useSDCard, true)->asString(); + if (sdcard == "on") { + SPIClass SD_SPI = SPIClass(HSPI); + SD_SPI.begin(SD_SPI_CLK, SD_SPI_MISO, SD_SPI_MOSI); + if (SD.begin(SD_SPI_CS, SD_SPI, 80000000)) { + String sdtype = "unknown"; + uint8_t cardType = SD.cardType(); + switch (cardType) { + case CARD_MMC: + sdtype = "MMC"; + break; + case CARD_SD: + sdtype = "SDSC"; + break; + case CARD_SDHC: + sdtype = "SDHC"; + break; + } + uint64_t cardSize = SD.cardSize() / (1024 * 1024); + LOG_DEBUG(GwLog::LOG,"SD card type %s of size %d MB detected", sdtype, cardSize); + } + } + + // Deep sleep wakeup configuration + esp_sleep_enable_ext0_wakeup(OBP_WAKEWUP_PIN, 0); // 1 = High, 0 = Low + rtc_gpio_pullup_en(OBP_WAKEWUP_PIN); // Activate pullup resistor + rtc_gpio_pulldown_dis(OBP_WAKEWUP_PIN); // Disable pulldown resistor +#endif + + // Settings for e-paper display + String fastrefresh = api->getConfig()->getConfigItem(api->getConfig()->fastRefresh,true)->asString(); + api->getLogger()->logDebug(GwLog::DEBUG,"Fast Refresh Mode is: %s", fastrefresh.c_str()); + #ifdef DISPLAY_GDEY042T81 + if(fastrefresh == "true"){ + static const bool useFastFullUpdate = true; // Enable fast full display update only for GDEY042T81 + } + #endif + + #ifdef BOARD_OBP60S3 + touchSleepWakeUpEnable(TP1, 45); // TODO sensitivity should be configurable via web interface + touchSleepWakeUpEnable(TP2, 45); + touchSleepWakeUpEnable(TP3, 45); + touchSleepWakeUpEnable(TP4, 45); + touchSleepWakeUpEnable(TP5, 45); + touchSleepWakeUpEnable(TP6, 45); + esp_sleep_enable_touchpad_wakeup(); + #endif + + // Get CPU speed + int freq = getCpuFrequencyMhz(); + api->getLogger()->logDebug(GwLog::LOG,"CPU speed at boot: %i MHz", freq); + + // Settings for backlight + String backlightMode = api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString(); + api->getLogger()->logDebug(GwLog::DEBUG,"Backlight Mode is: %s", backlightMode.c_str()); + uint brightness = uint(api->getConfig()->getConfigItem(api->getConfig()->blBrightness,true)->asInt()); + String backlightColor = api->getConfig()->getConfigItem(api->getConfig()->blColor,true)->asString(); + if(String(backlightMode) == "On"){ + setBacklightLED(brightness, colorMapping(backlightColor)); + } + else if(String(backlightMode) == "Off"){ + setBacklightLED(0, COLOR_BLACK); // Backlight LEDs off (blue without britghness) + } + else if(String(backlightMode) == "Control by Key"){ + setBacklightLED(0, COLOR_BLUE); // Backlight LEDs off (blue without britghness) + } + + // Settings flash LED mode + String ledMode = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString(); + api->getLogger()->logDebug(GwLog::DEBUG,"LED Mode is: %s", ledMode.c_str()); + if(String(ledMode) == "Off"){ + setBlinkingLED(false); + } + + // Marker for init complete + // Used in OBP60Task() + initComplete = true; + + // Buzzer tone for initialization finish + setBuzzerPower(uint(api->getConfig()->getConfigItem(api->getConfig()->buzzerPower,true)->asInt())); + buzzer(TONE4, 500); + +} + +typedef struct { + int page0=0; + QueueHandle_t queue; + GwLog* logger = NULL; +// GwApi* api = NULL; + uint sensitivity = 100; + bool use_syspage = true; + } MyData; + +// Keyboard Task +void keyboardTask(void *param){ + MyData *data=(MyData *)param; + + int keycode = 0; + data->logger->logDebug(GwLog::LOG,"Start keyboard task"); + + // Loop for keyboard task + while (true){ + keycode = readKeypad(data->logger, data->sensitivity, data->use_syspage); + //send a key event + if(keycode != 0){ + xQueueSend(data->queue, &keycode, 0); + data->logger->logDebug(GwLog::LOG,"Send keycode: %d", keycode); + } + delay(20); // 50Hz update rate (20ms) + } + vTaskDelete(NULL); +} + +class BoatValueList{ + public: + static const int MAXVALUES=100; + //we create a list containing all our BoatValues + //this is the list we later use to let the api fill all the values + //additionally we put the necessary values into the paga data - see below + GwApi::BoatValue *allBoatValues[MAXVALUES]; + int numValues=0; + + bool addValueToList(GwApi::BoatValue *v){ + for (int i=0;i= MAXVALUES) return false; + allBoatValues[numValues]=v; + numValues++; + return true; + } + //helper to ensure that each BoatValue is only queried once + GwApi::BoatValue *findValueOrCreate(String name){ + for (int i=0;igetName() == name) { + return allBoatValues[i]; + } + } + GwApi::BoatValue *rt=new GwApi::BoatValue(name); + addValueToList(rt); + return rt; + } +}; + +//we want to have a list that has all our page definitions +//this way each page can easily be added here +//needs some minor tricks for the safe static initialization +typedef std::vector Pages; +//the page list class +class PageList{ + public: + Pages pages; + void add(PageDescription *p){ + pages.push_back(p); + } + PageDescription *find(String name){ + for (auto it=pages.begin();it != pages.end();it++){ + if ((*it)->pageName == name){ + return *it; + } + } + return NULL; + } +}; + +/** + * this function will add all the pages we know to the pagelist + * each page should have defined a registerXXXPage variable of type + * PageData that describes what it needs + */ +void registerAllPages(PageList &list){ + //the next line says that this variable is defined somewhere else + //in our case in a separate C++ source file + //this way this separate source file can be compiled by it's own + //and has no access to any of our data except the one that we + //give as a parameter to the page function + extern PageDescription registerPageSystem; + //we add the variable to our list + list.add(®isterPageSystem); + extern PageDescription registerPageOneValue; + list.add(®isterPageOneValue); + extern PageDescription registerPageTwoValues; + list.add(®isterPageTwoValues); + extern PageDescription registerPageThreeValues; + list.add(®isterPageThreeValues); + extern PageDescription registerPageSixValues; + list.add(®isterPageSixValues); + extern PageDescription registerPageFourValues; + list.add(®isterPageFourValues); + extern PageDescription registerPageFourValues2; + list.add(®isterPageFourValues2); + extern PageDescription registerPageWind; + list.add(®isterPageWind); + extern PageDescription registerPageWindRose; + list.add(®isterPageWindRose); + extern PageDescription registerPageWindRoseFlex; + list.add(®isterPageWindRoseFlex); // + extern PageDescription registerPageVoltage; + list.add(®isterPageVoltage); + extern PageDescription registerPageDST810; + list.add(®isterPageDST810); + extern PageDescription registerPageClock; + list.add(®isterPageClock); + extern PageDescription registerPageWhite; + list.add(®isterPageWhite); + extern PageDescription registerPageBME280; + list.add(®isterPageBME280); + extern PageDescription registerPageRudderPosition; + list.add(®isterPageRudderPosition); + extern PageDescription registerPageKeelPosition; + list.add(®isterPageKeelPosition); + extern PageDescription registerPageBattery; + list.add(®isterPageBattery); + extern PageDescription registerPageBattery2; + list.add(®isterPageBattery2); + extern PageDescription registerPageRollPitch; + list.add(®isterPageRollPitch); + extern PageDescription registerPageSolar; + list.add(®isterPageSolar); + extern PageDescription registerPageGenerator; + list.add(®isterPageGenerator); + extern PageDescription registerPageXTETrack; + list.add(®isterPageXTETrack); + extern PageDescription registerPageFluid; + list.add(®isterPageFluid); + extern PageDescription registerPageCompass; + list.add(®isterPageCompass); +} + +// Undervoltage detection for shutdown display +void underVoltageDetection(GwApi *api, CommonData &common){ + // Read settings + double voffset = (api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asString()).toFloat(); + double vslope = (api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asString()).toFloat(); + // Read supply voltage + #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200 + float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 + float minVoltage = 3.65; // Absolut minimum volatge for 3,7V LiPo accu + #else + float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 + float minVoltage = MIN_VOLTAGE; + #endif + double calVoltage = actVoltage * vslope + voffset; // Calibration + if(calVoltage < minVoltage){ + #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200 + // Switch off all power lines + setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off + setFlashLED(false); // Flash LED Off + buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms + // Shutdown EInk display + getdisplay().setFullWindow(); // Set full Refresh + //getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + getdisplay().fillScreen(common.bgcolor);// Clear screen + getdisplay().setTextColor(common.fgcolor); + getdisplay().setFont(&Ubuntu_Bold20pt7b); + getdisplay().setCursor(65, 150); + getdisplay().print("Undervoltage"); + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(65, 175); + getdisplay().print("Charge battery and restart system"); + getdisplay().nextPage(); // Partial update + getdisplay().powerOff(); // Display power off + setPortPin(OBP_POWER_EPD, false); // Power off ePaper display + setPortPin(OBP_POWER_SD, false); // Power off SD card + #else + // Switch off all power lines + setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off + setFlashLED(false); // Flash LED Off + buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms + setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off + // Shutdown EInk display + getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + getdisplay().fillScreen(common.bgcolor);// Clear screen + getdisplay().setTextColor(common.fgcolor); + getdisplay().setFont(&Ubuntu_Bold20pt7b); + getdisplay().setCursor(65, 150); + getdisplay().print("Undervoltage"); + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(65, 175); + getdisplay().print("To wake up repower system"); + getdisplay().nextPage(); // Partial update + getdisplay().powerOff(); // Display power off + #endif + // Stop system + while(true){ + esp_deep_sleep_start(); // Deep Sleep without weakup. Weakup only after power cycle (restart). + } + } +} + +// OBP60 Task +//#################################################################################### +void OBP60Task(GwApi *api){ +// vTaskDelete(NULL); +// return; + GwLog *logger=api->getLogger(); + GwConfigHandler *config=api->getConfig(); +#ifdef HARDWARE_V21 + startLedTask(api); +#endif + PageList allPages; + registerAllPages(allPages); + CommonData commonData; + commonData.logger=logger; + commonData.config=config; + +#ifdef HARDWARE_V21 + // Keyboard coordinates for page footer + initKeys(commonData); +#endif + + tN2kMsg N2kMsg; + + LOG_DEBUG(GwLog::LOG,"obp60task started"); + for (auto it=allPages.pages.begin();it != allPages.pages.end();it++){ + LOG_DEBUG(GwLog::LOG,"found registered page %s",(*it)->pageName.c_str()); + } + + // Init E-Ink display + String displaymode = api->getConfig()->getConfigItem(api->getConfig()->display,true)->asString(); + String displaycolor = api->getConfig()->getConfigItem(api->getConfig()->displaycolor,true)->asString(); + if (displaycolor == "Normal") { + commonData.fgcolor = GxEPD_BLACK; + commonData.bgcolor = GxEPD_WHITE; + } + else{ + commonData.fgcolor = GxEPD_WHITE; + commonData.bgcolor = GxEPD_BLACK; + } + String systemname = api->getConfig()->getConfigItem(api->getConfig()->systemName,true)->asString(); + String wifipass = api->getConfig()->getConfigItem(api->getConfig()->apPassword,true)->asString(); + bool refreshmode = api->getConfig()->getConfigItem(api->getConfig()->refresh,true)->asBoolean(); + String fastrefresh = api->getConfig()->getConfigItem(api->getConfig()->fastRefresh,true)->asString(); + uint fullrefreshtime = uint(api->getConfig()->getConfigItem(api->getConfig()->fullRefreshTime,true)->asInt()); + #ifdef BOARD_OBP40S3 + bool syspage_enabled = config->getBool(config->systemPage); + #endif + + #ifdef DISPLAY_GDEY042T81 + getdisplay().init(115200, true, 2, false); // Use this for Waveshare boards with "clever" reset circuit, 2ms reset pulse + #else + getdisplay().init(115200); // Init for normal displays + #endif + + getdisplay().setRotation(0); // Set display orientation (horizontal) + getdisplay().setFullWindow(); // Set full Refresh + getdisplay().firstPage(); // set first page + getdisplay().fillScreen(commonData.bgcolor); + getdisplay().setTextColor(commonData.fgcolor); + getdisplay().nextPage(); // Full Refresh + getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + getdisplay().fillScreen(commonData.bgcolor); + getdisplay().nextPage(); // Fast Refresh + getdisplay().nextPage(); // Fast Refresh + if(String(displaymode) == "Logo + QR Code" || String(displaymode) == "Logo"){ + getdisplay().fillScreen(commonData.bgcolor); + getdisplay().drawBitmap(0, 0, gImage_Logo_OBP_400x300_sw, getdisplay().width(), getdisplay().height(), commonData.fgcolor); // Draw start logo + getdisplay().nextPage(); // Fast Refresh + getdisplay().nextPage(); // Fast Refresh + delay(SHOW_TIME); // Logo show time + if(String(displaymode) == "Logo + QR Code"){ + getdisplay().fillScreen(commonData.bgcolor); + qrWiFi(systemname, wifipass, commonData.fgcolor, commonData.bgcolor); // Show QR code for WiFi connection + getdisplay().nextPage(); // Fast Refresh + getdisplay().nextPage(); // Fast Refresh + delay(SHOW_TIME); // QR code show time + } + getdisplay().fillScreen(commonData.bgcolor); + getdisplay().nextPage(); // Fast Refresh + getdisplay().nextPage(); // Fast Refresh + } + + // Init pages + int numPages=1; + PageStruct pages[MAX_PAGE_NUMBER]; + // Set start page + int pageNumber = int(api->getConfig()->getConfigItem(api->getConfig()->startPage,true)->asInt()) - 1; + + LOG_DEBUG(GwLog::LOG,"Checking wakeup..."); +#ifdef BOARD_OBP60S3 + if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TOUCHPAD) { + LOG_DEBUG(GwLog::LOG,"Wake up by touch pad %d",esp_sleep_get_touchpad_wakeup_status()); + pageNumber = getLastPage(); + } else { + LOG_DEBUG(GwLog::LOG,"Other wakeup reason"); + } +#endif +#ifdef BOARD_OBP40S3 + if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT0) { + LOG_DEBUG(GwLog::LOG,"Wake up by key"); + pageNumber = getLastPage(); + } else { + LOG_DEBUG(GwLog::LOG,"Other wakeup reason"); + } +#endif + LOG_DEBUG(GwLog::LOG,"...done"); + + int lastPage=pageNumber; + + BoatValueList boatValues; //all the boat values for the api query + //commonData.distanceformat=config->getString(xxx); + //add all necessary data to common data + + //fill the page data from config + numPages=config->getInt(config->visiblePages,1); + if (numPages < 1) numPages=1; + if (numPages >= MAX_PAGE_NUMBER) numPages=MAX_PAGE_NUMBER; + LOG_DEBUG(GwLog::LOG,"Number of pages %d",numPages); + String configPrefix="page"; + for (int i=0;i< numPages;i++){ + String prefix=configPrefix+String(i+1); //e.g. page1 + String configName=prefix+String("type"); + LOG_DEBUG(GwLog::DEBUG,"asking for page config %s",configName.c_str()); + String pageType=config->getString(configName,""); + PageDescription *description=allPages.find(pageType); + if (description == NULL){ + LOG_DEBUG(GwLog::ERROR,"page description for %s not found",pageType.c_str()); + continue; + } + pages[i].description=description; + pages[i].page=description->creator(commonData); + pages[i].parameters.pageName=pageType; + pages[i].parameters.pageNumber = i + 1; + LOG_DEBUG(GwLog::DEBUG,"found page %s for number %d",pageType.c_str(),i); + //fill in all the user defined parameters + for (int uid=0;uiduserParam;uid++){ + String cfgName=prefix+String("value")+String(uid+1); + GwApi::BoatValue *value=boatValues.findValueOrCreate(config->getString(cfgName,"")); + LOG_DEBUG(GwLog::DEBUG,"add user input cfg=%s,value=%s for page %d", + cfgName.c_str(), + value->getName().c_str(), + i + ); + pages[i].parameters.values.push_back(value); + } + //now add the predefined values + for (auto it=description->fixedParam.begin();it != description->fixedParam.end();it++){ + GwApi::BoatValue *value=boatValues.findValueOrCreate(*it); + LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i); + pages[i].parameters.values.push_back(value); + } + } + // add out of band system page (always available) + Page *syspage = allPages.pages[0]->creator(commonData); + + // Display screenshot handler for HTTP request + // http://192.168.15.1/api/user/OBP60Task/screenshot + api->registerRequestHandler("screenshot", [api, &pageNumber, pages](AsyncWebServerRequest *request) { + doImageRequest(api, &pageNumber, pages, request); + }); + + //now we have prepared the page data + //we start a separate task that will fetch our keys... + MyData allParameters; + allParameters.logger=api->getLogger(); + allParameters.page0=3; + allParameters.queue=xQueueCreate(10,sizeof(int)); + allParameters.sensitivity= api->getConfig()->getInt(GwConfigDefinitions::tSensitivity); + #ifdef BOARD_OBP40S3 + allParameters.use_syspage = syspage_enabled; + #endif + xTaskCreate(keyboardTask,"keyboard",2000,&allParameters,configMAX_PRIORITIES-1,NULL); + SharedData *shared=new SharedData(api); + createSensorTask(shared); + + // Task Loop + //#################################################################################### + + // 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(); + 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()); + commonData.backlight.brightness = 2.55 * uint(config->getConfigItem(config->blBrightness,true)->asInt()); + commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString(); + + bool uvoltage = api->getConfig()->getConfigItem(api->getConfig()->underVoltage,true)->asBoolean(); + 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 + GwApi::BoatValue *date = boatValues.findValueOrCreate("GPSD"); // Load GpsDate + GwApi::BoatValue *time = boatValues.findValueOrCreate("GPST"); // Load GpsTime + GwApi::BoatValue *lat = boatValues.findValueOrCreate("LAT"); // Load GpsLatitude + GwApi::BoatValue *lon = boatValues.findValueOrCreate("LON"); // Load GpsLongitude + GwApi::BoatValue *hdop = boatValues.findValueOrCreate("HDOP"); // Load GpsHDOP + + LOG_DEBUG(GwLog::LOG,"obp60task: start mainloop"); + + commonData.time = boatValues.findValueOrCreate("GPST"); // Load GpsTime + commonData.date = boatValues.findValueOrCreate("GPSD"); // Load GpsTime + bool delayedDisplayUpdate = false; // If select a new pages then make a delayed full display update + bool cpuspeedsetted = false; // Marker for change CPU speed + long firststart = millis(); // First start + long starttime0 = millis(); // Mainloop + long starttime1 = millis(); // Full display refresh for the first 5 min (more often as normal) + long starttime2 = millis(); // Full display refresh after 5 min + long starttime3 = millis(); // Display update all 1s + long starttime4 = millis(); // Delayed display update after 4s when select a new page + long starttime5 = millis(); // Calculate sunrise and sunset all 1s + + pages[pageNumber].page->setupKeys(); // Initialize keys for first page + + // Main loop runs with 100ms + //#################################################################################### + + bool systemPage = false; + Page *currentPage; + while (true){ + delay(100); // Delay 100ms (loop time) + bool keypressed = false; + + // Undervoltage detection + if(uvoltage == true){ + underVoltageDetection(api, commonData); + } + + // Set CPU speed after boot after 1min + if(millis() > firststart + (1 * 60 * 1000) && cpuspeedsetted == false){ + if(String(cpuspeed) == "80"){ + setCpuFrequencyMhz(80); + } + if(String(cpuspeed) == "160"){ + setCpuFrequencyMhz(160); + } + if(String(cpuspeed) == "240"){ + setCpuFrequencyMhz(240); + } + int freq = getCpuFrequencyMhz(); + api->getLogger()->logDebug(GwLog::LOG,"CPU speed: %i MHz", freq); + cpuspeedsetted = true; + } + + if(millis() > starttime0 + 100){ + starttime0 = millis(); + commonData.data=shared->getSensorData(); + commonData.data.actpage = pageNumber + 1; + commonData.data.maxpage = numPages; + + // If GPS fix then LED off (HDOP) + if(String(gpsFix) == "GPS Fix Lost" && hdop->value <= hdopAccuracy && hdop->valid == true){ + setFlashLED(false); + } + // If missing GPS fix then LED on + if((String(gpsFix) == "GPS Fix Lost" && hdop->value > hdopAccuracy && hdop->valid == true) || (String(gpsFix) == "GPS Fix Lost" && hdop->valid == false)){ + setFlashLED(true); + } + + // Check the keyboard message + int keyboardMessage=0; + while (xQueueReceive(allParameters.queue,&keyboardMessage,0)){ + LOG_DEBUG(GwLog::LOG,"new key from keyboard %d",keyboardMessage); + keypressed = true; + + if (keyboardMessage == 12 and !systemPage) { + LOG_DEBUG(GwLog::LOG, "Calling system page"); + systemPage = true; // System page is out of band + syspage->setupKeys(); + keyboardMessage = 0; + } + else { + currentPage = pages[pageNumber].page; + if (systemPage && keyboardMessage == 1) { + // exit system mode with exit key number 1 + systemPage = false; + currentPage->setupKeys(); + keyboardMessage = 0; + } + } + if (systemPage) { + keyboardMessage = syspage->handleKey(keyboardMessage); + } else if (currentPage) { + keyboardMessage = currentPage->handleKey(keyboardMessage); + } + if (keyboardMessage > 0) // not handled by page + { + // Decoding all key codes + // #6 Backlight on if key controled + if (commonData.backlight.mode == BacklightMode::KEY) { + // if(String(backlight) == "Control by Key"){ + if(keyboardMessage == 6){ + LOG_DEBUG(GwLog::LOG,"Toggle Backlight LED"); + toggleBacklightLED(commonData.backlight.brightness, commonData.backlight.color); + } + } + #ifdef BOARD_OBP40S3 + // #3 Deep sleep mode for OBP40 + if ((keyboardMessage == 3) and !syspage_enabled){ + deepSleep(commonData); + } + #endif + // #9 Swipe right or #4 key right + if ((keyboardMessage == 9) or (keyboardMessage == 4)) + { + pageNumber++; + if (pageNumber >= numPages){ + pageNumber = 0; + } + commonData.data.actpage = pageNumber + 1; + commonData.data.maxpage = numPages; + } + // #10 Swipe left or #3 key left + if ((keyboardMessage == 10) or (keyboardMessage == 3)) + { + pageNumber--; + if (pageNumber < 0){ + pageNumber = numPages - 1; + } + commonData.data.actpage = pageNumber + 1; + commonData.data.maxpage = numPages; + } + + // #9 or #10 Refresh display after a new page after 4s waiting time and if refresh is disabled + if(refreshmode == true && (keyboardMessage == 9 || keyboardMessage == 10)){ + starttime4 = millis(); + starttime2 = millis(); // Reset the timer for full display update + delayedDisplayUpdate = true; + } + } + LOG_DEBUG(GwLog::LOG,"set pagenumber to %d",pageNumber); + } + + // Calculate sunrise, sunset and backlight control with sun status all 1s + if(millis() > starttime5 + 1000){ + starttime5 = millis(); + if(time->valid == true && date->valid == true && lat->valid == true && lon->valid == true){ + // Provide sundata to all pages + 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"){ + if(commonData.sundata.sunDown == true){ + setBacklightLED(commonData.backlight.brightness, commonData.backlight.color); + } + else{ + 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); + } + } + + // Full display update afer a new selected page and 4s wait time + if(millis() > starttime4 + 4000 && delayedDisplayUpdate == true){ + starttime1 = millis(); + starttime2 = millis(); + getdisplay().setFullWindow(); // Set full update + getdisplay().nextPage(); + if(fastrefresh == "false"){ + getdisplay().fillScreen(commonData.fgcolor); // Clear display + getdisplay().nextPage(); // Full update + getdisplay().fillScreen(commonData.bgcolor); // Clear display + getdisplay().nextPage(); // Full update + } + delayedDisplayUpdate = false; + } + + // Subtask E-Ink full refresh all 1 min for the first 5 min after power on or restart + // This needs for a better display contrast after power on in cold or warm environments + if(millis() < firststart + (5 * 60 * 1000) && millis() > starttime1 + (60 * 1000)){ + starttime1 = millis(); + starttime2 = millis(); + LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh first 5 min"); + getdisplay().setFullWindow(); // Set full update + getdisplay().nextPage(); + if(fastrefresh == "false"){ + getdisplay().fillScreen(commonData.fgcolor); // Clear display + getdisplay().nextPage(); // Full update + getdisplay().fillScreen(commonData.bgcolor); // Clear display + getdisplay().nextPage(); // Full update + } + } + + // Subtask E-Ink full refresh + if(millis() > starttime2 + fullrefreshtime * 60 * 1000){ + starttime2 = millis(); + LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh"); + getdisplay().setFullWindow(); // Set full update + getdisplay().nextPage(); + if(fastrefresh == "false"){ + getdisplay().fillScreen(commonData.fgcolor); // Clear display + getdisplay().nextPage(); // Full update + getdisplay().fillScreen(commonData.bgcolor); // Clear display + getdisplay().nextPage(); // Full update + } + } + + // Refresh display data, default all 1s + currentPage = pages[pageNumber].page; + int pagetime = 1000; + if ((lastPage == pageNumber) and (!keypressed)) { + // same page we use page defined time + pagetime = currentPage->refreshtime; + } + if(millis() > starttime3 + pagetime){ + LOG_DEBUG(GwLog::DEBUG,"Page with refreshtime=%d", pagetime); + starttime3 = millis(); + + //refresh data from api + api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues); + api->getStatus(commonData.status); + + // Clear display + // getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor); + getdisplay().fillScreen(commonData.bgcolor); // Clear display + + // Show header if enabled + if (pages[pageNumber].description && pages[pageNumber].description->header or systemPage){ + // build header using commonData + displayHeader(commonData, date, time, hdop); // Show page header + } + + // Call the particular page + if (systemPage) { + displayFooter(commonData); + PageData sysparams; // empty + syspage->displayPage(sysparams); + } + else { + if (currentPage == NULL){ + LOG_DEBUG(GwLog::ERROR,"page number %d not found", pageNumber); + // Error handling for missing page + getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + getdisplay().fillScreen(commonData.bgcolor); // Clear display + getdisplay().drawXBitmap(200 - unknown_width / 2, 150 - unknown_height / 2, unknown_bits, unknown_width, unknown_height, commonData.fgcolor); + getdisplay().setCursor(140, 250); + getdisplay().setFont(&Atari16px); + getdisplay().print("Here be dragons!"); + getdisplay().nextPage(); // Partial update (fast) + } + else{ + if (lastPage != pageNumber){ + if (hasFRAM) fram.write(FRAM_PAGE_NO, pageNumber); // remember page for device restart + currentPage->setupKeys(); + currentPage->displayNew(pages[pageNumber].parameters); + lastPage=pageNumber; + } + //call the page code + LOG_DEBUG(GwLog::DEBUG,"calling page %d",pageNumber); + // Show footer if enabled (together with header) + if (pages[pageNumber].description && pages[pageNumber].description->header){ + displayFooter(commonData); + } + currentPage->displayPage(pages[pageNumber].parameters); + } + + } + } // refresh display all 1s + } + } + vTaskDelete(NULL); + +} + +#endif