From a311f2f16434adc1f3a6e44946b2989ffa41b91b Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Sat, 26 Jul 2025 09:31:38 +0200 Subject: [PATCH 1/5] Preparation for new page "Anchor" --- lib/obp60task/PageAnchor.cpp | 100 ++++++++++++++++++++++++++++++++ lib/obp60task/config.json | 10 ++++ lib/obp60task/config_obp40.json | 10 ++++ lib/obp60task/gen_set.py | 1 + lib/obp60task/obp60task.cpp | 2 + 5 files changed, 123 insertions(+) create mode 100644 lib/obp60task/PageAnchor.cpp diff --git a/lib/obp60task/PageAnchor.cpp b/lib/obp60task/PageAnchor.cpp new file mode 100644 index 0000000..06b4ff5 --- /dev/null +++ b/lib/obp60task/PageAnchor.cpp @@ -0,0 +1,100 @@ +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 + +#include "Pagedata.h" +#include "OBP60Extensions.h" + +/* + Anchor overview with additional associated data + This page is in experimental stage so be warned! + + DBS - Water depth + HDT - Boat heading + TWS - Wind strength + TWD - Wind direction + + This is the fist page to contain a configuration page with + data entry option. + Also it will make use of the new alarm function. + +*/ + +#define anchor_width 16 +#define anchor_height 16 +static unsigned char anchor_bits[] = { + 0x80, 0x01, 0x40, 0x02, 0x40, 0x02, 0x80, 0x01, 0xf0, 0x0f, 0x80, 0x01, + 0x80, 0x01, 0x88, 0x11, 0x8c, 0x31, 0x8e, 0x71, 0x84, 0x21, 0x86, 0x61, + 0x86, 0x61, 0xfc, 0x3f, 0xf8, 0x1f, 0x80, 0x01 }; + +class PageAnchor : public Page +{ + bool simulation = false; + bool holdvalues = false; + String flashLED; + String backlightMode; + + public: + PageAnchor(CommonData &common){ + commonData = &common; + common.logger->logDebug(GwLog::LOG,"Instantiate PageAnchor"); + + // preload configuration data + simulation = common.config->getBool(common.config->useSimuData); + holdvalues = common.config->getBool(common.config->holdvalues); + flashLED = common.config->getString(common.config->flashLED); + backlightMode = common.config->getString(common.config->backlight); + } + + virtual int handleKey(int key){ + // Code for keylock + if (key == 11) { + commonData->keylock = !commonData->keylock; + return 0; // Commit the key + } + return key; + } + + void displayNew(PageData &pageData){ + } + + int displayPage(PageData &pageData){ + GwConfigHandler *config = commonData->config; + GwLog *logger = commonData->logger; + + // Logging boat values + LOG_DEBUG(GwLog::LOG,"Drawing at PageAnchor"); + + // Set display in partial refresh mode + getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + + uint16_t cx = 200; // center = anchor position + uint16_t cy = 150; + + // draw anchor symbol (as bitmap) + getdisplay().drawXBitmap(cx - anchor_width / 2, cy - anchor_height / 2, + anchor_bits, anchor_width, anchor_height, commonData->fgcolor); + + + getdisplay().setTextColor(commonData->fgcolor); + + return PAGE_UPDATE; + }; +}; + +static Page *createPage(CommonData &common){ + return new PageAnchor(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 registerPageAnchor( + "Anchor", // Page name + createPage, // Action + 0, // Number of bus values depends on selection in Web configuration + {"DBS", "HDT", "TWS", "TWD"}, // Names of bus values undepends on selection in Web configuration (refer GwBoatData.h) + true // Show display header on/off +); + +#endif diff --git a/lib/obp60task/config.json b/lib/obp60task/config.json index eed7ba2..820ce59 100644 --- a/lib/obp60task/config.json +++ b/lib/obp60task/config.json @@ -1303,6 +1303,7 @@ "default": "Voltage", "description": "Type of page for page 1", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -1584,6 +1585,7 @@ "default": "WindRose", "description": "Type of page for page 2", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -1862,6 +1864,7 @@ "default": "OneValue", "description": "Type of page for page 3", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2137,6 +2140,7 @@ "default": "TwoValues", "description": "Type of page for page 4", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2409,6 +2413,7 @@ "default": "ThreeValues", "description": "Type of page for page 5", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2678,6 +2683,7 @@ "default": "FourValues", "description": "Type of page for page 6", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2944,6 +2950,7 @@ "default": "FourValues2", "description": "Type of page for page 7", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -3207,6 +3214,7 @@ "default": "Clock", "description": "Type of page for page 8", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -3467,6 +3475,7 @@ "default": "RollPitch", "description": "Type of page for page 9", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -3724,6 +3733,7 @@ "default": "Battery2", "description": "Type of page for page 10", "list": [ + "Anchor", "BME280", "Battery", "Battery2", diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index 92cb0f6..2e6c55b 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -1326,6 +1326,7 @@ "default": "Clock", "description": "Type of page for page 1", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -1607,6 +1608,7 @@ "default": "Wind", "description": "Type of page for page 2", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -1885,6 +1887,7 @@ "default": "OneValue", "description": "Type of page for page 3", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2160,6 +2163,7 @@ "default": "TwoValues", "description": "Type of page for page 4", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2432,6 +2436,7 @@ "default": "ThreeValues", "description": "Type of page for page 5", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2701,6 +2706,7 @@ "default": "FourValues", "description": "Type of page for page 6", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2967,6 +2973,7 @@ "default": "FourValues2", "description": "Type of page for page 7", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -3230,6 +3237,7 @@ "default": "Fluid", "description": "Type of page for page 8", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -3490,6 +3498,7 @@ "default": "RollPitch", "description": "Type of page for page 9", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -3747,6 +3756,7 @@ "default": "Battery2", "description": "Type of page for page 10", "list": [ + "Anchor", "BME280", "Battery", "Battery2", diff --git a/lib/obp60task/gen_set.py b/lib/obp60task/gen_set.py index fd3a3e0..dcebb2c 100755 --- a/lib/obp60task/gen_set.py +++ b/lib/obp60task/gen_set.py @@ -34,6 +34,7 @@ no_of_fields_per_page = { "WindPlot": 0, "WindRose": 0, "WindRoseFlex": 6, + "Anchor", 0 } # No changes needed beyond this point diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 11b986d..cd247e1 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -313,6 +313,8 @@ void registerAllPages(PageList &list){ list.add(®isterPageXTETrack); extern PageDescription registerPageFluid; list.add(®isterPageFluid); + extern PageDescription registerPageAnchor; + list.add(®isterPageAnchor); } // Undervoltage detection for shutdown display From c7a580612e0359ed006f1156c4681e420bc5fb88 Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Mon, 28 Jul 2025 20:44:34 +0200 Subject: [PATCH 2/5] More work on page anchor --- lib/obp60task/PageAnchor.cpp | 207 +++++++++++++++++++++++++++----- lib/obp60task/config.json | 14 +++ lib/obp60task/config_obp40.json | 14 +++ lib/obp60task/obp60task.cpp | 2 +- 4 files changed, 207 insertions(+), 30 deletions(-) diff --git a/lib/obp60task/PageAnchor.cpp b/lib/obp60task/PageAnchor.cpp index 06b4ff5..1f43542 100644 --- a/lib/obp60task/PageAnchor.cpp +++ b/lib/obp60task/PageAnchor.cpp @@ -6,16 +6,37 @@ /* Anchor overview with additional associated data This page is in experimental stage so be warned! + North is up. + Boatdata used DBS - Water depth HDT - Boat heading - TWS - Wind strength - TWD - Wind direction + AWS - Wind strength; Boat not moving so we assume AWS=TWS and AWD=TWD + AWD - Wind direction + LAT/LON - Boat position, current + HDOP - Position error This is the fist page to contain a configuration page with data entry option. Also it will make use of the new alarm function. + Data + Anchor position lat/lon + Depth at anchor position + Chain length used + Boat position current + Depth at boat position + Boat heading + Wind direction + Wind strength + Alarm j/n + Alarm radius + GPS position error + Timestamp while dropping anchor + + Drop / raise function in device OBP40 has to be done inside + config mode because of limited number of buttons. + */ #define anchor_width 16 @@ -27,54 +48,180 @@ static unsigned char anchor_bits[] = { class PageAnchor : public Page { + private: + GwConfigHandler *config; + GwLog *logger; bool simulation = false; bool holdvalues = false; String flashLED; String backlightMode; + bool alarm = false; + bool alarm_enabled = false; + uint8_t alarm_range; + + uint8_t chain_length; + uint8_t chain; + + bool anchor_set = false; + double anchor_lat; + double anchor_lon; + double anchor_depth; + int anchor_ts; // time stamp anchor dropped + + char mode = 'N'; // (N)ormal, (C)onfig + + void displayModeNormal(PageData &pageData) { + + // Boatvalues: DBS, HDT, AWS, AWD, LAT, LON, HDOP + GwApi::BoatValue *bv_dbs = pageData.values[0]; // DBS + String sval_dbs = formatValue(bv_dbs, *commonData).svalue; + GwApi::BoatValue *bv_hdt = pageData.values[1]; // HDT + String sval_hdt = formatValue(bv_hdt, *commonData).svalue; + GwApi::BoatValue *bv_aws = pageData.values[2]; // AWS + String sval_aws = formatValue(bv_aws, *commonData).svalue; + GwApi::BoatValue *bv_awd = pageData.values[3]; // AWD + String sval_awd = formatValue(bv_awd, *commonData).svalue; + GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT + String sval_lat = formatValue(bv_lat, *commonData).svalue; + GwApi::BoatValue *bv_lon = pageData.values[5]; // LON + String sval_lon = formatValue(bv_lon, *commonData).svalue; + GwApi::BoatValue *bv_hdop = pageData.values[6]; // HDOP + String sval_hdop = formatValue(bv_hdop, *commonData).svalue; + + Point c = {200, 150}; // center = anchor position + uint16_t r = 125; + + Point b = {200, 180}; // boat position while dropping anchor + + std::vector pts_boat = { // polygon lines + {b.x - 5, b.y}, + {b.x - 5, b.y - 10}, + {b.x, b.y - 16}, + {b.x + 5, b.y - 10} + }; + //rotatePoints und dann Linien zeichnen + // TODO rotate boat according to current heading + //fillPoly4(rotatePoints(c, pts, RadToDeg(value2)), commonData->fgcolor); + + // Draw wind arrow + /* + if self._bd.awa.value: + p = ((cx, cy - r + 25), (cx - 12, cy - r - 4), (cx, cy - r + 6), (cx + 12, cy - r - 4), (cx, cy - r + 25)) + wind = self.rotate((cx, cy), p, self._bd.awa.value) + ctx.move_to(*wind[0]) + for point in wind[1:]: + ctx.line_to(*point) + ctx.fill() + */ + + // Title and corner value headings + getdisplay().setTextColor(commonData->fgcolor); + getdisplay().setFont(&Ubuntu_Bold10pt8b); + getdisplay().setCursor(8, 48); + getdisplay().print("Anchor"); + + getdisplay().setCursor(8, 200); + getdisplay().print("Depth"); + + drawTextRalign(392, 50, "Chain"); + drawTextRalign(392, 200, "Wind"); + + // Corner values + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(2, 70); + getdisplay().print("Alarm: "); + getdisplay().print(alarm_enabled ? "On" : "Off"); + + // Units + + // Values + getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); + // Depth + getdisplay().setCursor(8, 250); + //getdisplay().print(sval_dbs); + getdisplay().print("6.4"); + // Wind + getdisplay().setCursor(320, 250); + //getdisplay().print(sval_aws); + getdisplay().print("12"); + + getdisplay().drawCircle(c.x, c.y, r, commonData->fgcolor); + + // draw anchor symbol (as bitmap) + getdisplay().drawXBitmap(c.x - anchor_width / 2, c.y - anchor_height / 2, + anchor_bits, anchor_width, anchor_height, commonData->fgcolor); + + } + + void displayModeConfig() { + getdisplay().setTextColor(commonData->fgcolor); + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(8, 48); + getdisplay().print("Anchor configuration"); + } + public: PageAnchor(CommonData &common){ commonData = &common; - common.logger->logDebug(GwLog::LOG,"Instantiate PageAnchor"); + config = commonData->config; + logger = commonData->logger; + logger->logDebug(GwLog::LOG,"Instantiate PageAnchor"); // preload configuration data - simulation = common.config->getBool(common.config->useSimuData); - holdvalues = common.config->getBool(common.config->holdvalues); - flashLED = common.config->getString(common.config->flashLED); - backlightMode = common.config->getString(common.config->backlight); + simulation = config->getBool(config->useSimuData); + holdvalues = config->getBool(config->holdvalues); + flashLED = config->getString(config->flashLED); + backlightMode = config->getString(config->backlight); + chainLength = config->getInt(config->chainLength); + + chain = 0; + anchor_set = false; + } + + void setupKeys(){ + Page::setupKeys(); + commonData->keydata[0].label = "MODE"; + commonData->keydata[1].label = "ALARM"; } - virtual int handleKey(int key){ - // Code for keylock - if (key == 11) { + int handleKey(int key){ + if (key == 1) { // Switch between normal and config mode + if (mode == 'N') { + mode = 'C'; + } else { + mode = 'N'; + } + return 0; + } + if (key == 2) { // Toggle alarm + alarm_enabled = !alarm_enabled; + return 0; + } + if (key == 11) { // Code for keylock commonData->keylock = !commonData->keylock; - return 0; // Commit the key + return 0; } return key; } void displayNew(PageData &pageData){ - } + }; int displayPage(PageData &pageData){ - GwConfigHandler *config = commonData->config; - GwLog *logger = commonData->logger; // Logging boat values LOG_DEBUG(GwLog::LOG,"Drawing at PageAnchor"); + LOG_DEBUG(GwLog::LOG,"Drawing at PageAnchor; Mode=%c", mode); // Set display in partial refresh mode getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update - uint16_t cx = 200; // center = anchor position - uint16_t cy = 150; - - // draw anchor symbol (as bitmap) - getdisplay().drawXBitmap(cx - anchor_width / 2, cy - anchor_height / 2, - anchor_bits, anchor_width, anchor_height, commonData->fgcolor); - - - getdisplay().setTextColor(commonData->fgcolor); + if (mode == 'N') { + displayModeNormal(pageData); + } else if (mode == 'C') { + displayModeConfig(); + } return PAGE_UPDATE; }; @@ -82,7 +229,9 @@ class PageAnchor : public Page static Page *createPage(CommonData &common){ return new PageAnchor(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 @@ -90,11 +239,11 @@ static Page *createPage(CommonData &common){ * this will be number of BoatValue pointers in pageData.values */ PageDescription registerPageAnchor( - "Anchor", // Page name - createPage, // Action - 0, // Number of bus values depends on selection in Web configuration - {"DBS", "HDT", "TWS", "TWD"}, // Names of bus values undepends on selection in Web configuration (refer GwBoatData.h) - true // Show display header on/off + "Anchor", // Page name + createPage, // Action + 0, // Number of bus values depends on selection in Web configuration + {"DBS", "HDT", "AWS", "AWD", "LAT", "LON", "HDOP"}, // Names of bus values undepends on selection in Web configuration (refer GwBoatData.h) + true // Show display header on/off ); #endif diff --git a/lib/obp60task/config.json b/lib/obp60task/config.json index 820ce59..214b4ad 100644 --- a/lib/obp60task/config.json +++ b/lib/obp60task/config.json @@ -75,6 +75,20 @@ "obp60":"true" } }, + { + "name": "chainLength", + "label": "Anchor Chain Length [m]", + "type": "number", + "default": 0, + "check": "checkMinMax", + "min": 0, + "max": 255, + "description": "The length of the anchor chain [0...255m]", + "category": "OBP60 Settings", + "capabilities": { + "obp60":"true" + } + }, { "name": "fuelTank", "label": "Fuel Tank [l]", diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index 2e6c55b..e811748 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -75,6 +75,20 @@ "obp40": "true" } }, + { + "name": "chainLength", + "label": "Anchor Chain Length [m]", + "type": "number", + "default": "0", + "check": "checkMinMax", + "min": 0, + "max": 255, + "description": "The length of the anchor chain [0...255m]", + "category": "OBP40 Settings", + "capabilities": { + "obp40":"true" + } + }, { "name": "fuelTank", "label": "Fuel Tank [l]", diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index cd247e1..7a2b493 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -17,7 +17,7 @@ #include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation #ifdef BOARD_OBP40S3 -#include "driver/rtc_io.h" // Needs for weakup from deep sleep +#include "driver/rtc_io.h" // Needs for wakeup from deep sleep #include // SD-Card access #include #include From 1637bdf9eedd3545f8e05da3e09d21b0115a6b7d Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Tue, 29 Jul 2025 15:30:57 +0200 Subject: [PATCH 3/5] Start implementing config menu with page anchor --- lib/obp60task/ConfigMenu.cpp | 101 +++++++++++++++++++++++++++ lib/obp60task/ConfigMenu.h | 51 ++++++++++++++ lib/obp60task/Graphics.cpp | 25 +++++++ lib/obp60task/Graphics.h | 17 +++++ lib/obp60task/OBP60Extensions.cpp | 28 +++----- lib/obp60task/OBP60Extensions.h | 10 +-- lib/obp60task/PageAnchor.cpp | 110 +++++++++++++++++++++++------- 7 files changed, 293 insertions(+), 49 deletions(-) create mode 100644 lib/obp60task/ConfigMenu.cpp create mode 100644 lib/obp60task/ConfigMenu.h create mode 100644 lib/obp60task/Graphics.cpp create mode 100644 lib/obp60task/Graphics.h diff --git a/lib/obp60task/ConfigMenu.cpp b/lib/obp60task/ConfigMenu.cpp new file mode 100644 index 0000000..9bf9464 --- /dev/null +++ b/lib/obp60task/ConfigMenu.cpp @@ -0,0 +1,101 @@ +/* + Menu system for online configuration +*/ +#include "ConfigMenu.h" + +ConfigMenuItem::ConfigMenuItem(String itemtype, String itemlabel) { + if (! (itemtype == "int" or itemtype == "bool")) { + valtype = "int"; + } else { + valtype = itemtype; + } + label = itemlabel; + min = 0; + max = std::numeric_limits::max(); +} + +void ConfigMenuItem::setRange(uint16_t valmin, uint16_t valmax, std::vector valsteps) { + min = valmin; + max = valmax; + steps = valsteps; +}; + +bool ConfigMenuItem::setValue(uint16_t newval) { + if (valtype == "int") { + if (newval >= min and newval <= max) { + value = newval; + return true; + } + return false; // out of range + } else if (valtype == "bool") { + value = (newval != 0) ? 1 : 0; + return true; + } + return false; // invalid type +}; + +void ConfigMenuItem::setPos(int8_t newpos) { + position = newpos; +}; + +int8_t ConfigMenuItem::getPos() { + return position; +}; + +ConfigMenu::ConfigMenu(String menutitle, uint16_t menu_x, uint16_t menu_y) { + title = menutitle; + x = menu_x; + y = menu_y; +}; + +ConfigMenuItem* ConfigMenu::addItem(String key, String label, String valtype) { + if (items.find(key) != items.end()) { + // duplicate keys not allowed + return nullptr; + } + ConfigMenuItem itm(valtype, "Test1"); + return &itm; + // map.insert(std::pair(itm)); + // Append key to index + int8_t ix = items.size(); + index[ix] = key; + itm.setPos(ix); +}; + +void ConfigMenu::setItemDimension(uint16_t itemwidth, uint16_t itemheight) { + w = itemwidth; + h = itemheight; +}; + +void ConfigMenu::setItemActive(String key) { + uint8_t ix = + activeitem = ix; +}; + +ConfigMenuItem* ConfigMenu::getActiveItem() { + return nullptr; +}; + +ConfigMenuItem* ConfigMenu::getItemByIndex(uint8_t index) { + return nullptr; +}; + +ConfigMenuItem* ConfigMenu::getItemByKey(String Key) { + return nullptr; +}; + +uint8_t ConfigMenu::getItemCount() { + return items.size(); +}; + +Point ConfigMenu::getXY() { + return {x, y}; +} + +/* +void getRect(); +void getItemRect(); +*/ + + + diff --git a/lib/obp60task/ConfigMenu.h b/lib/obp60task/ConfigMenu.h new file mode 100644 index 0000000..5e35e86 --- /dev/null +++ b/lib/obp60task/ConfigMenu.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include "Graphics.h" // for Point and Rect + +class ConfigMenuItem { +private: + String label; + uint16_t value; + String unit; + String valtype; // "int" | "bool" + uint16_t min; + uint16_t max; + std::vector steps; + uint16_t step; + int8_t position; // counted fom 0 + +public: + ConfigMenuItem(String itemtype, String itemlabel); + void setRange(uint16_t valmin, uint16_t valmax, std::vector steps); + bool setValue(uint16_t newval); + void setPos(int8_t newpos); + int8_t getPos(); +}; + +class ConfigMenu { +private: + String title; + std::map items; + std::map index; + int8_t activeitem = -1; // refers to position of item + uint16_t x; + uint16_t y; + uint16_t w; + uint16_t h; + +public: + ConfigMenu(String title, uint16_t menu_x, uint16_t menu_y); + ConfigMenuItem* addItem(String key, String label, String valtype); + void setItemDimension(uint16_t itemwidth, uint16_t itemheight); + void setItemActive(String key); + ConfigMenuItem* getActiveItem(); + ConfigMenuItem* getItemByIndex(uint8_t index); + ConfigMenuItem* getItemByKey(String key); + uint8_t getItemCount(); + Point getXY(); + /* void getRect(); + void getItemRect(); */ +}; 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 70348e7..9f97af1 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -276,30 +276,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; diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h index ed67716..81226ef 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 @@ -62,14 +63,9 @@ 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); uint8_t getLastPage(); diff --git a/lib/obp60task/PageAnchor.cpp b/lib/obp60task/PageAnchor.cpp index 1f43542..fb51d4a 100644 --- a/lib/obp60task/PageAnchor.cpp +++ b/lib/obp60task/PageAnchor.cpp @@ -2,6 +2,7 @@ #include "Pagedata.h" #include "OBP60Extensions.h" +#include "ConfigMenu.h" /* Anchor overview with additional associated data @@ -48,13 +49,16 @@ static unsigned char anchor_bits[] = { class PageAnchor : public Page { - private: +private: GwConfigHandler *config; GwLog *logger; bool simulation = false; bool holdvalues = false; String flashLED; String backlightMode; + String lengthformat; + + int scale = 50; // Radius of display circle in meter bool alarm = false; bool alarm_enabled = false; @@ -71,15 +75,19 @@ class PageAnchor : public Page char mode = 'N'; // (N)ormal, (C)onfig + // ConfigMenu menu; + void displayModeNormal(PageData &pageData) { // Boatvalues: DBS, HDT, AWS, AWD, LAT, LON, HDOP GwApi::BoatValue *bv_dbs = pageData.values[0]; // DBS String sval_dbs = formatValue(bv_dbs, *commonData).svalue; + String sunit_dbs = formatValue(bv_dbs, *commonData).unit; GwApi::BoatValue *bv_hdt = pageData.values[1]; // HDT String sval_hdt = formatValue(bv_hdt, *commonData).svalue; GwApi::BoatValue *bv_aws = pageData.values[2]; // AWS String sval_aws = formatValue(bv_aws, *commonData).svalue; + String sunit_aws = formatValue(bv_aws, *commonData).unit; GwApi::BoatValue *bv_awd = pageData.values[3]; // AWD String sval_awd = formatValue(bv_awd, *commonData).svalue; GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT @@ -88,6 +96,7 @@ class PageAnchor : public Page String sval_lon = formatValue(bv_lon, *commonData).svalue; GwApi::BoatValue *bv_hdop = pageData.values[6]; // HDOP String sval_hdop = formatValue(bv_hdop, *commonData).svalue; + String sunit_hdop = formatValue(bv_hdop, *commonData).unit; Point c = {200, 150}; // center = anchor position uint16_t r = 125; @@ -98,56 +107,83 @@ class PageAnchor : public Page {b.x - 5, b.y}, {b.x - 5, b.y - 10}, {b.x, b.y - 16}, - {b.x + 5, b.y - 10} + {b.x + 5, b.y - 10}, + {b.x + 5, b.y} }; //rotatePoints und dann Linien zeichnen // TODO rotate boat according to current heading - //fillPoly4(rotatePoints(c, pts, RadToDeg(value2)), commonData->fgcolor); + //drawPoly(rotatePoints(c, pts, RadToDeg(value2)), commonData->fgcolor); + drawPoly(pts_boat, commonData->fgcolor); + + /*size_t polysize = pts_boat.size(); + for (size_t i = 0; i < polysize - 1; i++) { + getdisplay().drawLine(pts_boat[i].x, pts_boat[i].y, pts_boat[i+1].x, pts_boat[i+1].y, commonData->fgcolor); + } + // close path + getdisplay().drawLine(pts_boat[polysize-1].x, pts_boat[polysize-1].y, pts_boat[0].x, pts_boat[0].y, commonData->fgcolor); + */ // Draw wind arrow - /* - if self._bd.awa.value: - p = ((cx, cy - r + 25), (cx - 12, cy - r - 4), (cx, cy - r + 6), (cx + 12, cy - r - 4), (cx, cy - r + 25)) - wind = self.rotate((cx, cy), p, self._bd.awa.value) - ctx.move_to(*wind[0]) - for point in wind[1:]: - ctx.line_to(*point) - ctx.fill() - */ + std::vector pts_wind = { + {c.x, c.y - r + 25}, + {c.x - 12, c.y - r - 4}, + {c.x, c.y - r + 6}, + {c.x + 12, c.y - r - 4} + }; + fillPoly4(rotatePoints(c, pts_wind, 63), commonData->fgcolor); // Title and corner value headings getdisplay().setTextColor(commonData->fgcolor); - getdisplay().setFont(&Ubuntu_Bold10pt8b); + getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(8, 48); getdisplay().print("Anchor"); + getdisplay().setFont(&Ubuntu_Bold10pt8b); getdisplay().setCursor(8, 200); getdisplay().print("Depth"); - - drawTextRalign(392, 50, "Chain"); + drawTextRalign(392, 38, "Chain"); drawTextRalign(392, 200, "Wind"); + // Units + getdisplay().setCursor(8, 272); + getdisplay().print(sunit_dbs); + drawTextRalign(392, 272, sunit_aws); + drawTextRalign(392, 100, lengthformat); // chain unit not implemented + // Corner values getdisplay().setFont(&Ubuntu_Bold8pt8b); - getdisplay().setCursor(2, 70); + getdisplay().setCursor(8, 70); getdisplay().print("Alarm: "); getdisplay().print(alarm_enabled ? "On" : "Off"); - - // Units // Values getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); + // Current chain used + getdisplay().setCursor(328, 85); + getdisplay().print("27"); + // Depth getdisplay().setCursor(8, 250); //getdisplay().print(sval_dbs); getdisplay().print("6.4"); // Wind - getdisplay().setCursor(320, 250); + getdisplay().setCursor(328, 250); //getdisplay().print(sval_aws); getdisplay().print("12"); getdisplay().drawCircle(c.x, c.y, r, commonData->fgcolor); + // zoom scale + getdisplay().drawLine(c.x + 10, c.y, c.x + r - 4, c.y, commonData->fgcolor); + // arrow left + getdisplay().drawLine(c.x + 10, c.y, c.x + 16, c.y - 4, commonData->fgcolor); + getdisplay().drawLine(c.x + 10, c.y, c.x + 16, c.y + 4, commonData->fgcolor); + // arrow right + getdisplay().drawLine(c.x + r - 4, c.y, c.x + r - 10, c.y - 4, commonData->fgcolor); + getdisplay().drawLine(c.x + r - 4, c.y, c.x + r - 10, c.y + 4, commonData->fgcolor); + getdisplay().setFont(&Ubuntu_Bold8pt8b); + drawTextCenter(c.x + r / 2, c.y + 8, String(scale) + "m"); + // draw anchor symbol (as bitmap) getdisplay().drawXBitmap(c.x - anchor_width / 2, c.y - anchor_height / 2, anchor_bits, anchor_width, anchor_height, commonData->fgcolor); @@ -159,10 +195,18 @@ class PageAnchor : public Page getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(8, 48); getdisplay().print("Anchor configuration"); + + // TODO + // show lat/lon for anchor pos + // show lat/lon for boat pos + // show distance anchor <-> boat + } - public: - PageAnchor(CommonData &common){ +public: + PageAnchor(CommonData &common) + // : menu("Options", 80, 20) + { commonData = &common; config = commonData->config; logger = commonData->logger; @@ -173,10 +217,31 @@ class PageAnchor : public Page holdvalues = config->getBool(config->holdvalues); flashLED = config->getString(config->flashLED); backlightMode = config->getString(config->backlight); - chainLength = config->getInt(config->chainLength); + lengthformat = config->getString(config->lengthFormat); + chain_length = config->getInt(config->chainLength); chain = 0; anchor_set = false; + /* + // Initialize config menu + ConfigMenuItem *newitem; + menu.setItemDimension(120, 20); + newitem = menu.addItem("chain", "Chain out", "int"); + newitem->setRange(0, 200, {1, 5, 10}); + newitem = menu.addItem("chainmax", "Chain max", "int"); + newitem->setRange(0, 200, {1, 5, 10}); + newitem = menu.addItem("zoom", "Zoom", "int"); + newitem->setRange(0, 200, {1, }); + newitem = menu.addItem("range", "Alarm range", "int"); + newitem->setRange(0, 200, {1, 5, 10}); + // START only for OBP40 + newitem = menu.addItem("anchor", "Anchor down", "bool"); + newitem = menu.addItem("anchor_lat", "Adjust anchor lat.", "int"); + newitem->setRange(0, 200, {1, 5, 10}); + newitem = menu.addItem("anchor_lon", "Adjust anchor lon.", "int"); + newitem->setRange(0, 200, {1, 5, 10}); + // STOP only for OBP40 + menu.setItemActive("chain"); */ } void setupKeys(){ @@ -211,7 +276,6 @@ class PageAnchor : public Page int displayPage(PageData &pageData){ // Logging boat values - LOG_DEBUG(GwLog::LOG,"Drawing at PageAnchor"); LOG_DEBUG(GwLog::LOG,"Drawing at PageAnchor; Mode=%c", mode); // Set display in partial refresh mode From fbe5e343b4cd74e1765ea2ef540b11482fa42d6a Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Tue, 29 Jul 2025 19:12:55 +0200 Subject: [PATCH 4/5] Page anchor ongoing work --- lib/obp60task/PageAnchor.cpp | 40 +++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/lib/obp60task/PageAnchor.cpp b/lib/obp60task/PageAnchor.cpp index fb51d4a..7de8306 100644 --- a/lib/obp60task/PageAnchor.cpp +++ b/lib/obp60task/PageAnchor.cpp @@ -98,12 +98,14 @@ private: String sval_hdop = formatValue(bv_hdop, *commonData).svalue; String sunit_hdop = formatValue(bv_hdop, *commonData).unit; + LOG_DEBUG(GwLog::DEBUG,"Drawing at PageAnchor; DBS=%f, HDT=%f, AWS=%f", bv_dbs->value, bv_hdt->value, bv_aws->value); + Point c = {200, 150}; // center = anchor position uint16_t r = 125; Point b = {200, 180}; // boat position while dropping anchor - std::vector pts_boat = { // polygon lines + const std::vector pts_boat = { // polygon lines {b.x - 5, b.y}, {b.x - 5, b.y - 10}, {b.x, b.y - 16}, @@ -124,13 +126,15 @@ private: */ // Draw wind arrow - std::vector pts_wind = { + const std::vector pts_wind = { {c.x, c.y - r + 25}, {c.x - 12, c.y - r - 4}, {c.x, c.y - r + 6}, {c.x + 12, c.y - r - 4} }; - fillPoly4(rotatePoints(c, pts_wind, 63), commonData->fgcolor); + if (bv_awd->valid) { + fillPoly4(rotatePoints(c, pts_wind, bv_awd->value), commonData->fgcolor); + } // Title and corner value headings getdisplay().setTextColor(commonData->fgcolor); @@ -156,6 +160,16 @@ private: getdisplay().print("Alarm: "); getdisplay().print(alarm_enabled ? "On" : "Off"); + getdisplay().setCursor(8, 90); + getdisplay().print("HDOP"); + getdisplay().setCursor(8, 106); + if (bv_hdop->valid) { + getdisplay().print(round(bv_hdop->value), 0); + getdisplay().print(sunit_hdop); + } else { + getdisplay().print("n/a"); + } + // Values getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); // Current chain used @@ -164,14 +178,13 @@ private: // Depth getdisplay().setCursor(8, 250); - //getdisplay().print(sval_dbs); - getdisplay().print("6.4"); + getdisplay().print(sval_dbs); // Wind getdisplay().setCursor(328, 250); - //getdisplay().print(sval_aws); - getdisplay().print("12"); + getdisplay().print(sval_aws); getdisplay().drawCircle(c.x, c.y, r, commonData->fgcolor); + getdisplay().drawCircle(c.x, c.y, r + 1, commonData->fgcolor); // zoom scale getdisplay().drawLine(c.x + 10, c.y, c.x + r - 4, c.y, commonData->fgcolor); @@ -184,6 +197,15 @@ private: getdisplay().setFont(&Ubuntu_Bold8pt8b); drawTextCenter(c.x + r / 2, c.y + 8, String(scale) + "m"); + // alarm range circle + if (alarm_enabled) { + // alarm range in meter has to be smaller than the scale in meter + // r and r_range are pixel values + uint16_t r_range = int(alarm_range * r / scale); + LOG_DEBUG(GwLog::LOG,"Drawing at PageAnchor; Alarm range = %d", r_range); + getdisplay().drawCircle(c.x, c.y, r_range, commonData->fgcolor); + } + // draw anchor symbol (as bitmap) getdisplay().drawXBitmap(c.x - anchor_width / 2, c.y - anchor_height / 2, anchor_bits, anchor_width, anchor_height, commonData->fgcolor); @@ -191,6 +213,9 @@ private: } void displayModeConfig() { + + // LOG_DEBUG(GwLog::LOG,"Drawing at PageAnchor; Mode=%c", mode); + getdisplay().setTextColor(commonData->fgcolor); getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(8, 48); @@ -222,6 +247,7 @@ public: chain = 0; anchor_set = false; + alarm_range = 30; /* // Initialize config menu ConfigMenuItem *newitem; From 2e797644dbeb17e6ca080275e4cf5e3942272f66 Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Wed, 30 Jul 2025 20:06:35 +0200 Subject: [PATCH 5/5] First preview of working config menu for OBP40 --- lib/obp60task/ConfigMenu.cpp | 139 ++++++++++++++++++++++----- lib/obp60task/ConfigMenu.h | 24 +++-- lib/obp60task/OBP60Extensions.cpp | 18 ++++ lib/obp60task/OBP60Extensions.h | 1 + lib/obp60task/PageAnchor.cpp | 150 ++++++++++++++++++++++++------ 5 files changed, 275 insertions(+), 57 deletions(-) diff --git a/lib/obp60task/ConfigMenu.cpp b/lib/obp60task/ConfigMenu.cpp index 9bf9464..1627531 100644 --- a/lib/obp60task/ConfigMenu.cpp +++ b/lib/obp60task/ConfigMenu.cpp @@ -3,7 +3,7 @@ */ #include "ConfigMenu.h" -ConfigMenuItem::ConfigMenuItem(String itemtype, String itemlabel) { +ConfigMenuItem::ConfigMenuItem(String itemtype, String itemlabel, uint16_t itemval, String itemunit) { if (! (itemtype == "int" or itemtype == "bool")) { valtype = "int"; } else { @@ -12,6 +12,8 @@ ConfigMenuItem::ConfigMenuItem(String itemtype, String itemlabel) { label = itemlabel; min = 0; max = std::numeric_limits::max(); + value = itemval; + unit = itemunit; } void ConfigMenuItem::setRange(uint16_t valmin, uint16_t valmax, std::vector valsteps) { @@ -20,6 +22,18 @@ void ConfigMenuItem::setRange(uint16_t valmin, uint16_t valmax, std::vector= min) and (checkval <= max); +} + +String ConfigMenuItem::getLabel() { + return label; +}; + +uint16_t ConfigMenuItem::getValue() { + return value; +} + bool ConfigMenuItem::setValue(uint16_t newval) { if (valtype == "int") { if (newval >= min and newval <= max) { @@ -34,32 +48,77 @@ bool ConfigMenuItem::setValue(uint16_t newval) { return false; // invalid type }; -void ConfigMenuItem::setPos(int8_t newpos) { - position = newpos; +void ConfigMenuItem::incValue() { + // increase value by step + if (valtype == "int") { + if (value + step < max) { + value += step; + } else { + value = max; + } + } else if (valtype == "bool") { + value = !value; + } }; +void ConfigMenuItem::decValue() { + // decrease value by step + if (valtype == "int") { + if (value - step > min) { + value -= step; + } else { + value = min; + } + } else if (valtype == "bool") { + value = !value; + } +}; + +String ConfigMenuItem::getUnit() { + return unit; +} + +uint16_t ConfigMenuItem::getStep() { + return step; +} + +void ConfigMenuItem::setStep(uint16_t newstep) { + if (std::find(steps.begin(), steps.end(), newstep) == steps.end()) { + return; // invalid step: not in list of possible steps + } + step = newstep; +} + int8_t ConfigMenuItem::getPos() { return position; }; +void ConfigMenuItem::setPos(int8_t newpos) { + position = newpos; +}; + +String ConfigMenuItem::getType() { + return valtype; +} + ConfigMenu::ConfigMenu(String menutitle, uint16_t menu_x, uint16_t menu_y) { title = menutitle; x = menu_x; y = menu_y; }; -ConfigMenuItem* ConfigMenu::addItem(String key, String label, String valtype) { +ConfigMenuItem* ConfigMenu::addItem(String key, String label, String valtype, uint16_t val, String valunit) { if (items.find(key) != items.end()) { // duplicate keys not allowed return nullptr; } - ConfigMenuItem itm(valtype, "Test1"); - return &itm; - // map.insert(std::pair(itm)); - // Append key to index - int8_t ix = items.size(); + ConfigMenuItem *itm = new ConfigMenuItem(valtype, label, val, valunit); + items.insert(std::pair(key, itm)); + // Append key to index, index starting with 0 + int8_t ix = items.size() - 1; index[ix] = key; - itm.setPos(ix); + itm->setPos(ix); + return itm; }; void ConfigMenu::setItemDimension(uint16_t itemwidth, uint16_t itemheight) { @@ -68,34 +127,68 @@ void ConfigMenu::setItemDimension(uint16_t itemwidth, uint16_t itemheight) { }; void ConfigMenu::setItemActive(String key) { - uint8_t ix = - activeitem = ix; + if (items.find(key) != items.end()) { + activeitem = items[key]->getPos(); + } else { + activeitem = -1; + } }; +int8_t ConfigMenu::getActiveIndex() { + return activeitem; +} + ConfigMenuItem* ConfigMenu::getActiveItem() { - return nullptr; + if (activeitem < 0) { + return nullptr; + } + return items[index[activeitem]]; }; -ConfigMenuItem* ConfigMenu::getItemByIndex(uint8_t index) { - return nullptr; +ConfigMenuItem* ConfigMenu::getItemByIndex(uint8_t ix) { + if (ix > index.size() - 1) { + return nullptr; + } + return items[index[ix]]; }; -ConfigMenuItem* ConfigMenu::getItemByKey(String Key) { - return nullptr; +ConfigMenuItem* ConfigMenu::getItemByKey(String key) { + if (items.find(key) == items.end()) { + return nullptr; + } + return items[key]; }; uint8_t ConfigMenu::getItemCount() { return items.size(); }; -Point ConfigMenu::getXY() { - return {x, y}; +void ConfigMenu::goPrev() { + if (activeitem == 0) { + activeitem = items.size() - 1; + } else { + activeitem--; + } } -/* -void getRect(); -void getItemRect(); -*/ +void ConfigMenu::goNext() { + if (activeitem == items.size() - 1) { + activeitem = 0; + } else { + activeitem++; + } +} +Point ConfigMenu::getXY() { + return {static_cast(x), static_cast(y)}; +} +Rect ConfigMenu::getRect() { + return {static_cast(x), static_cast(y), + static_cast(w), static_cast(h)}; +} +Rect ConfigMenu::getItemRect(int8_t index) { + return {static_cast(x), static_cast(y + index * h), + static_cast(w), static_cast(h)}; +} diff --git a/lib/obp60task/ConfigMenu.h b/lib/obp60task/ConfigMenu.h index 5e35e86..439097d 100644 --- a/lib/obp60task/ConfigMenu.h +++ b/lib/obp60task/ConfigMenu.h @@ -18,17 +18,26 @@ private: int8_t position; // counted fom 0 public: - ConfigMenuItem(String itemtype, String itemlabel); + ConfigMenuItem(String itemtype, String itemlabel, uint16_t itemval, String itemunit); void setRange(uint16_t valmin, uint16_t valmax, std::vector steps); + bool checkRange(uint16_t checkval); + String getLabel(); + uint16_t getValue(); bool setValue(uint16_t newval); - void setPos(int8_t newpos); + void incValue(); + void decValue(); + String getUnit(); + uint16_t getStep(); + void setStep(uint16_t newstep); int8_t getPos(); + void setPos(int8_t newpos); + String getType(); }; class ConfigMenu { private: String title; - std::map items; + std::map items; std::map index; int8_t activeitem = -1; // refers to position of item uint16_t x; @@ -38,14 +47,17 @@ private: public: ConfigMenu(String title, uint16_t menu_x, uint16_t menu_y); - ConfigMenuItem* addItem(String key, String label, String valtype); + ConfigMenuItem* addItem(String key, String label, String valtype, uint16_t val, String valunit); void setItemDimension(uint16_t itemwidth, uint16_t itemheight); + int8_t getActiveIndex(); void setItemActive(String key); ConfigMenuItem* getActiveItem(); ConfigMenuItem* getItemByIndex(uint8_t index); ConfigMenuItem* getItemByKey(String key); uint8_t getItemCount(); + void goPrev(); + void goNext(); Point getXY(); - /* void getRect(); - void getItemRect(); */ + Rect getRect(); + Rect getItemRect(int8_t index); }; diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 9f97af1..b4b3b47 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -351,6 +351,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 81226ef..46da9c0 100644 --- a/lib/obp60task/OBP60Extensions.h +++ b/lib/obp60task/OBP60Extensions.h @@ -92,6 +92,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/PageAnchor.cpp b/lib/obp60task/PageAnchor.cpp index 7de8306..476ed20 100644 --- a/lib/obp60task/PageAnchor.cpp +++ b/lib/obp60task/PageAnchor.cpp @@ -74,8 +74,9 @@ private: int anchor_ts; // time stamp anchor dropped char mode = 'N'; // (N)ormal, (C)onfig + int8_t editmode = -1; // marker for menu/edit/set function - // ConfigMenu menu; + ConfigMenu *menu; void displayModeNormal(PageData &pageData) { @@ -117,14 +118,6 @@ private: //drawPoly(rotatePoints(c, pts, RadToDeg(value2)), commonData->fgcolor); drawPoly(pts_boat, commonData->fgcolor); - /*size_t polysize = pts_boat.size(); - for (size_t i = 0; i < polysize - 1; i++) { - getdisplay().drawLine(pts_boat[i].x, pts_boat[i].y, pts_boat[i+1].x, pts_boat[i+1].y, commonData->fgcolor); - } - // close path - getdisplay().drawLine(pts_boat[polysize-1].x, pts_boat[polysize-1].y, pts_boat[0].x, pts_boat[0].y, commonData->fgcolor); - */ - // Draw wind arrow const std::vector pts_wind = { {c.x, c.y - r + 25}, @@ -214,8 +207,6 @@ private: void displayModeConfig() { - // LOG_DEBUG(GwLog::LOG,"Drawing at PageAnchor; Mode=%c", mode); - getdisplay().setTextColor(commonData->fgcolor); getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(8, 48); @@ -226,11 +217,33 @@ private: // show lat/lon for boat pos // show distance anchor <-> boat + getdisplay().setFont(&Ubuntu_Bold8pt8b); + for (int i = 0 ; i < menu->getItemCount(); i++) { + ConfigMenuItem *itm = menu->getItemByIndex(i); + if (!itm) { + LOG_DEBUG(GwLog::ERROR, "Menu item not found: %d", i); + } else { + Rect r = menu->getItemRect(i); + bool inverted = (i == menu->getActiveIndex()); + drawTextBoxed(r, itm->getLabel(), commonData->fgcolor, commonData->bgcolor, inverted, false); + if (inverted and editmode > 0) { + // triangle as edit marker + getdisplay().fillTriangle(r.x + r.w + 20, r.y, r.x + r.w + 30, r.y + r.h / 2, r.x + r.w + 20, r.y + r.h, commonData->fgcolor); + } + getdisplay().setCursor(r.x + r.w + 40, r.y + r.h - 4); + if (itm->getType() == "int") { + getdisplay().print(itm->getValue()); + getdisplay().print(itm->getUnit()); + } else { + getdisplay().print(itm->getValue() == 0 ? "No" : "Yes"); + } + } + } + } public: PageAnchor(CommonData &common) - // : menu("Options", 80, 20) { commonData = &common; config = commonData->config; @@ -244,30 +257,38 @@ public: backlightMode = config->getString(config->backlight); lengthformat = config->getString(config->lengthFormat); chain_length = config->getInt(config->chainLength); - + chain = 0; anchor_set = false; alarm_range = 30; - /* + // Initialize config menu + menu = new ConfigMenu("Options", 40, 80); + menu->setItemDimension(150, 20); + ConfigMenuItem *newitem; - menu.setItemDimension(120, 20); - newitem = menu.addItem("chain", "Chain out", "int"); + newitem = menu->addItem("chain", "Chain out", "int", 0, "m"); + if (! newitem) { + // Demo: in case of failure exit here, should never be happen + logger->logDebug(GwLog::ERROR,"Menu item creation failed"); + return; + } newitem->setRange(0, 200, {1, 5, 10}); - newitem = menu.addItem("chainmax", "Chain max", "int"); + newitem = menu->addItem("chainmax", "Chain max", "int", chain_length, "m"); newitem->setRange(0, 200, {1, 5, 10}); - newitem = menu.addItem("zoom", "Zoom", "int"); + newitem = menu->addItem("zoom", "Zoom", "int", 50, "m"); newitem->setRange(0, 200, {1, }); - newitem = menu.addItem("range", "Alarm range", "int"); + newitem = menu->addItem("range", "Alarm range", "int", 40, "m"); newitem->setRange(0, 200, {1, 5, 10}); - // START only for OBP40 - newitem = menu.addItem("anchor", "Anchor down", "bool"); - newitem = menu.addItem("anchor_lat", "Adjust anchor lat.", "int"); + newitem = menu->addItem("alat", "Adjust anchor lat.", "int", 0, "m"); newitem->setRange(0, 200, {1, 5, 10}); - newitem = menu.addItem("anchor_lon", "Adjust anchor lon.", "int"); + newitem = menu->addItem("alon", "Adjust anchor lon.", "int", 0, "m"); newitem->setRange(0, 200, {1, 5, 10}); - // STOP only for OBP40 - menu.setItemActive("chain"); */ +#ifdef BOARD_OBP40S3 + // Intodruced here because of missing keys for OBP40 + newitem = menu->addItem("anchor", "Anchor down", "bool", 0, ""); +#endif + menu->setItemActive("zoom"); } void setupKeys(){ @@ -276,6 +297,7 @@ public: commonData->keydata[1].label = "ALARM"; } +#ifdef BOARD_OBP60S3 int handleKey(int key){ if (key == 1) { // Switch between normal and config mode if (mode == 'N') { @@ -285,9 +307,22 @@ public: } return 0; } - if (key == 2) { // Toggle alarm - alarm_enabled = !alarm_enabled; - return 0; + if (mode == 'N') { + if (key == 2) { // Toggle alarm + alarm_enabled = !alarm_enabled; + return 0; + } + } else { // Config mode + if (key == 3) { + // menu down + menu->goNext(); + return 0; + } + if (key == 4) { + // menu up + menu->goPrev(); + return 0; + } } if (key == 11) { // Code for keylock commonData->keylock = !commonData->keylock; @@ -295,6 +330,65 @@ public: } return key; } +#endif +#ifdef BOARD_OBP40S3 + int handleKey(int key){ + if (key == 1) { // Switch between normal and config mode + if (mode == 'N') { + mode = 'C'; + commonData->keydata[1].label = "EDIT"; + } else { + mode = 'N'; + commonData->keydata[1].label = "ALARM"; + } + return 0; + } + if (mode == 'N') { + if (key == 2) { // Toggle alarm + alarm_enabled = !alarm_enabled; + return 0; + } + } else { // Config mode + // TODO different code for OBP40 / OBP60 + if (key == 9) { + // menu down + if (editmode > 0) { + // decrease item value + menu->getActiveItem()->decValue(); + } else { + menu->goNext(); + } + return 0; + } + if (key == 10) { + // menu up or value up + if (editmode > 0) { + // increase item value + menu->getActiveItem()->incValue(); + } else { + menu->goPrev(); + } + return 0; + } + if (key == 2) { + // enter / leave edit mode for current menu item + if (editmode > 0) { + commonData->keydata[1].label = "EDIT"; + editmode = 0; + } else { + commonData->keydata[1].label = "SET"; + editmode = 1; + } + return 0; + } + } + if (key == 11) { // Code for keylock + commonData->keylock = !commonData->keylock; + return 0; + } + return key; + } +#endif void displayNew(PageData &pageData){ };