mirror of
https://github.com/thooge/esp32-nmea2000-obp60.git
synced 2026-03-28 18:06:37 +01:00
Compare commits
11 Commits
anchor
...
66e71acac3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66e71acac3 | ||
|
|
b85504bf50 | ||
|
|
3043be8e1d | ||
|
|
02b2c888ee | ||
|
|
00b06f458b | ||
|
|
6c7997e369 | ||
|
|
7f747e9b35 | ||
|
|
71512e7262 | ||
|
|
4468c0555b | ||
|
|
99404991a3 | ||
|
|
ee5077e0a5 |
@@ -2,6 +2,9 @@
|
|||||||
#define _GWWIFI_H
|
#define _GWWIFI_H
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <GWConfig.h>
|
#include <GWConfig.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
|
||||||
class GwWifi{
|
class GwWifi{
|
||||||
private:
|
private:
|
||||||
const GwConfigHandler *config;
|
const GwConfigHandler *config;
|
||||||
@@ -16,13 +19,19 @@ class GwWifi{
|
|||||||
bool apActive=false;
|
bool apActive=false;
|
||||||
bool fixedApPass=true;
|
bool fixedApPass=true;
|
||||||
bool clientIsConnected=false;
|
bool clientIsConnected=false;
|
||||||
|
SemaphoreHandle_t wifiMutex=nullptr;
|
||||||
|
static const TickType_t WIFI_MUTEX_TIMEOUT=pdMS_TO_TICKS(1000);
|
||||||
|
bool acquireMutex();
|
||||||
|
void releaseMutex();
|
||||||
public:
|
public:
|
||||||
const char *AP_password = "esp32nmea2k";
|
const char *AP_password = "esp32nmea2k";
|
||||||
GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass=true);
|
GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass=true);
|
||||||
|
~GwWifi();
|
||||||
void setup();
|
void setup();
|
||||||
void loop();
|
void loop();
|
||||||
bool clientConnected();
|
bool clientConnected();
|
||||||
bool connectClient();
|
bool connectClient(); // Blocking version
|
||||||
|
bool connectClientAsync(); // Non-blocking version for other tasks
|
||||||
String apIP();
|
String apIP();
|
||||||
bool isApActive(){return apActive;}
|
bool isApActive(){return apActive;}
|
||||||
bool isClientActive(){return wifiClient->asBoolean();}
|
bool isClientActive(){return wifiClient->asBoolean();}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include <esp_wifi.h>
|
#include <esp_wifi.h>
|
||||||
#include "GWWifi.h"
|
#include "GWWifi.h"
|
||||||
|
|
||||||
|
|
||||||
GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
|
GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
|
||||||
this->config=config;
|
this->config=config;
|
||||||
this->logger=log;
|
this->logger=log;
|
||||||
@@ -9,6 +8,28 @@ GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
|
|||||||
wifiSSID=config->getConfigItem(config->wifiSSID,true);
|
wifiSSID=config->getConfigItem(config->wifiSSID,true);
|
||||||
wifiPass=config->getConfigItem(config->wifiPass,true);
|
wifiPass=config->getConfigItem(config->wifiPass,true);
|
||||||
this->fixedApPass=fixedApPass;
|
this->fixedApPass=fixedApPass;
|
||||||
|
wifiMutex=xSemaphoreCreateMutex();
|
||||||
|
if (wifiMutex==nullptr){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: unable to create mutex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GwWifi::~GwWifi(){
|
||||||
|
if (wifiMutex!=nullptr){
|
||||||
|
vSemaphoreDelete(wifiMutex);
|
||||||
|
wifiMutex=nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GwWifi::acquireMutex(){
|
||||||
|
if (wifiMutex==nullptr) return false;
|
||||||
|
return xSemaphoreTake(wifiMutex,WIFI_MUTEX_TIMEOUT)==pdTRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GwWifi::releaseMutex(){
|
||||||
|
if (wifiMutex!=nullptr){
|
||||||
|
xSemaphoreGive(wifiMutex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void GwWifi::setup(){
|
void GwWifi::setup(){
|
||||||
LOG_DEBUG(GwLog::LOG,"Wifi setup");
|
LOG_DEBUG(GwLog::LOG,"Wifi setup");
|
||||||
@@ -85,8 +106,14 @@ bool GwWifi::connectInternal(){
|
|||||||
if (wifiClient->asBoolean()){
|
if (wifiClient->asBoolean()){
|
||||||
clientIsConnected=false;
|
clientIsConnected=false;
|
||||||
LOG_DEBUG(GwLog::LOG,"creating wifiClient ssid=%s",wifiSSID->asString().c_str());
|
LOG_DEBUG(GwLog::LOG,"creating wifiClient ssid=%s",wifiSSID->asString().c_str());
|
||||||
|
// CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden
|
||||||
|
if (!acquireMutex()){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in connectInternal");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
WiFi.setAutoReconnect(false); //#102
|
WiFi.setAutoReconnect(false); //#102
|
||||||
wl_status_t rt=WiFi.begin(wifiSSID->asCString(),wifiPass->asCString());
|
wl_status_t rt=WiFi.begin(wifiSSID->asCString(),wifiPass->asCString());
|
||||||
|
releaseMutex();
|
||||||
LOG_DEBUG(GwLog::LOG,"wifiClient connect returns %d",(int)rt);
|
LOG_DEBUG(GwLog::LOG,"wifiClient connect returns %d",(int)rt);
|
||||||
lastConnectStart=millis();
|
lastConnectStart=millis();
|
||||||
return true;
|
return true;
|
||||||
@@ -104,8 +131,20 @@ void GwWifi::loop(){
|
|||||||
if (lastConnectStart > now || (lastConnectStart + RETRY_MILLIS) < now)
|
if (lastConnectStart > now || (lastConnectStart + RETRY_MILLIS) < now)
|
||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString());
|
LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString());
|
||||||
WiFi.disconnect();
|
|
||||||
connectInternal();
|
// CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden
|
||||||
|
if (acquireMutex()){
|
||||||
|
WiFi.disconnect(true);
|
||||||
|
delay(300);
|
||||||
|
esp_wifi_stop();
|
||||||
|
delay(100);
|
||||||
|
esp_wifi_start();
|
||||||
|
releaseMutex();
|
||||||
|
connectInternal();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
@@ -126,11 +165,42 @@ void GwWifi::loop(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GwWifi::clientConnected(){
|
bool GwWifi::clientConnected(){
|
||||||
return WiFi.status() == WL_CONNECTED;
|
// CRITICAL SECTION: WiFi.status() muss geschützt werden
|
||||||
|
if (!acquireMutex()){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in clientConnected");
|
||||||
|
return false; // Conservative: nehme an, nicht verbunden
|
||||||
|
}
|
||||||
|
bool result = WiFi.status() == WL_CONNECTED;
|
||||||
|
releaseMutex();
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool GwWifi::connectClient(){
|
bool GwWifi::connectClient(){
|
||||||
|
// CRITICAL SECTION: Disconnect und Connect müssen atomar sein
|
||||||
|
if (!acquireMutex()){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in connectClient");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
WiFi.disconnect();
|
WiFi.disconnect();
|
||||||
|
releaseMutex();
|
||||||
|
return connectInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GwWifi::connectClientAsync(){
|
||||||
|
// Non-blocking version: Versuche Mutex zu nehmen, gib aber sofort auf
|
||||||
|
// Ideal für Tasks, die nicht blockieren dürfen
|
||||||
|
if (wifiMutex==nullptr){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex not initialized in connectClientAsync");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (xSemaphoreTake(wifiMutex, 0)!=pdTRUE){
|
||||||
|
LOG_DEBUG(GwLog::LOG,"GwWifi: connectClientAsync skipped - WiFi busy");
|
||||||
|
return false; // WiFi ist aktuell busy, versuche es später nochmal
|
||||||
|
}
|
||||||
|
WiFi.disconnect();
|
||||||
|
xSemaphoreGive(wifiMutex);
|
||||||
return connectInternal();
|
return connectInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,217 +0,0 @@
|
|||||||
/*
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
#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();
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
#include "NetworkClient.h"
|
#include "NetworkClient.h"
|
||||||
|
#include "GWWifi.h" // WiFi management (thread-safe)
|
||||||
|
|
||||||
|
extern GwWifi gwWifi; // Extern declaration of global WiFi instance
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "puff.h"
|
#include "puff.h"
|
||||||
@@ -51,8 +54,13 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
|
|||||||
const size_t capacity = READLIMIT; // Read limit for data (can be adjusted in NetworkClient.h)
|
const size_t capacity = READLIMIT; // Read limit for data (can be adjusted in NetworkClient.h)
|
||||||
uint8_t* buffer = (uint8_t*)malloc(capacity);
|
uint8_t* buffer = (uint8_t*)malloc(capacity);
|
||||||
|
|
||||||
|
if (!gwWifi.clientConnected()) {
|
||||||
|
if (DEBUGING) {Serial.println("No WiFi connection");}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
if (DEBUG) {Serial.println("Malloc failed (buffer");}
|
if (DEBUGING) {Serial.println("Malloc failed buffer");}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +114,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
|
|||||||
len += read;
|
len += read;
|
||||||
lastData = millis();
|
lastData = millis();
|
||||||
|
|
||||||
if (DEBUG) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
|
if (DEBUGING) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
|
||||||
|
|
||||||
if (len < 20) continue; // Not enough data for header
|
if (len < 20) continue; // Not enough data for header
|
||||||
|
|
||||||
@@ -122,7 +130,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
|
|||||||
|
|
||||||
int res = puff(test, &testLen, buffer + headerOffset, &srcLen);
|
int res = puff(test, &testLen, buffer + headerOffset, &srcLen);
|
||||||
if (res == 0) {
|
if (res == 0) {
|
||||||
if (DEBUG) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
|
if (DEBUGING) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
|
||||||
outData = test;
|
outData = test;
|
||||||
outLen = testLen;
|
outLen = testLen;
|
||||||
complete = true;
|
complete = true;
|
||||||
@@ -167,7 +175,7 @@ bool NetworkClient::fetchAndDecompressJson(const String& url) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DEBUG) {Serial.println("JSON OK!");}
|
if (DEBUGING) {Serial.println("JSON OK!");}
|
||||||
_valid = true;
|
_valid = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <HTTPClient.h>
|
#include <HTTPClient.h>
|
||||||
|
|
||||||
#define DEBUG false // Debug flag for NetworkClient for more live information
|
#define DEBUGING false // Debug flag for NetworkClient for more live information
|
||||||
#define READLIMIT 200000 // HTTP read limit in byte for gzip content (can be adjusted)
|
#define READLIMIT 200000 // HTTP read limit in byte for gzip content (can be adjusted)
|
||||||
#define CONNECTIONTIMEOUT 3000 // Timeout in ms for HTTP connection
|
#define CONNECTIONTIMEOUT 3000 // Timeout in ms for HTTP connection
|
||||||
#define TCPREADTIMEOUT 2000 // Timeout in ms for read HTTP client stack
|
#define TCPREADTIMEOUT 2000 // Timeout in ms for read HTTP client stack
|
||||||
|
|||||||
@@ -475,30 +475,6 @@ 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;
|
||||||
@@ -662,19 +638,20 @@ 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)(commonData.tz * 3600);
|
time_t tv = mktime(&commonData.data.rtcTime) + (int)(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(commonData.tz == 0 ? "UTC" : "LOT");
|
getdisplay().print(tz == 0 ? "UTC" : "LOT");
|
||||||
} else {
|
} else {
|
||||||
drawTextRalign(396, 15, "RTC invalid");
|
drawTextRalign(396, 15, "RTC invalid");
|
||||||
}
|
}
|
||||||
@@ -689,7 +666,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(commonData.tz == 0 ? "UTC" : "LOT");
|
getdisplay().print(tz == 0 ? "UTC" : "LOT");
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if(commonData.config->getBool(commonData.config->useSimuData) == true){
|
if(commonData.config->getBool(commonData.config->useSimuData) == true){
|
||||||
@@ -946,7 +923,7 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor){
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generator graphic with fill level
|
// Generator graphic
|
||||||
void generatorGraphic(uint x, uint y, int pcolor, int bcolor){
|
void generatorGraphic(uint x, uint y, int pcolor, int bcolor){
|
||||||
// Show battery
|
// Show battery
|
||||||
int xb = x; // X position
|
int xb = x; // X position
|
||||||
@@ -963,6 +940,74 @@ void generatorGraphic(uint x, uint y, int pcolor, int bcolor){
|
|||||||
getdisplay().print("G");
|
getdisplay().print("G");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display rudder position as horizontal bargraph with configurable +/- range (degrees)
|
||||||
|
void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg){
|
||||||
|
const int w = 360;
|
||||||
|
const int h = 20;
|
||||||
|
const int t = 3; // Line thickness
|
||||||
|
const int halfw = w/2;
|
||||||
|
const int halfh = h/2;
|
||||||
|
// Calculate top-left of bar (cx,cy are center of 0°)
|
||||||
|
int left = int(cx) - halfw;
|
||||||
|
int top = int(cy) - halfh;
|
||||||
|
|
||||||
|
// clamp provided range to allowed bounds [10,45]
|
||||||
|
if (rangeDeg < 10) rangeDeg = 10;
|
||||||
|
if (rangeDeg > 45) rangeDeg = 45;
|
||||||
|
|
||||||
|
// Pixels per degree for +/-rangeDeg -> total span = 2*rangeDeg
|
||||||
|
const float pxPerDeg = float(w) / (2.0f * float(rangeDeg));
|
||||||
|
|
||||||
|
// Draw outer border (thickness t)
|
||||||
|
for (int i = 0; i < t; i++) {
|
||||||
|
getdisplay().drawRect(left + i, top + i, w - 2 * i, h - 2 * i, fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill inner area with background
|
||||||
|
getdisplay().fillRect(left + t, top + t, w - 2 * t, h - 2 * t, bg);
|
||||||
|
|
||||||
|
// Draw center line
|
||||||
|
getdisplay().drawRect(cx - 1, top + 1, 3 , h - 2, fg);
|
||||||
|
|
||||||
|
// Clamp rudder position to -rangeDeg..rangeDeg
|
||||||
|
if (rudderPosition > (int)rangeDeg) rudderPosition = (int)rangeDeg;
|
||||||
|
if (rudderPosition < -((int)rangeDeg)) rudderPosition = -((int)rangeDeg);
|
||||||
|
|
||||||
|
// Compute fill width in pixels
|
||||||
|
int fillPx = int(round(rudderPosition * pxPerDeg)); // positive -> right
|
||||||
|
|
||||||
|
// Fill area from center to position (if non-zero)
|
||||||
|
int centerx = cx;
|
||||||
|
int innerTop = top + t;
|
||||||
|
int innerH = h - 2 * t;
|
||||||
|
if (fillPx > 0) {
|
||||||
|
// Right side
|
||||||
|
getdisplay().fillRect(centerx, innerTop, fillPx, innerH, fg);
|
||||||
|
} else if (fillPx < 0) {
|
||||||
|
// Left side
|
||||||
|
getdisplay().fillRect(centerx + fillPx, innerTop, -fillPx, innerH, fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Draw tick marks every 5° and labels outside the bar
|
||||||
|
getdisplay().setTextColor(fg);
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
for (int angle = -((int)rangeDeg); angle <= (int)rangeDeg; angle += 5) {
|
||||||
|
int xpos = int(round(centerx + angle * pxPerDeg));
|
||||||
|
// Vertical tick inside bar
|
||||||
|
getdisplay().drawLine(xpos, top, xpos, top + h + 2, fg);
|
||||||
|
// Label outside: below the bar
|
||||||
|
String lbl = String(angle);
|
||||||
|
int16_t bx, by;
|
||||||
|
uint16_t bw, bh;
|
||||||
|
getdisplay().getTextBounds(lbl, 0, 0, &bx, &by, &bw, &bh);
|
||||||
|
int16_t tx = xpos - bw/2;
|
||||||
|
int16_t ty = top + h + bh + 5; // A little spacing
|
||||||
|
getdisplay().setCursor(tx, ty);
|
||||||
|
getdisplay().print(lbl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Function to handle HTTP image request
|
// Function to handle HTTP image request
|
||||||
// http://192.168.15.1/api/user/OBP60Task/screenshot
|
// http://192.168.15.1/api/user/OBP60Task/screenshot
|
||||||
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request) {
|
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request) {
|
||||||
|
|||||||
@@ -128,6 +128,10 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor); // S
|
|||||||
void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic
|
void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic
|
||||||
void startLedTask(GwApi *api);
|
void startLedTask(GwApi *api);
|
||||||
|
|
||||||
|
// Display rudder position as horizontal bargraph with configurable +/- range (degrees)
|
||||||
|
// 'rangeDeg' is unsigned and will be clamped to [10,45]
|
||||||
|
void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg);
|
||||||
|
|
||||||
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request);
|
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request);
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ void sensorTask(void *param){
|
|||||||
GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP);
|
GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP);
|
||||||
GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop};
|
GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop};
|
||||||
|
|
||||||
// Internal RTC with NTP init
|
// Internal iRTC with NTP init
|
||||||
ESP32Time rtc(0);
|
ESP32Time rtc(0);
|
||||||
if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") {
|
if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") {
|
||||||
GwApi::Status status;
|
GwApi::Status status;
|
||||||
@@ -432,17 +432,17 @@ void sensorTask(void *param){
|
|||||||
|
|
||||||
iRTC RTC GPS N2K
|
iRTC RTC GPS N2K
|
||||||
0 0 0 (1)
|
0 0 0 (1)
|
||||||
0 0 (1) (X)
|
0 0 (1) X
|
||||||
0 (1) 0 (X)
|
0 (1) 0 X
|
||||||
0 1 <-(1) (X)
|
0 1 <-(1) X
|
||||||
(1) 0 0 (X)
|
(1) 0 0 X
|
||||||
1 0 (1) (X)
|
1 0 (1) X
|
||||||
1 ->(1) 0 (X)
|
1 ->(1) 0 X
|
||||||
1 1 <-(1) (X)
|
1 1 <-(1) X
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// If RTC DS1388 ready, then copy iRTC and GPS data to RTC all 1min
|
// If RTC DS1388 ready, then copy iRTC and GPS data to RTC all 1 min
|
||||||
if(millis() > starttime11 + 1*60*1000){
|
if(millis() > starttime11 + 1*60*1000){
|
||||||
starttime11 = millis();
|
starttime11 = millis();
|
||||||
// Set RTC chip via iRTC (NTP)
|
// Set RTC chip via iRTC (NTP)
|
||||||
@@ -475,7 +475,7 @@ void sensorTask(void *param){
|
|||||||
// Adjust RTC time as unix time value
|
// Adjust RTC time as unix time value
|
||||||
ds1388.adjust(adjusttime);
|
ds1388.adjust(adjusttime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set RTC chip via N2K or 183 in case the internal GPS is off (only one time)
|
// Set RTC chip via N2K or 183 in case the internal GPS is off (only one time)
|
||||||
@@ -524,7 +524,7 @@ void sensorTask(void *param){
|
|||||||
// N2K sysTime is double in n2klib
|
// N2K sysTime is double in n2klib
|
||||||
double sysTime = (dt.hour() * 3600) + (dt.minute() * 60) + dt.second();
|
double sysTime = (dt.hour() * 3600) + (dt.minute() * 60) + dt.second();
|
||||||
if(!isnan(daysAt1970) && !isnan(sysTime)){
|
if(!isnan(daysAt1970) && !isnan(sysTime)){
|
||||||
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
|
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon+1, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
|
||||||
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
|
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
|
||||||
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
|
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
|
||||||
api->sendN2kMessage(N2kMsg);
|
api->sendN2kMessage(N2kMsg);
|
||||||
@@ -533,25 +533,26 @@ void sensorTask(void *param){
|
|||||||
}
|
}
|
||||||
// Send date and time from software RTC (iRTC)
|
// Send date and time from software RTC (iRTC)
|
||||||
if (iRTC_ready == true && RTC_ready == false && GPS_ready == false) {
|
if (iRTC_ready == true && RTC_ready == false && GPS_ready == false) {
|
||||||
// Use internal RTC feature
|
sensors.rtcTime = rtc.getTimeStruct();
|
||||||
sensors.rtcTime = rtc.getTimeStruct(); // Save software RTC values in SensorData
|
|
||||||
// TODO implement daysAt1970 and sysTime as methods of DateTime
|
|
||||||
const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334};
|
const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334};
|
||||||
uint16_t switchYear = ((sensors.rtcTime.tm_year-1)-1968)/4 - ((sensors.rtcTime.tm_year-1)-1900)/100 + ((sensors.rtcTime.tm_year-1)-1600)/400;
|
int year = sensors.rtcTime.tm_year + 1900;
|
||||||
long daysAt1970 = (sensors.rtcTime.tm_year-1970)*365 + switchYear + daysOfYear[sensors.rtcTime.tm_mon-1] + sensors.rtcTime.tm_mday-1;
|
int month = sensors.rtcTime.tm_mon;
|
||||||
// If switch year then add one day
|
int day = sensors.rtcTime.tm_mday;
|
||||||
if ((sensors.rtcTime.tm_mon > 2) && (sensors.rtcTime.tm_year % 4 == 0 && (sensors.rtcTime.tm_year % 100 != 0 || sensors.rtcTime.tm_year % 400 == 0))) {
|
uint16_t switchYear = ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 + ((year - 1) - 1600) / 400;
|
||||||
|
long daysAt1970 = (year - 1970) * 365L + switchYear + daysOfYear[month] + day - 1;
|
||||||
|
|
||||||
|
// Leap day add if date is after Feb (i.e. month >= March)
|
||||||
|
if (month >= 2 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) {
|
||||||
daysAt1970 += 1;
|
daysAt1970 += 1;
|
||||||
}
|
}
|
||||||
// N2K sysTime is double in n2klib
|
double sysTime = sensors.rtcTime.tm_hour * 3600.0 + sensors.rtcTime.tm_min * 60.0 + sensors.rtcTime.tm_sec;
|
||||||
double sysTime = (sensors.rtcTime.tm_hour * 3600) + (sensors.rtcTime.tm_min * 60) + sensors.rtcTime.tm_sec;
|
//api->getLogger()->logDebug(GwLog::LOG, "iRTC time: %04d/%02d/%02d %02d:%02d:%02d", year, month + 1, day, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
|
||||||
if(!isnan(daysAt1970) && !isnan(sysTime)){
|
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
|
||||||
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
|
SetN2kPGN126992(N2kMsg, 0, daysAt1970, sysTime, N2ktimes_LocalCrystalClock);
|
||||||
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
|
api->sendN2kMessage(N2kMsg);
|
||||||
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
|
|
||||||
api->sendN2kMessage(N2kMsg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send 1Wire data for all temperature sensors to N2K all 2s
|
// Send 1Wire data for all temperature sensors to N2K all 2s
|
||||||
|
|||||||
@@ -1,771 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
|
||||||
|
|
||||||
/*
|
|
||||||
This page is in experimental stage so be warned!
|
|
||||||
North is up.
|
|
||||||
Anchor page with background map from mapservice
|
|
||||||
|
|
||||||
Boatdata used
|
|
||||||
DBS - Water depth
|
|
||||||
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, horizontal
|
|
||||||
|
|
||||||
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 (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
|
|
||||||
srot: symbol rotation in degrees
|
|
||||||
ssize: symbol size in px, default=15
|
|
||||||
grid: show map grid
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <WiFi.h>
|
|
||||||
#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
|
|
||||||
static unsigned char anchor_bits[] PROGMEM = {
|
|
||||||
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:
|
|
||||||
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;
|
|
||||||
//uint8_t *mapbuf = (uint8_t*) heap_caps_malloc(mapbuf_size, MALLOC_CAP_SPIRAM);
|
|
||||||
GFXcanvas1 *canvas;
|
|
||||||
const uint16_t map_width = 264;
|
|
||||||
const uint16_t map_height = 260;
|
|
||||||
bool map_valid = false;
|
|
||||||
char map_service = 'R'; // (O)BP Service, (L)ocal Service, (R)emote Service
|
|
||||||
double map_lat = 0; // current center of valid map
|
|
||||||
double map_lon = 0;
|
|
||||||
String server_name; // server with map service
|
|
||||||
uint16_t server_port = 80;
|
|
||||||
String tile_path;
|
|
||||||
|
|
||||||
String lengthformat;
|
|
||||||
|
|
||||||
double scale = 50; // Radius of display circle in meter, depends on lat
|
|
||||||
uint8_t zoom = 15; // map zoom level
|
|
||||||
|
|
||||||
bool alarm = false;
|
|
||||||
bool alarm_enabled = false;
|
|
||||||
uint8_t alarm_range;
|
|
||||||
|
|
||||||
uint8_t chain_length;
|
|
||||||
uint8_t chain = 0;
|
|
||||||
|
|
||||||
bool anchor_set = false;
|
|
||||||
double anchor_lat;
|
|
||||||
double anchor_lon;
|
|
||||||
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) {
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
bv_hdt = pageData.values[1]; // HDT
|
|
||||||
String sval_hdt = formatValue(bv_hdt, *commonData).svalue;
|
|
||||||
bv_aws = pageData.values[2]; // AWS
|
|
||||||
String sval_aws = formatValue(bv_aws, *commonData).svalue;
|
|
||||||
String sunit_aws = formatValue(bv_aws, *commonData).unit;
|
|
||||||
bv_awd = pageData.values[3]; // AWD
|
|
||||||
String sval_awd = formatValue(bv_awd, *commonData).svalue;
|
|
||||||
bv_lat = pageData.values[4]; // LAT
|
|
||||||
String sval_lat = formatValue(bv_lat, *commonData).svalue;
|
|
||||||
bv_lon = pageData.values[5]; // LON
|
|
||||||
String sval_lon = formatValue(bv_lon, *commonData).svalue;
|
|
||||||
bv_hdop = pageData.values[6]; // HDOP
|
|
||||||
String sval_hdop = formatValue(bv_hdop, *commonData).svalue;
|
|
||||||
String sunit_hdop = formatValue(bv_hdop, *commonData).unit;
|
|
||||||
|
|
||||||
commonData->logger->logDebug(GwLog::DEBUG, "Drawing at PageAnchor; DBS=%f, HDT=%f, AWS=%f", bv_dbs->value, bv_hdt->value, bv_aws->value);
|
|
||||||
|
|
||||||
// Draw canvas with background map
|
|
||||||
// rhumb(map_lat, map_lon, bv_lat->value, bv_lon->value)
|
|
||||||
int posdiff = 0;
|
|
||||||
if (map_valid) {
|
|
||||||
if (bv_lat->valid and bv_lon->valid) {
|
|
||||||
// calculate movement since last map refresh
|
|
||||||
posdiff = rhumb(map_lat, map_lon, bv_lat->value, bv_lon->value);
|
|
||||||
if (posdiff > 25) {
|
|
||||||
map_lat = bv_lat->value;
|
|
||||||
map_lon = bv_lon->value;
|
|
||||||
map_valid = getBackgroundMap(map_lat, map_lon, zoom);
|
|
||||||
if (map_valid) {
|
|
||||||
// prepare visible space for anchor-symbol or boat
|
|
||||||
canvas->fillCircle(132, 130, 12, commonData->fgcolor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getdisplay().drawBitmap(68, 20, canvas->getBuffer(), map_width, map_height, commonData->fgcolor);
|
|
||||||
}
|
|
||||||
|
|
||||||
Point c = {200, 150}; // center = anchor position
|
|
||||||
uint16_t r = 125;
|
|
||||||
|
|
||||||
// Circle as map border
|
|
||||||
getdisplay().drawCircle(c.x, c.y, r, commonData->fgcolor);
|
|
||||||
getdisplay().drawCircle(c.x, c.y, r + 1, commonData->fgcolor);
|
|
||||||
|
|
||||||
Point b = {200, 180}; // boat position while dropping anchor
|
|
||||||
|
|
||||||
const std::vector<Point> 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 then draw lines
|
|
||||||
// TODO rotate boat according to current heading
|
|
||||||
if (bv_hdt->valid) {
|
|
||||||
if (map_valid) {
|
|
||||||
Point b1 = rotatePoint(c, {b.x, b.y - 8}, bv_hdt->value * RAD_TO_DEG);
|
|
||||||
getdisplay().fillCircle(b1.x, b1.y, 10, commonData->bgcolor);
|
|
||||||
}
|
|
||||||
drawPoly(rotatePoints(c, pts_boat, bv_hdt->value * RAD_TO_DEG), commonData->fgcolor);
|
|
||||||
} else {
|
|
||||||
// no heading available: draw north oriented
|
|
||||||
if (map_valid) {
|
|
||||||
getdisplay().fillCircle(b.x, b.y - 8, 10, commonData->bgcolor);
|
|
||||||
}
|
|
||||||
drawPoly(pts_boat, commonData->fgcolor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw wind arrow
|
|
||||||
const std::vector<Point> 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_Bold10pt8b);
|
|
||||||
// Left
|
|
||||||
getdisplay().setCursor(8, 36);
|
|
||||||
getdisplay().print("Anchor");
|
|
||||||
getdisplay().setCursor(8, 210);
|
|
||||||
getdisplay().print("Depth");
|
|
||||||
// Right
|
|
||||||
drawTextRalign(392, 80, "Chain");
|
|
||||||
drawTextRalign(392, 210, "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, 54);
|
|
||||||
getdisplay().print(anchor_set ? "Dropped" : "Ready"); // Anchor state
|
|
||||||
getdisplay().setCursor(8, 72);
|
|
||||||
getdisplay().print("Alarm: "); // Alarm state
|
|
||||||
getdisplay().print(alarm_enabled ? "on" : "off");
|
|
||||||
|
|
||||||
getdisplay().setCursor(8, 120);
|
|
||||||
getdisplay().print("Zoom");
|
|
||||||
getdisplay().setCursor(8, 136);
|
|
||||||
getdisplay().print(zoom);
|
|
||||||
|
|
||||||
getdisplay().setCursor(8, 160);
|
|
||||||
getdisplay().print("diff");
|
|
||||||
getdisplay().setCursor(8, 176);
|
|
||||||
if (map_valid and bv_lat->valid and bv_lon->valid) {
|
|
||||||
getdisplay().print(String(posdiff));
|
|
||||||
} else {
|
|
||||||
getdisplay().print("n/a");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chain out TODO lengthformat ft/m
|
|
||||||
drawTextRalign(392, 96, String(chain) + " m");
|
|
||||||
drawTextRalign(392, 96+16, "of " + String(chain_length) + " m");
|
|
||||||
|
|
||||||
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
|
|
||||||
|
|
||||||
// Depth
|
|
||||||
getdisplay().setCursor(8, 250);
|
|
||||||
getdisplay().print(sval_dbs);
|
|
||||||
|
|
||||||
// Wind
|
|
||||||
getdisplay().setCursor(320, 250);
|
|
||||||
getdisplay().print(sval_aws);
|
|
||||||
|
|
||||||
// Position of boat in center of map
|
|
||||||
getdisplay().setFont(&IBM8x8px);
|
|
||||||
drawTextRalign(392, 34, sval_lat);
|
|
||||||
drawTextRalign(392, 44, sval_lon);
|
|
||||||
// quality
|
|
||||||
String hdop = "HDOP: ";
|
|
||||||
if (bv_hdop->valid) {
|
|
||||||
hdop += String(round(bv_hdop->value));
|
|
||||||
} else {
|
|
||||||
hdop += " n/a";
|
|
||||||
}
|
|
||||||
drawTextRalign(392, 54, hdop);
|
|
||||||
|
|
||||||
// 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, 0) + "m");
|
|
||||||
|
|
||||||
// draw anchor symbol (as bitmap)
|
|
||||||
getdisplay().drawXBitmap(c.x - anchor_width / 2, c.y - anchor_height / 2,
|
|
||||||
anchor_bits, anchor_width, anchor_height, commonData->fgcolor);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void displayModeConfig(PageData &pageData) {
|
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
|
||||||
getdisplay().setCursor(8, 48);
|
|
||||||
getdisplay().print("Anchor configuration");
|
|
||||||
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
|
||||||
|
|
||||||
getdisplay().setCursor(8, 260);
|
|
||||||
getdisplay().print("Press BACK to leave config");
|
|
||||||
|
|
||||||
/* 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, 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:
|
|
||||||
PageAnchor(CommonData &common)
|
|
||||||
{
|
|
||||||
commonData = &common;
|
|
||||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageAnchor");
|
|
||||||
|
|
||||||
String mapsource = common.config->getString(common.config->mapsource);
|
|
||||||
if (mapsource == "Local Service") {
|
|
||||||
map_service = 'L';
|
|
||||||
server_name = common.config->getString(common.config->ipAddress);
|
|
||||||
server_port = common.config->getInt(common.config->localPort);
|
|
||||||
tile_path = "";
|
|
||||||
} else if (mapsource == "Remote Service") {
|
|
||||||
map_service = 'R';
|
|
||||||
server_name = common.config->getString(common.config->mapServer);
|
|
||||||
tile_path = common.config->getString(common.config->mapTilePath);
|
|
||||||
} else { // OBP Service or undefined
|
|
||||||
map_service = 'O';
|
|
||||||
server_name = "norbert-walter.dnshome.de";
|
|
||||||
tile_path = "";
|
|
||||||
}
|
|
||||||
zoom = common.config->getInt(common.config->zoomlevel);
|
|
||||||
|
|
||||||
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 = "CFG";
|
|
||||||
#ifdef BOARD_OBP40S3
|
|
||||||
commonData->keydata[1].label = "DROP";
|
|
||||||
#endif
|
|
||||||
#ifdef BOARD_OBP60S3
|
|
||||||
commonData->keydata[4].label = "DROP";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
commonData->keydata[4].label = anchor_set ? "RAISE": "DROP";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (key == 2) {
|
|
||||||
if (mode == 'N') {
|
|
||||||
anchor_set = !anchor_set;
|
|
||||||
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) {
|
|
||||||
commonData->keylock = !commonData->keylock;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rhumb(double lat1, double lon1, double lat2, double lon2) {
|
|
||||||
// calc distance in m between two geo points
|
|
||||||
static const double degToRad = M_PI / 180.0;
|
|
||||||
lat1 = degToRad * lat1;
|
|
||||||
lon1 = degToRad * lon1;
|
|
||||||
lat2 = degToRad * lat2;
|
|
||||||
lon2 = degToRad * lon2;
|
|
||||||
double dlon = lon2 - lon1;
|
|
||||||
double dlat = lat2 - lat1;
|
|
||||||
double mlat = (lat1 + lat2) / 2;
|
|
||||||
return (int) (6371000 * sqrt(pow(dlat, 2) + pow(cos(mlat) * dlon, 2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool getBackgroundMap(double lat, double lon, uint8_t zoom) {
|
|
||||||
// HTTP-Request for map
|
|
||||||
// TODO über pagedata -> status abfragen?
|
|
||||||
if (WiFi.status() != WL_CONNECTED) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
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.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();
|
|
||||||
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>
|
|
||||||
int header_size = 0;
|
|
||||||
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;
|
|
||||||
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);
|
|
||||||
} else {
|
|
||||||
commonData->logger->logDebug(GwLog::LOG, "HTTP result #%d", httpCode);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
commonData->logger->logDebug(GwLog::ERROR, "HTTP error #%d", httpCode);
|
|
||||||
}
|
|
||||||
http.end();
|
|
||||||
return valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
void displayNew(PageData &pageData){
|
|
||||||
|
|
||||||
GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT
|
|
||||||
GwApi::BoatValue *bv_lon = pageData.values[5]; // LON
|
|
||||||
|
|
||||||
// check if valid data available
|
|
||||||
if (!bv_lat->valid or !bv_lon->valid) {
|
|
||||||
map_valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
errmsg = "";
|
|
||||||
|
|
||||||
map_lat = bv_lat->value; // save for later comparison
|
|
||||||
map_lon = bv_lon->value;
|
|
||||||
map_valid = getBackgroundMap(map_lat, map_lon, zoom);
|
|
||||||
|
|
||||||
if (map_valid) {
|
|
||||||
// prepare visible space for anchor-symbol or boat
|
|
||||||
canvas->fillCircle(132, 130, 10, commonData->fgcolor);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void display_side_keys() {
|
|
||||||
// An rechter Seite neben dem Rad inc, dec, set etc ?
|
|
||||||
}
|
|
||||||
|
|
||||||
int displayPage(PageData &pageData) {
|
|
||||||
|
|
||||||
// Logging boat values
|
|
||||||
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
|
|
||||||
|
|
||||||
if (mode == 'N') {
|
|
||||||
displayModeNormal(pageData);
|
|
||||||
} else if (mode == 'C') {
|
|
||||||
displayModeConfig(pageData);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -5,8 +5,9 @@
|
|||||||
|
|
||||||
// These constants have to match the declaration below in :
|
// These constants have to match the declaration below in :
|
||||||
// PageDescription registerPageAutopilot(
|
// PageDescription registerPageAutopilot(
|
||||||
// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page
|
// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page
|
||||||
const int HowManyValues = 9;
|
|
||||||
|
const int HowManyValues = 11;
|
||||||
|
|
||||||
const int AverageValues = 4;
|
const int AverageValues = 4;
|
||||||
|
|
||||||
@@ -19,10 +20,13 @@ const int ShowDBT = 5;
|
|||||||
const int ShowXTE = 6;
|
const int ShowXTE = 6;
|
||||||
const int ShowDTW = 7;
|
const int ShowDTW = 7;
|
||||||
const int ShowBTW = 8;
|
const int ShowBTW = 8;
|
||||||
|
const int ShowRPOS = 9;
|
||||||
|
const int ShowROT = 10;
|
||||||
|
|
||||||
const int Compass_X0 = 200; // X center point of compass band
|
const int Compass_X0 = 200; // X center point of compass band
|
||||||
const int Compass_Y0 = 220; // Y position of compass lines
|
const int Compass_Y0 = 90; // Y position of compass lines
|
||||||
const int Compass_LineLength = 22; // Length of compass lines
|
//const int Compass_LineLength = 22; // Length of compass lines
|
||||||
|
const int Compass_LineLength = 15; // Length of compass lines
|
||||||
const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
|
const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
|
||||||
|
|
||||||
class PageAutopilot : public Page
|
class PageAutopilot : public Page
|
||||||
@@ -38,8 +42,11 @@ class PageAutopilot : public Page
|
|||||||
|
|
||||||
virtual void setupKeys(){
|
virtual void setupKeys(){
|
||||||
Page::setupKeys();
|
Page::setupKeys();
|
||||||
commonData->keydata[0].label = "CMP";
|
commonData->keydata[0].label = "-10";
|
||||||
commonData->keydata[1].label = "SRC";
|
commonData->keydata[1].label = "-1";
|
||||||
|
commonData->keydata[2].label = "Auto";
|
||||||
|
commonData->keydata[3].label = "+1";
|
||||||
|
commonData->keydata[4].label = "+10";
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual int handleKey(int key){
|
virtual int handleKey(int key){
|
||||||
@@ -69,8 +76,8 @@ class PageAutopilot : public Page
|
|||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
// Old values for hold function
|
// Old values for hold function
|
||||||
static String OldDataText[HowManyValues] = {"", "", "","", "", "","", "", ""};
|
static String OldDataText[HowManyValues] = {"", "", "", "", "", "","", "", "", "", ""};
|
||||||
static String OldDataUnits[HowManyValues] = {"", "", "","", "", "","", "", ""};
|
static String OldDataUnits[HowManyValues] = {"", "", "", "", "", "","", "", "", "", ""};
|
||||||
|
|
||||||
// Get config data
|
// Get config data
|
||||||
String lengthformat = config->getString(config->lengthFormat);
|
String lengthformat = config->getString(config->lengthFormat);
|
||||||
@@ -106,15 +113,13 @@ class PageAutopilot : public Page
|
|||||||
setBlinkingLED(false);
|
setBlinkingLED(false);
|
||||||
setFlashLED(false);
|
setFlashLED(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bvalue == NULL) return PAGE_OK; // WTF why this statement?
|
|
||||||
|
|
||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// 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
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
/*
|
||||||
// Horizontal line 2 pix top & bottom
|
// Horizontal line 2 pix top & bottom
|
||||||
// Print data on top half
|
// Print data on top half
|
||||||
getdisplay().fillRect(0, 130, 400, 2, commonData->fgcolor);
|
getdisplay().fillRect(0, 130, 400, 2, commonData->fgcolor);
|
||||||
@@ -138,7 +143,7 @@ class PageAutopilot : public Page
|
|||||||
OldDataText[WhichDataDisplay] = DataText[WhichDataDisplay]; // Save the old value
|
OldDataText[WhichDataDisplay] = DataText[WhichDataDisplay]; // Save the old value
|
||||||
OldDataUnits[WhichDataDisplay] = DataUnits[WhichDataDisplay]; // Save the old unit
|
OldDataUnits[WhichDataDisplay] = DataUnits[WhichDataDisplay]; // Save the old unit
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
// Now draw compass band
|
// Now draw compass band
|
||||||
// Get the data
|
// Get the data
|
||||||
double TheAngle = DataValue[WhichDataCompass];
|
double TheAngle = DataValue[WhichDataCompass];
|
||||||
@@ -152,13 +157,13 @@ class PageAutopilot : public Page
|
|||||||
buffer[0]=0;
|
buffer[0]=0;
|
||||||
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
||||||
getdisplay().setCursor(10, Compass_Y0-60);
|
getdisplay().setCursor(10, Compass_Y0-40);
|
||||||
getdisplay().print(DataName[WhichDataCompass]); // Page name
|
getdisplay().print(DataName[WhichDataCompass]); // Page name
|
||||||
|
|
||||||
|
|
||||||
// Draw compass base line and pointer
|
// Draw compass base line and pointer
|
||||||
getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor);
|
getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor);
|
||||||
getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor);
|
//getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor);
|
||||||
|
getdisplay().fillTriangle(Compass_X0,Compass_Y0-30,Compass_X0-10,Compass_Y0-60,Compass_X0+10,Compass_Y0-60,commonData->fgcolor);
|
||||||
// Draw trendlines
|
// Draw trendlines
|
||||||
for ( int i = 1; i < abs(TheTrend) / 2; i++){
|
for ( int i = 1; i < abs(TheTrend) / 2; i++){
|
||||||
int x1;
|
int x1;
|
||||||
@@ -238,6 +243,8 @@ class PageAutopilot : public Page
|
|||||||
// if ( x_test > 390)
|
// if ( x_test > 390)
|
||||||
// x_test = 320;
|
// x_test = 320;
|
||||||
|
|
||||||
|
displayRudderPosition(DataValue[ShowSOG], 20, 200, 160, commonData->fgcolor, commonData->bgcolor);
|
||||||
|
|
||||||
return PAGE_UPDATE;
|
return PAGE_UPDATE;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -256,7 +263,7 @@ PageDescription registerPageAutopilot(
|
|||||||
"Autopilot", // Page name
|
"Autopilot", // Page name
|
||||||
createPage, // Action
|
createPage, // Action
|
||||||
0, // Number of bus values depends on selection in Web configuration
|
0, // Number of bus values depends on selection in Web configuration
|
||||||
{"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page
|
{"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page
|
||||||
true // Show display header on/off
|
true // Show display header on/off
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ 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;
|
||||||
|
|||||||
@@ -75,20 +75,6 @@
|
|||||||
"obp40": "true"
|
"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",
|
"name": "fuelTank",
|
||||||
"label": "Fuel Tank [l]",
|
"label": "Fuel Tank [l]",
|
||||||
@@ -692,7 +678,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "text1",
|
"default": "text1",
|
||||||
"description": "Button name",
|
"description": "Button name",
|
||||||
"category": "OBP40 IO-Modul1",
|
"category": "OBP60 IO-Modul1",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"obp40":"true"
|
"obp40":"true"
|
||||||
}
|
}
|
||||||
@@ -703,7 +689,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "text2",
|
"default": "text2",
|
||||||
"description": "Button name",
|
"description": "Button name",
|
||||||
"category": "OBP40 IO-Modul1",
|
"category": "OBP60 IO-Modul1",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"obp40":"true"
|
"obp40":"true"
|
||||||
}
|
}
|
||||||
@@ -714,7 +700,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "text3",
|
"default": "text3",
|
||||||
"description": "Button name",
|
"description": "Button name",
|
||||||
"category": "OBP40 IO-Modul1",
|
"category": "OBP60 IO-Modul1",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"obp40":"true"
|
"obp40":"true"
|
||||||
}
|
}
|
||||||
@@ -725,7 +711,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "text4",
|
"default": "text4",
|
||||||
"description": "Button name",
|
"description": "Button name",
|
||||||
"category": "OBP40 IO-Modul1",
|
"category": "OBP60 IO-Modul1",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"obp40":"true"
|
"obp40":"true"
|
||||||
}
|
}
|
||||||
@@ -736,7 +722,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "text5",
|
"default": "text5",
|
||||||
"description": "Button name",
|
"description": "Button name",
|
||||||
"category": "OBP40 IO-Modul1",
|
"category": "OBP60 IO-Modul1",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"obp40":"true"
|
"obp40":"true"
|
||||||
}
|
}
|
||||||
@@ -1081,8 +1067,7 @@
|
|||||||
"description": "Type of map source, cloud service or local service",
|
"description": "Type of map source, cloud service or local service",
|
||||||
"list": [
|
"list": [
|
||||||
"OBP Service",
|
"OBP Service",
|
||||||
"Local Service",
|
"Local Service"
|
||||||
"Remote Service"
|
|
||||||
],
|
],
|
||||||
"category": "OBP40 Navigation",
|
"category": "OBP40 Navigation",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
@@ -1119,34 +1104,6 @@
|
|||||||
{ "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": "OBP40 Navigation",
|
|
||||||
"capabilities": {
|
|
||||||
"obp40": "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",
|
||||||
@@ -1288,8 +1245,8 @@
|
|||||||
"name": "timeSource",
|
"name": "timeSource",
|
||||||
"label": "Status Time Source",
|
"label": "Status Time Source",
|
||||||
"type": "list",
|
"type": "list",
|
||||||
"default": "GPS",
|
"default": "iRTC",
|
||||||
"description": "Data source for date and time display in status line [RTC|iRTC|GPS]",
|
"description": "Data source for date and time display in status line [iRTC|RTC|GPS]",
|
||||||
"list": [
|
"list": [
|
||||||
{"l":"Internal real time clock (iRTC)","v":"iRTC"},
|
{"l":"Internal real time clock (iRTC)","v":"iRTC"},
|
||||||
{"l":"External real time clock (RTC)","v":"RTC"},
|
{"l":"External real time clock (RTC)","v":"RTC"},
|
||||||
@@ -1552,6 +1509,7 @@
|
|||||||
"obp40": "true"
|
"obp40": "true"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "page1type",
|
"name": "page1type",
|
||||||
"label": "Type",
|
"label": "Type",
|
||||||
@@ -1559,7 +1517,7 @@
|
|||||||
"default": "Voltage",
|
"default": "Voltage",
|
||||||
"description": "Type of page for page 1",
|
"description": "Type of page for page 1",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -1890,7 +1848,7 @@
|
|||||||
"default": "WindRose",
|
"default": "WindRose",
|
||||||
"description": "Type of page for page 2",
|
"description": "Type of page for page 2",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2212,7 +2170,7 @@
|
|||||||
"default": "OneValue",
|
"default": "OneValue",
|
||||||
"description": "Type of page for page 3",
|
"description": "Type of page for page 3",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2525,7 +2483,7 @@
|
|||||||
"default": "TwoValues",
|
"default": "TwoValues",
|
||||||
"description": "Type of page for page 4",
|
"description": "Type of page for page 4",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2829,7 +2787,7 @@
|
|||||||
"default": "ThreeValues",
|
"default": "ThreeValues",
|
||||||
"description": "Type of page for page 5",
|
"description": "Type of page for page 5",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3124,7 +3082,7 @@
|
|||||||
"default": "FourValues",
|
"default": "FourValues",
|
||||||
"description": "Type of page for page 6",
|
"description": "Type of page for page 6",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3410,7 +3368,7 @@
|
|||||||
"default": "FourValues2",
|
"default": "FourValues2",
|
||||||
"description": "Type of page for page 7",
|
"description": "Type of page for page 7",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3687,7 +3645,7 @@
|
|||||||
"default": "Clock",
|
"default": "Clock",
|
||||||
"description": "Type of page for page 8",
|
"description": "Type of page for page 8",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3955,7 +3913,7 @@
|
|||||||
"default": "RollPitch",
|
"default": "RollPitch",
|
||||||
"description": "Type of page for page 9",
|
"description": "Type of page for page 9",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -4214,7 +4172,7 @@
|
|||||||
"default": "Battery2",
|
"default": "Battery2",
|
||||||
"description": "Type of page for page 10",
|
"description": "Type of page for page 10",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
|
|||||||
@@ -75,20 +75,6 @@
|
|||||||
"obp60":"true"
|
"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",
|
"name": "fuelTank",
|
||||||
"label": "Fuel Tank [l]",
|
"label": "Fuel Tank [l]",
|
||||||
@@ -1107,34 +1093,6 @@
|
|||||||
{ "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",
|
||||||
@@ -1277,8 +1235,9 @@
|
|||||||
"label": "Status Time Source",
|
"label": "Status Time Source",
|
||||||
"type": "list",
|
"type": "list",
|
||||||
"default": "GPS",
|
"default": "GPS",
|
||||||
"description": "Data source for date and time display in status line [RTC|GPS]",
|
"description": "Data source for date and time display in status line [iRTC|RTC|GPS]",
|
||||||
"list": [
|
"list": [
|
||||||
|
{"l":"Internal real time clock (iRTC)","v":"iRTC"},
|
||||||
{"l":"Real time clock (RTC)","v":"RTC"},
|
{"l":"Real time clock (RTC)","v":"RTC"},
|
||||||
{"l":"Time via bus (GPS)","v":"GPS"}
|
{"l":"Time via bus (GPS)","v":"GPS"}
|
||||||
],
|
],
|
||||||
@@ -1528,6 +1487,7 @@
|
|||||||
"obp60":"true"
|
"obp60":"true"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "page1type",
|
"name": "page1type",
|
||||||
"label": "Type",
|
"label": "Type",
|
||||||
@@ -1535,7 +1495,7 @@
|
|||||||
"default": "Voltage",
|
"default": "Voltage",
|
||||||
"description": "Type of page for page 1",
|
"description": "Type of page for page 1",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -1836,7 +1796,7 @@
|
|||||||
"default": "WindRose",
|
"default": "WindRose",
|
||||||
"description": "Type of page for page 2",
|
"description": "Type of page for page 2",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2129,7 +2089,7 @@
|
|||||||
"default": "OneValue",
|
"default": "OneValue",
|
||||||
"description": "Type of page for page 3",
|
"description": "Type of page for page 3",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2414,7 +2374,7 @@
|
|||||||
"default": "TwoValues",
|
"default": "TwoValues",
|
||||||
"description": "Type of page for page 4",
|
"description": "Type of page for page 4",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2691,7 +2651,7 @@
|
|||||||
"default": "ThreeValues",
|
"default": "ThreeValues",
|
||||||
"description": "Type of page for page 5",
|
"description": "Type of page for page 5",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2960,7 +2920,7 @@
|
|||||||
"default": "FourValues",
|
"default": "FourValues",
|
||||||
"description": "Type of page for page 6",
|
"description": "Type of page for page 6",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3221,7 +3181,7 @@
|
|||||||
"default": "FourValues2",
|
"default": "FourValues2",
|
||||||
"description": "Type of page for page 7",
|
"description": "Type of page for page 7",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3474,7 +3434,7 @@
|
|||||||
"default": "Clock",
|
"default": "Clock",
|
||||||
"description": "Type of page for page 8",
|
"description": "Type of page for page 8",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3719,7 +3679,7 @@
|
|||||||
"default": "RollPitch",
|
"default": "RollPitch",
|
||||||
"description": "Type of page for page 9",
|
"description": "Type of page for page 9",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3956,7 +3916,7 @@
|
|||||||
"default": "Battery2",
|
"default": "Battery2",
|
||||||
"description": "Type of page for page 10",
|
"description": "Type of page for page 10",
|
||||||
"list": [
|
"list": [
|
||||||
"Anchor",
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
|
|||||||
@@ -264,8 +264,6 @@ void registerAllPages(PageList &list){
|
|||||||
list.add(®isterPageDigitalOut);
|
list.add(®isterPageDigitalOut);
|
||||||
extern PageDescription registerPageAutopilot;
|
extern PageDescription registerPageAutopilot;
|
||||||
list.add(®isterPageAutopilot);
|
list.add(®isterPageAutopilot);
|
||||||
extern PageDescription registerPageAnchor;
|
|
||||||
list.add(®isterPageAnchor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Undervoltage detection for shutdown display
|
// Undervoltage detection for shutdown display
|
||||||
@@ -527,8 +525,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 +701,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, commonData.tz);
|
commonData.sundata = calcSunsetSunrise(time->value , date->value, lat->value, lon->value, 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 +714,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, commonData.tz);
|
commonData.sundata = calcSunsetSunriseRTC(&commonData.data.rtcTime, homelat, homelon, tz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ build_flags=
|
|||||||
-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
|
||||||
|
|||||||
Reference in New Issue
Block a user