1
0
mirror of https://github.com/thooge/esp32-nmea2000-obp60.git synced 2026-03-28 18:06:37 +01:00

1 Commits

Author SHA1 Message Date
6d052f8827 More work on page anchor 2026-03-08 19:52:51 +01:00
9 changed files with 668 additions and 107 deletions

View File

@@ -0,0 +1,217 @@
/*
Menu system for online configuration
A menu consists of a list of menuitems.
Graphical representation is stored:
upper left corner: x, y
bounding box:
A menu consists of three columns
- menu text, if selected highlighted
- menu value with optional unit
- menu description or additional data for value
*/
#include "ConfigMenu.h"
ConfigMenuItem::ConfigMenuItem(String itemtype, String itemlabel, uint16_t itemval, String itemunit) {
if (! (itemtype == "int" or itemtype == "bool")) {
valtype = "int";
} else {
valtype = itemtype;
}
label = itemlabel;
min = 0;
max = std::numeric_limits<uint16_t>::max();
value = itemval;
unit = itemunit;
step = 1;
}
void ConfigMenuItem::setRange(uint16_t valmin, uint16_t valmax, std::vector<uint16_t> valsteps) {
min = valmin;
max = valmax;
steps = valsteps;
step = steps[0];
};
bool ConfigMenuItem::checkRange(uint16_t checkval) {
return (checkval >= 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) {
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::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, uint16_t val, String valunit) {
if (items.find(key) != items.end()) {
// duplicate keys not allowed
return nullptr;
}
ConfigMenuItem *itm = new ConfigMenuItem(valtype, label, val, valunit);
items.insert(std::pair<String, ConfigMenuItem*>(key, itm));
// Append key to index, index starting with 0
int8_t ix = items.size() - 1;
index[ix] = key;
itm->setPos(ix);
return itm;
};
void ConfigMenu::setItemDimension(uint16_t itemwidth, uint16_t itemheight) {
w = itemwidth;
h = itemheight;
};
void ConfigMenu::setItemActive(String key) {
if (items.find(key) != items.end()) {
activeitem = items[key]->getPos();
} else {
activeitem = -1;
}
};
int8_t ConfigMenu::getActiveIndex() {
return activeitem;
}
ConfigMenuItem* ConfigMenu::getActiveItem() {
if (activeitem < 0) {
return nullptr;
}
return items[index[activeitem]];
};
ConfigMenuItem* ConfigMenu::getItemByIndex(uint8_t ix) {
if (ix > index.size() - 1) {
return nullptr;
}
return items[index[ix]];
};
ConfigMenuItem* ConfigMenu::getItemByKey(String key) {
if (items.find(key) == items.end()) {
return nullptr;
}
return items[key];
};
uint8_t ConfigMenu::getItemCount() {
return items.size();
};
void ConfigMenu::goPrev() {
if (activeitem == 0) {
activeitem = items.size() - 1;
} else {
activeitem--;
}
}
void ConfigMenu::goNext() {
if (activeitem == items.size() - 1) {
activeitem = 0;
} else {
activeitem++;
}
}
Point ConfigMenu::getXY() {
return {static_cast<double>(x), static_cast<double>(y)};
}
Rect ConfigMenu::getRect() {
return {static_cast<double>(x), static_cast<double>(y),
static_cast<double>(w), static_cast<double>(h)};
}
Rect ConfigMenu::getItemRect(int8_t index) {
return {static_cast<double>(x), static_cast<double>(y + index * h),
static_cast<double>(w), static_cast<double>(h)};
}
void ConfigMenu::setCallback(void (*callback)()) {
fptrCallback = callback;
}
void ConfigMenu::storeValues() {
if (fptrCallback) {
fptrCallback();
}
}

View File

@@ -0,0 +1,67 @@
#pragma once
#include <Arduino.h>
#include <vector>
#include <map>
#include "Graphics.h" // for Point and Rect
class ConfigMenuItem {
private:
String label;
uint16_t value;
String unit;
String desc; // optional data to display
String valtype; // "int" | "bool" -> TODO "list"
uint16_t min;
uint16_t max;
std::vector<uint16_t> steps;
uint16_t step;
int8_t position; // counted fom 0
public:
ConfigMenuItem(String itemtype, String itemlabel, uint16_t itemval, String itemunit);
void setRange(uint16_t valmin, uint16_t valmax, std::vector<uint16_t> steps);
bool checkRange(uint16_t checkval);
String getLabel();
uint16_t getValue();
bool setValue(uint16_t newval);
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 <String,ConfigMenuItem*> items;
std::map <uint8_t,String> index;
int8_t activeitem = -1; // refers to position of item
uint16_t x;
uint16_t y;
uint16_t w;
uint16_t h;
void (*fptrCallback)();
public:
ConfigMenu(String title, uint16_t menu_x, uint16_t menu_y);
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();
Rect getRect();
Rect getItemRect(int8_t index);
void setCallback(void (*callback)());
void storeValues();
};

View File

@@ -475,6 +475,30 @@ std::vector<String> wordwrap(String &line, uint16_t maxwidth) {
return lines; return lines;
} }
// Helper function to just get the exact width of a string
uint16_t getStringPixelWidth(const char* str, const GFXfont* font) {
int16_t minx = INT16_MAX;
int16_t maxx = INT16_MIN;
int16_t cursor_x = 0;
while (*str) {
char c = *str++;
if (c < font->first || c > font->last) {
continue;
}
GFXglyph* glyph = &font->glyph[c - font->first];
if (glyph->width > 0) {
int16_t glyphStart = cursor_x + glyph->xOffset;
int16_t glyphEnd = glyphStart + glyph->width;
if (glyphStart < minx) minx = glyphStart;
if (glyphEnd > maxx) maxx = glyphEnd;
}
cursor_x += glyph->xAdvance;
}
if (minx > maxx)
return 0;
return maxx - minx;
}
// Draw centered text // Draw centered text
void drawTextCenter(int16_t cx, int16_t cy, String text) { void drawTextCenter(int16_t cx, int16_t cy, String text) {
int16_t x1, y1; int16_t x1, y1;
@@ -638,20 +662,19 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa
// Date and time // Date and time
String fmttype = commonData.config->getString(commonData.config->dateFormat); String fmttype = commonData.config->getString(commonData.config->dateFormat);
String timesource = commonData.config->getString(commonData.config->timeSource); String timesource = commonData.config->getString(commonData.config->timeSource);
double tz = commonData.config->getString(commonData.config->timeZone).toDouble();
getdisplay().setTextColor(commonData.fgcolor); getdisplay().setTextColor(commonData.fgcolor);
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(230, 15); getdisplay().setCursor(230, 15);
if (timesource == "RTC" or timesource == "iRTC") { if (timesource == "RTC" or timesource == "iRTC") {
// TODO take DST into account // TODO take DST into account
if (commonData.data.rtcValid) { if (commonData.data.rtcValid) {
time_t tv = mktime(&commonData.data.rtcTime) + (int)(tz * 3600); time_t tv = mktime(&commonData.data.rtcTime) + (int)(commonData.tz * 3600);
struct tm *local_tm = localtime(&tv); struct tm *local_tm = localtime(&tv);
getdisplay().print(formatTime('m', local_tm->tm_hour, local_tm->tm_min, 0)); getdisplay().print(formatTime('m', local_tm->tm_hour, local_tm->tm_min, 0));
getdisplay().print(" "); getdisplay().print(" ");
getdisplay().print(formatDate(fmttype, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday)); getdisplay().print(formatDate(fmttype, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday));
getdisplay().print(" "); getdisplay().print(" ");
getdisplay().print(tz == 0 ? "UTC" : "LOT"); getdisplay().print(commonData.tz == 0 ? "UTC" : "LOT");
} else { } else {
drawTextRalign(396, 15, "RTC invalid"); drawTextRalign(396, 15, "RTC invalid");
} }
@@ -666,7 +689,7 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa
getdisplay().print(" "); getdisplay().print(" ");
getdisplay().print(actdate); getdisplay().print(actdate);
getdisplay().print(" "); getdisplay().print(" ");
getdisplay().print(tz == 0 ? "UTC" : "LOT"); getdisplay().print(commonData.tz == 0 ? "UTC" : "LOT");
} }
else{ else{
if(commonData.config->getBool(commonData.config->useSimuData) == true){ if(commonData.config->getBool(commonData.config->useSimuData) == true){

View File

@@ -8,36 +8,40 @@
Boatdata used Boatdata used
DBS - Water depth DBS - Water depth
HDT - Boat heading HDT - Boat heading, true
AWS - Wind strength; Boat not moving so we assume AWS=TWS and AWD=TWD AWS - Wind strength; Boat not moving so we assume AWS=TWS and AWD=TWD
AWD - Wind direction AWD - Wind direction
LAT/LON - Boat position, current LAT/LON - Boat position, current
HDOP - Position error HDOP - Position error, horizontal
Drop / raise function in device OBP40 has to be done inside Raise function in device OBP40 has to be done inside config mode
config mode because of limited number of buttons. because of limited number of buttons.
TODO TODO
gzip for data transfer, gzip for data transfer,
miniz.c from ROM?
manually inflating with tinflate from ROM manually inflating with tinflate from ROM
Save position in FRAM Save position in FRAM
Alarm: gps fix lost Alarm: gps fix lost
switch unit feet/meter switch unit feet/meter
force map update if new position is different from old position by force map update if new position is different from old position by
a certain level (e.g. 10m) a certain level (e.g. 10m)
windlass integration
chain counter
Map service options / URL parameters Map service options / URL parameters
- mandatory - mandatory
lat: latitude lat: latitude
lon: longitude lon: longitude
width: image width in px width: image width in px (400)
height: image height in px height: image height in px (300)
- optional - optional
zoom: zoom level, default 15 zoom: zoom level, default 15
mrot: map rotation angle in degrees mrot: map rotation angle in degrees
mtype: map type, default="Open Street Map" mtype: map type, default="Open Street Map"
dtype: dithering type, default="Atkinson" dtype: dithering type, default="Atkinson"
cutout: image cutout type 0=none cutout: image cutout type 0=none
alpha: alpha blending for cutout
tab: tab size, 0=none tab: tab size, 0=none
border: border line zize in px, default 2 border: border line zize in px, default 2
symbol: synmol number, default=2 triangle symbol: synmol number, default=2 triangle
@@ -51,6 +55,12 @@
#include <HTTPClient.h> #include <HTTPClient.h>
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "ConfigMenu.h"
// #include "miniz.h" // devices without PSRAM use <rom/miniz.h>
// extern "C" {
#include "rom/miniz.h"
// }
#define anchor_width 16 #define anchor_width 16
#define anchor_height 16 #define anchor_height 16
@@ -64,6 +74,7 @@ class PageAnchor : public Page
private: private:
char mode = 'N'; // (N)ormal, (C)onfig char mode = 'N'; // (N)ormal, (C)onfig
int8_t editmode = -1; // marker for menu/edit/set function int8_t editmode = -1; // marker for menu/edit/set function
ConfigMenu *menu;
//uint8_t *mapbuf = new uint8_t[10000]; // 8450 Byte without header //uint8_t *mapbuf = new uint8_t[10000]; // 8450 Byte without header
//int mapbuf_size = 10000; //int mapbuf_size = 10000;
@@ -81,7 +92,7 @@ private:
String lengthformat; String lengthformat;
double scale = 50; // Radius of display circle in meter, depends on lat double scale = 50; // Radius of display circle in meter, depends on lat
uint8_t zoom = 15; // map zoom level uint8_t zoom = 15; // map zoom level
bool alarm = false; bool alarm = false;
@@ -97,24 +108,38 @@ private:
double anchor_depth; double anchor_depth;
int anchor_ts; // time stamp anchor dropped int anchor_ts; // time stamp anchor dropped
GwApi::BoatValue *bv_dbs; // depth below surface
GwApi::BoatValue *bv_hdt; // true heading
GwApi::BoatValue *bv_aws; // apparent wind speed
GwApi::BoatValue *bv_awd; // apparent wind direction
GwApi::BoatValue *bv_lat; // latitude, current
GwApi::BoatValue *bv_lon; // longitude, current
GwApi::BoatValue *bv_hdop; // horizontal position error
bool simulation = false;
int last_mapsize = 0;
String errmsg = "";
int loops;
int readbytes = 0;
void displayModeNormal(PageData &pageData) { void displayModeNormal(PageData &pageData) {
// Boatvalues: DBS, HDT, AWS, AWD, LAT, LON, HDOP // get currrent boatvalues
GwApi::BoatValue *bv_dbs = pageData.values[0]; // DBS bv_dbs = pageData.values[0]; // DBS
String sval_dbs = formatValue(bv_dbs, *commonData).svalue; String sval_dbs = formatValue(bv_dbs, *commonData).svalue;
String sunit_dbs = formatValue(bv_dbs, *commonData).unit; String sunit_dbs = formatValue(bv_dbs, *commonData).unit;
GwApi::BoatValue *bv_hdt = pageData.values[1]; // HDT bv_hdt = pageData.values[1]; // HDT
String sval_hdt = formatValue(bv_hdt, *commonData).svalue; String sval_hdt = formatValue(bv_hdt, *commonData).svalue;
GwApi::BoatValue *bv_aws = pageData.values[2]; // AWS bv_aws = pageData.values[2]; // AWS
String sval_aws = formatValue(bv_aws, *commonData).svalue; String sval_aws = formatValue(bv_aws, *commonData).svalue;
String sunit_aws = formatValue(bv_aws, *commonData).unit; String sunit_aws = formatValue(bv_aws, *commonData).unit;
GwApi::BoatValue *bv_awd = pageData.values[3]; // AWD bv_awd = pageData.values[3]; // AWD
String sval_awd = formatValue(bv_awd, *commonData).svalue; String sval_awd = formatValue(bv_awd, *commonData).svalue;
GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT bv_lat = pageData.values[4]; // LAT
String sval_lat = formatValue(bv_lat, *commonData).svalue; String sval_lat = formatValue(bv_lat, *commonData).svalue;
GwApi::BoatValue *bv_lon = pageData.values[5]; // LON bv_lon = pageData.values[5]; // LON
String sval_lon = formatValue(bv_lon, *commonData).svalue; String sval_lon = formatValue(bv_lon, *commonData).svalue;
GwApi::BoatValue *bv_hdop = pageData.values[6]; // HDOP bv_hdop = pageData.values[6]; // HDOP
String sval_hdop = formatValue(bv_hdop, *commonData).svalue; String sval_hdop = formatValue(bv_hdop, *commonData).svalue;
String sunit_hdop = formatValue(bv_hdop, *commonData).unit; String sunit_hdop = formatValue(bv_hdop, *commonData).unit;
@@ -156,7 +181,7 @@ private:
{b.x + 5, b.y - 10}, {b.x + 5, b.y - 10},
{b.x + 5, b.y} {b.x + 5, b.y}
}; };
//rotatePoints und dann Linien zeichnen //rotatePoints then draw lines
// TODO rotate boat according to current heading // TODO rotate boat according to current heading
if (bv_hdt->valid) { if (bv_hdt->valid) {
if (map_valid) { if (map_valid) {
@@ -165,7 +190,7 @@ private:
} }
drawPoly(rotatePoints(c, pts_boat, bv_hdt->value * RAD_TO_DEG), commonData->fgcolor); drawPoly(rotatePoints(c, pts_boat, bv_hdt->value * RAD_TO_DEG), commonData->fgcolor);
} else { } else {
// no heading available draw north oriented // no heading available: draw north oriented
if (map_valid) { if (map_valid) {
getdisplay().fillCircle(b.x, b.y - 8, 10, commonData->bgcolor); getdisplay().fillCircle(b.x, b.y - 8, 10, commonData->bgcolor);
} }
@@ -276,23 +301,54 @@ private:
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(8, 250); getdisplay().setCursor(8, 260);
getdisplay().print("Press MODE to leave config"); getdisplay().print("Press BACK to leave config");
getdisplay().setCursor(8, 68); /* getdisplay().setCursor(8, 68);
getdisplay().printf("Server: %s", server_name.c_str()); getdisplay().printf("Server: %s", server_name.c_str());
getdisplay().setCursor(8, 88); getdisplay().setCursor(8, 88);
getdisplay().printf("Port: %d", server_port); getdisplay().printf("Port: %d", server_port);
getdisplay().setCursor(8, 108); getdisplay().setCursor(8, 108);
getdisplay().printf("Tilepath: %s", tile_path.c_str()); getdisplay().printf("Tilepath: %s", tile_path.c_str());
getdisplay().setCursor(8, 128);
getdisplay().printf("Last mapsize: %d", last_mapsize);
getdisplay().setCursor(8, 148);
getdisplay().printf("Last error: %s", errmsg);
getdisplay().setCursor(8, 168);
getdisplay().printf("Loops: %d, Readbytes: %d", loops, readbytes);
*/
GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT
GwApi::BoatValue *bv_lon = pageData.values[5]; // LON GwApi::BoatValue *bv_lon = pageData.values[5]; // LON
if (!bv_lat->valid or !bv_lon->valid) { if (!bv_lat->valid or !bv_lon->valid) {
getdisplay().setCursor(8, 128); getdisplay().setCursor(8, 228);
getdisplay().printf("No valid position: background map disabled"); getdisplay().printf("No valid position: background map disabled");
} }
// Display menu
getdisplay().setFont(&Ubuntu_Bold8pt8b);
for (int i = 0 ; i < menu->getItemCount(); i++) {
ConfigMenuItem *itm = menu->getItemByIndex(i);
if (!itm) {
commonData->logger->logDebug(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: public:
@@ -321,12 +377,32 @@ public:
lengthformat = common.config->getString(common.config->lengthFormat); lengthformat = common.config->getString(common.config->lengthFormat);
chain_length = common.config->getInt(common.config->chainLength); chain_length = common.config->getInt(common.config->chainLength);
if (simulation) {
map_lat = 53.56938345759218;
map_lon = 9.679658234303275;
}
canvas = new GFXcanvas1(264, 260); // Byte aligned, no padding! canvas = new GFXcanvas1(264, 260); // Byte aligned, no padding!
// Initialize config menu
menu = new ConfigMenu("Options", 40, 80);
menu->setItemDimension(150, 20);
ConfigMenuItem *newitem;
newitem = menu->addItem("chain", "Chain out", "int", 0, "m");
newitem->setRange(0, 200, {1, 2, 5, 10});
newitem = menu->addItem("alarm", "Alarm", "bool", 0, "");
newitem = menu->addItem("alarm", "Alarm range", "int", 50, "m");
newitem->setRange(0, 200, {1, 2, 5, 10});
newitem = menu->addItem("raise", "Raise Anchor", "bool", 0, "");
newitem = menu->addItem("zoom", "Zoom", "int", 15, "");
newitem->setRange(14, 17, {1});
menu->setItemActive("chain");
} }
void setupKeys(){ void setupKeys(){
Page::setupKeys(); Page::setupKeys();
commonData->keydata[0].label = "MODE"; commonData->keydata[0].label = "CFG";
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
commonData->keydata[1].label = "DROP"; commonData->keydata[1].label = "DROP";
#endif #endif
@@ -337,13 +413,18 @@ public:
// TODO OBP40 / OBP60 different handling // TODO OBP40 / OBP60 different handling
int handleKey(int key) { int handleKey(int key) {
commonData->logger->logDebug(GwLog::LOG, "Page Anchor handle key %d", key);
if (key == 1) { // Switch between normal and config mode if (key == 1) { // Switch between normal and config mode
if (mode == 'N') { if (mode == 'N') {
mode = 'C'; mode = 'C';
#ifdef BOARD_OBP40S3
commonData->keydata[0].label = "BACK";
commonData->keydata[1].label = "EDIT"; commonData->keydata[1].label = "EDIT";
#endif
} else { } else {
mode = 'N'; mode = 'N';
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
commonData->keydata[0].label = "CFG";
commonData->keydata[1].label = anchor_set ? "RAISE": "DROP"; commonData->keydata[1].label = anchor_set ? "RAISE": "DROP";
#endif #endif
#ifdef BOARD_OBP60S3 #ifdef BOARD_OBP60S3
@@ -353,12 +434,58 @@ public:
return 0; return 0;
} }
if (key == 2) { if (key == 2) {
anchor_set = !anchor_set; if (mode == 'N') {
commonData->keydata[1].label = anchor_set ? "RAISE": "DROP"; anchor_set = !anchor_set;
return 0; commonData->keydata[1].label = anchor_set ? "ALARM": "DROP";
if (anchor_set) {
anchor_lat = bv_lat->value;
anchor_lon = bv_lon->value;
anchor_depth = bv_dbs->value;
// TODO set timestamp
// anchor_ts =
}
return 0;
} else if (mode == 'C') {
// Change edit mode
if (editmode > 0) {
editmode = 0;
commonData->keydata[1].label = "EDIT";
} else {
editmode = 1;
commonData->keydata[1].label = "OK";
}
}
}
if (key == 9) {
// OBP40 Down
if (mode == 'C') {
if (editmode > 0) {
// decrease current menu item
menu->getActiveItem()->decValue();
} else {
// move to next menu item
menu->goNext();
}
return 0;
}
}
if (key == 10) {
// OBP40 Up
if (mode == 'C') {
if (editmode > 0) {
// increase current menu item
ConfigMenuItem *itm = menu->getActiveItem();
commonData->logger->logDebug(GwLog::LOG, "step = %d", itm->getStep());
itm->incValue();
} else {
// move to previous menu item
menu->goPrev();
}
return 0;
}
} }
// Code for keylock // Code for keylock
if (key == 11){ if (key == 11) {
commonData->keylock = !commonData->keylock; commonData->keylock = !commonData->keylock;
return 0; return 0;
} }
@@ -386,43 +513,185 @@ public:
} }
bool valid = false; bool valid = false;
HTTPClient http; HTTPClient http;
const char* headerKeys[] = { "Content-Encoding", "Content-Length" };
http.collectHeaders(headerKeys, 2);
String url = "http://" + server_name + "/" + tile_path; String url = "http://" + server_name + "/" + tile_path;
String parameter = "?lat=" + String(lat, 6) + "&lon=" + String(lon, 6)+ "&zoom=" + String(zoom) String parameter = "?lat=" + String(lat, 6) + "&lon=" + String(lon, 6)+ "&zoom=" + String(zoom)
+ "&width=" + String(map_width) + "&height=" + String(map_height); + "&width=" + String(map_width) + "&height=" + String(map_height);
commonData->logger->logDebug(GwLog::LOG, "HTTP query: %s", String(url + parameter).c_str()); commonData->logger->logDebug(GwLog::LOG, "HTTP query: %s", String(url + parameter).c_str());
http.begin(url + parameter); http.begin(url + parameter);
// http.SetAcceptEncoding("gzip"); http.addHeader("Accept-Encoding", "deflate");
// TODO miniz.c from ROM
int httpCode = http.GET(); int httpCode = http.GET();
if (httpCode > 0) { if (httpCode > 0) {
commonData->logger->logDebug(GwLog::LOG, "HTTP GET result code: %d", httpCode);
if (httpCode == HTTP_CODE_OK) { if (httpCode == HTTP_CODE_OK) {
WiFiClient* stream = http.getStreamPtr(); WiFiClient* stream = http.getStreamPtr();
int size = http.getSize(); int size = http.getSize();
commonData->logger->logDebug(GwLog::LOG, "HTTP get size: %d", size); String encoding = http.header("Content-Encoding");
// header: P4<LF><width> <height><LF> (e.g. 11 byte) commonData->logger->logDebug(GwLog::LOG, "HTTP size: %d, encoding: '%s'", size, encoding);
bool is_gzip = encoding.equalsIgnoreCase("deflate");
uint8_t header[14]; // max: P4<LF>wwww wwww<LF> uint8_t header[14]; // max: P4<LF>wwww wwww<LF>
bool header_read = false;
int header_size = 0; int header_size = 0;
uint8_t* buf = canvas->getBuffer(); bool header_read = false;
int n = 0; int n = 0;
int ix = 0; int ix = 0;
while (stream->available()) {
uint8_t b = stream->read(); uint8_t* buf = canvas->getBuffer();
n += 1;
if ((! header_read) and (n < 13) ) { if (is_gzip) {
header[n-1] = b; /* gzip compressed data
if ((n > 3) and (b == 0x0a)) { * has to be decompressed into a buffer big enough
header_read = true; * to hold the whole data.
header_size = n; * so the PBM header is included
header[n] = 0; * search a method to use that as canvas without
* additional copy
*/
commonData->logger->logDebug(GwLog::LOG, "Map received in gzip encoding");
#define HEADER_MAX 24
#define HTTP_CHUNK 512
uint8_t in_buf[HTTP_CHUNK];
uint8_t header_buf[HEADER_MAX];
tinfl_decompressor decomp;
tinfl_init(&decomp);
size_t bitmap_written = 0;
size_t header_written = 0;
bool header_done = false;
int row_bytes = 0;
size_t expected_bitmap = 0;
while (stream->connected() || stream->available()) {
int bytes_read = stream->read(in_buf, HTTP_CHUNK);
if (bytes_read <= 0) break;
commonData->logger->logDebug(GwLog::LOG, "stream: bytes_read=%d", bytes_read);
size_t in_ofs = 0; // offset
while (in_ofs < (size_t)bytes_read) {
size_t in_size = bytes_read - in_ofs;
size_t out_size;
uint8_t *out_ptr;
uint8_t *out_ptr_next;
if (!header_done) {
if (header_written >= HEADER_MAX) {
commonData->logger->logDebug(GwLog::LOG, "PBM header too large");
return false;
}
out_ptr = header_buf + header_written;
out_size = HEADER_MAX - header_written;
} else {
out_ptr = buf + bitmap_written;
out_size = expected_bitmap - bitmap_written;
}
commonData->logger->logDebug(GwLog::LOG, "in_size=%d, out_size=%d", in_size, out_size);
// TODO correct loop !!!
// tinfl_status tinfl_decompress(
// tinfl_decompressor *r,
// const mz_uint8 *pIn_buf_next,
// size_t *pIn_buf_size,
// mz_uint8 *pOut_buf_start
// mz_uint8 *pOut_buf_next,
// size_t *pOut_buf_size,
// const mz_uint32 decomp_flags)
tinfl_status status = tinfl_decompress(
&decomp,
in_buf + in_ofs, // start address in input buffer
&in_size, // number of bytes to process
out_ptr, // start of output buffer
out_ptr, // next write position in output buffer
&out_size, // free size in output buffer
// TINFL_FLAG_PARSE_ZLIB_HEADER |
TINFL_FLAG_HAS_MORE_INPUT |
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF
);
if (status < 0) {
commonData->logger->logDebug(GwLog::LOG, "Decompression error (%d)", status);
return false;
}
in_ofs += in_size;
commonData->logger->logDebug(GwLog::LOG, "in_size=%d, in_ofs=%d", in_size, in_ofs);
if (!header_done) {
commonData->logger->logDebug(GwLog::LOG, "Decoding header");
header_written += out_size;
// Detect header end: two '\n'
char *first_nl = strchr((char*)header_buf, '\n');
if (!first_nl) continue;
char *second_nl = strchr(first_nl + 1, '\n');
if (!second_nl) continue;
// Null-terminate header for sscanf
header_buf[header_written < HEADER_MAX ? header_written : HEADER_MAX - 1] = 0;
// Check magic
if (strncmp((char*)header_buf, "P4", 2) != 0) {
commonData->logger->logDebug(GwLog::LOG, "Invalid PBM magic");
return false;
}
// Parse width and height strictly
int header_width = 0, header_height = 0;
if (sscanf((char*)header_buf, "P4\n%d %d", &header_width, &header_height) != 2) {
commonData->logger->logDebug(GwLog::LOG, "Failed to parse PBM dimensions");
return false;
}
if (header_width != map_width || header_height != map_height) {
commonData->logger->logDebug(GwLog::LOG, "PBM size mismatch: header %dx%d, requested %dx%d\n",
header_width, header_height, map_width, map_height);
return false;
}
commonData->logger->logDebug(GwLog::LOG, "Header: %dx%d", header_width, header_height);
// Compute row bytes and expected bitmap size
row_bytes = (header_width + 7) / 8;
commonData->logger->logDebug(GwLog::LOG, "row_bytes=%d", row_bytes);
expected_bitmap = (size_t)row_bytes * header_height;
commonData->logger->logDebug(GwLog::LOG, "expected_bitmap=%d", expected_bitmap);
// Copy any extra decompressed bitmap after header
size_t header_size = (second_nl + 1) - (char*)header_buf;
commonData->logger->logDebug(GwLog::LOG, "header_size=%d", header_size);
size_t extra_bitmap = header_written - header_size;
commonData->logger->logDebug(GwLog::LOG, "extra bitmap=%d", extra_bitmap);
header_done = true;
if (extra_bitmap > 0) {
memcpy(buf, header_buf + header_size, extra_bitmap);
bitmap_written = extra_bitmap;
}
} else {
bitmap_written += out_size;
if (bitmap_written >= expected_bitmap) {
commonData->logger->logDebug(GwLog::LOG, "Image fully received");
}
}
commonData->logger->logDebug(GwLog::LOG, "bitmap_written=%d", bitmap_written);
} }
} else {
// write image data to canvas buffer
buf[ix++] = b;
} }
} } else {
if (n == size) { // uncompressed data
valid = true; commonData->logger->logDebug(GwLog::LOG, "Map received uncompressed");
while (stream->available()) {
uint8_t b = stream->read();
n += 1;
if ((! header_read) and (n < 13) ) {
header[n-1] = b;
if ((n > 3) and (b == 0x0a)) {
header_read = true;
header_size = n;
header[n] = 0;
}
} else {
// write image data to canvas buffer
buf[ix++] = b;
}
}
if (n == size) {
valid = true;
}
} }
commonData->logger->logDebug(GwLog::LOG, "HTTP: final bytesRead=%d, header-size=%d", n, header_size); commonData->logger->logDebug(GwLog::LOG, "HTTP: final bytesRead=%d, header-size=%d", n, header_size);
} else { } else {
@@ -446,6 +715,8 @@ public:
return; return;
} }
errmsg = "";
map_lat = bv_lat->value; // save for later comparison map_lat = bv_lat->value; // save for later comparison
map_lon = bv_lon->value; map_lon = bv_lon->value;
map_valid = getBackgroundMap(map_lat, map_lon, zoom); map_valid = getBackgroundMap(map_lat, map_lon, zoom);
@@ -456,11 +727,14 @@ public:
} }
}; };
void display_side_keys() {
// An rechter Seite neben dem Rad inc, dec, set etc ?
}
int displayPage(PageData &pageData) { int displayPage(PageData &pageData) {
GwLog *logger = commonData->logger;
// Logging boat values // Logging boat values
logger->logDebug(GwLog::LOG, "Drawing at PageAnchor; Mode=%c", mode); commonData->logger->logDebug(GwLog::LOG, "Drawing at PageAnchor; Mode=%c", mode);
// Set display in partial refresh mode // Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update

View File

@@ -110,6 +110,7 @@ typedef struct{
AlarmData alarm; AlarmData alarm;
GwApi::BoatValue *time = nullptr; GwApi::BoatValue *time = nullptr;
GwApi::BoatValue *date = nullptr; GwApi::BoatValue *date = nullptr;
float tz = 0.0; // timezone from config
uint16_t fgcolor; uint16_t fgcolor;
uint16_t bgcolor; uint16_t bgcolor;
bool keylock = false; bool keylock = false;

View File

@@ -19,28 +19,6 @@
"obp40": "true" "obp40": "true"
} }
}, },
{
"name": "mapServer",
"label": "map server",
"type": "string",
"default": "",
"description": "Server for converting map tiles. Use only one hostname or IP address",
"category": "wifi client",
"capabilities": {
"obp40": "true"
}
},
{
"name": "mapTilePath",
"label": "map tile path",
"type": "string",
"default": "map.php",
"description": "Path to converter access e.g. index.php or map.php",
"category": "wifi client",
"capabilities": {
"obp40": "true"
}
},
{ {
"name": "timeZone", "name": "timeZone",
"label": "Time Zone", "label": "Time Zone",
@@ -1574,7 +1552,6 @@
"obp40": "true" "obp40": "true"
} }
}, },
{ {
"name": "page1type", "name": "page1type",
"label": "Type", "label": "Type",

View File

@@ -19,28 +19,6 @@
"obp60": "true" "obp60": "true"
} }
}, },
{
"name": "mapServer",
"label": "map server",
"type": "string",
"default": "",
"description": "Server for converting map tiles. Use only one hostname or IP address",
"category": "wifi client",
"capabilities": {
"obp40": "true"
}
},
{
"name": "mapTilePath",
"label": "map tile path",
"type": "string",
"default": "map.php",
"description": "Path to converter access e.g. index.php or map.php",
"category": "wifi client",
"capabilities": {
"obp40": "true"
}
},
{ {
"name": "timeZone", "name": "timeZone",
"label": "Time Zone", "label": "Time Zone",
@@ -1129,6 +1107,34 @@
{ "mapsource": ["Local Service"] } { "mapsource": ["Local Service"] }
] ]
}, },
{
"name": "mapServer",
"label": "map server",
"type": "string",
"default": "",
"description": "Server for converting map tiles. Use only one hostname or IP address",
"category": "OBP60 Navigation",
"capabilities": {
"obp60": "true"
},
"condition": [
{ "mapsource": ["Remote Service"] }
]
},
{
"name": "mapTilePath",
"label": "map tile path",
"type": "string",
"default": "map.php",
"description": "Path to converter access e.g. index.php or map.php",
"category": "OBP40 Navigation",
"capabilities": {
"obp40": "true"
},
"condition": [
{ "mapsource": ["Remote Service"] }
]
},
{ {
"name": "maptype", "name": "maptype",
"label": "Map Type", "label": "Map Type",

View File

@@ -527,8 +527,8 @@ void OBP60Task(GwApi *api){
// Configuration values for main loop // Configuration values for main loop
String gpsFix = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString(); String gpsFix = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString();
String gpsOn=api->getConfig()->getConfigItem(api->getConfig()->useGPS,true)->asString(); String gpsOn=api->getConfig()->getConfigItem(api->getConfig()->useGPS,true)->asString();
float tz = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asFloat();
commonData.tz = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asFloat();
commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight,true)->asString()); commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight,true)->asString());
commonData.backlight.color = colorMapping(config->getConfigItem(config->blColor,true)->asString()); commonData.backlight.color = colorMapping(config->getConfigItem(config->blColor,true)->asString());
commonData.backlight.brightness = uint(config->getConfigItem(config->blBrightness,true)->asInt()); commonData.backlight.brightness = uint(config->getConfigItem(config->blBrightness,true)->asInt());
@@ -703,7 +703,7 @@ void OBP60Task(GwApi *api){
starttime5 = millis(); starttime5 = millis();
if(time->valid == true && date->valid == true && lat->valid == true && lon->valid == true){ if(time->valid == true && date->valid == true && lat->valid == true && lon->valid == true){
// Provide sundata to all pages // Provide sundata to all pages
commonData.sundata = calcSunsetSunrise(time->value , date->value, lat->value, lon->value, tz); commonData.sundata = calcSunsetSunrise(time->value , date->value, lat->value, lon->value, commonData.tz);
// Backlight with sun control // Backlight with sun control
if (commonData.backlight.mode == BacklightMode::SUN) { if (commonData.backlight.mode == BacklightMode::SUN) {
// if(String(backlight) == "Control by Sun"){ // if(String(backlight) == "Control by Sun"){
@@ -716,7 +716,7 @@ void OBP60Task(GwApi *api){
} }
} else if (homevalid and commonData.data.rtcValid) { } else if (homevalid and commonData.data.rtcValid) {
// No gps fix but valid home location and time configured // No gps fix but valid home location and time configured
commonData.sundata = calcSunsetSunriseRTC(&commonData.data.rtcTime, homelat, homelon, tz); commonData.sundata = calcSunsetSunriseRTC(&commonData.data.rtcTime, homelat, homelon, commonData.tz);
} }
} }

View File

@@ -45,8 +45,6 @@ lib_deps =
milesburton/DallasTemperature@3.11.0 milesburton/DallasTemperature@3.11.0
signetica/SunRise@2.0.2 signetica/SunRise@2.0.2
adafruit/Adafruit FRAM I2C@2.0.3 adafruit/Adafruit FRAM I2C@2.0.3
WifiClientSecure
HTTPClient
build_flags= build_flags=
#https://thingpulse.com/usb-settings-for-logging-with-the-esp32-s3-in-platformio/?srsltid=AfmBOopGskbkr4GoeVkNlFaZXe_zXkLceKF6Rn-tmoXABCeAR2vWsdHL #https://thingpulse.com/usb-settings-for-logging-with-the-esp32-s3-in-platformio/?srsltid=AfmBOopGskbkr4GoeVkNlFaZXe_zXkLceKF6Rn-tmoXABCeAR2vWsdHL
# -D CORE_DEBUG_LEVEL=1 #Debug level for CPU core via CDC (serial device) # -D CORE_DEBUG_LEVEL=1 #Debug level for CPU core via CDC (serial device)
@@ -103,16 +101,14 @@ lib_deps =
milesburton/DallasTemperature@3.11.0 milesburton/DallasTemperature@3.11.0
signetica/SunRise@2.0.2 signetica/SunRise@2.0.2
adafruit/Adafruit FRAM I2C@2.0.3 adafruit/Adafruit FRAM I2C@2.0.3
WifiClientSecure
HTTPClient
build_flags= build_flags=
-D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib -D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib
-D BOARD_OBP40S3 #Board OBP40 with ESP32S3 -D BOARD_OBP40S3 #Board OBP40 with ESP32S3
-D HARDWARE_V10 #OBP40 hardware revision V1.0 SKU:DIE07300S V1.1 (CrowPanel 4.2) -D HARDWARE_V10 #OBP40 hardware revision V1.0 SKU:DIE07300S V1.1 (CrowPanel 4.2)
-D DISPLAY_GDEY042T81 #new E-Ink display from Good Display (Waveshare), R10 2.2 ohm - good (contast lost by shunshine) -D DISPLAY_GDEY042T81 #new E-Ink display from Good Display (Waveshare), R10 2.2 ohm - good (contast lost by shunshine)
#-D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good #-D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh #-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors #-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
#-D ENABLE_PATCHES #enable patching of gateway code #-D ENABLE_PATCHES #enable patching of gateway code
${env.build_flags} ${env.build_flags}
upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter