From 6fb5e01969b9fadaa922209a171c3425affd30e1 Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Mon, 4 Aug 2025 15:28:49 +0200 Subject: [PATCH] Preparation for new anchor page --- lib/obp60task/PageAnchor.cpp | 433 ++++++++++++++++++++++++++++++++ lib/obp60task/config.json | 24 ++ lib/obp60task/config_obp40.json | 24 ++ lib/obp60task/obp60task.cpp | 2 + 4 files changed, 483 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..476ed20 --- /dev/null +++ b/lib/obp60task/PageAnchor.cpp @@ -0,0 +1,433 @@ +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 + +#include "Pagedata.h" +#include "OBP60Extensions.h" +#include "ConfigMenu.h" + +/* + 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 + 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 +#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 +{ +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; + 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 + int8_t editmode = -1; // marker for menu/edit/set function + + 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 + 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; + 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 + + const 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}, + {b.x + 5, b.y} + }; + //rotatePoints und dann Linien zeichnen + // TODO rotate boat according to current heading + //drawPoly(rotatePoints(c, pts, RadToDeg(value2)), commonData->fgcolor); + drawPoly(pts_boat, commonData->fgcolor); + + // Draw wind arrow + 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} + }; + if (bv_awd->valid) { + fillPoly4(rotatePoints(c, pts_wind, bv_awd->value), commonData->fgcolor); + } + + // Title and corner value headings + getdisplay().setTextColor(commonData->fgcolor); + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(8, 48); + getdisplay().print("Anchor"); + + getdisplay().setFont(&Ubuntu_Bold10pt8b); + getdisplay().setCursor(8, 200); + getdisplay().print("Depth"); + 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(8, 70); + 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 + getdisplay().setCursor(328, 85); + getdisplay().print("27"); + + // Depth + getdisplay().setCursor(8, 250); + getdisplay().print(sval_dbs); + // Wind + getdisplay().setCursor(328, 250); + 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); + // 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"); + + // 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); + + } + + void displayModeConfig() { + + getdisplay().setTextColor(commonData->fgcolor); + 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 + + 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) + { + commonData = &common; + config = commonData->config; + logger = commonData->logger; + logger->logDebug(GwLog::LOG,"Instantiate PageAnchor"); + + // preload configuration data + simulation = config->getBool(config->useSimuData); + holdvalues = config->getBool(config->holdvalues); + flashLED = config->getString(config->flashLED); + 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; + 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", chain_length, "m"); + newitem->setRange(0, 200, {1, 5, 10}); + newitem = menu->addItem("zoom", "Zoom", "int", 50, "m"); + newitem->setRange(0, 200, {1, }); + newitem = menu->addItem("range", "Alarm range", "int", 40, "m"); + newitem->setRange(0, 200, {1, 5, 10}); + newitem = menu->addItem("alat", "Adjust anchor lat.", "int", 0, "m"); + newitem->setRange(0, 200, {1, 5, 10}); + newitem = menu->addItem("alon", "Adjust anchor lon.", "int", 0, "m"); + newitem->setRange(0, 200, {1, 5, 10}); +#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(){ + Page::setupKeys(); + commonData->keydata[0].label = "MODE"; + commonData->keydata[1].label = "ALARM"; + } + +#ifdef BOARD_OBP60S3 + int handleKey(int key){ + if (key == 1) { // Switch between normal and config mode + if (mode == 'N') { + mode = 'C'; + } else { + mode = 'N'; + } + 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; + return 0; + } + 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){ + }; + + int displayPage(PageData &pageData){ + + // Logging boat values + 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 + + if (mode == 'N') { + displayModeNormal(pageData); + } else if (mode == 'C') { + displayModeConfig(); + } + + 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", "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 570dd08..cbba468 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]", @@ -1333,6 +1347,7 @@ "default": "Voltage", "description": "Type of page for page 1", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -1615,6 +1630,7 @@ "default": "WindRose", "description": "Type of page for page 2", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -1894,6 +1910,7 @@ "default": "OneValue", "description": "Type of page for page 3", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2170,6 +2187,7 @@ "default": "TwoValues", "description": "Type of page for page 4", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2443,6 +2461,7 @@ "default": "ThreeValues", "description": "Type of page for page 5", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2713,6 +2732,7 @@ "default": "FourValues", "description": "Type of page for page 6", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2980,6 +3000,7 @@ "default": "FourValues2", "description": "Type of page for page 7", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -3244,6 +3265,7 @@ "default": "Clock", "description": "Type of page for page 8", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -3505,6 +3527,7 @@ "default": "RollPitch", "description": "Type of page for page 9", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -3763,6 +3786,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 e086fee..3cfb991 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]", @@ -1356,6 +1370,7 @@ "default": "Clock", "description": "Type of page for page 1", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -1638,6 +1653,7 @@ "default": "Wind", "description": "Type of page for page 2", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -1917,6 +1933,7 @@ "default": "OneValue", "description": "Type of page for page 3", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2193,6 +2210,7 @@ "default": "TwoValues", "description": "Type of page for page 4", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2466,6 +2484,7 @@ "default": "ThreeValues", "description": "Type of page for page 5", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -2736,6 +2755,7 @@ "default": "FourValues", "description": "Type of page for page 6", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -3003,6 +3023,7 @@ "default": "FourValues2", "description": "Type of page for page 7", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -3267,6 +3288,7 @@ "default": "Fluid", "description": "Type of page for page 8", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -3528,6 +3550,7 @@ "default": "RollPitch", "description": "Type of page for page 9", "list": [ + "Anchor", "BME280", "Battery", "Battery2", @@ -3786,6 +3809,7 @@ "default": "Battery2", "description": "Type of page for page 10", "list": [ + "Anchor", "BME280", "Battery", "Battery2", diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index e0af647..3d1e0bb 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -298,6 +298,8 @@ void registerAllPages(PageList &list){ list.add(®isterPageFluid); extern PageDescription registerPageSkyView; list.add(®isterPageSkyView); + extern PageDescription registerPageAnchor; + list.add(®isterPageAnchor); } // Undervoltage detection for shutdown display