diff --git a/lib/obp60task/Graphics.cpp b/lib/obp60task/Graphics.cpp new file mode 100644 index 0000000..ae0dcbd --- /dev/null +++ b/lib/obp60task/Graphics.cpp @@ -0,0 +1,25 @@ +/* +Generic graphics functions + +*/ +#include +#include "Graphics.h" + +Point rotatePoint(const Point& origin, const Point& p, double angle) { + // rotate poind around origin by degrees + Point rotated; + double phi = angle * M_PI / 180.0; + double dx = p.x - origin.x; + double dy = p.y - origin.y; + rotated.x = origin.x + cos(phi) * dx - sin(phi) * dy; + rotated.y = origin.y + sin(phi) * dx + cos(phi) * dy; + return rotated; +} + +std::vector rotatePoints(const Point& origin, const std::vector& pts, double angle) { + std::vector rotatedPoints; + for (const auto& p : pts) { + rotatedPoints.push_back(rotatePoint(origin, p, angle)); + } + return rotatedPoints; +} diff --git a/lib/obp60task/Graphics.h b/lib/obp60task/Graphics.h new file mode 100644 index 0000000..b55971b --- /dev/null +++ b/lib/obp60task/Graphics.h @@ -0,0 +1,17 @@ +#pragma once +#include + +struct Point { + double x; + double y; +}; + +struct Rect { + double x; + double y; + double w; + double h; +}; + +Point rotatePoint(const Point& origin, const Point& p, double angle); +std::vector rotatePoints(const Point& origin, const std::vector& pts, double angle); diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 4beec1d..88d8325 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -362,30 +362,20 @@ String xdrDelete(String input){ return input; } -Point rotatePoint(const Point& origin, const Point& p, double angle) { - // rotate poind around origin by degrees - Point rotated; - double phi = angle * M_PI / 180.0; - double dx = p.x - origin.x; - double dy = p.y - origin.y; - rotated.x = origin.x + cos(phi) * dx - sin(phi) * dy; - rotated.y = origin.y + sin(phi) * dx + cos(phi) * dy; - return rotated; -} - -std::vector rotatePoints(const Point& origin, const std::vector& pts, double angle) { - std::vector rotatedPoints; - for (const auto& p : pts) { - rotatedPoints.push_back(rotatePoint(origin, p, angle)); - } - return rotatedPoints; -} - void fillPoly4(const std::vector& p4, uint16_t color) { getdisplay().fillTriangle(p4[0].x, p4[0].y, p4[1].x, p4[1].y, p4[2].x, p4[2].y, color); getdisplay().fillTriangle(p4[0].x, p4[0].y, p4[2].x, p4[2].y, p4[3].x, p4[3].y, color); } +void drawPoly(const std::vector& points, uint16_t color) { + size_t polysize = points.size(); + for (size_t i = 0; i < polysize - 1; i++) { + getdisplay().drawLine(points[i].x, points[i].y, points[i+1].x, points[i+1].y, color); + } + // close path + getdisplay().drawLine(points[polysize-1].x, points[polysize-1].y, points[0].x, points[0].y, color); +} + // Split string into words, whitespace separated std::vector split(const String &s) { std::vector words; @@ -447,6 +437,24 @@ void drawTextRalign(int16_t x, int16_t y, String text) { getdisplay().print(text); } +// Draw text inside box, normal or inverted +void drawTextBoxed(Rect box, String text, uint16_t fg, uint16_t bg, bool inverted, bool border) { + if (inverted) { + getdisplay().fillRect(box.x, box.y, box.w, box.h, fg); + getdisplay().setTextColor(bg); + } else { + if (border) { + getdisplay().fillRect(box.x + 1, box.y + 1, box.w - 2, box.h - 2, bg); + getdisplay().drawRect(box.x, box.y, box.w, box.h, fg); + } + getdisplay().setTextColor(fg); + } + uint16_t border_offset = box.h / 4; // 25% of box height + getdisplay().setCursor(box.x + border_offset, box.y + box.h - border_offset); + getdisplay().print(text); + getdisplay().setTextColor(fg); +} + // Show a triangle for trend direction high (x, y is the left edge) void displayTrendHigh(int16_t x, int16_t y, uint16_t size, uint16_t color){ getdisplay().fillTriangle(x, y, x+size*2, y, x+size, y-size*2, color); diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h index b773827..d7727ed 100644 --- a/lib/obp60task/OBP60Extensions.h +++ b/lib/obp60task/OBP60Extensions.h @@ -4,6 +4,7 @@ #include #include "OBP60Hardware.h" #include "LedSpiTask.h" +#include "Graphics.h" #include // E-paper lib V2 #include // I2C FRAM @@ -73,13 +74,8 @@ GxEPD2_BW & getdisplay(); #define PAGE_UPDATE 1 // page wants display to update #define PAGE_HIBERNATE 2 // page wants displey to hibernate -struct Point { - double x; - double y; -}; -Point rotatePoint(const Point& origin, const Point& p, double angle); -std::vector rotatePoints(const Point& origin, const std::vector& pts, double angle); void fillPoly4(const std::vector& p4, uint16_t color); +void drawPoly(const std::vector& points, uint16_t color); void deepSleep(CommonData &common); @@ -108,6 +104,7 @@ String xdrDelete(String input); // Delete xdr prefix from string void drawTextCenter(int16_t cx, int16_t cy, String text); void drawTextRalign(int16_t x, int16_t y, String text); +void drawTextBoxed(Rect box, String text, uint16_t fg, uint16_t bg, bool inverted, bool border); void displayTrendHigh(int16_t x, int16_t y, uint16_t size, uint16_t color); void displayTrendLow(int16_t x, int16_t y, uint16_t size, uint16_t color); diff --git a/lib/obp60task/PageSkyView.cpp b/lib/obp60task/PageSkyView.cpp new file mode 100644 index 0000000..66a0151 --- /dev/null +++ b/lib/obp60task/PageSkyView.cpp @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 + +#include "Pagedata.h" +#include "OBP60Extensions.h" + +#include +#include // for vector sorting + +/* + * SkyView / Satellites + */ + +class PageSkyView : public Page +{ +private: + String flashLED; + GwBoatData *bd; + +public: + PageSkyView(CommonData &common) + { + commonData = &common; + + // task name access is for example purpose only + TaskHandle_t currentTaskHandle = xTaskGetCurrentTaskHandle(); + const char* taskName = pcTaskGetName(currentTaskHandle); + common.logger->logDebug(GwLog::LOG, "Instantiate PageSkyView in task '%s'", taskName); + + flashLED = common.config->getString(common.config->flashLED); + } + + int handleKey(int key) { + // return 0 to mark the key handled completely + // return the key to allow further action + if (key == 11) { + commonData->keylock = !commonData->keylock; + return 0; + } + return key; + } + + void displayNew(PageData &pageData) { +#ifdef BOARD_OBP60S3 + // Clear optical warning + if (flashLED == "Limit Violation") { + setBlinkingLED(false); + setFlashLED(false); + } +#endif + bd = pageData.api->getBoatData(); + }; + + // Comparator function to sort by SNR + static bool compareBySNR(const GwSatInfo& a, const GwSatInfo& b) { + return a.SNR > b.SNR; // Sort in descending order + } + + int displayPage(PageData &pageData) { + GwLog *logger = commonData->logger; + + std::vector sats; + int nSat = bd->SatInfo->getNumSats(); + + logger->logDebug(GwLog::LOG, "Drawing at PageSkyView, %d satellites", nSat); + + for (int i = 0; i < nSat; i++) { + sats.push_back(*bd->SatInfo->getAt(i)); + } + std::sort(sats.begin(), sats.end(), compareBySNR); + + // Draw page + //*********************************************************** + + // Set display in partial refresh mode + getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + + // current position + getdisplay().setFont(&Ubuntu_Bold8pt8b); + + // sky view + Point c = {130, 148}; + uint16_t r = 125; + uint16_t r1 = r / 2; + + getdisplay().fillCircle(c.x, c.y, r, commonData->bgcolor); + getdisplay().drawCircle(c.x, c.y, r + 1, commonData->fgcolor); + getdisplay().drawCircle(c.x, c.y, r + 2, commonData->fgcolor); + getdisplay().drawCircle(c.x, c.y, r1, commonData->fgcolor); + + // separation lines + getdisplay().drawLine(c.x - r, c.y, c.x + r, c.y, commonData->fgcolor); + getdisplay().drawLine(c.x, c.y - r, c.x, c.y + r, commonData->fgcolor); + Point p = {c.x, c.y - r}; + Point p1, p2; + p1 = rotatePoint(c, p, 45); + p2 = rotatePoint(c, p, 45 + 180); + getdisplay().drawLine(p1.x, p1.y, p2.x, p2.y, commonData->fgcolor); + p1 = rotatePoint(c, p, -45); + p2 = rotatePoint(c, p, -45 + 180); + getdisplay().drawLine(p1.x, p1.y, p2.x, p2.y, commonData->fgcolor); + + // directions + + int16_t x1, y1; + uint16_t w, h; + getdisplay().setFont(&Ubuntu_Bold12pt8b); + + getdisplay().getTextBounds("N", 0, 150, &x1, &y1, &w, &h); + getdisplay().setCursor(c.x - w / 2, c.y - r + h + 2); + getdisplay().print("N"); + + getdisplay().getTextBounds("S", 0, 150, &x1, &y1, &w, &h); + getdisplay().setCursor(c.x - w / 2, c.y + r - 2); + getdisplay().print("S"); + + getdisplay().getTextBounds("E", 0, 150, &x1, &y1, &w, &h); + getdisplay().setCursor(c.x + r - w - 2, c.y + h / 2); + getdisplay().print("E"); + + getdisplay().getTextBounds("W", 0, 150, &x1, &y1, &w, &h); + getdisplay().setCursor(c.x - r + 2 , c.y + h / 2); + getdisplay().print("W"); + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + + // show satellites in "map" + for (int i = 0; i < nSat; i++) { + float arad = sats[i].Azimut * M_PI / 180.0; + float erad = sats[i].Elevation * M_PI / 180.0; + uint16_t x = c.x + sin(arad) * erad * r; + uint16_t y = c.y + cos(arad) * erad * r; + getdisplay().drawRect(x-4, y-4, 8, 8, commonData->fgcolor); + } + + // Signal / Noise bars + getdisplay().setCursor(325, 34); + getdisplay().print("SNR"); + getdisplay().drawRect(270, 20, 125, 257, commonData->fgcolor); + int maxsat = std::min(nSat, 12); + for (int i = 0; i < maxsat; i++) { + uint16_t y = 29 + (i + 1) * 20; + getdisplay().setCursor(276, y); + char buffer[3]; + snprintf(buffer, 3, "%02d", static_cast(sats[i].PRN)); + getdisplay().print(String(buffer)); + getdisplay().drawRect(305, y-12, 85, 14, commonData->fgcolor); + getdisplay().setCursor(315, y); + // TODO SNR as number or as bar via mode key? + if (sats[i].SNR <= 100) { + // getdisplay().print(sats[i].SNR); + getdisplay().fillRect(307, y-10, int(81 * sats[i].SNR / 100.0), 10, commonData->fgcolor); + } else { + getdisplay().print("n/a"); + } + } + + return PAGE_UPDATE; + }; +}; + +static Page* createPage(CommonData &common){ + return new PageSkyView(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 registerPageSkyView( + "SkyView", // Page name + createPage, // Action + 0, // Number of bus values depends on selection in Web configuration + true // Show display header on/off +); + +#endif diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index 58a0a57..0e70d4a 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -12,6 +12,7 @@ typedef std::vector ValueList; typedef struct{ + GwApi *api; String pageName; uint8_t pageNumber; // page number in sequence of visible pages //the values will always contain the user defined values first @@ -99,20 +100,20 @@ typedef struct{ } AlarmData; typedef struct{ - GwApi::Status status; - GwLog *logger=NULL; - GwConfigHandler *config=NULL; - SensorData data; - SunData sundata; - TouchKeyData keydata[6]; - BacklightData backlight; - AlarmData alarm; - GwApi::BoatValue *time=NULL; - GwApi::BoatValue *date=NULL; - uint16_t fgcolor; - uint16_t bgcolor; - bool keylock = false; - String powermode; + GwApi::Status status; + GwLog *logger = nullptr; + GwConfigHandler *config = nullptr; + SensorData data; + SunData sundata; + TouchKeyData keydata[6]; + BacklightData backlight; + AlarmData alarm; + GwApi::BoatValue *time = nullptr; + GwApi::BoatValue *date = nullptr; + uint16_t fgcolor; + uint16_t bgcolor; + bool keylock = false; + String powermode; } CommonData; //a base class that all pages must inherit from @@ -182,9 +183,9 @@ class PageDescription{ class PageStruct{ public: - Page *page=NULL; + Page *page = nullptr; PageData parameters; - PageDescription *description=NULL; + PageDescription *description = nullptr; }; // Standard format functions without overhead diff --git a/lib/obp60task/config.json b/lib/obp60task/config.json index c1c2d06..dffdeff 100644 --- a/lib/obp60task/config.json +++ b/lib/obp60task/config.json @@ -1333,6 +1333,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -1608,6 +1609,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -1880,6 +1882,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -2149,6 +2152,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -2415,6 +2419,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -2678,6 +2683,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -2938,6 +2944,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -3195,6 +3202,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -3449,6 +3457,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -3700,6 +3709,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index c4796dd..1eeaa2f 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -1356,6 +1356,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -1657,6 +1658,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -1955,6 +1957,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -2250,6 +2253,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -2542,6 +2546,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -2831,6 +2836,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -3117,6 +3123,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -3400,6 +3407,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -3680,6 +3688,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", @@ -3957,6 +3966,7 @@ "RollPitch", "RudderPosition", "SixValues", + "SkyView", "Solar", "ThreeValues", "TwoValues", diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 62e2332..8415c1d 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -122,8 +122,8 @@ void OBP60Init(GwApi *api){ typedef struct { int page0=0; QueueHandle_t queue; - GwLog* logger = NULL; -// GwApi* api = NULL; + GwLog* logger = nullptr; +// GwApi* api = nullptr; uint sensitivity = 100; bool use_syspage = true; } MyData; @@ -266,6 +266,8 @@ void registerAllPages(PageList &list){ list.add(®isterPageXTETrack); extern PageDescription registerPageFluid; list.add(®isterPageFluid); + extern PageDescription registerPageSkyView; + list.add(®isterPageSkyView); } // Undervoltage detection for shutdown display @@ -309,7 +311,6 @@ void underVoltageError(CommonData &common) { getdisplay().nextPage(); // Partial update getdisplay().powerOff(); // Display power off #endif - // Stop system while (true) { esp_deep_sleep_start(); // Deep Sleep without wakeup. Wakeup only after power cycle (restart). } @@ -324,7 +325,6 @@ inline bool underVoltageDetection(float voffset, float vslope) { float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 float minVoltage = MIN_VOLTAGE; #endif - // TODO Why double here? float calVoltage = actVoltage * vslope + voffset; // Calibration return (calVoltage < minVoltage); } @@ -665,6 +665,7 @@ void OBP60Task(GwApi *api){ pages[i].page=description->creator(commonData); pages[i].parameters.pageName=pageType; pages[i].parameters.pageNumber = i + 1; + pages[i].parameters.api = api; 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++){ @@ -787,6 +788,7 @@ void OBP60Task(GwApi *api){ // Undervoltage detection if (uvoltage == true) { if (underVoltageDetection(voffset, vslope)) { + LOG_DEBUG(GwLog::ERROR, "Undervoltage detected, shutting down!"); underVoltageError(commonData); } } @@ -1032,6 +1034,7 @@ void OBP60Task(GwApi *api){ if (systemPage) { displayFooter(commonData); PageData sysparams; // empty + sysparams.api = api; if (systemPageNew) { syspage->displayNew(sysparams); systemPageNew = false;