mirror of
https://github.com/thooge/esp32-nmea2000-obp60.git
synced 2026-03-28 09:56:37 +01:00
More work on page anchor
This commit is contained in:
217
lib/obp60task/ConfigMenu.cpp
Normal file
217
lib/obp60task/ConfigMenu.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
67
lib/obp60task/ConfigMenu.h
Normal file
67
lib/obp60task/ConfigMenu.h
Normal 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();
|
||||
};
|
||||
@@ -475,6 +475,30 @@ std::vector<String> wordwrap(String &line, uint16_t maxwidth) {
|
||||
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
|
||||
void drawTextCenter(int16_t cx, int16_t cy, String text) {
|
||||
int16_t x1, y1;
|
||||
@@ -638,20 +662,19 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa
|
||||
// Date and time
|
||||
String fmttype = commonData.config->getString(commonData.config->dateFormat);
|
||||
String timesource = commonData.config->getString(commonData.config->timeSource);
|
||||
double tz = commonData.config->getString(commonData.config->timeZone).toDouble();
|
||||
getdisplay().setTextColor(commonData.fgcolor);
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
getdisplay().setCursor(230, 15);
|
||||
if (timesource == "RTC" or timesource == "iRTC") {
|
||||
// TODO take DST into account
|
||||
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);
|
||||
getdisplay().print(formatTime('m', local_tm->tm_hour, local_tm->tm_min, 0));
|
||||
getdisplay().print(" ");
|
||||
getdisplay().print(formatDate(fmttype, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday));
|
||||
getdisplay().print(" ");
|
||||
getdisplay().print(tz == 0 ? "UTC" : "LOT");
|
||||
getdisplay().print(commonData.tz == 0 ? "UTC" : "LOT");
|
||||
} else {
|
||||
drawTextRalign(396, 15, "RTC invalid");
|
||||
}
|
||||
@@ -666,7 +689,7 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa
|
||||
getdisplay().print(" ");
|
||||
getdisplay().print(actdate);
|
||||
getdisplay().print(" ");
|
||||
getdisplay().print(tz == 0 ? "UTC" : "LOT");
|
||||
getdisplay().print(commonData.tz == 0 ? "UTC" : "LOT");
|
||||
}
|
||||
else{
|
||||
if(commonData.config->getBool(commonData.config->useSimuData) == true){
|
||||
|
||||
@@ -8,36 +8,40 @@
|
||||
|
||||
Boatdata used
|
||||
DBS - Water depth
|
||||
HDT - Boat heading
|
||||
HDT - Boat heading, true
|
||||
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
|
||||
HDOP - Position error, horizontal
|
||||
|
||||
Drop / raise function in device OBP40 has to be done inside
|
||||
config mode because of limited number of buttons.
|
||||
Raise function in device OBP40 has to be done inside config mode
|
||||
because of limited number of buttons.
|
||||
|
||||
TODO
|
||||
gzip for data transfer,
|
||||
miniz.c from ROM?
|
||||
manually inflating with tinflate from ROM
|
||||
Save position in FRAM
|
||||
Alarm: gps fix lost
|
||||
switch unit feet/meter
|
||||
force map update if new position is different from old position by
|
||||
a certain level (e.g. 10m)
|
||||
windlass integration
|
||||
chain counter
|
||||
|
||||
Map service options / URL parameters
|
||||
- mandatory
|
||||
lat: latitude
|
||||
lon: longitude
|
||||
width: image width in px
|
||||
height: image height in px
|
||||
width: image width in px (400)
|
||||
height: image height in px (300)
|
||||
- optional
|
||||
zoom: zoom level, default 15
|
||||
mrot: map rotation angle in degrees
|
||||
mtype: map type, default="Open Street Map"
|
||||
dtype: dithering type, default="Atkinson"
|
||||
cutout: image cutout type 0=none
|
||||
alpha: alpha blending for cutout
|
||||
tab: tab size, 0=none
|
||||
border: border line zize in px, default 2
|
||||
symbol: synmol number, default=2 triangle
|
||||
@@ -51,6 +55,12 @@
|
||||
#include <HTTPClient.h>
|
||||
#include "Pagedata.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_height 16
|
||||
@@ -64,6 +74,7 @@ class PageAnchor : public Page
|
||||
private:
|
||||
char mode = 'N'; // (N)ormal, (C)onfig
|
||||
int8_t editmode = -1; // marker for menu/edit/set function
|
||||
ConfigMenu *menu;
|
||||
|
||||
//uint8_t *mapbuf = new uint8_t[10000]; // 8450 Byte without header
|
||||
//int mapbuf_size = 10000;
|
||||
@@ -97,24 +108,38 @@ private:
|
||||
double anchor_depth;
|
||||
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) {
|
||||
|
||||
// Boatvalues: DBS, HDT, AWS, AWD, LAT, LON, HDOP
|
||||
GwApi::BoatValue *bv_dbs = pageData.values[0]; // DBS
|
||||
// get currrent boatvalues
|
||||
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
|
||||
bv_hdt = pageData.values[1]; // HDT
|
||||
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 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;
|
||||
GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT
|
||||
bv_lat = pageData.values[4]; // LAT
|
||||
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;
|
||||
GwApi::BoatValue *bv_hdop = pageData.values[6]; // HDOP
|
||||
bv_hdop = pageData.values[6]; // HDOP
|
||||
String sval_hdop = formatValue(bv_hdop, *commonData).svalue;
|
||||
String sunit_hdop = formatValue(bv_hdop, *commonData).unit;
|
||||
|
||||
@@ -156,7 +181,7 @@ private:
|
||||
{b.x + 5, b.y - 10},
|
||||
{b.x + 5, b.y}
|
||||
};
|
||||
//rotatePoints und dann Linien zeichnen
|
||||
//rotatePoints then draw lines
|
||||
// TODO rotate boat according to current heading
|
||||
if (bv_hdt->valid) {
|
||||
if (map_valid) {
|
||||
@@ -165,7 +190,7 @@ private:
|
||||
}
|
||||
drawPoly(rotatePoints(c, pts_boat, bv_hdt->value * RAD_TO_DEG), commonData->fgcolor);
|
||||
} else {
|
||||
// no heading available draw north oriented
|
||||
// no heading available: draw north oriented
|
||||
if (map_valid) {
|
||||
getdisplay().fillCircle(b.x, b.y - 8, 10, commonData->bgcolor);
|
||||
}
|
||||
@@ -276,23 +301,54 @@ private:
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
|
||||
getdisplay().setCursor(8, 250);
|
||||
getdisplay().print("Press MODE to leave config");
|
||||
getdisplay().setCursor(8, 260);
|
||||
getdisplay().print("Press BACK to leave config");
|
||||
|
||||
getdisplay().setCursor(8, 68);
|
||||
/* getdisplay().setCursor(8, 68);
|
||||
getdisplay().printf("Server: %s", server_name.c_str());
|
||||
getdisplay().setCursor(8, 88);
|
||||
getdisplay().printf("Port: %d", server_port);
|
||||
getdisplay().setCursor(8, 108);
|
||||
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_lon = pageData.values[5]; // LON
|
||||
if (!bv_lat->valid or !bv_lon->valid) {
|
||||
getdisplay().setCursor(8, 128);
|
||||
getdisplay().setCursor(8, 228);
|
||||
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:
|
||||
@@ -321,12 +377,32 @@ public:
|
||||
lengthformat = common.config->getString(common.config->lengthFormat);
|
||||
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!
|
||||
|
||||
// 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(){
|
||||
Page::setupKeys();
|
||||
commonData->keydata[0].label = "MODE";
|
||||
commonData->keydata[0].label = "CFG";
|
||||
#ifdef BOARD_OBP40S3
|
||||
commonData->keydata[1].label = "DROP";
|
||||
#endif
|
||||
@@ -337,13 +413,18 @@ public:
|
||||
|
||||
// TODO OBP40 / OBP60 different handling
|
||||
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 (mode == 'N') {
|
||||
mode = 'C';
|
||||
#ifdef BOARD_OBP40S3
|
||||
commonData->keydata[0].label = "BACK";
|
||||
commonData->keydata[1].label = "EDIT";
|
||||
#endif
|
||||
} else {
|
||||
mode = 'N';
|
||||
#ifdef BOARD_OBP40S3
|
||||
commonData->keydata[0].label = "CFG";
|
||||
commonData->keydata[1].label = anchor_set ? "RAISE": "DROP";
|
||||
#endif
|
||||
#ifdef BOARD_OBP60S3
|
||||
@@ -353,12 +434,58 @@ public:
|
||||
return 0;
|
||||
}
|
||||
if (key == 2) {
|
||||
if (mode == 'N') {
|
||||
anchor_set = !anchor_set;
|
||||
commonData->keydata[1].label = anchor_set ? "RAISE": "DROP";
|
||||
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
|
||||
if (key == 11){
|
||||
if (key == 11) {
|
||||
commonData->keylock = !commonData->keylock;
|
||||
return 0;
|
||||
}
|
||||
@@ -386,26 +513,167 @@ public:
|
||||
}
|
||||
bool valid = false;
|
||||
HTTPClient http;
|
||||
const char* headerKeys[] = { "Content-Encoding", "Content-Length" };
|
||||
http.collectHeaders(headerKeys, 2);
|
||||
String url = "http://" + server_name + "/" + tile_path;
|
||||
String parameter = "?lat=" + String(lat, 6) + "&lon=" + String(lon, 6)+ "&zoom=" + String(zoom)
|
||||
+ "&width=" + String(map_width) + "&height=" + String(map_height);
|
||||
commonData->logger->logDebug(GwLog::LOG, "HTTP query: %s", String(url + parameter).c_str());
|
||||
http.begin(url + parameter);
|
||||
// http.SetAcceptEncoding("gzip");
|
||||
// TODO miniz.c from ROM
|
||||
http.addHeader("Accept-Encoding", "deflate");
|
||||
int httpCode = http.GET();
|
||||
if (httpCode > 0) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "HTTP GET result code: %d", httpCode);
|
||||
if (httpCode == HTTP_CODE_OK) {
|
||||
WiFiClient* stream = http.getStreamPtr();
|
||||
int size = http.getSize();
|
||||
commonData->logger->logDebug(GwLog::LOG, "HTTP get size: %d", size);
|
||||
// header: P4<LF><width> <height><LF> (e.g. 11 byte)
|
||||
String encoding = http.header("Content-Encoding");
|
||||
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>
|
||||
bool header_read = false;
|
||||
int header_size = 0;
|
||||
uint8_t* buf = canvas->getBuffer();
|
||||
bool header_read = false;
|
||||
int n = 0;
|
||||
int ix = 0;
|
||||
|
||||
uint8_t* buf = canvas->getBuffer();
|
||||
|
||||
if (is_gzip) {
|
||||
/* gzip compressed data
|
||||
* has to be decompressed into a buffer big enough
|
||||
* to hold the whole data.
|
||||
* so the PBM header is included
|
||||
* 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 {
|
||||
// uncompressed data
|
||||
commonData->logger->logDebug(GwLog::LOG, "Map received uncompressed");
|
||||
while (stream->available()) {
|
||||
uint8_t b = stream->read();
|
||||
n += 1;
|
||||
@@ -424,6 +692,7 @@ public:
|
||||
if (n == size) {
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
commonData->logger->logDebug(GwLog::LOG, "HTTP: final bytesRead=%d, header-size=%d", n, header_size);
|
||||
} else {
|
||||
commonData->logger->logDebug(GwLog::LOG, "HTTP result #%d", httpCode);
|
||||
@@ -446,6 +715,8 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
errmsg = "";
|
||||
|
||||
map_lat = bv_lat->value; // save for later comparison
|
||||
map_lon = bv_lon->value;
|
||||
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) {
|
||||
GwLog *logger = commonData->logger;
|
||||
|
||||
// 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
|
||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||
|
||||
@@ -110,6 +110,7 @@ typedef struct{
|
||||
AlarmData alarm;
|
||||
GwApi::BoatValue *time = nullptr;
|
||||
GwApi::BoatValue *date = nullptr;
|
||||
float tz = 0.0; // timezone from config
|
||||
uint16_t fgcolor;
|
||||
uint16_t bgcolor;
|
||||
bool keylock = false;
|
||||
|
||||
@@ -19,28 +19,6 @@
|
||||
"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",
|
||||
"label": "Time Zone",
|
||||
@@ -1574,7 +1552,6 @@
|
||||
"obp40": "true"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "page1type",
|
||||
"label": "Type",
|
||||
|
||||
@@ -19,28 +19,6 @@
|
||||
"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",
|
||||
"label": "Time Zone",
|
||||
@@ -1129,6 +1107,34 @@
|
||||
{ "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",
|
||||
"label": "Map Type",
|
||||
|
||||
@@ -527,8 +527,8 @@ void OBP60Task(GwApi *api){
|
||||
// Configuration values for main loop
|
||||
String gpsFix = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString();
|
||||
String gpsOn=api->getConfig()->getConfigItem(api->getConfig()->useGPS,true)->asString();
|
||||
float tz = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asFloat();
|
||||
|
||||
commonData.tz = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asFloat();
|
||||
commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight,true)->asString());
|
||||
commonData.backlight.color = colorMapping(config->getConfigItem(config->blColor,true)->asString());
|
||||
commonData.backlight.brightness = uint(config->getConfigItem(config->blBrightness,true)->asInt());
|
||||
@@ -703,7 +703,7 @@ void OBP60Task(GwApi *api){
|
||||
starttime5 = millis();
|
||||
if(time->valid == true && date->valid == true && lat->valid == true && lon->valid == true){
|
||||
// Provide sundata to all pages
|
||||
commonData.sundata = calcSunsetSunrise(time->value , date->value, lat->value, lon->value, tz);
|
||||
commonData.sundata = calcSunsetSunrise(time->value , date->value, lat->value, lon->value, commonData.tz);
|
||||
// Backlight with sun control
|
||||
if (commonData.backlight.mode == BacklightMode::SUN) {
|
||||
// if(String(backlight) == "Control by Sun"){
|
||||
@@ -716,7 +716,7 @@ void OBP60Task(GwApi *api){
|
||||
}
|
||||
} else if (homevalid and commonData.data.rtcValid) {
|
||||
// No gps fix but valid home location and time configured
|
||||
commonData.sundata = calcSunsetSunriseRTC(&commonData.data.rtcTime, homelat, homelon, tz);
|
||||
commonData.sundata = calcSunsetSunriseRTC(&commonData.data.rtcTime, homelat, homelon, commonData.tz);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,6 @@ lib_deps =
|
||||
milesburton/DallasTemperature@3.11.0
|
||||
signetica/SunRise@2.0.2
|
||||
adafruit/Adafruit FRAM I2C@2.0.3
|
||||
WifiClientSecure
|
||||
HTTPClient
|
||||
build_flags=
|
||||
#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)
|
||||
@@ -103,16 +101,14 @@ lib_deps =
|
||||
milesburton/DallasTemperature@3.11.0
|
||||
signetica/SunRise@2.0.2
|
||||
adafruit/Adafruit FRAM I2C@2.0.3
|
||||
WifiClientSecure
|
||||
HTTPClient
|
||||
build_flags=
|
||||
-D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib
|
||||
-D BOARD_OBP40S3 #Board OBP40 with ESP32S3
|
||||
-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_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 VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
|
||||
#-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
|
||||
#-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
|
||||
#-D ENABLE_PATCHES #enable patching of gateway code
|
||||
${env.build_flags}
|
||||
upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter
|
||||
|
||||
Reference in New Issue
Block a user