mirror of
https://github.com/thooge/esp32-nmea2000-obp60.git
synced 2026-08-18 18:32:30 +02:00
Compare commits
95 Commits
86cd1ed97c
...
f99656f3d7
| Author | SHA1 | Date | |
|---|---|---|---|
| f99656f3d7 | |||
| ae1f402b49 | |||
| 3b2f73e22a | |||
| 89dfdb35c3 | |||
| 3f1d2d4ee2 | |||
| 856e19cd5e | |||
| 530c101421 | |||
| 3deb80b2c6 | |||
| 1f0e3cb6eb | |||
| a445726757 | |||
| aecc3c775f | |||
| 0dfb1f8535 | |||
| ba851d7e1b | |||
| f7b1b56715 | |||
| 08153cc585 | |||
| e309ec2123 | |||
| 7be72a8cc4 | |||
| 5fcca363f8 | |||
| 7c6005958e | |||
| 522759a26c | |||
| ea63386cfc | |||
| f362e96749 | |||
| 27d0e5d1b7 | |||
| 53f7a1c5bb | |||
| 588c88dfff | |||
| 73bc57fe9a | |||
| d13c4af9cb | |||
| a7cdfe700b | |||
| f165c628ae | |||
| 54dcb5fd5a | |||
| 604e2a15bd | |||
| 4e0bc88419 | |||
| c1a49fac4b | |||
| 019f7a11ef | |||
| 12fc962107 | |||
| a0afd5deca | |||
| 57272f3a44 | |||
| 64f9058f85 | |||
| cd84e427a3 | |||
| ec667c18f5 | |||
| af9bca4d66 | |||
| bc1440c428 | |||
| 5f161ce839 | |||
| a17db389fb | |||
| c5533ab0ef | |||
| 44e99cda50 | |||
| 2c2318adcc | |||
| 373878825c | |||
| 8cd193f2fc | |||
| 44b318ea0c | |||
| 9cfa9ccf67 | |||
| 762004ac9f | |||
| 907a817573 | |||
| 53bdc491f1 | |||
| 6cd0965e46 | |||
| f3f20f40c7 | |||
| ab0bac7577 | |||
| a4b99fb619 | |||
| b7fab08306 | |||
| b10792b243 | |||
| 2b2d2e3fd6 | |||
| 75724b29eb | |||
| abcbf50422 | |||
| 03b9294a1e | |||
| 2e47263008 | |||
| ffa9c8ac83 | |||
| 39c4e7fb32 | |||
| 5a30d7d022 | |||
| b29d89525a | |||
| 270642ad7c | |||
| 253aaa5b2e | |||
| 1aac8fdcd9 | |||
| 7e35c250cd | |||
| 0b3b5f387b | |||
| 42c147ac57 | |||
| 7434601382 | |||
| 2d6127912b | |||
| 415c155949 | |||
| 9ac0b4a2bb | |||
| 8a9f87a8f4 | |||
| ea6920f696 | |||
| ce4b581f7f | |||
| aea401589d | |||
| 79aeeed65c | |||
| 386b023677 | |||
| 501b9f7023 | |||
| 94f29eed06 | |||
| 6a0811f42f | |||
| bcb618fc7b | |||
| 2de3a1b084 | |||
| c647c2fe44 | |||
| 25422b77d6 | |||
| f21c8da2d2 | |||
| 1b4c952d2b | |||
| 34af16092b |
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino":{
|
||||||
|
"ldscript": "esp32s3_out.ld",
|
||||||
|
"partitions": "default_16MB.csv",
|
||||||
|
"memory_type": "qio_opi"
|
||||||
|
},
|
||||||
|
"core": "esp32",
|
||||||
|
"extra_flags": [
|
||||||
|
"-DBOARD_HAS_PSRAM",
|
||||||
|
"-DARDUINO_ESP32S3_DEV",
|
||||||
|
"-DARDUINO_USB_MODE=1",
|
||||||
|
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||||
|
"-DARDUINO_RUNNING_CORE=1",
|
||||||
|
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||||
|
],
|
||||||
|
"f_cpu": "240000000L",
|
||||||
|
"f_flash": "80000000L",
|
||||||
|
"flash_mode": "qio",
|
||||||
|
"hwids": [
|
||||||
|
[
|
||||||
|
"0x303A",
|
||||||
|
"0x1001"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"mcu": "esp32s3",
|
||||||
|
"variant": "obp70s3"
|
||||||
|
},
|
||||||
|
"connectivity": [
|
||||||
|
"bluetooth",
|
||||||
|
"wifi"
|
||||||
|
],
|
||||||
|
"debug": {
|
||||||
|
"default_tool": "esp-builtin",
|
||||||
|
"onboard_tools": [
|
||||||
|
"esp-builtin"
|
||||||
|
],
|
||||||
|
"openocd_target": "esp32s3.cfg"
|
||||||
|
},
|
||||||
|
"frameworks": [
|
||||||
|
"arduino",
|
||||||
|
"espidf"
|
||||||
|
],
|
||||||
|
"name": "OBP70 ESP32-S3-N16R8 16 MB QD, 8 MB PSRAM)",
|
||||||
|
"upload": {
|
||||||
|
"flash_size": "16MB",
|
||||||
|
"maximum_ram_size": 327680,
|
||||||
|
"maximum_size": 16777216,
|
||||||
|
"use_1200bps_touch": true,
|
||||||
|
"wait_for_upload_port": true,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 460800
|
||||||
|
},
|
||||||
|
"url": "https://open-boat-projects.org/en/diy-multifunktionsdisplay-obp-60/",
|
||||||
|
"vendor": "Open Boat Projects"
|
||||||
|
}
|
||||||
+29
-7
@@ -132,18 +132,40 @@ void GwWifi::loop(){
|
|||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString());
|
LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString());
|
||||||
|
|
||||||
// CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden
|
// Keep locked sections short to avoid cross-core stalls/WDT.
|
||||||
if (acquireMutex()){
|
if (acquireMutex()){
|
||||||
WiFi.disconnect(true);
|
WiFi.disconnect(true);
|
||||||
delay(300);
|
|
||||||
esp_wifi_stop();
|
|
||||||
delay(100);
|
|
||||||
esp_wifi_start();
|
|
||||||
releaseMutex();
|
releaseMutex();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop (disconnect)");
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(300);
|
||||||
|
|
||||||
|
if (acquireMutex()){
|
||||||
|
esp_err_t stopErr=esp_wifi_stop();
|
||||||
|
releaseMutex();
|
||||||
|
if (stopErr != ESP_OK){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: esp_wifi_stop failed: %d",(int)stopErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop (stop)");
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(100);
|
||||||
|
|
||||||
|
if (acquireMutex()){
|
||||||
|
esp_err_t startErr=esp_wifi_start();
|
||||||
|
releaseMutex();
|
||||||
|
if (startErr != ESP_OK){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: esp_wifi_start failed: %d",(int)startErr);
|
||||||
|
}
|
||||||
connectInternal();
|
connectInternal();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop");
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop (start)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -2,13 +2,22 @@
|
|||||||
#include <mbedtls/base64.h>
|
#include <mbedtls/base64.h>
|
||||||
|
|
||||||
// Decoder for Base64 content
|
// 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(
|
int ret = mbedtls_base64_decode(
|
||||||
outBuffer,
|
outBuffer,
|
||||||
outSize,
|
outSize,
|
||||||
&decodedSize,
|
&decodedSize,
|
||||||
(const unsigned char*)base64.c_str(),
|
(const unsigned char*)base64,
|
||||||
base64.length()
|
base64Len
|
||||||
);
|
);
|
||||||
return (ret == 0);
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,5 +5,6 @@
|
|||||||
|
|
||||||
class ImageDecoder {
|
class ImageDecoder {
|
||||||
public:
|
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);
|
bool decodeBase64(const String& base64, uint8_t* outBuffer, size_t outSize, size_t& decodedSize);
|
||||||
};
|
};
|
||||||
|
|||||||
+457
-53
@@ -7,13 +7,134 @@ extern "C" {
|
|||||||
#include "puff.h"
|
#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
|
// Constructor
|
||||||
NetworkClient::NetworkClient(size_t reserveSize)
|
NetworkClient::NetworkClient(size_t reserveSize)
|
||||||
: _doc(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
|
// Skip GZIP Header an goto DEFLATE content
|
||||||
int NetworkClient::skipGzipHeader(const uint8_t* data, size_t len) {
|
int NetworkClient::skipGzipHeader(const uint8_t* data, size_t len) {
|
||||||
if (len < 10) return -1;
|
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)
|
// HTTP GET + GZIP Decompression (reading in chunks)
|
||||||
bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& outLen) {
|
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);
|
uint8_t* buffer = (uint8_t*)malloc(capacity);
|
||||||
|
|
||||||
|
// If not with WiFi connectetd then return without any activities
|
||||||
if (!gwWifi.clientConnected()) {
|
if (!gwWifi.clientConnected()) {
|
||||||
if (DEBUGING) {Serial.println("No WiFi connection");}
|
if (DEBUGING) {Serial.println("No WiFi connection");}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If frame buffer not correct allocated then return without any activities
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
if (DEBUGING) {Serial.println("Malloc failed buffer");}
|
if (DEBUGING) {Serial.println("Malloc failed buffer");}
|
||||||
return false;
|
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.setTimeout(TCPREADTIMEOUT); // Read timeout in ms (can be adjusted in NetworkClient.h)
|
||||||
|
|
||||||
http.begin(url);
|
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");
|
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();
|
int code = http.GET();
|
||||||
if (code != HTTP_CODE_OK) {
|
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
|
// Hard reset HTTP + socket
|
||||||
WiFiClient* tmp = http.getStreamPtr();
|
WiFiClient* tmp = http.getStreamPtr();
|
||||||
if (tmp) tmp->stop(); // Force close TCP socket
|
if (tmp) tmp->stop(); // Force close TCP socket
|
||||||
|
|
||||||
http.end();
|
http.end();
|
||||||
|
|
||||||
free(buffer);
|
free(buffer);
|
||||||
return false;
|
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();
|
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)
|
const uint32_t READ_TIMEOUT = READDATATIMEOUT; // Timeout for reading data (can be adjusted in NetworkClient.h)
|
||||||
|
|
||||||
bool complete = false;
|
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) {
|
// NEW: fail fast if server claims something larger than our buffer
|
||||||
if (millis() - lastData > READ_TIMEOUT) {
|
if (total > 0 && (size_t)total > capacity) {
|
||||||
Serial.println("TIMEOUT waiting for data!");
|
Serial.println("Response exceeds READLIMIT.");
|
||||||
break;
|
aborting = true;
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Added: Force-close connection in all cases to avoid stuck TCP sockets ---
|
// NEW: if not gzip, we will not try to decompress (prevents false "Decompress OK" / random success)
|
||||||
if (stream) stream->stop();
|
// 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();
|
http.end();
|
||||||
free(buffer);
|
free(buffer);
|
||||||
@@ -158,6 +507,18 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
|
|||||||
bool NetworkClient::fetchAndDecompressJson(const String& url) {
|
bool NetworkClient::fetchAndDecompressJson(const String& url) {
|
||||||
|
|
||||||
_valid = false;
|
_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;
|
uint8_t* raw = nullptr;
|
||||||
size_t rawLen = 0;
|
size_t rawLen = 0;
|
||||||
@@ -167,15 +528,38 @@ bool NetworkClient::fetchAndDecompressJson(const String& url) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DeserializationError err = deserializeJson(_doc, raw, rawLen);
|
char* json = reinterpret_cast<char*>(raw);
|
||||||
free(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) {
|
if (!ok) {
|
||||||
Serial.printf("JSON ERROR: %s\n", err.c_str());
|
Serial.println("JSON field extraction failed.");
|
||||||
|
free(raw);
|
||||||
return false;
|
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;
|
_valid = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -184,6 +568,26 @@ JsonDocument& NetworkClient::json() {
|
|||||||
return _doc;
|
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 {
|
bool NetworkClient::isValid() const {
|
||||||
return _valid;
|
return _valid;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <HTTPClient.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 READLIMIT 200000 // HTTP read limit in byte for gzip content (can be adjusted)
|
||||||
#define CONNECTIONTIMEOUT 3000 // Timeout in ms for HTTP connection
|
#define CONNECTIONTIMEOUT 3000 // Timeout in ms for HTTP connection
|
||||||
#define TCPREADTIMEOUT 2000 // Timeout in ms for read HTTP client stack
|
#define TCPREADTIMEOUT 2000 // Timeout in ms for read HTTP client stack
|
||||||
@@ -12,16 +12,31 @@
|
|||||||
class NetworkClient {
|
class NetworkClient {
|
||||||
public:
|
public:
|
||||||
NetworkClient(size_t reserveSize = 0);
|
NetworkClient(size_t reserveSize = 0);
|
||||||
|
~NetworkClient();
|
||||||
|
|
||||||
bool fetchAndDecompressJson(const String& url);
|
bool fetchAndDecompressJson(const String& url);
|
||||||
JsonDocument& json();
|
JsonDocument& json();
|
||||||
|
int imageWidth() const;
|
||||||
|
int imageHeight() const;
|
||||||
|
int numberPixels() const;
|
||||||
|
const char* pictureBase64() const;
|
||||||
|
size_t pictureBase64Len() const;
|
||||||
bool isValid() const;
|
bool isValid() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DynamicJsonDocument _doc;
|
DynamicJsonDocument _doc;
|
||||||
bool _valid;
|
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);
|
int skipGzipHeader(const uint8_t* data, size_t len);
|
||||||
bool httpGetGzip(const String& url, uint8_t*& outData, size_t& outLen);
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;}
|
GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> & getdisplay(){return display;}
|
||||||
#endif
|
#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
|
// Horter I2C moduls
|
||||||
PCF8574 pcf8574_Modul1(PCF8574_I2C_ADDR1); // First digital IO modul PCF8574 from Horter
|
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();
|
GwLog *logger = api->getLogger();
|
||||||
GwConfigHandler *config = api->getConfig();
|
GwConfigHandler *config = api->getConfig();
|
||||||
|
|
||||||
Wire.begin();
|
Wire.begin(OBP_I2C_SDA, OBP_I2C_SCL);
|
||||||
// Init PCF8574 digital outputs
|
// Init PCF8574 digital outputs
|
||||||
Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz
|
Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz
|
||||||
if(pcf8574_Modul1.begin()){ // Initialize PCF8574
|
if(pcf8574_Modul1.begin()){ // Initialize PCF8574
|
||||||
@@ -175,7 +230,7 @@ void hardwareInit(GwApi *api)
|
|||||||
void powerInit(String powermode) {
|
void powerInit(String powermode) {
|
||||||
// Max Power | Only 5.0V | Min Power
|
// Max Power | Only 5.0V | Min Power
|
||||||
if (powermode == "Max Power" || powermode == "Only 5.0V") {
|
if (powermode == "Max Power" || powermode == "Only 5.0V") {
|
||||||
#ifdef HARDWARE_V21
|
#ifdef BOARD_OBP60S3
|
||||||
setPortPin(OBP_POWER_50, true); // Power on 5.0V rail
|
setPortPin(OBP_POWER_50, true); // Power on 5.0V rail
|
||||||
#endif
|
#endif
|
||||||
#ifdef BOARD_OBP40S3
|
#ifdef BOARD_OBP40S3
|
||||||
@@ -183,7 +238,7 @@ void powerInit(String powermode) {
|
|||||||
setPortPin(OBP_POWER_SD, true); // Power on SD card
|
setPortPin(OBP_POWER_SD, true); // Power on SD card
|
||||||
#endif
|
#endif
|
||||||
} else { // Min Power
|
} else { // Min Power
|
||||||
#ifdef HARDWARE_V21
|
#ifdef BOARD_OBP60S3
|
||||||
setPortPin(OBP_POWER_50, false); // Power off 5.0V rail
|
setPortPin(OBP_POWER_50, false); // Power off 5.0V rail
|
||||||
#endif
|
#endif
|
||||||
#ifdef BOARD_OBP40S3
|
#ifdef BOARD_OBP40S3
|
||||||
@@ -251,8 +306,12 @@ void deepSleep(CommonData &common){
|
|||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
getdisplay().setCursor(65, 175);
|
getdisplay().setCursor(65, 175);
|
||||||
getdisplay().print("To wake up press key and wait 5s");
|
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
|
getdisplay().powerOff(); // Display power off
|
||||||
|
#endif
|
||||||
setPortPin(OBP_POWER_50, false); // Power off ePaper display
|
setPortPin(OBP_POWER_50, false); // Power off ePaper display
|
||||||
// Stop system
|
// Stop system
|
||||||
esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin
|
esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin
|
||||||
@@ -276,8 +335,12 @@ void deepSleep(CommonData &common){
|
|||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
getdisplay().setCursor(65, 175);
|
getdisplay().setCursor(65, 175);
|
||||||
getdisplay().print("To wake up press wheel and wait 5s");
|
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
|
getdisplay().powerOff(); // Display power off
|
||||||
|
#endif
|
||||||
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
|
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
|
||||||
setPortPin(OBP_POWER_SD, false); // Power off SD card
|
setPortPin(OBP_POWER_SD, false); // Power off SD card
|
||||||
// Stop system
|
// 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) {
|
void drawTextCenter(int16_t cx, int16_t cy, String text) {
|
||||||
int16_t x1, y1;
|
int16_t x1, y1;
|
||||||
uint16_t w, h;
|
uint16_t w, h;
|
||||||
getdisplay().getTextBounds(text, 0, 150, &x1, &y1, &w, &h);
|
displayGetTextBounds(text, 0, 0, &x1, &y1, &w, &h);
|
||||||
getdisplay().setCursor(cx - w / 2, cy + h / 2);
|
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);
|
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) {
|
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;
|
int16_t x1, y1;
|
||||||
uint16_t w, h;
|
uint16_t w, h;
|
||||||
uint16_t color;
|
displayGetTextBounds(text, 0, 0, &x1, &y1, &w, &h);
|
||||||
|
int16_t cursorX = cx - (x1 + static_cast<int16_t>(w / 2));
|
||||||
getdisplay().getTextBounds(text, cx, cy, &x1, &y1, &w, &h); // Find text center
|
int16_t cursorY = cy - (y1 + static_cast<int16_t>(h / 2));
|
||||||
getdisplay().setCursor(cx - w/2, cy + h/2); // Set cursor to center
|
|
||||||
//getdisplay().drawPixel(cx, cy, fg); // Debug pixel for center position
|
//getdisplay().drawPixel(cx, cy, fg); // Debug pixel for center position
|
||||||
if (inverted) {
|
if (inverted) {
|
||||||
getdisplay().fillRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button
|
getdisplay().fillRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button
|
||||||
getdisplay().setTextColor(bg);
|
getdisplay().setTextColor(bg);
|
||||||
|
getdisplay().setCursor(cursorX, cursorY); // Set cursor to center
|
||||||
getdisplay().print(text); // Draw text
|
getdisplay().print(text); // Draw text
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
getdisplay().drawRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button
|
getdisplay().drawRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button
|
||||||
getdisplay().setTextColor(fg);
|
getdisplay().setTextColor(fg);
|
||||||
|
getdisplay().setCursor(cursorX, cursorY); // Set cursor to center
|
||||||
getdisplay().print(text); // Draw text
|
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) {
|
void drawTextRalign(int16_t x, int16_t y, String text) {
|
||||||
int16_t x1, y1;
|
int16_t x1, y1;
|
||||||
uint16_t w, h;
|
uint16_t w, h;
|
||||||
|
#ifdef TFT_DISPLAY
|
||||||
|
w = getdisplay().textWidth(text);
|
||||||
|
h = getdisplay().fontHeight();
|
||||||
|
#else
|
||||||
getdisplay().getTextBounds(text, 0, 150, &x1, &y1, &w, &h);
|
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().setCursor(x - w - 1, y); // '-1' required since some strings wrap around w/o it
|
||||||
getdisplay().print(text);
|
getdisplay().print(text);
|
||||||
}
|
}
|
||||||
@@ -595,7 +666,7 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa
|
|||||||
usbRxOld = commonData.status.usbRx;
|
usbRxOld = commonData.status.usbRx;
|
||||||
usbTxOld = commonData.status.usbTx;
|
usbTxOld = commonData.status.usbTx;
|
||||||
|
|
||||||
#ifdef HARDWARE_V21
|
#ifdef BOARD_OBP60S3
|
||||||
// Display key lock status
|
// Display key lock status
|
||||||
if (commonData.keylock) {
|
if (commonData.keylock) {
|
||||||
getdisplay().drawXBitmap(170, 1, lock_bits, icon_width, icon_height, commonData.fgcolor);
|
getdisplay().drawXBitmap(170, 1, lock_bits, icon_width, icon_height, commonData.fgcolor);
|
||||||
@@ -688,7 +759,7 @@ void displayFooter(CommonData &commonData) {
|
|||||||
getdisplay().setFont(&Atari16px);
|
getdisplay().setFont(&Atari16px);
|
||||||
getdisplay().setTextColor(commonData.fgcolor);
|
getdisplay().setTextColor(commonData.fgcolor);
|
||||||
|
|
||||||
#ifdef HARDWARE_V21
|
#ifdef BOARD_OBP60S3
|
||||||
// Frame around key icon area
|
// Frame around key icon area
|
||||||
if (! commonData.keylock) {
|
if (! commonData.keylock) {
|
||||||
// horizontal elements
|
// horizontal elements
|
||||||
@@ -1000,7 +1071,14 @@ void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, ui
|
|||||||
String lbl = String(angle);
|
String lbl = String(angle);
|
||||||
int16_t bx, by;
|
int16_t bx, by;
|
||||||
uint16_t bw, bh;
|
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 tx = xpos - bw/2;
|
||||||
int16_t ty = top + h + bh + 5; // A little spacing
|
int16_t ty = top + h + bh + 5; // A little spacing
|
||||||
getdisplay().setCursor(tx, ty);
|
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);
|
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
|
std::vector<uint8_t> imageBuffer; // image in webserver transferbuffer
|
||||||
String mimetype;
|
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") {
|
if (imgformat == "gif") {
|
||||||
// GIF is commpressed with LZW, so small
|
// GIF is commpressed with LZW, so small
|
||||||
|
|||||||
@@ -5,14 +5,28 @@
|
|||||||
#include "OBP60Hardware.h"
|
#include "OBP60Hardware.h"
|
||||||
#include "LedSpiTask.h"
|
#include "LedSpiTask.h"
|
||||||
#include "Graphics.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 <Adafruit_FRAM_I2C.h> // I2C FRAM
|
||||||
#include <math.h>
|
#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
|
#ifdef BOARD_OBP40S3
|
||||||
#include "esp_vfs_fat.h"
|
#include "esp_vfs_fat.h"
|
||||||
#include "sdmmc_cmd.h"
|
#include "sdmmc_cmd.h"
|
||||||
#define MOUNT_POINT "/sdcard"
|
#define MOUNT_POINT "/sdcard"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// FRAM address reservations 32kB: 0x0000 - 0x7FFF
|
// 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();
|
GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> & getdisplay();
|
||||||
#endif
|
#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
|
// Page display return values
|
||||||
#define PAGE_OK 0 // all ok, do nothing
|
#define PAGE_OK 0 // all ok, do nothing
|
||||||
#define PAGE_UPDATE 1 // page wants display to update
|
#define PAGE_UPDATE 1 // page wants display to update
|
||||||
#define PAGE_HIBERNATE 2 // page wants displey to hibernate
|
#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 per‑pixel
|
||||||
|
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
|
||||||
|
// E‑Paper: just hand over to driver (expects MSB‑first horizontal)
|
||||||
|
getdisplay().drawBitmap(x, y, bmp, w, h, color);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Display wrapper functions for E-Ink/TFT compatibility
|
||||||
|
|
||||||
|
// generic bitmap draw that accepts 1‑bit 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 fillPoly4(const std::vector<Point>& p4, uint16_t color);
|
||||||
void drawPoly(const std::vector<Point>& points, uint16_t color);
|
void drawPoly(const std::vector<Point>& points, uint16_t color);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// General hardware definitions
|
// General hardware definitions
|
||||||
// CAN and RS485 bus pin definitions see obp60task.h
|
// 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
|
// Direction pin for RS485 NMEA0183
|
||||||
#define OBP_DIRECTION_PIN 18
|
#define OBP_DIRECTION_PIN 18
|
||||||
// I2C
|
// I2C
|
||||||
@@ -34,13 +34,17 @@
|
|||||||
#define PCF8574_I2C_ADDR1 0x20 // First digital out module
|
#define PCF8574_I2C_ADDR1 0x20 // First digital out module
|
||||||
// FRAM (e.g. MB85RC256V)
|
// FRAM (e.g. MB85RC256V)
|
||||||
#define FRAM_I2C_ADDR 0x50
|
#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_CS 39
|
||||||
#define OBP_SPI_DC 40
|
#define OBP_SPI_DC 40
|
||||||
#define OBP_SPI_RST 41
|
#define OBP_SPI_RST 41
|
||||||
#define OBP_SPI_BUSY 42
|
#define OBP_SPI_BUSY 42
|
||||||
#define OBP_SPI_CLK 38
|
#define OBP_SPI_CLK 38
|
||||||
#define OBP_SPI_DIN 48
|
#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 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 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
|
#define GxEPD_WIDTH 400 // Display width
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ void initKeys(CommonData &commonData) {
|
|||||||
commonData.keydata[5].h = height;
|
commonData.keydata[5].h = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined HARDWARE_V20 || HARDWARE_V21
|
#ifdef BOARD_OBP60S3
|
||||||
// Keypad functions for original OBP60 hardware
|
// Keypad functions for original OBP60 hardware
|
||||||
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {
|
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ void qrWiFi(String ssid, String passwd, uint16_t fgcolor, uint16_t bgcolor){
|
|||||||
getdisplay().setTextColor(fgcolor);
|
getdisplay().setTextColor(fgcolor);
|
||||||
getdisplay().setCursor(140, 285);
|
getdisplay().setCursor(140, 285);
|
||||||
getdisplay().print("WiFi");
|
getdisplay().print("WiFi");
|
||||||
getdisplay().nextPage(); // Full Refresh
|
displayNextPage(); // Full Refresh
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+109
-108
@@ -1,5 +1,4 @@
|
|||||||
#include "OBPDataOperations.h"
|
#include "OBPDataOperations.h"
|
||||||
//#include "BoatDataCalibration.h" // Functions lib for data instance calibration
|
|
||||||
|
|
||||||
// --- Class CalibrationData ---------------
|
// --- Class CalibrationData ---------------
|
||||||
CalibrationData::CalibrationData(GwLog* log)
|
CalibrationData::CalibrationData(GwLog* log)
|
||||||
@@ -155,8 +154,7 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
|||||||
|
|
||||||
if (format == "formatWind") { // instance is of type angle
|
if (format == "formatWind") { // instance is of type angle
|
||||||
dataValue = (dataValue * slope) + offset;
|
dataValue = (dataValue * slope) + offset;
|
||||||
// dataValue = WindUtils::toPI(dataValue);
|
dataValue = WindUtils::to2PI(dataValue); // wind angle data is stored in [0..2PI] format
|
||||||
dataValue = WindUtils::to2PI(dataValue); // we should call <toPI> for format of [-180..180], but pages cannot display negative values properly yet
|
|
||||||
|
|
||||||
} else if (format == "formatCourse") { // instance is of type direction
|
} else if (format == "formatCourse") { // instance is of type direction
|
||||||
dataValue = (dataValue * slope) + offset;
|
dataValue = (dataValue * slope) + offset;
|
||||||
@@ -169,7 +167,6 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
|||||||
dataValue = (dataValue * slope) + offset;
|
dataValue = (dataValue * slope) + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
boatDataValue->value = dataValue; // update boat data value with calibrated value
|
boatDataValue->value = dataValue; // update boat data value with calibrated value
|
||||||
calibrationMap[instance].value = dataValue; // store the calibrated value in the list
|
calibrationMap[instance].value = dataValue; // store the calibrated value in the list
|
||||||
calibrationMap[instance].isCalibrated = true;
|
calibrationMap[instance].isCalibrated = true;
|
||||||
@@ -247,23 +244,15 @@ void HstryBuf::add(double value)
|
|||||||
|
|
||||||
void HstryBuf::handle(bool useSimuData, CommonData& common)
|
void HstryBuf::handle(bool useSimuData, CommonData& common)
|
||||||
{
|
{
|
||||||
// GwApi::BoatValue* tmpBVal;
|
|
||||||
std::unique_ptr<GwApi::BoatValue> tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter
|
std::unique_ptr<GwApi::BoatValue> tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter
|
||||||
|
|
||||||
// create temporary boat value for calibration purposes and retrieval of simulation value
|
|
||||||
// tmpBVal = new GwApi::BoatValue(boatDataName.c_str());
|
|
||||||
tmpBVal = std::unique_ptr<GwApi::BoatValue>(new GwApi::BoatValue(boatDataName));
|
|
||||||
tmpBVal->setFormat(boatValue->getFormat());
|
|
||||||
tmpBVal->value = boatValue->value;
|
|
||||||
tmpBVal->valid = boatValue->valid;
|
|
||||||
|
|
||||||
if (boatValue->valid) {
|
if (boatValue->valid) {
|
||||||
// Calibrate boat value before adding it to history buffer
|
|
||||||
//calibrationData.calibrateInstance(tmpBVal.get(), logger);
|
|
||||||
//add(tmpBVal->value);
|
|
||||||
add(boatValue->value);
|
add(boatValue->value);
|
||||||
|
|
||||||
} else if (useSimuData) { // add simulated value to history buffer
|
} else if (useSimuData) { // add simulated value to history buffer
|
||||||
|
tmpBVal = std::unique_ptr<GwApi::BoatValue>(new GwApi::BoatValue(boatDataName)); // create temporary boat value for retrieval of simulation value
|
||||||
|
tmpBVal->setFormat(boatValue->getFormat());
|
||||||
|
tmpBVal->value = boatValue->value;
|
||||||
|
tmpBVal->valid = boatValue->valid;
|
||||||
double simSIValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at <formatValue>; here: retreive SI value
|
double simSIValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at <formatValue>; here: retreive SI value
|
||||||
add(simSIValue);
|
add(simSIValue);
|
||||||
} else {
|
} else {
|
||||||
@@ -276,20 +265,7 @@ void HstryBuf::handle(bool useSimuData, CommonData& common)
|
|||||||
HstryBuffers::HstryBuffers(int size, BoatValueList* boatValues, GwLog* log)
|
HstryBuffers::HstryBuffers(int size, BoatValueList* boatValues, GwLog* log)
|
||||||
: size(size)
|
: size(size)
|
||||||
, boatValueList(boatValues)
|
, boatValueList(boatValues)
|
||||||
, logger(log)
|
, logger(log) { };
|
||||||
{
|
|
||||||
|
|
||||||
// collect boat values for true wind calculation
|
|
||||||
// should all have been already created at true wind object initialization
|
|
||||||
// potentially to be moved to history buffer handling
|
|
||||||
awaBVal = boatValueList->findValueOrCreate("AWA");
|
|
||||||
hdtBVal = boatValueList->findValueOrCreate("HDT");
|
|
||||||
hdmBVal = boatValueList->findValueOrCreate("HDM");
|
|
||||||
varBVal = boatValueList->findValueOrCreate("VAR");
|
|
||||||
cogBVal = boatValueList->findValueOrCreate("COG");
|
|
||||||
sogBVal = boatValueList->findValueOrCreate("SOG");
|
|
||||||
awdBVal = boatValueList->findValueOrCreate("AWD");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create history buffer for boat data type
|
// Create history buffer for boat data type
|
||||||
void HstryBuffers::addBuffer(const String& name)
|
void HstryBuffers::addBuffer(const String& name)
|
||||||
@@ -301,8 +277,6 @@ void HstryBuffers::addBuffer(const String& name)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
hstryBuffers[name] = std::unique_ptr<HstryBuf>(new HstryBuf(name, size, boatValueList, logger));
|
|
||||||
|
|
||||||
// Initialize metadata for buffer
|
// Initialize metadata for buffer
|
||||||
String valueFormat = bufferParams[name].format; // Data format of boat data type
|
String valueFormat = bufferParams[name].format; // Data format of boat data type
|
||||||
// String valueFormat = boatValueList->findValueOrCreate(name)->getFormat().c_str(); // Unfortunately, format is not yet available during system initialization
|
// String valueFormat = boatValueList->findValueOrCreate(name)->getFormat().c_str(); // Unfortunately, format is not yet available during system initialization
|
||||||
@@ -311,6 +285,7 @@ void HstryBuffers::addBuffer(const String& name)
|
|||||||
double bufferMinVal = bufferParams[name].bufferMinVal; // Min value for this history buffer
|
double bufferMinVal = bufferParams[name].bufferMinVal; // Min value for this history buffer
|
||||||
double bufferMaxVal = bufferParams[name].bufferMaxVal; // Max value for this history buffer
|
double bufferMaxVal = bufferParams[name].bufferMaxVal; // Max value for this history buffer
|
||||||
|
|
||||||
|
hstryBuffers[name] = std::unique_ptr<HstryBuf>(new HstryBuf(name, size, boatValueList, logger));
|
||||||
hstryBuffers[name]->init(valueFormat, hstryUpdFreq, mltplr, bufferMinVal, bufferMaxVal);
|
hstryBuffers[name]->init(valueFormat, hstryUpdFreq, mltplr, bufferMinVal, bufferMaxVal);
|
||||||
LOG_DEBUG(GwLog::DEBUG, "HstryBuffers: new buffer added: name: %s, format: %s, multiplier: %d, min value: %.2f, max value: %.2f", name, valueFormat, mltplr, bufferMinVal, bufferMaxVal);
|
LOG_DEBUG(GwLog::DEBUG, "HstryBuffers: new buffer added: name: %s, format: %s, multiplier: %d, min value: %.2f, max value: %.2f", name, valueFormat, mltplr, bufferMinVal, bufferMaxVal);
|
||||||
}
|
}
|
||||||
@@ -329,8 +304,9 @@ RingBuffer<uint16_t>* HstryBuffers::getBuffer(const String& name)
|
|||||||
auto it = hstryBuffers.find(name);
|
auto it = hstryBuffers.find(name);
|
||||||
if (it != hstryBuffers.end()) {
|
if (it != hstryBuffers.end()) {
|
||||||
return &it->second->hstryBuf;
|
return &it->second->hstryBuf;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
// --- End Class HstryBuffers ---------------
|
// --- End Class HstryBuffers ---------------
|
||||||
|
|
||||||
@@ -396,56 +372,75 @@ void WindUtils::addPolar(const double* phi1, const double* r1,
|
|||||||
toPol(&x1, &y1, phi, r);
|
toPol(&x1, &y1, phi, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindUtils::calcTwdSA(const double* AWA, const double* AWS,
|
void WindUtils::calcTwdSA(const double* AWA, const double* AWS, const double* AWD, const double* CTW, const double* STW, const double* HDT,
|
||||||
const double* CTW, const double* STW, const double* HDT,
|
double* TWA, double* TWS, double* TWD)
|
||||||
double* TWD, double* TWS, double* TWA, double* AWD)
|
|
||||||
{
|
{
|
||||||
*AWD = *AWA + *HDT;
|
|
||||||
*AWD = to2PI(*AWD);
|
|
||||||
double stw = -*STW;
|
double stw = -*STW;
|
||||||
addPolar(AWD, AWS, CTW, &stw, TWD, TWS);
|
addPolar(AWD, AWS, CTW, &stw, TWD, TWS);
|
||||||
|
|
||||||
// Normalize TWD to [0..360°] (2PI) and TWA to [-180..180] (PI)
|
// Normalize to [0..2PI]
|
||||||
*TWD = to2PI(*TWD);
|
*TWD = to2PI(*TWD);
|
||||||
*TWA = toPI(*TWD - *HDT);
|
*TWA = to2PI(*TWD - *HDT); // for internal storage we normalize wind angle to [0..2PI], as well
|
||||||
}
|
}
|
||||||
|
|
||||||
double WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal)
|
// calculate HDT from either HDM + VAR, HDM only, or COG
|
||||||
|
bool WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal, double* hdtVal)
|
||||||
{
|
{
|
||||||
double hdt;
|
|
||||||
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
|
|
||||||
|
|
||||||
if (*hdmVal != DBL_MAX) {
|
|
||||||
hdt = *hdmVal + (*varVal != DBL_MAX ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available)
|
|
||||||
hdt = to2PI(hdt);
|
|
||||||
} else if (*cogVal != DBL_MAX && *sogVal >= minSogVal) {
|
|
||||||
hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise
|
|
||||||
} else {
|
|
||||||
hdt = DBL_MAX; // Cannot calculate HDT without valid HDM or HDM+VAR or COG
|
|
||||||
}
|
|
||||||
|
|
||||||
return hdt;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WindUtils::calcWinds(const double* awaVal, const double* awsVal,
|
|
||||||
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
|
|
||||||
const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal, double* awdVal)
|
|
||||||
{
|
|
||||||
double stw, hdt, ctw;
|
|
||||||
double twd, tws, twa, awd;
|
|
||||||
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
|
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
|
||||||
|
|
||||||
if (*hdtVal != DBL_MAX) {
|
if (*hdtVal != DBL_MAX) {
|
||||||
hdt = *hdtVal; // Use HDT if available
|
// HDT already available-> nothing to do
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*hdmVal != DBL_MAX) {
|
||||||
|
*hdtVal = *hdmVal + (*varVal != DBL_MAX ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available)
|
||||||
|
*hdtVal = to2PI(*hdtVal);
|
||||||
|
} else if (*cogVal != DBL_MAX && *sogVal >= minSogVal) {
|
||||||
|
*hdtVal = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise
|
||||||
} else {
|
} else {
|
||||||
hdt = calcHDT(hdmVal, varVal, cogVal, sogVal);
|
*hdtVal = DBL_MAX; // Cannot calculate HDT without valid HDM or HDM+VAR or COG
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcHDT: HDT: %.1f, HDM %.1f, VAR %.1f, COG %.1f, SOG %.1f", *hdtVal * RAD_TO_DEG, *hdmVal * RAD_TO_DEG, *varVal * RAD_TO_DEG,
|
||||||
|
// *cogVal * RAD_TO_DEG, *sogVal * 3.6 / 1.852);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate wind direction (either TWD or AWD) from wind angle (TWA or AWA) and HDT
|
||||||
|
bool WindUtils::calcWD(const double* waVal, const double* hdtVal, double* wdVal)
|
||||||
|
{
|
||||||
|
if (*waVal != DBL_MAX && *hdtVal != DBL_MAX) {
|
||||||
|
*wdVal = *waVal + *hdtVal;
|
||||||
|
*wdVal = to2PI(*wdVal);
|
||||||
|
} else {
|
||||||
|
*wdVal = DBL_MAX; // Cannot calculate wind direction WD without valid wind angle WA and HDT
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate full set of true winds (TWA, TWS, TWD) apparent winds and more data
|
||||||
|
bool WindUtils::calcTrueWinds(const double* awaVal, const double* awsVal, const double* awd,
|
||||||
|
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
|
||||||
|
double* twaVal, double* twsVal, double* twdVal)
|
||||||
|
{
|
||||||
|
double stw, ctw;
|
||||||
|
double twd, tws, twa;
|
||||||
|
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
|
||||||
|
|
||||||
|
if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) {
|
||||||
|
// Cannot calculate true winds at all without valid AWA, AWS
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*cogVal != DBL_MAX && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG
|
if (*cogVal != DBL_MAX && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG
|
||||||
|
|
||||||
ctw = *cogVal; // Use COG for CTW if available
|
ctw = *cogVal; // Use COG for CTW if available
|
||||||
} else {
|
} else {
|
||||||
ctw = hdt; // 2nd approximation for CTW; hdt must exist if we reach this part of the code
|
ctw = *hdtVal; // 2nd approximation for CTW; HDT must exist if we reach this part of code
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*stwVal != DBL_MAX) {
|
if (*stwVal != DBL_MAX) {
|
||||||
@@ -456,28 +451,21 @@ bool WindUtils::calcWinds(const double* awaVal, const double* awsVal,
|
|||||||
// If STW and SOG are not available, we cannot calculate true wind
|
// If STW and SOG are not available, we cannot calculate true wind
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcWinds: HDT: %.1f, CTW %.1f, STW %.1f", hdt, ctw, stw);
|
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcTrueWinds: HDT: %.1f, CTW %.1f, STW %.1f", *hdtVal * RAD_TO_DEG, ctw * RAD_TO_DEG, stw * 3.6 / 1.852);
|
||||||
|
|
||||||
if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) {
|
calcTwdSA(awaVal, awsVal, awd, &ctw, &stw, hdtVal, &twa, &tws, &twd);
|
||||||
// Cannot calculate true wind without valid AWA, AWS; other checks are done earlier
|
*twaVal = twa;
|
||||||
return false;
|
*twsVal = tws;
|
||||||
} else {
|
*twdVal = twd;
|
||||||
calcTwdSA(awaVal, awsVal, &ctw, &stw, &hdt, &twd, &tws, &twa, &awd);
|
|
||||||
*twdVal = twd;
|
|
||||||
*twsVal = tws;
|
|
||||||
*twaVal = twa;
|
|
||||||
*awdVal = awd;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate true wind data and add to obp60task boat data list
|
// Calculate true wind data and add to obp60task boat data list
|
||||||
bool WindUtils::addWinds()
|
bool WindUtils::handleWinds(bool calcWinds)
|
||||||
{
|
{
|
||||||
double twd, tws, twa, awd, hdt;
|
double twd, tws, twa, awd;
|
||||||
bool twCalculated = false;
|
bool twCalculated = false;
|
||||||
bool awdCalculated = false;
|
|
||||||
|
|
||||||
double awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX;
|
double awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX;
|
||||||
double awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX;
|
double awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX;
|
||||||
@@ -487,48 +475,61 @@ bool WindUtils::addWinds()
|
|||||||
double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
|
double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
|
||||||
double hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX;
|
double hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX;
|
||||||
double varVal = varBVal->valid ? varBVal->value : DBL_MAX;
|
double varVal = varBVal->valid ? varBVal->value : DBL_MAX;
|
||||||
//LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852,
|
double twaVal = twaBVal->valid ? twaBVal->value : DBL_MAX;
|
||||||
// cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG);
|
double twsVal = twsBVal->valid ? twsBVal->value : DBL_MAX;
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:handleWinds: AWA %.1f, AWS %.1f, AWD %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaVal * RAD_TO_DEG, awsVal * 3.6 / 1.852,
|
||||||
|
// awd * RAD_TO_DEG, cogVal * RAD_TO_DEG, stwVal * 3.6 / 1.852, sogVal * 3.6 / 1.852, hdtVal * RAD_TO_DEG, hdmVal * RAD_TO_DEG, varVal * RAD_TO_DEG);
|
||||||
|
|
||||||
// Check if TWD can be calculated from TWA and HDT/HDM
|
if (calcHDT(&hdmVal, &varVal, &cogVal, &sogVal, &hdtVal)) {
|
||||||
if (twaBVal->valid) {
|
hdtBVal->value = hdtVal;
|
||||||
if (!twdBVal->valid) {
|
hdtBVal->valid = true;
|
||||||
if (hdtVal != DBL_MAX) {
|
} else {
|
||||||
hdt = hdtVal; // Use HDT if available
|
// determination of HDT was not possible due to missing prerequisite data
|
||||||
} else {
|
hdtBVal->valid = false;
|
||||||
hdt = calcHDT(&hdmVal, &varVal, &cogVal, &sogVal);
|
return false;
|
||||||
}
|
}
|
||||||
twd = twaBVal->value + hdt;
|
|
||||||
twd = to2PI(twd);
|
// calculate AWD if not existing and if possible
|
||||||
|
if (!awdBVal->valid) {
|
||||||
|
if (calcWD(&awaVal, &hdtVal, &awd)) {
|
||||||
|
awdBVal->value = awd;
|
||||||
|
awdBVal->valid = true;
|
||||||
|
} else {
|
||||||
|
awdBVal->valid = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate TWD if not existing and if possible
|
||||||
|
if (!twdBVal->valid) {
|
||||||
|
// calculate TWD if it does not exist yet and TWA is available
|
||||||
|
if (calcWD(&twaVal, &hdtVal, &twd)) {
|
||||||
twdBVal->value = twd;
|
twdBVal->value = twd;
|
||||||
twdBVal->valid = true;
|
twdBVal->valid = true;
|
||||||
|
} else {
|
||||||
|
twdBVal->valid = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
if (calcWinds && (!twaBVal->valid || !twsBVal->valid || !twdBVal->valid)) {
|
||||||
// Calculate true winds and AWD; if true winds exist, use at least AWD calculation
|
// calculate true winds if user setting and at least one of three true wind values does not exist
|
||||||
twCalculated = calcWinds(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa, &awd);
|
twCalculated = calcTrueWinds(&awaVal, &awsVal, &awd, &cogVal, &stwVal, &sogVal, &hdtVal, &twa, &tws, &twd);
|
||||||
|
|
||||||
if (twCalculated) { // Replace values only, if successfully calculated and not already available
|
if (twCalculated) { // Replace values only, if successfully calculated and not already available
|
||||||
if (!twdBVal->valid) {
|
if (!twaBVal->valid) {
|
||||||
twdBVal->value = twd;
|
twaBVal->value = twa;
|
||||||
twdBVal->valid = true;
|
twaBVal->valid = true;
|
||||||
}
|
}
|
||||||
if (!twsBVal->valid) {
|
if (!twsBVal->valid) {
|
||||||
twsBVal->value = tws;
|
twsBVal->value = tws;
|
||||||
twsBVal->valid = true;
|
twsBVal->valid = true;
|
||||||
}
|
}
|
||||||
if (!twaBVal->valid) {
|
if (!twdBVal->valid) {
|
||||||
//twaBVal->value = twa;
|
twdBVal->value = twd;
|
||||||
twaBVal->value = to2PI(twa); // convert to [0..360], because pages cannot display negative values properly yet
|
twdBVal->valid = true;
|
||||||
twaBVal->valid = true;
|
|
||||||
}
|
|
||||||
if (!awdBVal->valid) {
|
|
||||||
awdBVal->value = awd;
|
|
||||||
awdBVal->valid = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG,
|
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:handleWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG,
|
||||||
// twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG);
|
// twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG);
|
||||||
|
|
||||||
return twCalculated;
|
return twCalculated;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ private:
|
|||||||
} tCalibrationData;
|
} tCalibrationData;
|
||||||
|
|
||||||
std::unordered_map<std::string, tCalibrationData> calibrationMap; // list of calibration data instances
|
std::unordered_map<std::string, tCalibrationData> calibrationMap; // list of calibration data instances
|
||||||
std::unordered_map<std::string, double> lastValue; // array for last smoothed values of boat data values
|
std::unordered_map<std::string, double> lastValue; // array for last smoothed value of boat data values
|
||||||
GwLog* logger;
|
GwLog* logger;
|
||||||
|
|
||||||
static constexpr int8_t MAX_CALIBRATION_DATA = 4; // maximum number of calibration data instances
|
static constexpr int8_t MAX_CALIBRATION_DATA = 4; // maximum number of calibration data instances
|
||||||
@@ -32,6 +32,7 @@ public:
|
|||||||
bool smoothInstance(GwApi::BoatValue* boatDataValue); // Smooth single boat data value
|
bool smoothInstance(GwApi::BoatValue* boatDataValue); // Smooth single boat data value
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Class for a single history buffer of boat values
|
||||||
class HstryBuf {
|
class HstryBuf {
|
||||||
private:
|
private:
|
||||||
RingBuffer<uint16_t> hstryBuf; // Circular buffer to store history values
|
RingBuffer<uint16_t> hstryBuf; // Circular buffer to store history values
|
||||||
@@ -50,13 +51,13 @@ public:
|
|||||||
void handle(bool useSimuData, CommonData& common);
|
void handle(bool useSimuData, CommonData& common);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Manage list of history buffers for supported boat data; used by boat data charts
|
||||||
class HstryBuffers {
|
class HstryBuffers {
|
||||||
private:
|
private:
|
||||||
std::map<String, std::unique_ptr<HstryBuf>> hstryBuffers;
|
std::map<String, std::unique_ptr<HstryBuf>> hstryBuffers;
|
||||||
int size; // size of all history buffers
|
int size; // size of all history buffers
|
||||||
BoatValueList* boatValueList;
|
BoatValueList* boatValueList;
|
||||||
GwLog* logger;
|
GwLog* logger;
|
||||||
GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal, *awdBVal; // boat values for true wind calculation
|
|
||||||
|
|
||||||
struct HistoryParams {
|
struct HistoryParams {
|
||||||
int hstryUpdFreq; // update frequency of history buffer (documentation only)
|
int hstryUpdFreq; // update frequency of history buffer (documentation only)
|
||||||
@@ -97,7 +98,8 @@ public:
|
|||||||
class WindUtils {
|
class WindUtils {
|
||||||
private:
|
private:
|
||||||
GwApi::BoatValue *twaBVal, *twsBVal, *twdBVal;
|
GwApi::BoatValue *twaBVal, *twsBVal, *twdBVal;
|
||||||
GwApi::BoatValue *awaBVal, *awsBVal, *awdBVal, *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal;
|
GwApi::BoatValue *awaBVal, *awsBVal, *awdBVal;
|
||||||
|
GwApi::BoatValue *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal;
|
||||||
static constexpr double DBL_MAX = std::numeric_limits<double>::max();
|
static constexpr double DBL_MAX = std::numeric_limits<double>::max();
|
||||||
GwLog* logger;
|
GwLog* logger;
|
||||||
|
|
||||||
@@ -128,12 +130,12 @@ public:
|
|||||||
void addPolar(const double* phi1, const double* r1,
|
void addPolar(const double* phi1, const double* r1,
|
||||||
const double* phi2, const double* r2,
|
const double* phi2, const double* r2,
|
||||||
double* phi, double* r);
|
double* phi, double* r);
|
||||||
void calcTwdSA(const double* AWA, const double* AWS,
|
void calcTwdSA(const double* AWA, const double* AWS, const double* AWD, const double* CTW, const double* STW, const double* HDT,
|
||||||
const double* CTW, const double* STW, const double* HDT,
|
double* TWA, double* TWS, double* TWD);
|
||||||
double* TWD, double* TWS, double* TWA, double* AWD);
|
bool calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal, double* hdtVal);
|
||||||
static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal);
|
bool calcWD(const double* waVal, const double* hdtVal, double* wdVal);
|
||||||
bool calcWinds(const double* awaVal, const double* awsVal,
|
bool calcTrueWinds(const double* awaVal, const double* awsVal, const double* awd,
|
||||||
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
|
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
|
||||||
const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal, double* awdVal);
|
double* twdVal, double* twsVal, double* twaVal);
|
||||||
bool addWinds();
|
bool handleWinds(bool calcWinds);
|
||||||
};
|
};
|
||||||
@@ -66,8 +66,8 @@ void sensorTask(void *param){
|
|||||||
const int avgsize = 300;
|
const int avgsize = 300;
|
||||||
constexpr int arrayBatV{avgsize};
|
constexpr int arrayBatV{avgsize};
|
||||||
constexpr int arrayBatC{avgsize};
|
constexpr int arrayBatC{avgsize};
|
||||||
movingAvg batV(arrayBatV);
|
movingAvg<int> batV(arrayBatV);
|
||||||
movingAvg batC(arrayBatC);
|
movingAvg<int> batC(arrayBatC);
|
||||||
batV.begin();
|
batV.begin();
|
||||||
batC.begin();
|
batC.begin();
|
||||||
|
|
||||||
|
|||||||
+58
-23
@@ -6,9 +6,9 @@
|
|||||||
std::map<String, ChartProps> Chart::dfltChrtDta = {
|
std::map<String, ChartProps> Chart::dfltChrtDta = {
|
||||||
{ "formatWind", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees
|
{ "formatWind", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees
|
||||||
{ "formatCourse", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees
|
{ "formatCourse", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees
|
||||||
{ "formatKnots", { 7.71, 2.56 } }, // default speed range in m/s
|
{ "formatKnots", { 2.57, 2.57 } }, // default speed range in m/s
|
||||||
{ "formatDepth", { 15.0, 5.0 } }, // default depth range in m
|
{ "formatDepth", { 10.0, 5.0 } }, // default depth range in m
|
||||||
{ "kelvinToC", { 30.0, 5.0 } } // default temp range in °C/K
|
{ "kelvinToC", { 20.0, 5.0 } } // default temp range in °C/K
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Class Chart ---------------
|
// --- Class Chart ---------------
|
||||||
@@ -28,14 +28,25 @@ Chart::Chart(RingBuffer<uint16_t>& dataBuf, double dfltRng, CommonData& common,
|
|||||||
fgColor = commonData->fgcolor;
|
fgColor = commonData->fgcolor;
|
||||||
bgColor = commonData->bgcolor;
|
bgColor = commonData->bgcolor;
|
||||||
|
|
||||||
dWidth = getdisplay().width();
|
// display dimensions (avoid calling width()/height() on incomplete LGFX type)
|
||||||
dHeight = getdisplay().height();
|
#ifdef TFT_DISPLAY
|
||||||
|
dWidth = 480;
|
||||||
|
dHeight = 320;
|
||||||
|
#else
|
||||||
|
dWidth = getdisplay().width();
|
||||||
|
dHeight = getdisplay().height();
|
||||||
|
#endif
|
||||||
|
|
||||||
dataBuf.getMetaData(dbName, dbFormat);
|
dataBuf.getMetaData(dbName, dbFormat);
|
||||||
dbMIN_VAL = dataBuf.getMinVal();
|
dbMIN_VAL = dataBuf.getMinVal();
|
||||||
dbMAX_VAL = dataBuf.getMaxVal();
|
dbMAX_VAL = dataBuf.getMaxVal();
|
||||||
bufSize = dataBuf.getCapacity();
|
bufSize = dataBuf.getCapacity();
|
||||||
|
|
||||||
|
smoothCharts = common.config->getBool(common.config->smoothCharts);
|
||||||
|
if (smoothCharts) {
|
||||||
|
chrtAvg.begin();
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize chart data format; shorter version of standard format indicator
|
// Initialize chart data format; shorter version of standard format indicator
|
||||||
if (dbFormat == "formatCourse" || dbFormat == "formatWind" || dbFormat == "formatRot") {
|
if (dbFormat == "formatCourse" || dbFormat == "formatWind" || dbFormat == "formatRot") {
|
||||||
chrtDataFmt = WIND; // Chart is showing data of course / wind <degree> format
|
chrtDataFmt = WIND; // Chart is showing data of course / wind <degree> format
|
||||||
@@ -331,6 +342,23 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
|
|||||||
double chrtVal; // Current data value
|
double chrtVal; // Current data value
|
||||||
Pos point, prevPoint; // current and previous chart point
|
Pos point, prevPoint; // current and previous chart point
|
||||||
|
|
||||||
|
if (smoothCharts) {
|
||||||
|
// prime moving average filter to ensure the first plotted point is already averaged
|
||||||
|
|
||||||
|
chrtAvg.reset();
|
||||||
|
|
||||||
|
// Feed the filter the 10 values preceding bufStart
|
||||||
|
for (int p = 10; p > 0; p--) {
|
||||||
|
// Calculate index with wrapping: (start - offset + size) % size
|
||||||
|
int primeIdx = (bufStart - (p * chrtIntv));
|
||||||
|
double primeVal = dataBuf.get(primeIdx);
|
||||||
|
|
||||||
|
if (primeVal != dbMAX_VAL) {
|
||||||
|
chrtAvg.reading(primeVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < (numBufVals / chrtIntv); i++) {
|
for (int i = 0; i < (numBufVals / chrtIntv); i++) {
|
||||||
|
|
||||||
chrtVal = dataBuf.get(bufStart + (i * chrtIntv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer
|
chrtVal = dataBuf.get(bufStart + (i * chrtIntv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer
|
||||||
@@ -339,6 +367,11 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
|
|||||||
chrtPrevVal = dbMAX_VAL;
|
chrtPrevVal = dbMAX_VAL;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
if (smoothCharts) {
|
||||||
|
// if chart lines shall be smoothed, apply moving average filter of the last 10 values
|
||||||
|
chrtVal = chrtAvg.reading(chrtVal);
|
||||||
|
}
|
||||||
|
|
||||||
point = setCurrentChartPoint(i, direction, chrtVal, chrtScale);
|
point = setCurrentChartPoint(i, direction, chrtVal, chrtScale);
|
||||||
|
|
||||||
// if (i >= (numBufVals / chrtIntv) - 5) // log chart data of 1 line (adjust for test purposes)
|
// if (i >= (numBufVals / chrtIntv) - 5) // log chart data of 1 line (adjust for test purposes)
|
||||||
@@ -393,7 +426,7 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
|
|||||||
|
|
||||||
if (chrtDataFmt == WIND) { // degree of course or wind
|
if (chrtDataFmt == WIND) { // degree of course or wind
|
||||||
recalcRngMid = true;
|
recalcRngMid = true;
|
||||||
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chart end: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngMid);
|
// LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chart end: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngMid);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -434,46 +467,48 @@ Pos Chart::setCurrentChartPoint(const int i, const char direction, const double
|
|||||||
// chart time axis label + lines
|
// chart time axis label + lines
|
||||||
void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8_t chrtIntv)
|
void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8_t chrtIntv)
|
||||||
{
|
{
|
||||||
float axSlots, intv, i;
|
int axSlots, intv, i, timeRng;
|
||||||
char sTime[6];
|
char sTime[6];
|
||||||
int timeRng = chrtIntv * 4; // chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min.
|
int tOffset;
|
||||||
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
getdisplay().setTextColor(fgColor);
|
getdisplay().setTextColor(fgColor);
|
||||||
|
|
||||||
axSlots = 5; // number of axis labels
|
intv = 60; // print axis label each 60 pixels = seconds
|
||||||
intv = timAxis / (axSlots - 1); // minutes per chart axis interval (interval is 1 less than axSlots)
|
axSlots = timAxis / intv; // number of axis labels; value will be truncated to full number
|
||||||
i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes
|
|
||||||
|
|
||||||
if (chrtDir == HORIZONTAL) {
|
if (chrtDir == HORIZONTAL) {
|
||||||
getdisplay().fillRect(0, cRoot.y, dWidth, 2, fgColor);
|
getdisplay().fillRect(0, cRoot.y, dWidth, 2, fgColor);
|
||||||
|
|
||||||
for (float j = 0; j < timAxis - 1; j += intv) { // fill time axis with values but keep area free on right hand side for value label
|
i = axSlots * chrtIntv * -1; // current minute label
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtTimeAxis: intv: %d, axSlots: %d, timAxis: %d, chrtIntv: %d", intv, axSlots, timAxis, chrtIntv);
|
||||||
|
for (int j = intv; j < timAxis - 1; j += intv) { // fill time axis with values but keep area free on right hand side for value label
|
||||||
|
|
||||||
// draw text with appropriate offset
|
// draw text with appropriate offset
|
||||||
int tOffset = j == 0 ? 13 : -4;
|
tOffset = j == 0 ? 13 : -4;
|
||||||
snprintf(sTime, sizeof(sTime), "-%.0f", i);
|
snprintf(sTime, sizeof(sTime), "%d", i);
|
||||||
drawTextCenter(cRoot.x + j + tOffset, cRoot.y - 8, sTime);
|
drawTextCenter(cRoot.x + j + tOffset, cRoot.y - 8, sTime);
|
||||||
getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + 5, fgColor); // draw short vertical time mark
|
getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + 5, fgColor); // draw short vertical time mark
|
||||||
|
|
||||||
i -= chrtIntv;
|
i += chrtIntv;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else { // vertical chart
|
} else { // vertical chart
|
||||||
|
|
||||||
|
i = chrtIntv * -1; // current minute label
|
||||||
for (float j = intv; j < timAxis - 1; j += intv) { // don't print time label at upper and lower end of time axis
|
for (float j = intv; j < timAxis - 1; j += intv) { // don't print time label at upper and lower end of time axis
|
||||||
|
|
||||||
i -= chrtIntv; // we start not at top chart position
|
snprintf(sTime, sizeof(sTime), "%d", i);
|
||||||
snprintf(sTime, sizeof(sTime), "-%.0f", i);
|
|
||||||
getdisplay().drawLine(cRoot.x, cRoot.y + j, cRoot.x + valAxis, cRoot.y + j, fgColor); // Grid line
|
getdisplay().drawLine(cRoot.x, cRoot.y + j, cRoot.x + valAxis, cRoot.y + j, fgColor); // Grid line
|
||||||
|
|
||||||
if (chrtSz == FULL_SIZE) { // full size chart
|
if (chrtSz == FULL_SIZE) { // full size chart
|
||||||
getdisplay().fillRect(0, cRoot.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines
|
getdisplay().fillRect(0, cRoot.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines
|
||||||
getdisplay().setCursor((4 - strlen(sTime)) * 7, cRoot.y + j + 3); // time value; print left screen; value right-formated
|
getdisplay().setCursor((4 - strlen(sTime)) * 7, cRoot.y + j + 3); // time value; print left screen; value right-formated
|
||||||
getdisplay().printf("%s", sTime); // Range value
|
getdisplay().printf("%s", sTime); // time value
|
||||||
} else if (chrtSz == HALF_SIZE_RIGHT) { // half size chart; right side
|
} else if (chrtSz == HALF_SIZE_RIGHT) { // half size chart; right side
|
||||||
drawTextCenter(dWidth / 2, cRoot.y + j, sTime); // time value; print mid screen
|
drawTextCenter(dWidth / 2, cRoot.y + j, sTime); // time value; print mid screen
|
||||||
}
|
}
|
||||||
|
i -= chrtIntv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -491,11 +526,11 @@ void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntNa
|
|||||||
|
|
||||||
if (chrtSz == FULL_SIZE) {
|
if (chrtSz == FULL_SIZE) {
|
||||||
|
|
||||||
|
// print buffer data name on left hand side of time axis (max. size 5 characters)
|
||||||
font = &Ubuntu_Bold12pt8b;
|
font = &Ubuntu_Bold12pt8b;
|
||||||
|
|
||||||
// print buffer data name on right hand side of time axis (max. size 5 characters)
|
|
||||||
getdisplay().setFont(font);
|
getdisplay().setFont(font);
|
||||||
drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5));
|
getdisplay().fillRect(cRoot.x + timAxis - 57, cRoot.y + 2, 58, 20, bgColor); // clear small area to remove potential chart lines
|
||||||
|
drawTextRalign(cRoot.x + timAxis, cRoot.y + 19, dbName.substring(0, 5));
|
||||||
|
|
||||||
if (chrtDataFmt == WIND) {
|
if (chrtDataFmt == WIND) {
|
||||||
prntHorizChartThreeValueAxisLabel(font);
|
prntHorizChartThreeValueAxisLabel(font);
|
||||||
@@ -509,11 +544,11 @@ void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntNa
|
|||||||
} else { // half size chart -> just print edge values + middle chart line
|
} else { // half size chart -> just print edge values + middle chart line
|
||||||
|
|
||||||
font = &Ubuntu_Bold10pt8b;
|
font = &Ubuntu_Bold10pt8b;
|
||||||
|
|
||||||
if (prntName) {
|
if (prntName) {
|
||||||
// print buffer data name on right hand side of time axis (max. size 5 characters)
|
// print buffer data name on right hand side of time axis (max. size 5 characters)
|
||||||
getdisplay().setFont(font);
|
getdisplay().setFont(font);
|
||||||
drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5));
|
getdisplay().fillRect(cRoot.x + timAxis - 57, cRoot.y + 2, 58, 20, bgColor); // clear small area to remove potential chart lines
|
||||||
|
drawTextRalign(cRoot.x + timAxis, cRoot.y + 16, dbName.substring(0, 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
prntHorizChartThreeValueAxisLabel(font);
|
prntHorizChartThreeValueAxisLabel(font);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "Pagedata.h"
|
#include "Pagedata.h"
|
||||||
#include "OBP60Extensions.h"
|
#include "OBP60Extensions.h"
|
||||||
|
#include "movingAvg.h"
|
||||||
|
|
||||||
struct Pos {
|
struct Pos {
|
||||||
int x;
|
int x;
|
||||||
@@ -44,12 +45,11 @@ protected:
|
|||||||
static constexpr bool NO_SIMUDATA = true; // switch off simulation feature of <formatValue> function
|
static constexpr bool NO_SIMUDATA = true; // switch off simulation feature of <formatValue> function
|
||||||
|
|
||||||
RingBuffer<uint16_t>& dataBuf; // Buffer to display
|
RingBuffer<uint16_t>& dataBuf; // Buffer to display
|
||||||
//char chrtDir; // Chart timeline direction: 'H' = horizontal, 'V' = vertical
|
|
||||||
//int8_t chrtSz; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom
|
|
||||||
double dfltRng; // Default range of chart, e.g. 30 = [0..30]
|
double dfltRng; // Default range of chart, e.g. 30 = [0..30]
|
||||||
uint16_t fgColor; // color code for any screen writing
|
uint16_t fgColor; // color code for any screen writing
|
||||||
uint16_t bgColor; // color code for screen background
|
uint16_t bgColor; // color code for screen background
|
||||||
bool useSimuData; // flag to indicate if simulation data is active
|
bool useSimuData; // flag to indicate if simulation data is active
|
||||||
|
bool smoothCharts; // flag to indicate if charts shall be printed with smoothed gradient
|
||||||
String tempFormat; // user defined format for temperature
|
String tempFormat; // user defined format for temperature
|
||||||
double zeroValue; // "0" SI value for temperature
|
double zeroValue; // "0" SI value for temperature
|
||||||
|
|
||||||
@@ -84,6 +84,7 @@ protected:
|
|||||||
bool bufDataValid = false; // Flag to indicate if buffer data is valid
|
bool bufDataValid = false; // Flag to indicate if buffer data is valid
|
||||||
int oldChrtIntv = 0; // remember recent user selection of data interval
|
int oldChrtIntv = 0; // remember recent user selection of data interval
|
||||||
|
|
||||||
|
movingAvg<double> chrtAvg{7}; // Store average of the last 7 chart values if chart gradient shall be smoothed
|
||||||
double chrtPrevVal; // Last data value in chart area
|
double chrtPrevVal; // Last data value in chart area
|
||||||
int x, y; // x and y coordinates for drawing
|
int x, y; // x and y coordinates for drawing
|
||||||
int prevX, prevY; // Last x and y coordinates for drawing
|
int prevX, prevY; // Last x and y coordinates for drawing
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class PageAutopilot : public Page
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
/*
|
/*
|
||||||
// Horizontal line 2 pix top & bottom
|
// Horizontal line 2 pix top & bottom
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class PageBME280 : public Page
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ class PageBattery : public Page
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
// Show average settings
|
// Show average settings
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "Pagedata.h"
|
#include "Pagedata.h"
|
||||||
#include "OBP60Extensions.h"
|
#include "OBP60Extensions.h"
|
||||||
#include "movingAvg.h" // Lib for moving average building
|
|
||||||
|
|
||||||
class PageBattery2 : public Page
|
class PageBattery2 : public Page
|
||||||
{
|
{
|
||||||
@@ -184,7 +183,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
|||||||
@@ -375,7 +375,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
@@ -442,8 +442,8 @@ public:
|
|||||||
uint16_t wDigit, hDigit;
|
uint16_t wDigit, hDigit;
|
||||||
uint16_t wColon, hColon;
|
uint16_t wColon, hColon;
|
||||||
|
|
||||||
getdisplay().getTextBounds("00", 0, 0, &x0, &y0, &wDigit, &hDigit);
|
displayGetTextBounds("00", 0, 0, &x0, &y0, &wDigit, &hDigit);
|
||||||
getdisplay().getTextBounds(":", 0, 0, &x0, &y0, &wColon, &hColon);
|
displayGetTextBounds(":", 0, 0, &x0, &y0, &wColon, &hColon);
|
||||||
|
|
||||||
uint16_t totalWidth = 3 * wDigit + 2 * wColon;
|
uint16_t totalWidth = 3 * wDigit + 2 * wColon;
|
||||||
|
|
||||||
@@ -453,7 +453,7 @@ public:
|
|||||||
// Draw time string centered
|
// Draw time string centered
|
||||||
int16_t x1b, y1b;
|
int16_t x1b, y1b;
|
||||||
uint16_t wb, hb;
|
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 textX = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2;
|
||||||
int16_t textY = centerY + hb / 2;
|
int16_t textY = centerY + hb / 2;
|
||||||
|
|
||||||
@@ -520,7 +520,7 @@ public:
|
|||||||
|
|
||||||
int16_t x1b, y1b;
|
int16_t x1b, y1b;
|
||||||
uint16_t wb, hb;
|
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 x = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2;
|
||||||
int16_t y = 150 + hb / 2;
|
int16_t y = 150 + hb / 2;
|
||||||
@@ -665,7 +665,7 @@ public:
|
|||||||
// Print text centered on position x, y
|
// Print text centered on position x, y
|
||||||
int16_t x1c, y1c; // Return values of getTextBounds
|
int16_t x1c, y1c; // Return values of getTextBounds
|
||||||
uint16_t wc, hc; // 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);
|
getdisplay().setCursor(x - wc / 2, y + hc / 2);
|
||||||
if (i % 90 == 0) {
|
if (i % 90 == 0) {
|
||||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class PageCompass : public Page
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
// Horizontal line 2 pix top & bottom
|
// Horizontal line 2 pix top & bottom
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ bool button5 = false;
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
// Write text
|
// Write text
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class PageFluid : public Page
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh mode
|
// 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);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class PageFourValues : public Page
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class PageFourValues2 : public Page
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "Pagedata.h"
|
#include "Pagedata.h"
|
||||||
#include "OBP60Extensions.h"
|
#include "OBP60Extensions.h"
|
||||||
#include "movingAvg.h" // Lib for moving average building
|
|
||||||
|
|
||||||
class PageGenerator : public Page
|
class PageGenerator : public Page
|
||||||
{
|
{
|
||||||
@@ -83,7 +82,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
//*******************************************************************************************
|
//*******************************************************************************************
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ public:
|
|||||||
// Print text centered on position x, y
|
// Print text centered on position x, y
|
||||||
int16_t x1, y1; // Return values of getTextBounds
|
int16_t x1, y1; // Return values of getTextBounds
|
||||||
uint16_t w, h; // 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);
|
getdisplay().setCursor(x-w/2, y+h/2);
|
||||||
if(i % 30 == 0){
|
if(i % 30 == 0){
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "OBP60Extensions.h"
|
#include "OBP60Extensions.h"
|
||||||
#include "NetworkClient.h" // Network connection
|
#include "NetworkClient.h" // Network connection
|
||||||
#include "ImageDecoder.h" // Image decoder for navigation map
|
#include "ImageDecoder.h" // Image decoder for navigation map
|
||||||
|
#include <mbedtls/base64.h>
|
||||||
|
|
||||||
#include "Logo_OBP_400x300_sw.h"
|
#include "Logo_OBP_400x300_sw.h"
|
||||||
|
|
||||||
@@ -12,6 +13,54 @@
|
|||||||
NetworkClient net(JSON_BUFFER); // Define network client
|
NetworkClient net(JSON_BUFFER); // Define network client
|
||||||
ImageDecoder decoder; // Define image decoder
|
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
|
class PageNavigation : public Page
|
||||||
{
|
{
|
||||||
// Values for buttons
|
// Values for buttons
|
||||||
@@ -24,13 +73,19 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
|
|||||||
int imageBackupWidth = 0;
|
int imageBackupWidth = 0;
|
||||||
int imageBackupHeight = 0;
|
int imageBackupHeight = 0;
|
||||||
size_t imageBackupSize = 0;
|
size_t imageBackupSize = 0;
|
||||||
|
size_t imageBackupCapacity = 0;
|
||||||
bool hasImageBackup = false;
|
bool hasImageBackup = false;
|
||||||
|
bool imageBackupIsRgb565 = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PageNavigation(CommonData &common){
|
PageNavigation(CommonData &common){
|
||||||
commonData = &common;
|
commonData = &common;
|
||||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageNavigation");
|
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
|
// Set botton labels
|
||||||
@@ -295,6 +350,18 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
|
|||||||
mType = 9;
|
mType = 9;
|
||||||
dType = 1;
|
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{
|
else{
|
||||||
mType = 1;
|
mType = 1;
|
||||||
dType = 1;
|
dType = 1;
|
||||||
@@ -347,22 +414,33 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
|
|||||||
// URL to OBP Maps Converter
|
// URL to OBP Maps Converter
|
||||||
// For more details see: https://github.com/norbert-walter/maps-converter
|
// For more details see: https://github.com/norbert-walter/maps-converter
|
||||||
String url = String("http://") + server + ":" + port + // OBP Server
|
String url = String("http://") + server + ":" + port + // OBP Server
|
||||||
String("/get_image_json?") + // Service: Output B&W picture as JSON (Base64 + gzip)
|
String("/get_image_json?") + // Service: Output B&W picture as JSON (Base64 + gzip)
|
||||||
"zoom=" + zoom + // Default zoom level: 15
|
#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
|
"&lat=" + String(latitude, 6) + // Latitude
|
||||||
"&lon=" + String(longitude, 6) + // Longitude
|
"&lon=" + String(longitude, 6) + // Longitude
|
||||||
"&mrot=" + mapRot + // Rotation angle navigation map in degree
|
"&mrot=" + mapRot + // Rotation angle navigation map in degree
|
||||||
"&mtype=" + mType + // Default Map: Open Street Map
|
"&mtype=" + mType + // Default Map: Open Street Map
|
||||||
"&dtype=" + dType + // Dithering type: Atkinson dithering
|
#ifdef TFT_DISPLAY
|
||||||
"&width=400" + // With navigation map
|
"&itype=1" + // Image type: 1=Color
|
||||||
"&height=250" + // Height navigation map
|
#else
|
||||||
"&cutout=0" + // No picture cutouts
|
"&itype=4" + // Image type: 4=b/w with dithering
|
||||||
"&tab=0" + // No tab size
|
#endif
|
||||||
"&border=2" + // Border line size: 2 pixel
|
"&dtype=" + dType + // Dithering type: Atkinson dithering (only activ when itype=4 otherwise inactive)
|
||||||
"&symbol=2" + // Symbol: Triangle
|
"&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
|
"&srot=" + symbolRot + // Symbol rotation angle
|
||||||
"&ssize=15" + // Symbole size: 15 pixel
|
"&ssize=15" + // Symbole size: 15 pixel (center pointer)
|
||||||
"&grid=" + mapGrid // Show grid: On
|
"&grid=" + mapGrid // Show grid: On
|
||||||
;
|
;
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -371,19 +449,45 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
|
|||||||
// ############### Draw Navigation Map ################
|
// ############### Draw Navigation Map ################
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
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 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
|
// NEW: reset backoff on success
|
||||||
int numPix = json["number_pixels"] | 0; // Read number of pixels
|
failCount = 0;
|
||||||
imgWidth = json["width"] | 0; // Read width of image
|
nextAllowedMs = now + 1000; // keep 1 Hz on success
|
||||||
imgHeight = json["height"] | 0; // Read height og image
|
|
||||||
|
|
||||||
const char* b64src = json["picture_base64"].as<const char*>(); // Read picture as Base64 content
|
int numPix = net.numberPixels(); // Read number of pixels
|
||||||
size_t b64len = strlen(b64src); // Calculate length of Base64 content
|
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
|
// Copy Base64 content in PSRAM
|
||||||
char* b64 = (char*) heap_caps_malloc(b64len + 1, MALLOC_CAP_SPIRAM); // Allcate PSRAM for Base64 content
|
char* b64 = (char*) heap_caps_malloc(b64len + 1, MALLOC_CAP_SPIRAM); // Allcate PSRAM for Base64 content
|
||||||
if (!b64) {
|
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
|
memcpy(b64, b64src, b64len + 1); // Copy Base64 content in PSRAM
|
||||||
|
|
||||||
// Set image buffer in PSRAM
|
// Set image buffer in PSRAM
|
||||||
//size_t imgSize = getdisplay().width() * getdisplay().height();
|
size_t imgSize = (numPix > 0) ? (size_t)numPix : requiredBytesMono; // Calculate image size
|
||||||
size_t imgSize = numPix; // 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
|
uint8_t* imageData = (uint8_t*) heap_caps_malloc(imgSize, MALLOC_CAP_SPIRAM); // Allocate PSRAM for image
|
||||||
if (!imageData) {
|
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);
|
free(b64);
|
||||||
return PAGE_UPDATE;
|
return PAGE_UPDATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode Base64 content to image
|
// Decode Base64 content to image
|
||||||
size_t decodedSize = 0;
|
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;
|
imageBackupWidth = imgWidth;
|
||||||
imageBackupHeight = imgHeight;
|
imageBackupHeight = imgHeight;
|
||||||
imageBackupSize = imgSize;
|
imageBackupSize = imgSize;
|
||||||
if (decodedSize > 0) {
|
if (decodedSize > 0 && imageBackupData != nullptr) {
|
||||||
memcpy(imageBackupData, imageData, decodedSize);
|
size_t copySize = (decodedSize > imageBackupCapacity) ? imageBackupCapacity : decodedSize;
|
||||||
imageBackupSize = decodedSize;
|
memcpy(imageBackupData, imageData, copySize);
|
||||||
|
imageBackupSize = copySize;
|
||||||
}
|
}
|
||||||
hasImageBackup = true;
|
imageBackupIsRgb565 = imageIsRgb565;
|
||||||
|
hasImageBackup = (imageBackupData != nullptr);
|
||||||
lostCounter = 0;
|
lostCounter = 0;
|
||||||
|
|
||||||
// Show image (navigation map)
|
// 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
|
// Clean PSRAM
|
||||||
free(b64);
|
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
|
// If no network connection then use backup navigation map
|
||||||
else{
|
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)
|
// Show backup image (backup navigation map)
|
||||||
if (hasImageBackup) {
|
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
|
// Short connection losts are uncritical
|
||||||
if(lostCounter >= 5){
|
if(lostCounter >= 5){
|
||||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
@@ -444,7 +618,6 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map
|
|||||||
lostCounter++; // Increment lost counter
|
lostCounter++; // Increment lost counter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ############### Draw Values ################
|
// ############### Draw Values ################
|
||||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
|
||||||
|
|||||||
@@ -209,8 +209,6 @@ public:
|
|||||||
dataIntv = 3;
|
dataIntv = 3;
|
||||||
} else if (dataIntv == 3) {
|
} else if (dataIntv == 3) {
|
||||||
dataIntv = 4;
|
dataIntv = 4;
|
||||||
} else if (dataIntv == 4) {
|
|
||||||
dataIntv = 8;
|
|
||||||
} else {
|
} else {
|
||||||
dataIntv = 1;
|
dataIntv = 1;
|
||||||
}
|
}
|
||||||
@@ -276,7 +274,7 @@ public:
|
|||||||
// Draw page
|
// Draw page
|
||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
getdisplay().setPartialWindow(0, 0, width, height); // Set partial update
|
displaySetPartialWindow(0, 0, width, height); // Set partial update
|
||||||
|
|
||||||
if (pageMode == VALUE || dataHstryBuf == nullptr) {
|
if (pageMode == VALUE || dataHstryBuf == nullptr) {
|
||||||
// show only data value; ignore other pageMode options if no chart supported boat data history buffer is available
|
// show only data value; ignore other pageMode options if no chart supported boat data history buffer is available
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ public:
|
|||||||
// Print text centered on position x, y
|
// Print text centered on position x, y
|
||||||
int16_t x1, y1; // Return values of getTextBounds
|
int16_t x1, y1; // Return values of getTextBounds
|
||||||
uint16_t w, h; // 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);
|
getdisplay().setCursor(x-w/2, y+h/2);
|
||||||
if(i % 20 == 0){
|
if(i % 20 == 0){
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
//*******************************************************************************************
|
//*******************************************************************************************
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ public:
|
|||||||
// Print text centered on position x, y
|
// Print text centered on position x, y
|
||||||
int16_t x1, y1; // Return values of getTextBounds
|
int16_t x1, y1; // Return values of getTextBounds
|
||||||
uint16_t w, h; // 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);
|
getdisplay().setCursor(x-w/2, y+h/2);
|
||||||
if(i % 30 == 0){
|
if(i % 30 == 0){
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class PageSixValues : public Page
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
for (int i = 0; i < ( HowManyValues / 2 ); i++){
|
for (int i = 0; i < ( HowManyValues / 2 ); i++){
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
// current position
|
// current position
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
@@ -105,19 +105,19 @@ public:
|
|||||||
uint16_t w, h;
|
uint16_t w, h;
|
||||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
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().setCursor(c.x - w / 2, c.y - r + h + 3);
|
||||||
getdisplay().print("N");
|
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().setCursor(c.x - w / 2, c.y + r - 3);
|
||||||
getdisplay().print("S");
|
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().setCursor(c.x + r - w - 3, c.y + h / 2);
|
||||||
getdisplay().print("E");
|
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().setCursor(c.x - r + 3 , c.y + h / 2);
|
||||||
getdisplay().print("W");
|
getdisplay().print("W");
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "Pagedata.h"
|
#include "Pagedata.h"
|
||||||
#include "OBP60Extensions.h"
|
#include "OBP60Extensions.h"
|
||||||
#include "movingAvg.h" // Lib for moving average building
|
|
||||||
|
|
||||||
class PageSolar : public Page
|
class PageSolar : public Page
|
||||||
{
|
{
|
||||||
@@ -82,7 +81,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
|||||||
@@ -635,7 +635,7 @@ public:
|
|||||||
commonData->logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
|
commonData->logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
|
||||||
|
|
||||||
// Set display in partial refresh mode
|
// Set display in partial refresh mode
|
||||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
|
|
||||||
// call current system page
|
// call current system page
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
@@ -657,7 +657,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
// Update display
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
displayNextPage(); // Partial update (fast)
|
||||||
return PAGE_OK;
|
return PAGE_OK;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class PageThreeValues : public Page
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
/// Set display in partial refresh 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
|
||||||
|
|
||||||
// ############### Value 1 ################
|
// ############### Value 1 ################
|
||||||
|
|
||||||
|
|||||||
@@ -211,8 +211,6 @@ public:
|
|||||||
dataIntv = 3;
|
dataIntv = 3;
|
||||||
} else if (dataIntv == 3) {
|
} else if (dataIntv == 3) {
|
||||||
dataIntv = 4;
|
dataIntv = 4;
|
||||||
} else if (dataIntv == 4) {
|
|
||||||
dataIntv = 8;
|
|
||||||
} else {
|
} else {
|
||||||
dataIntv = 1;
|
dataIntv = 1;
|
||||||
}
|
}
|
||||||
@@ -283,7 +281,7 @@ public:
|
|||||||
// Draw page
|
// 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)) {
|
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
|
// show only data value; ignore other pageMode options if no chart supported boat data history buffer is available
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "Pagedata.h"
|
#include "Pagedata.h"
|
||||||
#include "OBP60Extensions.h"
|
#include "OBP60Extensions.h"
|
||||||
#include "movingAvg.h" // Lib for moving average building
|
|
||||||
|
|
||||||
class PageVoltage : public Page
|
class PageVoltage : public Page
|
||||||
{
|
{
|
||||||
@@ -193,7 +192,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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 == 'D') {
|
if (mode == 'D') {
|
||||||
// Display mode digital
|
// Display mode digital
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public:
|
|||||||
if (mode == 'W') {
|
if (mode == 'W') {
|
||||||
getdisplay().setFullWindow();
|
getdisplay().setFullWindow();
|
||||||
} else {
|
} 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') {
|
if (mode == 'L') {
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ private:
|
|||||||
bool oldShowTruW = false; // remember recent user selection of wind data type
|
bool oldShowTruW = false; // remember recent user selection of wind data type
|
||||||
|
|
||||||
int8_t dataIntv = 1; // Update interval for wind history chart:
|
int8_t dataIntv = 1; // Update interval for wind history chart:
|
||||||
// (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart
|
// (1)|(2)|(3)|(4)|(8) seconds for up to 32 min. history chart
|
||||||
bool useSimuData;
|
bool useSimuData;
|
||||||
// bool holdValues;
|
// bool holdValues;
|
||||||
String flashLED;
|
String flashLED;
|
||||||
@@ -221,7 +221,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh mode
|
// 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);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
if (chrtMode == DIRECTION) {
|
if (chrtMode == DIRECTION) {
|
||||||
@@ -231,6 +231,9 @@ public:
|
|||||||
|
|
||||||
} else if (chrtMode == SPEED) {
|
} else if (chrtMode == SPEED) {
|
||||||
if (wsChart) {
|
if (wsChart) {
|
||||||
|
if (dataIntv == 8) {
|
||||||
|
dataIntv = 1; // horizontal charts show max. 4 x 7 min. only; no factor 8 multiplier
|
||||||
|
}
|
||||||
wsChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *wsBVal);
|
wsChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *wsBVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ public:
|
|||||||
// Print text centered on position x, y
|
// Print text centered on position x, y
|
||||||
int16_t x1, y1; // Return values of getTextBounds
|
int16_t x1, y1; // Return values of getTextBounds
|
||||||
uint16_t w, h; // 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);
|
getdisplay().setCursor(x-w/2, y+h/2);
|
||||||
if(i % 30 == 0){
|
if(i % 30 == 0){
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ public:
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
@@ -314,7 +314,7 @@ public:
|
|||||||
// Print text centered on position x, y
|
// Print text centered on position x, y
|
||||||
int16_t x1, y1; // Return values of getTextBounds
|
int16_t x1, y1; // Return values of getTextBounds
|
||||||
uint16_t w, h; // 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);
|
getdisplay().setCursor(x-w/2, y+h/2);
|
||||||
if(i % 30 == 0){
|
if(i % 30 == 0){
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class PageXTETrack : public Page
|
|||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh 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
|
||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
@@ -112,25 +112,25 @@ class PageXTETrack : public Page
|
|||||||
|
|
||||||
GwApi::BoatValue *bv_xte = pageData.values[0]; // XTE
|
GwApi::BoatValue *bv_xte = pageData.values[0]; // XTE
|
||||||
String sval_xte = formatValue(bv_xte, *commonData).svalue;
|
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().setCursor(160-w, 170);
|
||||||
getdisplay().print(sval_xte);
|
getdisplay().print(sval_xte);
|
||||||
|
|
||||||
GwApi::BoatValue *bv_cog = pageData.values[1]; // COG
|
GwApi::BoatValue *bv_cog = pageData.values[1]; // COG
|
||||||
String sval_cog = formatValue(bv_cog, *commonData).svalue;
|
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().setCursor(360-w, 170);
|
||||||
getdisplay().print(sval_cog);
|
getdisplay().print(sval_cog);
|
||||||
|
|
||||||
GwApi::BoatValue *bv_dtw = pageData.values[2]; // DTW
|
GwApi::BoatValue *bv_dtw = pageData.values[2]; // DTW
|
||||||
String sval_dtw = formatValue(bv_dtw, *commonData).svalue;
|
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().setCursor(160-w, 257);
|
||||||
getdisplay().print(sval_dtw);
|
getdisplay().print(sval_dtw);
|
||||||
|
|
||||||
GwApi::BoatValue *bv_btw = pageData.values[3]; // BTW
|
GwApi::BoatValue *bv_btw = pageData.values[3]; // BTW
|
||||||
String sval_btw = formatValue(bv_btw, *commonData).svalue;
|
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().setCursor(360-w, 257);
|
||||||
getdisplay().print(sval_btw);
|
getdisplay().print(sval_btw);
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ class PageXTETrack : public Page
|
|||||||
}
|
}
|
||||||
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold10pt8b);
|
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.
|
// TODO if text don't fix use smaller font size.
|
||||||
// if smallest size does not fit use 2 lines
|
// if smallest size does not fit use 2 lines
|
||||||
// last resort: clip with ellipsis
|
// last resort: clip with ellipsis
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class Page{
|
|||||||
virtual void displayNew(PageData &pageData){}
|
virtual void displayNew(PageData &pageData){}
|
||||||
virtual void leavePage(PageData &pageData){}
|
virtual void leavePage(PageData &pageData){}
|
||||||
virtual void setupKeys() {
|
virtual void setupKeys() {
|
||||||
#ifdef HARDWARE_V21
|
#ifdef BOARD_OBP60S3
|
||||||
commonData->keydata[0].label = "";
|
commonData->keydata[0].label = "";
|
||||||
commonData->keydata[1].label = "";
|
commonData->keydata[1].label = "";
|
||||||
commonData->keydata[2].label = "#LEFT";
|
commonData->keydata[2].label = "#LEFT";
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -230,6 +230,17 @@
|
|||||||
"obp40": "true"
|
"obp40": "true"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "smoothCharts",
|
||||||
|
"label": "Smooth chart lines",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "false",
|
||||||
|
"description": "Print chart lines with a smoothed gradient",
|
||||||
|
"category": "OBP40 Settings",
|
||||||
|
"capabilities": {
|
||||||
|
"obp40": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "lengthFormat",
|
"name": "lengthFormat",
|
||||||
"label": "Length Format",
|
"label": "Length Format",
|
||||||
@@ -678,7 +689,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "text1",
|
"default": "text1",
|
||||||
"description": "Button name",
|
"description": "Button name",
|
||||||
"category": "OBP60 IO-Modul1",
|
"category": "OBP40 IO-Module1",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"obp40":"true"
|
"obp40":"true"
|
||||||
}
|
}
|
||||||
@@ -689,7 +700,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "text2",
|
"default": "text2",
|
||||||
"description": "Button name",
|
"description": "Button name",
|
||||||
"category": "OBP60 IO-Modul1",
|
"category": "OBP40 IO-Module1",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"obp40":"true"
|
"obp40":"true"
|
||||||
}
|
}
|
||||||
@@ -700,7 +711,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "text3",
|
"default": "text3",
|
||||||
"description": "Button name",
|
"description": "Button name",
|
||||||
"category": "OBP60 IO-Modul1",
|
"category": "OBP40 IO-Module1",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"obp40":"true"
|
"obp40":"true"
|
||||||
}
|
}
|
||||||
@@ -711,7 +722,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "text4",
|
"default": "text4",
|
||||||
"description": "Button name",
|
"description": "Button name",
|
||||||
"category": "OBP60 IO-Modul1",
|
"category": "OBP40 IO-Module1",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"obp40":"true"
|
"obp40":"true"
|
||||||
}
|
}
|
||||||
@@ -722,7 +733,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "text5",
|
"default": "text5",
|
||||||
"description": "Button name",
|
"description": "Button name",
|
||||||
"category": "OBP60 IO-Modul1",
|
"category": "OBP40 IO-Module1",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"obp40":"true"
|
"obp40":"true"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,6 +230,17 @@
|
|||||||
"obp60": "true"
|
"obp60": "true"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "smoothCharts",
|
||||||
|
"label": "Smooth chart lines",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "false",
|
||||||
|
"description": "Print chart lines with a smoothed gradient",
|
||||||
|
"category": "OBP60 Settings",
|
||||||
|
"capabilities": {
|
||||||
|
"obp40": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "lengthFormat",
|
"name": "lengthFormat",
|
||||||
"label": "Length Format",
|
"label": "Length Format",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,4 +3,7 @@ Debugging tool
|
|||||||
|
|
||||||
log.txt = text file with error messages from terminal console
|
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
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
// Arduino Moving Average Library
|
|
||||||
// https://github.com/JChristensen/movingAvg
|
|
||||||
// Copyright (C) 2018 by Jack Christensen and licensed under
|
|
||||||
// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
|
|
||||||
|
|
||||||
#include <movingAvg.h>
|
|
||||||
|
|
||||||
// initialize - allocate the interval array
|
|
||||||
void movingAvg::begin()
|
|
||||||
{
|
|
||||||
m_readings = new int[m_interval];
|
|
||||||
}
|
|
||||||
|
|
||||||
// add a new reading and return the new moving average
|
|
||||||
int movingAvg::reading(int newReading)
|
|
||||||
{
|
|
||||||
// add each new data point to the sum until the m_readings array is filled
|
|
||||||
if (m_nbrReadings < m_interval) {
|
|
||||||
++m_nbrReadings;
|
|
||||||
m_sum += newReading;
|
|
||||||
}
|
|
||||||
// once the array is filled, subtract the oldest data point and add the new one
|
|
||||||
else {
|
|
||||||
m_sum = m_sum - m_readings[m_next] + newReading;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_readings[m_next] = newReading;
|
|
||||||
if (++m_next >= m_interval) m_next = 0;
|
|
||||||
return (m_sum + m_nbrReadings / 2) / m_nbrReadings;
|
|
||||||
}
|
|
||||||
|
|
||||||
// just return the current moving average
|
|
||||||
int movingAvg::getAvg()
|
|
||||||
{
|
|
||||||
return (m_sum + m_nbrReadings / 2) / m_nbrReadings;
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the average for a subset of the data, the most recent nPoints readings.
|
|
||||||
// for invalid values of nPoints, return zero.
|
|
||||||
int movingAvg::getAvg(int nPoints)
|
|
||||||
{
|
|
||||||
if (nPoints < 1 || nPoints > m_interval || nPoints > m_nbrReadings) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
long sum{0};
|
|
||||||
int i = m_next;
|
|
||||||
for (int n=0; n<nPoints; ++n) {
|
|
||||||
if (i == 0) {
|
|
||||||
i = m_interval - 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
--i;
|
|
||||||
}
|
|
||||||
sum += m_readings[i];
|
|
||||||
}
|
|
||||||
return (sum + nPoints / 2) / nPoints;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// start the moving average over again
|
|
||||||
void movingAvg::reset()
|
|
||||||
{
|
|
||||||
m_nbrReadings = 0;
|
|
||||||
m_sum = 0;
|
|
||||||
m_next = 0;
|
|
||||||
}
|
|
||||||
@@ -3,27 +3,37 @@
|
|||||||
// Copyright (C) 2018 by Jack Christensen and licensed under
|
// Copyright (C) 2018 by Jack Christensen and licensed under
|
||||||
// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
|
// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
|
||||||
|
|
||||||
|
// Extended to template class for handling of multiple data types
|
||||||
|
|
||||||
#ifndef MOVINGAVG_H_INCLUDED
|
#ifndef MOVINGAVG_H_INCLUDED
|
||||||
#define MOVINGAVG_H_INCLUDED
|
#define MOVINGAVG_H_INCLUDED
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
class movingAvg
|
class movingAvg
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
movingAvg(int interval)
|
movingAvg(int interval)
|
||||||
: m_interval{interval}, m_nbrReadings{0}, m_sum{0}, m_next{0} {}
|
: m_interval{interval}, m_nbrReadings{0}, m_sum{0}, m_next{0}, m_readings{nullptr} {}
|
||||||
|
~movingAvg() { delete[] m_readings; }
|
||||||
void begin();
|
void begin();
|
||||||
int reading(int newReading);
|
T reading(T newReading);
|
||||||
int getAvg();
|
T getAvg();
|
||||||
int getAvg(int nPoints);
|
T getAvg(int nPoints);
|
||||||
int getCount() {return m_nbrReadings;}
|
int getCount() { return m_nbrReadings; }
|
||||||
void reset();
|
void reset();
|
||||||
int* getReadings() {return m_readings;}
|
T* getReadings() { return m_readings; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_interval; // number of data points for the moving average
|
int m_interval; // number of data points for the moving average
|
||||||
int m_nbrReadings; // number of readings
|
int m_nbrReadings; // number of readings
|
||||||
long m_sum; // sum of the m_readings array
|
// Sum type adapts to T: long for integers, T for floating point
|
||||||
|
using SumType = typename std::conditional<std::is_floating_point<T>::value, T, long>::type;
|
||||||
|
SumType m_sum;
|
||||||
int m_next; // index to the next reading
|
int m_next; // index to the next reading
|
||||||
int* m_readings; // pointer to the dynamically allocated interval array
|
T* m_readings; // pointer to the dynamically allocated interval array
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Include the implementation to satisfy template instantiation requirements
|
||||||
|
#include "movingAvg.tpp"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// Arduino Moving Average Library
|
||||||
|
// https://github.com/JChristensen/movingAvg
|
||||||
|
// Copyright (C) 2018 by Jack Christensen and licensed under
|
||||||
|
// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
|
||||||
|
|
||||||
|
// Extended to template class for handling of multiple data types
|
||||||
|
|
||||||
|
//template <typename T>
|
||||||
|
//movingAvg<T>::movingAvg(int interval)
|
||||||
|
// : m_interval{interval}, m_nbrReadings{0}, m_sum{0}, m_next{0}, m_readings{nullptr}
|
||||||
|
//{}
|
||||||
|
|
||||||
|
// initialize - allocate the interval array
|
||||||
|
template <typename T>
|
||||||
|
void movingAvg<T>::begin()
|
||||||
|
{
|
||||||
|
m_readings = new T[m_interval];
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a new reading and return the new moving average
|
||||||
|
template <typename T>
|
||||||
|
T movingAvg<T>::reading(T newReading)
|
||||||
|
{
|
||||||
|
// add each new data point to the sum until the m_readings array is filled
|
||||||
|
if (m_nbrReadings < m_interval) {
|
||||||
|
++m_nbrReadings;
|
||||||
|
m_sum += newReading;
|
||||||
|
}
|
||||||
|
// once the array is filled, subtract the oldest data point and add the new one
|
||||||
|
else {
|
||||||
|
m_sum = m_sum - m_readings[m_next] + newReading;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_readings[m_next] = newReading;
|
||||||
|
if (++m_next >= m_interval) m_next = 0;
|
||||||
|
return getAvg();
|
||||||
|
}
|
||||||
|
|
||||||
|
// just return the current moving average
|
||||||
|
template <typename T>
|
||||||
|
T movingAvg<T>::getAvg()
|
||||||
|
{
|
||||||
|
if (m_nbrReadings == 0) return 0;
|
||||||
|
|
||||||
|
// Apply rounding for integers only
|
||||||
|
if (std::is_floating_point<T>::value) {
|
||||||
|
return m_sum / m_nbrReadings;
|
||||||
|
} else {
|
||||||
|
return (m_sum + (SumType)m_nbrReadings / 2) / m_nbrReadings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the average for a subset of the data, the most recent nPoints readings.
|
||||||
|
// for invalid values of nPoints, return zero.
|
||||||
|
template <typename T>
|
||||||
|
T movingAvg<T>::getAvg(int nPoints)
|
||||||
|
{
|
||||||
|
if (nPoints < 1 || nPoints > m_interval || nPoints > m_nbrReadings) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SumType sum{0};
|
||||||
|
int i = m_next;
|
||||||
|
for (int n=0; n<nPoints; ++n) {
|
||||||
|
if (i == 0) {
|
||||||
|
i = m_interval - 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
sum += m_readings[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::is_floating_point<T>::value) {
|
||||||
|
return sum / nPoints;
|
||||||
|
} else {
|
||||||
|
return (sum + (SumType)nPoints / 2) / nPoints; //
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start the moving average over again
|
||||||
|
template <typename T>
|
||||||
|
void movingAvg<T>::reset()
|
||||||
|
{
|
||||||
|
m_nbrReadings = 0;
|
||||||
|
m_sum = 0;
|
||||||
|
m_next = 0;
|
||||||
|
}
|
||||||
+70
-37
@@ -9,7 +9,6 @@
|
|||||||
#include <NMEA0183.h> // NMEA0183
|
#include <NMEA0183.h> // NMEA0183
|
||||||
#include <NMEA0183Msg.h>
|
#include <NMEA0183Msg.h>
|
||||||
#include <NMEA0183Messages.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 "OBP60Extensions.h" // Functions lib for extension board
|
||||||
#include "OBP60Keypad.h" // Functions for keypad
|
#include "OBP60Keypad.h" // Functions for keypad
|
||||||
#include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation
|
#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().setFont(&Ubuntu_Bold8pt8b);
|
||||||
getdisplay().setCursor(65, 175);
|
getdisplay().setCursor(65, 175);
|
||||||
getdisplay().print("Charge battery and restart system");
|
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
|
getdisplay().powerOff(); // Display power off
|
||||||
|
#endif
|
||||||
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
|
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
|
||||||
setPortPin(OBP_POWER_SD, false); // Power off SD card
|
setPortPin(OBP_POWER_SD, false); // Power off SD card
|
||||||
#else
|
#else
|
||||||
@@ -295,7 +298,7 @@ void underVoltageError(CommonData &common) {
|
|||||||
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
|
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
|
||||||
setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off
|
setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off
|
||||||
// Shutdown EInk display
|
// 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().fillScreen(common.bgcolor);// Clear screen
|
||||||
getdisplay().setTextColor(common.fgcolor);
|
getdisplay().setTextColor(common.fgcolor);
|
||||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||||
@@ -304,8 +307,12 @@ void underVoltageError(CommonData &common) {
|
|||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
getdisplay().setCursor(65, 175);
|
getdisplay().setCursor(65, 175);
|
||||||
getdisplay().print("To wake up repower system");
|
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
|
getdisplay().powerOff(); // Display power off
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
while (true) {
|
while (true) {
|
||||||
esp_deep_sleep_start(); // Deep Sleep without wakeup. Wakeup only after power cycle (restart).
|
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
|
// Read supply voltage
|
||||||
#if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
|
#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 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
|
#else
|
||||||
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60
|
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60
|
||||||
float minVoltage = MIN_VOLTAGE;
|
float minVoltage = MIN_VOLTAGE;
|
||||||
@@ -325,6 +332,7 @@ inline bool underVoltageDetection(float voffset, float vslope) {
|
|||||||
return (calVoltage < minVoltage);
|
return (calVoltage < minVoltage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// OBP60 Task
|
// OBP60 Task
|
||||||
//####################################################################################
|
//####################################################################################
|
||||||
void OBP60Task(GwApi *api){
|
void OBP60Task(GwApi *api){
|
||||||
@@ -332,7 +340,7 @@ void OBP60Task(GwApi *api){
|
|||||||
// return;
|
// return;
|
||||||
GwLog *logger=api->getLogger();
|
GwLog *logger=api->getLogger();
|
||||||
GwConfigHandler *config=api->getConfig();
|
GwConfigHandler *config=api->getConfig();
|
||||||
#if defined HARDWARE_V20 || HARDWARE_V21
|
#ifdef BOARD_OBP60S3
|
||||||
startLedTask(api);
|
startLedTask(api);
|
||||||
#endif
|
#endif
|
||||||
PageList allPages;
|
PageList allPages;
|
||||||
@@ -341,7 +349,7 @@ void OBP60Task(GwApi *api){
|
|||||||
commonData.logger=logger;
|
commonData.logger=logger;
|
||||||
commonData.config=config;
|
commonData.config=config;
|
||||||
|
|
||||||
#if defined HARDWARE_V20 || HARDWARE_V21
|
#ifdef BOARD_OBP60S3
|
||||||
// Keyboard coordinates for page footer
|
// Keyboard coordinates for page footer
|
||||||
initKeys(commonData);
|
initKeys(commonData);
|
||||||
#endif
|
#endif
|
||||||
@@ -375,36 +383,47 @@ void OBP60Task(GwApi *api){
|
|||||||
|
|
||||||
#ifdef DISPLAY_GDEY042T81
|
#ifdef DISPLAY_GDEY042T81
|
||||||
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
|
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
|
#else
|
||||||
getdisplay().init(115200); // Init for normal displays
|
getdisplay().init(115200); // Init for normal displays
|
||||||
#endif
|
#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().setRotation(0); // Set display orientation (horizontal)
|
||||||
getdisplay().setFullWindow(); // Set full Refresh
|
#endif
|
||||||
getdisplay().firstPage(); // set first page
|
displaySetFullWindow(); // Set full Refresh (E-Ink only)
|
||||||
|
displayFirstPage(); // set first page
|
||||||
getdisplay().fillScreen(commonData.bgcolor);
|
getdisplay().fillScreen(commonData.bgcolor);
|
||||||
getdisplay().setTextColor(commonData.fgcolor);
|
getdisplay().setTextColor(commonData.fgcolor);
|
||||||
getdisplay().nextPage(); // Full Refresh
|
displayNextPage(); // Full Refresh
|
||||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
displaySetPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update (E-Ink only)
|
||||||
getdisplay().fillScreen(commonData.bgcolor);
|
getdisplay().fillScreen(commonData.bgcolor);
|
||||||
getdisplay().nextPage(); // Fast Refresh
|
displayNextPage(); // Fast Refresh
|
||||||
getdisplay().nextPage(); // Fast Refresh
|
displayNextPage(); // Fast Refresh
|
||||||
if(String(displaymode) == "Logo + QR Code" || String(displaymode) == "Logo"){
|
if(String(displaymode) == "Logo + QR Code" || String(displaymode) == "Logo"){
|
||||||
getdisplay().fillScreen(commonData.bgcolor);
|
getdisplay().fillScreen(commonData.bgcolor);
|
||||||
getdisplay().drawBitmap(0, 0, gImage_Logo_OBP_400x300_sw, getdisplay().width(), getdisplay().height(), commonData.fgcolor); // Draw start logo
|
// draw the fixed-size logo via generic display wrapper
|
||||||
getdisplay().nextPage(); // Fast Refresh
|
displayDrawBitmap(0, 0, gImage_Logo_OBP_400x300_sw,
|
||||||
getdisplay().nextPage(); // Fast Refresh
|
400, 300, commonData.fgcolor);
|
||||||
|
displayNextPage(); // Fast Refresh
|
||||||
|
displayNextPage(); // Fast Refresh
|
||||||
delay(SHOW_TIME); // Logo show time
|
delay(SHOW_TIME); // Logo show time
|
||||||
if(String(displaymode) == "Logo + QR Code"){
|
if(String(displaymode) == "Logo + QR Code"){
|
||||||
getdisplay().fillScreen(commonData.bgcolor);
|
getdisplay().fillScreen(commonData.bgcolor);
|
||||||
qrWiFi(systemname, wifipass, commonData.fgcolor, commonData.bgcolor); // Show QR code for WiFi connection
|
qrWiFi(systemname, wifipass, commonData.fgcolor, commonData.bgcolor); // Show QR code for WiFi connection
|
||||||
getdisplay().nextPage(); // Fast Refresh
|
displayNextPage(); // Fast Refresh
|
||||||
getdisplay().nextPage(); // Fast Refresh
|
displayNextPage(); // Fast Refresh
|
||||||
delay(SHOW_TIME); // QR code show time
|
delay(SHOW_TIME); // QR code show time
|
||||||
}
|
}
|
||||||
getdisplay().fillScreen(commonData.bgcolor);
|
getdisplay().fillScreen(commonData.bgcolor);
|
||||||
getdisplay().nextPage(); // Fast Refresh
|
displayNextPage(); // Fast Refresh
|
||||||
getdisplay().nextPage(); // Fast Refresh
|
displayNextPage(); // Fast Refresh
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init pages
|
// Init pages
|
||||||
@@ -479,7 +498,8 @@ void OBP60Task(GwApi *api){
|
|||||||
pages[i].parameters.values.push_back(value);
|
pages[i].parameters.values.push_back(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the specified boat data types of relevant pages and create a history buffer for each type
|
// Read the specified boat data types of relevant pages and create a history buffer for each type for later use in charts
|
||||||
|
// applies only for pages that uses charts
|
||||||
if (pages[i].parameters.pageName == "OneValue" || pages[i].parameters.pageName == "TwoValues" || pages[i].parameters.pageName == "WindPlot") {
|
if (pages[i].parameters.pageName == "OneValue" || pages[i].parameters.pageName == "TwoValues" || pages[i].parameters.pageName == "WindPlot") {
|
||||||
for (auto pVal : pages[i].parameters.values) {
|
for (auto pVal : pages[i].parameters.values) {
|
||||||
hstryBufferList.addBuffer(pVal->getName());
|
hstryBufferList.addBuffer(pVal->getName());
|
||||||
@@ -722,9 +742,12 @@ void OBP60Task(GwApi *api){
|
|||||||
if(millis() > starttime4 + 8000 && delayedDisplayUpdate == true){
|
if(millis() > starttime4 + 8000 && delayedDisplayUpdate == true){
|
||||||
starttime1 = millis();
|
starttime1 = millis();
|
||||||
starttime2 = 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"){
|
if(fastrefresh == "true"){
|
||||||
getdisplay().nextPage(); // Full update
|
displayNextPage(); // Full update
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
||||||
@@ -734,13 +757,14 @@ void OBP60Task(GwApi *api){
|
|||||||
#else
|
#else
|
||||||
getdisplay().init(115200); // Init for normal displays
|
getdisplay().init(115200); // Init for normal displays
|
||||||
#endif
|
#endif
|
||||||
getdisplay().firstPage(); // Full update
|
displayFirstPage(); // Full update
|
||||||
getdisplay().nextPage(); // Full update
|
displayNextPage(); // Full update
|
||||||
// getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
// getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
// getdisplay().fillScreen(commonData.bgcolor); // Clear display
|
// getdisplay().fillScreen(commonData.bgcolor); // Clear display
|
||||||
// getdisplay().nextPage(); // Partial update
|
// getdisplay().nextPage(); // Partial update
|
||||||
// getdisplay().nextPage(); // Partial update
|
// getdisplay().nextPage(); // Partial update
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
delayedDisplayUpdate = false;
|
delayedDisplayUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,9 +774,12 @@ void OBP60Task(GwApi *api){
|
|||||||
starttime1 = millis();
|
starttime1 = millis();
|
||||||
starttime2 = millis();
|
starttime2 = millis();
|
||||||
LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh first 5 min");
|
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"){
|
if(fastrefresh == "true"){
|
||||||
getdisplay().nextPage(); // Full update
|
displayNextPage(); // Full update
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
||||||
@@ -762,19 +789,23 @@ void OBP60Task(GwApi *api){
|
|||||||
#else
|
#else
|
||||||
getdisplay().init(115200); // Init for normal displays
|
getdisplay().init(115200); // Init for normal displays
|
||||||
#endif
|
#endif
|
||||||
getdisplay().firstPage(); // Full update
|
displayFirstPage(); // Full update
|
||||||
getdisplay().nextPage(); // Full update
|
displayNextPage(); // Full update
|
||||||
// getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
// getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
// getdisplay().fillScreen(commonData.bgcolor); // Clear display
|
// getdisplay().fillScreen(commonData.bgcolor); // Clear display
|
||||||
// getdisplay().nextPage(); // Partial update
|
// getdisplay().nextPage(); // Partial update
|
||||||
// getdisplay().nextPage(); // Partial update
|
// getdisplay().nextPage(); // Partial update
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subtask E-Ink full refresh
|
// Subtask E-Ink full refresh
|
||||||
if(millis() > starttime2 + fullrefreshtime * 60 * 1000){
|
if(millis() > starttime2 + fullrefreshtime * 60 * 1000){
|
||||||
starttime2 = millis();
|
starttime2 = millis();
|
||||||
LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh");
|
LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh");
|
||||||
|
#ifdef TFT_DISPLAY
|
||||||
|
// TFT LCD: no special refresh
|
||||||
|
#else
|
||||||
getdisplay().setFullWindow(); // Set full update
|
getdisplay().setFullWindow(); // Set full update
|
||||||
if(fastrefresh == "true"){
|
if(fastrefresh == "true"){
|
||||||
getdisplay().nextPage(); // Full update
|
getdisplay().nextPage(); // Full update
|
||||||
@@ -794,6 +825,7 @@ void OBP60Task(GwApi *api){
|
|||||||
// getdisplay().nextPage(); // Partial update
|
// getdisplay().nextPage(); // Partial update
|
||||||
// getdisplay().nextPage(); // Partial update
|
// getdisplay().nextPage(); // Partial update
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh display data, default all 1s
|
// Refresh display data, default all 1s
|
||||||
@@ -806,16 +838,15 @@ void OBP60Task(GwApi *api){
|
|||||||
if(millis() > starttime3 + pagetime){
|
if(millis() > starttime3 + pagetime){
|
||||||
LOG_DEBUG(GwLog::DEBUG,"Page with refreshtime=%d", pagetime);
|
LOG_DEBUG(GwLog::DEBUG,"Page with refreshtime=%d", pagetime);
|
||||||
starttime3 = millis();
|
starttime3 = millis();
|
||||||
|
bool pageChanged = (lastPage != pageNumber);
|
||||||
|
|
||||||
//refresh data from api
|
//refresh data from api
|
||||||
api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues);
|
api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues);
|
||||||
api->getStatus(commonData.status);
|
api->getStatus(commonData.status);
|
||||||
|
|
||||||
if (calcTrueWnds) {
|
trueWind.handleWinds(calcTrueWnds); // calculate true wind data from apparent wind values
|
||||||
trueWind.addWinds(); // calculate true wind data from apparent wind values
|
|
||||||
}
|
|
||||||
calibrationDataList.handleCalibration(&boatValues); // Process calibration for all boat data in <calibrationDataList>
|
calibrationDataList.handleCalibration(&boatValues); // Process calibration for all boat data in <calibrationDataList>
|
||||||
hstryBufferList.handleHstryBufs(useSimuData, commonData); // Handle history buffers for certain boat data for windplot page and other usage
|
hstryBufferList.handleHstryBufs(useSimuData, commonData); // Handle history buffers for certain boat data for charts and other usage
|
||||||
|
|
||||||
// Clear display
|
// Clear display
|
||||||
// getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);
|
// getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);
|
||||||
@@ -842,16 +873,16 @@ void OBP60Task(GwApi *api){
|
|||||||
if (currentPage == NULL){
|
if (currentPage == NULL){
|
||||||
LOG_DEBUG(GwLog::ERROR,"page number %d not found", pageNumber);
|
LOG_DEBUG(GwLog::ERROR,"page number %d not found", pageNumber);
|
||||||
// Error handling for missing page
|
// 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().fillScreen(commonData.bgcolor); // Clear display
|
||||||
getdisplay().drawXBitmap(200 - unknown_width / 2, 150 - unknown_height / 2, unknown_bits, unknown_width, unknown_height, commonData.fgcolor);
|
getdisplay().drawXBitmap(200 - unknown_width / 2, 150 - unknown_height / 2, unknown_bits, unknown_width, unknown_height, commonData.fgcolor);
|
||||||
getdisplay().setCursor(140, 250);
|
getdisplay().setCursor(140, 250);
|
||||||
getdisplay().setFont(&Atari16px);
|
getdisplay().setFont(&Atari16px);
|
||||||
getdisplay().print("Here be dragons!");
|
getdisplay().print("Here be dragons!");
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
displayNextPage(); // Partial update (fast)
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (lastPage != pageNumber){
|
if (pageChanged){
|
||||||
if (lastPage != -1){ // skip cleanup if we are during startup, and no page has been displayed yet.
|
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
|
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
|
if (hasFRAM) fram.write(FRAM_PAGE_NO, pageNumber); // remember new page for device restart
|
||||||
@@ -871,10 +902,12 @@ void OBP60Task(GwApi *api){
|
|||||||
displayAlarm(commonData);
|
displayAlarm(commonData);
|
||||||
}
|
}
|
||||||
if (ret & PAGE_UPDATE) {
|
if (ret & PAGE_UPDATE) {
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
displayNextPage(); // Partial update (fast)
|
||||||
}
|
}
|
||||||
if (ret & PAGE_HIBERNATE) {
|
if (ret & PAGE_HIBERNATE) {
|
||||||
|
#ifndef TFT_DISPLAY
|
||||||
getdisplay().hibernate();
|
getdisplay().hibernate();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//we only compile for some boards
|
//we only compile for some boards
|
||||||
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||||
#define USBSerial Serial
|
#define USBSerial Serial
|
||||||
#ifdef HARDWARE_V21
|
#ifdef BOARD_OBP60S3
|
||||||
// CAN NMEA2000
|
// CAN NMEA2000
|
||||||
#define ESP32_CAN_TX_PIN 46
|
#define ESP32_CAN_TX_PIN 46
|
||||||
#define ESP32_CAN_RX_PIN 3
|
#define ESP32_CAN_RX_PIN 3
|
||||||
@@ -35,13 +35,19 @@
|
|||||||
// OBP60 Task
|
// OBP60 Task
|
||||||
void OBP60Task(GwApi *param);
|
void OBP60Task(GwApi *param);
|
||||||
DECLARE_USERTASK_PARAM(OBP60Task, 35000); // Need 35k RAM as stack size
|
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);
|
DECLARE_CAPABILITY(obp60,true);
|
||||||
#endif
|
#endif
|
||||||
#ifdef BOARD_OBP40S3
|
#ifdef BOARD_OBP40S3
|
||||||
DECLARE_CAPABILITY(obp40,true)
|
DECLARE_CAPABILITY(obp40,true)
|
||||||
#endif
|
#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
|
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp60-v2-docu.readthedocs.io/en/latest/"); // Link to help pages
|
||||||
#endif
|
#endif
|
||||||
#ifdef BOARD_OBP40S3
|
#ifdef BOARD_OBP40S3
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ index 88f9690..9663a65 100644
|
|||||||
virtual ~GwApi(){}
|
virtual ~GwApi(){}
|
||||||
};
|
};
|
||||||
diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h
|
diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h
|
||||||
index 604c356..2fe4496 100644
|
index 9e2bc99..34eaeac 100644
|
||||||
--- a/lib/obp60task/OBP60Extensions.h
|
--- a/lib/obp60task/OBP60Extensions.h
|
||||||
+++ b/lib/obp60task/OBP60Extensions.h
|
+++ b/lib/obp60task/OBP60Extensions.h
|
||||||
@@ -15,6 +15,9 @@
|
@@ -29,6 +29,9 @@
|
||||||
#define MOUNT_POINT "/sdcard"
|
#define MOUNT_POINT "/sdcard"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
+// Patches to apply to gateway code
|
+// Patches to apply to gateway code
|
||||||
@@ -61,7 +61,7 @@ index 1b007f8..90087d4 100644
|
|||||||
\ No newline at end of file
|
\ No newline at end of file
|
||||||
+}
|
+}
|
||||||
diff --git a/src/main.cpp b/src/main.cpp
|
diff --git a/src/main.cpp b/src/main.cpp
|
||||||
index 44c715f..fdb0366 100644
|
index 95d5d1e..01fcd32 100644
|
||||||
--- a/src/main.cpp
|
--- a/src/main.cpp
|
||||||
+++ b/src/main.cpp
|
+++ b/src/main.cpp
|
||||||
@@ -100,6 +100,7 @@ GwLog logger(LOGLEVEL,NULL);
|
@@ -100,6 +100,7 @@ GwLog logger(LOGLEVEL,NULL);
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
diff --git a/lib/boatData/GwBoatData.cpp b/lib/boatData/GwBoatData.cpp
|
||||||
|
index 424ebe4..b717097 100644
|
||||||
|
--- a/lib/boatData/GwBoatData.cpp
|
||||||
|
+++ b/lib/boatData/GwBoatData.cpp
|
||||||
|
@@ -6,6 +6,7 @@
|
||||||
|
#define GWTYPE_UINT32 2
|
||||||
|
#define GWTYPE_UINT16 3
|
||||||
|
#define GWTYPE_INT16 4
|
||||||
|
+#define GWTYPE_STRING 5
|
||||||
|
#define GWTYPE_USER 100
|
||||||
|
|
||||||
|
class GwBoatItemTypes
|
||||||
|
@@ -15,6 +16,7 @@ public:
|
||||||
|
static int getType(const uint16_t &x) { return GWTYPE_UINT16; }
|
||||||
|
static int getType(const int16_t &x) { return GWTYPE_INT16; }
|
||||||
|
static int getType(const double &x) { return GWTYPE_DOUBLE; }
|
||||||
|
+ static int getType(const String &x) { return GWTYPE_STRING; }
|
||||||
|
static int getType(const GwSatInfoList &x) { return GWTYPE_USER + 1; }
|
||||||
|
};
|
||||||
|
|
||||||
|
@@ -252,6 +254,10 @@ static void writeToString(GwTextWriter *writer, const int16_t &value)
|
||||||
|
{
|
||||||
|
writer->writeInteger(value);
|
||||||
|
}
|
||||||
|
+static void writeToString(GwTextWriter *writer, String value)
|
||||||
|
+{
|
||||||
|
+ writer->writeString(value.c_str());
|
||||||
|
+}
|
||||||
|
static void writeToString(GwTextWriter *writer, GwSatInfoList &value)
|
||||||
|
{
|
||||||
|
writer->writeInteger(value.getNumSats());
|
||||||
|
@@ -288,6 +294,8 @@ template class GwBoatItem<double>;
|
||||||
|
template class GwBoatItem<uint32_t>;
|
||||||
|
template class GwBoatItem<uint16_t>;
|
||||||
|
template class GwBoatItem<int16_t>;
|
||||||
|
+template class GwBoatItem<String>;
|
||||||
|
+
|
||||||
|
void GwSatInfoList::houseKeeping(unsigned long ts)
|
||||||
|
{
|
||||||
|
if (ts == 0)
|
||||||
|
diff --git a/lib/boatData/GwBoatData.h b/lib/boatData/GwBoatData.h
|
||||||
|
index ae50b3f..5b32f70 100644
|
||||||
|
--- a/lib/boatData/GwBoatData.h
|
||||||
|
+++ b/lib/boatData/GwBoatData.h
|
||||||
|
@@ -58,6 +58,7 @@ class GwBoatItemBase{
|
||||||
|
GWSC(formatRot);
|
||||||
|
GWSC(formatDate);
|
||||||
|
GWSC(formatTime);
|
||||||
|
+ GWSC(formatName);
|
||||||
|
protected:
|
||||||
|
int type;
|
||||||
|
unsigned long lastSet=0;
|
||||||
|
@@ -92,6 +93,7 @@ class GwBoatItemBase{
|
||||||
|
virtual int getLastSource(){return lastUpdateSource;}
|
||||||
|
virtual void refresh(unsigned long ts=0){uls(ts);}
|
||||||
|
virtual double getDoubleValue()=0;
|
||||||
|
+ virtual String getStringValue()=0;
|
||||||
|
String getName(){return name;}
|
||||||
|
const String & getFormat() const{return format;}
|
||||||
|
virtual void setInvalidTime(GwConfigHandler *cfg);
|
||||||
|
@@ -120,7 +122,19 @@ template<class T> class GwBoatItem : public GwBoatItemBase{
|
||||||
|
if (! isValid(millis())) return defaultv;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
- virtual double getDoubleValue(){return (double)data;}
|
||||||
|
+ virtual double getDoubleValue(){
|
||||||
|
+ if constexpr (std::is_same<T, String>::value) {
|
||||||
|
+ return 0.0;
|
||||||
|
+ // TODO any better ideas?
|
||||||
|
+ // perhaps return std::numeric_limits<double>::quiet_NaN();
|
||||||
|
+ } else {
|
||||||
|
+ return (double)data;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ virtual String getStringValue(){
|
||||||
|
+ return (String)data;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
virtual void fillString();
|
||||||
|
virtual void toJsonDoc(GwJsonDocument *doc, unsigned long minTime);
|
||||||
|
virtual int getLastSource(){return lastUpdateSource;}
|
||||||
|
@@ -235,6 +249,7 @@ class GwBoatData{
|
||||||
|
GWBOATDATA(double,XTE,formatXte) // cross track error
|
||||||
|
GWBOATDATA(double,WPLat,formatLatitude) // waypoint latitude
|
||||||
|
GWBOATDATA(double,WPLon,formatLongitude) // waypoint longitude
|
||||||
|
+ GWBOATDATA(String,WPName,formatName) // waypoint name
|
||||||
|
GWSPECBOATDATA(GwBoatDataSatList,SatInfo,GwSatInfoList::toType,formatFixed0);
|
||||||
|
public:
|
||||||
|
GwBoatData(GwLog *logger, GwConfigHandler *cfg);
|
||||||
|
diff --git a/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp b/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp
|
||||||
|
index 6989570..238af38 100644
|
||||||
|
--- a/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp
|
||||||
|
+++ b/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp
|
||||||
|
@@ -355,6 +355,7 @@ private:
|
||||||
|
AppendN2kRouteWPInfo(n2kMsg,destinationId,rmb.destID,rmb.latitude,rmb.longitude);
|
||||||
|
send(n2kMsg,msg.sourceId);
|
||||||
|
}
|
||||||
|
+ boatData->WPName->update(String(rmb.destID), msg.sourceId);
|
||||||
|
}
|
||||||
|
void convertRMC(const SNMEA0183Msg &msg)
|
||||||
|
{
|
||||||
|
diff --git a/platformio.ini b/platformio.ini
|
||||||
|
index b738131..e131a6e 100644
|
||||||
|
--- a/platformio.ini
|
||||||
|
+++ b/platformio.ini
|
||||||
|
@@ -68,6 +68,9 @@ lib_ldf_mode = off
|
||||||
|
monitor_speed = 115200
|
||||||
|
build_flags =
|
||||||
|
-D PIO_ENV_BUILD=$PIOENV
|
||||||
|
+ -std=gnu++17
|
||||||
|
+build_unflags =
|
||||||
|
+ -std=gnu++11
|
||||||
|
|
||||||
|
[sensors]
|
||||||
|
; collect the libraries for sensors here
|
||||||
@@ -4,9 +4,66 @@
|
|||||||
#by uncommenting the next line
|
#by uncommenting the next line
|
||||||
|
|
||||||
default_envs =
|
default_envs =
|
||||||
|
obp70_s3
|
||||||
obp60_s3
|
obp60_s3
|
||||||
obp40_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]
|
[env:obp60_s3]
|
||||||
platform = espressif32@6.8.1
|
platform = espressif32@6.8.1
|
||||||
board_build.variants_dir = variants
|
board_build.variants_dir = variants
|
||||||
@@ -26,7 +83,7 @@ lib_deps =
|
|||||||
ESP32time
|
ESP32time
|
||||||
HTTPClient
|
HTTPClient
|
||||||
WiFiClientSecure
|
WiFiClientSecure
|
||||||
esphome/AsyncTCP-esphome@2.0.1
|
esphome/AsyncTCP-esphome@2.1.1
|
||||||
robtillaart/PCF8574@0.3.9
|
robtillaart/PCF8574@0.3.9
|
||||||
adafruit/Adafruit Unified Sensor @ 1.1.13
|
adafruit/Adafruit Unified Sensor @ 1.1.13
|
||||||
blemasle/MCP23017@2.0.0
|
blemasle/MCP23017@2.0.0
|
||||||
@@ -82,7 +139,7 @@ lib_deps =
|
|||||||
ESP32time
|
ESP32time
|
||||||
HTTPClient
|
HTTPClient
|
||||||
WiFiClientSecure
|
WiFiClientSecure
|
||||||
esphome/AsyncTCP-esphome@2.0.1
|
esphome/AsyncTCP-esphome@2.1.1
|
||||||
robtillaart/PCF8574@0.3.9
|
robtillaart/PCF8574@0.3.9
|
||||||
adafruit/Adafruit Unified Sensor @ 1.1.13
|
adafruit/Adafruit Unified Sensor @ 1.1.13
|
||||||
blemasle/MCP23017@2.0.0
|
blemasle/MCP23017@2.0.0
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
Regular → Executable
Executable
+10
@@ -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
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
static inline int64_t gwMonotonicUs(){
|
||||||
|
TickType_t ticks=xTaskGetTickCount();
|
||||||
|
return ((int64_t)ticks) * ((int64_t)portTICK_PERIOD_MS) * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
class TimeAverage{
|
class TimeAverage{
|
||||||
double factor=0.3;
|
double factor=0.3;
|
||||||
double current=0;
|
double current=0;
|
||||||
@@ -70,7 +78,7 @@ class TimeMonitor{
|
|||||||
}
|
}
|
||||||
void reset(){
|
void reset(){
|
||||||
if (last != 0 && start != 0) loop->add(last-start);
|
if (last != 0 && start != 0) loop->add(last-start);
|
||||||
start=esp_timer_get_time();
|
start=gwMonotonicUs();
|
||||||
for (size_t i=0;i<len;i++) current[i]=0;
|
for (size_t i=0;i<len;i++) current[i]=0;
|
||||||
count++;
|
count++;
|
||||||
|
|
||||||
@@ -114,7 +122,7 @@ class TimeMonitor{
|
|||||||
for (size_t i=0;i<index;i++){
|
for (size_t i=0;i<index;i++){
|
||||||
if (current[i] != 0) sv=current[i];
|
if (current[i] != 0) sv=current[i];
|
||||||
}
|
}
|
||||||
int64_t now=esp_timer_get_time();
|
int64_t now=gwMonotonicUs();
|
||||||
last=now;
|
last=now;
|
||||||
current[index]=now;
|
current[index]=now;
|
||||||
int64_t currentv=now-sv;
|
int64_t currentv=now-sv;
|
||||||
|
|||||||
+4
-4
@@ -21,8 +21,8 @@ lib_deps =
|
|||||||
ttlappalainen_NMEA2000=https://github.com/wellenvogel/NMEA2000.git#20251126
|
ttlappalainen_NMEA2000=https://github.com/wellenvogel/NMEA2000.git#20251126
|
||||||
ttlappalainen/NMEA0183 @ 1.10.1
|
ttlappalainen/NMEA0183 @ 1.10.1
|
||||||
ArduinoJson @ 6.18.5
|
ArduinoJson @ 6.18.5
|
||||||
AsyncTCP-esphome @ 2.0.1
|
AsyncTCP-esphome @ 2.1.1
|
||||||
ottowinter/ESPAsyncWebServer-esphome@2.0.1
|
ottowinter/ESPAsyncWebServer-esphome@3.4.0
|
||||||
FS
|
FS
|
||||||
Preferences
|
Preferences
|
||||||
ESPmDNS
|
ESPmDNS
|
||||||
@@ -34,8 +34,8 @@ lib_deps=
|
|||||||
ttlappalainen_NMEA2000=symlink://../NMEA2000
|
ttlappalainen_NMEA2000=symlink://../NMEA2000
|
||||||
ttlappalainen/NMEA0183 @ 1.10.1
|
ttlappalainen/NMEA0183 @ 1.10.1
|
||||||
ArduinoJson @ 6.18.5
|
ArduinoJson @ 6.18.5
|
||||||
AsyncTCP-esphome @ 2.0.1
|
AsyncTCP-esphome @ 2.1.1
|
||||||
ottowinter/ESPAsyncWebServer-esphome@2.0.1
|
ottowinter/ESPAsyncWebServer-esphome@3.4.0
|
||||||
FS
|
FS
|
||||||
Preferences
|
Preferences
|
||||||
ESPmDNS
|
ESPmDNS
|
||||||
|
|||||||
+1
-1
@@ -767,7 +767,7 @@ void loopFunction(void *){
|
|||||||
//if(Serial1.available()) {}
|
//if(Serial1.available()) {}
|
||||||
//if(Serial.available()) {}
|
//if(Serial.available()) {}
|
||||||
//if(Serial2.available()) {}
|
//if(Serial2.available()) {}
|
||||||
//delay(1);
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const String USERPREFIX="/api/user/";
|
const String USERPREFIX="/api/user/";
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#ifndef Pins_Arduino_h
|
||||||
|
#define Pins_Arduino_h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
|
||||||
|
#define USB_VID 0x303a
|
||||||
|
#define USB_PID 0x1001
|
||||||
|
|
||||||
|
/*
|
||||||
|
#define EXTERNAL_NUM_INTERRUPTS 46
|
||||||
|
#define NUM_DIGITAL_PINS 48
|
||||||
|
#define NUM_ANALOG_INPUTS 20
|
||||||
|
|
||||||
|
// Multi Function Display OBP60 V2.0
|
||||||
|
static const uint8_t LED_BUILTIN = SOC_GPIO_PIN_COUNT+48;
|
||||||
|
#define BUILTIN_LED LED_BUILTIN // backward compatibility
|
||||||
|
#define LED_BUILTIN LED_BUILTIN
|
||||||
|
#define RGB_BUILTIN LED_BUILTIN
|
||||||
|
#define RGB_BRIGHTNESS 64
|
||||||
|
|
||||||
|
#define analogInputToDigitalPin(p) (((p)<20)?(analogChannelToDigitalPin(p)):-1)
|
||||||
|
#define digitalPinToInterrupt(p) (((p)<48)?(p):-1)
|
||||||
|
#define digitalPinHasPWM(p) (p < 46)
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const uint8_t TX = 43;
|
||||||
|
static const uint8_t RX = 44;
|
||||||
|
|
||||||
|
static const uint8_t SDA = 47;
|
||||||
|
static const uint8_t SCL = 21;
|
||||||
|
|
||||||
|
static const uint8_t SS = 39;
|
||||||
|
static const uint8_t MOSI = 48;
|
||||||
|
static const uint8_t MISO = 11;
|
||||||
|
static const uint8_t SCK = 38;
|
||||||
|
|
||||||
|
static const uint8_t A0 = 1;
|
||||||
|
static const uint8_t A1 = 2;
|
||||||
|
static const uint8_t A2 = 3;
|
||||||
|
static const uint8_t A3 = 4;
|
||||||
|
static const uint8_t A4 = 5;
|
||||||
|
static const uint8_t A5 = 6;
|
||||||
|
static const uint8_t A6 = 7;
|
||||||
|
static const uint8_t A7 = 8;
|
||||||
|
static const uint8_t A8 = 9;
|
||||||
|
static const uint8_t A9 = 10;
|
||||||
|
static const uint8_t A10 = 11;
|
||||||
|
static const uint8_t A11 = 12;
|
||||||
|
static const uint8_t A12 = 13;
|
||||||
|
static const uint8_t A13 = 14;
|
||||||
|
static const uint8_t A14 = 15;
|
||||||
|
static const uint8_t A15 = 16;
|
||||||
|
static const uint8_t A16 = 17;
|
||||||
|
static const uint8_t A17 = 18;
|
||||||
|
static const uint8_t A18 = 19;
|
||||||
|
static const uint8_t A19 = 20;
|
||||||
|
|
||||||
|
static const uint8_t T1 = 1;
|
||||||
|
static const uint8_t T2 = 2;
|
||||||
|
static const uint8_t T3 = 3;
|
||||||
|
static const uint8_t T4 = 4;
|
||||||
|
static const uint8_t T5 = 5;
|
||||||
|
static const uint8_t T6 = 6;
|
||||||
|
static const uint8_t T7 = 7;
|
||||||
|
static const uint8_t T8 = 8;
|
||||||
|
static const uint8_t T9 = 9;
|
||||||
|
static const uint8_t T10 = 10;
|
||||||
|
static const uint8_t T11 = 11;
|
||||||
|
static const uint8_t T12 = 12;
|
||||||
|
static const uint8_t T13 = 13;
|
||||||
|
static const uint8_t T14 = 14;
|
||||||
|
|
||||||
|
#endif /* Pins_Arduino_h */
|
||||||
Reference in New Issue
Block a user