1
0
mirror of https://github.com/thooge/esp32-nmea2000-obp60.git synced 2026-08-18 10:22:31 +02:00

Merge branch 'norbert-walter:master' into TrueWind-opt

This commit is contained in:
Scorgan01
2026-03-26 21:44:18 +01:00
committed by GitHub
64 changed files with 6643 additions and 538 deletions
+14
View File
@@ -0,0 +1,14 @@
Changes to original project (wellenvogel)
* esp32-nmea2000-obp60/gwwifi/GwWifi.cpp
- any fixes for reconnect handling
* GWStatisticsw.h
- changed time source for log messages
* esp32-nmea2000-obp60/platformio.ini
- change to newer versions for AsyncTCP and AsyncWebServer (better handling for bad WiFi connections)
From AsyncTCP-esphome @ 2.0.1
ottowinter/ESPAsyncWebServer-esphome@2.0.1
To AsyncTCP-esphome @ 2.1.1
ottowinter/ESPAsyncWebServer-esphome@3.4.0
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
dir="$1"
total=0
while IFS= read -r -d '' file; do
lines=$(wc -l < "$file")
echo "$file : $lines"
total=$((total + lines))
done < <(find "$dir" \( -name "*.c" -o -name "*.cpp" -o -name "*.h" \) -type f -print0)
echo "-----------------------"
echo "Over all files: $total"
+12 -3
View File
@@ -2,13 +2,22 @@
#include <mbedtls/base64.h>
// Decoder for Base64 content
bool ImageDecoder::decodeBase64(const String& base64, uint8_t* outBuffer, size_t outSize, size_t& decodedSize) {
bool ImageDecoder::decodeBase64(const char* base64, size_t base64Len, uint8_t* outBuffer, size_t outSize, size_t& decodedSize) {
if (base64 == nullptr) {
decodedSize = 0;
return false;
}
int ret = mbedtls_base64_decode(
outBuffer,
outSize,
&decodedSize,
(const unsigned char*)base64.c_str(),
base64.length()
(const unsigned char*)base64,
base64Len
);
return (ret == 0);
}
// Decoder for Base64 content
bool ImageDecoder::decodeBase64(const String& base64, uint8_t* outBuffer, size_t outSize, size_t& decodedSize) {
return decodeBase64(base64.c_str(), base64.length(), outBuffer, outSize, decodedSize);
}
+1
View File
@@ -5,5 +5,6 @@
class ImageDecoder {
public:
bool decodeBase64(const char* base64, size_t base64Len, uint8_t* outBuffer, size_t outSize, size_t& decodedSize);
bool decodeBase64(const String& base64, uint8_t* outBuffer, size_t outSize, size_t& decodedSize);
};
+25 -1
View File
@@ -14,6 +14,30 @@ https://controllerstech.com/ws2812-leds-using-spi/
*/
String Color::toHex() {
char hexColor[8];
sprintf(hexColor, "#%02X%02X%02X", r, g, b);
return String(hexColor);
}
String Color::toName() {
static std::map<int, String> const names = {
{0xff0000, "Red"},
{0x00ff00, "Green"},
{0x0000ff, "Blue",},
{0xff9900, "Orange"},
{0xffff00, "Yellow"},
{0x3366ff, "Aqua"},
{0xff0066, "Violet"},
{0xffffff, "White"}
};
int color = (r << 16) + (g << 8) + b;
auto it = names.find(color);
if (it == names.end()) {
return toHex();
}
return it->second;
}
static uint8_t mulcolor(uint8_t f1, uint8_t f2){
uint16_t rt=f1;
@@ -231,4 +255,4 @@ void handleSpiLeds(void *param){
void createSpiLedTask(LedTaskData *param){
xTaskCreate(handleSpiLeds,"handleLeds",4000,param,3,NULL);
}
}
+3 -1
View File
@@ -10,7 +10,7 @@ class Color{
uint8_t g;
uint8_t b;
Color():r(0),g(0),b(0){}
Color(uint8_t cr, uint8_t cg,uint8_t cb):
Color(uint8_t cr, uint8_t cg, uint8_t cb):
b(cb),g(cg),r(cr){}
Color(const Color &o):b(o.b),g(o.g),r(o.r){}
bool equal(const Color &o) const{
@@ -22,6 +22,8 @@ class Color{
bool operator != (const Color &other) const{
return ! equal(other);
}
String toHex();
String toName();
};
static Color COLOR_GREEN=Color(0,255,0);
+457 -53
View File
@@ -7,13 +7,134 @@ extern "C" {
#include "puff.h"
}
static uint32_t crc32_update(uint32_t crc, const uint8_t* data, size_t len) {
crc = ~crc;
for (size_t i = 0; i < len; ++i) {
crc ^= data[i];
for (int bit = 0; bit < 8; ++bit) {
uint32_t mask = -(int32_t)(crc & 1U);
crc = (crc >> 1) ^ (0xEDB88320U & mask);
}
}
return ~crc;
}
// Constructor
NetworkClient::NetworkClient(size_t reserveSize)
: _doc(reserveSize),
_valid(false)
_valid(false),
_jsonRaw(nullptr),
_jsonRawLen(0),
_imageWidth(0),
_imageHeight(0),
_numberPixels(0),
_pictureBase64(nullptr),
_pictureBase64Len(0)
{
}
NetworkClient::~NetworkClient() {
if (_jsonRaw != nullptr) {
free(_jsonRaw);
_jsonRaw = nullptr;
_jsonRawLen = 0;
}
}
bool NetworkClient::findJsonIntField(const char* json, size_t len, const char* key, int& outValue) {
if (json == nullptr || key == nullptr || len == 0) {
return false;
}
char pattern[64];
int plen = snprintf(pattern, sizeof(pattern), "\"%s\"", key);
if (plen <= 0 || (size_t)plen >= sizeof(pattern)) {
return false;
}
const char* keyPos = strstr(json, pattern);
if (keyPos == nullptr) {
return false;
}
const char* end = json + len;
const char* colon = strchr(keyPos + plen, ':');
if (colon == nullptr || colon >= end) {
return false;
}
const char* p = colon + 1;
while (p < end && (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')) {
++p;
}
if (p >= end) {
return false;
}
char* parseEnd = nullptr;
long value = strtol(p, &parseEnd, 10);
if (parseEnd == p) {
return false;
}
outValue = (int)value;
return true;
}
bool NetworkClient::extractJsonStringInPlace(char* json, size_t len, const char* key, char*& outValue, size_t& outLen) {
outValue = nullptr;
outLen = 0;
if (json == nullptr || key == nullptr || len == 0) {
return false;
}
char pattern[64];
int plen = snprintf(pattern, sizeof(pattern), "\"%s\"", key);
if (plen <= 0 || (size_t)plen >= sizeof(pattern)) {
return false;
}
char* keyPos = strstr(json, pattern);
if (keyPos == nullptr) {
return false;
}
char* end = json + len;
char* colon = strchr(keyPos + plen, ':');
if (colon == nullptr || colon >= end) {
return false;
}
char* p = colon + 1;
while (p < end && (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')) {
++p;
}
if (p >= end || *p != '"') {
return false;
}
char* valueStart = p + 1;
char* cur = valueStart;
while (cur < end) {
if (*cur == '\\') {
++cur;
if (cur < end) {
++cur;
}
continue;
}
if (*cur == '"') {
*cur = '\0';
outValue = valueStart;
outLen = (size_t)(cur - valueStart);
return true;
}
++cur;
}
return false;
}
// Skip GZIP Header an goto DEFLATE content
int NetworkClient::skipGzipHeader(const uint8_t* data, size_t len) {
if (len < 10) return -1;
@@ -51,14 +172,16 @@ int NetworkClient::skipGzipHeader(const uint8_t* data, size_t len) {
// HTTP GET + GZIP Decompression (reading in chunks)
bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& outLen) {
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);
// If not with WiFi connectetd then return without any activities
if (!gwWifi.clientConnected()) {
if (DEBUGING) {Serial.println("No WiFi connection");}
return false;
}
// If frame buffer not correct allocated then return without any activities
if (!buffer) {
if (DEBUGING) {Serial.println("Malloc failed buffer");}
return false;
@@ -71,20 +194,50 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
http.setTimeout(TCPREADTIMEOUT); // Read timeout in ms (can be adjusted in NetworkClient.h)
http.begin(url);
// NEW: force server to close the connection after the response (prevents "stuck" keep-alive reads)
http.addHeader("Connection", "close");
// NEW: request gzip, but we will only decompress if the server actually answers with gzip
http.addHeader("Accept-Encoding", "gzip");
// NEW: register headers BEFORE GET() (more reliable with Arduino HTTPClient)
if (DEBUGING) {
// We need follow key words
const char* keys[] = {
"Content-Encoding",
"Transfer-Encoding",
"Content-Length"
};
// Read header
http.collectHeaders(keys, 3);
}
int code = http.GET();
if (code != HTTP_CODE_OK) {
Serial.printf("HTTP ERROR: %d\n", code);
Serial.printf("HTTP Client ERROR: %d (%s)\n", code, http.errorToString(code).c_str());
// Hard reset HTTP + socket
WiFiClient* tmp = http.getStreamPtr();
if (tmp) tmp->stop(); // Force close TCP socket
http.end();
free(buffer);
return false;
}
else{
if (DEBUGING) {
String ce = http.header("Content-Encoding");
String te = http.header("Transfer-Encoding");
String cl = http.header("Content-Length");
// Print header informations
Serial.printf("Content-Encoding=%s Transfer-Encoding=%s Content-Length=%s\n",
ce.c_str(),
te.c_str(),
cl.c_str());
}
}
WiFiClient* stream = http.getStreamPtr();
@@ -93,55 +246,251 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
const uint32_t READ_TIMEOUT = READDATATIMEOUT; // Timeout for reading data (can be adjusted in NetworkClient.h)
bool complete = false;
bool aborting = false; // NEW: remember if we must force-close socket
while (http.connected() && !complete) {
// NEW: detect if server really sent gzip
String ce = http.header("Content-Encoding");
bool isGzip = ce.equalsIgnoreCase("gzip");
size_t avail = stream->available();
// NEW: read expected body size if provided by server (prevents waiting forever for missing bytes)
int total = http.getSize(); // returns Content-Length, or -1 if unknown/chunked
if (avail == 0) {
if (millis() - lastData > READ_TIMEOUT) {
Serial.println("TIMEOUT waiting for data!");
break;
}
delay(1);
continue;
}
if (len + avail > capacity)
avail = capacity - len;
int read = stream->readBytes(buffer + len, avail);
len += read;
lastData = millis();
if (DEBUGING) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
if (len < 20) continue; // Not enough data for header
int headerOffset = skipGzipHeader(buffer, len);
if (headerOffset < 0) continue;
unsigned long testLen = len * 8; // Dynamic expansion
uint8_t* test = (uint8_t*)malloc(testLen);
if (!test) continue;
unsigned long srcLen = len - headerOffset;
int res = puff(test, &testLen, buffer + headerOffset, &srcLen);
if (res == 0) {
if (DEBUGING) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
outData = test;
outLen = testLen;
complete = true;
break;
}
free(test);
// NEW: fail fast if server claims something larger than our buffer
if (total > 0 && (size_t)total > capacity) {
Serial.println("Response exceeds READLIMIT.");
aborting = true;
}
// --- Added: Force-close connection in all cases to avoid stuck TCP sockets ---
if (stream) stream->stop();
// NEW: if not gzip, we will not try to decompress (prevents false "Decompress OK" / random success)
// You can either handle plain JSON here or just fail-fast.
if (!isGzip && !aborting) {
if (DEBUGING) {
Serial.println("Server response is NOT gzip (Content-Encoding != gzip).");
Serial.println("Either disable Accept-Encoding: gzip or add plain-body handling here.");
}
// --- Plain-body handling (recommended): read full body into outData as-is ---
// NEW: try to read Content-Length bytes if available (more robust)
if (total > 0 && (size_t)total > capacity) {
Serial.println("Plain response exceeds READLIMIT.");
aborting = true;
} else {
// Read until we have all bytes (Content-Length) or until connection closes + buffer drains
while ((http.connected() || (stream && stream->available())) && !aborting) {
size_t avail = stream ? stream->available() : 0;
if (avail == 0) {
if (millis() - lastData > READ_TIMEOUT) {
Serial.println("TIMEOUT waiting for data (plain)!");
aborting = true;
break;
}
delay(1);
continue;
}
if (len >= capacity) {
Serial.println("READLIMIT reached, aborting (plain).");
aborting = true;
break;
}
if (len + avail > capacity)
avail = capacity - len;
int read = stream->readBytes(buffer + len, avail);
if (read > 0) {
len += (size_t)read;
lastData = millis();
}
// NEW: stop reading as soon as we have the full response
if (total > 0 && (int)len >= total) {
break; // we got full body
}
}
}
if (aborting) {
// --- Added: Force-close connection only if aborted to avoid TCP RST storms ---
if (stream) stream->stop(); // Force close TCP socket
http.end();
free(buffer);
return false;
}
if (total > 0 && (int)len != total) {
Serial.printf("Plain response incomplete: got=%d expected=%d\n", (int)len, total);
if (stream) stream->stop();
http.end();
free(buffer);
return false;
}
// Return plain body to caller
outData = (uint8_t*)malloc(len + 1);
if (!outData) {
Serial.println("Malloc failed outData (plain).");
// --- Added: Force-close connection only if aborted to avoid TCP RST storms ---
if (stream) stream->stop(); // Force close TCP socket
http.end();
free(buffer);
return false;
}
memcpy(outData, buffer, len);
outData[len] = 0;
outLen = len;
http.end();
free(buffer);
return true;
}
// --- GZIP path (only if Content-Encoding is gzip) ---
if (!aborting) {
// NEW: read exactly Content-Length bytes when available (prevents partial-body timeout loops)
while ((http.connected() || (stream && stream->available())) && !complete && !aborting) {
size_t avail = stream ? stream->available() : 0;
if (avail == 0) {
// NEW: if Content-Length is known and we already read it all, stop immediately
if (total > 0 && (int)len >= total) {
break;
}
if (millis() - lastData > READ_TIMEOUT) {
Serial.println("TIMEOUT waiting for data!");
aborting = true; // NEW: mark abnormal exit
break;
}
delay(1);
continue;
}
// NEW: safety check if buffer limit is reached
if (len >= capacity) {
Serial.println("READLIMIT reached, aborting.");
aborting = true;
break;
}
// NEW: if Content-Length is known, do not read beyond it
if (total > 0) {
size_t remaining = (size_t)total - len;
if (avail > remaining) avail = remaining;
}
if (len + avail > capacity)
avail = capacity - len;
int read = stream->readBytes(buffer + len, avail);
if (read <= 0) {
// NEW: avoid tight loop if read returns zero
delay(1);
continue;
}
len += (size_t)read;
lastData = millis();
if (DEBUGING) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
// NEW: if Content-Length is known and fully received, we can stop reading
if (total > 0 && (int)len >= total) {
break;
}
}
// NEW: only attempt gzip parse/decompress after we have a complete body (when Content-Length is known)
// This avoids wasting heap with repeated malloc/free and reduces fragmentation over long runtimes.
if (!aborting) {
if (total > 0 && (int)len != total) {
Serial.printf("GZIP response incomplete: got=%d expected=%d\n", (int)len, total);
aborting = true;
}
}
if (!aborting) {
if (len < 20) {
aborting = true;
} else {
int headerOffset = skipGzipHeader(buffer, len);
if (headerOffset < 0) {
aborting = true;
} else {
size_t deflateLen = len - (size_t)headerOffset;
// GZIP trailer (CRC32 + ISIZE) is 8 bytes and not part of deflate stream.
if (deflateLen >= 8) {
deflateLen -= 8;
}
unsigned long srcLenForSize = (unsigned long)deflateLen;
unsigned long outNeeded = 0;
int sizeRes = puff(NIL, &outNeeded, buffer + headerOffset, &srcLenForSize);
if (sizeRes != 0) {
if (DEBUGING) {
Serial.printf("Decompress size probe failed: res=%d src=%lu\n", sizeRes, srcLenForSize);
}
aborting = true;
} else {
uint8_t* test = (uint8_t*)malloc((size_t)outNeeded + 1);
if (!test) {
Serial.println("Malloc failed test buffer, aborting.");
aborting = true;
} else {
unsigned long srcLen = (unsigned long)deflateLen;
unsigned long testLen = outNeeded;
int res = puff(test, &testLen, buffer + headerOffset, &srcLen);
if (res == 0) {
uint32_t trailerCrc =
(uint32_t)buffer[len - 8] |
((uint32_t)buffer[len - 7] << 8) |
((uint32_t)buffer[len - 6] << 16) |
((uint32_t)buffer[len - 5] << 24);
uint32_t trailerIsize =
(uint32_t)buffer[len - 4] |
((uint32_t)buffer[len - 3] << 8) |
((uint32_t)buffer[len - 2] << 16) |
((uint32_t)buffer[len - 1] << 24);
uint32_t calcCrc = crc32_update(0, test, (size_t)testLen);
uint32_t calcIsize = (uint32_t)testLen;
if (calcCrc != trailerCrc || calcIsize != trailerIsize) {
Serial.printf(
"GZIP CRC/ISIZE mismatch crc=%08lx/%08lx isize=%lu/%lu\n",
(unsigned long)calcCrc,
(unsigned long)trailerCrc,
(unsigned long)calcIsize,
(unsigned long)trailerIsize
);
free(test);
aborting = true;
} else {
test[testLen] = 0;
if (DEBUGING) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
outData = test;
outLen = (size_t)testLen;
complete = true;
}
} else {
if (DEBUGING) {
Serial.printf("Decompress failed: res=%d out=%lu src=%lu\n", res, testLen, srcLen);
}
free(test);
aborting = true;
}
}
}
}
}
}
}
// --- Added: Force-close connection only if aborted to avoid TCP RST storms ---
if (aborting && stream) stream->stop(); // NEW: stop() only on abnormal termination
http.end();
free(buffer);
@@ -158,6 +507,18 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
bool NetworkClient::fetchAndDecompressJson(const String& url) {
_valid = false;
_doc.clear();
_imageWidth = 0;
_imageHeight = 0;
_numberPixels = 0;
_pictureBase64 = nullptr;
_pictureBase64Len = 0;
if (_jsonRaw != nullptr) {
free(_jsonRaw);
_jsonRaw = nullptr;
_jsonRawLen = 0;
}
uint8_t* raw = nullptr;
size_t rawLen = 0;
@@ -167,15 +528,38 @@ bool NetworkClient::fetchAndDecompressJson(const String& url) {
return false;
}
DeserializationError err = deserializeJson(_doc, raw, rawLen);
free(raw);
char* json = reinterpret_cast<char*>(raw);
bool ok = true;
ok = findJsonIntField(json, rawLen, "number_pixels", _numberPixels) && ok;
ok = findJsonIntField(json, rawLen, "width", _imageWidth) && ok;
ok = findJsonIntField(json, rawLen, "height", _imageHeight) && ok;
ok = extractJsonStringInPlace(json, rawLen, "picture_base64", _pictureBase64, _pictureBase64Len) && ok;
if (err) {
Serial.printf("JSON ERROR: %s\n", err.c_str());
if (!ok) {
Serial.println("JSON field extraction failed.");
free(raw);
return false;
}
if (DEBUGING) {Serial.println("JSON OK!");}
if (_imageWidth <= 0 || _imageHeight <= 0 || _pictureBase64Len == 0) {
Serial.printf("JSON invalid geometry/data w=%d h=%d b64=%u\n",
_imageWidth,
_imageHeight,
(unsigned int)_pictureBase64Len);
free(raw);
return false;
}
_jsonRaw = raw;
_jsonRawLen = rawLen;
if (DEBUGING) {
Serial.printf("JSON fields OK: num=%d w=%d h=%d b64=%u\n",
_numberPixels,
_imageWidth,
_imageHeight,
(unsigned int)_pictureBase64Len);
}
_valid = true;
return true;
}
@@ -184,6 +568,26 @@ JsonDocument& NetworkClient::json() {
return _doc;
}
int NetworkClient::imageWidth() const {
return _imageWidth;
}
int NetworkClient::imageHeight() const {
return _imageHeight;
}
int NetworkClient::numberPixels() const {
return _numberPixels;
}
const char* NetworkClient::pictureBase64() const {
return _pictureBase64;
}
size_t NetworkClient::pictureBase64Len() const {
return _pictureBase64Len;
}
bool NetworkClient::isValid() const {
return _valid;
}
+16 -1
View File
@@ -3,7 +3,7 @@
#include <WiFi.h>
#include <HTTPClient.h>
#define DEBUGING false // Debug flag for NetworkClient for more live information
#define DEBUGING true // Debug flag for NetworkClient for more live information
#define READLIMIT 200000 // HTTP read limit in byte for gzip content (can be adjusted)
#define CONNECTIONTIMEOUT 3000 // Timeout in ms for HTTP connection
#define TCPREADTIMEOUT 2000 // Timeout in ms for read HTTP client stack
@@ -12,16 +12,31 @@
class NetworkClient {
public:
NetworkClient(size_t reserveSize = 0);
~NetworkClient();
bool fetchAndDecompressJson(const String& url);
JsonDocument& json();
int imageWidth() const;
int imageHeight() const;
int numberPixels() const;
const char* pictureBase64() const;
size_t pictureBase64Len() const;
bool isValid() const;
private:
DynamicJsonDocument _doc;
bool _valid;
uint8_t* _jsonRaw;
size_t _jsonRawLen;
int _imageWidth;
int _imageHeight;
int _numberPixels;
char* _pictureBase64;
size_t _pictureBase64Len;
int skipGzipHeader(const uint8_t* data, size_t len);
bool httpGetGzip(const String& url, uint8_t*& outData, size_t& outLen);
static bool findJsonIntField(const char* json, size_t len, const char* key, int& outValue);
static bool extractJsonStringInPlace(char* json, size_t len, const char* key, char*& outValue, size_t& outLen);
};
+100 -15
View File
@@ -56,6 +56,61 @@ GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> display(GxEPD2_4
GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> & getdisplay(){return display;}
#endif
#ifdef TFT_DISPLAY
// panel device + offscreen shadow framebuffer
static LGFX panelDisplay;
static LGFXCanvas shadowDisplay(&panelDisplay);
static LGFXCanvas scaleDisplay(&panelDisplay);
static bool shadowDisplayInitialized = false;
static bool scaleDisplayInitialized = false;
static uint16_t scaleDisplayWidth = 0;
static uint16_t scaleDisplayHeight = 0;
LGFXCanvas & getdisplay(){return shadowDisplay;}
LGFX & getpaneldisplay(){return panelDisplay;}
LGFXCanvas & getscaleddisplay(){return scaleDisplay;}
bool initDisplayShadowBuffer(){
if (shadowDisplayInitialized) return true;
shadowDisplay.setPsram(true);
shadowDisplay.setColorDepth(16);
shadowDisplay.setTextDatum(textdatum_t::baseline_left);
if (shadowDisplay.createSprite(GxEPD_WIDTH, GxEPD_HEIGHT) == nullptr) {
shadowDisplayInitialized = false;
return false;
}
shadowDisplay.fillScreen(GxEPD_BLACK);
shadowDisplayInitialized = true;
return true;
}
bool initDisplayScaleBuffer(uint16_t width, uint16_t height){
if (scaleDisplayInitialized && scaleDisplayWidth == width && scaleDisplayHeight == height) {
return true;
}
scaleDisplay.deleteSprite();
scaleDisplay.setPsram(true);
scaleDisplay.setColorDepth(16);
scaleDisplay.setTextDatum(textdatum_t::baseline_left);
if (scaleDisplay.createSprite(width, height) == nullptr) {
scaleDisplayInitialized = false;
scaleDisplayWidth = 0;
scaleDisplayHeight = 0;
return false;
}
scaleDisplayWidth = width;
scaleDisplayHeight = height;
scaleDisplayInitialized = true;
return true;
}
#endif
// Horter I2C moduls
PCF8574 pcf8574_Modul1(PCF8574_I2C_ADDR1); // First digital IO modul PCF8574 from Horter
@@ -86,7 +141,7 @@ void hardwareInit(GwApi *api)
GwLog *logger = api->getLogger();
GwConfigHandler *config = api->getConfig();
Wire.begin();
Wire.begin(OBP_I2C_SDA, OBP_I2C_SCL);
// Init PCF8574 digital outputs
Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz
if(pcf8574_Modul1.begin()){ // Initialize PCF8574
@@ -175,7 +230,7 @@ void hardwareInit(GwApi *api)
void powerInit(String powermode) {
// Max Power | Only 5.0V | Min Power
if (powermode == "Max Power" || powermode == "Only 5.0V") {
#ifdef HARDWARE_V21
#ifdef BOARD_OBP60S3
setPortPin(OBP_POWER_50, true); // Power on 5.0V rail
#endif
#ifdef BOARD_OBP40S3
@@ -183,7 +238,7 @@ void powerInit(String powermode) {
setPortPin(OBP_POWER_SD, true); // Power on SD card
#endif
} else { // Min Power
#ifdef HARDWARE_V21
#ifdef BOARD_OBP60S3
setPortPin(OBP_POWER_50, false); // Power off 5.0V rail
#endif
#ifdef BOARD_OBP40S3
@@ -251,8 +306,12 @@ void deepSleep(CommonData &common){
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(65, 175);
getdisplay().print("To wake up press key and wait 5s");
getdisplay().nextPage(); // Update display contents
displayNextPage(); // Update display contents
#ifdef TFT_DISPLAY
getpaneldisplay().powerSave(true); // Display power save
#else
getdisplay().powerOff(); // Display power off
#endif
setPortPin(OBP_POWER_50, false); // Power off ePaper display
// Stop system
esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin
@@ -276,8 +335,12 @@ void deepSleep(CommonData &common){
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(65, 175);
getdisplay().print("To wake up press wheel and wait 5s");
getdisplay().nextPage(); // Partial update
displayNextPage(); // Partial update
#ifdef TFT_DISPLAY
getpaneldisplay().powerSave(true); // Display power save
#else
getdisplay().powerOff(); // Display power off
#endif
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
setPortPin(OBP_POWER_SD, false); // Power off SD card
// Stop system
@@ -479,8 +542,10 @@ std::vector<String> wordwrap(String &line, uint16_t maxwidth) {
void drawTextCenter(int16_t cx, int16_t cy, String text) {
int16_t x1, y1;
uint16_t w, h;
getdisplay().getTextBounds(text, 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(cx - w / 2, cy + h / 2);
displayGetTextBounds(text, 0, 0, &x1, &y1, &w, &h);
int16_t cursorX = cx - (x1 + static_cast<int16_t>(w / 2));
int16_t cursorY = cy - (y1 + static_cast<int16_t>(h / 2));
getdisplay().setCursor(cursorX, cursorY);
getdisplay().print(text);
}
@@ -488,19 +553,20 @@ void drawTextCenter(int16_t cx, int16_t cy, String text) {
void drawButtonCenter(int16_t cx, int16_t cy, int8_t sx, int8_t sy, String text, uint16_t fg, uint16_t bg, bool inverted) {
int16_t x1, y1;
uint16_t w, h;
uint16_t color;
getdisplay().getTextBounds(text, cx, cy, &x1, &y1, &w, &h); // Find text center
getdisplay().setCursor(cx - w/2, cy + h/2); // Set cursor to center
displayGetTextBounds(text, 0, 0, &x1, &y1, &w, &h);
int16_t cursorX = cx - (x1 + static_cast<int16_t>(w / 2));
int16_t cursorY = cy - (y1 + static_cast<int16_t>(h / 2));
//getdisplay().drawPixel(cx, cy, fg); // Debug pixel for center position
if (inverted) {
getdisplay().fillRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button
getdisplay().setTextColor(bg);
getdisplay().setCursor(cursorX, cursorY); // Set cursor to center
getdisplay().print(text); // Draw text
}
else{
getdisplay().drawRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button
getdisplay().setTextColor(fg);
getdisplay().setCursor(cursorX, cursorY); // Set cursor to center
getdisplay().print(text); // Draw text
}
}
@@ -509,7 +575,12 @@ void drawButtonCenter(int16_t cx, int16_t cy, int8_t sx, int8_t sy, String text,
void drawTextRalign(int16_t x, int16_t y, String text) {
int16_t x1, y1;
uint16_t w, h;
#ifdef TFT_DISPLAY
w = getdisplay().textWidth(text);
h = getdisplay().fontHeight();
#else
getdisplay().getTextBounds(text, 0, 150, &x1, &y1, &w, &h);
#endif
getdisplay().setCursor(x - w - 1, y); // '-1' required since some strings wrap around w/o it
getdisplay().print(text);
}
@@ -595,7 +666,7 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa
usbRxOld = commonData.status.usbRx;
usbTxOld = commonData.status.usbTx;
#ifdef HARDWARE_V21
#ifdef BOARD_OBP60S3
// Display key lock status
if (commonData.keylock) {
getdisplay().drawXBitmap(170, 1, lock_bits, icon_width, icon_height, commonData.fgcolor);
@@ -688,7 +759,7 @@ void displayFooter(CommonData &commonData) {
getdisplay().setFont(&Atari16px);
getdisplay().setTextColor(commonData.fgcolor);
#ifdef HARDWARE_V21
#ifdef BOARD_OBP60S3
// Frame around key icon area
if (! commonData.keylock) {
// horizontal elements
@@ -1000,7 +1071,14 @@ void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, ui
String lbl = String(angle);
int16_t bx, by;
uint16_t bw, bh;
getdisplay().getTextBounds(lbl, 0, 0, &bx, &by, &bw, &bh);
#ifdef TFT_DISPLAY
// LovyanGFX: compute width/height manually
bw = getdisplay().textWidth(lbl);
bh = getdisplay().fontHeight();
bx = 0; by = 0;
#else
getdisplay().getTextBounds(lbl, 0, 0, &bx, &by, &bw, &bh);
#endif
int16_t tx = xpos - bw/2;
int16_t ty = top + h + bh + 5; // A little spacing
getdisplay().setCursor(tx, ty);
@@ -1019,9 +1097,16 @@ void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUM
logger->logDebug(GwLog::LOG,"handle image request [%s]: %s", imgformat, filename);
uint8_t *fb = getdisplay().getBuffer(); // EPD framebuffer
uint8_t *fb = nullptr; // EPD framebuffer
std::vector<uint8_t> imageBuffer; // image in webserver transferbuffer
String mimetype;
#ifndef TFT_DISPLAY
fb = getdisplay().getBuffer(); // available only for EPD
#endif
if (!fb) {
request->send(500, "text/plain", "screenshot not available");
return;
}
if (imgformat == "gif") {
// GIF is commpressed with LZW, so small
+628 -4
View File
@@ -5,14 +5,28 @@
#include "OBP60Hardware.h"
#include "LedSpiTask.h"
#include "Graphics.h"
#include <GxEPD2_BW.h> // E-paper lib V2
#include <GxEPD2_BW.h> // GxEPD2 lib for b/w E-Ink displays
#include <Adafruit_FRAM_I2C.h> // I2C FRAM
#include <math.h>
#ifdef TFT_DISPLAY
#if !defined(TFT_320x480_ST7796) && !defined(TFT_320x480_ILI9488)
#error "TFT_DISPLAY requires one panel type: TFT_320x480_ST7796 or TFT_320x480_ILI9488"
#endif
#if defined(TFT_320x480_ST7796) && defined(TFT_320x480_ILI9488)
#error "Select exactly one TFT panel type: TFT_320x480_ST7796 or TFT_320x480_ILI9488"
#endif
#include <LovyanGFX.hpp> // TFT LCD lib for 320x480 color displays
#undef GxEPD_WHITE
#define GxEPD_WHITE TFT_WHITE // Replacement color for white on TFT (OBPHardware.h)
#undef GxEPD_BLACK
#define GxEPD_BLACK TFT_BLACK // Replacement color for black on TFT (OBPHardware.h)
#endif
#ifdef BOARD_OBP40S3
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#define MOUNT_POINT "/sdcard"
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#define MOUNT_POINT "/sdcard"
#endif
// FRAM address reservations 32kB: 0x0000 - 0x7FFF
@@ -74,11 +88,621 @@ GxEPD2_BW<GxEPD2_420_GYE042A87, GxEPD2_420_GYE042A87::HEIGHT> & getdisplay();
GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> & getdisplay();
#endif
#ifdef TFT_DISPLAY
// LovyanGFX based display wrapper for TFT panels
class LGFX : public lgfx::LGFX_Device {
public:
lgfx::Bus_SPI _bus_instance;
#ifdef TFT_320x480_ST7796
LGFX(void) {
{
auto cfg = _bus_instance.config();
cfg.spi_host = SPI2_HOST;
cfg.spi_mode = 0;
cfg.freq_write = 80000000; // High speed ST7796
cfg.freq_read = 16000000;
cfg.pin_sclk = OBP_SPI_CLK;
cfg.pin_mosi = OBP_SPI_DIN;
cfg.pin_miso = -1;
cfg.pin_dc = OBP_SPI_DC;
_bus_instance.config(cfg);
_panel_instance.setBus(&_bus_instance);
}
{
auto cfg = _panel_instance.config();
cfg.pin_cs = OBP_SPI_CS;
cfg.pin_rst = OBP_SPI_RST;
cfg.pin_busy = -1;
cfg.panel_width = 320; // Native width resolution
cfg.panel_height = 480; // Native hight resolution
cfg.offset_x = 0; // No panel offset: full framebuffer mapping
cfg.offset_y = 0; // No panel offset: full framebuffer mapping
cfg.offset_rotation = 3; // Rotate display content conter clock wise 90 deg ST7796
cfg.dummy_read_pixel = 8;
cfg.dummy_read_bits = 1;
cfg.memory_width = 320;
cfg.memory_height = 480;
// cfg.pwm_control not available in this LovyanGFX version
cfg.invert = false;
cfg.rgb_order = false;
cfg.dlen_16bit = false;
cfg.bus_shared = true;
_panel_instance.config(cfg);
}
// No dedicated TFT PWM backlight pin configured on this board.
// Keep backlight handling outside LovyanGFX to avoid LEDC init on invalid GPIO.
setPanel(&_panel_instance);
// Match Adafruit GFX cursor semantics: y coordinate is text baseline.
setTextDatum(textdatum_t::baseline_left);
}
#endif
#ifdef TFT_320x480_ILI9488
LGFX(void) {
{
auto cfg = _bus_instance.config();
cfg.spi_host = SPI2_HOST;
cfg.spi_mode = 0;
cfg.freq_write = 40000000; // Slow speed ILI9488
cfg.freq_read = 16000000;
cfg.pin_sclk = OBP_SPI_CLK;
cfg.pin_mosi = OBP_SPI_DIN;
cfg.pin_miso = -1;
cfg.pin_dc = OBP_SPI_DC;
_bus_instance.config(cfg);
_panel_instance.setBus(&_bus_instance);
}
{
auto cfg = _panel_instance.config();
cfg.pin_cs = OBP_SPI_CS;
cfg.pin_rst = OBP_SPI_RST;
cfg.pin_busy = -1;
cfg.panel_width = 320; // Native width resolution
cfg.panel_height = 480; // Native hight resolution
cfg.offset_x = 0; // No panel offset: full framebuffer mapping
cfg.offset_y = 0; // No panel offset: full framebuffer mapping
cfg.offset_rotation = 1; // Rotate display content clock wise 90 deg ILI9488
cfg.dummy_read_pixel = 8;
cfg.dummy_read_bits = 1;
cfg.memory_width = 320;
cfg.memory_height = 480;
// cfg.pwm_control not available in this LovyanGFX version
cfg.invert = false;
cfg.rgb_order = false;
cfg.dlen_16bit = false;
cfg.bus_shared = true;
_panel_instance.config(cfg);
}
// No dedicated TFT PWM backlight pin configured on this board.
// Keep backlight handling outside LovyanGFX to avoid LEDC init on invalid GPIO.
setPanel(&_panel_instance);
// Match Adafruit GFX cursor semantics: y coordinate is text baseline.
setTextDatum(textdatum_t::baseline_left);
}
#endif
// compatibility helpers --------------------------------------------------
using lgfx::LGFX_Device::setFont;
void setFont(const lgfx::IFont* font) {
_currentAdfFont = nullptr;
lgfx::LGFX_Device::setFont(font);
}
// Adafruit GFX fonts support on TFT via LovyanGFX bridge
void setFont(const GFXfont *font) {
if (font == nullptr) {
_currentAdfFont = nullptr;
lgfx::LGFX_Device::setFont(nullptr);
return;
}
if (font->glyph == nullptr || font->bitmap == nullptr || font->last < font->first) {
_currentAdfFont = nullptr;
lgfx::LGFX_Device::setFont(nullptr);
return;
}
const uint16_t glyphCount = static_cast<uint16_t>(font->last - font->first + 1);
if (glyphCount == 0) {
lgfx::LGFX_Device::setFont(nullptr);
return;
}
if (_adfGlyphCount != glyphCount || _adfGlyphBridge == nullptr) {
if (_adfGlyphBridge != nullptr) {
free(_adfGlyphBridge);
_adfGlyphBridge = nullptr;
_adfGlyphCount = 0;
}
_adfGlyphBridge = static_cast<lgfx::GFXglyph*>(malloc(sizeof(lgfx::GFXglyph) * glyphCount));
if (_adfGlyphBridge == nullptr) {
lgfx::LGFX_Device::setFont(nullptr);
return;
}
_adfGlyphCount = glyphCount;
}
for (uint16_t index = 0; index < glyphCount; ++index) {
_adfGlyphBridge[index].bitmapOffset = font->glyph[index].bitmapOffset;
_adfGlyphBridge[index].width = font->glyph[index].width;
_adfGlyphBridge[index].height = font->glyph[index].height;
_adfGlyphBridge[index].xAdvance = font->glyph[index].xAdvance;
_adfGlyphBridge[index].xOffset = font->glyph[index].xOffset;
_adfGlyphBridge[index].yOffset = font->glyph[index].yOffset;
}
_adfFontBridge = lgfx::GFXfont(
const_cast<uint8_t*>(font->bitmap),
_adfGlyphBridge,
font->first,
font->last,
font->yAdvance
);
_currentAdfFont = font;
lgfx::LGFX_Device::setFont(&_adfFontBridge);
}
void getTextBounds(const String &txt, int16_t x, int16_t y,
int16_t *x0, int16_t *y0,
uint16_t *w, uint16_t *h) {
if (w == nullptr || h == nullptr) {
return;
}
if (_currentAdfFont == nullptr || txt.length() == 0) {
*w = textWidth(txt);
*h = fontHeight();
if (x0) *x0 = x;
if (y0) *y0 = y - static_cast<int16_t>(*h);
return;
}
const float sx = getTextSizeX();
const float sy = getTextSizeY();
int32_t cursorX = x;
int32_t cursorY = y;
int32_t minX = 0;
int32_t minY = 0;
int32_t maxX = 0;
int32_t maxY = 0;
bool hasPixel = false;
for (size_t i = 0; i < txt.length(); ++i) {
char c = txt[i];
if (c == '\r') continue;
if (c == '\n') {
cursorX = x;
cursorY += static_cast<int32_t>(_currentAdfFont->yAdvance * sy);
continue;
}
if (c < _currentAdfFont->first || c > _currentAdfFont->last) {
continue;
}
const GFXglyph* glyph = &_currentAdfFont->glyph[static_cast<uint16_t>(c) - _currentAdfFont->first];
const int32_t gw = static_cast<int32_t>(glyph->width * sx);
const int32_t gh = static_cast<int32_t>(glyph->height * sy);
const int32_t gx1 = cursorX + static_cast<int32_t>(glyph->xOffset * sx);
const int32_t gy1 = cursorY + static_cast<int32_t>(glyph->yOffset * sy);
if (gw > 0 && gh > 0) {
const int32_t gx2 = gx1 + gw - 1;
const int32_t gy2 = gy1 + gh - 1;
if (!hasPixel) {
minX = gx1; minY = gy1; maxX = gx2; maxY = gy2;
hasPixel = true;
} else {
if (gx1 < minX) minX = gx1;
if (gy1 < minY) minY = gy1;
if (gx2 > maxX) maxX = gx2;
if (gy2 > maxY) maxY = gy2;
}
}
cursorX += static_cast<int32_t>(glyph->xAdvance * sx);
}
if (hasPixel) {
if (x0) *x0 = static_cast<int16_t>(minX);
if (y0) *y0 = static_cast<int16_t>(minY);
*w = static_cast<uint16_t>(maxX - minX + 1);
*h = static_cast<uint16_t>(maxY - minY + 1);
} else {
if (x0) *x0 = x;
if (y0) *y0 = y;
*w = 0;
*h = 0;
}
}
// E-Ink interface compatibility
void setFullWindow() { /* no-op on TFT */ }
// Runtime panel offset control for TFT panels
void setPanelOffset(int16_t x, int16_t y) {
auto cfg = _panel_instance.config();
cfg.offset_x = x;
cfg.offset_y = y;
_panel_instance.config(cfg);
}
void getPanelOffset(int16_t &x, int16_t &y) {
auto cfg = _panel_instance.config();
x = cfg.offset_x;
y = cfg.offset_y;
}
private:
lgfx::GFXfont _adfFontBridge { nullptr, nullptr, 0, 0, 0 };
lgfx::GFXglyph* _adfGlyphBridge = nullptr;
uint16_t _adfGlyphCount = 0;
const GFXfont* _currentAdfFont = nullptr;
#if defined(TFT_320x480_ST7796)
lgfx::Panel_ST7796 _panel_instance;
#elif defined(TFT_320x480_ILI9488)
lgfx::Panel_ILI9488 _panel_instance;
#endif
};
class LGFXCanvas : public lgfx::LGFX_Sprite {
public:
explicit LGFXCanvas(lgfx::LGFX_Device* parent = nullptr) : lgfx::LGFX_Sprite(parent) {}
using lgfx::LGFX_Sprite::setFont;
void setFont(const GFXfont *font) {
if (font == nullptr) {
_currentAdfFont = nullptr;
lgfx::LGFX_Sprite::setFont(nullptr);
return;
}
if (font->glyph == nullptr || font->bitmap == nullptr || font->last < font->first) {
_currentAdfFont = nullptr;
lgfx::LGFX_Sprite::setFont(nullptr);
return;
}
const uint16_t glyphCount = static_cast<uint16_t>(font->last - font->first + 1);
if (glyphCount == 0) {
lgfx::LGFX_Sprite::setFont(nullptr);
return;
}
if (_adfGlyphCount != glyphCount || _adfGlyphBridge == nullptr) {
if (_adfGlyphBridge != nullptr) {
free(_adfGlyphBridge);
_adfGlyphBridge = nullptr;
_adfGlyphCount = 0;
}
_adfGlyphBridge = static_cast<lgfx::GFXglyph*>(malloc(sizeof(lgfx::GFXglyph) * glyphCount));
if (_adfGlyphBridge == nullptr) {
lgfx::LGFX_Sprite::setFont(nullptr);
return;
}
_adfGlyphCount = glyphCount;
}
for (uint16_t index = 0; index < glyphCount; ++index) {
_adfGlyphBridge[index].bitmapOffset = font->glyph[index].bitmapOffset;
_adfGlyphBridge[index].width = font->glyph[index].width;
_adfGlyphBridge[index].height = font->glyph[index].height;
_adfGlyphBridge[index].xAdvance = font->glyph[index].xAdvance;
_adfGlyphBridge[index].xOffset = font->glyph[index].xOffset;
_adfGlyphBridge[index].yOffset = font->glyph[index].yOffset;
}
_adfFontBridge = lgfx::GFXfont(
const_cast<uint8_t*>(font->bitmap),
_adfGlyphBridge,
font->first,
font->last,
font->yAdvance
);
_currentAdfFont = font;
lgfx::LGFX_Sprite::setFont(&_adfFontBridge);
}
void getTextBounds(const String &txt, int16_t x, int16_t y,
int16_t *x0, int16_t *y0,
uint16_t *w, uint16_t *h) {
if (w == nullptr || h == nullptr) {
return;
}
if (_currentAdfFont == nullptr || txt.length() == 0) {
*w = textWidth(txt);
*h = fontHeight();
if (x0) *x0 = x;
if (y0) *y0 = y - static_cast<int16_t>(*h);
return;
}
const float sx = getTextSizeX();
const float sy = getTextSizeY();
int32_t cursorX = x;
int32_t cursorY = y;
int32_t minX = 0;
int32_t minY = 0;
int32_t maxX = 0;
int32_t maxY = 0;
bool hasPixel = false;
for (size_t i = 0; i < txt.length(); ++i) {
char c = txt[i];
if (c == '\r') continue;
if (c == '\n') {
cursorX = x;
cursorY += static_cast<int32_t>(_currentAdfFont->yAdvance * sy);
continue;
}
if (c < _currentAdfFont->first || c > _currentAdfFont->last) {
continue;
}
const GFXglyph* glyph = &_currentAdfFont->glyph[static_cast<uint16_t>(c) - _currentAdfFont->first];
const int32_t gw = static_cast<int32_t>(glyph->width * sx);
const int32_t gh = static_cast<int32_t>(glyph->height * sy);
const int32_t gx1 = cursorX + static_cast<int32_t>(glyph->xOffset * sx);
const int32_t gy1 = cursorY + static_cast<int32_t>(glyph->yOffset * sy);
if (gw > 0 && gh > 0) {
const int32_t gx2 = gx1 + gw - 1;
const int32_t gy2 = gy1 + gh - 1;
if (!hasPixel) {
minX = gx1; minY = gy1; maxX = gx2; maxY = gy2;
hasPixel = true;
} else {
if (gx1 < minX) minX = gx1;
if (gy1 < minY) minY = gy1;
if (gx2 > maxX) maxX = gx2;
if (gy2 > maxY) maxY = gy2;
}
}
cursorX += static_cast<int32_t>(glyph->xAdvance * sx);
}
if (hasPixel) {
if (x0) *x0 = static_cast<int16_t>(minX);
if (y0) *y0 = static_cast<int16_t>(minY);
*w = static_cast<uint16_t>(maxX - minX + 1);
*h = static_cast<uint16_t>(maxY - minY + 1);
} else {
if (x0) *x0 = x;
if (y0) *y0 = y;
*w = 0;
*h = 0;
}
}
void setFullWindow() { /* no-op on TFT */ }
private:
lgfx::GFXfont _adfFontBridge { nullptr, nullptr, 0, 0, 0 };
lgfx::GFXglyph* _adfGlyphBridge = nullptr;
uint16_t _adfGlyphCount = 0;
const GFXfont* _currentAdfFont = nullptr;
};
LGFXCanvas & getdisplay();
LGFX & getpaneldisplay();
LGFXCanvas & getscaleddisplay();
bool initDisplayShadowBuffer();
bool initDisplayScaleBuffer(uint16_t width, uint16_t height);
#endif
// Page display return values
#define PAGE_OK 0 // all ok, do nothing
#define PAGE_UPDATE 1 // page wants display to update
#define PAGE_HIBERNATE 2 // page wants displey to hibernate
#ifdef TFT_DISPLAY
#ifndef OBP_TFT_ENABLE_SCALING
#define OBP_TFT_ENABLE_SCALING 1
#endif
#ifndef OBP_TFT_SCALE_ANTIALIAS
#define OBP_TFT_SCALE_ANTIALIAS 1
#endif
#if OBP_TFT_SCALE_ANTIALIAS
inline uint16_t lerpRgb565(uint16_t c0, uint16_t c1, uint16_t w8) {
const uint16_t r0 = (c0 >> 11) & 0x1F;
const uint16_t g0 = (c0 >> 5) & 0x3F;
const uint16_t b0 = c0 & 0x1F;
const uint16_t r1 = (c1 >> 11) & 0x1F;
const uint16_t g1 = (c1 >> 5) & 0x3F;
const uint16_t b1 = c1 & 0x1F;
const uint16_t r = static_cast<uint16_t>(r0 + ((static_cast<int32_t>(r1) - r0) * w8 + 128) / 256);
const uint16_t g = static_cast<uint16_t>(g0 + ((static_cast<int32_t>(g1) - g0) * w8 + 128) / 256);
const uint16_t b = static_cast<uint16_t>(b0 + ((static_cast<int32_t>(b1) - b0) * w8 + 128) / 256);
return static_cast<uint16_t>((r << 11) | (g << 5) | b);
}
inline uint16_t sampleBilinearRgb565(LGFXCanvas& src, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t wx, uint16_t wy) {
const uint16_t c00 = src.readPixel(x0, y0);
const uint16_t c10 = src.readPixel(x1, y0);
const uint16_t c01 = src.readPixel(x0, y1);
const uint16_t c11 = src.readPixel(x1, y1);
const uint16_t top = lerpRgb565(c00, c10, wx);
const uint16_t bot = lerpRgb565(c01, c11, wx);
return lerpRgb565(top, bot, wy);
}
#endif
#endif
// Draw monochrome bitmap on both E-Ink and TFT displays
// supports various packing and bit orders; optional runtime conversion for TFT
inline void drawMonochromeBitmap(
int16_t x, int16_t y,
const uint8_t *bmp,
int16_t w, int16_t h,
uint16_t color,
bool vertical=false, // true: bytes run vertically (each byte 8 pixels down)
bool lsbFirst=false, // true: least significant bit = left/top pixel
bool mirrorX=false) // true: bytes run right-to-left within each row
{
#ifdef TFT_DISPLAY
// TFT converts perpixel
int bytesPerRow = (w + 7) / 8;
for (int yy = 0; yy < h; yy++) {
for (int xx = 0; xx < w; xx++) {
int byteIdx;
int bitIdx;
if (vertical) {
// vertical packing: column-major bytes
int col = mirrorX ? (w - 1 - xx) : xx;
byteIdx = col * ((h + 7) / 8) + (yy / 8);
bitIdx = yy % 8;
} else {
// horizontal packing: row-major bytes
int col = mirrorX ? (w - 1 - xx) : xx;
byteIdx = yy * bytesPerRow + (col / 8);
bitIdx = col % 8;
}
uint8_t b = bmp[byteIdx];
bool pix;
if (lsbFirst) {
pix = b & (1 << bitIdx);
} else {
pix = b & (1 << (7 - bitIdx));
}
if (pix) {
getdisplay().drawPixel(x + xx, y + yy, color);
}
}
if ((yy & 0x0F) == 0) {
yield();
}
}
#else
// EPaper: just hand over to driver (expects MSBfirst horizontal)
getdisplay().drawBitmap(x, y, bmp, w, h, color);
#endif
}
// Display wrapper functions for E-Ink/TFT compatibility
// generic bitmap draw that accepts 1bit data; TFT version
// forwards to drawMonochromeBitmap whereas EPD uses native drawBitmap
inline void displayDrawBitmap(int16_t x, int16_t y,
const uint8_t *bmp,
int16_t w, int16_t h,
uint16_t color) {
#ifdef TFT_DISPLAY
drawMonochromeBitmap(x, y, bmp, w, h, color);
#else
getdisplay().drawBitmap(x, y, bmp, w, h, color);
#endif
}
inline void displayFirstPage() {
#ifdef TFT_DISPLAY
initDisplayShadowBuffer();
#else
getdisplay().firstPage();
#endif
}
inline void displayNextPage() {
#ifdef TFT_DISPLAY
if (initDisplayShadowBuffer()) {
LGFXCanvas &src = getdisplay();
LGFX &dst = getpaneldisplay();
const uint16_t srcW = GxEPD_WIDTH;
const uint16_t srcH = GxEPD_HEIGHT;
const uint16_t dstW = static_cast<uint16_t>(dst.width());
const uint16_t dstH = static_cast<uint16_t>(dst.height());
#if !OBP_TFT_ENABLE_SCALING
const uint16_t drawX = static_cast<uint16_t>((dstW > srcW) ? ((dstW - srcW) / 2U) : 0U);
const uint16_t drawY = static_cast<uint16_t>((dstH > srcH) ? ((dstH - srcH) / 2U) : 0U);
src.pushSprite(drawX, drawY);
#else
const uint16_t targetH = (dstH < 320U) ? dstH : 320U;
const uint32_t scaledW32 = (static_cast<uint32_t>(srcW) * targetH + (srcH / 2U)) / srcH;
const uint16_t targetW = static_cast<uint16_t>((scaledW32 < dstW) ? scaledW32 : dstW);
const uint16_t drawX = static_cast<uint16_t>((dstW - targetW) / 2U);
const uint16_t drawY = static_cast<uint16_t>((dstH - targetH) / 2U);
const uint16_t borderColor = src.readPixel(0, 0);
if (initDisplayScaleBuffer(dstW, dstH)) {
LGFXCanvas &scaled = getscaleddisplay();
scaled.fillScreen(borderColor);
for (uint16_t y = 0; y < targetH; ++y) {
const uint32_t syfp = (targetH > 1)
? (static_cast<uint32_t>(y) * (srcH - 1) * 256U) / (targetH - 1)
: 0;
const uint16_t sy0 = static_cast<uint16_t>(syfp >> 8);
const uint16_t sy1 = (sy0 + 1 < srcH) ? static_cast<uint16_t>(sy0 + 1) : sy0;
const uint16_t wy = static_cast<uint16_t>(syfp & 0xFFU);
for (uint16_t x = 0; x < targetW; ++x) {
const uint32_t sxfp = (targetW > 1)
? (static_cast<uint32_t>(x) * (srcW - 1) * 256U) / (targetW - 1)
: 0;
const uint16_t sx0 = static_cast<uint16_t>(sxfp >> 8);
const uint16_t sx1 = (sx0 + 1 < srcW) ? static_cast<uint16_t>(sx0 + 1) : sx0;
#if OBP_TFT_SCALE_ANTIALIAS
const uint16_t wx = static_cast<uint16_t>(sxfp & 0xFFU);
const uint16_t color = sampleBilinearRgb565(src, sx0, sy0, sx1, sy1, wx, wy);
#else
const uint16_t color = src.readPixel(sx0, sy0);
#endif
scaled.drawPixel(drawX + x, drawY + y, color);
}
if ((y & 0x0F) == 0) {
yield();
}
}
scaled.pushSprite(0, 0);
} else {
src.pushSprite(drawX, drawY);
}
#endif
}
#else
getdisplay().nextPage();
#endif
}
inline void displaySetPartialWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
#ifdef TFT_DISPLAY
// TFT LCD doesn't use partial windows
(void)x; (void)y; (void)w; (void)h;
#else
getdisplay().setPartialWindow(x, y, w, h);
#endif
}
inline void displaySetFullWindow() {
#ifdef TFT_DISPLAY
// TFT LCD doesn't need setFullWindow()
#else
getdisplay().setFullWindow();
#endif
}
// replacement for getTextBounds that works with both EPD and TFT
inline void displayGetTextBounds(const String &txt, int16_t x, int16_t y,
int16_t *x0, int16_t *y0,
uint16_t *w, uint16_t *h) {
#ifdef TFT_DISPLAY
getdisplay().getTextBounds(txt, x, y, x0, y0, w, h);
#else
getdisplay().getTextBounds(txt, x, y, x0, y0, w, h);
#endif
}
void fillPoly4(const std::vector<Point>& p4, uint16_t color);
void drawPoly(const std::vector<Point>& points, uint16_t color);
+1 -1
View File
@@ -835,7 +835,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
result.cvalue = dplace;
}
//########################################################
else if (value->getFormat() == "formatXdr:A:D"){
else if ((value->getFormat() == "formatXdr:A:D") || ((value->getFormat() == "formatXdr:A:rd"))){
double angle = 0;
if (usesimudata == false) {
angle = value->value;
+6 -2
View File
@@ -1,7 +1,7 @@
// General hardware definitions
// CAN and RS485 bus pin definitions see obp60task.h
#if defined HARDWARE_V20 || HARDWARE_V21
#if defined BOARD_OBP60S3 || defined BOARD_OBP70S3
// Direction pin for RS485 NMEA0183
#define OBP_DIRECTION_PIN 18
// I2C
@@ -34,13 +34,17 @@
#define PCF8574_I2C_ADDR1 0x20 // First digital out module
// FRAM (e.g. MB85RC256V)
#define FRAM_I2C_ADDR 0x50
// SPI (E-Ink display, Extern Bus)
// SPI (E-paper display, TFT display Extern Bus)
#define OBP_SPI_CS 39
#define OBP_SPI_DC 40
#define OBP_SPI_RST 41
#define OBP_SPI_BUSY 42
#define OBP_SPI_CLK 38
#define OBP_SPI_DIN 48
#define OBP_TFT_OFFSET_X 10 // ST7796, ILI9488 operating x-offset for centered 400x300 content
#define OBP_TFT_OFFSET_Y -20 // ST7796, ILI9488 operating y-offset for centered 400x300 content
#define TFT_BLACK 0x0109 // Replacement color for black on TFT (RGB565)
#define TFT_WHITE 0xFFFF // Replacement color for white on TFT (RGB565)
#define SHOW_TIME 6000 // Show time in [ms] for logo and WiFi QR code
#define FULL_REFRESH_TIME 600 // Refresh cycle time in [s][600...3600] for full display update (very important healcy function)
#define GxEPD_WIDTH 400 // Display width
+1 -1
View File
@@ -58,7 +58,7 @@ void initKeys(CommonData &commonData) {
commonData.keydata[5].h = height;
}
#if defined HARDWARE_V20 || HARDWARE_V21
#ifdef BOARD_OBP60S3
// Keypad functions for original OBP60 hardware
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {
+1 -1
View File
@@ -39,7 +39,7 @@ void qrWiFi(String ssid, String passwd, uint16_t fgcolor, uint16_t bgcolor){
getdisplay().setTextColor(fgcolor);
getdisplay().setCursor(140, 285);
getdisplay().print("WiFi");
getdisplay().nextPage(); // Full Refresh
displayNextPage(); // Full Refresh
}
#endif
+8 -2
View File
@@ -28,8 +28,14 @@ Chart::Chart(RingBuffer<uint16_t>& dataBuf, double dfltRng, CommonData& common,
fgColor = commonData->fgcolor;
bgColor = commonData->bgcolor;
dWidth = getdisplay().width();
dHeight = getdisplay().height();
// display dimensions (avoid calling width()/height() on incomplete LGFX type)
#ifdef TFT_DISPLAY
dWidth = 480;
dHeight = 320;
#else
dWidth = getdisplay().width();
dHeight = getdisplay().height();
#endif
dataBuf.getMetaData(dbName, dbFormat);
dbMIN_VAL = dataBuf.getMinVal();
+1 -1
View File
@@ -117,7 +117,7 @@ class PageAutopilot : public Page
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
/*
// Horizontal line 2 pix top & bottom
+1 -1
View File
@@ -105,7 +105,7 @@ class PageBME280 : public Page
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
+1 -1
View File
@@ -158,7 +158,7 @@ class PageBattery : public Page
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// Show average settings
getdisplay().setTextColor(commonData->fgcolor);
+1 -1
View File
@@ -184,7 +184,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
+6 -6
View File
@@ -375,7 +375,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
@@ -442,8 +442,8 @@ public:
uint16_t wDigit, hDigit;
uint16_t wColon, hColon;
getdisplay().getTextBounds("00", 0, 0, &x0, &y0, &wDigit, &hDigit);
getdisplay().getTextBounds(":", 0, 0, &x0, &y0, &wColon, &hColon);
displayGetTextBounds("00", 0, 0, &x0, &y0, &wDigit, &hDigit);
displayGetTextBounds(":", 0, 0, &x0, &y0, &wColon, &hColon);
uint16_t totalWidth = 3 * wDigit + 2 * wColon;
@@ -453,7 +453,7 @@ public:
// Draw time string centered
int16_t x1b, y1b;
uint16_t wb, hb;
getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb);
displayGetTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb);
int16_t textX = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2;
int16_t textY = centerY + hb / 2;
@@ -520,7 +520,7 @@ public:
int16_t x1b, y1b;
uint16_t wb, hb;
getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb);
displayGetTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb);
int16_t x = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2;
int16_t y = 150 + hb / 2;
@@ -665,7 +665,7 @@ public:
// Print text centered on position x, y
int16_t x1c, y1c; // Return values of getTextBounds
uint16_t wc, hc; // Return values of getTextBounds
getdisplay().getTextBounds(ii, int(x), int(y), &x1c, &y1c, &wc, &hc); // Calc width of new string
displayGetTextBounds(ii, int(x), int(y), &x1c, &y1c, &wc, &hc); // Calc width of new string
getdisplay().setCursor(x - wc / 2, y + hc / 2);
if (i % 90 == 0) {
getdisplay().setFont(&Ubuntu_Bold12pt8b);
+1 -1
View File
@@ -109,7 +109,7 @@ class PageCompass : public Page
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
// Horizontal line 2 pix top & bottom
+1 -1
View File
@@ -91,7 +91,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
+1 -1
View File
@@ -106,7 +106,7 @@ bool button5 = false;
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold12pt8b);
// Write text
+1 -1
View File
@@ -138,7 +138,7 @@ class PageFluid : public Page
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height());
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height());
getdisplay().setTextColor(commonData->fgcolor);
+1 -1
View File
@@ -91,7 +91,7 @@ class PageFourValues : public Page
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
+1 -1
View File
@@ -91,7 +91,7 @@ class PageFourValues2 : public Page
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
+1 -1
View File
@@ -83,7 +83,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
+2 -2
View File
@@ -68,7 +68,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
//*******************************************************************************************
@@ -105,7 +105,7 @@ public:
// Print text centered on position x, y
int16_t x1, y1; // Return values of getTextBounds
uint16_t w, h; // Return values of getTextBounds
getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
displayGetTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
getdisplay().setCursor(x-w/2, y+h/2);
if(i % 30 == 0){
getdisplay().setFont(&Ubuntu_Bold8pt8b);
+206 -33
View File
@@ -4,6 +4,7 @@
#include "OBP60Extensions.h"
#include "NetworkClient.h" // Network connection
#include "ImageDecoder.h" // Image decoder for navigation map
#include <mbedtls/base64.h>
#include "Logo_OBP_400x300_sw.h"
@@ -12,6 +13,54 @@
NetworkClient net(JSON_BUFFER); // Define network client
ImageDecoder decoder; // Define image decoder
#ifdef TFT_DISPLAY
// Set to true to render a generated RGB565 color-bar test image.
static constexpr bool kShowRgb565StripeTestImage = false;
static void drawRgb565Image(int16_t x, int16_t y, const uint16_t *img, int16_t w, int16_t h) {
if (img == nullptr || w <= 0 || h <= 0) {
return;
}
for (int16_t yy = 0; yy < h; ++yy) {
const uint16_t *row = img + ((size_t)yy * (size_t)w);
for (int16_t xx = 0; xx < w; ++xx) {
getdisplay().drawPixel(x + xx, y + yy, row[xx]);
}
if ((yy & 0x0F) == 0) {
yield();
}
}
}
static void createRgb565StripeImage(uint16_t *img, int16_t w, int16_t h) {
if (img == nullptr || w <= 0 || h <= 0) {
return;
}
static const uint16_t stripes[] = {
0xF800, // red
0xFD20, // orange
0xFFE0, // yellow
0x07E0, // green
0x07FF, // cyan
0x001F, // blue
0xF81F, // magenta
0xFFFF // white
};
const int stripeCount = (int)(sizeof(stripes) / sizeof(stripes[0]));
for (int16_t y = 0; y < h; ++y) {
uint16_t *row = img + ((size_t)y * (size_t)w);
for (int16_t x = 0; x < w; ++x) {
int idx = ((int)x * stripeCount) / (int)w;
if (idx >= stripeCount) {
idx = stripeCount - 1;
}
row[x] = stripes[idx];
}
}
}
#endif
class PageNavigation : public Page
{
// Values for buttons
@@ -24,13 +73,19 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
int imageBackupWidth = 0;
int imageBackupHeight = 0;
size_t imageBackupSize = 0;
size_t imageBackupCapacity = 0;
bool hasImageBackup = false;
bool imageBackupIsRgb565 = false;
public:
PageNavigation(CommonData &common){
commonData = &common;
common.logger->logDebug(GwLog::LOG,"Instantiate PageNavigation");
imageBackupData = (uint8_t*)heap_caps_malloc((GxEPD_WIDTH * GxEPD_HEIGHT), MALLOC_CAP_SPIRAM);
imageBackupCapacity = (size_t)GxEPD_WIDTH * (size_t)GxEPD_HEIGHT;
#ifdef TFT_DISPLAY
imageBackupCapacity *= 2U;
#endif
imageBackupData = (uint8_t*)heap_caps_malloc(imageBackupCapacity, MALLOC_CAP_SPIRAM);
}
// Set botton labels
@@ -295,6 +350,18 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
mType = 9;
dType = 1;
}
else if(mapType == "C-Map"){
mType = 103486987;
dType = 1;
}
else if(mapType == "Garmin Fish"){
mType = 113486987;
dType = 1;
}
else if(mapType == "Garmin Nav"){
mType = 123486987;
dType = 1;
}
else{
mType = 1;
dType = 1;
@@ -347,22 +414,33 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
// URL to OBP Maps Converter
// For more details see: https://github.com/norbert-walter/maps-converter
String url = String("http://") + server + ":" + port + // OBP Server
String("/get_image_json?") + // Service: Output B&W picture as JSON (Base64 + gzip)
"zoom=" + zoom + // Default zoom level: 15
String("/get_image_json?") + // Service: Output B&W picture as JSON (Base64 + gzip)
#ifdef TFT_DISPLAY
"oformat=3" + // Image output format in JSON: 3=RGB565 format
#else
"oformat=4" + // Image output format in JSON: 4=b/w 1-Bit format
#endif
"&zoom=" + zoom + // Default zoom level: 15
"&lat=" + String(latitude, 6) + // Latitude
"&lon=" + String(longitude, 6) + // Longitude
"&mrot=" + mapRot + // Rotation angle navigation map in degree
"&mtype=" + mType + // Default Map: Open Street Map
"&dtype=" + dType + // Dithering type: Atkinson dithering
"&width=400" + // With navigation map
"&height=250" + // Height navigation map
"&cutout=0" + // No picture cutouts
"&tab=0" + // No tab size
"&border=2" + // Border line size: 2 pixel
"&symbol=2" + // Symbol: Triangle
#ifdef TFT_DISPLAY
"&itype=1" + // Image type: 1=Color
#else
"&itype=4" + // Image type: 4=b/w with dithering
#endif
"&dtype=" + dType + // Dithering type: Atkinson dithering (only activ when itype=4 otherwise inactive)
"&width=400" + // With navigation map
"&height=250" + // Height navigation map
"&cutout=0" + // No picture cutouts (tab, border and alpha are unused when cutout=0)
"&tab=0" + // No tab size (only available when sqare cutouts selected coutout=3...7)
"&border=2" + // Border line size: 2 pixel (only available when sqare cutouts selected)
"&alpha=80" + // Alpha for tabs: 80% visible (only available when sqare cutouts selected)
"&symbol=2" + // Symbol: Triangle
"&srot=" + symbolRot + // Symbol rotation angle
"&ssize=15" + // Symbole size: 15 pixel
"&grid=" + mapGrid // Show grid: On
"&ssize=15" + // Symbole size: 15 pixel (center pointer)
"&grid=" + mapGrid // Show grid: On
;
// Draw page
@@ -371,19 +449,45 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
// ############### Draw Navigation Map ################
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
// NEW: simple exponential backoff for 1 Hz polling (prevents connection-refused storms)
static uint32_t nextAllowedMs = 0;
static uint8_t failCount = 0;
uint32_t now = millis();
// NEW: if we are in backoff window, skip network call and use backup immediately
bool allowFetch = ((int32_t)(now - nextAllowedMs) >= 0);
// If a network connection to URL then load the navigation map
if (net.fetchAndDecompressJson(url)) {
if (allowFetch && net.fetchAndDecompressJson(url)) {
auto& json = net.json(); // Extract JSON content
int numPix = json["number_pixels"] | 0; // Read number of pixels
imgWidth = json["width"] | 0; // Read width of image
imgHeight = json["height"] | 0; // Read height og image
// NEW: reset backoff on success
failCount = 0;
nextAllowedMs = now + 1000; // keep 1 Hz on success
const char* b64src = json["picture_base64"].as<const char*>(); // Read picture as Base64 content
size_t b64len = strlen(b64src); // Calculate length of Base64 content
int numPix = net.numberPixels(); // Read number of pixels
imgWidth = net.imageWidth(); // Read width of image
imgHeight = net.imageHeight(); // Read height of image
size_t requiredBytesMono = 0;
size_t requiredBytesRgb565 = 0;
if (imgWidth > 0 && imgHeight > 0){
requiredBytesMono = (size_t)((imgWidth + 7) / 8) * (size_t)imgHeight;
requiredBytesRgb565 = (size_t)imgWidth * (size_t)imgHeight * 2U;
}
if (requiredBytesMono == 0){
LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: invalid image geometry w=%d h=%d",imgWidth,imgHeight);
return PAGE_UPDATE;
}
const char* b64src = net.pictureBase64(); // Read picture as Base64 content
if (b64src == nullptr){
LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: picture_base64 missing");
return PAGE_UPDATE;
}
size_t b64len = net.pictureBase64Len(); // Calculate length of Base64 content
// Copy Base64 content in PSRAM
char* b64 = (char*) heap_caps_malloc(b64len + 1, MALLOC_CAP_SPIRAM); // Allcate PSRAM for Base64 content
if (!b64) {
@@ -393,32 +497,81 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
memcpy(b64, b64src, b64len + 1); // Copy Base64 content in PSRAM
// Set image buffer in PSRAM
//size_t imgSize = getdisplay().width() * getdisplay().height();
size_t imgSize = numPix; // Calculate image size
size_t imgSize = (numPix > 0) ? (size_t)numPix : requiredBytesMono; // Calculate image size
if (imgSize < requiredBytesMono){
imgSize = requiredBytesMono;
}
#ifdef TFT_DISPLAY
if (imgSize < requiredBytesRgb565){
imgSize = requiredBytesRgb565;
}
#endif
uint8_t* imageData = (uint8_t*) heap_caps_malloc(imgSize, MALLOC_CAP_SPIRAM); // Allocate PSRAM for image
if (!imageData) {
LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: PPSRAM alloc image buffer failed");
LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: PSRAM alloc image buffer failed");
free(b64);
return PAGE_UPDATE;
}
// Decode Base64 content to image
size_t decodedSize = 0;
decoder.decodeBase64(b64, imageData, imgSize, decodedSize);
bool decodeOk = decoder.decodeBase64(b64, b64len, imageData, imgSize, decodedSize);
if (!decodeOk || decodedSize < requiredBytesMono){
int base64Ret = mbedtls_base64_decode(
nullptr,
0,
&decodedSize,
(const unsigned char*)b64,
b64len
);
LOG_DEBUG(GwLog::ERROR,
"Error PageNavigation: decode failed (ok=%d, decoded=%u, required=%u, b64ret=%d)",
decodeOk ? 1 : 0,
(unsigned int)decodedSize,
(unsigned int)requiredBytesMono,
base64Ret
);
free(b64);
free(imageData);
return PAGE_UPDATE;
}
// Copy actual navigation man to ackup map
bool imageIsRgb565 = false;
#ifdef TFT_DISPLAY
imageIsRgb565 = (decodedSize >= requiredBytesRgb565);
#endif
#ifdef TFT_DISPLAY
if (kShowRgb565StripeTestImage) {
createRgb565StripeImage(reinterpret_cast<uint16_t*>(imageData), imgWidth, imgHeight);
decodedSize = requiredBytesRgb565;
imageIsRgb565 = true;
}
#endif
// Copy actual navigation map to backup map
imageBackupWidth = imgWidth;
imageBackupHeight = imgHeight;
imageBackupSize = imgSize;
if (decodedSize > 0) {
memcpy(imageBackupData, imageData, decodedSize);
imageBackupSize = decodedSize;
if (decodedSize > 0 && imageBackupData != nullptr) {
size_t copySize = (decodedSize > imageBackupCapacity) ? imageBackupCapacity : decodedSize;
memcpy(imageBackupData, imageData, copySize);
imageBackupSize = copySize;
}
hasImageBackup = true;
imageBackupIsRgb565 = imageIsRgb565;
hasImageBackup = (imageBackupData != nullptr);
lostCounter = 0;
// Show image (navigation map)
getdisplay().drawBitmap(0, 25, imageData, imgWidth, imgHeight, commonData->fgcolor);
#ifdef TFT_DISPLAY
if (imageIsRgb565) {
drawRgb565Image(0, 25, reinterpret_cast<const uint16_t*>(imageData), imgWidth, imgHeight);
} else {
displayDrawBitmap(0, 25, imageData, imgWidth, imgHeight, commonData->fgcolor);
}
#else
displayDrawBitmap(0, 25, imageData, imgWidth, imgHeight, commonData->fgcolor);
#endif
// Clean PSRAM
free(b64);
@@ -426,12 +579,33 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
}
// If no network connection then use backup navigation map
else{
// NEW: update backoff only if we actually attempted a fetch (not when skipping due to backoff)
if (allowFetch) {
// NEW: exponential backoff: 1s,2s,4s,8s,16s,30s (capped)
if (failCount < 6) failCount++;
uint32_t backoffMs = 1000u << failCount;
if (backoffMs > 30000u) backoffMs = 30000u;
nextAllowedMs = now + backoffMs;
} else {
// NEW: we are currently backing off; do not increase failCount further
// nextAllowedMs stays unchanged
}
// Show backup image (backup navigation map)
if (hasImageBackup) {
getdisplay().drawBitmap(0, 25, imageBackupData, imageBackupWidth, imageBackupHeight, commonData->fgcolor);
#ifdef TFT_DISPLAY
if (imageBackupIsRgb565) {
drawRgb565Image(0, 25, reinterpret_cast<const uint16_t*>(imageBackupData), imageBackupWidth, imageBackupHeight);
} else {
displayDrawBitmap(0, 25, imageBackupData, imageBackupWidth, imageBackupHeight, commonData->fgcolor);
}
#else
displayDrawBitmap(0, 25, imageBackupData, imageBackupWidth, imageBackupHeight, commonData->fgcolor);
#endif
}
// Show info: Connection lost when 5 page refreshes has a connection lost to the map server
// Show connection lost info when 5 page refreshes has a connection lost to the map server
// Short connection losts are uncritical
if(lostCounter >= 5){
getdisplay().setFont(&Ubuntu_Bold12pt8b);
@@ -444,7 +618,6 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
lostCounter++; // Increment lost counter
}
// ############### Draw Values ################
getdisplay().setFont(&Ubuntu_Bold12pt8b);
+1 -1
View File
@@ -274,7 +274,7 @@ public:
// Draw page
//***********************************************************
getdisplay().setPartialWindow(0, 0, width, height); // Set partial update
displaySetPartialWindow(0, 0, width, height); // Set partial update
if (pageMode == VALUE || dataHstryBuf == nullptr) {
// show only data value; ignore other pageMode options if no chart supported boat data history buffer is available
+2 -2
View File
@@ -116,7 +116,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
@@ -194,7 +194,7 @@ public:
// Print text centered on position x, y
int16_t x1, y1; // Return values of getTextBounds
uint16_t w, h; // Return values of getTextBounds
getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
displayGetTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
getdisplay().setCursor(x-w/2, y+h/2);
if(i % 20 == 0){
getdisplay().setFont(&Ubuntu_Bold8pt8b);
+2 -2
View File
@@ -72,7 +72,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
//*******************************************************************************************
@@ -110,7 +110,7 @@ public:
// Print text centered on position x, y
int16_t x1, y1; // Return values of getTextBounds
uint16_t w, h; // Return values of getTextBounds
getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
displayGetTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
getdisplay().setCursor(x-w/2, y+h/2);
if(i % 30 == 0){
getdisplay().setFont(&Ubuntu_Bold8pt8b);
+1 -1
View File
@@ -75,7 +75,7 @@ class PageSixValues : public Page
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
for (int i = 0; i < ( HowManyValues / 2 ); i++){
+5 -5
View File
@@ -73,7 +73,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// current position
getdisplay().setFont(&Ubuntu_Bold8pt8b);
@@ -105,19 +105,19 @@ public:
uint16_t w, h;
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().getTextBounds("N", 0, 150, &x1, &y1, &w, &h);
displayGetTextBounds("N", 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(c.x - w / 2, c.y - r + h + 3);
getdisplay().print("N");
getdisplay().getTextBounds("S", 0, 150, &x1, &y1, &w, &h);
displayGetTextBounds("S", 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(c.x - w / 2, c.y + r - 3);
getdisplay().print("S");
getdisplay().getTextBounds("E", 0, 150, &x1, &y1, &w, &h);
displayGetTextBounds("E", 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(c.x + r - w - 3, c.y + h / 2);
getdisplay().print("E");
getdisplay().getTextBounds("W", 0, 150, &x1, &y1, &w, &h);
displayGetTextBounds("W", 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(c.x - r + 3 , c.y + h / 2);
getdisplay().print("W");
+1 -1
View File
@@ -82,7 +82,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
+497 -310
View File
@@ -15,6 +15,7 @@
#include "images/logo64.xbm"
#include <esp32/clk.h>
#include "qrcode.h"
#include <vector>
#ifdef BOARD_OBP40S3
#include "dirent.h"
@@ -37,9 +38,11 @@ private:
String buzzer_mode;
uint8_t buzzer_power;
String cpuspeed;
String powermode;
String rtc_module;
String gps_module;
String env_module;
String flashLED;
String batt_sensor;
String solar_sensor;
@@ -48,15 +51,445 @@ private:
double homelat;
double homelon;
char mode = 'N'; // (N)ormal, (S)ettings, (D)evice list, (C)ard
char mode = 'N'; // (N)ormal, (S)ettings, (C)onfiguration, (D)evice list, c(A)rd
#ifdef PATCH_N2K
struct device {
uint64_t NAME;
uint8_t id;
char hex_name[17];
uint16_t manuf_code;
const char *model;
};
std::vector<device> devicelist;
#endif
void incMode() {
if (mode == 'N') { // Normal
mode = 'S';
} else if (mode == 'S') { // Settings
mode = 'C';
} else if (mode == 'C') { // Config
mode = 'D';
} else if (mode == 'D') { // Device list
if (use_sdcard) {
mode = 'A'; // SD-Card
} else {
mode = 'N';
}
} else {
mode = 'N';
}
}
void decMode() {
if (mode == 'N') {
if (use_sdcard) {
mode = 'A'; // SD-Card
} else {
mode = 'D'; // Device list
}
} else if (mode == 'S') { // Settings
mode = 'N';
} else if (mode == 'C') { // Config
mode = 'S';
} else if (mode == 'D') { // Device list
mode = 'C';
} else {
mode = 'D';
}
}
void displayModeNormal() {
// Default system page view
uint16_t y0 = 155;
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(8, 48);
getdisplay().print("System Information");
getdisplay().drawXBitmap(320, 25, logo64_bits, logo64_width, logo64_height, commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold8pt8b);
char ssid[13];
snprintf(ssid, 13, "%04X%08X", (uint16_t)(chipid >> 32), (uint32_t)chipid);
displayBarcode(String(ssid), 320, 200, 2);
getdisplay().setCursor(8, 70);
getdisplay().print(String("MCUDEVICE-") + String(ssid));
getdisplay().setCursor(8, 95);
getdisplay().print("Firmware version: ");
getdisplay().setCursor(150, 95);
getdisplay().print(VERSINFO);
getdisplay().setCursor(8, 113);
getdisplay().print("Board version: ");
getdisplay().setCursor(150, 113);
getdisplay().print(BOARDINFO);
getdisplay().print(String(" HW ") + String(PCBINFO));
getdisplay().setCursor(8, 131);
getdisplay().print("Display version: ");
getdisplay().setCursor(150, 131);
getdisplay().print(DISPLAYINFO);
getdisplay().print("; GxEPD2 v");
getdisplay().print(GXEPD2INFO);
getdisplay().setCursor(8, 265);
#ifdef BOARD_OBP60S3
getdisplay().print("Press STBY to enter deep sleep mode");
#endif
#ifdef BOARD_OBP40S3
getdisplay().print("Press wheel to enter deep sleep mode");
#endif
// Flash memory size
uint32_t flash_size = ESP.getFlashChipSize();
getdisplay().setCursor(8, y0);
getdisplay().print("FLASH:");
getdisplay().setCursor(90, y0);
getdisplay().print(String(flash_size / 1024) + String(" kB"));
// PSRAM memory size
uint32_t psram_size = ESP.getPsramSize();
getdisplay().setCursor(8, y0 + 16);
getdisplay().print("PSRAM:");
getdisplay().setCursor(90, y0 + 16);
getdisplay().print(String(psram_size / 1024) + String(" kB"));
// FRAM available / status
getdisplay().setCursor(8, y0 + 32);
getdisplay().print("FRAM:");
getdisplay().setCursor(90, y0 + 32);
getdisplay().print(hasFRAM ? "available" : "not found");
#ifdef BOARD_OBP40S3
// SD-Card
getdisplay().setCursor(8, y0 + 48);
getdisplay().print("SD-Card:");
getdisplay().setCursor(90, y0 + 48);
if (hasSDCard) {
uint64_t cardsize = ((uint64_t) sdcard->csd.capacity) * sdcard->csd.sector_size / (1024 * 1024);
getdisplay().printf("%llu MB", cardsize);
} else {
getdisplay().print("off");
}
#endif
// Uptime
int64_t uptime = esp_timer_get_time() / 1000000;
String uptime_unit;
if (uptime < 120) {
uptime_unit = " seconds";
} else {
if (uptime < 2 * 3600) {
uptime /= 60;
uptime_unit = " minutes";
} else if (uptime < 2 * 3600 * 24) {
uptime /= 3600;
uptime_unit = " hours";
} else {
uptime /= 86400;
uptime_unit = " days";
}
}
getdisplay().setCursor(8, y0 + 80);
getdisplay().print("Uptime:");
getdisplay().setCursor(90, y0 + 80);
getdisplay().print(uptime);
getdisplay().print(uptime_unit);
// CPU speed config / active
getdisplay().setCursor(202, y0);
getdisplay().print("CPU speed:");
getdisplay().setCursor(300, y0);
getdisplay().print(cpuspeed);
getdisplay().print(" / ");
int cpu_freq = esp_clk_cpu_freq() / 1000000;
getdisplay().print(String(cpu_freq));
// total RAM free
int Heap_free = esp_get_free_heap_size();
getdisplay().setCursor(202, y0 + 16);
getdisplay().print("Total free:");
getdisplay().setCursor(300, y0 + 16);
getdisplay().print(String(Heap_free));
// RAM free for task
int RAM_free = uxTaskGetStackHighWaterMark(NULL);
getdisplay().setCursor(202, y0 + 32);
getdisplay().print("Task free:");
getdisplay().setCursor(300, y0 + 32);
getdisplay().print(String(RAM_free));
}
void displayModeConfig() {
// Configuration interface
uint16_t x0 = 16;
uint16_t y0 = 80;
uint16_t dy = 20;
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(8, 48);
getdisplay().print("System configuration");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(x0, y0);
getdisplay().print("CPU speed: 80 | 160 | 240");
getdisplay().setCursor(x0, y0 + 1 * dy);
getdisplay().print("Power mode: Max | 5V | Min");
getdisplay().setCursor(x0, y0 + 2 * dy);
getdisplay().print("Accesspoint: On | Off");
// TODO Change NVRAM-preferences settings here
getdisplay().setCursor(x0, y0 + 4 * dy);
getdisplay().print("Simulation: On | Off");
}
void displayModeSettings() {
// View some of the current settings
const uint16_t x0 = 8;
const uint16_t y0 = 72;
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(x0, 48);
getdisplay().print("System settings");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
// left column
getdisplay().setCursor(x0, y0);
getdisplay().print("Simulation:");
getdisplay().setCursor(120, y0);
getdisplay().print(simulation ? "on" : "off");
getdisplay().setCursor(x0, y0 + 16);
getdisplay().print("Environment:");
getdisplay().setCursor(120, y0 + 16);
getdisplay().print(env_module);
getdisplay().setCursor(x0, y0 + 32);
getdisplay().print("Buzzer:");
getdisplay().setCursor(120, y0 + 32);
getdisplay().print(buzzer_mode);
getdisplay().setCursor(x0, y0 + 64);
getdisplay().print("GPS:");
getdisplay().setCursor(120, y0 + 64);
getdisplay().print(gps_module);
getdisplay().setCursor(x0, y0 + 80);
getdisplay().print("RTC:");
getdisplay().setCursor(120, y0 + 80);
getdisplay().print(rtc_module);
getdisplay().setCursor(x0, y0 + 96);
getdisplay().print("Wifi:");
getdisplay().setCursor(120, y0 + 96);
getdisplay().print(commonData->status.wifiApOn ? "on" : "off");
// Home location
getdisplay().setCursor(x0, y0 + 128);
getdisplay().print("Home Lat.:");
getdisplay().setCursor(120, y0 + 128);
getdisplay().print(formatLatitude(homelat));
getdisplay().setCursor(x0, y0 + 144);
getdisplay().print("Home Lon.:");
getdisplay().setCursor(120, y0 + 144);
getdisplay().print(formatLongitude(homelon));
// Power
getdisplay().setCursor(x0, y0 + 176);
getdisplay().print("Power mode:");
getdisplay().setCursor(120, y0 + 176);
getdisplay().print(powermode);
// right column
getdisplay().setCursor(202, y0);
getdisplay().print("Batt. sensor:");
getdisplay().setCursor(320, y0);
getdisplay().print(batt_sensor);
// Solar sensor
getdisplay().setCursor(202, y0 + 16);
getdisplay().print("Solar sensor:");
getdisplay().setCursor(320, y0 + 16);
getdisplay().print(solar_sensor);
// Generator sensor
getdisplay().setCursor(202, y0 + 32);
getdisplay().print("Gen. sensor:");
getdisplay().setCursor(320, y0 + 32);
getdisplay().print(gen_sensor);
// TODO
// Gyro sensor (rotation)
getdisplay().setCursor(202, y0 + 48);
getdisplay().print("Rot. sensor:");
getdisplay().setCursor(320, y0 + 48);
getdisplay().print(rot_sensor);
// Temp.-sensor
// Power Mode
#ifdef BOARD_OBP60S3
// Backlight infos
getdisplay().setCursor(202, y0 + 64);
getdisplay().print("Backlight:");
getdisplay().setCursor(320, y0 + 64);
getdisplay().printf("%d%%", commonData->backlight.brightness);
// TODO test function with OBP60 device
getdisplay().setCursor(202, y0 + 80);
getdisplay().print("Bl color:");
getdisplay().setCursor(320, y0 + 80);
getdisplay().print(commonData->backlight.color.toName());
getdisplay().setCursor(202, y0 + 96);
getdisplay().print("Bl mode:");
getdisplay().setCursor(320, y0 + 96);
getdisplay().print(commonData->backlight.mode);
// TODO Buzzer mode and power
#endif
}
void displayModeSDCard() {
// SD Card info
uint16_t x0 = 20;
uint16_t y0 = 72;
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(8, 48);
getdisplay().print("SD Card info");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(x0, y0);
#ifdef BOARD_OBP60S3
// This mode should not be callable by devices without card hardware
// In case of accidential reaching this, display a friendly message
getdisplay().print("This mode is not indended to be reached!\n");
getdisplay().print("There's nothing to see here. Move on.");
#endif
#ifdef BOARD_OBP40S3
getdisplay().print("Work in progress...");
/* TODO
this code should go somewhere else. only for testing purposes here
identify card as OBP-Card:
magic.dat
version.dat
readme.txt
IMAGES/
CHARTS/
LOGS/
DATA/
hint: file access with fopen, fgets, fread, fclose
*/
// Simple test for magic file in root
getdisplay().setCursor(x0, y0 + 32);
String file_magic = MOUNT_POINT "/magic.dat";
commonData->logger->logDebug(GwLog::LOG, "Test magicfile: %s", file_magic.c_str());
struct stat st;
if (stat(file_magic.c_str(), &st) == 0) {
getdisplay().printf("File %s exists", file_magic.c_str());
} else {
getdisplay().printf("File %s not found", file_magic.c_str());
}
// Root directory check
DIR* dir = opendir(MOUNT_POINT);
int dy = 0;
if (dir != NULL) {
commonData->logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT);
struct dirent* entry;
while (((entry = readdir(dir)) != NULL) and (dy < 140)) {
getdisplay().setCursor(x0, y0 + 64 + dy);
getdisplay().print(entry->d_name);
// type 1 is file, type 2 is dir
if (entry->d_type == 2) {
getdisplay().print("/");
}
dy += 20;
commonData->logger->logDebug(GwLog::DEBUG, " %s type %d", entry->d_name, entry->d_type);
}
closedir(dir);
} else {
commonData->logger->logDebug(GwLog::LOG, "Failed to open root directory");
}
#endif
}
void displayModeDevicelist() {
// NMEA2000 device list
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(8, 48);
getdisplay().print("NMEA2000 device list");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(20, 70);
getdisplay().print("RxD: ");
getdisplay().print(String(commonData->status.n2kRx));
getdisplay().setCursor(120, 70);
getdisplay().print("TxD: ");
getdisplay().print(String(commonData->status.n2kTx));
#ifdef PATCH_N2K
uint16_t x0 = 20;
uint16_t y0 = 100;
getdisplay().setFont(&Ubuntu_Bold10pt8b);
getdisplay().setCursor(x0, y0);
getdisplay().print("ID");
getdisplay().setCursor(x0 + 50, y0);
getdisplay().print("Model");
getdisplay().setCursor(x0 + 250, y0);
getdisplay().print("Manuf.");
getdisplay().drawLine(18, y0 + 4, 360 , y0 + 4 , commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold8pt8b);
y0 = 120;
uint8_t n_dev = 0;
for (const device& item : devicelist) {
if (n_dev > 8) {
break;
}
getdisplay().setCursor(x0, y0 + n_dev * 20);
getdisplay().print(item.id);
getdisplay().setCursor(x0 + 50, y0 + n_dev * 20);
getdisplay().print(item.model);
getdisplay().setCursor(x0 + 250, y0 + n_dev * 20);
getdisplay().print(item.manuf_code);
n_dev++;
}
getdisplay().setCursor(x0, y0 + (n_dev + 1) * 20);
if (n_dev == 0) {
getdisplay().printf("no devices found on bus");
} else {
getdisplay().drawLine(18, y0 + n_dev * 20, 360 , y0 + n_dev * 20, commonData->fgcolor);
getdisplay().printf("%d devices of %d in total", n_dev, devicelist.size());
}
#else
getdisplay().setCursor(20, 100);
getdisplay().print("NMEA2000 not exposed to obp60 task");
#endif
}
public:
PageSystem(CommonData &common){
commonData = &common;
common.logger->logDebug(GwLog::LOG,"Instantiate PageSystem");
commonData->logger->logDebug(GwLog::LOG,"Instantiate PageSystem");
if (hasFRAM) {
mode = fram.read(FRAM_SYSTEM_MODE);
common.logger->logDebug(GwLog::DEBUG, "Loaded mode '%c' from FRAM", mode);
commonData->logger->logDebug(GwLog::DEBUG, "Loaded mode '%c' from FRAM", mode);
}
chipid = ESP.getEfuseMac();
simulation = common.config->getBool(common.config->useSimuData);
@@ -67,6 +500,7 @@ public:
buzzer_mode.toLowerCase();
buzzer_power = common.config->getInt(common.config->buzzerPower);
cpuspeed = common.config->getString(common.config->cpuSpeed);
powermode = common.config->getString(common.config->powerMode);
env_module = common.config->getString(common.config->useEnvSensor);
rtc_module = common.config->getString(common.config->useRTC);
gps_module = common.config->getString(common.config->useGPS);
@@ -76,6 +510,7 @@ public:
rot_sensor = common.config->getString(common.config->useRotSensor);
homelat = common.config->getString(common.config->homeLAT).toDouble();
homelon = common.config->getString(common.config->homeLON).toDouble();
flashLED = common.config->getString(common.config->flashLED);
}
void setupKeys() {
@@ -92,19 +527,7 @@ public:
// Switch display mode
commonData->logger->logDebug(GwLog::LOG, "System keyboard handler");
if (key == 2) {
if (mode == 'N') {
mode = 'S';
} else if (mode == 'S') {
mode = 'D';
} else if (mode == 'D') {
if (hasSDCard) {
mode = 'C';
} else {
mode = 'N';
}
} else {
mode = 'N';
}
incMode();
if (hasFRAM) fram.write(FRAM_SYSTEM_MODE, mode);
return 0;
}
@@ -129,8 +552,13 @@ public:
}
#endif
#ifdef BOARD_OBP40S3
// grab cursor keys to disable page navigation
if (key == 9 or key == 10) {
// use cursor keys for local mode navigation
if (key == 9) {
incMode();
return 0;
}
if (key == 10) {
decMode();
return 0;
}
// standby / deep sleep
@@ -168,309 +596,68 @@ public:
}
}
int displayPage(PageData &pageData){
GwConfigHandler *config = commonData->config;
GwLog *logger = commonData->logger;
// Get config data
String flashLED = config->getString(config->flashLED);
// Optical warning by limit violation (unused)
if(String(flashLED) == "Limit Violation"){
void displayNew(PageData &pageData) {
#ifdef BOARD_OBP60S3
// Clear optical warning
if (flashLED == "Limit Violation") {
setBlinkingLED(false);
setFlashLED(false);
setFlashLED(false);
}
#endif
// Logging boat values
logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
#ifdef PATCH_N2K
// load current device list
tN2kDeviceList *pDevList = pageData.api->getN2kDeviceList();
// TODO check if changed
if (pDevList->ReadResetIsListUpdated()) {
// only reload if changed
devicelist.clear();
for (uint8_t i = 0; i <= 252; i++) {
const tNMEA2000::tDevice *d = pDevList->FindDeviceBySource(i);
if (d == nullptr) {
continue;
}
device dev;
dev.id = i;
dev.NAME = d->GetName();
snprintf(dev.hex_name, sizeof(dev.hex_name), "%08X%08X", (uint32_t)(dev.NAME >> 32), (uint32_t)(dev.NAME & 0xFFFFFFFF));
dev.manuf_code = d->GetManufacturerCode();
dev.model = d->GetModelID();
devicelist.push_back(dev);
}
}
#endif
};
// Draw page
//***********************************************************
int displayPage(PageData &pageData){
uint16_t x0 = 8; // left column
uint16_t y0 = 48; // data table starts here
// Logging page information
commonData->logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
if (mode == 'N') {
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(8, 48);
getdisplay().print("System Information");
getdisplay().drawXBitmap(320, 25, logo64_bits, logo64_width, logo64_height, commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold8pt8b);
y0 = 155;
char ssid[13];
snprintf(ssid, 13, "%04X%08X", (uint16_t)(chipid >> 32), (uint32_t)chipid);
displayBarcode(String(ssid), 320, 200, 2);
getdisplay().setCursor(8, 70);
getdisplay().print(String("MCUDEVICE-") + String(ssid));
getdisplay().setCursor(8, 95);
getdisplay().print("Firmware version: ");
getdisplay().setCursor(150, 95);
getdisplay().print(VERSINFO);
getdisplay().setCursor(8, 113);
getdisplay().print("Board version: ");
getdisplay().setCursor(150, 113);
getdisplay().print(BOARDINFO);
getdisplay().print(String(" HW ") + String(PCBINFO));
getdisplay().setCursor(8, 131);
getdisplay().print("Display version: ");
getdisplay().setCursor(150, 131);
getdisplay().print(DISPLAYINFO);
getdisplay().print("; GxEPD2 v");
getdisplay().print(GXEPD2INFO);
getdisplay().setCursor(8, 265);
#ifdef BOARD_OBP60S3
getdisplay().print("Press STBY to enter deep sleep mode");
#endif
#ifdef BOARD_OBP40S3
getdisplay().print("Press wheel to enter deep sleep mode");
#endif
// Flash memory size
uint32_t flash_size = ESP.getFlashChipSize();
getdisplay().setCursor(8, y0);
getdisplay().print("FLASH:");
getdisplay().setCursor(90, y0);
getdisplay().print(String(flash_size / 1024) + String(" kB"));
// PSRAM memory size
uint32_t psram_size = ESP.getPsramSize();
getdisplay().setCursor(8, y0 + 16);
getdisplay().print("PSRAM:");
getdisplay().setCursor(90, y0 + 16);
getdisplay().print(String(psram_size / 1024) + String(" kB"));
// FRAM available / status
getdisplay().setCursor(8, y0 + 32);
getdisplay().print("FRAM:");
getdisplay().setCursor(90, y0 + 32);
getdisplay().print(hasFRAM ? "available" : "not found");
#ifdef BOARD_OBP40S3
// SD-Card
getdisplay().setCursor(8, y0 + 48);
getdisplay().print("SD-Card:");
getdisplay().setCursor(90, y0 + 48);
if (hasSDCard) {
uint64_t cardsize = ((uint64_t) sdcard->csd.capacity) * sdcard->csd.sector_size / (1024 * 1024);
getdisplay().printf("%llu MB", cardsize);
} else {
getdisplay().print("off");
}
#endif
// Uptime
int64_t uptime = esp_timer_get_time() / 1000000;
String uptime_unit;
if (uptime < 120) {
uptime_unit = " seconds";
} else {
if (uptime < 2 * 3600) {
uptime /= 60;
uptime_unit = " minutes";
} else if (uptime < 2 * 3600 * 24) {
uptime /= 3600;
uptime_unit = " hours";
} else {
uptime /= 86400;
uptime_unit = " days";
}
}
getdisplay().setCursor(8, y0 + 80);
getdisplay().print("Uptime:");
getdisplay().setCursor(90, y0 + 80);
getdisplay().print(uptime);
getdisplay().print(uptime_unit);
// CPU speed config / active
getdisplay().setCursor(202, y0);
getdisplay().print("CPU speed:");
getdisplay().setCursor(300, y0);
getdisplay().print(cpuspeed);
getdisplay().print(" / ");
int cpu_freq = esp_clk_cpu_freq() / 1000000;
getdisplay().print(String(cpu_freq));
// total RAM free
int Heap_free = esp_get_free_heap_size();
getdisplay().setCursor(202, y0 + 16);
getdisplay().print("Total free:");
getdisplay().setCursor(300, y0 + 16);
getdisplay().print(String(Heap_free));
// RAM free for task
int RAM_free = uxTaskGetStackHighWaterMark(NULL);
getdisplay().setCursor(202, y0 + 32);
getdisplay().print("Task free:");
getdisplay().setCursor(300, y0 + 32);
getdisplay().print(String(RAM_free));
} else if (mode == 'S') {
// Settings
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(x0, 48);
getdisplay().print("System settings");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
x0 = 8;
y0 = 72;
// left column
getdisplay().setCursor(x0, y0);
getdisplay().print("Simulation:");
getdisplay().setCursor(120, y0);
getdisplay().print(simulation ? "on" : "off");
getdisplay().setCursor(x0, y0 + 16);
getdisplay().print("Environment:");
getdisplay().setCursor(120, y0 + 16);
getdisplay().print(env_module);
getdisplay().setCursor(x0, y0 + 32);
getdisplay().print("Buzzer:");
getdisplay().setCursor(120, y0 + 32);
getdisplay().print(buzzer_mode);
getdisplay().setCursor(x0, y0 + 64);
getdisplay().print("GPS:");
getdisplay().setCursor(120, y0 + 64);
getdisplay().print(gps_module);
getdisplay().setCursor(x0, y0 + 80);
getdisplay().print("RTC:");
getdisplay().setCursor(120, y0 + 80);
getdisplay().print(rtc_module);
getdisplay().setCursor(x0, y0 + 96);
getdisplay().print("Wifi:");
getdisplay().setCursor(120, y0 + 96);
getdisplay().print(commonData->status.wifiApOn ? "on" : "off");
// Home location
getdisplay().setCursor(x0, y0 + 128);
getdisplay().print("Home Lat.:");
getdisplay().setCursor(120, y0 + 128);
getdisplay().print(formatLatitude(homelat));
getdisplay().setCursor(x0, y0 + 144);
getdisplay().print("Home Lon.:");
getdisplay().setCursor(120, y0 + 144);
getdisplay().print(formatLongitude(homelon));
// right column
getdisplay().setCursor(202, y0);
getdisplay().print("Batt. sensor:");
getdisplay().setCursor(320, y0);
getdisplay().print(batt_sensor);
// Solar sensor
getdisplay().setCursor(202, y0 + 16);
getdisplay().print("Solar sensor:");
getdisplay().setCursor(320, y0 + 16);
getdisplay().print(solar_sensor);
// Generator sensor
getdisplay().setCursor(202, y0 + 32);
getdisplay().print("Gen. sensor:");
getdisplay().setCursor(320, y0 + 32);
getdisplay().print(gen_sensor);
// Gyro sensor
} else if (mode == 'C') {
// Card info
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(8, 48);
getdisplay().print("SD Card info");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
x0 = 20;
y0 = 72;
getdisplay().setCursor(x0, y0);
#ifdef BOARD_OBP60S3
// This mode should not be callable by devices without card hardware
// In case of accidential reaching this, display a friendly message
getdisplay().print("This mode is not indended to be reached!\n");
getdisplay().print("There's nothing to see here. Move on.");
#endif
#ifdef BOARD_OBP40S3
getdisplay().print("Work in progress...");
/* TODO
this code should go somewhere else. only for testing purposes here
identify card as OBP-Card:
magic.dat
version.dat
readme.txt
IMAGES/
CHARTS/
LOGS/
DATA/
hint: file access with fopen, fgets, fread, fclose
*/
// Simple test for magic file in root
getdisplay().setCursor(x0, y0 + 32);
String file_magic = MOUNT_POINT "/magic.dat";
logger->logDebug(GwLog::LOG, "Test magicfile: %s", file_magic.c_str());
struct stat st;
if (stat(file_magic.c_str(), &st) == 0) {
getdisplay().printf("File %s exists", file_magic.c_str());
} else {
getdisplay().printf("File %s not found", file_magic.c_str());
}
// Root directory check
DIR* dir = opendir(MOUNT_POINT);
int dy = 0;
if (dir != NULL) {
logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT);
struct dirent* entry;
while (((entry = readdir(dir)) != NULL) and (dy < 140)) {
getdisplay().setCursor(x0, y0 + 64 + dy);
getdisplay().print(entry->d_name);
// type 1 is file, type 2 is dir
if (entry->d_type == 2) {
getdisplay().print("/");
}
dy += 20;
logger->logDebug(GwLog::DEBUG, " %s type %d", entry->d_name, entry->d_type);
}
closedir(dir);
} else {
logger->logDebug(GwLog::LOG, "Failed to open root directory");
}
#endif
} else {
// NMEA2000 device list
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(8, 48);
getdisplay().print("NMEA2000 device list");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(20, 80);
getdisplay().print("RxD: ");
getdisplay().print(String(commonData->status.n2kRx));
getdisplay().setCursor(20, 100);
getdisplay().print("TxD: ");
getdisplay().print(String(commonData->status.n2kTx));
// call current system page
switch (mode) {
case 'N':
displayModeNormal();
break;
case 'S':
displayModeSettings();
break;
case 'C':
displayModeConfig();
break;
case 'A':
displayModeSDCard();
break;
case 'D':
displayModeDevicelist();
break;
}
// Update display
getdisplay().nextPage(); // Partial update (fast)
displayNextPage(); // Partial update (fast)
return PAGE_OK;
};
};
+1 -1
View File
@@ -80,7 +80,7 @@ class PageThreeValues : public Page
//***********************************************************
/// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// ############### Value 1 ################
+1 -1
View File
@@ -281,7 +281,7 @@ public:
// Draw page
//***********************************************************
getdisplay().setPartialWindow(0, 0, width, height); // Set partial update
displaySetPartialWindow(0, 0, width, height); // Set partial update
if (pageMode == VALUES || (dataHstryBuf[0] == nullptr && dataHstryBuf[1] == nullptr)) {
// show only data value; ignore other pageMode options if no chart supported boat data history buffer is available
+1 -1
View File
@@ -193,7 +193,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
if (mode == 'D') {
// Display mode digital
+1 -1
View File
@@ -63,7 +63,7 @@ public:
if (mode == 'W') {
getdisplay().setFullWindow();
} else {
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
}
if (mode == 'L') {
+1 -1
View File
@@ -358,7 +358,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
+1 -1
View File
@@ -221,7 +221,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, width, height); // Set partial update
displaySetPartialWindow(0, 0, width, height); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
if (chrtMode == DIRECTION) {
+2 -2
View File
@@ -140,7 +140,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
@@ -260,7 +260,7 @@ public:
// Print text centered on position x, y
int16_t x1, y1; // Return values of getTextBounds
uint16_t w, h; // Return values of getTextBounds
getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
displayGetTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
getdisplay().setCursor(x-w/2, y+h/2);
if(i % 30 == 0){
getdisplay().setFont(&Ubuntu_Bold8pt8b);
+2 -2
View File
@@ -200,7 +200,7 @@ public:
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
@@ -314,7 +314,7 @@ public:
// Print text centered on position x, y
int16_t x1, y1; // Return values of getTextBounds
uint16_t w, h; // Return values of getTextBounds
getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
displayGetTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
getdisplay().setCursor(x-w/2, y+h/2);
if(i % 30 == 0){
getdisplay().setFont(&Ubuntu_Bold8pt8b);
+6 -6
View File
@@ -89,7 +89,7 @@ class PageXTETrack : public Page
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
@@ -112,25 +112,25 @@ class PageXTETrack : public Page
GwApi::BoatValue *bv_xte = pageData.values[0]; // XTE
String sval_xte = formatValue(bv_xte, *commonData).svalue;
getdisplay().getTextBounds(sval_xte, 0, 0, &x, &y, &w, &h);
displayGetTextBounds(sval_xte, 0, 0, &x, &y, &w, &h);
getdisplay().setCursor(160-w, 170);
getdisplay().print(sval_xte);
GwApi::BoatValue *bv_cog = pageData.values[1]; // COG
String sval_cog = formatValue(bv_cog, *commonData).svalue;
getdisplay().getTextBounds(sval_cog, 0, 0, &x, &y, &w, &h);
displayGetTextBounds(sval_cog, 0, 0, &x, &y, &w, &h);
getdisplay().setCursor(360-w, 170);
getdisplay().print(sval_cog);
GwApi::BoatValue *bv_dtw = pageData.values[2]; // DTW
String sval_dtw = formatValue(bv_dtw, *commonData).svalue;
getdisplay().getTextBounds(sval_dtw, 0, 0, &x, &y, &w, &h);
displayGetTextBounds(sval_dtw, 0, 0, &x, &y, &w, &h);
getdisplay().setCursor(160-w, 257);
getdisplay().print(sval_dtw);
GwApi::BoatValue *bv_btw = pageData.values[3]; // BTW
String sval_btw = formatValue(bv_btw, *commonData).svalue;
getdisplay().getTextBounds(sval_btw, 0, 0, &x, &y, &w, &h);
displayGetTextBounds(sval_btw, 0, 0, &x, &y, &w, &h);
getdisplay().setCursor(360-w, 257);
getdisplay().print(sval_btw);
@@ -149,7 +149,7 @@ class PageXTETrack : public Page
}
getdisplay().setFont(&Ubuntu_Bold10pt8b);
getdisplay().getTextBounds(sval_wpname, 0, 150, &x, &y, &w, &h);
displayGetTextBounds(sval_wpname, 0, 150, &x, &y, &w, &h);
// TODO if text don't fix use smaller font size.
// if smallest size does not fit use 2 lines
// last resort: clip with ellipsis
+1 -1
View File
@@ -126,7 +126,7 @@ class Page{
virtual void displayNew(PageData &pageData){}
virtual void leavePage(PageData &pageData){}
virtual void setupKeys() {
#ifdef HARDWARE_V21
#ifdef BOARD_OBP60S3
commonData->keydata[0].label = "";
commonData->keydata[1].label = "";
commonData->keydata[2].label = "#LEFT";
+16
View File
@@ -0,0 +1,16 @@
param(
[string]$dir = "."
)
$total = 0
Get-ChildItem -Path $dir -Recurse -File | Where-Object {
$_.Extension -in ".c", ".cpp", ".h"
} | ForEach-Object {
$lines = [System.Linq.Enumerable]::Count([System.IO.File]::ReadLines($_.FullName))
Write-Output "$($_.FullName) : $lines"
$total += $lines
}
Write-Output "-----------------------------"
Write-Output "Over all files: $total"
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -3,4 +3,7 @@ Debugging tool
log.txt = text file with error messages from terminal console
tools/decoder.py -p ESP32S3 -t ~/.platformio/packages/toolchain-xtensa-esp32s3/ -e .pio/build/obp60_s3/firmware.elf log.txt
cd /home/norbert/esp32-nmea2000-obp60
tools/decoder.py -p ESP32S3 -t ~/.platformio/packages/toolchain-xtensa-esp32s3/ -e .pio/build/obp60_s3/firmware.elf log.txt
tools/decoder.py -p ESP32S3 -t ~/.platformio/packages/toolchain-xtensa-esp32s3/ -e .pio/build/obp70_s3/firmware.elf /home/norbert/Dokumente/Multifunktionsdisplay_OBP60/Crashes/20260313/log.txt
+16 -6
View File
@@ -2,6 +2,14 @@
import subprocess
def cleanup_patches(source, target, env):
for p in patchfiles:
patch = os.path.join(patchdir, p)
print(f"removing {patch}")
res = subprocess.run(["git", "apply", "-R", patch], capture_output=True, text=True)
if res.returncode != 0:
print(res.stderr)
patching = False
epdtype = "unknown"
@@ -46,11 +54,13 @@ if patching:
print("patchdir not found, no patches applied")
else:
patchfiles = [f for f in os.listdir(patchdir)]
for p in patchfiles:
patch = os.path.join(patchdir, p)
print(f"applying {patch}")
res = subprocess.run(["git", "apply", patch], capture_output=True, text=True)
if res.returncode != 0:
print(res.stderr)
if len(patchfiles) > 0:
for p in patchfiles:
patch = os.path.join(patchdir, p)
print(f"applying {patch}")
res = subprocess.run(["git", "apply", patch], capture_output=True, text=True)
if res.returncode != 0:
print(res.stderr)
env.AddPostAction("$PROGPATH", cleanup_patches)
else:
print("no patches found")
+70 -36
View File
@@ -9,7 +9,6 @@
#include <NMEA0183.h> // NMEA0183
#include <NMEA0183Msg.h>
#include <NMEA0183Messages.h>
#include <GxEPD2_BW.h> // GxEPD2 lib for b/w E-Ink displays
#include "OBP60Extensions.h" // Functions lib for extension board
#include "OBP60Keypad.h" // Functions for keypad
#include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation
@@ -284,8 +283,12 @@ void underVoltageError(CommonData &common) {
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(65, 175);
getdisplay().print("Charge battery and restart system");
getdisplay().nextPage(); // Partial update
displayNextPage(); // Partial update
#ifdef TFT_DISPLAY
getpaneldisplay().powerSave(true); // Display power save
#else
getdisplay().powerOff(); // Display power off
#endif
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
setPortPin(OBP_POWER_SD, false); // Power off SD card
#else
@@ -295,7 +298,7 @@ void underVoltageError(CommonData &common) {
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off
// Shutdown EInk display
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().fillScreen(common.bgcolor);// Clear screen
getdisplay().setTextColor(common.fgcolor);
getdisplay().setFont(&Ubuntu_Bold20pt8b);
@@ -304,8 +307,12 @@ void underVoltageError(CommonData &common) {
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(65, 175);
getdisplay().print("To wake up repower system");
getdisplay().nextPage(); // Partial update
displayNextPage(); // Partial update
#ifdef TFT_DISPLAY
getpaneldisplay().powerSave(true); // Display power save
#else
getdisplay().powerOff(); // Display power off
#endif
#endif
while (true) {
esp_deep_sleep_start(); // Deep Sleep without wakeup. Wakeup only after power cycle (restart).
@@ -316,7 +323,7 @@ inline bool underVoltageDetection(float voffset, float vslope) {
// Read supply voltage
#if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40
float minVoltage = 3.65; // Absolut minimum volatge for 3,7V LiPo accu
float minVoltage = 3.65; // Absolut minimum voltage for 3,7V LiPo accu
#else
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60
float minVoltage = MIN_VOLTAGE;
@@ -325,6 +332,7 @@ inline bool underVoltageDetection(float voffset, float vslope) {
return (calVoltage < minVoltage);
}
// OBP60 Task
//####################################################################################
void OBP60Task(GwApi *api){
@@ -332,7 +340,7 @@ void OBP60Task(GwApi *api){
// return;
GwLog *logger=api->getLogger();
GwConfigHandler *config=api->getConfig();
#if defined HARDWARE_V20 || HARDWARE_V21
#ifdef BOARD_OBP60S3
startLedTask(api);
#endif
PageList allPages;
@@ -341,7 +349,7 @@ void OBP60Task(GwApi *api){
commonData.logger=logger;
commonData.config=config;
#if defined HARDWARE_V20 || HARDWARE_V21
#ifdef BOARD_OBP60S3
// Keyboard coordinates for page footer
initKeys(commonData);
#endif
@@ -375,36 +383,47 @@ void OBP60Task(GwApi *api){
#ifdef DISPLAY_GDEY042T81
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
#elif defined(TFT_DISPLAY)
getpaneldisplay().init(); // Init for TFT LCD panel
#else
getdisplay().init(115200); // Init for normal displays
#endif
#ifdef TFT_DISPLAY
getpaneldisplay().setRotation(0); // Set display orientation (horizontal)
getpaneldisplay().setPanelOffset(0, 0); // Use full native framebuffer coordinates
getpaneldisplay().fillScreen(0x0000); // Initialize full TFT screen to black (native RGB565)
getpaneldisplay().setPanelOffset(OBP_TFT_OFFSET_X, OBP_TFT_OFFSET_Y); // Restore configured operating panel offset
#else
getdisplay().setRotation(0); // Set display orientation (horizontal)
getdisplay().setFullWindow(); // Set full Refresh
getdisplay().firstPage(); // set first page
#endif
displaySetFullWindow(); // Set full Refresh (E-Ink only)
displayFirstPage(); // set first page
getdisplay().fillScreen(commonData.bgcolor);
getdisplay().setTextColor(commonData.fgcolor);
getdisplay().nextPage(); // Full Refresh
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displayNextPage(); // Full Refresh
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update (E-Ink only)
getdisplay().fillScreen(commonData.bgcolor);
getdisplay().nextPage(); // Fast Refresh
getdisplay().nextPage(); // Fast Refresh
displayNextPage(); // Fast Refresh
displayNextPage(); // Fast Refresh
if(String(displaymode) == "Logo + QR Code" || String(displaymode) == "Logo"){
getdisplay().fillScreen(commonData.bgcolor);
getdisplay().drawBitmap(0, 0, gImage_Logo_OBP_400x300_sw, getdisplay().width(), getdisplay().height(), commonData.fgcolor); // Draw start logo
getdisplay().nextPage(); // Fast Refresh
getdisplay().nextPage(); // Fast Refresh
// draw the fixed-size logo via generic display wrapper
displayDrawBitmap(0, 0, gImage_Logo_OBP_400x300_sw,
400, 300, commonData.fgcolor);
displayNextPage(); // Fast Refresh
displayNextPage(); // Fast Refresh
delay(SHOW_TIME); // Logo show time
if(String(displaymode) == "Logo + QR Code"){
getdisplay().fillScreen(commonData.bgcolor);
qrWiFi(systemname, wifipass, commonData.fgcolor, commonData.bgcolor); // Show QR code for WiFi connection
getdisplay().nextPage(); // Fast Refresh
getdisplay().nextPage(); // Fast Refresh
displayNextPage(); // Fast Refresh
displayNextPage(); // Fast Refresh
delay(SHOW_TIME); // QR code show time
}
getdisplay().fillScreen(commonData.bgcolor);
getdisplay().nextPage(); // Fast Refresh
getdisplay().nextPage(); // Fast Refresh
displayNextPage(); // Fast Refresh
displayNextPage(); // Fast Refresh
}
// Init pages
@@ -432,7 +451,7 @@ void OBP60Task(GwApi *api){
#endif
LOG_DEBUG(GwLog::LOG,"...done");
int lastPage=-1; // initialize with an impiossible value, so we can detect wether we are during startup and no page has been displayed yet
int lastPage=-1; // initialize with an impossible value, so we can detect wether we are during startup and no page has been displayed yet
BoatValueList boatValues; //all the boat values for the api query
HstryBuffers hstryBufferList(1920, &boatValues, logger); // Create empty list of boat data history buffers (1.920 values = seconds = 32 min.)
@@ -723,25 +742,29 @@ void OBP60Task(GwApi *api){
if(millis() > starttime4 + 8000 && delayedDisplayUpdate == true){
starttime1 = millis();
starttime2 = millis();
getdisplay().setFullWindow(); // Set full update
displaySetFullWindow(); // Set full update
#ifdef TFT_DISPLAY
// TFT LCD doesn't need refresh operations
#else
if(fastrefresh == "true"){
getdisplay().nextPage(); // Full update
displayNextPage(); // Full update
}
else{
getdisplay().fillScreen(commonData.fgcolor); // Clear display
#ifdef DISPLAY_GDEY042T81
getdisplay().hibernate(); // Set display in hybenate mode
getdisplay().hibernate(); // Set display in hibenate mode
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
#else
getdisplay().init(115200); // Init for normal displays
#endif
getdisplay().firstPage(); // Full update
getdisplay().nextPage(); // Full update
displayFirstPage(); // Full update
displayNextPage(); // Full update
// getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// getdisplay().fillScreen(commonData.bgcolor); // Clear display
// getdisplay().nextPage(); // Partial update
// getdisplay().nextPage(); // Partial update
}
#endif
delayedDisplayUpdate = false;
}
@@ -751,31 +774,38 @@ void OBP60Task(GwApi *api){
starttime1 = millis();
starttime2 = millis();
LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh first 5 min");
getdisplay().setFullWindow(); // Set full update
displaySetFullWindow(); // Set full update
#ifdef TFT_DISPLAY
// TFT LCD doesn't need refresh operations
#else
if(fastrefresh == "true"){
getdisplay().nextPage(); // Full update
displayNextPage(); // Full update
}
else{
getdisplay().fillScreen(commonData.fgcolor); // Clear display
#ifdef DISPLAY_GDEY042T81
getdisplay().hibernate(); // Set display in hybenate mode
getdisplay().hibernate(); // Set display in hibernate mode
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
#else
getdisplay().init(115200); // Init for normal displays
#endif
getdisplay().firstPage(); // Full update
getdisplay().nextPage(); // Full update
displayFirstPage(); // Full update
displayNextPage(); // Full update
// getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// getdisplay().fillScreen(commonData.bgcolor); // Clear display
// getdisplay().nextPage(); // Partial update
// getdisplay().nextPage(); // Partial update
}
#endif
}
// Subtask E-Ink full refresh
if(millis() > starttime2 + fullrefreshtime * 60 * 1000){
starttime2 = millis();
LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh");
#ifdef TFT_DISPLAY
// TFT LCD: no special refresh
#else
getdisplay().setFullWindow(); // Set full update
if(fastrefresh == "true"){
getdisplay().nextPage(); // Full update
@@ -783,7 +813,7 @@ void OBP60Task(GwApi *api){
else{
getdisplay().fillScreen(commonData.fgcolor); // Clear display
#ifdef DISPLAY_GDEY042T81
getdisplay().hibernate(); // Set display in hybenate mode
getdisplay().hibernate(); // Set display in hibernate mode
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
#else
getdisplay().init(115200); // Init for normal displays
@@ -795,6 +825,7 @@ void OBP60Task(GwApi *api){
// getdisplay().nextPage(); // Partial update
// getdisplay().nextPage(); // Partial update
}
#endif
}
// Refresh display data, default all 1s
@@ -807,6 +838,7 @@ void OBP60Task(GwApi *api){
if(millis() > starttime3 + pagetime){
LOG_DEBUG(GwLog::DEBUG,"Page with refreshtime=%d", pagetime);
starttime3 = millis();
bool pageChanged = (lastPage != pageNumber);
//refresh data from api
api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues);
@@ -841,16 +873,16 @@ void OBP60Task(GwApi *api){
if (currentPage == NULL){
LOG_DEBUG(GwLog::ERROR,"page number %d not found", pageNumber);
// Error handling for missing page
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().fillScreen(commonData.bgcolor); // Clear display
getdisplay().drawXBitmap(200 - unknown_width / 2, 150 - unknown_height / 2, unknown_bits, unknown_width, unknown_height, commonData.fgcolor);
getdisplay().setCursor(140, 250);
getdisplay().setFont(&Atari16px);
getdisplay().print("Here be dragons!");
getdisplay().nextPage(); // Partial update (fast)
displayNextPage(); // Partial update (fast)
}
else{
if (lastPage != pageNumber){
if (pageChanged){
if (lastPage != -1){ // skip cleanup if we are during startup, and no page has been displayed yet.
pages[lastPage].page->leavePage(pages[lastPage].parameters); // call page cleanup code
if (hasFRAM) fram.write(FRAM_PAGE_NO, pageNumber); // remember new page for device restart
@@ -870,10 +902,12 @@ void OBP60Task(GwApi *api){
displayAlarm(commonData);
}
if (ret & PAGE_UPDATE) {
getdisplay().nextPage(); // Partial update (fast)
displayNextPage(); // Partial update (fast)
}
if (ret & PAGE_HIBERNATE) {
#ifndef TFT_DISPLAY
getdisplay().hibernate();
#endif
}
}
+9 -3
View File
@@ -3,7 +3,7 @@
//we only compile for some boards
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#define USBSerial Serial
#ifdef HARDWARE_V21
#ifdef BOARD_OBP60S3
// CAN NMEA2000
#define ESP32_CAN_TX_PIN 46
#define ESP32_CAN_RX_PIN 3
@@ -35,13 +35,19 @@
// OBP60 Task
void OBP60Task(GwApi *param);
DECLARE_USERTASK_PARAM(OBP60Task, 35000); // Need 35k RAM as stack size
#ifdef HARDWARE_V21
#if defined(BOARD_OBP60S3) && defined(TFT_DISPLAY)
DECLARE_CAPABILITY(obp70,true);
#endif
#if defined(BOARD_OBP60S3) && !defined(TFT_DISPLAY)
DECLARE_CAPABILITY(obp60,true);
#endif
#ifdef BOARD_OBP40S3
DECLARE_CAPABILITY(obp40,true)
#endif
#ifdef BOARD_OBP60S3
#if defined(BOARD_OBP60S3) && defined(TFT_DISPLAY)
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp60-v2-docu.readthedocs.io/en/latest/"); // Link to help pages
#endif
#if defined(BOARD_OBP60S3) && !defined(TFT_DISPLAY)
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp60-v2-docu.readthedocs.io/en/latest/"); // Link to help pages
#endif
#ifdef BOARD_OBP40S3
+103
View File
@@ -0,0 +1,103 @@
diff --git a/lib/api/GwApi.h b/lib/api/GwApi.h
index 88f9690..9663a65 100644
--- a/lib/api/GwApi.h
+++ b/lib/api/GwApi.h
@@ -2,6 +2,8 @@
#define _GWAPI_H
#include "GwMessage.h"
#include "N2kMsg.h"
+#include "Nmea2kTwai.h"
+#include "N2kDeviceList.h"
#include "NMEA0183Msg.h"
#include "GWConfig.h"
#include "GwBoatData.h"
@@ -222,6 +224,8 @@ class GwApi{
* accessing boat data must only be executed from within the main thread
* you need to use the request pattern as shown in GwExampleTask.cpp
*/
+ virtual Nmea2kTwai *getNMEA2000()=0;
+ virtual tN2kDeviceList *getN2kDeviceList()=0;
virtual GwBoatData *getBoatData()=0;
virtual ~GwApi(){}
};
diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h
index 604c356..2fe4496 100644
--- a/lib/obp60task/OBP60Extensions.h
+++ b/lib/obp60task/OBP60Extensions.h
@@ -15,6 +15,9 @@
#define MOUNT_POINT "/sdcard"
#endif
+// Patches to apply to gateway code
+#define PATCH_N2K
+
// FRAM address reservations 32kB: 0x0000 - 0x7FFF
// 0x0000 - 0x03ff: single variables
#define FRAM_PAGE_NO 0x0002
diff --git a/lib/usercode/GwUserCode.cpp b/lib/usercode/GwUserCode.cpp
index 1b007f8..90087d4 100644
--- a/lib/usercode/GwUserCode.cpp
+++ b/lib/usercode/GwUserCode.cpp
@@ -216,6 +216,14 @@ public:
{
return api->getLogger();
}
+ virtual Nmea2kTwai *getNMEA2000()
+ {
+ return api->getNMEA2000();
+ }
+ virtual tN2kDeviceList *getN2kDeviceList()
+ {
+ return api->getN2kDeviceList();
+ }
virtual GwBoatData *getBoatData()
{
return api->getBoatData();
@@ -428,4 +436,4 @@ void GwUserCode::handleWebRequest(const String &url,AsyncWebServerRequest *req){
}
LOG_DEBUG(GwLog::DEBUG,"no task found for web request %s[%s]",url.c_str(),tname.c_str());
req->send(404, "text/plain", "not found");
-}
\ No newline at end of file
+}
diff --git a/src/main.cpp b/src/main.cpp
index 44c715f..fdb0366 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -100,6 +100,7 @@ GwLog logger(LOGLEVEL,NULL);
GwConfigHandler config(&logger);
#include "Nmea2kTwai.h"
+#include <N2kDeviceList.h>
static const unsigned long CAN_RECOVERY_PERIOD=3000; //ms
static const unsigned long NMEA2000_HEARTBEAT_INTERVAL=5000;
class Nmea2kTwaiLog : public Nmea2kTwai{
@@ -126,6 +127,7 @@ class Nmea2kTwaiLog : public Nmea2kTwai{
#endif
Nmea2kTwai &NMEA2000=*(new Nmea2kTwaiLog((gpio_num_t)ESP32_CAN_TX_PIN,(gpio_num_t)ESP32_CAN_RX_PIN,CAN_RECOVERY_PERIOD,&logger));
+tN2kDeviceList *pN2kDeviceList;
#ifdef GWBUTTON_PIN
bool fixedApPass=false;
@@ -333,6 +335,12 @@ public:
status.n2kTx=countNMEA2KOut.getGlobal();
channels.fillStatus(status);
}
+ virtual Nmea2kTwai *getNMEA2000(){
+ return &NMEA2000;
+ }
+ virtual tN2kDeviceList *getN2kDeviceList(){
+ return pN2kDeviceList;
+ }
virtual GwBoatData *getBoatData(){
return &boatData;
}
@@ -935,6 +943,7 @@ void setup() {
NMEA2000.SetMsgHandler([](const tN2kMsg &n2kMsg){
handleN2kMessage(n2kMsg,N2K_CHANNEL_ID);
});
+ pN2kDeviceList = new tN2kDeviceList(&NMEA2000);
NMEA2000.Open();
logger.logDebug(GwLog::LOG,"starting addon tasks");
logger.flush();
+59 -2
View File
@@ -4,9 +4,66 @@
#by uncommenting the next line
default_envs =
obp70_s3
obp60_s3
obp40_s3
[env:obp70_s3]
platform = espressif32@6.8.1
board_build.variants_dir = variants
board = obp70_s3_n16r8 #ESP32-S3 N16R8, 16MB flash, 8MB PSRAM, production series
board_build.partitions = default_16MB.csv #ESP32-S3 N16, 16MB flash
custom_config = lib/obp60task/config_obp70.json
custom_script = lib/obp60task/extra_task.py
framework = arduino
lib_deps =
${basedeps.lib_deps}
Wire
SPI
ESP32time
HTTPClient
WiFiClientSecure
esphome/AsyncTCP-esphome@2.1.1
robtillaart/PCF8574@0.3.9
adafruit/Adafruit Unified Sensor @ 1.1.13
blemasle/MCP23017@2.0.0
adafruit/Adafruit BusIO@1.5.0
adafruit/Adafruit GFX Library@1.11.9
#zinggjm/GxEPD2@1.5.8
#https://github.com/ZinggJM/GxEPD2
https://github.com/thooge/GxEPD2
sstaub/Ticker@4.4.0
adafruit/Adafruit BMP280 Library@2.6.2
adafruit/Adafruit BME280 Library@2.2.2
adafruit/Adafruit BMP085 Library@1.2.1
enjoyneering/HTU21D@1.2.1
robtillaart/INA226@0.2.0
paulstoffregen/OneWire@2.3.8
milesburton/DallasTemperature@3.11.0
signetica/SunRise@2.0.2
adafruit/Adafruit FRAM I2C@2.0.3
lovyan03/LovyanGFX@1.2.19
build_flags=
#https://thingpulse.com/usb-settings-for-logging-with-the-esp32-s3-in-platformio/?srsltid=AfmBOopGskbkr4GoeVkNlFaZXe_zXkLceKF6Rn-tmoXABCeAR2vWsdHL
# -D CORE_DEBUG_LEVEL=1 #Debug level for CPU core via CDC (serial device)
# -D TIME=$UNIX_TIME #Set PC time for RTC (only settable via VSC)
-D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib
-D BOARD_OBP60S3 #Board OBP60 V2.1 with ESP32S3
# -D HARDWARE_V20 #OBP60 hardware revision V2.0
-D HARDWARE_V21 #OBP60 hardware revision V2.1
-D TFT_DISPLAY #Enable TFT LCD display path (instead of E-Ink)
# -D TFT_320x480_ST7796 #TFT panel type: ST7796 (320x480, 80 MHz), best performance
-D TFT_320x480_ILI9488 #TFT panel type: ILI9488 (320x480, 40 MHz), lower performance
-D OBP_TFT_ENABLE_SCALING=1 #TFT scaling on/off (1=scale to max Y=320, 0=no scaling)
-D OBP_TFT_SCALE_ANTIALIAS=1 #Antialiasing for TFT scaling (1=on, 0=off)
# -D ENABLE_PATCHES #enable patching of gateway code
${env.build_flags}
#CONFIG_ESP_TASK_WDT_TIMEOUT_S = 10 #Task Watchdog timeout period (seconds) [1...60] 5 default
upload_port = /dev/ttyACM0 #OBP60 download via USB-C direct
upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27
upload_speed = 230400
monitor_speed = 115200
[env:obp60_s3]
platform = espressif32@6.8.1
board_build.variants_dir = variants
@@ -26,7 +83,7 @@ lib_deps =
ESP32time
HTTPClient
WiFiClientSecure
esphome/AsyncTCP-esphome@2.0.1
esphome/AsyncTCP-esphome@2.1.1
robtillaart/PCF8574@0.3.9
adafruit/Adafruit Unified Sensor @ 1.1.13
blemasle/MCP23017@2.0.0
@@ -82,7 +139,7 @@ lib_deps =
ESP32time
HTTPClient
WiFiClientSecure
esphome/AsyncTCP-esphome@2.0.1
esphome/AsyncTCP-esphome@2.1.1
robtillaart/PCF8574@0.3.9
adafruit/Adafruit Unified Sensor @ 1.1.13
blemasle/MCP23017@2.0.0
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# This script compile the software für OBP60-S3
# Attention! Start this cript only in the Gitpod Docker container.
# Start the script with: bash run
# Compile the firmware
echo "Compiling Firmware"
platformio run -e obp70_s3