mirror of
https://github.com/thooge/esp32-nmea2000-obp60.git
synced 2025-12-28 21:23:07 +01:00
Compare commits
143 Commits
7ca0a4d09a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f838194f06 | ||
|
|
2deaf07ea4 | ||
|
|
142f6ca774 | ||
|
|
c6276cdcff | ||
|
|
213812ed14 | ||
|
|
b54acbae42 | ||
|
|
69367b91d7 | ||
|
|
6edf847958 | ||
|
|
fe78fb434b | ||
|
|
fc097b09fe | ||
|
|
a392d88445 | ||
|
|
f08a119f40 | ||
|
|
ae2b7047f5 | ||
|
|
eab7d74aef | ||
|
|
0f50b614eb | ||
|
|
1b55439135 | ||
|
|
625f9c087e | ||
|
|
3fa7ca5e99 | ||
|
|
9935cb54a6 | ||
|
|
b31addf852 | ||
|
|
a9007a6b6f | ||
|
|
0972f12b9e | ||
|
|
f8378c3a2b | ||
|
|
e02ca265ae | ||
|
|
16f9f9217d | ||
|
|
f77107616d | ||
|
|
942ca28ab5 | ||
|
|
a90689228d | ||
|
|
489ee7ed09 | ||
|
|
dd5f05922a | ||
|
|
469a81f87d | ||
|
|
81825370c0 | ||
|
|
bcc24ee99d | ||
|
|
470c0e5f4d | ||
|
|
9a792b49db | ||
| 8f851a4b61 | |||
|
|
f46a43d7fd | ||
| 84e99365f7 | |||
|
|
e5950f95fd | ||
|
|
d0076f336d | ||
|
|
d94c4bbbdb | ||
|
|
6ef7681a40 | ||
|
|
16b8a0dacd | ||
|
|
b3e2dea45b | ||
|
|
34a289048f | ||
|
|
df1bd498ae | ||
|
|
e5eee37b59 | ||
|
|
28b3cfba0b | ||
|
|
674a78b03c | ||
|
|
de448974d9 | ||
|
|
6b91400cfc | ||
|
|
be946440d3 | ||
|
|
ac86bfb304 | ||
|
|
d719c7260e | ||
|
|
1abcb158ec | ||
|
|
00ea413411 | ||
|
|
851149bae6 | ||
|
|
c6c2ad537a | ||
| 3eb2c8093e | |||
|
|
636b1596f5 | ||
|
|
a21ce00260 | ||
|
|
794cbf1c4f | ||
| 4f6079f418 | |||
|
|
a8f3fbb34d | ||
|
|
748867682c | ||
|
|
5b5e003836 | ||
|
|
07200ad701 | ||
|
|
371816f946 | ||
|
|
c8a7f14773 | ||
|
|
4a97768d0b | ||
|
|
e19bd0898d | ||
|
|
d130f7ff78 | ||
|
|
7c14577bbc | ||
|
|
ba94fddb80 | ||
|
|
8faead0a1a | ||
| bc9d139d19 | |||
|
|
a74ce9e553 | ||
| 779f557d47 | |||
|
|
4a273d2c93 | ||
|
|
9be1b864f4 | ||
|
|
bfc4337417 | ||
|
|
398b8e0d02 | ||
| 28a7e58e27 | |||
| eb51092b23 | |||
|
|
fb3af0bf83 | ||
|
|
539500e088 | ||
|
|
229106b04c | ||
|
|
eefa59a7c2 | ||
| 588008e370 | |||
| caf5572459 | |||
|
|
05f8b3ec65 | ||
|
|
351ef5d9fe | ||
|
|
e93193c3e0 | ||
| e367d15568 | |||
|
|
2773685db3 | ||
|
|
9953165dfe | ||
|
|
da451bee70 | ||
|
|
f79124eed3 | ||
|
|
33b5776421 | ||
|
|
2954a9a58b | ||
|
|
e6add8e6fc | ||
|
|
15bcd53350 | ||
|
|
938b566bfc | ||
|
|
fe2223839f | ||
|
|
c888804aef | ||
| d963153b35 | |||
|
|
4934fb1346 | ||
|
|
8150947cd6 | ||
| 1e7368db4d | |||
| 238e0fc79d | |||
|
|
34801d1e0e | ||
| 0afe629b38 | |||
|
|
ccc0d2b6c1 | ||
|
|
791fa10b01 | ||
|
|
c48c6a2e48 | ||
|
|
4110e3f812 | ||
|
|
bb99978177 | ||
|
|
85dee2a622 | ||
|
|
f2e069b768 | ||
|
|
71946248e2 | ||
|
|
91a3ac081f | ||
|
|
59cf52b5d2 | ||
|
|
60193fa3be | ||
|
|
7f954e702d | ||
|
|
72ddeb3cfb | ||
|
|
2729ef9cb6 | ||
|
|
1f90cefbd6 | ||
|
|
9ada5be7cb | ||
|
|
03d8339170 | ||
|
|
73656e7d14 | ||
|
|
bd9741d851 | ||
|
|
13c85adad2 | ||
|
|
9b504469bc | ||
|
|
f0aba89301 | ||
|
|
fe095a9716 | ||
|
|
235188dfb2 | ||
|
|
aa70c34a96 | ||
|
|
62aef176d3 | ||
|
|
9f79a7d4bc | ||
|
|
bf4dff45b4 | ||
|
|
f153d82825 | ||
|
|
da06f3e791 | ||
|
|
7d66ec91da |
@@ -526,3 +526,17 @@ env.Append(
|
|||||||
)
|
)
|
||||||
#script does not run on clean yet - maybe in the future
|
#script does not run on clean yet - maybe in the future
|
||||||
env.AddPostAction("clean",cleangenerated)
|
env.AddPostAction("clean",cleangenerated)
|
||||||
|
|
||||||
|
#look for extra task scripts and include them here
|
||||||
|
for taskdir in userTaskDirs:
|
||||||
|
script = os.path.join(taskdir, "extra_task.py")
|
||||||
|
if os.path.isfile(script):
|
||||||
|
taskname = os.path.basename(os.path.normpath(taskdir))
|
||||||
|
print("#extra task script for '{}'".format(taskname))
|
||||||
|
with open(script) as fh:
|
||||||
|
try:
|
||||||
|
code = compile(fh.read(), taskname, 'exec')
|
||||||
|
except SyntaxError:
|
||||||
|
print("#ERROR: script does not compile")
|
||||||
|
continue
|
||||||
|
exec(code)
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ void CalibrationDataList::readConfig(GwConfigHandler* config, GwLog* logger)
|
|||||||
calibMap[instance].slope = slope;
|
calibMap[instance].slope = slope;
|
||||||
calibMap[instance].smooth = smooth;
|
calibMap[instance].smooth = smooth;
|
||||||
calibMap[instance].isCalibrated = false;
|
calibMap[instance].isCalibrated = false;
|
||||||
LOG_DEBUG(GwLog::LOG, "stored calibration data: %s, offset: %f, slope: %f, smoothing: %f", instance.c_str(),
|
LOG_DEBUG(GwLog::LOG, "calibration data: %s, offset: %f, slope: %f, smoothing: %f", instance.c_str(),
|
||||||
calibMap[instance].offset, calibMap[instance].slope, calibMap[instance].smooth);
|
calibMap[instance].offset, calibMap[instance].slope, calibMap[instance].smooth);
|
||||||
}
|
}
|
||||||
LOG_DEBUG(GwLog::LOG, "all calibration data read");
|
LOG_DEBUG(GwLog::LOG, "all calibration data read");
|
||||||
@@ -117,7 +117,7 @@ void CalibrationDataList::calibrateInstance(GwApi::BoatValue* boatDataValue, GwL
|
|||||||
std::string format = "";
|
std::string format = "";
|
||||||
|
|
||||||
if (calibMap.find(instance) == calibMap.end()) {
|
if (calibMap.find(instance) == calibMap.end()) {
|
||||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not found in calibration data list", instance.c_str());
|
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
||||||
return;
|
return;
|
||||||
} else if (!boatDataValue->valid) { // no valid boat data value, so we don't want to apply calibration data
|
} else if (!boatDataValue->valid) { // no valid boat data value, so we don't want to apply calibration data
|
||||||
calibMap[instance].isCalibrated = false;
|
calibMap[instance].isCalibrated = false;
|
||||||
@@ -173,7 +173,7 @@ void CalibrationDataList::smoothInstance(GwApi::BoatValue* boatDataValue, GwLog*
|
|||||||
if (!boatDataValue->valid) { // no valid boat data value, so we don't want to smoothen value
|
if (!boatDataValue->valid) { // no valid boat data value, so we don't want to smoothen value
|
||||||
return;
|
return;
|
||||||
} else if (calibMap.find(instance) == calibMap.end()) {
|
} else if (calibMap.find(instance) == calibMap.end()) {
|
||||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: smooth factor for %s not found in calibration data list", instance.c_str());
|
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: smooth factor for %s not found in calibration list", instance.c_str());
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
smoothFactor = calibMap[instance].smooth;
|
smoothFactor = calibMap[instance].smooth;
|
||||||
@@ -184,8 +184,6 @@ void CalibrationDataList::smoothInstance(GwApi::BoatValue* boatDataValue, GwLog*
|
|||||||
}
|
}
|
||||||
lastValue[instance] = dataValue; // store the new value for next cycle; first time, store only the current value and return
|
lastValue[instance] = dataValue; // store the new value for next cycle; first time, store only the current value and return
|
||||||
boatDataValue->value = dataValue; // set the smoothed value to the boat data value
|
boatDataValue->value = dataValue; // set the smoothed value to the boat data value
|
||||||
|
|
||||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: Smoothing factor: %f, Smoothed value: %f", instance.c_str(), smoothFactor, dataValue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
#ifndef _BOATDATACALIBRATION_H
|
#ifndef _BOATDATACALIBRATION_H
|
||||||
#define _BOATDATACALIBRATION_H
|
#define _BOATDATACALIBRATION_H
|
||||||
|
|
||||||
#include "Pagedata.h"
|
// #include "Pagedata.h"
|
||||||
|
#include "GwApi.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
|||||||
25
lib/obp60task/Graphics.cpp
Normal file
25
lib/obp60task/Graphics.cpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
Generic graphics functions
|
||||||
|
|
||||||
|
*/
|
||||||
|
#include <math.h>
|
||||||
|
#include "Graphics.h"
|
||||||
|
|
||||||
|
Point rotatePoint(const Point& origin, const Point& p, double angle) {
|
||||||
|
// rotate poind around origin by degrees
|
||||||
|
Point rotated;
|
||||||
|
double phi = angle * M_PI / 180.0;
|
||||||
|
double dx = p.x - origin.x;
|
||||||
|
double dy = p.y - origin.y;
|
||||||
|
rotated.x = origin.x + cos(phi) * dx - sin(phi) * dy;
|
||||||
|
rotated.y = origin.y + sin(phi) * dx + cos(phi) * dy;
|
||||||
|
return rotated;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Point> rotatePoints(const Point& origin, const std::vector<Point>& pts, double angle) {
|
||||||
|
std::vector<Point> rotatedPoints;
|
||||||
|
for (const auto& p : pts) {
|
||||||
|
rotatedPoints.push_back(rotatePoint(origin, p, angle));
|
||||||
|
}
|
||||||
|
return rotatedPoints;
|
||||||
|
}
|
||||||
17
lib/obp60task/Graphics.h
Normal file
17
lib/obp60task/Graphics.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct Point {
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Rect {
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
double w;
|
||||||
|
double h;
|
||||||
|
};
|
||||||
|
|
||||||
|
Point rotatePoint(const Point& origin, const Point& p, double angle);
|
||||||
|
std::vector<Point> rotatePoints(const Point& origin, const std::vector<Point>& pts, double angle);
|
||||||
14
lib/obp60task/ImageDecoder.cpp
Normal file
14
lib/obp60task/ImageDecoder.cpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#include "ImageDecoder.h"
|
||||||
|
#include <mbedtls/base64.h>
|
||||||
|
|
||||||
|
// Decoder for Base64 content
|
||||||
|
bool ImageDecoder::decodeBase64(const String& base64, uint8_t* outBuffer, size_t outSize, size_t& decodedSize) {
|
||||||
|
int ret = mbedtls_base64_decode(
|
||||||
|
outBuffer,
|
||||||
|
outSize,
|
||||||
|
&decodedSize,
|
||||||
|
(const unsigned char*)base64.c_str(),
|
||||||
|
base64.length()
|
||||||
|
);
|
||||||
|
return (ret == 0);
|
||||||
|
}
|
||||||
9
lib/obp60task/ImageDecoder.h
Normal file
9
lib/obp60task/ImageDecoder.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class ImageDecoder {
|
||||||
|
public:
|
||||||
|
bool decodeBase64(const String& base64, uint8_t* outBuffer, size_t outSize, size_t& decodedSize);
|
||||||
|
};
|
||||||
181
lib/obp60task/NetworkClient.cpp
Normal file
181
lib/obp60task/NetworkClient.cpp
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
#include "NetworkClient.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "puff.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
NetworkClient::NetworkClient(size_t reserveSize)
|
||||||
|
: _doc(reserveSize),
|
||||||
|
_valid(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip GZIP Header an goto DEFLATE content
|
||||||
|
int NetworkClient::skipGzipHeader(const uint8_t* data, size_t len) {
|
||||||
|
if (len < 10) return -1;
|
||||||
|
|
||||||
|
if (data[0] != 0x1F || data[1] != 0x8B || data[2] != 8) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pos = 10;
|
||||||
|
uint8_t flags = data[3];
|
||||||
|
|
||||||
|
if (flags & 4) {
|
||||||
|
if (pos + 2 > len) return -1;
|
||||||
|
uint16_t xlen = data[pos] | (data[pos+1] << 8);
|
||||||
|
pos += 2 + xlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & 8) {
|
||||||
|
while (pos < len && data[pos] != 0) pos++;
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & 16) {
|
||||||
|
while (pos < len && data[pos] != 0) pos++;
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & 2) pos += 2;
|
||||||
|
|
||||||
|
if (pos >= len) return -1;
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP GET + GZIP Decompression (reading in chunks)
|
||||||
|
bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& outLen) {
|
||||||
|
|
||||||
|
const size_t capacity = READLIMIT; // Read limit for data (can be adjusted in NetworkClient.h)
|
||||||
|
uint8_t* buffer = (uint8_t*)malloc(capacity);
|
||||||
|
|
||||||
|
if (!buffer) {
|
||||||
|
if (DEBUG) {Serial.println("Malloc failed (buffer");}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPClient http;
|
||||||
|
|
||||||
|
// Timeouts to prevent hanging connections
|
||||||
|
http.setConnectTimeout(CONNECTIONTIMEOUT); // Connect 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.addHeader("Accept-Encoding", "gzip");
|
||||||
|
|
||||||
|
int code = http.GET();
|
||||||
|
if (code != HTTP_CODE_OK) {
|
||||||
|
Serial.printf("HTTP ERROR: %d\n", code);
|
||||||
|
|
||||||
|
// Hard reset HTTP + socket
|
||||||
|
WiFiClient* tmp = http.getStreamPtr();
|
||||||
|
if (tmp) tmp->stop(); // Force close TCP socket
|
||||||
|
http.end();
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WiFiClient* stream = http.getStreamPtr();
|
||||||
|
|
||||||
|
size_t len = 0;
|
||||||
|
uint32_t lastData = millis();
|
||||||
|
const uint32_t READ_TIMEOUT = READDATATIMEOUT; // Timeout for reading data (can be adjusted in NetworkClient.h)
|
||||||
|
|
||||||
|
bool complete = false;
|
||||||
|
|
||||||
|
while (http.connected() && !complete) {
|
||||||
|
|
||||||
|
size_t avail = stream->available();
|
||||||
|
|
||||||
|
if (avail == 0) {
|
||||||
|
if (millis() - lastData > READ_TIMEOUT) {
|
||||||
|
Serial.println("TIMEOUT waiting for data!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
delay(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len + avail > capacity)
|
||||||
|
avail = capacity - len;
|
||||||
|
|
||||||
|
int read = stream->readBytes(buffer + len, avail);
|
||||||
|
len += read;
|
||||||
|
lastData = millis();
|
||||||
|
|
||||||
|
if (DEBUG) {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 (DEBUG) {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 ---
|
||||||
|
if (stream) stream->stop();
|
||||||
|
|
||||||
|
http.end();
|
||||||
|
free(buffer);
|
||||||
|
|
||||||
|
if (!complete) {
|
||||||
|
Serial.println("Failed to complete decompress.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decompress JSON
|
||||||
|
bool NetworkClient::fetchAndDecompressJson(const String& url) {
|
||||||
|
|
||||||
|
_valid = false;
|
||||||
|
|
||||||
|
uint8_t* raw = nullptr;
|
||||||
|
size_t rawLen = 0;
|
||||||
|
|
||||||
|
if (!httpGetGzip(url, raw, rawLen)) {
|
||||||
|
Serial.println("GZIP download/decompress failed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeserializationError err = deserializeJson(_doc, raw, rawLen);
|
||||||
|
free(raw);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
Serial.printf("JSON ERROR: %s\n", err.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG) {Serial.println("JSON OK!");}
|
||||||
|
_valid = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonDocument& NetworkClient::json() {
|
||||||
|
return _doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkClient::isValid() const {
|
||||||
|
return _valid;
|
||||||
|
}
|
||||||
27
lib/obp60task/NetworkClient.h
Normal file
27
lib/obp60task/NetworkClient.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
|
||||||
|
#define DEBUG false // Debug flag for NetworkClient for more live information
|
||||||
|
#define READLIMIT 200000 // HTTP read limit in byte for gzip content (can be adjusted)
|
||||||
|
#define CONNECTIONTIMEOUT 3000 // Timeout in ms for HTTP connection
|
||||||
|
#define TCPREADTIMEOUT 2000 // Timeout in ms for read HTTP client stack
|
||||||
|
#define READDATATIMEOUT 2000 // Timeout in ms for read data
|
||||||
|
|
||||||
|
class NetworkClient {
|
||||||
|
public:
|
||||||
|
NetworkClient(size_t reserveSize = 0);
|
||||||
|
|
||||||
|
bool fetchAndDecompressJson(const String& url);
|
||||||
|
JsonDocument& json();
|
||||||
|
bool isValid() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DynamicJsonDocument _doc;
|
||||||
|
bool _valid;
|
||||||
|
|
||||||
|
int skipGzipHeader(const uint8_t* data, size_t len);
|
||||||
|
bool httpGetGzip(const String& url, uint8_t*& outData, size_t& outLen);
|
||||||
|
};
|
||||||
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <PCF8574.h> // Driver for PCF8574 output modul from Horter
|
|
||||||
#include <Wire.h> // I2C
|
#include <Wire.h> // I2C
|
||||||
#include <RTClib.h> // Driver for DS1388 RTC
|
#include <RTClib.h> // Driver for DS1388 RTC
|
||||||
|
#include <PCF8574.h> // PCF8574 modules from Horter
|
||||||
#include "SunRise.h" // Lib for sunrise and sunset calculation
|
#include "SunRise.h" // Lib for sunrise and sunset calculation
|
||||||
#include "Pagedata.h"
|
#include "Pagedata.h"
|
||||||
#include "OBP60Hardware.h"
|
#include "OBP60Hardware.h"
|
||||||
@@ -24,11 +24,10 @@
|
|||||||
#include "fonts/Ubuntu_Bold20pt8b.h"
|
#include "fonts/Ubuntu_Bold20pt8b.h"
|
||||||
#include "fonts/Ubuntu_Bold32pt8b.h"
|
#include "fonts/Ubuntu_Bold32pt8b.h"
|
||||||
#include "fonts/Atari16px8b.h" // Key label font
|
#include "fonts/Atari16px8b.h" // Key label font
|
||||||
|
#include "fonts/IBM8x8px.h"
|
||||||
|
|
||||||
// E-Ink Display
|
// E-Ink Display
|
||||||
#define GxEPD_WIDTH 400 // Display width
|
// Definition for e-paper width an height refer OBP60Hardware.h
|
||||||
#define GxEPD_HEIGHT 300 // Display height
|
|
||||||
|
|
||||||
#ifdef DISPLAY_GDEW042T2
|
#ifdef DISPLAY_GDEW042T2
|
||||||
// Set display type and SPI pins for display
|
// Set display type and SPI pins for display
|
||||||
GxEPD2_BW<GxEPD2_420, GxEPD2_420::HEIGHT> display(GxEPD2_420(OBP_SPI_CS, OBP_SPI_DC, OBP_SPI_RST, OBP_SPI_BUSY)); // GDEW042T2 400x300, UC8176 (IL0398)
|
GxEPD2_BW<GxEPD2_420, GxEPD2_420::HEIGHT> display(GxEPD2_420(OBP_SPI_CS, OBP_SPI_DC, OBP_SPI_RST, OBP_SPI_BUSY)); // GDEW042T2 400x300, UC8176 (IL0398)
|
||||||
@@ -64,6 +63,12 @@ PCF8574 pcf8574_Out(PCF8574_I2C_ADDR1); // First digital output modul PCF8574 fr
|
|||||||
Adafruit_FRAM_I2C fram;
|
Adafruit_FRAM_I2C fram;
|
||||||
bool hasFRAM = false;
|
bool hasFRAM = false;
|
||||||
|
|
||||||
|
// SD Card
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
sdmmc_card_t *sdcard;
|
||||||
|
#endif
|
||||||
|
bool hasSDCard = false;
|
||||||
|
|
||||||
// Global vars
|
// Global vars
|
||||||
bool blinkingLED = false; // Enable / disable blinking flash LED
|
bool blinkingLED = false; // Enable / disable blinking flash LED
|
||||||
bool statusLED = false; // Actual status of flash LED on/off
|
bool statusLED = false; // Actual status of flash LED on/off
|
||||||
@@ -78,16 +83,20 @@ LedTaskData *ledTaskData=nullptr;
|
|||||||
|
|
||||||
void hardwareInit(GwApi *api)
|
void hardwareInit(GwApi *api)
|
||||||
{
|
{
|
||||||
|
GwLog *logger = api->getLogger();
|
||||||
|
GwConfigHandler *config = api->getConfig();
|
||||||
|
|
||||||
Wire.begin();
|
Wire.begin();
|
||||||
// Init PCF8574 digital outputs
|
// Init PCF8574 digital outputs
|
||||||
Wire.setClock(I2C_SPEED); // Set I2C clock on 10 kHz
|
Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz
|
||||||
if(pcf8574_Out.begin()){ // Initialize PCF8574
|
if(pcf8574_Out.begin()){ // Initialize PCF8574
|
||||||
pcf8574_Out.write8(255); // Clear all outputs
|
pcf8574_Out.write8(255); // Clear all outputs
|
||||||
}
|
}
|
||||||
|
Wire.setClock(I2C_SPEED); // Set I2C clock on 100 kHz
|
||||||
fram = Adafruit_FRAM_I2C();
|
fram = Adafruit_FRAM_I2C();
|
||||||
if (esp_reset_reason() == ESP_RST_POWERON) {
|
if (esp_reset_reason() == ESP_RST_POWERON) {
|
||||||
// help initialize FRAM
|
// help initialize FRAM
|
||||||
api->getLogger()->logDebug(GwLog::LOG,"Delaying I2C init for 250ms due to cold boot");
|
logger->logDebug(GwLog::LOG, "Delaying I2C init for 250ms due to cold boot");
|
||||||
delay(250);
|
delay(250);
|
||||||
}
|
}
|
||||||
// FRAM (e.g. MB85RC256V)
|
// FRAM (e.g. MB85RC256V)
|
||||||
@@ -99,14 +108,100 @@ void hardwareInit(GwApi *api)
|
|||||||
// Boot counter
|
// Boot counter
|
||||||
uint8_t framcounter = fram.read(0x0000);
|
uint8_t framcounter = fram.read(0x0000);
|
||||||
fram.write(0x0000, framcounter+1);
|
fram.write(0x0000, framcounter+1);
|
||||||
api->getLogger()->logDebug(GwLog::LOG,"FRAM detected: 0x%04x/0x%04x (counter=%d)", manufacturerID, productID, framcounter);
|
logger->logDebug(GwLog::LOG, "FRAM detected: 0x%04x/0x%04x (counter=%d)", manufacturerID, productID, framcounter);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
hasFRAM = false;
|
hasFRAM = false;
|
||||||
api->getLogger()->logDebug(GwLog::LOG,"NO FRAM detected");
|
logger->logDebug(GwLog::LOG, "NO FRAM detected");
|
||||||
|
}
|
||||||
|
// SD Card
|
||||||
|
hasSDCard = false;
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
if (config->getBool(config->useSDCard)) {
|
||||||
|
esp_err_t ret;
|
||||||
|
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
|
||||||
|
host.slot = SPI3_HOST;
|
||||||
|
logger->logDebug(GwLog::DEBUG, "SDSPI_HOST: max_freq_khz=%d" , host.max_freq_khz);
|
||||||
|
spi_bus_config_t bus_cfg = {
|
||||||
|
.mosi_io_num = SD_SPI_MOSI,
|
||||||
|
.miso_io_num = SD_SPI_MISO,
|
||||||
|
.sclk_io_num = SD_SPI_CLK,
|
||||||
|
.quadwp_io_num = -1,
|
||||||
|
.quadhd_io_num = -1,
|
||||||
|
.max_transfer_sz = 4000,
|
||||||
|
};
|
||||||
|
ret = spi_bus_initialize((spi_host_device_t) host.slot, &bus_cfg, SDSPI_DEFAULT_DMA);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
logger->logDebug(GwLog::ERROR, "Failed to initialize SPI bus for SD card");
|
||||||
|
} else {
|
||||||
|
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
||||||
|
slot_config.gpio_cs = SD_SPI_CS;
|
||||||
|
slot_config.host_id = (spi_host_device_t) host.slot;
|
||||||
|
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||||
|
.format_if_mount_failed = false,
|
||||||
|
.max_files = 5,
|
||||||
|
.allocation_unit_size = 16 * 1024
|
||||||
|
};
|
||||||
|
ret = esp_vfs_fat_sdspi_mount(MOUNT_POINT, &host, &slot_config, &mount_config, &sdcard);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
if (ret == ESP_FAIL) {
|
||||||
|
logger->logDebug(GwLog::ERROR, "Failed to mount SD card filesystem");
|
||||||
|
} else {
|
||||||
|
// ret == 263 could be not powered up yet
|
||||||
|
logger->logDebug(GwLog::ERROR, "Failed to initialize SD card (error #%d)", ret);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger->logDebug(GwLog::LOG, "SD card filesystem mounted at '%s'", MOUNT_POINT);
|
||||||
|
hasSDCard = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasSDCard) {
|
||||||
|
// read some stats
|
||||||
|
String features = "";
|
||||||
|
if (sdcard->is_mem) features += "MEM "; // Memory card
|
||||||
|
if (sdcard->is_sdio) features += "IO "; // IO Card
|
||||||
|
if (sdcard->is_mmc) features += "MMC "; // MMC Card
|
||||||
|
if (sdcard->is_ddr) features += "DDR ";
|
||||||
|
// if (sdcard->is_uhs1) features += "UHS-1 ";
|
||||||
|
// ext_csd. Extended information
|
||||||
|
// uint8_t rev, uint8_t power_class
|
||||||
|
logger->logDebug(GwLog::LOG, "SD card features: %s", features);
|
||||||
|
logger->logDebug(GwLog::LOG, "SD card size: %lluMB", ((uint64_t) sdcard->csd.capacity) * sdcard->csd.sector_size / (1024 * 1024));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void powerInit(String powermode) {
|
||||||
|
// Max Power | Only 5.0V | Min Power
|
||||||
|
if (powermode == "Max Power" || powermode == "Only 5.0V") {
|
||||||
|
#ifdef HARDWARE_V21
|
||||||
|
setPortPin(OBP_POWER_50, true); // Power on 5.0V rail
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
setPortPin(OBP_POWER_EPD, true);// Power on ePaper display
|
||||||
|
setPortPin(OBP_POWER_SD, true); // Power on SD card
|
||||||
|
#endif
|
||||||
|
} else { // Min Power
|
||||||
|
#ifdef HARDWARE_V21
|
||||||
|
setPortPin(OBP_POWER_50, false); // Power off 5.0V rail
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
setPortPin(OBP_POWER_EPD, false);// Power off ePaper display
|
||||||
|
setPortPin(OBP_POWER_SD, false); // Power off SD card
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPCF8574PortPin(uint pin, uint8_t value){
|
||||||
|
Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz
|
||||||
|
if(pcf8574_Out.begin()){ // Check available and initialize PCF8574
|
||||||
|
pcf8574_Out.write(pin, value); // Toggle pin
|
||||||
|
}
|
||||||
|
Wire.setClock(I2C_SPEED); // Set I2C clock on 100 kHz
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void setPortPin(uint pin, bool value){
|
void setPortPin(uint pin, bool value){
|
||||||
pinMode(pin, OUTPUT);
|
pinMode(pin, OUTPUT);
|
||||||
digitalWrite(pin, value);
|
digitalWrite(pin, value);
|
||||||
@@ -276,30 +371,63 @@ String xdrDelete(String input){
|
|||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
Point rotatePoint(const Point& origin, const Point& p, double angle) {
|
|
||||||
// rotate poind around origin by degrees
|
|
||||||
Point rotated;
|
|
||||||
double phi = angle * M_PI / 180.0;
|
|
||||||
double dx = p.x - origin.x;
|
|
||||||
double dy = p.y - origin.y;
|
|
||||||
rotated.x = origin.x + cos(phi) * dx - sin(phi) * dy;
|
|
||||||
rotated.y = origin.y + sin(phi) * dx + cos(phi) * dy;
|
|
||||||
return rotated;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Point> rotatePoints(const Point& origin, const std::vector<Point>& pts, double angle) {
|
|
||||||
std::vector<Point> rotatedPoints;
|
|
||||||
for (const auto& p : pts) {
|
|
||||||
rotatedPoints.push_back(rotatePoint(origin, p, angle));
|
|
||||||
}
|
|
||||||
return rotatedPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
void fillPoly4(const std::vector<Point>& p4, uint16_t color) {
|
void fillPoly4(const std::vector<Point>& p4, uint16_t color) {
|
||||||
getdisplay().fillTriangle(p4[0].x, p4[0].y, p4[1].x, p4[1].y, p4[2].x, p4[2].y, color);
|
getdisplay().fillTriangle(p4[0].x, p4[0].y, p4[1].x, p4[1].y, p4[2].x, p4[2].y, color);
|
||||||
getdisplay().fillTriangle(p4[0].x, p4[0].y, p4[2].x, p4[2].y, p4[3].x, p4[3].y, color);
|
getdisplay().fillTriangle(p4[0].x, p4[0].y, p4[2].x, p4[2].y, p4[3].x, p4[3].y, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void drawPoly(const std::vector<Point>& points, uint16_t color) {
|
||||||
|
size_t polysize = points.size();
|
||||||
|
for (size_t i = 0; i < polysize - 1; i++) {
|
||||||
|
getdisplay().drawLine(points[i].x, points[i].y, points[i+1].x, points[i+1].y, color);
|
||||||
|
}
|
||||||
|
// close path
|
||||||
|
getdisplay().drawLine(points[polysize-1].x, points[polysize-1].y, points[0].x, points[0].y, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split string into words, whitespace separated
|
||||||
|
std::vector<String> split(const String &s) {
|
||||||
|
std::vector<String> words;
|
||||||
|
String word = "";
|
||||||
|
for (size_t i = 0; i < s.length(); i++) {
|
||||||
|
if (s[i] == ' ' || s[i] == '\t' || s[i] == '\r' || s[i] == '\n') {
|
||||||
|
if (word.length() > 0) {
|
||||||
|
words.push_back(word);
|
||||||
|
word = "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
word += s[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (word.length() > 0) {
|
||||||
|
words.push_back(word);
|
||||||
|
}
|
||||||
|
return words;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wordwrap single line, monospaced font
|
||||||
|
std::vector<String> wordwrap(String &line, uint16_t maxwidth) {
|
||||||
|
std::vector<String> lines;
|
||||||
|
std::vector<String> words = split(line);
|
||||||
|
String currentLine = "";
|
||||||
|
for (const auto& word : words) {
|
||||||
|
if (currentLine.length() + word.length() + 1 > maxwidth) {
|
||||||
|
if (currentLine.length() > 0) {
|
||||||
|
lines.push_back(currentLine);
|
||||||
|
currentLine = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentLine.length() > 0) {
|
||||||
|
currentLine += " ";
|
||||||
|
}
|
||||||
|
currentLine += word;
|
||||||
|
}
|
||||||
|
if (currentLine.length() > 0) {
|
||||||
|
lines.push_back(currentLine);
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
// Draw centered text
|
// Draw centered text
|
||||||
void drawTextCenter(int16_t cx, int16_t cy, String text) {
|
void drawTextCenter(int16_t cx, int16_t cy, String text) {
|
||||||
int16_t x1, y1;
|
int16_t x1, y1;
|
||||||
@@ -318,6 +446,24 @@ void drawTextRalign(int16_t x, int16_t y, String text) {
|
|||||||
getdisplay().print(text);
|
getdisplay().print(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw text inside box, normal or inverted
|
||||||
|
void drawTextBoxed(Rect box, String text, uint16_t fg, uint16_t bg, bool inverted, bool border) {
|
||||||
|
if (inverted) {
|
||||||
|
getdisplay().fillRect(box.x, box.y, box.w, box.h, fg);
|
||||||
|
getdisplay().setTextColor(bg);
|
||||||
|
} else {
|
||||||
|
if (border) {
|
||||||
|
getdisplay().fillRect(box.x + 1, box.y + 1, box.w - 2, box.h - 2, bg);
|
||||||
|
getdisplay().drawRect(box.x, box.y, box.w, box.h, fg);
|
||||||
|
}
|
||||||
|
getdisplay().setTextColor(fg);
|
||||||
|
}
|
||||||
|
uint16_t border_offset = box.h / 4; // 25% of box height
|
||||||
|
getdisplay().setCursor(box.x + border_offset, box.y + box.h - border_offset);
|
||||||
|
getdisplay().print(text);
|
||||||
|
getdisplay().setTextColor(fg);
|
||||||
|
}
|
||||||
|
|
||||||
// Show a triangle for trend direction high (x, y is the left edge)
|
// Show a triangle for trend direction high (x, y is the left edge)
|
||||||
void displayTrendHigh(int16_t x, int16_t y, uint16_t size, uint16_t color){
|
void displayTrendHigh(int16_t x, int16_t y, uint16_t size, uint16_t color){
|
||||||
getdisplay().fillTriangle(x, y, x+size*2, y, x+size, y-size*2, color);
|
getdisplay().fillTriangle(x, y, x+size*2, y, x+size, y-size*2, color);
|
||||||
@@ -545,6 +691,47 @@ void displayFooter(CommonData &commonData) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alarm overlay, to be drawn as very last draw operation
|
||||||
|
void displayAlarm(CommonData &commonData) {
|
||||||
|
|
||||||
|
const uint16_t x = 50; // overlay area
|
||||||
|
const uint16_t y = 100;
|
||||||
|
const uint16_t w = 300;
|
||||||
|
const uint16_t h = 150;
|
||||||
|
|
||||||
|
getdisplay().setFont(&Atari16px);
|
||||||
|
getdisplay().setTextColor(commonData.fgcolor);
|
||||||
|
|
||||||
|
// overlay
|
||||||
|
getdisplay().drawRect(x, y, w, h, commonData.fgcolor);
|
||||||
|
getdisplay().fillRect(x + 1, y + 1, w - 1, h - 1, commonData.bgcolor);
|
||||||
|
getdisplay().drawRect(x + 3, y + 3, w - 5, h - 5, commonData.fgcolor);
|
||||||
|
|
||||||
|
// exclamation icon in left top corner
|
||||||
|
getdisplay().drawXBitmap(x + 16, y + 16, exclamation_bits, exclamation_width, exclamation_height, commonData.fgcolor);
|
||||||
|
|
||||||
|
// title
|
||||||
|
getdisplay().setCursor(x + 64, y + 30);
|
||||||
|
getdisplay().print("A L A R M");
|
||||||
|
getdisplay().setCursor(x + 64, y + 48);
|
||||||
|
getdisplay().print("#" + commonData.alarm.id);
|
||||||
|
getdisplay().print(" from ");
|
||||||
|
getdisplay().print(commonData.alarm.source);
|
||||||
|
|
||||||
|
// message, but maximum 4 lines
|
||||||
|
std::vector<String> lines = wordwrap (commonData.alarm.message, w - 16 - 8 / 8);
|
||||||
|
int n = 0;
|
||||||
|
for (const auto& l : lines) {
|
||||||
|
getdisplay().setCursor(x + 16, y + 80 + n);
|
||||||
|
getdisplay().print(l);
|
||||||
|
n += 16;
|
||||||
|
if (n > 64) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drawTextCenter(x + w / 2, y + h - 16, "Press button 1 to dismiss alarm");
|
||||||
|
}
|
||||||
|
|
||||||
// Sunset und sunrise calculation
|
// Sunset und sunrise calculation
|
||||||
SunData calcSunsetSunrise(double time, double date, double latitude, double longitude, float timezone){
|
SunData calcSunsetSunrise(double time, double date, double latitude, double longitude, float timezone){
|
||||||
SunData returnset;
|
SunData returnset;
|
||||||
@@ -726,4 +913,30 @@ void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUM
|
|||||||
imageBuffer.clear();
|
imageBuffer.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Calculate the distance between two Geo coordinates
|
||||||
|
double distanceBetweenCoordinates(double lat1, double lon1, double lat2, double lon2) {
|
||||||
|
// Grad → Radiant
|
||||||
|
double lat1Rad = lat1 * DEG_TO_RAD;
|
||||||
|
double lon1Rad = lon1 * DEG_TO_RAD;
|
||||||
|
double lat2Rad = lat2 * DEG_TO_RAD;
|
||||||
|
double lon2Rad = lon2 * DEG_TO_RAD;
|
||||||
|
|
||||||
|
// Differenzen
|
||||||
|
double dLat = lat2Rad - lat1Rad;
|
||||||
|
double dLon = lon2Rad - lon1Rad;
|
||||||
|
|
||||||
|
// Haversine-Formel
|
||||||
|
double a = sin(dLat / 2.0) * sin(dLat / 2.0) +
|
||||||
|
cos(lat1Rad) * cos(lat2Rad) *
|
||||||
|
sin(dLon / 2.0) * sin(dLon / 2.0);
|
||||||
|
|
||||||
|
double c = 2.0 * atan2(sqrt(a), sqrt(1.0 - a));
|
||||||
|
|
||||||
|
// Abstand in Metern
|
||||||
|
return double(EARTH_RADIUS) * c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -4,8 +4,16 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "OBP60Hardware.h"
|
#include "OBP60Hardware.h"
|
||||||
#include "LedSpiTask.h"
|
#include "LedSpiTask.h"
|
||||||
|
#include "Graphics.h"
|
||||||
#include <GxEPD2_BW.h> // E-paper lib V2
|
#include <GxEPD2_BW.h> // E-paper lib V2
|
||||||
#include <Adafruit_FRAM_I2C.h> // I2C FRAM
|
#include <Adafruit_FRAM_I2C.h> // I2C FRAM
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
#include "esp_vfs_fat.h"
|
||||||
|
#include "sdmmc_cmd.h"
|
||||||
|
#define MOUNT_POINT "/sdcard"
|
||||||
|
#endif
|
||||||
|
|
||||||
// FRAM address reservations 32kB: 0x0000 - 0x7FFF
|
// FRAM address reservations 32kB: 0x0000 - 0x7FFF
|
||||||
// 0x0000 - 0x03ff: single variables
|
// 0x0000 - 0x03ff: single variables
|
||||||
@@ -15,6 +23,7 @@
|
|||||||
#define FRAM_VOLTAGE_AVG 0x000A
|
#define FRAM_VOLTAGE_AVG 0x000A
|
||||||
#define FRAM_VOLTAGE_TREND 0x000B
|
#define FRAM_VOLTAGE_TREND 0x000B
|
||||||
#define FRAM_VOLTAGE_MODE 0x000C
|
#define FRAM_VOLTAGE_MODE 0x000C
|
||||||
|
// Wind page
|
||||||
#define FRAM_WIND_SIZE 0x000D
|
#define FRAM_WIND_SIZE 0x000D
|
||||||
#define FRAM_WIND_SRC 0x000E
|
#define FRAM_WIND_SRC 0x000E
|
||||||
#define FRAM_WIND_MODE 0x000F
|
#define FRAM_WIND_MODE 0x000F
|
||||||
@@ -22,8 +31,15 @@
|
|||||||
#define FRAM_BAROGRAPH_START 0x0400
|
#define FRAM_BAROGRAPH_START 0x0400
|
||||||
#define FRAM_BAROGRAPH_END 0x13FF
|
#define FRAM_BAROGRAPH_END 0x13FF
|
||||||
|
|
||||||
|
#define PI 3.1415926535897932384626433832795
|
||||||
|
#define EARTH_RADIUS 6371000.0
|
||||||
|
|
||||||
extern Adafruit_FRAM_I2C fram;
|
extern Adafruit_FRAM_I2C fram;
|
||||||
extern bool hasFRAM;
|
extern bool hasFRAM;
|
||||||
|
extern bool hasSDCard;
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
extern sdmmc_card_t *sdcard;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Fonts declarations for display (#includes see OBP60Extensions.cpp)
|
// Fonts declarations for display (#includes see OBP60Extensions.cpp)
|
||||||
extern const GFXfont DSEG7Classic_BoldItalic16pt7b;
|
extern const GFXfont DSEG7Classic_BoldItalic16pt7b;
|
||||||
@@ -39,6 +55,7 @@ extern const GFXfont Ubuntu_Bold16pt8b;
|
|||||||
extern const GFXfont Ubuntu_Bold20pt8b;
|
extern const GFXfont Ubuntu_Bold20pt8b;
|
||||||
extern const GFXfont Ubuntu_Bold32pt8b;
|
extern const GFXfont Ubuntu_Bold32pt8b;
|
||||||
extern const GFXfont Atari16px;
|
extern const GFXfont Atari16px;
|
||||||
|
extern const GFXfont IBM8x8px;
|
||||||
|
|
||||||
// Global functions
|
// Global functions
|
||||||
#ifdef DISPLAY_GDEW042T2
|
#ifdef DISPLAY_GDEW042T2
|
||||||
@@ -57,22 +74,23 @@ 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
|
||||||
|
|
||||||
struct Point {
|
// Page display return values
|
||||||
double x;
|
#define PAGE_OK 0 // all ok, do nothing
|
||||||
double y;
|
#define PAGE_UPDATE 1 // page wants display to update
|
||||||
};
|
#define PAGE_HIBERNATE 2 // page wants displey to hibernate
|
||||||
Point rotatePoint(const Point& origin, const Point& p, double angle);
|
|
||||||
std::vector<Point> rotatePoints(const Point& origin, const std::vector<Point>& pts, double angle);
|
|
||||||
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 deepSleep(CommonData &common);
|
void deepSleep(CommonData &common);
|
||||||
|
|
||||||
uint8_t getLastPage();
|
uint8_t getLastPage();
|
||||||
|
|
||||||
void hardwareInit(GwApi *api);
|
void hardwareInit(GwApi *api);
|
||||||
|
void powerInit(String powermode);
|
||||||
|
|
||||||
|
void setPCF8574PortPin(uint pin, uint8_t value);// Set PCF8574 port pin
|
||||||
void setPortPin(uint pin, bool value); // Set port pin for extension port
|
void setPortPin(uint pin, bool value); // Set port pin for extension port
|
||||||
|
|
||||||
void togglePortPin(uint pin); // Toggle extension port pin
|
void togglePortPin(uint pin); // Toggle extension port pin
|
||||||
|
|
||||||
Color colorMapping(const String &colorString); // Color mapping string to CHSV colors
|
Color colorMapping(const String &colorString); // Color mapping string to CHSV colors
|
||||||
@@ -91,12 +109,14 @@ String xdrDelete(String input); // Delete xdr prefix from string
|
|||||||
|
|
||||||
void drawTextCenter(int16_t cx, int16_t cy, String text);
|
void drawTextCenter(int16_t cx, int16_t cy, String text);
|
||||||
void drawTextRalign(int16_t x, int16_t y, String text);
|
void drawTextRalign(int16_t x, int16_t y, String text);
|
||||||
|
void drawTextBoxed(Rect box, String text, uint16_t fg, uint16_t bg, bool inverted, bool border);
|
||||||
|
|
||||||
void displayTrendHigh(int16_t x, int16_t y, uint16_t size, uint16_t color);
|
void displayTrendHigh(int16_t x, int16_t y, uint16_t size, uint16_t color);
|
||||||
void displayTrendLow(int16_t x, int16_t y, uint16_t size, uint16_t color);
|
void displayTrendLow(int16_t x, int16_t y, uint16_t size, uint16_t color);
|
||||||
|
|
||||||
void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatValue *time, GwApi::BoatValue *hdop); // Draw display header
|
void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatValue *time, GwApi::BoatValue *hdop); // Draw display header
|
||||||
void displayFooter(CommonData &commonData);
|
void displayFooter(CommonData &commonData);
|
||||||
|
void displayAlarm(CommonData &commonData);
|
||||||
|
|
||||||
SunData calcSunsetSunrise(double time, double date, double latitude, double longitude, float timezone); // Calulate sunset and sunrise
|
SunData calcSunsetSunrise(double time, double date, double latitude, double longitude, float timezone); // Calulate sunset and sunrise
|
||||||
SunData calcSunsetSunriseRTC(struct tm *rtctime, double latitude, double longitude, float timezone);
|
SunData calcSunsetSunriseRTC(struct tm *rtctime, double latitude, double longitude, float timezone);
|
||||||
@@ -142,12 +162,12 @@ static unsigned char fram_bits[] PROGMEM = {
|
|||||||
0xff, 0xff, 0xf8, 0x1f, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f,
|
0xff, 0xff, 0xf8, 0x1f, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f,
|
||||||
0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f };
|
0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x1f };
|
||||||
|
|
||||||
static unsigned char ap_bits[] = {
|
static unsigned char ap_bits[] PROGMEM = {
|
||||||
0xe0, 0x03, 0x18, 0x0c, 0x04, 0x10, 0xc2, 0x21, 0x30, 0x06, 0x08, 0x08,
|
0xe0, 0x03, 0x18, 0x0c, 0x04, 0x10, 0xc2, 0x21, 0x30, 0x06, 0x08, 0x08,
|
||||||
0xc0, 0x01, 0x20, 0x02, 0x00, 0x00, 0x80, 0x00, 0xc0, 0x01, 0xc0, 0x01,
|
0xc0, 0x01, 0x20, 0x02, 0x00, 0x00, 0x80, 0x00, 0xc0, 0x01, 0xc0, 0x01,
|
||||||
0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00 };
|
0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00 };
|
||||||
|
|
||||||
static unsigned char dish_bits[] PROGMEM= {
|
static unsigned char dish_bits[] PROGMEM = {
|
||||||
0x3c, 0x00, 0x42, 0x18, 0xfa, 0x1b, 0x02, 0x04, 0x02, 0x0a, 0x02, 0x09,
|
0x3c, 0x00, 0x42, 0x18, 0xfa, 0x1b, 0x02, 0x04, 0x02, 0x0a, 0x02, 0x09,
|
||||||
0x82, 0x08, 0x06, 0x0a, 0x0e, 0x1b, 0x9c, 0x2b, 0x38, 0x2b, 0x74, 0x20,
|
0x82, 0x08, 0x06, 0x0a, 0x0e, 0x1b, 0x9c, 0x2b, 0x38, 0x2b, 0x74, 0x20,
|
||||||
0xec, 0x1f, 0x1c, 0x00, 0xf4, 0x00, 0xfe, 0x03 };
|
0xec, 0x1f, 0x1c, 0x00, 0xf4, 0x00, 0xfe, 0x03 };
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,8 @@
|
|||||||
// Direction pin for RS485 NMEA0183
|
// Direction pin for RS485 NMEA0183
|
||||||
#define OBP_DIRECTION_PIN 18
|
#define OBP_DIRECTION_PIN 18
|
||||||
// I2C
|
// I2C
|
||||||
#define I2C_SPEED 10000UL // 10kHz clock speed on I2C bus
|
#define I2C_SPEED 10000UL // 100kHz clock speed on I2C bus
|
||||||
|
#define I2C_SPEED_LOW 1000UL // 10kHz clock speed on I2C bus for external bus
|
||||||
#define OBP_I2C_SDA 47
|
#define OBP_I2C_SDA 47
|
||||||
#define OBP_I2C_SCL 21
|
#define OBP_I2C_SCL 21
|
||||||
// DS1388 RTC
|
// DS1388 RTC
|
||||||
@@ -42,6 +43,8 @@
|
|||||||
#define OBP_SPI_DIN 48
|
#define OBP_SPI_DIN 48
|
||||||
#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_HEIGHT 300 // Display height
|
||||||
|
|
||||||
// GPS (NEO-6M, NEO-M8N, ATGM336H)
|
// GPS (NEO-6M, NEO-M8N, ATGM336H)
|
||||||
#define OBP_GPS_RX 2
|
#define OBP_GPS_RX 2
|
||||||
@@ -82,7 +85,8 @@
|
|||||||
// Direction pin for RS485 NMEA0183
|
// Direction pin for RS485 NMEA0183
|
||||||
#define OBP_DIRECTION_PIN 8
|
#define OBP_DIRECTION_PIN 8
|
||||||
// I2C
|
// I2C
|
||||||
#define I2C_SPEED 10000UL // 10kHz clock speed on I2C bus
|
#define I2C_SPEED 100000UL // 100kHz clock speed on I2C bus
|
||||||
|
#define I2C_SPEED_LOW 1000UL // 10kHz clock speed on I2C bus for external bus
|
||||||
#define OBP_I2C_SDA 21
|
#define OBP_I2C_SDA 21
|
||||||
#define OBP_I2C_SCL 38
|
#define OBP_I2C_SCL 38
|
||||||
// DS1388 RTC
|
// DS1388 RTC
|
||||||
@@ -119,11 +123,13 @@
|
|||||||
#define OBP_SPI_DIN 11
|
#define OBP_SPI_DIN 11
|
||||||
#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_HEIGHT 300 // Display height
|
||||||
// SPI SD-Card
|
// SPI SD-Card
|
||||||
#define SD_SPI_CS 10
|
#define SD_SPI_CS GPIO_NUM_10
|
||||||
#define SD_SPI_MOSI 40
|
#define SD_SPI_MOSI GPIO_NUM_40
|
||||||
#define SD_SPI_CLK 39
|
#define SD_SPI_CLK GPIO_NUM_39
|
||||||
#define SD_SPI_MISO 13
|
#define SD_SPI_MISO GPIO_NUM_13
|
||||||
|
|
||||||
// GPS (NEO-6M, NEO-M8N, ATGM336H)
|
// GPS (NEO-6M, NEO-M8N, ATGM336H)
|
||||||
#define OBP_GPS_RX 19
|
#define OBP_GPS_RX 19
|
||||||
|
|||||||
333
lib/obp60task/OBPDataOperations.cpp
Normal file
333
lib/obp60task/OBPDataOperations.cpp
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
#include "OBPDataOperations.h"
|
||||||
|
#include "BoatDataCalibration.h" // Functions lib for data instance calibration
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
// --- Class HstryBuf ---------------
|
||||||
|
|
||||||
|
// Init history buffers for selected boat data
|
||||||
|
void HstryBuf::init(BoatValueList* boatValues, GwLog *log) {
|
||||||
|
|
||||||
|
logger = log;
|
||||||
|
|
||||||
|
int hstryUpdFreq = 1000; // Update frequency for history buffers in ms
|
||||||
|
int mltplr = 1000; // Multiplier which transforms original <double> value into buffer type format
|
||||||
|
double hstryMinVal = 0; // Minimum value for these history buffers
|
||||||
|
twdHstryMax = 2 * M_PI; // Max value for wind direction (TWD, AWD) in rad [0...2*PI]
|
||||||
|
twsHstryMax = 65; // Max value for wind speed (TWS, AWS) in m/s [0..65] (limit due to type capacity of buffer - shifted by <mltplr>)
|
||||||
|
awdHstryMax = twdHstryMax;
|
||||||
|
awsHstryMax = twsHstryMax;
|
||||||
|
twdHstryMin = hstryMinVal;
|
||||||
|
twsHstryMin = hstryMinVal;
|
||||||
|
awdHstryMin = hstryMinVal;
|
||||||
|
awsHstryMin = hstryMinVal;
|
||||||
|
const double DBL_MAX = std::numeric_limits<double>::max();
|
||||||
|
|
||||||
|
// Initialize history buffers with meta data
|
||||||
|
mltplr = 10000; // Store 4 decimals for course data
|
||||||
|
hstryBufList.twdHstry->setMetaData("TWD", "formatCourse", hstryUpdFreq, mltplr, hstryMinVal, twdHstryMax);
|
||||||
|
hstryBufList.awdHstry->setMetaData("AWD", "formatCourse", hstryUpdFreq, mltplr, hstryMinVal, twdHstryMax);
|
||||||
|
mltplr = 1000; // Store 3 decimals for windspeed data
|
||||||
|
hstryBufList.twsHstry->setMetaData("TWS", "formatKnots", hstryUpdFreq, mltplr, hstryMinVal, twsHstryMax);
|
||||||
|
hstryBufList.awsHstry->setMetaData("AWS", "formatKnots", hstryUpdFreq, mltplr, hstryMinVal, twsHstryMax);
|
||||||
|
|
||||||
|
// create boat values for history data types, if they don't exist yet
|
||||||
|
twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName());
|
||||||
|
twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName());
|
||||||
|
twaBVal = boatValues->findValueOrCreate("TWA");
|
||||||
|
awdBVal = boatValues->findValueOrCreate(hstryBufList.awdHstry->getName());
|
||||||
|
awsBVal = boatValues->findValueOrCreate(hstryBufList.awsHstry->getName());
|
||||||
|
|
||||||
|
if (!awdBVal->valid) { // AWD usually does not exist
|
||||||
|
awdBVal->setFormat(hstryBufList.awdHstry->getFormat());
|
||||||
|
awdBVal->value = DBL_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect boat values for true wind calculation
|
||||||
|
awaBVal = boatValues->findValueOrCreate("AWA");
|
||||||
|
hdtBVal = boatValues->findValueOrCreate("HDT");
|
||||||
|
hdmBVal = boatValues->findValueOrCreate("HDM");
|
||||||
|
varBVal = boatValues->findValueOrCreate("VAR");
|
||||||
|
cogBVal = boatValues->findValueOrCreate("COG");
|
||||||
|
sogBVal = boatValues->findValueOrCreate("SOG");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle history buffers for TWD, TWS, AWD, AWS
|
||||||
|
//void HstryBuf::handleHstryBuf(GwApi* api, BoatValueList* boatValues, bool useSimuData) {
|
||||||
|
void HstryBuf::handleHstryBuf(bool useSimuData) {
|
||||||
|
|
||||||
|
static double twd, tws, awd, aws, hdt = 20; //initial value only relevant if we use simulation data
|
||||||
|
GwApi::BoatValue *calBVal; // temp variable just for data calibration -> we don't want to calibrate the original data here
|
||||||
|
|
||||||
|
LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf: TWD_isValid? %d, twdBVal: %.1f, twaBVal: %.1f, twsBVal: %.1f", twdBVal->valid, twdBVal->value * RAD_TO_DEG,
|
||||||
|
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852);
|
||||||
|
|
||||||
|
if (twdBVal->valid) {
|
||||||
|
// if (!useSimuData) {
|
||||||
|
calBVal = new GwApi::BoatValue("TWD"); // temporary solution for calibration of history buffer values
|
||||||
|
calBVal->setFormat(twdBVal->getFormat());
|
||||||
|
calBVal->value = twdBVal->value;
|
||||||
|
calBVal->valid = twdBVal->valid;
|
||||||
|
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
|
||||||
|
twd = calBVal->value;
|
||||||
|
if (twd >= twdHstryMin && twd <= twdHstryMax) {
|
||||||
|
hstryBufList.twdHstry->add(twd);
|
||||||
|
LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf: calBVal.value %.2f, twd: %.2f, twdHstryMin: %.1f, twdHstryMax: %.2f", calBVal->value, twd, twdHstryMin, twdHstryMax);
|
||||||
|
}
|
||||||
|
delete calBVal;
|
||||||
|
calBVal = nullptr;
|
||||||
|
} else if (useSimuData) {
|
||||||
|
// } else {
|
||||||
|
twd += random(-20, 20);
|
||||||
|
twd += static_cast<double>(random(-349, 349) / 1000.0); // add up to +/- 20 degree in RAD
|
||||||
|
twd = WindUtils::to2PI(twd);
|
||||||
|
hstryBufList.twdHstry->add(twd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (twsBVal->valid) {
|
||||||
|
calBVal = new GwApi::BoatValue("TWS"); // temporary solution for calibration of history buffer values
|
||||||
|
calBVal->setFormat(twsBVal->getFormat());
|
||||||
|
calBVal->value = twsBVal->value;
|
||||||
|
calBVal->valid = twsBVal->valid;
|
||||||
|
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
|
||||||
|
tws = calBVal->value;
|
||||||
|
if (tws >= twsHstryMin && tws <= twsHstryMax) {
|
||||||
|
hstryBufList.twsHstry->add(tws);
|
||||||
|
}
|
||||||
|
delete calBVal;
|
||||||
|
calBVal = nullptr;
|
||||||
|
} else if (useSimuData) {
|
||||||
|
// tws += random(-5000, 5000); // TWS value in m/s; expands to 3 decimals
|
||||||
|
tws += static_cast<double>(random(-5000, 5000) / 1000.0); // add up to +/- 5 m/s TWS speed
|
||||||
|
tws = constrain(tws, 0, 40); // Limit TWS to [0..40] m/s
|
||||||
|
hstryBufList.twsHstry->add(tws);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (awaBVal->valid) {
|
||||||
|
if (hdtBVal->valid) {
|
||||||
|
hdt = hdtBVal->value; // Use HDT if available
|
||||||
|
} else {
|
||||||
|
hdt = WindUtils::calcHDT(&hdmBVal->value, &varBVal->value, &cogBVal->value, &sogBVal->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
awd = awaBVal->value + hdt;
|
||||||
|
awd = WindUtils::to2PI(awd);
|
||||||
|
calBVal = new GwApi::BoatValue("AWD"); // temporary solution for calibration of history buffer values
|
||||||
|
calBVal->value = awd;
|
||||||
|
calBVal->setFormat(awdBVal->getFormat());
|
||||||
|
calBVal->valid = true;
|
||||||
|
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
|
||||||
|
awdBVal->value = calBVal->value;
|
||||||
|
awdBVal->valid = true;
|
||||||
|
awd = calBVal->value;
|
||||||
|
if (awd >= awdHstryMin && awd <= awdHstryMax) {
|
||||||
|
hstryBufList.awdHstry->add(awd);
|
||||||
|
}
|
||||||
|
delete calBVal;
|
||||||
|
calBVal = nullptr;
|
||||||
|
} else if (useSimuData) {
|
||||||
|
awd += static_cast<double>(random(-349, 349) / 1000.0); // add up to +/- 20 degree in RAD
|
||||||
|
awd = WindUtils::to2PI(awd);
|
||||||
|
hstryBufList.awdHstry->add(awd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (awsBVal->valid) {
|
||||||
|
calBVal = new GwApi::BoatValue("AWS"); // temporary solution for calibration of history buffer values
|
||||||
|
calBVal->setFormat(awsBVal->getFormat());
|
||||||
|
calBVal->value = awsBVal->value;
|
||||||
|
calBVal->valid = awsBVal->valid;
|
||||||
|
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
|
||||||
|
aws = calBVal->value;
|
||||||
|
if (aws >= awsHstryMin && aws <= awsHstryMax) {
|
||||||
|
hstryBufList.awsHstry->add(aws);
|
||||||
|
}
|
||||||
|
delete calBVal;
|
||||||
|
calBVal = nullptr;
|
||||||
|
} else if (useSimuData) {
|
||||||
|
aws += static_cast<double>(random(-5000, 5000) / 1000.0); // add up to +/- 5 m/s TWS speed
|
||||||
|
aws = constrain(aws, 0, 40); // Limit TWS to [0..40] m/s
|
||||||
|
hstryBufList.awsHstry->add(aws);
|
||||||
|
}
|
||||||
|
LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf-End: Buffer twdHstry: %.3f, twsHstry: %.3f, awdHstry: %.3f, awsHstry: %.3f", hstryBufList.twdHstry->getLast(), hstryBufList.twsHstry->getLast(),
|
||||||
|
hstryBufList.awdHstry->getLast(),hstryBufList.awsHstry->getLast());
|
||||||
|
}
|
||||||
|
// --- Class HstryBuf ---------------
|
||||||
|
|
||||||
|
// --- Class WindUtils --------------
|
||||||
|
double WindUtils::to2PI(double a)
|
||||||
|
{
|
||||||
|
a = fmod(a, M_TWOPI);
|
||||||
|
if (a < 0.0) {
|
||||||
|
a += M_TWOPI;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
double WindUtils::toPI(double a)
|
||||||
|
{
|
||||||
|
a += M_PI;
|
||||||
|
a = to2PI(a);
|
||||||
|
a -= M_PI;
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
double WindUtils::to360(double a)
|
||||||
|
{
|
||||||
|
a = fmod(a, 360.0);
|
||||||
|
if (a < 0.0) {
|
||||||
|
a += 360.0;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
double WindUtils::to180(double a)
|
||||||
|
{
|
||||||
|
a += 180.0;
|
||||||
|
a = to360(a);
|
||||||
|
a -= 180.0;
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindUtils::toCart(const double* phi, const double* r, double* x, double* y)
|
||||||
|
{
|
||||||
|
*x = *r * sin(*phi);
|
||||||
|
*y = *r * cos(*phi);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindUtils::toPol(const double* x, const double* y, double* phi, double* r)
|
||||||
|
{
|
||||||
|
*phi = (M_PI / 2) - atan2(*y, *x);
|
||||||
|
*phi = to2PI(*phi);
|
||||||
|
*r = sqrt(*x * *x + *y * *y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindUtils::addPolar(const double* phi1, const double* r1,
|
||||||
|
const double* phi2, const double* r2,
|
||||||
|
double* phi, double* r)
|
||||||
|
{
|
||||||
|
double x1, y1, x2, y2;
|
||||||
|
toCart(phi1, r1, &x1, &y1);
|
||||||
|
toCart(phi2, r2, &x2, &y2);
|
||||||
|
x1 += x2;
|
||||||
|
y1 += y2;
|
||||||
|
toPol(&x1, &y1, phi, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindUtils::calcTwdSA(const double* AWA, const double* AWS,
|
||||||
|
const double* CTW, const double* STW, const double* HDT,
|
||||||
|
double* TWD, double* TWS, double* TWA)
|
||||||
|
{
|
||||||
|
double awd = *AWA + *HDT;
|
||||||
|
awd = to2PI(awd);
|
||||||
|
double stw = -*STW;
|
||||||
|
addPolar(&awd, AWS, CTW, &stw, TWD, TWS);
|
||||||
|
|
||||||
|
// Normalize TWD and TWA to 0-360°
|
||||||
|
*TWD = to2PI(*TWD);
|
||||||
|
*TWA = toPI(*TWD - *HDT);
|
||||||
|
}
|
||||||
|
|
||||||
|
double WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal)
|
||||||
|
{
|
||||||
|
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::calcTrueWind(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 stw, hdt, 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 (*hdtVal != DBL_MAX) {
|
||||||
|
hdt = *hdtVal; // Use HDT if available
|
||||||
|
} else {
|
||||||
|
hdt = calcHDT(hdmVal, varVal, cogVal, sogVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*cogVal != DBL_MAX && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG
|
||||||
|
|
||||||
|
ctw = *cogVal; // Use COG for CTW if available
|
||||||
|
} else {
|
||||||
|
ctw = hdt; // 2nd approximation for CTW; hdt must exist if we reach this part of the code
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*stwVal != DBL_MAX) {
|
||||||
|
stw = *stwVal; // Use STW if available
|
||||||
|
} else if (*sogVal != DBL_MAX) {
|
||||||
|
stw = *sogVal;
|
||||||
|
} else {
|
||||||
|
// If STW and SOG are not available, we cannot calculate true wind
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Serial.println("\ncalcTrueWind: HDT: " + String(hdt) + ", CTW: " + String(ctw) + ", STW: " + String(stw));
|
||||||
|
|
||||||
|
if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) {
|
||||||
|
// Cannot calculate true wind without valid AWA, AWS; other checks are done earlier
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
calcTwdSA(awaVal, awsVal, &ctw, &stw, &hdt, &twd, &tws, &twa);
|
||||||
|
*twdVal = twd;
|
||||||
|
*twsVal = tws;
|
||||||
|
*twaVal = twa;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate true wind data and add to obp60task boat data list
|
||||||
|
bool WindUtils::addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog* log) {
|
||||||
|
|
||||||
|
GwLog* logger = log;
|
||||||
|
|
||||||
|
double awaVal, awsVal, cogVal, stwVal, sogVal, hdtVal, hdmVal, varVal;
|
||||||
|
double twd, tws, twa;
|
||||||
|
bool isCalculated = false;
|
||||||
|
|
||||||
|
awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX;
|
||||||
|
awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX;
|
||||||
|
cogVal = cogBVal->valid ? cogBVal->value : DBL_MAX;
|
||||||
|
stwVal = stwBVal->valid ? stwBVal->value : DBL_MAX;
|
||||||
|
sogVal = sogBVal->valid ? sogBVal->value : DBL_MAX;
|
||||||
|
hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
|
||||||
|
hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX;
|
||||||
|
varVal = varBVal->valid ? varBVal->value : DBL_MAX;
|
||||||
|
LOG_DEBUG(GwLog::DEBUG,"obp60task addTrueWind: 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,
|
||||||
|
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);
|
||||||
|
|
||||||
|
isCalculated = calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa);
|
||||||
|
|
||||||
|
if (isCalculated) { // Replace values only, if successfully calculated and not already available
|
||||||
|
if (!twdBVal->valid) {
|
||||||
|
twdBVal->value = twd;
|
||||||
|
twdBVal->valid = true;
|
||||||
|
}
|
||||||
|
if (!twsBVal->valid) {
|
||||||
|
twsBVal->value = tws;
|
||||||
|
twsBVal->valid = true;
|
||||||
|
}
|
||||||
|
if (!twaBVal->valid) {
|
||||||
|
twaBVal->value = twa;
|
||||||
|
twaBVal->valid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG_DEBUG(GwLog::DEBUG,"obp60task addTrueWind: isCalculated %d, TWD %.1f, TWA %.1f, TWS %.1f", isCalculated, twdBVal->value * RAD_TO_DEG,
|
||||||
|
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852);
|
||||||
|
|
||||||
|
return isCalculated;
|
||||||
|
}
|
||||||
|
// --- Class WindUtils --------------
|
||||||
90
lib/obp60task/OBPDataOperations.h
Normal file
90
lib/obp60task/OBPDataOperations.h
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// Function lib for history buffer handling, true wind calculation, and other operations on boat data
|
||||||
|
#pragma once
|
||||||
|
#include "OBPRingBuffer.h"
|
||||||
|
#include "obp60task.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
RingBuffer<uint16_t>* twdHstry;
|
||||||
|
RingBuffer<uint16_t>* twsHstry;
|
||||||
|
RingBuffer<uint16_t>* awdHstry;
|
||||||
|
RingBuffer<uint16_t>* awsHstry;
|
||||||
|
} tBoatHstryData; // Holds pointers to all history buffers for boat data
|
||||||
|
|
||||||
|
class HstryBuf {
|
||||||
|
private:
|
||||||
|
GwLog *logger;
|
||||||
|
|
||||||
|
RingBuffer<uint16_t> twdHstry; // Circular buffer to store true wind direction values
|
||||||
|
RingBuffer<uint16_t> twsHstry; // Circular buffer to store true wind speed values (TWS)
|
||||||
|
RingBuffer<uint16_t> awdHstry; // Circular buffer to store apparent wind direction values
|
||||||
|
RingBuffer<uint16_t> awsHstry; // Circular buffer to store apparent xwind speed values (AWS)
|
||||||
|
double twdHstryMin; // Min value for wind direction (TWD) in history buffer
|
||||||
|
double twdHstryMax; // Max value for wind direction (TWD) in history buffer
|
||||||
|
double twsHstryMin;
|
||||||
|
double twsHstryMax;
|
||||||
|
double awdHstryMin;
|
||||||
|
double awdHstryMax;
|
||||||
|
double awsHstryMin;
|
||||||
|
double awsHstryMax;
|
||||||
|
|
||||||
|
// boat values for buffers and for true wind calculation
|
||||||
|
GwApi::BoatValue *twdBVal, *twsBVal, *twaBVal, *awdBVal, *awsBVal;
|
||||||
|
GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal;
|
||||||
|
|
||||||
|
public:
|
||||||
|
tBoatHstryData hstryBufList;
|
||||||
|
|
||||||
|
HstryBuf(){
|
||||||
|
hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry}; // Generate history buffers of zero size
|
||||||
|
};
|
||||||
|
|
||||||
|
HstryBuf(int size) {
|
||||||
|
hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry};
|
||||||
|
hstryBufList.twdHstry->resize(size); // store <size> xWD values for <size>/60 minutes history
|
||||||
|
hstryBufList.twsHstry->resize(size);
|
||||||
|
hstryBufList.awdHstry->resize(size);
|
||||||
|
hstryBufList.awsHstry->resize(size);
|
||||||
|
};
|
||||||
|
void init(BoatValueList* boatValues, GwLog *log);
|
||||||
|
void handleHstryBuf(bool useSimuData);
|
||||||
|
};
|
||||||
|
|
||||||
|
class WindUtils {
|
||||||
|
private:
|
||||||
|
GwApi::BoatValue *twdBVal, *twsBVal, *twaBVal;
|
||||||
|
GwApi::BoatValue *awaBVal, *awsBVal, *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal;
|
||||||
|
static constexpr double DBL_MAX = std::numeric_limits<double>::max();
|
||||||
|
|
||||||
|
public:
|
||||||
|
WindUtils(BoatValueList* boatValues){
|
||||||
|
twdBVal = boatValues->findValueOrCreate("TWD");
|
||||||
|
twsBVal = boatValues->findValueOrCreate("TWS");
|
||||||
|
twaBVal = boatValues->findValueOrCreate("TWA");
|
||||||
|
awaBVal = boatValues->findValueOrCreate("AWA");
|
||||||
|
awsBVal = boatValues->findValueOrCreate("AWS");
|
||||||
|
cogBVal = boatValues->findValueOrCreate("COG");
|
||||||
|
stwBVal = boatValues->findValueOrCreate("STW");
|
||||||
|
sogBVal = boatValues->findValueOrCreate("SOG");
|
||||||
|
hdtBVal = boatValues->findValueOrCreate("HDT");
|
||||||
|
hdmBVal = boatValues->findValueOrCreate("HDM");
|
||||||
|
varBVal = boatValues->findValueOrCreate("VAR");
|
||||||
|
};
|
||||||
|
|
||||||
|
static double to2PI(double a);
|
||||||
|
static double toPI(double a);
|
||||||
|
static double to360(double a);
|
||||||
|
static double to180(double a);
|
||||||
|
void toCart(const double* phi, const double* r, double* x, double* y);
|
||||||
|
void toPol(const double* x, const double* y, double* phi, double* r);
|
||||||
|
void addPolar(const double* phi1, const double* r1,
|
||||||
|
const double* phi2, const double* r2,
|
||||||
|
double* phi, double* r);
|
||||||
|
void calcTwdSA(const double* AWA, const double* AWS,
|
||||||
|
const double* CTW, const double* STW, const double* HDT,
|
||||||
|
double* TWD, double* TWS, double* TWA);
|
||||||
|
static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal);
|
||||||
|
bool calcTrueWind(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);
|
||||||
|
bool addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog *log);
|
||||||
|
};
|
||||||
98
lib/obp60task/OBPRingBuffer.h
Normal file
98
lib/obp60task/OBPRingBuffer.h
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "FreeRTOS.h"
|
||||||
|
#include "GwSynchronized.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <WString.h>
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct PSRAMAllocator {
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
PSRAMAllocator() = default;
|
||||||
|
|
||||||
|
template <class U>
|
||||||
|
constexpr PSRAMAllocator(const PSRAMAllocator<U>&) noexcept { }
|
||||||
|
|
||||||
|
T* allocate(std::size_t n)
|
||||||
|
{
|
||||||
|
void* ptr = heap_caps_malloc(n * sizeof(T), MALLOC_CAP_SPIRAM);
|
||||||
|
if (!ptr) {
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
return static_cast<T*>(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(T* p, std::size_t) noexcept
|
||||||
|
{
|
||||||
|
heap_caps_free(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T, class U>
|
||||||
|
bool operator==(const PSRAMAllocator<T>&, const PSRAMAllocator<U>&) { return true; }
|
||||||
|
|
||||||
|
template <class T, class U>
|
||||||
|
bool operator!=(const PSRAMAllocator<T>&, const PSRAMAllocator<U>&) { return false; }
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class RingBuffer {
|
||||||
|
private:
|
||||||
|
std::vector<T, PSRAMAllocator<T>> buffer; // THE buffer vector, allocated in PSRAM
|
||||||
|
size_t capacity;
|
||||||
|
size_t head; // Points to the next insertion position
|
||||||
|
size_t first; // Points to the first (oldest) valid element
|
||||||
|
size_t last; // Points to the last (newest) valid element
|
||||||
|
size_t count; // Number of valid elements currently in buffer
|
||||||
|
bool is_Full; // Indicates that all buffer elements are used and ringing is in use
|
||||||
|
T MIN_VAL; // lowest possible value of buffer of type <T>
|
||||||
|
T MAX_VAL; // highest possible value of buffer of type <T> -> indicates invalid value in buffer
|
||||||
|
double dblMIN_VAL, dblMAX_VAL; // MIN_VAL, MAX_VAL in double format
|
||||||
|
mutable SemaphoreHandle_t bufLocker;
|
||||||
|
|
||||||
|
// metadata for buffer
|
||||||
|
String dataName; // Name of boat data in buffer
|
||||||
|
String dataFmt; // Format of boat data in buffer
|
||||||
|
int updFreq; // Update frequency in milliseconds
|
||||||
|
double mltplr; // Multiplier which transforms original <double> value into buffer type format
|
||||||
|
double smallest; // Value range of buffer: smallest value; needs to be => MIN_VAL
|
||||||
|
double largest; // Value range of buffer: biggest value; needs to be < MAX_VAL, since MAX_VAL indicates invalid entries
|
||||||
|
|
||||||
|
void initCommon();
|
||||||
|
|
||||||
|
public:
|
||||||
|
RingBuffer();
|
||||||
|
RingBuffer(size_t size);
|
||||||
|
void setMetaData(String name, String format, int updateFrequency, double multiplier, double minValue, double maxValue); // Set meta data for buffer
|
||||||
|
bool getMetaData(String& name, String& format, int& updateFrequency, double& multiplier, double& minValue, double& maxValue); // Get meta data of buffer
|
||||||
|
bool getMetaData(String& name, String& format);
|
||||||
|
String getName() const; // Get buffer name
|
||||||
|
String getFormat() const; // Get buffer data format
|
||||||
|
void add(const double& value); // Add a new value to buffer
|
||||||
|
double get(size_t index) const; // Get value at specific position (0-based index from oldest to newest)
|
||||||
|
double getFirst() const; // Get the first (oldest) value in buffer
|
||||||
|
double getLast() const; // Get the last (newest) value in buffer
|
||||||
|
double getMin() const; // Get the lowest value in buffer
|
||||||
|
double getMin(size_t amount) const; // Get minimum value of the last <amount> values of buffer
|
||||||
|
double getMax() const; // Get the highest value in buffer
|
||||||
|
double getMax(size_t amount) const; // Get maximum value of the last <amount> values of buffer
|
||||||
|
double getMid() const; // Get mid value between <min> and <max> value in buffer
|
||||||
|
double getMid(size_t amount) const; // Get mid value between <min> and <max> value of the last <amount> values of buffer
|
||||||
|
double getMedian() const; // Get the median value in buffer
|
||||||
|
double getMedian(size_t amount) const; // Get the median value of the last <amount> values of buffer
|
||||||
|
size_t getCapacity() const; // Get the buffer capacity (maximum size)
|
||||||
|
size_t getCurrentSize() const; // Get the current number of elements in buffer
|
||||||
|
size_t getFirstIdx() const; // Get the index of oldest value in buffer
|
||||||
|
size_t getLastIdx() const; // Get the index of newest value in buffer
|
||||||
|
bool isEmpty() const; // Check if buffer is empty
|
||||||
|
bool isFull() const; // Check if buffer is full
|
||||||
|
double getMinVal() const; // Get lowest possible value for buffer
|
||||||
|
double getMaxVal() const; // Get highest possible value for buffer; used for unset/invalid buffer data
|
||||||
|
void clear(); // Clear buffer
|
||||||
|
void resize(size_t size); // Delete buffer and set new size
|
||||||
|
double operator[](size_t index) const; // Operator[] for convenient access (same as get())
|
||||||
|
std::vector<double> getAllValues() const; // Get all current values in native buffer format as a vector
|
||||||
|
std::vector<double> getAllValues(size_t amount) const; // Get last <amount> values in native buffer format as a vector
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "OBPRingBuffer.tpp"
|
||||||
462
lib/obp60task/OBPRingBuffer.tpp
Normal file
462
lib/obp60task/OBPRingBuffer.tpp
Normal file
@@ -0,0 +1,462 @@
|
|||||||
|
#include "OBPRingBuffer.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void RingBuffer<T>::initCommon()
|
||||||
|
{
|
||||||
|
MIN_VAL = std::numeric_limits<T>::lowest();
|
||||||
|
MAX_VAL = std::numeric_limits<T>::max();
|
||||||
|
dblMIN_VAL = static_cast<double>(MIN_VAL);
|
||||||
|
dblMAX_VAL = static_cast<double>(MAX_VAL);
|
||||||
|
dataName = "";
|
||||||
|
dataFmt = "";
|
||||||
|
updFreq = -1;
|
||||||
|
mltplr = 1;
|
||||||
|
smallest = dblMIN_VAL;
|
||||||
|
largest = dblMAX_VAL;
|
||||||
|
bufLocker = xSemaphoreCreateMutex();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
RingBuffer<T>::RingBuffer()
|
||||||
|
: capacity(0)
|
||||||
|
, head(0)
|
||||||
|
, first(0)
|
||||||
|
, last(0)
|
||||||
|
, count(0)
|
||||||
|
, is_Full(false)
|
||||||
|
{
|
||||||
|
initCommon();
|
||||||
|
// <buffer> stays empty
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
RingBuffer<T>::RingBuffer(size_t size)
|
||||||
|
: capacity(size)
|
||||||
|
, head(0)
|
||||||
|
, first(0)
|
||||||
|
, last(0)
|
||||||
|
, count(0)
|
||||||
|
, is_Full(false)
|
||||||
|
{
|
||||||
|
initCommon();
|
||||||
|
|
||||||
|
buffer.reserve(size);
|
||||||
|
buffer.resize(size, MAX_VAL); // MAX_VAL indicate invalid values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify meta data of buffer content
|
||||||
|
template <typename T>
|
||||||
|
void RingBuffer<T>::setMetaData(String name, String format, int updateFrequency, double multiplier, double minValue, double maxValue)
|
||||||
|
{
|
||||||
|
GWSYNCHRONIZED(&bufLocker);
|
||||||
|
dataName = name;
|
||||||
|
dataFmt = format;
|
||||||
|
updFreq = updateFrequency;
|
||||||
|
mltplr = multiplier;
|
||||||
|
smallest = std::max(dblMIN_VAL, minValue);
|
||||||
|
largest = std::min(dblMAX_VAL, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get meta data of buffer content
|
||||||
|
template <typename T>
|
||||||
|
bool RingBuffer<T>::getMetaData(String& name, String& format, int& updateFrequency, double& multiplier, double& minValue, double& maxValue)
|
||||||
|
{
|
||||||
|
if (dataName == "" || dataFmt == "" || updFreq == -1) {
|
||||||
|
return false; // Meta data not set
|
||||||
|
}
|
||||||
|
|
||||||
|
GWSYNCHRONIZED(&bufLocker);
|
||||||
|
name = dataName;
|
||||||
|
format = dataFmt;
|
||||||
|
updateFrequency = updFreq;
|
||||||
|
multiplier = mltplr;
|
||||||
|
minValue = smallest;
|
||||||
|
maxValue = largest;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get meta data of buffer content
|
||||||
|
template <typename T>
|
||||||
|
bool RingBuffer<T>::getMetaData(String& name, String& format)
|
||||||
|
{
|
||||||
|
if (dataName == "" || dataFmt == "") {
|
||||||
|
return false; // Meta data not set
|
||||||
|
}
|
||||||
|
|
||||||
|
GWSYNCHRONIZED(&bufLocker);
|
||||||
|
name = dataName;
|
||||||
|
format = dataFmt;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get buffer name
|
||||||
|
template <typename T>
|
||||||
|
String RingBuffer<T>::getName() const
|
||||||
|
{
|
||||||
|
return dataName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get buffer data format
|
||||||
|
template <typename T>
|
||||||
|
String RingBuffer<T>::getFormat() const
|
||||||
|
{
|
||||||
|
return dataFmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new value to buffer
|
||||||
|
template <typename T>
|
||||||
|
void RingBuffer<T>::add(const double& value)
|
||||||
|
{
|
||||||
|
GWSYNCHRONIZED(&bufLocker);
|
||||||
|
if (value < smallest || value > largest) {
|
||||||
|
buffer[head] = MAX_VAL; // Store MAX_VAL if value is out of range
|
||||||
|
} else {
|
||||||
|
buffer[head] = static_cast<T>(std::round(value * mltplr));
|
||||||
|
}
|
||||||
|
last = head;
|
||||||
|
|
||||||
|
if (is_Full) {
|
||||||
|
first = (first + 1) % capacity; // Move pointer to oldest element when overwriting
|
||||||
|
} else {
|
||||||
|
count++;
|
||||||
|
if (count == capacity) {
|
||||||
|
is_Full = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Serial.printf("Ringbuffer: value %.3f, multiplier: %.1f, buffer: %d\n", value, mltplr, buffer[head]);
|
||||||
|
head = (head + 1) % capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get value at specific position (0-based index from oldest to newest)
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::get(size_t index) const
|
||||||
|
{
|
||||||
|
GWSYNCHRONIZED(&bufLocker);
|
||||||
|
if (isEmpty() || index < 0 || index >= count) {
|
||||||
|
return dblMAX_VAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t realIndex = (first + index) % capacity;
|
||||||
|
return static_cast<double>(buffer[realIndex] / mltplr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operator[] for convenient access (same as get())
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::operator[](size_t index) const
|
||||||
|
{
|
||||||
|
return get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the first (oldest) value in the buffer
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::getFirst() const
|
||||||
|
{
|
||||||
|
if (isEmpty()) {
|
||||||
|
return dblMAX_VAL;
|
||||||
|
}
|
||||||
|
return get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the last (newest) value in the buffer
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::getLast() const
|
||||||
|
{
|
||||||
|
if (isEmpty()) {
|
||||||
|
return dblMAX_VAL;
|
||||||
|
}
|
||||||
|
return get(count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the lowest value in the buffer
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::getMin() const
|
||||||
|
{
|
||||||
|
if (isEmpty()) {
|
||||||
|
return dblMAX_VAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
double minVal = dblMAX_VAL;
|
||||||
|
double value;
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
value = get(i);
|
||||||
|
if (value < minVal && value != dblMAX_VAL) {
|
||||||
|
minVal = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get minimum value of the last <amount> values of buffer
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::getMin(size_t amount) const
|
||||||
|
{
|
||||||
|
if (isEmpty() || amount <= 0) {
|
||||||
|
return dblMAX_VAL;
|
||||||
|
}
|
||||||
|
if (amount > count)
|
||||||
|
amount = count;
|
||||||
|
|
||||||
|
double minVal = dblMAX_VAL;
|
||||||
|
double value;
|
||||||
|
for (size_t i = 0; i < amount; i++) {
|
||||||
|
value = get(count - 1 - i);
|
||||||
|
if (value < minVal && value != dblMAX_VAL) {
|
||||||
|
minVal = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the highest value in the buffer
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::getMax() const
|
||||||
|
{
|
||||||
|
if (isEmpty()) {
|
||||||
|
return dblMAX_VAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
double maxVal = dblMIN_VAL;
|
||||||
|
double value;
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
value = get(i);
|
||||||
|
if (value > maxVal && value != dblMAX_VAL) {
|
||||||
|
maxVal = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (maxVal == dblMIN_VAL) { // no change of initial value -> buffer has only invalid values (MAX_VAL)
|
||||||
|
maxVal = dblMAX_VAL;
|
||||||
|
}
|
||||||
|
return maxVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get maximum value of the last <amount> values of buffer
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::getMax(size_t amount) const
|
||||||
|
{
|
||||||
|
if (isEmpty() || amount <= 0) {
|
||||||
|
return dblMAX_VAL;
|
||||||
|
}
|
||||||
|
if (amount > count)
|
||||||
|
amount = count;
|
||||||
|
|
||||||
|
double maxVal = dblMIN_VAL;
|
||||||
|
double value;
|
||||||
|
for (size_t i = 0; i < amount; i++) {
|
||||||
|
value = get(count - 1 - i);
|
||||||
|
if (value > maxVal && value != dblMAX_VAL) {
|
||||||
|
maxVal = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (maxVal == dblMIN_VAL) { // no change of initial value -> buffer has only invalid values (MAX_VAL)
|
||||||
|
maxVal = dblMAX_VAL;
|
||||||
|
}
|
||||||
|
return maxVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mid value between <min> and <max> value in the buffer
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::getMid() const
|
||||||
|
{
|
||||||
|
if (isEmpty()) {
|
||||||
|
return dblMAX_VAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (getMin() + getMax()) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mid value between <min> and <max> value of the last <amount> values of buffer
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::getMid(size_t amount) const
|
||||||
|
{
|
||||||
|
if (isEmpty() || amount <= 0) {
|
||||||
|
return dblMAX_VAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount > count)
|
||||||
|
amount = count;
|
||||||
|
|
||||||
|
return (getMin(amount) + getMax(amount)) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the median value in the buffer
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::getMedian() const
|
||||||
|
{
|
||||||
|
if (isEmpty()) {
|
||||||
|
return dblMAX_VAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a temporary vector with current valid elements
|
||||||
|
std::vector<T> temp;
|
||||||
|
temp.reserve(count);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
temp.push_back(get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort to find median
|
||||||
|
std::sort(temp.begin(), temp.end());
|
||||||
|
|
||||||
|
if (count % 2 == 1) {
|
||||||
|
// Odd number of elements
|
||||||
|
return static_cast<double>(temp[count / 2]);
|
||||||
|
} else {
|
||||||
|
// Even number of elements - return average of middle two
|
||||||
|
// Note: For integer types, this truncates. For floating point, it's exact.
|
||||||
|
return static_cast<double>((temp[count / 2 - 1] + temp[count / 2]) / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the median value of the last <amount> values of buffer
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::getMedian(size_t amount) const
|
||||||
|
{
|
||||||
|
if (isEmpty() || amount <= 0) {
|
||||||
|
return dblMAX_VAL;
|
||||||
|
}
|
||||||
|
if (amount > count)
|
||||||
|
amount = count;
|
||||||
|
|
||||||
|
// Create a temporary vector with current valid elements
|
||||||
|
std::vector<T> temp;
|
||||||
|
temp.reserve(amount);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < amount; i++) {
|
||||||
|
temp.push_back(get(count - 1 - i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort to find median
|
||||||
|
std::sort(temp.begin(), temp.end());
|
||||||
|
|
||||||
|
if (amount % 2 == 1) {
|
||||||
|
// Odd number of elements
|
||||||
|
return static_cast<double>(temp[amount / 2]);
|
||||||
|
} else {
|
||||||
|
// Even number of elements - return average of middle two
|
||||||
|
// Note: For integer types, this truncates. For floating point, it's exact.
|
||||||
|
return static_cast<double>((temp[amount / 2 - 1] + temp[amount / 2]) / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the buffer capacity (maximum size)
|
||||||
|
template <typename T>
|
||||||
|
size_t RingBuffer<T>::getCapacity() const
|
||||||
|
{
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current number of elements in the buffer
|
||||||
|
template <typename T>
|
||||||
|
size_t RingBuffer<T>::getCurrentSize() const
|
||||||
|
{
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the first index of buffer
|
||||||
|
template <typename T>
|
||||||
|
size_t RingBuffer<T>::getFirstIdx() const
|
||||||
|
{
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the last index of buffer
|
||||||
|
template <typename T>
|
||||||
|
size_t RingBuffer<T>::getLastIdx() const
|
||||||
|
{
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if buffer is empty
|
||||||
|
template <typename T>
|
||||||
|
bool RingBuffer<T>::isEmpty() const
|
||||||
|
{
|
||||||
|
return count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if buffer is full
|
||||||
|
template <typename T>
|
||||||
|
bool RingBuffer<T>::isFull() const
|
||||||
|
{
|
||||||
|
return is_Full;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get lowest possible value for buffer
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::getMinVal() const
|
||||||
|
{
|
||||||
|
return dblMIN_VAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get highest possible value for buffer; used for unset/invalid buffer data
|
||||||
|
template <typename T>
|
||||||
|
double RingBuffer<T>::getMaxVal() const
|
||||||
|
{
|
||||||
|
return dblMAX_VAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear buffer
|
||||||
|
template <typename T>
|
||||||
|
void RingBuffer<T>::clear()
|
||||||
|
{
|
||||||
|
GWSYNCHRONIZED(&bufLocker);
|
||||||
|
head = 0;
|
||||||
|
first = 0;
|
||||||
|
last = 0;
|
||||||
|
count = 0;
|
||||||
|
is_Full = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete buffer and set new size
|
||||||
|
template <typename T>
|
||||||
|
void RingBuffer<T>::resize(size_t newSize)
|
||||||
|
{
|
||||||
|
GWSYNCHRONIZED(&bufLocker);
|
||||||
|
capacity = newSize;
|
||||||
|
head = 0;
|
||||||
|
first = 0;
|
||||||
|
last = 0;
|
||||||
|
count = 0;
|
||||||
|
is_Full = false;
|
||||||
|
|
||||||
|
buffer.clear();
|
||||||
|
buffer.reserve(newSize);
|
||||||
|
buffer.resize(newSize, MAX_VAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all current values in native buffer format as a vector
|
||||||
|
template <typename T>
|
||||||
|
std::vector<double> RingBuffer<T>::getAllValues() const
|
||||||
|
{
|
||||||
|
std::vector<double> result;
|
||||||
|
result.reserve(count);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
result.push_back(get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get last <amount> values in native buffer format as a vector
|
||||||
|
template <typename T>
|
||||||
|
std::vector<double> RingBuffer<T>::getAllValues(size_t amount) const
|
||||||
|
{
|
||||||
|
std::vector<double> result;
|
||||||
|
|
||||||
|
if (isEmpty() || amount <= 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (amount > count)
|
||||||
|
amount = count;
|
||||||
|
|
||||||
|
result.reserve(amount);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < amount; i++) {
|
||||||
|
result.push_back(get(count - 1 - i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -498,14 +498,18 @@ void sensorTask(void *param){
|
|||||||
// Send supply voltage value all 1s
|
// Send supply voltage value all 1s
|
||||||
if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){
|
if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){
|
||||||
starttime5 = millis();
|
starttime5 = millis();
|
||||||
float rawVoltage = 0;
|
float rawVoltage = 0; // Default value
|
||||||
#if defined(BOARD_OBP40S3) && defined(VOLTAGE_SENSOR)
|
#ifdef BOARD_OBP40S3
|
||||||
|
sensors.batteryVoltage = 0; // If no sensor then zero voltage
|
||||||
|
#endif
|
||||||
|
#if defined(BOARD_OBP40S3) && defined(VOLTAGE_SENSOR)
|
||||||
rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40
|
rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40
|
||||||
|
sensors.batteryVoltage = rawVoltage * vslope + voffset; // Calibration
|
||||||
#endif
|
#endif
|
||||||
#ifdef BOARD_OBP60S3
|
#ifdef BOARD_OBP60S3
|
||||||
rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60
|
rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60
|
||||||
#endif
|
|
||||||
sensors.batteryVoltage = rawVoltage * vslope + voffset; // Calibration
|
sensors.batteryVoltage = rawVoltage * vslope + voffset; // Calibration
|
||||||
|
#endif
|
||||||
// Save new data in average array
|
// Save new data in average array
|
||||||
batV.reading(int(sensors.batteryVoltage * 100));
|
batV.reading(int(sensors.batteryVoltage * 100));
|
||||||
// Calculate the average values for different time lines from integer values
|
// Calculate the average values for different time lines from integer values
|
||||||
|
|||||||
609
lib/obp60task/OBPcharts.cpp
Normal file
609
lib/obp60task/OBPcharts.cpp
Normal file
@@ -0,0 +1,609 @@
|
|||||||
|
// Function lib for display of boat data in various chart formats
|
||||||
|
#include "OBPcharts.h"
|
||||||
|
#include "OBP60Extensions.h"
|
||||||
|
#include "OBPRingBuffer.h"
|
||||||
|
|
||||||
|
// --- Class Chart ---------------
|
||||||
|
template <typename T>
|
||||||
|
Chart<T>::Chart(RingBuffer<T>& dataBuf, int8_t chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData)
|
||||||
|
: dataBuf(dataBuf)
|
||||||
|
, chrtDir(chrtDir)
|
||||||
|
, chrtSz(chrtSz)
|
||||||
|
, dfltRng(dfltRng)
|
||||||
|
, commonData(&common)
|
||||||
|
, useSimuData(useSimuData)
|
||||||
|
{
|
||||||
|
logger = commonData->logger;
|
||||||
|
fgColor = commonData->fgcolor;
|
||||||
|
bgColor = commonData->bgcolor;
|
||||||
|
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "Chart Init: Chart::dataBuf: %p, passed dataBuf: %p", (void*)&this->dataBuf, (void*)&dataBuf);
|
||||||
|
dWidth = getdisplay().width();
|
||||||
|
dHeight = getdisplay().height();
|
||||||
|
|
||||||
|
if (chrtDir == 0) {
|
||||||
|
// horizontal chart timeline direction
|
||||||
|
timAxis = dWidth;
|
||||||
|
switch (chrtSz) {
|
||||||
|
case 0:
|
||||||
|
valAxis = dHeight - top - bottom;
|
||||||
|
cStart = { 0, top };
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
valAxis = (dHeight - top - bottom) / 2 - hGap;
|
||||||
|
cStart = { 0, top };
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
valAxis = (dHeight - top - bottom) / 2 - hGap;
|
||||||
|
cStart = { 0, top + (valAxis + hGap) + hGap };
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (chrtDir == 1) {
|
||||||
|
// vertical chart timeline direction
|
||||||
|
timAxis = dHeight - top - bottom;
|
||||||
|
switch (chrtSz) {
|
||||||
|
case 0:
|
||||||
|
valAxis = dWidth;
|
||||||
|
cStart = { 0, top };
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
valAxis = dWidth / 2 - vGap - 1;
|
||||||
|
cStart = { 0, top };
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
valAxis = dWidth / 2 - vGap - 1;
|
||||||
|
cStart = { dWidth / 2 + vGap, top };
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataBuf.getMetaData(dbName, dbFormat);
|
||||||
|
dbMIN_VAL = dataBuf.getMinVal();
|
||||||
|
dbMAX_VAL = dataBuf.getMaxVal();
|
||||||
|
bufSize = dataBuf.getCapacity();
|
||||||
|
|
||||||
|
if (dbFormat == "formatCourse" || dbFormat == "FormatWind" || dbFormat == "FormatRot") {
|
||||||
|
|
||||||
|
if (dbFormat == "FormatRot") {
|
||||||
|
chrtDataFmt = 2; // Chart is showing data of rotational <degree> format
|
||||||
|
} else {
|
||||||
|
chrtDataFmt = 1; // Chart is showing data of course / wind <degree> format
|
||||||
|
}
|
||||||
|
rngStep = M_TWOPI / 360.0 * 10.0; // +/-10 degrees on each end of chrtMid; we are calculating with SI values
|
||||||
|
|
||||||
|
} else {
|
||||||
|
chrtDataFmt = 0; // Chart is showing any other data format than <degree>
|
||||||
|
rngStep = 5.0; // +/- 10 for all other values (eg. m/s, m, K, mBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
chrtMin = 0;
|
||||||
|
chrtMax = 0;
|
||||||
|
chrtMid = dbMAX_VAL;
|
||||||
|
chrtRng = dfltRng;
|
||||||
|
recalcRngCntr = true; // initialize <chrtMid> on first screen call
|
||||||
|
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "Chart Init: dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cStart {x,y}: %d, %d, dbname: %s, rngStep: %.4f", dWidth, dHeight, timAxis, valAxis, cStart.x, cStart.y, dbName, rngStep);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
Chart<T>::~Chart()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform all actions to draw chart
|
||||||
|
// Parameters are chart time interval, and the current boat data value to be printed
|
||||||
|
template <typename T>
|
||||||
|
void Chart<T>::showChrt(int8_t chrtIntv, GwApi::BoatValue currValue)
|
||||||
|
{
|
||||||
|
drawChrt(chrtIntv, currValue);
|
||||||
|
drawChrtTimeAxis(chrtIntv);
|
||||||
|
drawChrtValAxis();
|
||||||
|
|
||||||
|
if (bufDataValid) {
|
||||||
|
// uses BoatValue temp variable <currValue> to format latest buffer value
|
||||||
|
// doesn't work unfortunately when 'simulation data' is active, because OBP60Formatter generates own simulation value in that case
|
||||||
|
currValue.value = dataBuf.getLast();
|
||||||
|
currValue.valid = currValue.value != dbMAX_VAL;
|
||||||
|
Chart<T>::prntCurrValue(currValue);
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "Chart drawChrt: currValue-value: %.1f, Valid: %d, Name: %s, Address: %p", currValue.value, currValue.valid, currValue.getName(), (void*)&currValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw chart
|
||||||
|
template <typename T>
|
||||||
|
void Chart<T>::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue)
|
||||||
|
{
|
||||||
|
double chrtVal; // Current data value
|
||||||
|
double chrtScl; // Scale for data values in pixels per value
|
||||||
|
static double chrtPrevVal; // Last data value in chart area
|
||||||
|
// bool bufDataValid = false; // Flag to indicate if buffer data is valid
|
||||||
|
static int numNoData; // Counter for multiple invalid data values in a row
|
||||||
|
|
||||||
|
int x, y; // x and y coordinates for drawing
|
||||||
|
static int prevX, prevY; // Last x and y coordinates for drawing
|
||||||
|
|
||||||
|
// Identify buffer size and buffer start position for chart
|
||||||
|
count = dataBuf.getCurrentSize();
|
||||||
|
currIdx = dataBuf.getLastIdx();
|
||||||
|
numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display
|
||||||
|
|
||||||
|
if (chrtIntv != oldChrtIntv || count == 1) {
|
||||||
|
// new data interval selected by user; this is only x * 230 values instead of 240 seconds (4 minutes) per interval step
|
||||||
|
// intvBufSize = timAxis * chrtIntv; // obsolete
|
||||||
|
numBufVals = min(count, (timAxis - 60) * chrtIntv); // keep free or release 60 values on chart for plotting of new values
|
||||||
|
bufStart = max(0, count - numBufVals);
|
||||||
|
lastAddedIdx = currIdx;
|
||||||
|
oldChrtIntv = chrtIntv;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
numBufVals = numBufVals + numAddedBufVals;
|
||||||
|
lastAddedIdx = currIdx;
|
||||||
|
if (count == bufSize) {
|
||||||
|
bufStart = max(0, bufStart - numAddedBufVals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
calcChrtBorders(chrtMid, chrtMin, chrtMax, chrtRng);
|
||||||
|
chrtScl = double(valAxis) / chrtRng; // Chart scale: pixels per value step
|
||||||
|
|
||||||
|
// Do we have valid buffer data?
|
||||||
|
if (dataBuf.getMax() == dbMAX_VAL) { // only <MAX_VAL> values in buffer -> no valid wind data available
|
||||||
|
bufDataValid = false;
|
||||||
|
} else if (!currValue.valid && !useSimuData) { // currently no valid boat data available and no simulation mode
|
||||||
|
numNoData++;
|
||||||
|
bufDataValid = true;
|
||||||
|
if (numNoData > 3) { // If more than 4 invalid values in a row, send message
|
||||||
|
bufDataValid = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
numNoData = 0; // reset data error counter
|
||||||
|
bufDataValid = true; // At least some wind data available
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw wind values in chart
|
||||||
|
//***********************************************************************
|
||||||
|
if (bufDataValid) {
|
||||||
|
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
|
||||||
|
if (chrtVal == dbMAX_VAL) {
|
||||||
|
chrtPrevVal = dbMAX_VAL;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (chrtDir == 0) { // horizontal chart
|
||||||
|
x = cStart.x + i; // Position in chart area
|
||||||
|
if (chrtDataFmt == 0) {
|
||||||
|
y = cStart.y + static_cast<int>(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round
|
||||||
|
} else { // degree type value
|
||||||
|
y = cStart.y + static_cast<int>((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round
|
||||||
|
}
|
||||||
|
} else { // vertical chart
|
||||||
|
y = cStart.y + timAxis - i; // Position in chart area
|
||||||
|
if (chrtDataFmt == 0) {
|
||||||
|
x = cStart.x + static_cast<int>(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round
|
||||||
|
} else { // degree type value
|
||||||
|
x = cStart.x + static_cast<int>((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (i >= (numBufVals / chrtIntv) - 5) // log chart data of 1 line (adjust for test purposes)
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %.4f, {x,y} {%d,%d}", i, chrtVal, x, y);
|
||||||
|
|
||||||
|
if ((i == 0) || (chrtPrevVal == dbMAX_VAL)) {
|
||||||
|
// just a dot for 1st chart point or after some invalid values
|
||||||
|
prevX = x;
|
||||||
|
prevY = y;
|
||||||
|
|
||||||
|
} else if (chrtDataFmt != 0) {
|
||||||
|
// cross borders check for degree values; shift values to [-PI..0..PI]; when crossing borders, range is 2x PI degrees
|
||||||
|
double normCurr = WindUtils::to2PI(chrtVal - chrtMin);
|
||||||
|
double normPrev = WindUtils::to2PI(chrtPrevVal - chrtMin);
|
||||||
|
// Check if pixel positions are far apart (crossing chart boundary); happens when one value is near chrtMax and the other near chrtMin
|
||||||
|
bool crossedBorders = std::abs(normCurr - normPrev) > (chrtRng / 2.0);
|
||||||
|
|
||||||
|
if (crossedBorders) { // If current value crosses chart borders compared to previous value, split line
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: crossedBorders: %d, chrtVal: %.2f, chrtPrevVal: %.2f", crossedBorders, chrtVal, chrtPrevVal);
|
||||||
|
bool wrappingFromHighToLow = normCurr < normPrev; // Determine which edge we're crossing
|
||||||
|
if (chrtDir == 0) {
|
||||||
|
int ySplit = wrappingFromHighToLow ? (cStart.y + valAxis) : cStart.y;
|
||||||
|
getdisplay().drawLine(prevX, prevY, x, ySplit, fgColor);
|
||||||
|
if (x != prevX) { // line with some horizontal trend
|
||||||
|
getdisplay().drawLine(prevX, prevY - 1, x, ySplit - 1, fgColor);
|
||||||
|
} else {
|
||||||
|
getdisplay().drawLine(prevX, prevY - 1, x - 1, ySplit, fgColor);
|
||||||
|
}
|
||||||
|
prevY = wrappingFromHighToLow ? cStart.y : (cStart.y + valAxis);
|
||||||
|
} else { // vertical chart
|
||||||
|
int xSplit = wrappingFromHighToLow ? (cStart.x + valAxis) : cStart.x;
|
||||||
|
getdisplay().drawLine(prevX, prevY, xSplit, y, fgColor);
|
||||||
|
getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), fgColor);
|
||||||
|
prevX = wrappingFromHighToLow ? cStart.x : (cStart.x + valAxis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw line with 2 pixels width + make sure vertical lines are drawn correctly
|
||||||
|
if (chrtDir == 0 || x == prevX) { // horizontal chart & vertical line
|
||||||
|
// if (x == prevX) { // vertical line
|
||||||
|
getdisplay().drawLine(prevX, prevY, x, y, fgColor);
|
||||||
|
getdisplay().drawLine(prevX - 1, prevY, x - 1, y, fgColor);
|
||||||
|
} else if (chrtDir == 1 || x != prevX) { // vertical chart & line with some horizontal trend -> normal state
|
||||||
|
// } else { // line with some horizontal trend -> normal state
|
||||||
|
getdisplay().drawLine(prevX, prevY, x, y, fgColor);
|
||||||
|
getdisplay().drawLine(prevX, prevY - 1, x, y - 1, fgColor);
|
||||||
|
}
|
||||||
|
chrtPrevVal = chrtVal;
|
||||||
|
prevX = x;
|
||||||
|
prevY = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reaching chart area bottom end
|
||||||
|
if (i >= timAxis - 1) {
|
||||||
|
oldChrtIntv = 0; // force reset of buffer start and number of values to show in next display loop
|
||||||
|
|
||||||
|
if (chrtDataFmt == 1) { // degree of course or wind
|
||||||
|
recalcRngCntr = true;
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngCntr);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// No valid data available
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold10pt8b);
|
||||||
|
|
||||||
|
int pX, pY;
|
||||||
|
if (chrtDir == 0) { // horizontal chart
|
||||||
|
pX = cStart.x + (timAxis / 2);
|
||||||
|
pY = cStart.y + (valAxis / 2) - 10;
|
||||||
|
} else { // vertical chart
|
||||||
|
pX = cStart.x + (valAxis / 2);
|
||||||
|
pY = cStart.y + (timAxis / 2) - 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().fillRect(pX - 33, pY - 10, 66, 24, bgColor); // Clear area for message
|
||||||
|
drawTextCenter(pX, pY, "No data");
|
||||||
|
LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get maximum difference of last <amount> of dataBuf ringbuffer values to center chart
|
||||||
|
template <typename T>
|
||||||
|
double Chart<T>::getRng(double center, size_t amount)
|
||||||
|
{
|
||||||
|
size_t count = dataBuf.getCurrentSize();
|
||||||
|
|
||||||
|
if (dataBuf.isEmpty() || amount <= 0) {
|
||||||
|
return dbMAX_VAL;
|
||||||
|
}
|
||||||
|
if (amount > count)
|
||||||
|
amount = count;
|
||||||
|
|
||||||
|
double value = 0;
|
||||||
|
double range = 0;
|
||||||
|
double maxRng = dbMIN_VAL;
|
||||||
|
|
||||||
|
// Start from the newest value (last) and go backwards x times
|
||||||
|
for (size_t i = 0; i < amount; i++) {
|
||||||
|
value = dataBuf.get(count - 1 - i);
|
||||||
|
|
||||||
|
if (value == dbMAX_VAL) {
|
||||||
|
continue; // ignore invalid values
|
||||||
|
}
|
||||||
|
|
||||||
|
range = abs(fmod((value - center + (M_TWOPI + M_PI)), M_TWOPI) - M_PI);
|
||||||
|
if (range > maxRng)
|
||||||
|
maxRng = range;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxRng > M_PI) {
|
||||||
|
maxRng = M_PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (maxRng != dbMIN_VAL ? maxRng : dbMAX_VAL); // Return range from <mid> to <max>
|
||||||
|
}
|
||||||
|
|
||||||
|
// check and adjust chart range and set range borders and range middle
|
||||||
|
template <typename T>
|
||||||
|
void Chart<T>::calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, double& rng)
|
||||||
|
{
|
||||||
|
if (chrtDataFmt == 0) {
|
||||||
|
// Chart data is of any type but 'degree'
|
||||||
|
|
||||||
|
double oldRngMin = rngMin;
|
||||||
|
double oldRngMax = rngMax;
|
||||||
|
|
||||||
|
// Chart starts at lowest range value, but at least '0' or includes even negative values
|
||||||
|
double currMinVal = dataBuf.getMin(numBufVals);
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "calcChrtRange0a: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, oldRngMin: %.1f, oldRngMax: %.1f, dfltRng: %.1f, numBufVals: %d",
|
||||||
|
currMinVal, dataBuf.getMax(numBufVals), rngMin, rngMid, rngMax, rng, rngStep, oldRngMin, oldRngMax, dfltRng, numBufVals);
|
||||||
|
|
||||||
|
if (currMinVal != dbMAX_VAL) { // current min value is valid
|
||||||
|
if (currMinVal > 0 && dbMIN_VAL == 0) { // Chart range starts at least at '0' or includes negative values
|
||||||
|
rngMin = 0;
|
||||||
|
} else if (currMinVal < oldRngMin || (oldRngMin < 0 && (currMinVal > (oldRngMin + rngStep)))) { // decrease rngMin if required or increase if lowest value is higher than old rngMin
|
||||||
|
rngMin = std::floor(currMinVal / rngStep) * rngStep;
|
||||||
|
}
|
||||||
|
} // otherwise keep rngMin unchanged
|
||||||
|
|
||||||
|
double currMaxVal = dataBuf.getMax(numBufVals);
|
||||||
|
if (currMaxVal != dbMAX_VAL) { // current max value is valid
|
||||||
|
if ((currMaxVal > oldRngMax) || (currMaxVal < (oldRngMax - rngStep))) { // increase rngMax if required or decrease if lowest value is lower than old rngMax
|
||||||
|
rngMax = std::ceil(currMaxVal / rngStep) * rngStep;
|
||||||
|
rngMax = std::max(rngMax, rngMin + dfltRng); // keep at least default chart range
|
||||||
|
}
|
||||||
|
} // otherwise keep rngMax unchanged
|
||||||
|
|
||||||
|
rngMid = (rngMin + rngMax) / 2.0;
|
||||||
|
rng = rngMax - rngMin;
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "calcChrtRange1a: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, oldRngMin: %.1f, oldRngMax: %.1f, dfltRng: %.1f, numBufVals: %d",
|
||||||
|
currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, oldRngMin, oldRngMax, dfltRng, numBufVals);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (chrtDataFmt == 1) {
|
||||||
|
// Chart data is of type 'course' or 'wind'
|
||||||
|
|
||||||
|
if ((count == 1 && rngMid == 0) || rngMid == dbMAX_VAL) {
|
||||||
|
recalcRngCntr = true; // initialize <rngMid>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set rngMid
|
||||||
|
if (recalcRngCntr) {
|
||||||
|
rngMid = dataBuf.getMid(numBufVals);
|
||||||
|
if (rngMid == dbMAX_VAL) {
|
||||||
|
rngMid = 0;
|
||||||
|
} else {
|
||||||
|
rngMid = std::round(rngMid / rngStep) * rngStep; // Set new center value; round to next <rngStep> value
|
||||||
|
|
||||||
|
// Check if range between 'min' and 'max' is > 180° or crosses '0'
|
||||||
|
rngMin = dataBuf.getMin(numBufVals);
|
||||||
|
rngMax = dataBuf.getMax(numBufVals);
|
||||||
|
rng = (rngMax >= rngMin ? rngMax - rngMin : M_TWOPI - rngMin + rngMax);
|
||||||
|
rng = max(rng, dfltRng); // keep at least default chart range
|
||||||
|
if (rng > M_PI) { // If wind range > 180°, adjust wndCenter to smaller wind range end
|
||||||
|
rngMid = WindUtils::to2PI(rngMid + M_PI);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recalcRngCntr = false; // Reset flag for <rngMid> determination
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "calcChrtRange1b: rngMid: %.1f°, rngMin: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMid * RAD_TO_DEG, rngMin * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||||
|
rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (chrtDataFmt == 2) {
|
||||||
|
// Chart data is of type 'rotation'; then we want to have <rndMid> always to be '0'
|
||||||
|
rngMid = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check and adjust range between left, center, and right chart limit
|
||||||
|
double halfRng = rng / 2.0; // we calculate with range between <rngMid> and edges
|
||||||
|
double diffRng = getRng(rngMid, numBufVals);
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG);
|
||||||
|
diffRng = (diffRng == dbMAX_VAL ? 0 : std::ceil(diffRng / rngStep) * rngStep);
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG);
|
||||||
|
|
||||||
|
if (diffRng > halfRng) {
|
||||||
|
halfRng = diffRng; // round to next <rngStep> value
|
||||||
|
} else if (diffRng + rngStep < halfRng) { // Reduce chart range for higher resolution if possible
|
||||||
|
halfRng = max(dfltRng / 2.0, diffRng);
|
||||||
|
}
|
||||||
|
|
||||||
|
rngMin = WindUtils::to2PI(rngMid - halfRng);
|
||||||
|
rngMax = (halfRng < M_PI ? rngMid + halfRng : rngMid + halfRng - (M_TWOPI / 360)); // if chart range is 360°, then make <rngMax> 1° smaller than <rngMin>
|
||||||
|
rngMax = WindUtils::to2PI(rngMax);
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG);
|
||||||
|
|
||||||
|
rng = halfRng * 2.0;
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2b: rngMid: %.1f°, rngMin: %.1f°, rngMax: %.1f°, diffRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMid * RAD_TO_DEG, rngMin * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||||
|
diffRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// chart time axis label + lines
|
||||||
|
template <typename T>
|
||||||
|
void Chart<T>::drawChrtTimeAxis(int8_t chrtIntv)
|
||||||
|
{
|
||||||
|
int timeRng;
|
||||||
|
float slots, intv, i;
|
||||||
|
char sTime[6];
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setTextColor(fgColor);
|
||||||
|
|
||||||
|
if (chrtDir == 0) { // horizontal chart
|
||||||
|
getdisplay().fillRect(0, cStart.y, dWidth, 2, fgColor);
|
||||||
|
|
||||||
|
timeRng = chrtIntv * 4; // Chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min.
|
||||||
|
slots = timAxis / 80.0; // number of axis labels
|
||||||
|
intv = timeRng / slots; // minutes per chart axis interval
|
||||||
|
i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes
|
||||||
|
|
||||||
|
for (int j = 0; j < timAxis - 30; j += 80) { // fill time axis with values but keep area free on right hand side for value label
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "ChartTimeAxis: timAxis: %d, {x,y}: {%d,%d}, i: %.1f, j: %d, chrtIntv: %d, intv: %.1f, slots: %.1f", timAxis, cStart.x, cStart.y, i, j, chrtIntv, intv, slots);
|
||||||
|
|
||||||
|
// Format time label based on interval
|
||||||
|
if (chrtIntv < 3) {
|
||||||
|
snprintf(sTime, sizeof(sTime), "-%.1f", i);
|
||||||
|
} else {
|
||||||
|
snprintf(sTime, sizeof(sTime), "-%.0f", std::round(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw text with appropriate offset
|
||||||
|
// int tOffset = (j == 0) ? 13 : (chrtIntv < 3 ? -4 : -4);
|
||||||
|
int tOffset = j == 0 ? 13 : -4;
|
||||||
|
drawTextCenter(cStart.x + j + tOffset, cStart.y - 8, sTime);
|
||||||
|
getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + 5, fgColor); // draw short vertical time mark
|
||||||
|
|
||||||
|
i -= intv;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // vertical chart
|
||||||
|
timeRng = chrtIntv * 4; // chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min.
|
||||||
|
slots = timAxis / 75.0; // number of axis labels
|
||||||
|
intv = timeRng / slots; // minutes per chart axis interval
|
||||||
|
i = -intv; // chart axis label start at -32, -16, -12, ... minutes
|
||||||
|
|
||||||
|
for (int j = 75; j < (timAxis - 75); j += 75) { // don't print time label at upper and lower end of time axis
|
||||||
|
if (chrtIntv < 3) { // print 1 decimal if time range is single digit (4 or 8 minutes)
|
||||||
|
snprintf(sTime, sizeof(sTime), "%.1f", i);
|
||||||
|
} else {
|
||||||
|
snprintf(sTime, sizeof(sTime), "%.0f", std::floor(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + valAxis, cStart.y + j, fgColor); // Grid line
|
||||||
|
|
||||||
|
if (chrtSz == 0) { // full size chart
|
||||||
|
getdisplay().fillRect(0, cStart.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines
|
||||||
|
getdisplay().setCursor((4 - strlen(sTime)) * 7, cStart.y + j + 3); // time value; print left screen; value right-formated
|
||||||
|
getdisplay().printf("%s", sTime); // Range value
|
||||||
|
} else if (chrtSz == 2) { // half size chart; right side
|
||||||
|
drawTextCenter(dWidth / 2, cStart.y + j, sTime); // time value; print mid screen
|
||||||
|
}
|
||||||
|
|
||||||
|
i -= intv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// chart value axis labels + lines
|
||||||
|
template <typename T>
|
||||||
|
void Chart<T>::drawChrtValAxis()
|
||||||
|
{
|
||||||
|
double slots;
|
||||||
|
int i, intv;
|
||||||
|
double cVal, cchrtRng, crngMin;
|
||||||
|
char sVal[6];
|
||||||
|
int sLen;
|
||||||
|
std::unique_ptr<GwApi::BoatValue> tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter
|
||||||
|
tmpBVal = std::unique_ptr<GwApi::BoatValue>(new GwApi::BoatValue(dataBuf.getName()));
|
||||||
|
tmpBVal->setFormat(dataBuf.getFormat());
|
||||||
|
tmpBVal->valid = true;
|
||||||
|
|
||||||
|
if (chrtDir == 0) { // horizontal chart
|
||||||
|
slots = valAxis / 60.0; // number of axis labels
|
||||||
|
tmpBVal->value = chrtRng;
|
||||||
|
cchrtRng = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted)
|
||||||
|
intv = static_cast<int>(round(cchrtRng / slots));
|
||||||
|
i = intv;
|
||||||
|
|
||||||
|
if (chrtSz == 0) { // full size chart -> print multiple value lines
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
for (int j = 60; j < valAxis - 30; j += 60) {
|
||||||
|
getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + timAxis, cStart.y + j, fgColor);
|
||||||
|
|
||||||
|
getdisplay().fillRect(cStart.x, cStart.y + j - 11, 42, 21, bgColor); // Clear small area to remove potential chart lines
|
||||||
|
String sVal = String(i);
|
||||||
|
getdisplay().setCursor((3 - sVal.length()) * 10, cStart.y + j + 7); // value right-formated
|
||||||
|
getdisplay().printf("%s", sVal); // Range value
|
||||||
|
|
||||||
|
i += intv;
|
||||||
|
}
|
||||||
|
} else { // half size chart -> print just edge values + middle chart line
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold10pt8b);
|
||||||
|
|
||||||
|
tmpBVal->value = chrtMin;
|
||||||
|
cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted)
|
||||||
|
sLen = snprintf(sVal, sizeof(sVal), "%.0f", round(cVal));
|
||||||
|
getdisplay().fillRect(cStart.x, cStart.y + 2, 42, 16, bgColor); // Clear small area to remove potential chart lines
|
||||||
|
getdisplay().setCursor(cStart.x + ((3 - sLen) * 10), cStart.y + 16);
|
||||||
|
getdisplay().printf("%s", sVal); // Range low end
|
||||||
|
|
||||||
|
tmpBVal->value = chrtMid;
|
||||||
|
cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted)
|
||||||
|
sLen = snprintf(sVal, sizeof(sVal), "%.0f", round(cVal));
|
||||||
|
getdisplay().fillRect(cStart.x, cStart.y + (valAxis / 2) - 9, 42, 16, bgColor); // Clear small area to remove potential chart lines
|
||||||
|
getdisplay().setCursor(cStart.x + ((3 - sLen) * 10), cStart.y + (valAxis / 2) + 5);
|
||||||
|
getdisplay().printf("%s", sVal); // Range mid value
|
||||||
|
getdisplay().drawLine(cStart.x + 43, cStart.y + (valAxis / 2), cStart.x + timAxis, cStart.y + (valAxis / 2), fgColor);
|
||||||
|
|
||||||
|
tmpBVal->value = chrtMax;
|
||||||
|
cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted)
|
||||||
|
sLen = snprintf(sVal, sizeof(sVal), "%.0f", round(cVal));
|
||||||
|
getdisplay().fillRect(cStart.x, cStart.y + valAxis - 16, 42, 16, bgColor); // Clear small area to remove potential chart lines
|
||||||
|
getdisplay().setCursor(cStart.x + ((3 - sLen) * 10), cStart.y + valAxis - 1);
|
||||||
|
getdisplay().printf("%s", sVal); // Range high end
|
||||||
|
getdisplay().drawLine(cStart.x + 43, cStart.y + valAxis, cStart.x + timAxis, cStart.y + valAxis, fgColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
drawTextRalign(cStart.x + timAxis, cStart.y - 3, dbName); // buffer data name
|
||||||
|
|
||||||
|
} else { // vertical chart
|
||||||
|
if (chrtSz == 0) { // full size chart -> use larger font
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
drawTextCenter(cStart.x + (valAxis / 4) + 25, cStart.y - 10, dbName); // buffer data name
|
||||||
|
} else {
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold10pt8b);
|
||||||
|
}
|
||||||
|
getdisplay().fillRect(cStart.x, top, valAxis, 2, fgColor); // top chart line
|
||||||
|
|
||||||
|
tmpBVal->value = chrtMin;
|
||||||
|
cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted)
|
||||||
|
snprintf(sVal, sizeof(sVal), "%.0f", round(cVal));
|
||||||
|
getdisplay().setCursor(cStart.x, cStart.y - 2);
|
||||||
|
getdisplay().printf("%s", sVal); // Range low end
|
||||||
|
|
||||||
|
tmpBVal->value = chrtMid;
|
||||||
|
cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted)
|
||||||
|
snprintf(sVal, sizeof(sVal), "%.0f", round(cVal));
|
||||||
|
drawTextCenter(cStart.x + (valAxis / 2), cStart.y - 10, sVal); // Range mid end
|
||||||
|
|
||||||
|
tmpBVal->value = chrtMax;
|
||||||
|
cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted)
|
||||||
|
snprintf(sVal, sizeof(sVal), "%.0f", round(cVal));
|
||||||
|
drawTextRalign(cStart.x + valAxis - 2, cStart.y - 2, sVal); // Range high end
|
||||||
|
|
||||||
|
for (int j = 0; j <= valAxis + 2; j += ((valAxis + 2) / 2)) {
|
||||||
|
getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + timAxis, fgColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (chrtSz == 0) {
|
||||||
|
// getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
// drawTextCenter(cStart.x + (valAxis / 4) + 15, cStart.y - 11, dbName); // buffer data name
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print current data value
|
||||||
|
template <typename T>
|
||||||
|
void Chart<T>::prntCurrValue(GwApi::BoatValue& currValue)
|
||||||
|
{
|
||||||
|
const int xPosVal = (chrtDir == 0) ? cStart.x + (timAxis / 2) - 56 : cStart.x + 32;
|
||||||
|
const int yPosVal = (chrtDir == 0) ? cStart.y + valAxis - 7 : cStart.y + timAxis - 7;
|
||||||
|
|
||||||
|
FormattedData frmtDbData = formatValue(&currValue, *commonData);
|
||||||
|
double testdbValue = frmtDbData.value;
|
||||||
|
String sdbValue = frmtDbData.svalue; // value (string)
|
||||||
|
String dbUnit = frmtDbData.unit; // Unit of value
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "Chart CurrValue: dbValue: %.2f, sdbValue: %s, fmrtDbValue: %.2f, dbFormat: %s, dbUnit: %s, Valid: %d, Name: %s, Address: %p", currValue.value, sdbValue,
|
||||||
|
// testdbValue, currValue.getFormat(), dbUnit, currValue.valid, currValue.getName(), currValue);
|
||||||
|
|
||||||
|
getdisplay().fillRect(xPosVal - 1, yPosVal - 34, 125, 41, bgColor); // Clear area for TWS value
|
||||||
|
getdisplay().drawRect(xPosVal, yPosVal - 33, 123, 39, fgColor); // Draw box for TWS value
|
||||||
|
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||||
|
getdisplay().setCursor(xPosVal + 1, yPosVal);
|
||||||
|
if (useSimuData) {
|
||||||
|
getdisplay().printf("%2.1f", currValue.value); // Value
|
||||||
|
} else {
|
||||||
|
getdisplay().print(sdbValue); // Value
|
||||||
|
}
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold10pt8b);
|
||||||
|
getdisplay().setCursor(xPosVal + 76, yPosVal - 17);
|
||||||
|
getdisplay().print(dbName); // Name
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(xPosVal + 76, yPosVal + 0);
|
||||||
|
getdisplay().print(dbUnit); // Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly instantiate class with required data types to avoid linker errors
|
||||||
|
template class Chart<uint16_t>;
|
||||||
|
// --- Class Chart ---------------
|
||||||
70
lib/obp60task/OBPcharts.h
Normal file
70
lib/obp60task/OBPcharts.h
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// Function lib for display of boat data in various graphical chart formats
|
||||||
|
#pragma once
|
||||||
|
#include "Pagedata.h"
|
||||||
|
|
||||||
|
struct Pos {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
};
|
||||||
|
template <typename T> class RingBuffer;
|
||||||
|
class GwLog;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Chart {
|
||||||
|
protected:
|
||||||
|
CommonData *commonData;
|
||||||
|
GwLog *logger;
|
||||||
|
|
||||||
|
RingBuffer<T> &dataBuf; // Buffer to display
|
||||||
|
int8_t chrtDir; // Chart timeline direction: [0] = horizontal, [1] = 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]
|
||||||
|
uint16_t fgColor; // color code for any screen writing
|
||||||
|
uint16_t bgColor; // color code for screen background
|
||||||
|
bool useSimuData; // flag to indicate if simulation data is active
|
||||||
|
|
||||||
|
int top = 48; // display top header lines
|
||||||
|
int bottom = 22; // display bottom lines
|
||||||
|
int hGap = 11; // gap between 2 horizontal charts; actual gap is 2x <gap>
|
||||||
|
int vGap = 20; // gap between 2 vertical charts; actual gap is 2x <gap>
|
||||||
|
int xOffset = 33; // offset for horizontal axis (time/value), because of space for left vertical axis labeling
|
||||||
|
int yOffset = 10; // offset for vertical axis (time/value), because of space for top horizontal axis labeling
|
||||||
|
int dWidth; // Display width
|
||||||
|
int dHeight; // Display height
|
||||||
|
int timAxis, valAxis; // size of time and value chart axis
|
||||||
|
Pos cStart; // start point of chart area
|
||||||
|
double chrtRng; // Range of buffer values from min to max value
|
||||||
|
double chrtMin; // Range low end value
|
||||||
|
double chrtMax; // Range high end value
|
||||||
|
double chrtMid; // Range mid value
|
||||||
|
double rngStep; // Defines the step of adjustment (e.g. 10 m/s) for value axis range
|
||||||
|
bool recalcRngCntr = false; // Flag for re-calculation of mid value of chart for wind data types
|
||||||
|
|
||||||
|
String dbName, dbFormat; // Name and format of data buffer
|
||||||
|
int chrtDataFmt; // Data format of chart: [0] size values; [1] degree of course or wind; [2] rotational degrees
|
||||||
|
double dbMIN_VAL; // Lowest possible value of buffer of type <T>
|
||||||
|
double dbMAX_VAL; // Highest possible value of buffer of type <T>; indicates invalid value in buffer
|
||||||
|
size_t bufSize; // History buffer size: 1.920 values for 32 min. history chart
|
||||||
|
int count; // current size of buffer
|
||||||
|
int numBufVals; // number of wind values available for current interval selection
|
||||||
|
int bufStart; // 1st data value in buffer to show
|
||||||
|
int numAddedBufVals; // Number of values added to buffer since last display
|
||||||
|
size_t currIdx; // Current index in TWD history buffer
|
||||||
|
size_t lastIdx; // Last index of TWD history buffer
|
||||||
|
size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added
|
||||||
|
bool bufDataValid = false; // Flag to indicate if buffer data is valid
|
||||||
|
int oldChrtIntv = 0; // remember recent user selection of data interval
|
||||||
|
|
||||||
|
void drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue); // Draw chart line
|
||||||
|
double getRng(double center, size_t amount); // Calculate range between chart center and edges
|
||||||
|
void calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, double& rng); // Calculate chart points for value axis and return range between <min> and <max>
|
||||||
|
void drawChrtTimeAxis(int8_t chrtIntv); // Draw time axis of chart, value and lines
|
||||||
|
void drawChrtValAxis(); // Draw value axis of chart, value and lines
|
||||||
|
void prntCurrValue(GwApi::BoatValue& currValue); // Add current boat data value to chart
|
||||||
|
|
||||||
|
public:
|
||||||
|
Chart(RingBuffer<T>& dataBuf, int8_t chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData); // Chart object of data chart
|
||||||
|
~Chart();
|
||||||
|
void showChrt(int8_t chrtIntv, GwApi::BoatValue currValue); // Perform all actions to draw chart
|
||||||
|
|
||||||
|
};
|
||||||
@@ -20,7 +20,7 @@ class PageBME280 : public Page
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -176,9 +176,7 @@ class PageBME280 : public Page
|
|||||||
// Show bus data
|
// Show bus data
|
||||||
getdisplay().print(svalue3); // Real value as formated string
|
getdisplay().print(svalue3); // Real value as formated string
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
class PageBattery : public Page
|
class PageBattery : public Page
|
||||||
{
|
{
|
||||||
int average = 0; // Average type [0...3], 0=off, 1=10s, 2=60s, 3=300s
|
int average = 0; // Average type [0...3], 0=off, 1=10s, 2=60s, 3=300s
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PageBattery(CommonData &common){
|
PageBattery(CommonData &common){
|
||||||
@@ -34,7 +34,7 @@ class PageBattery : public Page
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -288,9 +288,7 @@ class PageBattery : public Page
|
|||||||
getdisplay().print("---"); // No sensor data (sensor is off)
|
getdisplay().print("---"); // No sensor data (sensor is off)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public:
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData)
|
int displayPage(PageData &pageData)
|
||||||
{
|
{
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
@@ -334,8 +334,7 @@ public:
|
|||||||
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
||||||
getdisplay().print("W");
|
getdisplay().print("W");
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ bool homevalid = false; // homelat and homelon are valid
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData)
|
int displayPage(PageData &pageData)
|
||||||
{
|
{
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
@@ -120,7 +120,7 @@ bool homevalid = false; // homelat and homelon are valid
|
|||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
value1 = simtime++; // Simulation data for time value 11:36 in seconds
|
value1 = simtime++; // Simulation data for time value 11:36 in seconds
|
||||||
} // Other simulation data see OBP60Formater.cpp
|
} // Other simulation data see OBP60Formatter.cpp
|
||||||
bool valid1 = bvalue1->valid; // Valid information
|
bool valid1 = bvalue1->valid; // Valid information
|
||||||
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
||||||
@@ -162,7 +162,7 @@ bool homevalid = false; // homelat and homelon are valid
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging boat values
|
// Logging boat values
|
||||||
if (bvalue1 == NULL) return;
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageClock, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2);
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageClock, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -438,9 +438,7 @@ bool homevalid = false; // homelat and homelon are valid
|
|||||||
getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor);
|
getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor);
|
||||||
getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor);
|
getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor);
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class PageCompass : public Page
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ class PageCompass : public Page
|
|||||||
String DataText[HowManyValues];
|
String DataText[HowManyValues];
|
||||||
String DataUnits[HowManyValues];
|
String DataUnits[HowManyValues];
|
||||||
String DataFormat[HowManyValues];
|
String DataFormat[HowManyValues];
|
||||||
FormatedData TheFormattedData;
|
FormattedData TheFormattedData;
|
||||||
|
|
||||||
for (int i = 0; i < HowManyValues; i++){
|
for (int i = 0; i < HowManyValues; i++){
|
||||||
bvalue = pageData.values[i];
|
bvalue = pageData.values[i];
|
||||||
@@ -104,7 +104,7 @@ class PageCompass : public Page
|
|||||||
setFlashLED(false);
|
setFlashLED(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bvalue == NULL) return;
|
if (bvalue == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
|
|
||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
@@ -235,12 +235,11 @@ class PageCompass : public Page
|
|||||||
// if ( x_test > 390)
|
// if ( x_test > 390)
|
||||||
// x_test = 320;
|
// x_test = 320;
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
static Page *createPage(CommonData &common){
|
static Page *createPage(CommonData &common){
|
||||||
return new PageCompass(common);
|
return new PageCompass(common);
|
||||||
}/**
|
}/**
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public:
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging boat values
|
// Logging boat values
|
||||||
if (bvalue1 == NULL) return;
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageDST810, %s: %f, %s: %f, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4);
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageDST810, %s: %f, %s: %f, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -242,9 +242,7 @@ public:
|
|||||||
unit4old = unit4; // Save the old unit
|
unit4old = unit4; // Save the old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
131
lib/obp60task/PageDigitalOut.cpp
Normal file
131
lib/obp60task/PageDigitalOut.cpp
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||||
|
|
||||||
|
#include <PCF8574.h> // PCF8574 modules from Horter
|
||||||
|
#include "Pagedata.h"
|
||||||
|
#include "OBP60Extensions.h"
|
||||||
|
|
||||||
|
#include "images/OBP_400x300.xbm" // OBP Logo
|
||||||
|
#ifdef BOARD_OBP60S3
|
||||||
|
#include "images/OBP60_400x300.xbm" // MFD with logo
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
#include "images/OBP40_400x300.xbm" // MFD with logo
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class PageDigitalOut : public Page
|
||||||
|
{
|
||||||
|
|
||||||
|
// Status values
|
||||||
|
bool button1 = false;
|
||||||
|
bool button2 = false;
|
||||||
|
bool button3 = false;
|
||||||
|
bool button4 = false;
|
||||||
|
bool button5 = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PageDigitalOut(CommonData &common){
|
||||||
|
commonData = &common;
|
||||||
|
common.logger->logDebug(GwLog::LOG,"Instantiate PageDigitalOut");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int handleKey(int key){
|
||||||
|
// Code for keylock
|
||||||
|
if(key == 11){
|
||||||
|
commonData->keylock = !commonData->keylock;
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
// Code for button 1
|
||||||
|
if(key == 1){
|
||||||
|
button1 = !button1;
|
||||||
|
setPCF8574PortPin(0, button1 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
// Code for button 2
|
||||||
|
if(key == 2){
|
||||||
|
button2 = !button2;
|
||||||
|
setPCF8574PortPin(1, button2 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
// Code for button 3
|
||||||
|
if(key == 3){
|
||||||
|
button3 = !button3;
|
||||||
|
setPCF8574PortPin(2, button3 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
// Code for button 4
|
||||||
|
if(key == 4){
|
||||||
|
button4 = !button4;
|
||||||
|
setPCF8574PortPin(3, button4 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
// Code for button 5
|
||||||
|
if(key == 5){
|
||||||
|
button5 = !button5;
|
||||||
|
setPCF8574PortPin(4, button5 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
int displayPage(PageData &pageData){
|
||||||
|
GwConfigHandler *config = commonData->config;
|
||||||
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
|
// Get config data
|
||||||
|
String lengthformat = config->getString(config->lengthFormat);
|
||||||
|
bool simulation = config->getBool(config->useSimuData);
|
||||||
|
bool holdvalues = config->getBool(config->holdvalues);
|
||||||
|
String flashLED = config->getString(config->flashLED);
|
||||||
|
String backlightMode = config->getString(config->backlight);
|
||||||
|
|
||||||
|
// Optical warning by limit violation (unused)
|
||||||
|
if(String(flashLED) == "Limit Violation"){
|
||||||
|
setBlinkingLED(false);
|
||||||
|
setFlashLED(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging boat values
|
||||||
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageDigitalOut");
|
||||||
|
|
||||||
|
// Draw page
|
||||||
|
//***********************************************************
|
||||||
|
|
||||||
|
// Set display in partial refresh mode
|
||||||
|
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().fillRoundRect(200, 250 , 200, 25, 5, commonData->fgcolor); // Black rect
|
||||||
|
getdisplay().fillRoundRect(202, 252 , 196, 21, 5, commonData->bgcolor); // White rect
|
||||||
|
getdisplay().setCursor(210, 270);
|
||||||
|
getdisplay().print("Map server lost");
|
||||||
|
|
||||||
|
// Set botton labels
|
||||||
|
commonData->keydata[0].label = "BTN 1";
|
||||||
|
commonData->keydata[1].label = "BTN 2";
|
||||||
|
commonData->keydata[2].label = "BTN 3";
|
||||||
|
commonData->keydata[3].label = "BTN 4";
|
||||||
|
commonData->keydata[4].label = "BTN 5";
|
||||||
|
|
||||||
|
return PAGE_UPDATE;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static Page* createPage(CommonData &common){
|
||||||
|
return new PageDigitalOut(common);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* with the code below we make this page known to the PageTask
|
||||||
|
* we give it a type (name) that can be selected in the config
|
||||||
|
* we define which function is to be called
|
||||||
|
* and we provide the number of user parameters we expect
|
||||||
|
* this will be number of BoatValue pointers in pageData.values
|
||||||
|
*/
|
||||||
|
PageDescription registerPageDigitalOut(
|
||||||
|
"DigitalOut", // Page name
|
||||||
|
createPage, // Action
|
||||||
|
0, // Number of bus values depends on selection in Web configuration
|
||||||
|
true // Show display header on/off
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -98,7 +98,7 @@ class PageFluid : public Page
|
|||||||
commonData->logger->logDebug(GwLog::LOG,"New PageFluid: fluidtype=%d", fluidtype);
|
commonData->logger->logDebug(GwLog::LOG,"New PageFluid: fluidtype=%d", fluidtype);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -252,9 +252,7 @@ class PageFluid : public Page
|
|||||||
getdisplay().fillCircle(c.x, c.y, 6, commonData->bgcolor);
|
getdisplay().fillCircle(c.x, c.y, 6, commonData->bgcolor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class PageFourValues : public Page
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ class PageFourValues : public Page
|
|||||||
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
||||||
|
|
||||||
// Get boat values #2
|
// Get boat values #2
|
||||||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
|
||||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||||
name2 = name2.substring(0, 6); // String length limit for value name
|
name2 = name2.substring(0, 6); // String length limit for value name
|
||||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||||
@@ -63,7 +63,7 @@ class PageFourValues : public Page
|
|||||||
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
|
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
|
||||||
|
|
||||||
// Get boat values #3
|
// Get boat values #3
|
||||||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
|
||||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||||
name3 = name3.substring(0, 6); // String length limit for value name
|
name3 = name3.substring(0, 6); // String length limit for value name
|
||||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||||
@@ -73,7 +73,7 @@ class PageFourValues : public Page
|
|||||||
String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value
|
String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value
|
||||||
|
|
||||||
// Get boat values #4
|
// Get boat values #4
|
||||||
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Second element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Fourth element in list
|
||||||
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||||
name4 = name4.substring(0, 6); // String length limit for value name
|
name4 = name4.substring(0, 6); // String length limit for value name
|
||||||
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
||||||
@@ -89,7 +89,7 @@ class PageFourValues : public Page
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging boat values
|
// Logging boat values
|
||||||
if (bvalue1 == NULL) return;
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageFourValues, %s: %f, %s: %f, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4);
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageFourValues, %s: %f, %s: %f, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -287,9 +287,7 @@ class PageFourValues : public Page
|
|||||||
unit4old = unit4; // Save the old unit
|
unit4old = unit4; // Save the old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -303,7 +301,7 @@ static Page *createPage(CommonData &common){
|
|||||||
* this will be number of BoatValue pointers in pageData.values
|
* this will be number of BoatValue pointers in pageData.values
|
||||||
*/
|
*/
|
||||||
PageDescription registerPageFourValues(
|
PageDescription registerPageFourValues(
|
||||||
"FourValues", // Page name
|
"FourValues", // Page name
|
||||||
createPage, // Action
|
createPage, // Action
|
||||||
4, // Number of bus values depends on selection in Web configuration
|
4, // Number of bus values depends on selection in Web configuration
|
||||||
true // Show display header on/off
|
true // Show display header on/off
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class PageFourValues2 : public Page
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ class PageFourValues2 : public Page
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging boat values
|
// Logging boat values
|
||||||
if (bvalue1 == NULL) return;
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageFourValues2, %s: %f, %s: %f, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4);
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageFourValues2, %s: %f, %s: %f, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -287,9 +287,7 @@ class PageFourValues2 : public Page
|
|||||||
unit4old = unit4; // Save the old unit
|
unit4old = unit4; // Save the old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public:
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData)
|
int displayPage(PageData &pageData)
|
||||||
{
|
{
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
@@ -203,8 +203,7 @@ public:
|
|||||||
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
||||||
getdisplay().print("W");
|
getdisplay().print("W");
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public:
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData)
|
int displayPage(PageData &pageData)
|
||||||
{
|
{
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
@@ -86,21 +86,20 @@ public:
|
|||||||
float x = 200 + (rInstrument-30)*sin(i/180.0*pi); // x-coordinate dots
|
float x = 200 + (rInstrument-30)*sin(i/180.0*pi); // x-coordinate dots
|
||||||
float y = 150 - (rInstrument-30)*cos(i/180.0*pi); // y-coordinate cots
|
float y = 150 - (rInstrument-30)*cos(i/180.0*pi); // y-coordinate cots
|
||||||
const char *ii = " ";
|
const char *ii = " ";
|
||||||
switch (i)
|
switch (i) {
|
||||||
{
|
case 0: ii=" "; break; // Use a blank for a empty scale value
|
||||||
case 0: ii=" "; break; // Use a blank for a empty scale value
|
case 30 : ii=" "; break;
|
||||||
case 30 : ii=" "; break;
|
case 60 : ii=" "; break;
|
||||||
case 60 : ii=" "; break;
|
case 90 : ii="45"; break;
|
||||||
case 90 : ii="45"; break;
|
case 120 : ii="30"; break;
|
||||||
case 120 : ii="30"; break;
|
case 150 : ii="15"; break;
|
||||||
case 150 : ii="15"; break;
|
case 180 : ii="0"; break;
|
||||||
case 180 : ii="0"; break;
|
case 210 : ii="15"; break;
|
||||||
case 210 : ii="15"; break;
|
case 240 : ii="30"; break;
|
||||||
case 240 : ii="30"; break;
|
case 270 : ii="45"; break;
|
||||||
case 270 : ii="45"; break;
|
case 300 : ii=" "; break;
|
||||||
case 300 : ii=" "; break;
|
case 330 : ii=" "; break;
|
||||||
case 330 : ii=" "; break;
|
default: break;
|
||||||
default: break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print text centered on position x, y
|
// Print text centered on position x, y
|
||||||
@@ -206,9 +205,7 @@ public:
|
|||||||
getdisplay().print("No sensor data"); // Info missing sensor
|
getdisplay().print("No sensor data"); // Info missing sensor
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
505
lib/obp60task/PageNavigation.cpp
Normal file
505
lib/obp60task/PageNavigation.cpp
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||||
|
|
||||||
|
#include "Pagedata.h"
|
||||||
|
#include "OBP60Extensions.h"
|
||||||
|
#include "NetworkClient.h" // Network connection
|
||||||
|
#include "ImageDecoder.h" // Image decoder for navigation map
|
||||||
|
|
||||||
|
#include "Logo_OBP_400x300_sw.h"
|
||||||
|
|
||||||
|
// Defines for reading of navigation map
|
||||||
|
#define JSON_BUFFER 30000 // Max buffer size for JSON content (30 kB picture + values)
|
||||||
|
NetworkClient net(JSON_BUFFER); // Define network client
|
||||||
|
ImageDecoder decoder; // Define image decoder
|
||||||
|
|
||||||
|
class PageNavigation : public Page
|
||||||
|
{
|
||||||
|
// Values for buttons
|
||||||
|
bool firstRun = true; // Detect the first page run
|
||||||
|
int zoom = 15; // Default zoom level
|
||||||
|
bool showValues = false; // Show values HDT, SOG, DBT in navigation map
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t* imageBackupData = nullptr;
|
||||||
|
int imageBackupWidth = 0;
|
||||||
|
int imageBackupHeight = 0;
|
||||||
|
size_t imageBackupSize = 0;
|
||||||
|
bool hasImageBackup = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PageNavigation(CommonData &common){
|
||||||
|
commonData = &common;
|
||||||
|
common.logger->logDebug(GwLog::LOG,"Instantiate PageNavigation");
|
||||||
|
imageBackupData = (uint8_t*)heap_caps_malloc((GxEPD_WIDTH * GxEPD_HEIGHT), MALLOC_CAP_SPIRAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int handleKey(int key){
|
||||||
|
// Code for keylock
|
||||||
|
if(key == 11){
|
||||||
|
commonData->keylock = !commonData->keylock;
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
// Code for zoom -
|
||||||
|
if(key == 1){
|
||||||
|
zoom --; // Zoom -
|
||||||
|
if(zoom <7){
|
||||||
|
zoom = 7;
|
||||||
|
}
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
// Code for zoom -
|
||||||
|
if(key == 2){
|
||||||
|
zoom ++; // Zoom +
|
||||||
|
if(zoom >17){
|
||||||
|
zoom = 17;
|
||||||
|
}
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
if(key == 5){
|
||||||
|
showValues = !showValues; // Toggle show values
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
int displayPage(PageData &pageData){
|
||||||
|
GwConfigHandler *config = commonData->config;
|
||||||
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
|
// Get config data
|
||||||
|
String lengthformat = config->getString(config->lengthFormat);
|
||||||
|
bool simulation = config->getBool(config->useSimuData);
|
||||||
|
bool holdvalues = config->getBool(config->holdvalues);
|
||||||
|
String flashLED = config->getString(config->flashLED);
|
||||||
|
String backlightMode = config->getString(config->backlight);
|
||||||
|
String mapsource = config->getString(config->mapsource);
|
||||||
|
String ipAddress = config->getString(config->ipAddress);
|
||||||
|
int localPort = config->getInt(config->localPort);
|
||||||
|
String mapType = config->getString(config->maptype);
|
||||||
|
int zoomLevel = config->getInt(config->zoomlevel);
|
||||||
|
bool grid = config->getBool(config->grid);
|
||||||
|
String orientation = config->getString(config->orientation);
|
||||||
|
int refreshDistance = config->getInt(config->refreshDistance);
|
||||||
|
bool showValuesMap = config->getBool(config->showvalues);
|
||||||
|
bool ownHeading = config->getBool(config->ownheading);
|
||||||
|
|
||||||
|
if(firstRun == true){
|
||||||
|
zoom = zoomLevel; // Over write zoom level with setup value
|
||||||
|
showValues = showValuesMap; // Over write showValues with setup value
|
||||||
|
firstRun = false; // Restet variable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local variables
|
||||||
|
String server = "norbert-walter.dnshome.de";
|
||||||
|
int port = 80;
|
||||||
|
int mType = 1;
|
||||||
|
int dType = 1;
|
||||||
|
int mapRot = 0;
|
||||||
|
int symbolRot = 0;
|
||||||
|
int mapGrid = 0;
|
||||||
|
|
||||||
|
|
||||||
|
// Old values for hold function
|
||||||
|
static double value1old = 0;
|
||||||
|
static String svalue1old = "";
|
||||||
|
static String unit1old = "";
|
||||||
|
static double value2old = 0;
|
||||||
|
static String svalue2old = "";
|
||||||
|
static String unit2old = "";
|
||||||
|
static double value3old = 0; // Deg
|
||||||
|
static String svalue3old = "";
|
||||||
|
static String unit3old = "";
|
||||||
|
static double value4old = 0;
|
||||||
|
static String svalue4old = "";
|
||||||
|
static String unit4old = "";
|
||||||
|
static double value5old = 0;
|
||||||
|
static String svalue5old = "";
|
||||||
|
static String unit5old = "";
|
||||||
|
static double value6old = 0;
|
||||||
|
static String svalue6old = "";
|
||||||
|
static String unit6old = "";
|
||||||
|
|
||||||
|
static double latitude = 0;
|
||||||
|
static double latitudeold = 0;
|
||||||
|
static double longitude = 0;
|
||||||
|
static double longitudeold = 0;
|
||||||
|
static double trueHeading = 0;
|
||||||
|
static double magneticHeading = 0;
|
||||||
|
static double speedOverGround = 0;
|
||||||
|
static double depthBelowTransducer = 0;
|
||||||
|
static int lostCounter = 0; // Counter for connection lost to the map server (increment by each page refresh)
|
||||||
|
int imgWidth = 0;
|
||||||
|
int imgHeight = 0;
|
||||||
|
|
||||||
|
// Get boat values #1 Latitude
|
||||||
|
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||||
|
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||||
|
name1 = name1.substring(0, 6); // String length limit for value name
|
||||||
|
double value1 = bvalue1->value; // Value as double in SI unit
|
||||||
|
bool valid1 = bvalue1->valid; // Valid information
|
||||||
|
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
|
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
||||||
|
|
||||||
|
// Get boat values #2 Longitude
|
||||||
|
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue)
|
||||||
|
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||||
|
name2 = name2.substring(0, 6); // String length limit for value name
|
||||||
|
double value2 = bvalue2->value; // Value as double in SI unit
|
||||||
|
bool valid2 = bvalue2->valid; // Valid information
|
||||||
|
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
|
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
|
||||||
|
|
||||||
|
// Get boat values #3 HDT
|
||||||
|
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue)
|
||||||
|
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||||
|
name3 = name3.substring(0, 6); // String length limit for value name
|
||||||
|
double value3 = bvalue3->value; // Value as double in SI unit
|
||||||
|
bool valid3 = bvalue3->valid; // Valid information
|
||||||
|
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
|
String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value
|
||||||
|
|
||||||
|
// Get boat values #4 HDM
|
||||||
|
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Second element in list (only one value by PageOneValue)
|
||||||
|
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||||
|
name4 = name4.substring(0, 6); // String length limit for value name
|
||||||
|
double value4 = bvalue4->value; // Value as double in SI unit
|
||||||
|
bool valid4 = bvalue4->valid; // Valid information
|
||||||
|
String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
|
String unit4 = formatValue(bvalue4, *commonData).unit; // Unit of value
|
||||||
|
|
||||||
|
// Get boat values #5 SOG
|
||||||
|
GwApi::BoatValue *bvalue5 = pageData.values[4]; // Second element in list (only one value by PageOneValue)
|
||||||
|
String name5 = xdrDelete(bvalue5->getName()); // Value name
|
||||||
|
name5 = name5.substring(0, 6); // String length limit for value name
|
||||||
|
double value5 = bvalue5->value; // Value as double in SI unit
|
||||||
|
bool valid5 = bvalue5->valid; // Valid information
|
||||||
|
String svalue5 = formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
|
String unit5 = formatValue(bvalue5, *commonData).unit; // Unit of value
|
||||||
|
|
||||||
|
// Get boat values #6 DBT
|
||||||
|
GwApi::BoatValue *bvalue6 = pageData.values[5]; // Second element in list (only one value by PageOneValue)
|
||||||
|
String name6 = xdrDelete(bvalue6->getName()); // Value name
|
||||||
|
name6 = name6.substring(0, 6); // String length limit for value name
|
||||||
|
double value6 = bvalue6->value; // Value as double in SI unit
|
||||||
|
bool valid6 = bvalue6->valid; // Valid information
|
||||||
|
String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
|
String unit6 = formatValue(bvalue6, *commonData).unit; // Unit of value
|
||||||
|
|
||||||
|
// Optical warning by limit violation (unused)
|
||||||
|
if(String(flashLED) == "Limit Violation"){
|
||||||
|
setBlinkingLED(false);
|
||||||
|
setFlashLED(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging boat values
|
||||||
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageNavigation, %s: %f, %s: %f, %s: %f, %s: %f, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, name6.c_str(), value6);
|
||||||
|
|
||||||
|
// Set variables
|
||||||
|
//***********************************************************
|
||||||
|
|
||||||
|
// Latitude
|
||||||
|
if(valid1){
|
||||||
|
latitude = value1;
|
||||||
|
latitudeold = value1;
|
||||||
|
value3old = value1;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
latitude = value1old;
|
||||||
|
}
|
||||||
|
// Longitude
|
||||||
|
if(valid2){
|
||||||
|
longitude = value2;
|
||||||
|
longitudeold = value2;
|
||||||
|
value2old = value2;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
longitude = value2old;
|
||||||
|
}
|
||||||
|
// HDT value (True Heading, GPS)
|
||||||
|
if(valid3){
|
||||||
|
trueHeading = (value3 * 360) / (2 * PI);
|
||||||
|
value3old = trueHeading;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
trueHeading = value3old;
|
||||||
|
}
|
||||||
|
// HDM value (Magnetic Heading)
|
||||||
|
if(valid4){
|
||||||
|
magneticHeading = (value4 * 360) / (2 * PI);
|
||||||
|
value4old = magneticHeading;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
speedOverGround = value4old;
|
||||||
|
}
|
||||||
|
// SOG value (Speed Over Ground)
|
||||||
|
if(valid5){
|
||||||
|
speedOverGround = value5;
|
||||||
|
value5old = value5;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
speedOverGround = value5old;
|
||||||
|
}
|
||||||
|
// DBT value (Depth Below Transducer)
|
||||||
|
if(valid6){
|
||||||
|
depthBelowTransducer = value6;
|
||||||
|
value6old = value6;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
depthBelowTransducer = value6old;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare config values for URL
|
||||||
|
//***********************************************************
|
||||||
|
|
||||||
|
// Server settings
|
||||||
|
if(mapsource == "OBP Service"){
|
||||||
|
server = "norbert-walter.dnshome.de";
|
||||||
|
port = 80;
|
||||||
|
}
|
||||||
|
else if(mapsource == "Local Service"){
|
||||||
|
server = String(ipAddress);
|
||||||
|
port = localPort;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
server = "norbert-walter.dnshome.de";
|
||||||
|
port = 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type of navigation map
|
||||||
|
if(mapType == "Open Street Map"){
|
||||||
|
mType = 1; // Map type
|
||||||
|
dType = 1; // Dithering type
|
||||||
|
}
|
||||||
|
else if(mapType == "Google Street"){
|
||||||
|
mType = 3;
|
||||||
|
dType = 2;
|
||||||
|
}
|
||||||
|
else if(mapType == "Open Topo Map"){
|
||||||
|
mType = 5;
|
||||||
|
dType = 2;
|
||||||
|
}
|
||||||
|
else if(mapType == "Stadimaps Toner"){
|
||||||
|
mType = 7;
|
||||||
|
dType = 1;
|
||||||
|
}
|
||||||
|
else if(mapType == "Free Nautical Chart"){
|
||||||
|
mType = 9;
|
||||||
|
dType = 1;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
mType = 1;
|
||||||
|
dType = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map grid on/off
|
||||||
|
if(grid == true){
|
||||||
|
mapGrid = 1;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
mapGrid = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map orientation
|
||||||
|
if(orientation == "North Direction"){
|
||||||
|
mapRot = 0;
|
||||||
|
// If true heading available then use HDT oterwise HDM
|
||||||
|
if(valid3 == true){
|
||||||
|
symbolRot = trueHeading;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
symbolRot = magneticHeading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(orientation == "Travel Direction"){
|
||||||
|
// If true heading available then use HDT oterwise HDM
|
||||||
|
if(valid3 == true){
|
||||||
|
mapRot = trueHeading;
|
||||||
|
symbolRot = trueHeading;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
mapRot = magneticHeading;
|
||||||
|
symbolRot = magneticHeading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
mapRot = 0;
|
||||||
|
// If true heading available then use HDT oterwise HDM
|
||||||
|
if(valid3 == true){
|
||||||
|
symbolRot = trueHeading;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
symbolRot = magneticHeading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load navigation map
|
||||||
|
//***********************************************************
|
||||||
|
|
||||||
|
// URL to OBP Maps Converter
|
||||||
|
// For more details see: https://github.com/norbert-walter/maps-converter
|
||||||
|
String url = String("http://") + server + ":" + port + // OBP Server
|
||||||
|
String("/get_image_json?") + // Service: Output B&W picture as JSON (Base64 + gzip)
|
||||||
|
"zoom=" + zoom + // Default zoom level: 15
|
||||||
|
"&lat=" + String(latitude, 6) + // Latitude
|
||||||
|
"&lon=" + String(longitude, 6) + // Longitude
|
||||||
|
"&mrot=" + mapRot + // Rotation angle navigation map in degree
|
||||||
|
"&mtype=" + mType + // Default Map: Open Street Map
|
||||||
|
"&dtype=" + dType + // Dithering type: Atkinson dithering
|
||||||
|
"&width=400" + // With navigation map
|
||||||
|
"&height=250" + // Height navigation map
|
||||||
|
"&cutout=0" + // No picture cutouts
|
||||||
|
"&tab=0" + // No tab size
|
||||||
|
"&border=2" + // Border line size: 2 pixel
|
||||||
|
"&symbol=2" + // Symbol: Triangle
|
||||||
|
"&srot=" + symbolRot + // Symbol rotation angle
|
||||||
|
"&ssize=15" + // Symbole size: 15 pixel
|
||||||
|
"&grid=" + mapGrid // Show grid: On
|
||||||
|
;
|
||||||
|
|
||||||
|
// Draw page
|
||||||
|
//***********************************************************
|
||||||
|
|
||||||
|
// ############### Draw Navigation Map ################
|
||||||
|
|
||||||
|
// Set display in partial refresh mode
|
||||||
|
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
// If a network connection to URL then load the navigation map
|
||||||
|
if (net.fetchAndDecompressJson(url)) {
|
||||||
|
|
||||||
|
auto& json = net.json(); // Extract JSON content
|
||||||
|
int numPix = json["number_pixels"] | 0; // Read number of pixels
|
||||||
|
imgWidth = json["width"] | 0; // Read width of image
|
||||||
|
imgHeight = json["height"] | 0; // Read height og image
|
||||||
|
|
||||||
|
const char* b64src = json["picture_base64"].as<const char*>(); // Read picture as Base64 content
|
||||||
|
size_t b64len = strlen(b64src); // Calculate length of Base64 content
|
||||||
|
// Copy Base64 content in PSRAM
|
||||||
|
char* b64 = (char*) heap_caps_malloc(b64len + 1, MALLOC_CAP_SPIRAM); // Allcate PSRAM for Base64 content
|
||||||
|
if (!b64) {
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: PSRAM alloc base64 failed");
|
||||||
|
return PAGE_UPDATE;
|
||||||
|
}
|
||||||
|
memcpy(b64, b64src, b64len + 1); // Copy Base64 content in PSRAM
|
||||||
|
|
||||||
|
// Set image buffer in PSRAM
|
||||||
|
//size_t imgSize = getdisplay().width() * getdisplay().height();
|
||||||
|
size_t imgSize = numPix; // Calculate image size
|
||||||
|
uint8_t* imageData = (uint8_t*) heap_caps_malloc(imgSize, MALLOC_CAP_SPIRAM); // Allocate PSRAM for image
|
||||||
|
if (!imageData) {
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: PPSRAM alloc image buffer failed");
|
||||||
|
free(b64);
|
||||||
|
return PAGE_UPDATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode Base64 content to image
|
||||||
|
size_t decodedSize = 0;
|
||||||
|
decoder.decodeBase64(b64, imageData, imgSize, decodedSize);
|
||||||
|
|
||||||
|
// Copy actual navigation man to ackup map
|
||||||
|
imageBackupWidth = imgWidth;
|
||||||
|
imageBackupHeight = imgHeight;
|
||||||
|
imageBackupSize = imgSize;
|
||||||
|
if (decodedSize > 0) {
|
||||||
|
memcpy(imageBackupData, imageData, decodedSize);
|
||||||
|
imageBackupSize = decodedSize;
|
||||||
|
}
|
||||||
|
hasImageBackup = true;
|
||||||
|
lostCounter = 0;
|
||||||
|
|
||||||
|
// Show image (navigation map)
|
||||||
|
getdisplay().drawBitmap(0, 25, imageData, imgWidth, imgHeight, commonData->fgcolor);
|
||||||
|
|
||||||
|
// Clean PSRAM
|
||||||
|
free(b64);
|
||||||
|
free(imageData);
|
||||||
|
}
|
||||||
|
// If no network connection then use backup navigation map
|
||||||
|
else{
|
||||||
|
// Show backup image (backup navigation map)
|
||||||
|
if (hasImageBackup) {
|
||||||
|
getdisplay().drawBitmap(0, 25, imageBackupData, imageBackupWidth, imageBackupHeight, commonData->fgcolor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show info: Connection lost when 5 page refreshes has a connection lost to the map server
|
||||||
|
// Short connection losts are uncritical
|
||||||
|
if(lostCounter >= 5){
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().fillRect(200, 250 , 200, 25, commonData->fgcolor); // Black rect
|
||||||
|
getdisplay().fillRect(202, 252 , 196, 21, commonData->bgcolor); // White rect
|
||||||
|
getdisplay().setCursor(210, 270);
|
||||||
|
getdisplay().print("Map server lost");
|
||||||
|
}
|
||||||
|
|
||||||
|
lostCounter++; // Increment lost counter
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ############### Draw Values ################
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
|
||||||
|
// Show zoom level
|
||||||
|
getdisplay().fillRect(355, 25 , 45, 25, commonData->fgcolor); // Black rect
|
||||||
|
getdisplay().fillRect(357, 27 , 41, 21, commonData->bgcolor); // White rect
|
||||||
|
getdisplay().setCursor(364, 45);
|
||||||
|
getdisplay().print(zoom);
|
||||||
|
// If true heading available then use HDT oterwise HDM
|
||||||
|
if(showValues == true){
|
||||||
|
// Frame
|
||||||
|
getdisplay().fillRect(0, 25 , 130, 65, commonData->fgcolor); // Black rect
|
||||||
|
getdisplay().fillRect(2, 27 , 126, 61, commonData->bgcolor); // White rect
|
||||||
|
if(valid3 == true){
|
||||||
|
// HDT
|
||||||
|
getdisplay().setCursor(10, 45);
|
||||||
|
getdisplay().print(name3);
|
||||||
|
getdisplay().setCursor(70, 45);
|
||||||
|
getdisplay().print(svalue3);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// HDM
|
||||||
|
getdisplay().setCursor(10, 45);
|
||||||
|
getdisplay().print(name4);
|
||||||
|
getdisplay().setCursor(70, 45);
|
||||||
|
getdisplay().print(svalue4);
|
||||||
|
}
|
||||||
|
// SOG
|
||||||
|
getdisplay().setCursor(10, 65);
|
||||||
|
getdisplay().print(name5);
|
||||||
|
getdisplay().setCursor(70, 65);
|
||||||
|
getdisplay().print(svalue5);
|
||||||
|
// DBT
|
||||||
|
getdisplay().setCursor(10, 85);
|
||||||
|
getdisplay().print(name6);
|
||||||
|
getdisplay().setCursor(70, 85);
|
||||||
|
getdisplay().print(svalue6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set botton labels
|
||||||
|
commonData->keydata[0].label = "ZOOM -";
|
||||||
|
commonData->keydata[1].label = "ZOOM +";
|
||||||
|
commonData->keydata[4].label = "VALUES";
|
||||||
|
|
||||||
|
return PAGE_UPDATE;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static Page *createPage(CommonData &common){
|
||||||
|
return new PageNavigation(common);
|
||||||
|
}/**
|
||||||
|
* with the code below we make this page known to the PageTask
|
||||||
|
* we give it a type (name) that can be selected in the config
|
||||||
|
* we define which function is to be called
|
||||||
|
* and we provide the number of user parameters we expect
|
||||||
|
* this will be number of BoatValue pointers in pageData.values
|
||||||
|
*/
|
||||||
|
PageDescription registerPageNavigation(
|
||||||
|
"Navigation", // Page name
|
||||||
|
createPage, // Action
|
||||||
|
0, // Number of bus values depends on selection in Web configuration
|
||||||
|
{"LAT","LON","HDT","HDM","SOG","DBT"}, // Bus values we need in the page
|
||||||
|
true // Show display header on/off
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -21,7 +21,7 @@ class PageOneValue : public Page
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ class PageOneValue : public Page
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging boat values
|
// Logging boat values
|
||||||
if (bvalue1 == NULL) return;
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageOneValue, %s: %f", name1.c_str(), value1);
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageOneValue, %s: %f", name1.c_str(), value1);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -104,9 +104,7 @@ class PageOneValue : public Page
|
|||||||
unit1old = unit1; // Save the old unit
|
unit1old = unit1; // Save the old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public:
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -41,9 +41,9 @@ public:
|
|||||||
String backlightMode = config->getString(config->backlight);
|
String backlightMode = config->getString(config->backlight);
|
||||||
int rolllimit = config->getInt(config->rollLimit);
|
int rolllimit = config->getInt(config->rollLimit);
|
||||||
String roffset = config->getString(config->rollOffset);
|
String roffset = config->getString(config->rollOffset);
|
||||||
double rolloffset = roffset.toFloat()/360*(2*PI);
|
double rolloffset = roffset.toFloat()/360*(2*M_PI);
|
||||||
String poffset = config->getString(config->pitchOffset);
|
String poffset = config->getString(config->pitchOffset);
|
||||||
double pitchoffset = poffset.toFloat()/360*(2*PI);
|
double pitchoffset = poffset.toFloat()/360*(2*M_PI);
|
||||||
|
|
||||||
// Get boat values for roll
|
// Get boat values for roll
|
||||||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (xdrRoll)
|
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (xdrRoll)
|
||||||
@@ -55,17 +55,17 @@ public:
|
|||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if(simulation == true){
|
if(simulation == true){
|
||||||
value1 = (20 + float(random(0, 50)) / 10.0)/360*2*PI;
|
value1 = (20 + float(random(0, 50)) / 10.0)/360*2*M_PI;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
value1 = 0;
|
value1 = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(value1/(2*PI)*360 > -10 && value1/(2*PI)*360 < 10){
|
if(value1/(2*M_PI)*360 > -10 && value1/(2*M_PI)*360 < 10){
|
||||||
svalue1 = String(value1/(2*PI)*360,1); // Convert raw value to string
|
svalue1 = String(value1/(2*M_PI)*360,1); // Convert raw value to string
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
svalue1 = String(value1/(2*PI)*360,0);
|
svalue1 = String(value1/(2*M_PI)*360,0);
|
||||||
}
|
}
|
||||||
if(valid1 == true){
|
if(valid1 == true){
|
||||||
svalue1old = svalue1; // Save the old value
|
svalue1old = svalue1; // Save the old value
|
||||||
@@ -80,17 +80,17 @@ public:
|
|||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if(simulation == true){
|
if(simulation == true){
|
||||||
value2 = (float(random(-5, 5)))/360*2*PI;
|
value2 = (float(random(-5, 5)))/360*2*M_PI;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
value2 = 0;
|
value2 = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(value2/(2*PI)*360 > -10 && value2/(2*PI)*360 < 10){
|
if(value2/(2*PI)*360 > -10 && value2/(2*M_PI)*360 < 10){
|
||||||
svalue2 = String(value2/(2*PI)*360,1); // Convert raw value to string
|
svalue2 = String(value2/(2*M_PI)*360,1); // Convert raw value to string
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
svalue2 = String(value2/(2*PI)*360,0);
|
svalue2 = String(value2/(2*M_PI)*360,0);
|
||||||
}
|
}
|
||||||
if(valid2 == true){
|
if(valid2 == true){
|
||||||
svalue2old = svalue2; // Save the old value
|
svalue2old = svalue2; // Save the old value
|
||||||
@@ -99,7 +99,7 @@ public:
|
|||||||
// Optical warning by limit violation
|
// Optical warning by limit violation
|
||||||
if(String(flashLED) == "Limit Violation"){
|
if(String(flashLED) == "Limit Violation"){
|
||||||
// Limits for roll
|
// Limits for roll
|
||||||
if(value1*360/(2*PI) >= -1*rolllimit && value1*360/(2*PI) <= rolllimit){
|
if(value1*360/(2*M_PI) >= -1*rolllimit && value1*360/(2*M_PI) <= rolllimit){
|
||||||
setBlinkingLED(false);
|
setBlinkingLED(false);
|
||||||
setFlashLED(false);
|
setFlashLED(false);
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging boat values
|
// Logging boat values
|
||||||
if (bvalue1 == NULL) return;
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageRollPitch, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2);
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageRollPitch, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -177,19 +177,18 @@ public:
|
|||||||
// Only scaling +/- 60 degrees
|
// Only scaling +/- 60 degrees
|
||||||
if((i >= 0 && i <= 60) || (i >= 300 && i <= 360)){
|
if((i >= 0 && i <= 60) || (i >= 300 && i <= 360)){
|
||||||
// Scaling values
|
// Scaling values
|
||||||
float x = 200 + (rInstrument+25)*sin(i/180.0*pi); // x-coordinate dots
|
float x = 200 + (rInstrument+25)*sin(i/180.0*M_PI); // x-coordinate dots
|
||||||
float y = 150 - (rInstrument+25)*cos(i/180.0*pi); // y-coordinate cots
|
float y = 150 - (rInstrument+25)*cos(i/180.0*M_PI); // y-coordinate cots
|
||||||
const char *ii = "";
|
const char *ii = "";
|
||||||
switch (i)
|
switch (i) {
|
||||||
{
|
case 0: ii="0"; break;
|
||||||
case 0: ii="0"; break;
|
case 20 : ii="20"; break;
|
||||||
case 20 : ii="20"; break;
|
case 40 : ii="40"; break;
|
||||||
case 40 : ii="40"; break;
|
case 60 : ii="60"; break;
|
||||||
case 60 : ii="60"; break;
|
case 300 : ii="60"; break;
|
||||||
case 300 : ii="60"; break;
|
case 320 : ii="40"; break;
|
||||||
case 320 : ii="40"; break;
|
case 340 : ii="20"; break;
|
||||||
case 340 : ii="20"; break;
|
default: break;
|
||||||
default: break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print text centered on position x, y
|
// Print text centered on position x, y
|
||||||
@@ -203,11 +202,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw sub scale with dots
|
// Draw sub scale with dots
|
||||||
float x1c = 200 + rInstrument*sin(i/180.0*pi);
|
float x1c = 200 + rInstrument*sin(i/180.0*M_PI);
|
||||||
float y1c = 150 - rInstrument*cos(i/180.0*pi);
|
float y1c = 150 - rInstrument*cos(i/180.0*M_PI);
|
||||||
getdisplay().fillCircle((int)x1c, (int)y1c, 2, commonData->fgcolor);
|
getdisplay().fillCircle((int)x1c, (int)y1c, 2, commonData->fgcolor);
|
||||||
float sinx=sin(i/180.0*pi);
|
float sinx=sin(i/180.0*M_PI);
|
||||||
float cosx=cos(i/180.0*pi);
|
float cosx=cos(i/180.0*M_PI);
|
||||||
|
|
||||||
// Draw sub scale with lines (two triangles)
|
// Draw sub scale with lines (two triangles)
|
||||||
if(i % 20 == 0){
|
if(i % 20 == 0){
|
||||||
@@ -229,11 +228,11 @@ public:
|
|||||||
// Draw mast position pointer
|
// Draw mast position pointer
|
||||||
float startwidth = 8; // Start width of pointer
|
float startwidth = 8; // Start width of pointer
|
||||||
|
|
||||||
// value1 = (2 * pi ) - value1; // Mirror coordiante system for pointer, keel and boat
|
// value1 = (2 * M_PI ) - value1; // Mirror coordiante system for pointer, keel and boat
|
||||||
|
|
||||||
if(valid1 == true || holdvalues == true || simulation == true){
|
if(valid1 == true || holdvalues == true || simulation == true){
|
||||||
float sinx=sin(value1 + pi);
|
float sinx=sin(value1 + M_PI);
|
||||||
float cosx=cos(value1 + pi);
|
float cosx=cos(value1 + M_PI);
|
||||||
// Normal pointer
|
// Normal pointer
|
||||||
// Pointer as triangle with center base 2*width
|
// Pointer as triangle with center base 2*width
|
||||||
float xx1 = -startwidth;
|
float xx1 = -startwidth;
|
||||||
@@ -305,9 +304,7 @@ public:
|
|||||||
getdisplay().print("No sensor data"); // Info missing sensor
|
getdisplay().print("No sensor data"); // Info missing sensor
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public:
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging boat values
|
// Logging boat values
|
||||||
if (bvalue1 == NULL) return;
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageRudderPosition, %s:%f", name1.c_str(), value1);
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageRudderPosition, %s:%f", name1.c_str(), value1);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -207,8 +207,7 @@ public:
|
|||||||
getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor);
|
getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor);
|
||||||
getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor);
|
getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor);
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class PageSixValues : public Page
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ class PageSixValues : public Page
|
|||||||
setFlashLED(false);
|
setFlashLED(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bvalue == NULL) return;
|
if (bvalue == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
//***********************************************************
|
//***********************************************************
|
||||||
@@ -149,9 +149,7 @@ class PageSixValues : public Page
|
|||||||
getdisplay().fillRect(SixValues_x1+SixValues_DeltaX-8, SixValues_y1+i*SixValues_DeltaY, 3, SixValues_DeltaY, commonData->fgcolor);
|
getdisplay().fillRect(SixValues_x1+SixValues_DeltaX-8, SixValues_y1+i*SixValues_DeltaY, 3, SixValues_DeltaY, commonData->fgcolor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
204
lib/obp60task/PageSkyView.cpp
Normal file
204
lib/obp60task/PageSkyView.cpp
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||||
|
|
||||||
|
#include "Pagedata.h"
|
||||||
|
#include "OBP60Extensions.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm> // for vector sorting
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SkyView / Satellites
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PageSkyView : public Page
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
String flashLED;
|
||||||
|
GwBoatData *bd;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PageSkyView(CommonData &common)
|
||||||
|
{
|
||||||
|
commonData = &common;
|
||||||
|
|
||||||
|
// task name access is for example purpose only
|
||||||
|
TaskHandle_t currentTaskHandle = xTaskGetCurrentTaskHandle();
|
||||||
|
const char* taskName = pcTaskGetName(currentTaskHandle);
|
||||||
|
common.logger->logDebug(GwLog::LOG, "Instantiate PageSkyView in task '%s'", taskName);
|
||||||
|
|
||||||
|
flashLED = common.config->getString(common.config->flashLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
int handleKey(int key) {
|
||||||
|
// return 0 to mark the key handled completely
|
||||||
|
// return the key to allow further action
|
||||||
|
if (key == 11) {
|
||||||
|
commonData->keylock = !commonData->keylock;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayNew(PageData &pageData) {
|
||||||
|
#ifdef BOARD_OBP60S3
|
||||||
|
// Clear optical warning
|
||||||
|
if (flashLED == "Limit Violation") {
|
||||||
|
setBlinkingLED(false);
|
||||||
|
setFlashLED(false);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
bd = pageData.api->getBoatData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Comparator function to sort by SNR
|
||||||
|
static bool compareBySNR(const GwSatInfo& a, const GwSatInfo& b) {
|
||||||
|
return a.SNR > b.SNR; // Sort in descending order
|
||||||
|
}
|
||||||
|
|
||||||
|
int displayPage(PageData &pageData) {
|
||||||
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
|
std::vector<GwSatInfo> sats;
|
||||||
|
int nSat = bd->SatInfo->getNumSats();
|
||||||
|
|
||||||
|
logger->logDebug(GwLog::LOG, "Drawing at PageSkyView, %d satellites", nSat);
|
||||||
|
|
||||||
|
for (int i = 0; i < nSat; i++) {
|
||||||
|
sats.push_back(*bd->SatInfo->getAt(i));
|
||||||
|
}
|
||||||
|
std::sort(sats.begin(), sats.end(), compareBySNR);
|
||||||
|
|
||||||
|
// Draw page
|
||||||
|
//***********************************************************
|
||||||
|
|
||||||
|
// Set display in partial refresh mode
|
||||||
|
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
|
|
||||||
|
// current position
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
|
||||||
|
// sky view
|
||||||
|
Point c = {130, 148};
|
||||||
|
uint16_t r = 120;
|
||||||
|
uint16_t r1 = r / 2;
|
||||||
|
|
||||||
|
getdisplay().fillCircle(c.x, c.y, r + 2, commonData->fgcolor);
|
||||||
|
getdisplay().fillCircle(c.x, c.y, r - 1, commonData->bgcolor);
|
||||||
|
getdisplay().drawCircle(c.x, c.y, r1, commonData->fgcolor);
|
||||||
|
|
||||||
|
// separation lines
|
||||||
|
getdisplay().drawLine(c.x - r, c.y, c.x + r, c.y, commonData->fgcolor);
|
||||||
|
getdisplay().drawLine(c.x, c.y - r, c.x, c.y + r, commonData->fgcolor);
|
||||||
|
Point p = {c.x, c.y - r};
|
||||||
|
Point p1, p2;
|
||||||
|
p1 = rotatePoint(c, p, 45);
|
||||||
|
p2 = rotatePoint(c, p, 45 + 180);
|
||||||
|
getdisplay().drawLine(p1.x, p1.y, p2.x, p2.y, commonData->fgcolor);
|
||||||
|
p1 = rotatePoint(c, p, -45);
|
||||||
|
p2 = rotatePoint(c, p, -45 + 180);
|
||||||
|
getdisplay().drawLine(p1.x, p1.y, p2.x, p2.y, commonData->fgcolor);
|
||||||
|
|
||||||
|
// directions
|
||||||
|
|
||||||
|
int16_t x1, y1;
|
||||||
|
uint16_t w, h;
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
|
||||||
|
getdisplay().getTextBounds("N", 0, 150, &x1, &y1, &w, &h);
|
||||||
|
getdisplay().setCursor(c.x - w / 2, c.y - r + h + 3);
|
||||||
|
getdisplay().print("N");
|
||||||
|
|
||||||
|
getdisplay().getTextBounds("S", 0, 150, &x1, &y1, &w, &h);
|
||||||
|
getdisplay().setCursor(c.x - w / 2, c.y + r - 3);
|
||||||
|
getdisplay().print("S");
|
||||||
|
|
||||||
|
getdisplay().getTextBounds("E", 0, 150, &x1, &y1, &w, &h);
|
||||||
|
getdisplay().setCursor(c.x + r - w - 3, c.y + h / 2);
|
||||||
|
getdisplay().print("E");
|
||||||
|
|
||||||
|
getdisplay().getTextBounds("W", 0, 150, &x1, &y1, &w, &h);
|
||||||
|
getdisplay().setCursor(c.x - r + 3 , c.y + h / 2);
|
||||||
|
getdisplay().print("W");
|
||||||
|
|
||||||
|
// show satellites in "map"
|
||||||
|
getdisplay().setFont(&IBM8x8px);
|
||||||
|
for (int i = 0; i < nSat; i++) {
|
||||||
|
float arad = (sats[i].Azimut * M_PI / 180.0) + M_PI;
|
||||||
|
float erad = sats[i].Elevation * M_PI / 180.0;
|
||||||
|
uint16_t x = c.x + sin(arad) * erad * r1;
|
||||||
|
uint16_t y = c.y + cos(arad) * erad * r1;
|
||||||
|
getdisplay().fillRect(x-4, y-4, 8, 8, commonData->fgcolor);
|
||||||
|
getdisplay().setCursor(x-7, y+12);
|
||||||
|
getdisplay().printf("%02d", static_cast<int>(sats[i].PRN));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signal / Noise bars
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(325, 34);
|
||||||
|
getdisplay().print("SNR");
|
||||||
|
// getdisplay().drawRect(270, 20, 125, 257, commonData->fgcolor);
|
||||||
|
int maxsat = std::min(nSat, 12);
|
||||||
|
for (int i = 0; i < maxsat; i++) {
|
||||||
|
uint16_t y = 29 + (i + 1) * 20;
|
||||||
|
getdisplay().setCursor(276, y);
|
||||||
|
char buffer[3];
|
||||||
|
snprintf(buffer, 3, "%02d", static_cast<int>(sats[i].PRN));
|
||||||
|
getdisplay().print(String(buffer));
|
||||||
|
getdisplay().drawRect(305, y-12, 85, 14, commonData->fgcolor);
|
||||||
|
getdisplay().setCursor(315, y);
|
||||||
|
// TODO SNR as number or as bar via mode key?
|
||||||
|
if (sats[i].SNR <= 100) {
|
||||||
|
// getdisplay().print(sats[i].SNR);
|
||||||
|
getdisplay().fillRect(307, y-10, int(81 * sats[i].SNR / 100.0), 10, commonData->fgcolor);
|
||||||
|
} else {
|
||||||
|
getdisplay().print("n/a");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show SatInfo and HDOP
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
|
||||||
|
getdisplay().setCursor(220, 34);
|
||||||
|
getdisplay().print("Sat:");
|
||||||
|
|
||||||
|
GwApi::BoatValue *bv_satinfo = pageData.values[0]; // SatInfo
|
||||||
|
String sval_satinfo = formatValue(bv_satinfo, *commonData).svalue;
|
||||||
|
getdisplay().setCursor(220, 49);
|
||||||
|
getdisplay().print(sval_satinfo);
|
||||||
|
|
||||||
|
getdisplay().setCursor(220, 254);
|
||||||
|
getdisplay().print("HDOP:");
|
||||||
|
|
||||||
|
GwApi::BoatValue *bv_hdop = pageData.values[1]; // HDOP
|
||||||
|
double hdop = formatValue(bv_hdop, *commonData).value * 4; // 4 is factor for UERE (translation in meter)
|
||||||
|
char sval_hdop[20];
|
||||||
|
dtostrf(hdop, 0, 1, sval_hdop); // Only one prefix
|
||||||
|
strcat(sval_hdop, "m");
|
||||||
|
getdisplay().setCursor(220, 269);
|
||||||
|
getdisplay().print(sval_hdop);
|
||||||
|
|
||||||
|
return PAGE_UPDATE;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static Page* createPage(CommonData &common){
|
||||||
|
return new PageSkyView(common);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* with the code below we make this page known to the PageTask
|
||||||
|
* we give it a type (name) that can be selected in the config
|
||||||
|
* we define which function is to be called
|
||||||
|
* and we provide the number of user parameters we expect
|
||||||
|
* this will be number of BoatValue pointers in pageData.values
|
||||||
|
*/
|
||||||
|
PageDescription registerPageSkyView(
|
||||||
|
"SkyView", // Page name
|
||||||
|
createPage, // Action
|
||||||
|
0, // Number of bus values depends on selection in Web configuration
|
||||||
|
{"SatInfo", "HDOP"}, // Bus values we need in the page
|
||||||
|
true // Show display header on/off
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -20,7 +20,7 @@ public:
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -199,8 +199,7 @@ public:
|
|||||||
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
||||||
getdisplay().print("W");
|
getdisplay().print("W");
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Special system page, called directly with fast key sequence 5,4
|
||||||
|
* Out of normal page order.
|
||||||
|
* Consists of some sub-pages with following content:
|
||||||
|
* 1. Hard and software information
|
||||||
|
* 2. System settings
|
||||||
|
* 3. NMEA2000 device list
|
||||||
|
* 4. SD Card information if available
|
||||||
|
*/
|
||||||
|
|
||||||
#include "Pagedata.h"
|
#include "Pagedata.h"
|
||||||
#include "OBP60Extensions.h"
|
#include "OBP60Extensions.h"
|
||||||
#include "images/logo64.xbm"
|
#include "images/logo64.xbm"
|
||||||
@@ -7,8 +17,7 @@
|
|||||||
#include "qrcode.h"
|
#include "qrcode.h"
|
||||||
|
|
||||||
#ifdef BOARD_OBP40S3
|
#ifdef BOARD_OBP40S3
|
||||||
#include <SD.h>
|
#include "dirent.h"
|
||||||
#include <FS.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define STRINGIZE_IMPL(x) #x
|
#define STRINGIZE_IMPL(x) #x
|
||||||
@@ -19,35 +28,27 @@
|
|||||||
#define DISPLAYINFO STRINGIZE(EPDTYPE)
|
#define DISPLAYINFO STRINGIZE(EPDTYPE)
|
||||||
#define GXEPD2INFO STRINGIZE(GXEPD2VERS)
|
#define GXEPD2INFO STRINGIZE(GXEPD2VERS)
|
||||||
|
|
||||||
/*
|
|
||||||
* Special system page, called directly with fast key sequence 5,4
|
|
||||||
* Out of normal page order.
|
|
||||||
* Consists of some sub-pages with following content:
|
|
||||||
* 1. Hard and software information
|
|
||||||
* 2. System settings
|
|
||||||
* 3. NMEA2000 device list
|
|
||||||
*/
|
|
||||||
|
|
||||||
class PageSystem : public Page
|
class PageSystem : public Page
|
||||||
{
|
{
|
||||||
uint64_t chipid;
|
private:
|
||||||
bool simulation;
|
uint64_t chipid;
|
||||||
bool sdcard;
|
bool simulation;
|
||||||
String buzzer_mode;
|
bool use_sdcard;
|
||||||
uint8_t buzzer_power;
|
String buzzer_mode;
|
||||||
String cpuspeed;
|
uint8_t buzzer_power;
|
||||||
String rtc_module;
|
String cpuspeed;
|
||||||
String gps_module;
|
String rtc_module;
|
||||||
String env_module;
|
String gps_module;
|
||||||
|
String env_module;
|
||||||
|
|
||||||
String batt_sensor;
|
String batt_sensor;
|
||||||
String solar_sensor;
|
String solar_sensor;
|
||||||
String gen_sensor;
|
String gen_sensor;
|
||||||
String rot_sensor;
|
String rot_sensor;
|
||||||
double homelat;
|
double homelat;
|
||||||
double homelon;
|
double homelon;
|
||||||
|
|
||||||
char mode = 'N'; // (N)ormal, (S)ettings, (D)evice list, (C)ard
|
char mode = 'N'; // (N)ormal, (S)ettings, (D)evice list, (C)ard
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PageSystem(CommonData &common){
|
PageSystem(CommonData &common){
|
||||||
@@ -55,11 +56,12 @@ public:
|
|||||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageSystem");
|
common.logger->logDebug(GwLog::LOG,"Instantiate PageSystem");
|
||||||
if (hasFRAM) {
|
if (hasFRAM) {
|
||||||
mode = fram.read(FRAM_SYSTEM_MODE);
|
mode = fram.read(FRAM_SYSTEM_MODE);
|
||||||
|
common.logger->logDebug(GwLog::DEBUG, "Loaded mode '%c' from FRAM", mode);
|
||||||
}
|
}
|
||||||
chipid = ESP.getEfuseMac();
|
chipid = ESP.getEfuseMac();
|
||||||
simulation = common.config->getBool(common.config->useSimuData);
|
simulation = common.config->getBool(common.config->useSimuData);
|
||||||
#ifdef BOARD_OBP40S3
|
#ifdef BOARD_OBP40S3
|
||||||
sdcard = common.config->getBool(common.config->useSDCard);
|
use_sdcard = common.config->getBool(common.config->useSDCard);
|
||||||
#endif
|
#endif
|
||||||
buzzer_mode = common.config->getString(common.config->buzzerMode);
|
buzzer_mode = common.config->getString(common.config->buzzerMode);
|
||||||
buzzer_mode.toLowerCase();
|
buzzer_mode.toLowerCase();
|
||||||
@@ -76,7 +78,7 @@ public:
|
|||||||
homelon = common.config->getString(common.config->homeLON).toDouble();
|
homelon = common.config->getString(common.config->homeLON).toDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void setupKeys(){
|
void setupKeys() {
|
||||||
commonData->keydata[0].label = "EXIT";
|
commonData->keydata[0].label = "EXIT";
|
||||||
commonData->keydata[1].label = "MODE";
|
commonData->keydata[1].label = "MODE";
|
||||||
commonData->keydata[2].label = "";
|
commonData->keydata[2].label = "";
|
||||||
@@ -85,7 +87,7 @@ public:
|
|||||||
commonData->keydata[5].label = "ILUM";
|
commonData->keydata[5].label = "ILUM";
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual int handleKey(int key){
|
int handleKey(int key) {
|
||||||
// do *NOT* handle key #1 this handled by obp60task as exit
|
// do *NOT* handle key #1 this handled by obp60task as exit
|
||||||
// Switch display mode
|
// Switch display mode
|
||||||
commonData->logger->logDebug(GwLog::LOG, "System keyboard handler");
|
commonData->logger->logDebug(GwLog::LOG, "System keyboard handler");
|
||||||
@@ -95,7 +97,7 @@ public:
|
|||||||
} else if (mode == 'S') {
|
} else if (mode == 'S') {
|
||||||
mode = 'D';
|
mode = 'D';
|
||||||
} else if (mode == 'D') {
|
} else if (mode == 'D') {
|
||||||
if (sdcard) {
|
if (hasSDCard) {
|
||||||
mode = 'C';
|
mode = 'C';
|
||||||
} else {
|
} else {
|
||||||
mode = 'N';
|
mode = 'N';
|
||||||
@@ -117,7 +119,8 @@ public:
|
|||||||
}
|
}
|
||||||
// standby / deep sleep
|
// standby / deep sleep
|
||||||
if (key == 5) {
|
if (key == 5) {
|
||||||
deepSleep(*commonData);
|
commonData->logger->logDebug(GwLog::LOG, "System going into deep sleep mode...");
|
||||||
|
deepSleep(*commonData);
|
||||||
}
|
}
|
||||||
// Code for keylock
|
// Code for keylock
|
||||||
if (key == 11) {
|
if (key == 11) {
|
||||||
@@ -132,6 +135,7 @@ public:
|
|||||||
}
|
}
|
||||||
// standby / deep sleep
|
// standby / deep sleep
|
||||||
if (key == 12) {
|
if (key == 12) {
|
||||||
|
commonData->logger->logDebug(GwLog::LOG, "System going into deep sleep mode...");
|
||||||
deepSleep(*commonData);
|
deepSleep(*commonData);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -164,7 +168,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -178,7 +182,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging boat values
|
// Logging boat values
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageSystem");
|
logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
//***********************************************************
|
//***********************************************************
|
||||||
@@ -257,14 +261,37 @@ public:
|
|||||||
getdisplay().setCursor(8, y0 + 48);
|
getdisplay().setCursor(8, y0 + 48);
|
||||||
getdisplay().print("SD-Card:");
|
getdisplay().print("SD-Card:");
|
||||||
getdisplay().setCursor(90, y0 + 48);
|
getdisplay().setCursor(90, y0 + 48);
|
||||||
if (sdcard) {
|
if (hasSDCard) {
|
||||||
uint64_t cardsize = SD.cardSize() / (1024 * 1024);
|
uint64_t cardsize = ((uint64_t) sdcard->csd.capacity) * sdcard->csd.sector_size / (1024 * 1024);
|
||||||
getdisplay().print(String(cardsize) + String(" MB"));
|
getdisplay().printf("%llu MB", cardsize);
|
||||||
} else {
|
} else {
|
||||||
getdisplay().print("off");
|
getdisplay().print("off");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Uptime
|
||||||
|
int64_t uptime = esp_timer_get_time() / 1000000;
|
||||||
|
String uptime_unit;
|
||||||
|
if (uptime < 120) {
|
||||||
|
uptime_unit = " seconds";
|
||||||
|
} else {
|
||||||
|
if (uptime < 2 * 3600) {
|
||||||
|
uptime /= 60;
|
||||||
|
uptime_unit = " minutes";
|
||||||
|
} else if (uptime < 2 * 3600 * 24) {
|
||||||
|
uptime /= 3600;
|
||||||
|
uptime_unit = " hours";
|
||||||
|
} else {
|
||||||
|
uptime /= 86400;
|
||||||
|
uptime_unit = " days";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getdisplay().setCursor(8, y0 + 80);
|
||||||
|
getdisplay().print("Uptime:");
|
||||||
|
getdisplay().setCursor(90, y0 + 80);
|
||||||
|
getdisplay().print(uptime);
|
||||||
|
getdisplay().print(uptime_unit);
|
||||||
|
|
||||||
// CPU speed config / active
|
// CPU speed config / active
|
||||||
getdisplay().setCursor(202, y0);
|
getdisplay().setCursor(202, y0);
|
||||||
getdisplay().print("CPU speed:");
|
getdisplay().print("CPU speed:");
|
||||||
@@ -371,8 +398,61 @@ public:
|
|||||||
x0 = 20;
|
x0 = 20;
|
||||||
y0 = 72;
|
y0 = 72;
|
||||||
getdisplay().setCursor(x0, y0);
|
getdisplay().setCursor(x0, y0);
|
||||||
|
#ifdef BOARD_OBP60S3
|
||||||
|
// This mode should not be callable by devices without card hardware
|
||||||
|
// In case of accidential reaching this, display a friendly message
|
||||||
|
getdisplay().print("This mode is not indended to be reached!\n");
|
||||||
|
getdisplay().print("There's nothing to see here. Move on.");
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
getdisplay().print("Work in progress...");
|
getdisplay().print("Work in progress...");
|
||||||
|
|
||||||
|
/* TODO
|
||||||
|
this code should go somewhere else. only for testing purposes here
|
||||||
|
identify card as OBP-Card:
|
||||||
|
magic.dat
|
||||||
|
version.dat
|
||||||
|
readme.txt
|
||||||
|
IMAGES/
|
||||||
|
CHARTS/
|
||||||
|
LOGS/
|
||||||
|
DATA/
|
||||||
|
hint: file access with fopen, fgets, fread, fclose
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Simple test for magic file in root
|
||||||
|
getdisplay().setCursor(x0, y0 + 32);
|
||||||
|
String file_magic = MOUNT_POINT "/magic.dat";
|
||||||
|
logger->logDebug(GwLog::LOG, "Test magicfile: %s", file_magic.c_str());
|
||||||
|
struct stat st;
|
||||||
|
if (stat(file_magic.c_str(), &st) == 0) {
|
||||||
|
getdisplay().printf("File %s exists", file_magic.c_str());
|
||||||
|
} else {
|
||||||
|
getdisplay().printf("File %s not found", file_magic.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root directory check
|
||||||
|
DIR* dir = opendir(MOUNT_POINT);
|
||||||
|
int dy = 0;
|
||||||
|
if (dir != NULL) {
|
||||||
|
logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT);
|
||||||
|
struct dirent* entry;
|
||||||
|
while (((entry = readdir(dir)) != NULL) and (dy < 140)) {
|
||||||
|
getdisplay().setCursor(x0, y0 + 64 + dy);
|
||||||
|
getdisplay().print(entry->d_name);
|
||||||
|
// type 1 is file, type 2 is dir
|
||||||
|
if (entry->d_type == 2) {
|
||||||
|
getdisplay().print("/");
|
||||||
|
}
|
||||||
|
dy += 20;
|
||||||
|
logger->logDebug(GwLog::DEBUG, " %s type %d", entry->d_name, entry->d_type);
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
} else {
|
||||||
|
logger->logDebug(GwLog::LOG, "Failed to open root directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// NMEA2000 device list
|
// NMEA2000 device list
|
||||||
@@ -391,7 +471,7 @@ public:
|
|||||||
|
|
||||||
// Update display
|
// Update display
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
getdisplay().nextPage(); // Partial update (fast)
|
||||||
|
return PAGE_OK;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class PageThreeValues : public Page
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ class PageThreeValues : public Page
|
|||||||
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
||||||
|
|
||||||
// Get boat values #2
|
// Get boat values #2
|
||||||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
|
||||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||||
name2 = name2.substring(0, 6); // String length limit for value name
|
name2 = name2.substring(0, 6); // String length limit for value name
|
||||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||||
@@ -61,7 +61,7 @@ class PageThreeValues : public Page
|
|||||||
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
|
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
|
||||||
|
|
||||||
// Get boat values #3
|
// Get boat values #3
|
||||||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
|
||||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||||
name3 = name3.substring(0, 6); // String length limit for value name
|
name3 = name3.substring(0, 6); // String length limit for value name
|
||||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||||
@@ -77,7 +77,7 @@ class PageThreeValues : public Page
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging boat values
|
// Logging boat values
|
||||||
if (bvalue1 == NULL) return;
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageThreeValues, %s: %f, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3);
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageThreeValues, %s: %f, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -226,8 +226,7 @@ class PageThreeValues : public Page
|
|||||||
unit3old = unit3; // Save the old unit
|
unit3old = unit3; // Save the old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class PageTwoValues : public Page
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ class PageTwoValues : public Page
|
|||||||
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
||||||
|
|
||||||
// Get boat values #2
|
// Get boat values #2
|
||||||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
|
||||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||||
name2 = name2.substring(0, 6); // String length limit for value name
|
name2 = name2.substring(0, 6); // String length limit for value name
|
||||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||||
@@ -65,7 +65,7 @@ class PageTwoValues : public Page
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging boat values
|
// Logging boat values
|
||||||
if (bvalue1 == NULL) return;
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageTwoValues, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2);
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageTwoValues, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -166,8 +166,7 @@ class PageTwoValues : public Page
|
|||||||
unit2old = unit2; // Save the old unit
|
unit2old = unit2; // Save the old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ public:
|
|||||||
getdisplay().fillRect(x + 16, y + 11, 6, 3, color);
|
getdisplay().fillRect(x + 16, y + 11, 6, 3, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -187,7 +187,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging voltage value
|
// Logging voltage value
|
||||||
if (raw == 0) return;
|
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageVoltage, Type:%s %s:=%f", batType, name1.c_str(), raw);
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageVoltage, Type:%s %s:=%f", batType, name1.c_str(), raw);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -384,8 +383,7 @@ public:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -400,11 +398,11 @@ static Page *createPage(CommonData &common){
|
|||||||
* and will will provide the names of the fixed values we need
|
* and will will provide the names of the fixed values we need
|
||||||
*/
|
*/
|
||||||
PageDescription registerPageVoltage(
|
PageDescription registerPageVoltage(
|
||||||
"Voltage", // Name of page
|
"Voltage", // Name of page
|
||||||
createPage, // Action
|
createPage, // Action
|
||||||
0, // Number of bus values depends on selection in Web configuration
|
0, // Number of bus values depends on selection in Web configuration
|
||||||
{}, // Names of bus values undepends on selection in Web configuration (refer GwBoatData.h)
|
{}, // Names of bus values undepends on selection in Web configuration (refer GwBoatData.h)
|
||||||
true // Show display header on/off
|
true // Show display header on/off
|
||||||
);
|
);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -2,8 +2,14 @@
|
|||||||
|
|
||||||
#include "Pagedata.h"
|
#include "Pagedata.h"
|
||||||
#include "OBP60Extensions.h"
|
#include "OBP60Extensions.h"
|
||||||
#include "MFD_OBP60_400x300_sw.h" // MFD with logo
|
|
||||||
#include "Logo_OBP_400x300_sw.h" // OBP Logo
|
#include "images/OBP_400x300.xbm" // OBP Logo
|
||||||
|
#ifdef BOARD_OBP60S3
|
||||||
|
#include "images/OBP60_400x300.xbm" // MFD with logo
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
#include "images/OBP40_400x300.xbm" // MFD with logo
|
||||||
|
#endif
|
||||||
|
|
||||||
class PageWhite : public Page
|
class PageWhite : public Page
|
||||||
{
|
{
|
||||||
@@ -31,7 +37,7 @@ public:
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -61,17 +67,21 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mode == 'L') {
|
if (mode == 'L') {
|
||||||
getdisplay().drawBitmap(0, 0, gImage_Logo_OBP_400x300_sw, getdisplay().width(), getdisplay().height(), commonData->fgcolor);
|
getdisplay().drawXBitmap(0, 0, OBP_400x300_bits, OBP_400x300_width, OBP_400x300_height, commonData->fgcolor);
|
||||||
} else if (mode == 'M') {
|
} else if (mode == 'M') {
|
||||||
getdisplay().drawBitmap(0, 0, gImage_MFD_OBP60_400x300_sw, getdisplay().width(), getdisplay().height(), commonData->fgcolor);
|
#ifdef BOARD_OBP60S3
|
||||||
|
getdisplay().drawXBitmap(0, 0, OBP60_400x300_bits, OBP60_400x300_width, OBP60_400x300_height, commonData->fgcolor);
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
getdisplay().drawXBitmap(0, 0, OBP40_400x300_bits, OBP40_400x300_width, OBP40_400x300_height, commonData->fgcolor);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
int ret = PAGE_UPDATE;
|
||||||
getdisplay().nextPage();
|
|
||||||
if (mode == 'W') {
|
if (mode == 'W') {
|
||||||
getdisplay().hibernate();
|
ret |= PAGE_HIBERNATE;
|
||||||
}
|
}
|
||||||
|
return ret;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -247,8 +247,8 @@ public:
|
|||||||
if(key == 1){ // Mode switch
|
if(key == 1){ // Mode switch
|
||||||
if(mode == 'N'){
|
if(mode == 'N'){
|
||||||
mode = 'L';
|
mode = 'L';
|
||||||
} else if (mode == 'L') {
|
// } else if (mode == 'L') {
|
||||||
mode = 'X';
|
// mode = 'X';
|
||||||
} else {
|
} else {
|
||||||
mode = 'N';
|
mode = 'N';
|
||||||
}
|
}
|
||||||
@@ -296,7 +296,7 @@ public:
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData)
|
int displayPage(PageData &pageData)
|
||||||
{
|
{
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
@@ -354,7 +354,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging boat values
|
// Logging boat values
|
||||||
if (bvalue1 == NULL) return;
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageWind, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2);
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageWind, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -618,9 +618,7 @@ public:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -635,11 +633,11 @@ static Page *createPage(CommonData &common){
|
|||||||
* and will will provide the names of the fixed values we need
|
* and will will provide the names of the fixed values we need
|
||||||
*/
|
*/
|
||||||
PageDescription registerPageWind(
|
PageDescription registerPageWind(
|
||||||
"Wind", // Page name
|
"Wind", // Page name
|
||||||
createPage, // Action
|
createPage, // Action
|
||||||
0, // Number of bus values depends on selection in Web configuration
|
0, // Number of bus values depends on selection in Web configuration
|
||||||
{"AWS","AWA", "TWS", "TWA"}, // Bus values we need in the page
|
{"AWS","AWA", "TWS", "TWA"}, // Bus values we need in the page
|
||||||
true // Show display header on/off
|
true // Show display header on/off
|
||||||
);
|
);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
266
lib/obp60task/PageWindPlot.cpp
Normal file
266
lib/obp60task/PageWindPlot.cpp
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||||
|
|
||||||
|
#include "Pagedata.h"
|
||||||
|
#include "OBP60Extensions.h"
|
||||||
|
#include "OBPcharts.h"
|
||||||
|
|
||||||
|
// ****************************************************************
|
||||||
|
class PageWindPlot : public Page {
|
||||||
|
|
||||||
|
private:
|
||||||
|
GwLog* logger;
|
||||||
|
|
||||||
|
int width; // Screen width
|
||||||
|
int height; // Screen height
|
||||||
|
|
||||||
|
bool keylock = false; // Keylock
|
||||||
|
char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both
|
||||||
|
bool showTruW = true; // Show true wind or apparent wind in chart area
|
||||||
|
bool oldShowTruW = false; // remember recent user selection of wind data type
|
||||||
|
|
||||||
|
int 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
|
||||||
|
bool useSimuData;
|
||||||
|
String flashLED;
|
||||||
|
String backlightMode;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PageWindPlot(CommonData& common)
|
||||||
|
{
|
||||||
|
commonData = &common;
|
||||||
|
logger = commonData->logger;
|
||||||
|
LOG_DEBUG(GwLog::LOG, "Instantiate PageWindPlot");
|
||||||
|
|
||||||
|
// Get config data
|
||||||
|
useSimuData = common.config->getBool(common.config->useSimuData);
|
||||||
|
// holdValues = common.config->getBool(common.config->holdvalues);
|
||||||
|
flashLED = common.config->getString(common.config->flashLED);
|
||||||
|
backlightMode = common.config->getString(common.config->backlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void setupKeys()
|
||||||
|
{
|
||||||
|
Page::setupKeys();
|
||||||
|
commonData->keydata[0].label = "MODE";
|
||||||
|
#if defined BOARD_OBP60S3
|
||||||
|
commonData->keydata[1].label = "SRC";
|
||||||
|
commonData->keydata[4].label = "INTV";
|
||||||
|
#elif defined BOARD_OBP40S3
|
||||||
|
commonData->keydata[1].label = "INTV";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key functions
|
||||||
|
virtual int handleKey(int key)
|
||||||
|
{
|
||||||
|
// Set chart mode TWD | TWS
|
||||||
|
if (key == 1) {
|
||||||
|
if (chrtMode == 'D') {
|
||||||
|
chrtMode = 'S';
|
||||||
|
} else if (chrtMode == 'S') {
|
||||||
|
chrtMode = 'B';
|
||||||
|
} else {
|
||||||
|
chrtMode = 'D';
|
||||||
|
}
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined BOARD_OBP60S3
|
||||||
|
// Set data source TRUE | APP
|
||||||
|
if (key == 2) {
|
||||||
|
showTruW = !showTruW;
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set interval for wind history chart update time (interval)
|
||||||
|
if (key == 5) {
|
||||||
|
#elif defined BOARD_OBP40S3
|
||||||
|
if (key == 2) {
|
||||||
|
#endif
|
||||||
|
if (dataIntv == 1) {
|
||||||
|
dataIntv = 2;
|
||||||
|
} else if (dataIntv == 2) {
|
||||||
|
dataIntv = 3;
|
||||||
|
} else if (dataIntv == 3) {
|
||||||
|
dataIntv = 4;
|
||||||
|
} else if (dataIntv == 4) {
|
||||||
|
dataIntv = 8;
|
||||||
|
} else {
|
||||||
|
dataIntv = 1;
|
||||||
|
}
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keylock function
|
||||||
|
if (key == 11) { // Code for keylock
|
||||||
|
commonData->keylock = !commonData->keylock;
|
||||||
|
return 0; // Commit the key
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void displayNew(PageData& pageData)
|
||||||
|
{
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
String wndSrc; // Wind source true/apparent wind - preselection for OBP40
|
||||||
|
|
||||||
|
wndSrc = commonData->config->getString("page" + String(pageData.pageNumber) + "wndsrc");
|
||||||
|
if (wndSrc == "True wind") {
|
||||||
|
showTruW = true;
|
||||||
|
} else {
|
||||||
|
showTruW = false; // Wind source is apparent wind
|
||||||
|
}
|
||||||
|
LOG_DEBUG(GwLog::LOG, "New PageWindPlot; wind source=%s", wndSrc);
|
||||||
|
#endif
|
||||||
|
oldShowTruW = !showTruW; // makes wind source being initialized at initial page call
|
||||||
|
|
||||||
|
width = getdisplay().width(); // Screen width
|
||||||
|
height = getdisplay().height(); // Screen height
|
||||||
|
}
|
||||||
|
|
||||||
|
int displayPage(PageData& pageData)
|
||||||
|
{
|
||||||
|
GwConfigHandler* config = commonData->config;
|
||||||
|
|
||||||
|
static RingBuffer<uint16_t>* wdHstry; // Wind direction data buffer
|
||||||
|
static RingBuffer<uint16_t>* wsHstry; // Wind speed data buffer
|
||||||
|
static String wdName, wdFormat; // Wind direction name and format
|
||||||
|
static String wsName, wsFormat; // Wind speed name and format
|
||||||
|
|
||||||
|
// Separate chart objects for true wind and apparent wind
|
||||||
|
static std::unique_ptr<Chart<uint16_t>> twdFlChart, awdFlChart; // chart object for wind direction chart, full size
|
||||||
|
static std::unique_ptr<Chart<uint16_t>> twsFlChart, awsFlChart; // chart object for wind speed chart, full size
|
||||||
|
static std::unique_ptr<Chart<uint16_t>> twdHfChart, awdHfChart; // chart object for wind direction chart, half size
|
||||||
|
static std::unique_ptr<Chart<uint16_t>> twsHfChart, awsHfChart; // chart object for wind speed chart, half size
|
||||||
|
// Pointers to the currently active charts
|
||||||
|
static Chart<uint16_t>* wdFlChart;
|
||||||
|
static Chart<uint16_t>* wsFlChart;
|
||||||
|
static Chart<uint16_t>* wdHfChart;
|
||||||
|
static Chart<uint16_t>* wsHfChart;
|
||||||
|
|
||||||
|
static GwApi::BoatValue* wdBVal = new GwApi::BoatValue("TWD"); // temp BoatValue for wind direction unit identification; required by OBP60Formater
|
||||||
|
static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater */
|
||||||
|
double dfltRngWd = 60.0 * DEG_TO_RAD; // default range for course chart from min to max value in RAD
|
||||||
|
double dfltRngWs = 7.5; // default range for wind speed chart from min to max value in m/s
|
||||||
|
|
||||||
|
const int numBoatData = 4;
|
||||||
|
GwApi::BoatValue* bvalue[numBoatData]; // current boat data values
|
||||||
|
|
||||||
|
LOG_DEBUG(GwLog::LOG, "Display PageWindPlot");
|
||||||
|
ulong pageTime = millis();
|
||||||
|
|
||||||
|
// read boat data values
|
||||||
|
for (int i = 0; i < numBoatData; i++) {
|
||||||
|
bvalue[i] = pageData.values[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optical warning by limit violation (unused)
|
||||||
|
if (String(flashLED) == "Limit Violation") {
|
||||||
|
setBlinkingLED(false);
|
||||||
|
setFlashLED(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showTruW != oldShowTruW) {
|
||||||
|
if (!twdFlChart) { // Create true wind charts if they don't exist
|
||||||
|
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating true wind charts");
|
||||||
|
auto* twdHstry = pageData.boatHstry->hstryBufList.twdHstry;
|
||||||
|
auto* twsHstry = pageData.boatHstry->hstryBufList.twsHstry;
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG,"History Buffer addresses PageWindPlot: twdBuf: %p, twsBuf: %p", (void*)pageData.boatHstry->hstryBufList.twdHstry,
|
||||||
|
// (void*)pageData.boatHstry->hstryBufList.twsHstry);
|
||||||
|
|
||||||
|
twdFlChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*twdHstry, 1, 0, dfltRngWd, *commonData, useSimuData));
|
||||||
|
twsFlChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*twsHstry, 0, 0, dfltRngWs, *commonData, useSimuData));
|
||||||
|
twdHfChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*twdHstry, 1, 1, dfltRngWd, *commonData, useSimuData));
|
||||||
|
twsHfChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*twsHstry, 1, 2, dfltRngWs, *commonData, useSimuData));
|
||||||
|
// twdHfChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*twdHstry, 0, 1, dfltRngWd, *commonData, useSimuData));
|
||||||
|
// twsHfChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*twsHstry, 0, 2, dfltRngWs, *commonData, useSimuData));
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: twdHstry: %p, twsHstry: %p", (void*)twdHstry, (void*)twsHstry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!awdFlChart) { // Create apparent wind charts if they don't exist
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating apparent wind charts");
|
||||||
|
auto* awdHstry = pageData.boatHstry->hstryBufList.awdHstry;
|
||||||
|
auto* awsHstry = pageData.boatHstry->hstryBufList.awsHstry;
|
||||||
|
|
||||||
|
awdFlChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*awdHstry, 1, 0, dfltRngWd, *commonData, useSimuData));
|
||||||
|
awsFlChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*awsHstry, 0, 0, dfltRngWs, *commonData, useSimuData));
|
||||||
|
awdHfChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*awdHstry, 1, 1, dfltRngWd, *commonData, useSimuData));
|
||||||
|
awsHfChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*awsHstry, 1, 2, dfltRngWs, *commonData, useSimuData));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch active charts based on showTruW
|
||||||
|
if (showTruW) {
|
||||||
|
wdHstry = pageData.boatHstry->hstryBufList.twdHstry;
|
||||||
|
wsHstry = pageData.boatHstry->hstryBufList.twsHstry;
|
||||||
|
wdFlChart = twdFlChart.get();
|
||||||
|
wsFlChart = twsFlChart.get();
|
||||||
|
wdHfChart = twdHfChart.get();
|
||||||
|
wsHfChart = twsHfChart.get();
|
||||||
|
} else {
|
||||||
|
wdHstry = pageData.boatHstry->hstryBufList.awdHstry;
|
||||||
|
wsHstry = pageData.boatHstry->hstryBufList.awsHstry;
|
||||||
|
wdFlChart = awdFlChart.get();
|
||||||
|
wsFlChart = awsFlChart.get();
|
||||||
|
wdHfChart = awdHfChart.get();
|
||||||
|
wsHfChart = awsHfChart.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
wdHstry->getMetaData(wdName, wdFormat);
|
||||||
|
wsHstry->getMetaData(wsName, wsFormat);
|
||||||
|
|
||||||
|
oldShowTruW = showTruW;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw page
|
||||||
|
//***********************************************************
|
||||||
|
|
||||||
|
// Set display in partial refresh mode
|
||||||
|
getdisplay().setPartialWindow(0, 0, width, height); // Set partial update
|
||||||
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
|
if (chrtMode == 'D') {
|
||||||
|
wdBVal->value = wdHstry->getLast();
|
||||||
|
wdBVal->valid = wdBVal->value != wdHstry->getMaxVal();
|
||||||
|
wdFlChart->showChrt(dataIntv, *bvalue[0]);
|
||||||
|
|
||||||
|
} else if (chrtMode == 'S') {
|
||||||
|
wsBVal->value = wsHstry->getLast();
|
||||||
|
wsBVal->valid = wsBVal->value != wsHstry->getMaxVal();
|
||||||
|
wsFlChart->showChrt(dataIntv, *bvalue[1]);
|
||||||
|
|
||||||
|
} else if (chrtMode == 'B') {
|
||||||
|
wdBVal->value = wdHstry->getLast();
|
||||||
|
wdBVal->valid = wdBVal->value != wdHstry->getMaxVal();
|
||||||
|
wsBVal->value = wsHstry->getLast();
|
||||||
|
wsBVal->valid = wsBVal->value != wsHstry->getMaxVal();
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot showChrt: wsBVal.name: %s, format: %s, wsBVal.value: %.1f, valid: %d, address: %p", wsBVal->getName(), wsBVal->getFormat(), wsBVal->value,
|
||||||
|
wsBVal->valid, wsBVal);
|
||||||
|
wdHfChart->showChrt(dataIntv, *bvalue[0]);
|
||||||
|
wsHfChart->showChrt(dataIntv, *bvalue[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG(GwLog::LOG, "PageWindPlot: page time %ldms", millis() - pageTime);
|
||||||
|
return PAGE_UPDATE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static Page* createPage(CommonData& common)
|
||||||
|
{
|
||||||
|
return new PageWindPlot(common);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* with the code below we make this page known to the PageTask
|
||||||
|
* we give it a type (name) that can be selected in the config
|
||||||
|
* we define which function is to be called
|
||||||
|
* and we provide the number of user parameters we expect (0 here)
|
||||||
|
* and will will provide the names of the fixed values we need */
|
||||||
|
PageDescription registerPageWindPlot(
|
||||||
|
"WindPlot", // Page name
|
||||||
|
createPage, // Action
|
||||||
|
0, // Number of bus values depends on selection in Web configuration
|
||||||
|
{ "TWD", "TWS", "AWD", "AWS" }, // Bus values we need in the page
|
||||||
|
true // Show display header on/off
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -24,7 +24,7 @@ public:
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ public:
|
|||||||
String flashLED = config->getString(config->flashLED);
|
String flashLED = config->getString(config->flashLED);
|
||||||
String backlightMode = config->getString(config->backlight);
|
String backlightMode = config->getString(config->backlight);
|
||||||
|
|
||||||
// Get boat values for AWA
|
// Get boat value for AWA
|
||||||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||||
name1 = name1.substring(0, 6); // String length limit for value name
|
name1 = name1.substring(0, 6); // String length limit for value name
|
||||||
@@ -63,8 +63,8 @@ public:
|
|||||||
unit1old = unit1; // Save old unit
|
unit1old = unit1; // Save old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get boat values for AWS
|
// Get boat value for AWS
|
||||||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // First element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
|
||||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||||
name2 = name2.substring(0, 6); // String length limit for value name
|
name2 = name2.substring(0, 6); // String length limit for value name
|
||||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||||
@@ -77,8 +77,8 @@ public:
|
|||||||
unit2old = unit2; // Save old unit
|
unit2old = unit2; // Save old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get boat values TWD
|
// Get boat value for TWD
|
||||||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
|
||||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||||
name3 = name3.substring(0, 6); // String length limit for value name
|
name3 = name3.substring(0, 6); // String length limit for value name
|
||||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||||
@@ -91,9 +91,9 @@ public:
|
|||||||
unit3old = unit3; // Save old unit
|
unit3old = unit3; // Save old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get boat values TWS
|
// Get boat value for TWS
|
||||||
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Second element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Fourth element in list
|
||||||
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||||
name4 = name4.substring(0, 6); // String length limit for value name
|
name4 = name4.substring(0, 6); // String length limit for value name
|
||||||
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
||||||
double value4 = bvalue4->value; // Value as double in SI unit
|
double value4 = bvalue4->value; // Value as double in SI unit
|
||||||
@@ -105,9 +105,9 @@ public:
|
|||||||
unit4old = unit4; // Save old unit
|
unit4old = unit4; // Save old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get boat values DBT
|
// Get boat value for DBT
|
||||||
GwApi::BoatValue *bvalue5 = pageData.values[4]; // Second element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue5 = pageData.values[4]; // Fifth element in list
|
||||||
String name5 = xdrDelete(bvalue5->getName()); // Value name
|
String name5 = xdrDelete(bvalue5->getName()); // Value name
|
||||||
name5 = name5.substring(0, 6); // String length limit for value name
|
name5 = name5.substring(0, 6); // String length limit for value name
|
||||||
calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
|
||||||
double value5 = bvalue5->value; // Value as double in SI unit
|
double value5 = bvalue5->value; // Value as double in SI unit
|
||||||
@@ -119,9 +119,9 @@ public:
|
|||||||
unit5old = unit5; // Save old unit
|
unit5old = unit5; // Save old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get boat values STW
|
// Get boat value for STW
|
||||||
GwApi::BoatValue *bvalue6 = pageData.values[5]; // Second element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue6 = pageData.values[5]; // Sixth element in list
|
||||||
String name6 = xdrDelete(bvalue6->getName()); // Value name
|
String name6 = xdrDelete(bvalue6->getName()); // Value name
|
||||||
name6 = name6.substring(0, 6); // String length limit for value name
|
name6 = name6.substring(0, 6); // String length limit for value name
|
||||||
calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
|
||||||
double value6 = bvalue6->value; // Value as double in SI unit
|
double value6 = bvalue6->value; // Value as double in SI unit
|
||||||
@@ -140,7 +140,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging boat values
|
// Logging boat values
|
||||||
if (bvalue1 == NULL) return;
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageWindRose, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, name6.c_str(), value6);
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageWindRose, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, name6.c_str(), value6);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -248,7 +248,7 @@ public:
|
|||||||
float y = 150 - (rInstrument-30)*cos(i/180.0*pi); // y-coordinate cots
|
float y = 150 - (rInstrument-30)*cos(i/180.0*pi); // y-coordinate cots
|
||||||
const char *ii = "";
|
const char *ii = "";
|
||||||
switch (i)
|
switch (i)
|
||||||
{
|
{
|
||||||
case 0: ii="0"; break;
|
case 0: ii="0"; break;
|
||||||
case 30 : ii="30"; break;
|
case 30 : ii="30"; break;
|
||||||
case 60 : ii="60"; break;
|
case 60 : ii="60"; break;
|
||||||
@@ -357,8 +357,7 @@ public:
|
|||||||
getdisplay().print(unit6old); // Unit
|
getdisplay().print(unit6old); // Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -373,11 +372,11 @@ static Page *createPage(CommonData &common){
|
|||||||
* and will will provide the names of the fixed values we need
|
* and will will provide the names of the fixed values we need
|
||||||
*/
|
*/
|
||||||
PageDescription registerPageWindRose(
|
PageDescription registerPageWindRose(
|
||||||
"WindRose", // Page name
|
"WindRose", // Page name
|
||||||
createPage, // Action
|
createPage, // Action
|
||||||
0, // Number of bus values depends on selection in Web configuration
|
0, // Number of bus values depends on selection in Web configuration
|
||||||
{"AWA", "AWS", "TWD", "TWS", "DBT", "STW"}, // Bus values we need in the page
|
{"AWA", "AWS", "TWD", "TWS", "DBT", "STW"}, // Bus values we need in the page
|
||||||
true // Show display header on/off
|
true // Show display header on/off
|
||||||
);
|
);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -7,15 +7,30 @@
|
|||||||
class PageWindRoseFlex : public Page
|
class PageWindRoseFlex : public Page
|
||||||
{
|
{
|
||||||
int16_t lp = 80; // Pointer length
|
int16_t lp = 80; // Pointer length
|
||||||
|
char source = 'A'; // data source (A)pparent | (T)rue
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PageWindRoseFlex(CommonData &common){
|
PageWindRoseFlex(CommonData &common){
|
||||||
commonData = &common;
|
commonData = &common;
|
||||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageWindRoseFlex");
|
common.logger->logDebug(GwLog::LOG,"Instantiate PageWindRoseFlex");
|
||||||
}
|
}
|
||||||
|
virtual void setupKeys(){
|
||||||
|
Page::setupKeys();
|
||||||
|
commonData->keydata[1].label = "SRC";
|
||||||
|
}
|
||||||
|
|
||||||
// Key functions
|
// Key functions
|
||||||
virtual int handleKey(int key){
|
virtual int handleKey(int key){
|
||||||
|
if(key == 2){
|
||||||
|
// Code for set source
|
||||||
|
if(source == 'A'){
|
||||||
|
source = 'T';
|
||||||
|
} else {
|
||||||
|
source = 'A';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key; // Commit the key
|
||||||
|
|
||||||
// Code for keylock
|
// Code for keylock
|
||||||
if(key == 11){
|
if(key == 11){
|
||||||
commonData->keylock = !commonData->keylock;
|
commonData->keylock = !commonData->keylock;
|
||||||
@@ -24,7 +39,7 @@ public:
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -40,6 +55,11 @@ public:
|
|||||||
static String unit5old = "";
|
static String unit5old = "";
|
||||||
static String svalue6old = "";
|
static String svalue6old = "";
|
||||||
static String unit6old = "";
|
static String unit6old = "";
|
||||||
|
static GFXfont name3font;
|
||||||
|
static GFXfont name4font;
|
||||||
|
static GFXfont name5font;
|
||||||
|
static GFXfont name6font;
|
||||||
|
|
||||||
|
|
||||||
// Get config data
|
// Get config data
|
||||||
String lengthformat = config->getString(config->lengthFormat);
|
String lengthformat = config->getString(config->lengthFormat);
|
||||||
@@ -48,39 +68,60 @@ public:
|
|||||||
String flashLED = config->getString(config->flashLED);
|
String flashLED = config->getString(config->flashLED);
|
||||||
String backlightMode = config->getString(config->backlight);
|
String backlightMode = config->getString(config->backlight);
|
||||||
|
|
||||||
// Get boat values for AWA
|
GwApi::BoatValue *bvalue1; // Value 1 for angle
|
||||||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue2; // Value 2 for speed
|
||||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
|
||||||
|
// Get boat value for wind angle (AWA/TWA), shown by pointer
|
||||||
|
if (source == 'A') {
|
||||||
|
bvalue1 = pageData.values[4];
|
||||||
|
} else {
|
||||||
|
bvalue1 = pageData.values[6];
|
||||||
|
}
|
||||||
|
String name1 = bvalue1->getName().c_str(); // Value name
|
||||||
name1 = name1.substring(0, 6); // String length limit for value name
|
name1 = name1.substring(0, 6); // String length limit for value name
|
||||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||||
double value1 = bvalue1->value; // Value as double in SI unit
|
double value1 = bvalue1->value; // Value as double in SI unit
|
||||||
bool valid1 = bvalue1->valid; // Valid information
|
bool valid1 = bvalue1->valid; // Valid information
|
||||||
value1 = formatValue(bvalue1, *commonData).value;// Format only nesaccery for simulation data for pointer
|
|
||||||
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
||||||
if(valid1 == true){
|
if(valid1 == true){
|
||||||
svalue1old = svalue1; // Save old value
|
svalue1old = svalue1; // Save old value
|
||||||
unit1old = unit1; // Save old unit
|
unit1old = unit1; // Save old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get boat values for AWS
|
// Get boat value for wind speed (AWS/TWS), shown in top left corner
|
||||||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // First element in list (only one value by PageOneValue)
|
if (source == 'A') {
|
||||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
bvalue2 =pageData.values[5];
|
||||||
|
} else {
|
||||||
|
bvalue2 = pageData.values[7];
|
||||||
|
}
|
||||||
|
String name2 = bvalue2->getName().c_str(); // Value name
|
||||||
name2 = name2.substring(0, 6); // String length limit for value name
|
name2 = name2.substring(0, 6); // String length limit for value name
|
||||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||||
double value2 = bvalue2->value; // Value as double in SI unit
|
double value2 = bvalue2->value; // Value as double in SI unit
|
||||||
bool valid2 = bvalue2->valid; // Valid information
|
bool valid2 = bvalue2->valid; // Valid information
|
||||||
|
if (simulation) {
|
||||||
|
value2 = 0.62731; // some random value
|
||||||
|
}
|
||||||
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||||
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
|
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
|
||||||
if(valid2 == true){
|
if(valid2 == true){
|
||||||
svalue2old = svalue2; // Save old value
|
svalue2old = svalue2; // Save old value
|
||||||
unit2old = unit2; // Save old unit
|
unit2old = unit2; // Save old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get boat values TWD
|
|
||||||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue)
|
|
||||||
|
// Get boat value for bottom left corner
|
||||||
|
GwApi::BoatValue *bvalue3 = pageData.values[0];
|
||||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||||
name3 = name3.substring(0, 6); // String length limit for value name
|
name3 = name3.substring(0, 6); // String length limit for value name
|
||||||
|
if (name3.length()>3){
|
||||||
|
name3font=Ubuntu_Bold8pt8b;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
name3font=Ubuntu_Bold12pt8b;
|
||||||
|
}
|
||||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||||
double value3 = bvalue3->value; // Value as double in SI unit
|
double value3 = bvalue3->value; // Value as double in SI unit
|
||||||
bool valid3 = bvalue3->valid; // Valid information
|
bool valid3 = bvalue3->valid; // Valid information
|
||||||
@@ -91,10 +132,16 @@ public:
|
|||||||
unit3old = unit3; // Save old unit
|
unit3old = unit3; // Save old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get boat values TWS
|
// Get boat value for top right corner
|
||||||
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Second element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue4 = pageData.values[1];
|
||||||
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||||
name4 = name4.substring(0, 6); // String length limit for value name
|
name4 = name4.substring(0, 6); // String length limit for value name
|
||||||
|
if (name4.length()>3){
|
||||||
|
name4font=Ubuntu_Bold8pt8b;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
name4font=Ubuntu_Bold12pt8b;
|
||||||
|
}
|
||||||
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
||||||
double value4 = bvalue4->value; // Value as double in SI unit
|
double value4 = bvalue4->value; // Value as double in SI unit
|
||||||
bool valid4 = bvalue4->valid; // Valid information
|
bool valid4 = bvalue4->valid; // Valid information
|
||||||
@@ -105,10 +152,16 @@ public:
|
|||||||
unit4old = unit4; // Save old unit
|
unit4old = unit4; // Save old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get boat values DBT
|
// Get boat value bottom right corner
|
||||||
GwApi::BoatValue *bvalue5 = pageData.values[4]; // Second element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue5 = pageData.values[2];
|
||||||
String name5 = xdrDelete(bvalue5->getName()); // Value name
|
String name5 = xdrDelete(bvalue5->getName()); // Value name
|
||||||
name5 = name5.substring(0, 6); // String length limit for value name
|
name5 = name5.substring(0, 6); // String length limit for value name
|
||||||
|
if (name5.length()>3){
|
||||||
|
name5font=Ubuntu_Bold8pt8b;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
name5font=Ubuntu_Bold12pt8b;
|
||||||
|
}
|
||||||
calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
|
||||||
double value5 = bvalue5->value; // Value as double in SI unit
|
double value5 = bvalue5->value; // Value as double in SI unit
|
||||||
bool valid5 = bvalue5->valid; // Valid information
|
bool valid5 = bvalue5->valid; // Valid information
|
||||||
@@ -119,10 +172,16 @@ public:
|
|||||||
unit5old = unit5; // Save old unit
|
unit5old = unit5; // Save old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get boat values STW
|
// Get boat value for center (name is not displayed)
|
||||||
GwApi::BoatValue *bvalue6 = pageData.values[5]; // Second element in list (only one value by PageOneValue)
|
GwApi::BoatValue *bvalue6 = pageData.values[3];
|
||||||
String name6 = xdrDelete(bvalue6->getName()); // Value name
|
String name6 = xdrDelete(bvalue6->getName()); // Value name
|
||||||
name6 = name6.substring(0, 6); // String length limit for value name
|
name6 = name6.substring(0, 6); // String length limit for value name
|
||||||
|
if (name6.length()>3){
|
||||||
|
name6font=Ubuntu_Bold8pt8b;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
name6font=Ubuntu_Bold8pt8b;
|
||||||
|
}
|
||||||
calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
|
calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
|
||||||
double value6 = bvalue6->value; // Value as double in SI unit
|
double value6 = bvalue6->value; // Value as double in SI unit
|
||||||
bool valid6 = bvalue6->valid; // Valid information
|
bool valid6 = bvalue6->valid; // Valid information
|
||||||
@@ -133,6 +192,7 @@ public:
|
|||||||
unit6old = unit6; // Save old unit
|
unit6old = unit6; // Save old unit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Optical warning by limit violation (unused)
|
// Optical warning by limit violation (unused)
|
||||||
if(String(flashLED) == "Limit Violation"){
|
if(String(flashLED) == "Limit Violation"){
|
||||||
setBlinkingLED(false);
|
setBlinkingLED(false);
|
||||||
@@ -140,7 +200,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Logging boat values
|
// Logging boat values
|
||||||
if (bvalue1 == NULL) return;
|
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageWindRoseFlex, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, name6.c_str(), value6);
|
LOG_DEBUG(GwLog::LOG,"Drawing at PageWindRoseFlex, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, name6.c_str(), value6);
|
||||||
|
|
||||||
// Draw page
|
// Draw page
|
||||||
@@ -151,7 +211,7 @@ public:
|
|||||||
|
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
|
||||||
// Show value 2 at position of value 1 (top left)
|
// Show AWS or TWS top left
|
||||||
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
|
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||||
getdisplay().setCursor(10, 65);
|
getdisplay().setCursor(10, 65);
|
||||||
getdisplay().print(svalue2); // Value
|
getdisplay().print(svalue2); // Value
|
||||||
@@ -171,11 +231,11 @@ public:
|
|||||||
// Horizintal separator left
|
// Horizintal separator left
|
||||||
getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor);
|
getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor);
|
||||||
|
|
||||||
// Show value 3 at bottom left
|
// Show value 3 (=first user-configured parameter) at bottom left
|
||||||
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
|
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||||
getdisplay().setCursor(10, 270);
|
getdisplay().setCursor(10, 270);
|
||||||
getdisplay().print(svalue3); // Value
|
getdisplay().print(svalue3); // Value
|
||||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
getdisplay().setFont(&name3font);
|
||||||
getdisplay().setCursor(10, 220);
|
getdisplay().setCursor(10, 220);
|
||||||
getdisplay().print(name3); // Name
|
getdisplay().print(name3); // Name
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
@@ -188,21 +248,15 @@ public:
|
|||||||
getdisplay().print(unit3old); // Unit
|
getdisplay().print(unit3old); // Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show value 4 at top right
|
// Show value 4 (=second user-configured parameter) at top right
|
||||||
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
|
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||||
getdisplay().setCursor(295, 65);
|
getdisplay().setCursor(295, 65);
|
||||||
if(valid3 == true){
|
getdisplay().print(svalue4); // Value
|
||||||
// getdisplay().print(abs(value3 * 180 / PI), 0); // Value
|
getdisplay().setFont(&name4font);
|
||||||
getdisplay().print(svalue4); // Value
|
getdisplay().setCursor(325, 95);
|
||||||
}
|
|
||||||
else{
|
|
||||||
getdisplay().print("---"); // Value
|
|
||||||
}
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
|
||||||
getdisplay().setCursor(335, 95);
|
|
||||||
getdisplay().print(name4); // Name
|
getdisplay().print(name4); // Name
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
getdisplay().setCursor(335, 115);
|
getdisplay().setCursor(325, 115);
|
||||||
getdisplay().print(" ");
|
getdisplay().print(" ");
|
||||||
if(holdvalues == false){
|
if(holdvalues == false){
|
||||||
getdisplay().print(unit4); // Unit
|
getdisplay().print(unit4); // Unit
|
||||||
@@ -214,15 +268,15 @@ public:
|
|||||||
// Horizintal separator right
|
// Horizintal separator right
|
||||||
getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor);
|
getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor);
|
||||||
|
|
||||||
// Show value 5 at bottom right
|
// Show value 5 (=third user-configured parameter) at bottom right
|
||||||
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
|
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||||
getdisplay().setCursor(295, 270);
|
getdisplay().setCursor(295, 270);
|
||||||
getdisplay().print(svalue5); // Value
|
getdisplay().print(svalue5); // Value
|
||||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
getdisplay().setFont(&name5font);
|
||||||
getdisplay().setCursor(335, 220);
|
getdisplay().setCursor(325, 220);
|
||||||
getdisplay().print(name5); // Name
|
getdisplay().print(name5); // Name
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
getdisplay().setCursor(335, 190);
|
getdisplay().setCursor(325, 190);
|
||||||
getdisplay().print(" ");
|
getdisplay().print(" ");
|
||||||
if(holdvalues == false){
|
if(holdvalues == false){
|
||||||
getdisplay().print(unit5); // Unit
|
getdisplay().print(unit5); // Unit
|
||||||
@@ -236,7 +290,6 @@ public:
|
|||||||
|
|
||||||
// Draw wind rose
|
// Draw wind rose
|
||||||
int rInstrument = 110; // Radius of grafic instrument
|
int rInstrument = 110; // Radius of grafic instrument
|
||||||
float pi = 3.141592;
|
|
||||||
|
|
||||||
getdisplay().fillCircle(200, 150, rInstrument + 10, commonData->fgcolor); // Outer circle
|
getdisplay().fillCircle(200, 150, rInstrument + 10, commonData->fgcolor); // Outer circle
|
||||||
getdisplay().fillCircle(200, 150, rInstrument + 7, commonData->bgcolor); // Outer circle
|
getdisplay().fillCircle(200, 150, rInstrument + 7, commonData->bgcolor); // Outer circle
|
||||||
@@ -246,24 +299,23 @@ public:
|
|||||||
for(int i=0; i<360; i=i+10)
|
for(int i=0; i<360; i=i+10)
|
||||||
{
|
{
|
||||||
// Scaling values
|
// Scaling values
|
||||||
float x = 200 + (rInstrument-30)*sin(i/180.0*pi); // x-coordinate dots
|
float x = 200 + (rInstrument-30)*sin(i/180.0*M_PI); // x-coordinate dots
|
||||||
float y = 150 - (rInstrument-30)*cos(i/180.0*pi); // y-coordinate dots
|
float y = 150 - (rInstrument-30)*cos(i/180.0*M_PI); // y-coordinate dots
|
||||||
const char *ii = "";
|
const char *ii = "";
|
||||||
switch (i)
|
switch (i) {
|
||||||
{
|
case 0: ii="0"; break;
|
||||||
case 0: ii="0"; break;
|
case 30 : ii="30"; break;
|
||||||
case 30 : ii="30"; break;
|
case 60 : ii="60"; break;
|
||||||
case 60 : ii="60"; break;
|
case 90 : ii="90"; break;
|
||||||
case 90 : ii="90"; break;
|
case 120 : ii="120"; break;
|
||||||
case 120 : ii="120"; break;
|
case 150 : ii="150"; break;
|
||||||
case 150 : ii="150"; break;
|
case 180 : ii="180"; break;
|
||||||
case 180 : ii="180"; break;
|
case 210 : ii="210"; break;
|
||||||
case 210 : ii="210"; break;
|
case 240 : ii="240"; break;
|
||||||
case 240 : ii="240"; break;
|
case 270 : ii="270"; break;
|
||||||
case 270 : ii="270"; break;
|
case 300 : ii="300"; break;
|
||||||
case 300 : ii="300"; break;
|
case 330 : ii="330"; break;
|
||||||
case 330 : ii="330"; break;
|
default: break;
|
||||||
default: break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print text centered on position x, y
|
// Print text centered on position x, y
|
||||||
@@ -277,11 +329,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw sub scale with dots
|
// Draw sub scale with dots
|
||||||
float x1c = 200 + rInstrument*sin(i/180.0*pi);
|
float x1c = 200 + rInstrument*sin(i/180.0*M_PI);
|
||||||
float y1c = 150 - rInstrument*cos(i/180.0*pi);
|
float y1c = 150 - rInstrument*cos(i/180.0*M_PI);
|
||||||
getdisplay().fillCircle((int)x1c, (int)y1c, 2, commonData->fgcolor);
|
getdisplay().fillCircle((int)x1c, (int)y1c, 2, commonData->fgcolor);
|
||||||
float sinx=sin(i/180.0*pi);
|
float sinx=sin(i/180.0*M_PI);
|
||||||
float cosx=cos(i/180.0*pi);
|
float cosx=cos(i/180.0*M_PI);
|
||||||
|
|
||||||
// Draw sub scale with lines (two triangles)
|
// Draw sub scale with lines (two triangles)
|
||||||
if(i % 30 == 0){
|
if(i % 30 == 0){
|
||||||
@@ -326,13 +378,24 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Center circle
|
// Center circle
|
||||||
getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor);
|
getdisplay().fillCircle(200, 150, startwidth + 8, commonData->bgcolor);
|
||||||
getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor);
|
getdisplay().fillCircle(200, 150, startwidth + 6, commonData->fgcolor);
|
||||||
|
getdisplay().fillCircle(200, 150, startwidth + 4, commonData->bgcolor);
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold10pt8b);
|
||||||
|
if (source=='A'){
|
||||||
|
getdisplay().setCursor(193, 155);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
getdisplay().setCursor(195, 156);
|
||||||
|
}
|
||||||
|
getdisplay().print({source});
|
||||||
|
|
||||||
|
|
||||||
//*******************************************************************************************
|
//*******************************************************************************************
|
||||||
|
|
||||||
// Show value6, so that it does not collide with the wind pointer
|
// Show value6 (=fourth user-configured parameter)
|
||||||
if ( cos(value1) > 0){
|
if ( cos(value1) > 0){
|
||||||
|
//pointer points upwards
|
||||||
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
|
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||||
getdisplay().setCursor(160, 200);
|
getdisplay().setCursor(160, 200);
|
||||||
getdisplay().print(svalue6); // Value
|
getdisplay().print(svalue6); // Value
|
||||||
@@ -345,8 +408,9 @@ if ( cos(value1) > 0){
|
|||||||
else{
|
else{
|
||||||
getdisplay().print(unit6old); // Unit
|
getdisplay().print(unit6old); // Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
// pointer points downwards
|
||||||
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
|
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||||
getdisplay().setCursor(160, 130);
|
getdisplay().setCursor(160, 130);
|
||||||
getdisplay().print(svalue6); // Value
|
getdisplay().print(svalue6); // Value
|
||||||
@@ -359,11 +423,9 @@ if ( cos(value1) > 0){
|
|||||||
else{
|
else{
|
||||||
getdisplay().print(unit6old); // Unit
|
getdisplay().print(unit6old); // Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return PAGE_UPDATE;
|
||||||
// Update display
|
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -374,15 +436,15 @@ static Page *createPage(CommonData &common){
|
|||||||
* with the code below we make this page known to the PageTask
|
* with the code below we make this page known to the PageTask
|
||||||
* we give it a type (name) that can be selected in the config
|
* we give it a type (name) that can be selected in the config
|
||||||
* we define which function is to be called
|
* we define which function is to be called
|
||||||
* and we provide the number of user parameters we expect (0 here)
|
* and we provide the number of user parameters we expect (4 here)
|
||||||
* and will will provide the names of the fixed values we need
|
* and will will provide the names of the fixed values we need
|
||||||
*/
|
*/
|
||||||
PageDescription registerPageWindRoseFlex(
|
PageDescription registerPageWindRoseFlex(
|
||||||
"WindRoseFlex", // Page name
|
"WindRoseFlex", // Page name
|
||||||
createPage, // Action
|
createPage, // Action
|
||||||
6, // Number of bus values depends on selection in Web configuration; was zero
|
4, // Number of bus values depends on selection in Web configuration
|
||||||
//{"AWA", "AWS", "COG", "SOG", "TWD", "TWS"}, // Bus values we need in the page, modified for WindRose2
|
{"AWA", "AWS", "TWA", "TWS"}, // fixed values we need in the page. They are inserted AFTER the web-configured values.
|
||||||
true // Show display header on/off
|
true // Show display header on/off
|
||||||
);
|
);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class PageXTETrack : public Page
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void displayPage(PageData &pageData){
|
int displayPage(PageData &pageData){
|
||||||
GwConfigHandler *config = commonData->config;
|
GwConfigHandler *config = commonData->config;
|
||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
@@ -207,9 +207,7 @@ class PageXTETrack : public Page
|
|||||||
drawSegment(399, 100, 318, 24, 289, 24, 350, 100, commonData->fgcolor, seg[4]);
|
drawSegment(399, 100, 318, 24, 289, 24, 350, 100, commonData->fgcolor, seg[4]);
|
||||||
drawSegment(399, 54, 354, 24, 325, 24, 399, 90, commonData->fgcolor, seg[5]);
|
drawSegment(399, 54, 354, 24, 325, 24, 399, 90, commonData->fgcolor, seg[5]);
|
||||||
|
|
||||||
// Update display
|
return PAGE_UPDATE;
|
||||||
getdisplay().nextPage(); // Partial update (fast)
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -225,11 +223,11 @@ static Page* createPage(CommonData &common){
|
|||||||
* this will be number of BoatValue pointers in pageData.values
|
* this will be number of BoatValue pointers in pageData.values
|
||||||
*/
|
*/
|
||||||
PageDescription registerPageXTETrack(
|
PageDescription registerPageXTETrack(
|
||||||
"XTETrack", // Page name
|
"XTETrack", // Page name
|
||||||
createPage, // Action
|
createPage, // Action
|
||||||
0, // Number of bus values depends on selection in Web configuration
|
0, // Number of bus values depends on selection in Web configuration
|
||||||
{"XTE", "COG", "DTW", "BTW"}, // Bus values we need in the page
|
{"XTE", "COG", "DTW", "BTW"}, // Bus values we need in the page
|
||||||
true // Show display header on/off
|
true // Show display header on/off
|
||||||
);
|
);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -4,15 +4,19 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "LedSpiTask.h"
|
#include "LedSpiTask.h"
|
||||||
|
#include "OBPDataOperations.h"
|
||||||
|
|
||||||
#define MAX_PAGE_NUMBER 10 // Max number of pages for show data
|
#define MAX_PAGE_NUMBER 10 // Max number of pages for show data
|
||||||
|
|
||||||
typedef std::vector<GwApi::BoatValue *> ValueList;
|
typedef std::vector<GwApi::BoatValue *> ValueList;
|
||||||
|
|
||||||
typedef struct{
|
typedef struct{
|
||||||
|
GwApi *api;
|
||||||
String pageName;
|
String pageName;
|
||||||
uint8_t pageNumber; // page number in sequence of visible pages
|
uint8_t pageNumber; // page number in sequence of visible pages
|
||||||
//the values will always contain the user defined values first
|
//the values will always contain the user defined values first
|
||||||
ValueList values;
|
ValueList values;
|
||||||
|
HstryBuf* boatHstry;
|
||||||
} PageData;
|
} PageData;
|
||||||
|
|
||||||
// Sensor data structure (only for extended sensors, not for NMEA bus sensors)
|
// Sensor data structure (only for extended sensors, not for NMEA bus sensors)
|
||||||
@@ -78,20 +82,37 @@ typedef struct{
|
|||||||
bool on; // fast on/off detector
|
bool on; // fast on/off detector
|
||||||
} BacklightData;
|
} BacklightData;
|
||||||
|
|
||||||
|
enum AlarmSource {
|
||||||
|
Alarm_Generic,
|
||||||
|
Alarm_Local,
|
||||||
|
Alarm_NMEA0183,
|
||||||
|
Alarm_NMEA2000
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct{
|
typedef struct{
|
||||||
GwApi::Status status;
|
uint8_t id; // alarm-id e.g. 01..99 from NMEA0183
|
||||||
GwLog *logger=NULL;
|
AlarmSource source;
|
||||||
GwConfigHandler *config=NULL;
|
String message; // single line of plain text
|
||||||
SensorData data;
|
bool active = false;
|
||||||
SunData sundata;
|
uint8_t signal; // how to signal MESSAGE | LED | BUZZER
|
||||||
TouchKeyData keydata[6];
|
uint8_t length_sec; // seconds until alarm disappeares without user interaction
|
||||||
BacklightData backlight;
|
} AlarmData;
|
||||||
GwApi::BoatValue *time=NULL;
|
|
||||||
GwApi::BoatValue *date=NULL;
|
typedef struct{
|
||||||
uint16_t fgcolor;
|
GwApi::Status status;
|
||||||
uint16_t bgcolor;
|
GwLog *logger = nullptr;
|
||||||
bool keylock = false;
|
GwConfigHandler *config = nullptr;
|
||||||
String powermode;
|
SensorData data;
|
||||||
|
SunData sundata;
|
||||||
|
TouchKeyData keydata[6];
|
||||||
|
BacklightData backlight;
|
||||||
|
AlarmData alarm;
|
||||||
|
GwApi::BoatValue *time = nullptr;
|
||||||
|
GwApi::BoatValue *date = nullptr;
|
||||||
|
uint16_t fgcolor;
|
||||||
|
uint16_t bgcolor;
|
||||||
|
bool keylock = false;
|
||||||
|
String powermode;
|
||||||
} CommonData;
|
} CommonData;
|
||||||
|
|
||||||
//a base class that all pages must inherit from
|
//a base class that all pages must inherit from
|
||||||
@@ -100,8 +121,9 @@ class Page{
|
|||||||
CommonData *commonData;
|
CommonData *commonData;
|
||||||
public:
|
public:
|
||||||
int refreshtime = 1000;
|
int refreshtime = 1000;
|
||||||
virtual void displayPage(PageData &pageData)=0;
|
virtual int displayPage(PageData &pageData)=0;
|
||||||
virtual void displayNew(PageData &pageData){}
|
virtual void displayNew(PageData &pageData){}
|
||||||
|
virtual void leavePage(PageData &pageData){}
|
||||||
virtual void setupKeys() {
|
virtual void setupKeys() {
|
||||||
#ifdef HARDWARE_V21
|
#ifdef HARDWARE_V21
|
||||||
commonData->keydata[0].label = "";
|
commonData->keydata[0].label = "";
|
||||||
@@ -160,9 +182,9 @@ class PageDescription{
|
|||||||
|
|
||||||
class PageStruct{
|
class PageStruct{
|
||||||
public:
|
public:
|
||||||
Page *page=NULL;
|
Page *page = nullptr;
|
||||||
PageData parameters;
|
PageData parameters;
|
||||||
PageDescription *description=NULL;
|
PageDescription *description = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Standard format functions without overhead
|
// Standard format functions without overhead
|
||||||
@@ -173,10 +195,11 @@ String formatLongitude(double lon);
|
|||||||
|
|
||||||
// Structure for formatted boat values
|
// Structure for formatted boat values
|
||||||
typedef struct{
|
typedef struct{
|
||||||
double value;
|
double value; // SI value of boat data value
|
||||||
String svalue;
|
double cvalue; // value converted to target unit
|
||||||
String unit;
|
String svalue; // value converted to target unit and formatted
|
||||||
} FormatedData;
|
String unit; // target value unit
|
||||||
|
} FormattedData;
|
||||||
|
|
||||||
// Formatter for boat values
|
// Formatter for boat values
|
||||||
FormatedData formatValue(GwApi::BoatValue *value, CommonData &commondata);
|
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6
lib/obp60task/debugging.txt
Normal file
6
lib/obp60task/debugging.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Debugging tool
|
||||||
|
##############
|
||||||
|
|
||||||
|
log.txt = text file with error messages from terminal console
|
||||||
|
|
||||||
|
tools/decoder.py -p ESP32S3 -t ~/.platformio/packages/toolchain-xtensa-esp32s3/ -e .pio/build/obp60_s3/firmware.elf log.txt
|
||||||
202
lib/obp60task/fonts/IBM8x8px.h
Normal file
202
lib/obp60task/fonts/IBM8x8px.h
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
const uint8_t IBM8x8pxBitmaps[] PROGMEM = {
|
||||||
|
0x00, /* 0x20 space */
|
||||||
|
0x6F, 0xF6, 0x60, 0x60, /* 0x21 exclam */
|
||||||
|
0xDE, 0xF6, /* 0x22 quotedbl */
|
||||||
|
0x6C, 0xDB, 0xFB, 0x6F, 0xED, 0x9B, 0x00, /* 0x23 numbersign */
|
||||||
|
0x31, 0xFC, 0x1E, 0x0F, 0xE3, 0x00, /* 0x24 dollar */
|
||||||
|
0xC7, 0x98, 0x61, 0x86, 0x78, 0xC0, /* 0x25 percent */
|
||||||
|
0x38, 0xD8, 0xE3, 0xBD, 0xD9, 0x9D, 0x80, /* 0x26 ampersand */
|
||||||
|
0x6F, 0x00, /* 0x27 quotesingle */
|
||||||
|
0x36, 0xCC, 0xC6, 0x30, /* 0x28 parenleft */
|
||||||
|
0xC6, 0x33, 0x36, 0xC0, /* 0x29 parenright */
|
||||||
|
0x66, 0x3C, 0xFF, 0x3C, 0x66, /* 0x2A asterisk */
|
||||||
|
0x30, 0xCF, 0xCC, 0x30, /* 0x2B plus */
|
||||||
|
0x6F, 0x00, /* 0x2C comma */
|
||||||
|
0xFC, /* 0x2D hyphen */
|
||||||
|
0xF0, /* 0x2E period */
|
||||||
|
0x06, 0x18, 0x61, 0x86, 0x18, 0x20, 0x00, /* 0x2F slash */
|
||||||
|
0x7D, 0x8F, 0x3E, 0xFF, 0x7C, 0xDF, 0x00, /* 0x30 zero */
|
||||||
|
0x31, 0xC3, 0x0C, 0x30, 0xCF, 0xC0, /* 0x31 one */
|
||||||
|
0x7B, 0x30, 0xCE, 0x63, 0x1F, 0xC0, /* 0x32 two */
|
||||||
|
0x7B, 0x30, 0xCE, 0x0F, 0x37, 0x80, /* 0x33 three */
|
||||||
|
0x1C, 0x79, 0xB6, 0x6F, 0xE1, 0x87, 0x80, /* 0x34 four */
|
||||||
|
0xFF, 0x0F, 0x83, 0x0F, 0x37, 0x80, /* 0x35 five */
|
||||||
|
0x39, 0x8C, 0x3E, 0xCF, 0x37, 0x80, /* 0x36 six */
|
||||||
|
0xFF, 0x30, 0xC6, 0x30, 0xC3, 0x00, /* 0x37 seven */
|
||||||
|
0x7B, 0x3C, 0xDE, 0xCF, 0x37, 0x80, /* 0x38 eight */
|
||||||
|
0x7B, 0x3C, 0xDF, 0x0C, 0x67, 0x00, /* 0x39 nine */
|
||||||
|
0xF0, 0xF0, /* 0x3A colon */
|
||||||
|
0x6C, 0x37, 0x80, /* 0x3B semicolon */
|
||||||
|
0x19, 0x99, 0x86, 0x18, 0x60, /* 0x3C less */
|
||||||
|
0xFC, 0x00, 0x3F, /* 0x3D equal */
|
||||||
|
0xC3, 0x0C, 0x33, 0x33, 0x00, /* 0x3E greater */
|
||||||
|
0x7B, 0x30, 0xC6, 0x30, 0x03, 0x00, /* 0x3F question */
|
||||||
|
0x7D, 0x8F, 0x7E, 0xFD, 0xF8, 0x1E, 0x00, /* 0x40 at */
|
||||||
|
0x31, 0xEC, 0xF3, 0xFF, 0x3C, 0xC0, /* 0x41 A */
|
||||||
|
0xFC, 0xCD, 0x9B, 0xE6, 0x6C, 0xFF, 0x00, /* 0x42 B */
|
||||||
|
0x3C, 0xCF, 0x06, 0x0C, 0x0C, 0xCF, 0x00, /* 0x43 C */
|
||||||
|
0xF8, 0xD9, 0x9B, 0x36, 0x6D, 0xBE, 0x00, /* 0x44 D */
|
||||||
|
0xFE, 0xC5, 0xA3, 0xC6, 0x8C, 0x7F, 0x80, /* 0x45 E */
|
||||||
|
0xFE, 0xC5, 0xA3, 0xC6, 0x8C, 0x3C, 0x00, /* 0x46 F */
|
||||||
|
0x3C, 0xCF, 0x06, 0x0C, 0xEC, 0xCF, 0x80, /* 0x47 G */
|
||||||
|
0xCF, 0x3C, 0xFF, 0xCF, 0x3C, 0xC0, /* 0x48 H */
|
||||||
|
0xF6, 0x66, 0x66, 0xF0, /* 0x49 I */
|
||||||
|
0x1E, 0x18, 0x30, 0x6C, 0xD9, 0x9E, 0x00, /* 0x4A J */
|
||||||
|
0xE6, 0xCD, 0xB3, 0xC6, 0xCC, 0xF9, 0x80, /* 0x4B K */
|
||||||
|
0xF0, 0xC1, 0x83, 0x06, 0x2C, 0xFF, 0x80, /* 0x4C L */
|
||||||
|
0xC7, 0xDF, 0xFF, 0xFD, 0x78, 0xF1, 0x80, /* 0x4D M */
|
||||||
|
0xC7, 0xCF, 0xDE, 0xFC, 0xF8, 0xF1, 0x80, /* 0x4E N */
|
||||||
|
0x38, 0xDB, 0x1E, 0x3C, 0x6D, 0x8E, 0x00, /* 0x4F O */
|
||||||
|
0xFC, 0xCD, 0x9B, 0xE6, 0x0C, 0x3C, 0x00, /* 0x50 P */
|
||||||
|
0x7B, 0x3C, 0xF3, 0xDD, 0xE1, 0xC0, /* 0x51 Q */
|
||||||
|
0xFC, 0xCD, 0x9B, 0xE6, 0xCC, 0xF9, 0x80, /* 0x52 R */
|
||||||
|
0x7B, 0x3E, 0x1C, 0x1F, 0x37, 0x80, /* 0x53 S */
|
||||||
|
0xFE, 0xD3, 0x0C, 0x30, 0xC7, 0x80, /* 0x54 T */
|
||||||
|
0xCF, 0x3C, 0xF3, 0xCF, 0x3F, 0xC0, /* 0x55 U */
|
||||||
|
0xCF, 0x3C, 0xF3, 0xCD, 0xE3, 0x00, /* 0x56 V */
|
||||||
|
0xC7, 0x8F, 0x1E, 0xBF, 0xFD, 0xF1, 0x80, /* 0x57 W */
|
||||||
|
0xC7, 0x8D, 0xB1, 0xC3, 0x8D, 0xB1, 0x80, /* 0x58 X */
|
||||||
|
0xCF, 0x3C, 0xDE, 0x30, 0xC7, 0x80, /* 0x59 Y */
|
||||||
|
0xFF, 0x8E, 0x30, 0xC3, 0x2C, 0xFF, 0x80, /* 0x5A Z */
|
||||||
|
0xFC, 0xCC, 0xCC, 0xF0, /* 0x5B bracketleft */
|
||||||
|
0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x80, /* 0x5C backslash */
|
||||||
|
0xF3, 0x33, 0x33, 0xF0, /* 0x5D bracketright */
|
||||||
|
0x10, 0x71, 0xB6, 0x30, /* 0x5E asciicircum */
|
||||||
|
0xFF, /* 0x5F underscore */
|
||||||
|
0xD9, 0x80, /* 0x60 grave */
|
||||||
|
0x78, 0x19, 0xF6, 0x67, 0x60, /* 0x61 a */
|
||||||
|
0xE0, 0xC1, 0x83, 0xE6, 0x6C, 0xF7, 0x00, /* 0x62 b */
|
||||||
|
0x7B, 0x3C, 0x33, 0x78, /* 0x63 c */
|
||||||
|
0x1C, 0x18, 0x33, 0xEC, 0xD9, 0x9D, 0x80, /* 0x64 d */
|
||||||
|
0x7B, 0x3F, 0xF0, 0x78, /* 0x65 e */
|
||||||
|
0x39, 0xB6, 0x3C, 0x61, 0x8F, 0x00, /* 0x66 f */
|
||||||
|
0x77, 0x9B, 0x33, 0xE0, 0xDF, 0x00, /* 0x67 g */
|
||||||
|
0xE0, 0xC1, 0xB3, 0xB6, 0x6C, 0xF9, 0x80, /* 0x68 h */
|
||||||
|
0x60, 0xE6, 0x66, 0xF0, /* 0x69 i */
|
||||||
|
0x0C, 0x00, 0xC3, 0x0F, 0x3C, 0xDE, /* 0x6A j */
|
||||||
|
0xE0, 0xC1, 0x9B, 0x67, 0x8D, 0xB9, 0x80, /* 0x6B k */
|
||||||
|
0xE6, 0x66, 0x66, 0xF0, /* 0x6C l */
|
||||||
|
0xCD, 0xFF, 0xFE, 0xBC, 0x60, /* 0x6D m */
|
||||||
|
0xFB, 0x3C, 0xF3, 0xCC, /* 0x6E n */
|
||||||
|
0x7B, 0x3C, 0xF3, 0x78, /* 0x6F o */
|
||||||
|
0xDC, 0xCD, 0x9B, 0xE6, 0x1E, 0x00, /* 0x70 p */
|
||||||
|
0x77, 0x9B, 0x33, 0xE0, 0xC3, 0xC0, /* 0x71 q */
|
||||||
|
0xDC, 0xED, 0x9B, 0x0F, 0x00, /* 0x72 r */
|
||||||
|
0x7F, 0x07, 0x83, 0xF8, /* 0x73 s */
|
||||||
|
0x23, 0x3E, 0xC6, 0x34, 0xC0, /* 0x74 t */
|
||||||
|
0xCD, 0x9B, 0x36, 0x67, 0x60, /* 0x75 u */
|
||||||
|
0xCF, 0x3C, 0xDE, 0x30, /* 0x76 v */
|
||||||
|
0xC7, 0xAF, 0xFF, 0xF6, 0xC0, /* 0x77 w */
|
||||||
|
0xC6, 0xD8, 0xE3, 0x6C, 0x60, /* 0x78 x */
|
||||||
|
0xCF, 0x3C, 0xDF, 0x0F, 0xE0, /* 0x79 y */
|
||||||
|
0xFE, 0x63, 0x19, 0xFC, /* 0x7A z */
|
||||||
|
0x1C, 0xC3, 0x38, 0x30, 0xC1, 0xC0, /* 0x7B braceleft */
|
||||||
|
0xFC, 0xFC, /* 0x7C bar */
|
||||||
|
0xE0, 0xC3, 0x07, 0x30, 0xCE, 0x00, /* 0x7D braceright */
|
||||||
|
0x77, 0xB8, /* 0x7E asciitilde */
|
||||||
|
0x10, 0x71, 0xB6, 0x3C, 0x7F, 0xC0 /* 0x7F uni007F */
|
||||||
|
};
|
||||||
|
|
||||||
|
const GFXglyph IBM8x8pxGlyphs[] PROGMEM = {
|
||||||
|
{ 0, 1, 1, 2, 0, -1 }, /* 0x20 space */
|
||||||
|
{ 1, 4, 7, 5, 0, -7 }, /* 0x21 exclam */
|
||||||
|
{ 5, 5, 3, 6, 0, -7 }, /* 0x22 quotedbl */
|
||||||
|
{ 7, 7, 7, 8, 0, -7 }, /* 0x23 numbersign */
|
||||||
|
{ 14, 6, 7, 7, 0, -7 }, /* 0x24 dollar */
|
||||||
|
{ 20, 7, 6, 8, 0, -6 }, /* 0x25 percent */
|
||||||
|
{ 26, 7, 7, 8, 0, -7 }, /* 0x26 ampersand */
|
||||||
|
{ 33, 3, 3, 4, 0, -7 }, /* 0x27 quotesingle */
|
||||||
|
{ 35, 4, 7, 5, 0, -7 }, /* 0x28 parenleft */
|
||||||
|
{ 39, 4, 7, 5, 0, -7 }, /* 0x29 parenright */
|
||||||
|
{ 43, 8, 5, 9, 0, -6 }, /* 0x2A asterisk */
|
||||||
|
{ 48, 6, 5, 7, 0, -6 }, /* 0x2B plus */
|
||||||
|
{ 52, 3, 3, 4, 0, -2 }, /* 0x2C comma */
|
||||||
|
{ 54, 6, 1, 7, 0, -4 }, /* 0x2D hyphen */
|
||||||
|
{ 55, 2, 2, 3, 0, -2 }, /* 0x2E period */
|
||||||
|
{ 56, 7, 7, 8, 0, -7 }, /* 0x2F slash */
|
||||||
|
{ 63, 7, 7, 8, 0, -7 }, /* 0x30 zero */
|
||||||
|
{ 70, 6, 7, 7, 0, -7 }, /* 0x31 one */
|
||||||
|
{ 76, 6, 7, 7, 0, -7 }, /* 0x32 two */
|
||||||
|
{ 82, 6, 7, 7, 0, -7 }, /* 0x33 three */
|
||||||
|
{ 88, 7, 7, 8, 0, -7 }, /* 0x34 four */
|
||||||
|
{ 95, 6, 7, 7, 0, -7 }, /* 0x35 five */
|
||||||
|
{ 101, 6, 7, 7, 0, -7 }, /* 0x36 six */
|
||||||
|
{ 107, 6, 7, 7, 0, -7 }, /* 0x37 seven */
|
||||||
|
{ 113, 6, 7, 7, 0, -7 }, /* 0x38 eight */
|
||||||
|
{ 119, 6, 7, 7, 0, -7 }, /* 0x39 nine */
|
||||||
|
{ 125, 2, 6, 3, 0, -6 }, /* 0x3A colon */
|
||||||
|
{ 127, 3, 6, 4, 0, -6 }, /* 0x3B semicolon */
|
||||||
|
{ 130, 5, 7, 6, 0, -7 }, /* 0x3C less */
|
||||||
|
{ 135, 6, 4, 7, 0, -5 }, /* 0x3D equal */
|
||||||
|
{ 138, 5, 7, 6, 0, -7 }, /* 0x3E greater */
|
||||||
|
{ 143, 6, 7, 7, 0, -7 }, /* 0x3F question */
|
||||||
|
{ 149, 7, 7, 8, 0, -7 }, /* 0x40 at */
|
||||||
|
{ 156, 6, 7, 7, 0, -7 }, /* 0x41 A */
|
||||||
|
{ 162, 7, 7, 8, 0, -7 }, /* 0x42 B */
|
||||||
|
{ 169, 7, 7, 8, 0, -7 }, /* 0x43 C */
|
||||||
|
{ 176, 7, 7, 8, 0, -7 }, /* 0x44 D */
|
||||||
|
{ 183, 7, 7, 8, 0, -7 }, /* 0x45 E */
|
||||||
|
{ 190, 7, 7, 8, 0, -7 }, /* 0x46 F */
|
||||||
|
{ 197, 7, 7, 8, 0, -7 }, /* 0x47 G */
|
||||||
|
{ 204, 6, 7, 7, 0, -7 }, /* 0x48 H */
|
||||||
|
{ 210, 4, 7, 5, 0, -7 }, /* 0x49 I */
|
||||||
|
{ 214, 7, 7, 8, 0, -7 }, /* 0x4A J */
|
||||||
|
{ 221, 7, 7, 8, 0, -7 }, /* 0x4B K */
|
||||||
|
{ 228, 7, 7, 8, 0, -7 }, /* 0x4C L */
|
||||||
|
{ 235, 7, 7, 8, 0, -7 }, /* 0x4D M */
|
||||||
|
{ 242, 7, 7, 8, 0, -7 }, /* 0x4E N */
|
||||||
|
{ 249, 7, 7, 8, 0, -7 }, /* 0x4F O */
|
||||||
|
{ 256, 7, 7, 8, 0, -7 }, /* 0x50 P */
|
||||||
|
{ 263, 6, 7, 7, 0, -7 }, /* 0x51 Q */
|
||||||
|
{ 269, 7, 7, 8, 0, -7 }, /* 0x52 R */
|
||||||
|
{ 276, 6, 7, 7, 0, -7 }, /* 0x53 S */
|
||||||
|
{ 282, 6, 7, 7, 0, -7 }, /* 0x54 T */
|
||||||
|
{ 288, 6, 7, 7, 0, -7 }, /* 0x55 U */
|
||||||
|
{ 294, 6, 7, 7, 0, -7 }, /* 0x56 V */
|
||||||
|
{ 300, 7, 7, 8, 0, -7 }, /* 0x57 W */
|
||||||
|
{ 307, 7, 7, 8, 0, -7 }, /* 0x58 X */
|
||||||
|
{ 314, 6, 7, 7, 0, -7 }, /* 0x59 Y */
|
||||||
|
{ 320, 7, 7, 8, 0, -7 }, /* 0x5A Z */
|
||||||
|
{ 327, 4, 7, 5, 0, -7 }, /* 0x5B bracketleft */
|
||||||
|
{ 331, 7, 7, 8, 0, -7 }, /* 0x5C backslash */
|
||||||
|
{ 338, 4, 7, 5, 0, -7 }, /* 0x5D bracketright */
|
||||||
|
{ 342, 7, 4, 8, 0, -7 }, /* 0x5E asciicircum */
|
||||||
|
{ 346, 8, 1, 9, 0, 0 }, /* 0x5F underscore */
|
||||||
|
{ 347, 3, 3, 4, 0, -7 }, /* 0x60 grave */
|
||||||
|
{ 349, 7, 5, 8, 0, -5 }, /* 0x61 a */
|
||||||
|
{ 354, 7, 7, 8, 0, -7 }, /* 0x62 b */
|
||||||
|
{ 361, 6, 5, 7, 0, -5 }, /* 0x63 c */
|
||||||
|
{ 365, 7, 7, 8, 0, -7 }, /* 0x64 d */
|
||||||
|
{ 372, 6, 5, 7, 0, -5 }, /* 0x65 e */
|
||||||
|
{ 376, 6, 7, 7, 0, -7 }, /* 0x66 f */
|
||||||
|
{ 382, 7, 6, 8, 0, -5 }, /* 0x67 g */
|
||||||
|
{ 388, 7, 7, 8, 0, -7 }, /* 0x68 h */
|
||||||
|
{ 395, 4, 7, 5, 0, -7 }, /* 0x69 i */
|
||||||
|
{ 399, 6, 8, 7, 0, -7 }, /* 0x6A j */
|
||||||
|
{ 405, 7, 7, 8, 0, -7 }, /* 0x6B k */
|
||||||
|
{ 412, 4, 7, 5, 0, -7 }, /* 0x6C l */
|
||||||
|
{ 416, 7, 5, 8, 0, -5 }, /* 0x6D m */
|
||||||
|
{ 421, 6, 5, 7, 0, -5 }, /* 0x6E n */
|
||||||
|
{ 425, 6, 5, 7, 0, -5 }, /* 0x6F o */
|
||||||
|
{ 429, 7, 6, 8, 0, -5 }, /* 0x70 p */
|
||||||
|
{ 435, 7, 6, 8, 0, -5 }, /* 0x71 q */
|
||||||
|
{ 441, 7, 5, 8, 0, -5 }, /* 0x72 r */
|
||||||
|
{ 446, 6, 5, 7, 0, -5 }, /* 0x73 s */
|
||||||
|
{ 450, 5, 7, 6, 0, -7 }, /* 0x74 t */
|
||||||
|
{ 455, 7, 5, 8, 0, -5 }, /* 0x75 u */
|
||||||
|
{ 460, 6, 5, 7, 0, -5 }, /* 0x76 v */
|
||||||
|
{ 464, 7, 5, 8, 0, -5 }, /* 0x77 w */
|
||||||
|
{ 469, 7, 5, 8, 0, -5 }, /* 0x78 x */
|
||||||
|
{ 474, 6, 6, 7, 0, -5 }, /* 0x79 y */
|
||||||
|
{ 479, 6, 5, 7, 0, -5 }, /* 0x7A z */
|
||||||
|
{ 483, 6, 7, 7, 0, -7 }, /* 0x7B braceleft */
|
||||||
|
{ 489, 2, 7, 3, 0, -7 }, /* 0x7C bar */
|
||||||
|
{ 491, 6, 7, 7, 0, -7 }, /* 0x7D braceright */
|
||||||
|
{ 497, 7, 2, 8, 0, -7 }, /* 0x7E asciitilde */
|
||||||
|
{ 499, 7, 6, 8, 0, -6 } /* 0x7F uni007F */
|
||||||
|
};
|
||||||
|
|
||||||
|
const GFXfont IBM8x8px PROGMEM = {
|
||||||
|
(uint8_t *)IBM8x8pxBitmaps,
|
||||||
|
(GFXglyph *)IBM8x8pxGlyphs,
|
||||||
|
0x20, 0x7F, 8 };
|
||||||
@@ -1,131 +1,206 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# A tool to generate that part of config.json that deals with pages and fields.
|
|
||||||
#
|
|
||||||
#Usage: 1. modify this script (e.g.add a page, change number of fields, etc.)
|
|
||||||
# 2. Delete all lines from config.json from the curly backet before "name": "page1type" to o the end of the file (as of today, delete from line 917 to the end of the File)
|
|
||||||
# 3. run ./gen_set.py >> config.json
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
A tool to generate that part of config.json that deals with pages and fields.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
1. Delete all lines from config.json from the curly backet before
|
||||||
|
"name": "page1type" to the end of the file
|
||||||
|
|
||||||
|
2. run ./gen_set.py -d obp60 -p 10 >> config.json
|
||||||
|
|
||||||
|
TODO Better handling of default pages
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import getopt
|
||||||
|
import re
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# List of all pages and the number of parameters they expect.
|
__version__ = "0.3"
|
||||||
no_of_fields_per_page = {
|
|
||||||
"Wind": 0,
|
|
||||||
"XTETrack": 0,
|
|
||||||
"Battery2": 0,
|
|
||||||
"Battery": 0,
|
|
||||||
"BME280": 0,
|
|
||||||
"Clock": 0,
|
|
||||||
"Compass" : 0,
|
|
||||||
"DST810": 0,
|
|
||||||
"Fluid": 1,
|
|
||||||
"FourValues2": 4,
|
|
||||||
"FourValues": 4,
|
|
||||||
"Generator": 0,
|
|
||||||
"KeelPosition": 0,
|
|
||||||
"OneValue": 1,
|
|
||||||
"RollPitch": 2,
|
|
||||||
"RudderPosition": 0,
|
|
||||||
"SixValues" : 6,
|
|
||||||
"Solar": 0,
|
|
||||||
"ThreeValues": 3,
|
|
||||||
"TwoValues": 2,
|
|
||||||
"Voltage": 0,
|
|
||||||
"WhitePage": 0,
|
|
||||||
"WindRose": 0,
|
|
||||||
"WindRoseFlex": 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
# No changes needed beyond this point
|
def detect_pages(filename):
|
||||||
# max number of pages supported by OBP60
|
# returns a dictionary with page name and the number of gui fields
|
||||||
no_of_pages = 10
|
pagefiles = []
|
||||||
# Default selection for each page
|
with open(filename, 'r') as fh:
|
||||||
default_pages = [
|
pattern = r'extern PageDescription\s*register(Page[^;\s]*)'
|
||||||
"Voltage",
|
for line in fh:
|
||||||
"WindRose",
|
if "extern PageDescription" in line:
|
||||||
"OneValue",
|
match = re.search(pattern, line)
|
||||||
"TwoValues",
|
if match:
|
||||||
"ThreeValues",
|
pagefiles.append(match.group(1))
|
||||||
"FourValues",
|
try:
|
||||||
"FourValues2",
|
pagefiles.remove('PageSystem')
|
||||||
"Clock",
|
except ValueError:
|
||||||
"RollPitch",
|
pass
|
||||||
"Battery2",
|
pagedata = {}
|
||||||
]
|
for pf in pagefiles:
|
||||||
numbers = [
|
filename = pf + ".cpp"
|
||||||
"one",
|
with open(filename, 'r') as fh:
|
||||||
"two",
|
content = fh.read()
|
||||||
"three",
|
pattern = r'PageDescription\s*?register' + pf + r'\s*\(\s*"([^"]+)".*?\n\s*(\d+)'
|
||||||
"four",
|
match = re.search(pattern, content, re.DOTALL)
|
||||||
"five",
|
if match:
|
||||||
"six",
|
pagedata[match.group(1)] = int(match.group(2))
|
||||||
"seven",
|
return pagedata
|
||||||
"eight",
|
|
||||||
"nine",
|
|
||||||
"ten",
|
|
||||||
]
|
|
||||||
pages = sorted(no_of_fields_per_page.keys())
|
|
||||||
max_no_of_fields_per_page = max(no_of_fields_per_page.values())
|
|
||||||
|
|
||||||
output = []
|
def get_default_page(pageno):
|
||||||
|
# Default selection for each page
|
||||||
|
default_pages = (
|
||||||
|
"Voltage",
|
||||||
|
"WindRose",
|
||||||
|
"OneValue",
|
||||||
|
"TwoValues",
|
||||||
|
"ThreeValues",
|
||||||
|
"FourValues",
|
||||||
|
"FourValues2",
|
||||||
|
"Clock",
|
||||||
|
"RollPitch",
|
||||||
|
"Battery2"
|
||||||
|
)
|
||||||
|
if pageno > len(default_pages):
|
||||||
|
return "OneValue"
|
||||||
|
return default_pages[pageno - 1]
|
||||||
|
|
||||||
for page_no in range(1, no_of_pages + 1):
|
def number_to_text(number):
|
||||||
page_data = {
|
if number < 0 or number > 99:
|
||||||
"name": f"page{page_no}type",
|
raise ValueError("Only numbers from 0 to 99 are allowed.")
|
||||||
"label": "Type",
|
numbers = ("zero", "one", "two", "three", "four", "five", "six", "seven",
|
||||||
"type": "list",
|
"eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
|
||||||
"default": default_pages[page_no - 1],
|
"fifteen", "sixteen", "seventeen", "eighteen", "nineteen")
|
||||||
"description": f"Type of page for page {page_no}",
|
tens = ("", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy",
|
||||||
"list": pages,
|
"eighty", "ninety")
|
||||||
"category": f"OBP60 Page {page_no}",
|
if number < 20:
|
||||||
"capabilities": {"obp60": "true"},
|
return numbers[number]
|
||||||
"condition": [{"visiblePages": vp} for vp in range(page_no, no_of_pages + 1)],
|
else:
|
||||||
#"fields": [],
|
q, r = divmod(number, 10)
|
||||||
}
|
return tens[q] + numbers[r]
|
||||||
output.append(page_data)
|
|
||||||
|
|
||||||
for field_no in range(1, max_no_of_fields_per_page + 1):
|
def create_json(device, no_of_pages, pagedata):
|
||||||
field_data = {
|
|
||||||
"name": f"page{page_no}value{field_no}",
|
pages = sorted(pagedata.keys())
|
||||||
"label": f"Field {field_no}",
|
max_no_of_fields_per_page = max(pagedata.values())
|
||||||
"type": "boatData",
|
|
||||||
"default": "",
|
output = []
|
||||||
"description": f"The display for field {numbers[field_no - 1]}",
|
|
||||||
"category": f"OBP60 Page {page_no}",
|
for page_no in range(1, no_of_pages + 1):
|
||||||
"capabilities": {"obp60": "true"},
|
|
||||||
"condition": [
|
category = f"{device.upper()} Page {page_no}"
|
||||||
{f"page{page_no}type": page}
|
capabilities = {device.lower(): "true"}
|
||||||
for page in pages
|
visiblepages = [str(vp) for vp in range(page_no, no_of_pages + 1)]
|
||||||
if no_of_fields_per_page[page] >= field_no
|
|
||||||
|
page_data = {
|
||||||
|
"name": f"page{page_no}type",
|
||||||
|
"label": "Type",
|
||||||
|
"type": "list",
|
||||||
|
"default": get_default_page(page_no),
|
||||||
|
"description": f"Type of page for page {page_no}",
|
||||||
|
"list": pages,
|
||||||
|
"category": category,
|
||||||
|
"capabilities": {device.lower(): "true"},
|
||||||
|
"condition": {
|
||||||
|
"visiblePages": visiblepages
|
||||||
|
},
|
||||||
|
#"fields": [],
|
||||||
|
}
|
||||||
|
output.append(page_data)
|
||||||
|
|
||||||
|
for field_no in range(1, max_no_of_fields_per_page + 1):
|
||||||
|
field_data = {
|
||||||
|
"name": f"page{page_no}value{field_no}",
|
||||||
|
"label": f"Field {field_no}",
|
||||||
|
"type": "boatData",
|
||||||
|
"default": "",
|
||||||
|
"description": "The display for field {}".format(number_to_text(field_no)),
|
||||||
|
"category": category,
|
||||||
|
"capabilities": capabilities,
|
||||||
|
"condition": {
|
||||||
|
f"page{page_no}type": [ p for p in pages if pagedata[p] >= field_no ]
|
||||||
|
,"visiblePages": visiblepages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.append(field_data)
|
||||||
|
|
||||||
|
fluid_data = {
|
||||||
|
"name": f"page{page_no}fluid",
|
||||||
|
"label": "Fluid type",
|
||||||
|
"type": "list",
|
||||||
|
"default": "0",
|
||||||
|
"list": [
|
||||||
|
{"l":"Fuel (0)","v":"0"},
|
||||||
|
{"l":"Water (1)","v":"1"},
|
||||||
|
{"l":"Gray Water (2)","v":"2"},
|
||||||
|
{"l":"Live Well (3)","v":"3"},
|
||||||
|
{"l":"Oil (4)","v":"4"},
|
||||||
|
{"l":"Black Water (5)","v":"5"},
|
||||||
|
{"l":"Fuel Gasoline (6)","v":"6"}
|
||||||
],
|
],
|
||||||
|
"description": "Fluid type in tank",
|
||||||
|
"category": category,
|
||||||
|
"capabilities": capabilities,
|
||||||
|
"condition": {
|
||||||
|
f"page{page_no}type": "Fluid",
|
||||||
|
"visiblePages": visiblepages
|
||||||
|
}
|
||||||
}
|
}
|
||||||
output.append(field_data)
|
output.append(fluid_data)
|
||||||
|
|
||||||
fluid_data ={
|
if device.upper() == 'OBP40':
|
||||||
"name": f"page{page_no}fluid",
|
windsource = {
|
||||||
"label": "Fluid type",
|
"name": f"page{page_no}wndsrc",
|
||||||
"type": "list",
|
"label": "Wind source",
|
||||||
"default": "0",
|
"type": "list",
|
||||||
"list": [
|
"default": "True wind",
|
||||||
{"l":"Fuel (0)","v":"0"},
|
"description": f"Wind source for page {page_no}: [true|apparent]",
|
||||||
{"l":"Water (1)","v":"1"},
|
"list": [
|
||||||
{"l":"Gray Water (2)","v":"2"},
|
"True wind",
|
||||||
{"l":"Live Well (3)","v":"3"},
|
"Apparent wind"
|
||||||
{"l":"Oil (4)","v":"4"},
|
],
|
||||||
{"l":"Black Water (5)","v":"5"},
|
"category": category,
|
||||||
{"l":"Fuel Gasoline (6)","v":"6"}
|
"capabilities": capabilities,
|
||||||
],
|
"condition": {
|
||||||
"description": "Fluid type in tank",
|
f"page{page_no}type": "WindPlot",
|
||||||
"category": f"OBP60 Page {page_no}",
|
"visiblePages": visiblepages
|
||||||
"capabilities": {
|
}
|
||||||
"obp60":"true"
|
}
|
||||||
},
|
output.append(windsource)
|
||||||
"condition":[{f"page{page_no}type":"Fluid"}]
|
|
||||||
}
|
|
||||||
output.append(fluid_data)
|
|
||||||
|
|
||||||
json_output = json.dumps(output, indent=4)
|
return json.dumps(output, indent=4)
|
||||||
# print omitting first and last line containing [ ] of JSON array
|
|
||||||
#print(json_output[1:-1])
|
def usage():
|
||||||
# print omitting first line containing [ of JSON array
|
print("{} v{}".format(os.path.basename(__file__), __version__))
|
||||||
print(json_output[1:])
|
print()
|
||||||
# print(",")
|
print("Command line options")
|
||||||
|
print(" -d --device device name to use e.g. obp60")
|
||||||
|
print(" -p --pages number of pages to create")
|
||||||
|
print(" -h show this help")
|
||||||
|
print()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
options, remainder = getopt.getopt(sys.argv[1:], 'd:p:', ['device=','--pages='])
|
||||||
|
except getopt.GetoptError as err:
|
||||||
|
print(err)
|
||||||
|
usage()
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
device = "obp60"
|
||||||
|
no_of_pages = 10
|
||||||
|
for opt, arg in options:
|
||||||
|
if opt in ('-d', '--device'):
|
||||||
|
device = arg
|
||||||
|
elif opt in ('-p', '--pages'):
|
||||||
|
no_of_pages = int(arg)
|
||||||
|
elif opt == '-h':
|
||||||
|
usage()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# automatic detect pages and number of fields from sourcecode
|
||||||
|
pagedata = detect_pages("obp60task.cpp")
|
||||||
|
|
||||||
|
json_output = create_json(device, no_of_pages, pagedata)
|
||||||
|
# print omitting first line containing [ of JSON array
|
||||||
|
print(json_output[1:])
|
||||||
|
|||||||
1253
lib/obp60task/images/OBP40_400x300.xbm
Normal file
1253
lib/obp60task/images/OBP40_400x300.xbm
Normal file
File diff suppressed because it is too large
Load Diff
1253
lib/obp60task/images/OBP60_400x300.xbm
Normal file
1253
lib/obp60task/images/OBP60_400x300.xbm
Normal file
File diff suppressed because it is too large
Load Diff
1253
lib/obp60task/images/OBP_400x300.xbm
Normal file
1253
lib/obp60task/images/OBP_400x300.xbm
Normal file
File diff suppressed because it is too large
Load Diff
14
lib/obp60task/index.js
Normal file
14
lib/obp60task/index.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Add a new register card in web configuration interface
|
||||||
|
// This is a Java Script!
|
||||||
|
(function(){
|
||||||
|
const api=window.esp32nmea2k;
|
||||||
|
if (! api) return;
|
||||||
|
const tabName="Screen";
|
||||||
|
api.registerListener((id, data) => {
|
||||||
|
// if (!data.testboard) return; //do nothing if we are not active
|
||||||
|
let page = api.addTabPage(tabName, "Screen");
|
||||||
|
api.addEl('button', '', page, 'Screenshot').addEventListener('click', function (ev) {
|
||||||
|
window.open('/api/user/OBP60Task/screenshot', 'screenshot');
|
||||||
|
})
|
||||||
|
}, api.EVENTS.init);
|
||||||
|
})();
|
||||||
@@ -13,17 +13,13 @@
|
|||||||
#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 "BoatDataCalibration.h" // Functions lib for data instance calibration
|
#include "BoatDataCalibration.h" // Functions lib for data instance calibration
|
||||||
|
#include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation
|
||||||
|
|
||||||
#ifdef BOARD_OBP40S3
|
#ifdef BOARD_OBP40S3
|
||||||
#include "driver/rtc_io.h" // Needs for weakup from deep sleep
|
#include "driver/rtc_io.h" // Needs for weakup from deep sleep
|
||||||
#include <FS.h> // SD-Card access
|
|
||||||
#include <SD.h>
|
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// True type character sets includes
|
|
||||||
// See OBP60ExtensionPort.cpp
|
|
||||||
|
|
||||||
// Pictures
|
// Pictures
|
||||||
//#include GxEPD_BitmapExamples // Example picture
|
//#include GxEPD_BitmapExamples // Example picture
|
||||||
#include "MFD_OBP60_400x300_sw.h" // MFD with logo
|
#include "MFD_OBP60_400x300_sw.h" // MFD with logo
|
||||||
@@ -32,7 +28,6 @@
|
|||||||
#include "OBP60QRWiFi.h" // Functions lib for WiFi QR code
|
#include "OBP60QRWiFi.h" // Functions lib for WiFi QR code
|
||||||
#include "OBPSensorTask.h" // Functions lib for sensor data
|
#include "OBPSensorTask.h" // Functions lib for sensor data
|
||||||
|
|
||||||
|
|
||||||
// Global vars
|
// Global vars
|
||||||
bool initComplete = false; // Initialization complete
|
bool initComplete = false; // Initialization complete
|
||||||
int taskRunCounter = 0; // Task couter for loop section
|
int taskRunCounter = 0; // Task couter for loop section
|
||||||
@@ -45,63 +40,23 @@ void OBP60Init(GwApi *api){
|
|||||||
GwConfigHandler *config = api->getConfig();
|
GwConfigHandler *config = api->getConfig();
|
||||||
|
|
||||||
// Set a new device name and hidden the original name in the main config
|
// Set a new device name and hidden the original name in the main config
|
||||||
String devicename = api->getConfig()->getConfigItem(api->getConfig()->deviceName,true)->asString();
|
String devicename = config->getConfigItem(config->deviceName, true)->asString();
|
||||||
api->getConfig()->setValue(GwConfigDefinitions::systemName, devicename, GwConfigInterface::ConfigType::HIDDEN);
|
config->setValue(GwConfigDefinitions::systemName, devicename, GwConfigInterface::ConfigType::HIDDEN);
|
||||||
|
|
||||||
api->getLogger()->logDebug(GwLog::LOG,"obp60init running");
|
logger->prefix = devicename + ":";
|
||||||
|
logger->logDebug(GwLog::LOG,"obp60init running");
|
||||||
|
|
||||||
// Check I2C devices
|
// Check I2C devices
|
||||||
|
|
||||||
|
// Init power
|
||||||
|
String powermode = config->getConfigItem(config->powerMode,true)->asString();
|
||||||
|
logger->logDebug(GwLog::DEBUG, "Power Mode is: %s", powermode.c_str());
|
||||||
|
powerInit(powermode);
|
||||||
|
|
||||||
// Init hardware
|
// Init hardware
|
||||||
hardwareInit(api);
|
hardwareInit(api);
|
||||||
|
|
||||||
// Init power rail 5.0V
|
#ifdef BOARD_OBP40S3
|
||||||
String powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString();
|
|
||||||
api->getLogger()->logDebug(GwLog::DEBUG,"Power Mode is: %s", powermode.c_str());
|
|
||||||
if(powermode == "Max Power" || powermode == "Only 5.0V"){
|
|
||||||
#ifdef HARDWARE_V21
|
|
||||||
setPortPin(OBP_POWER_50, true); // Power on 5.0V rail
|
|
||||||
#endif
|
|
||||||
#ifdef BOARD_OBP40S3
|
|
||||||
setPortPin(OBP_POWER_EPD, true);// Power on ePaper display
|
|
||||||
setPortPin(OBP_POWER_SD, true); // Power on SD card
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
#ifdef HARDWARE_V21
|
|
||||||
setPortPin(OBP_POWER_50, false); // Power off 5.0V rail
|
|
||||||
#endif
|
|
||||||
#ifdef BOARD_OBP40S3
|
|
||||||
setPortPin(OBP_POWER_EPD, false);// Power off ePaper display
|
|
||||||
setPortPin(OBP_POWER_SD, false); // Power off SD card
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef BOARD_OBP40S3
|
|
||||||
bool sdcard = config->getBool(config->useSDCard);
|
|
||||||
if (sdcard) {
|
|
||||||
SPIClass SD_SPI = SPIClass(HSPI);
|
|
||||||
SD_SPI.begin(SD_SPI_CLK, SD_SPI_MISO, SD_SPI_MOSI);
|
|
||||||
if (SD.begin(SD_SPI_CS, SD_SPI, 80000000)) {
|
|
||||||
String sdtype = "unknown";
|
|
||||||
uint8_t cardType = SD.cardType();
|
|
||||||
switch (cardType) {
|
|
||||||
case CARD_MMC:
|
|
||||||
sdtype = "MMC";
|
|
||||||
break;
|
|
||||||
case CARD_SD:
|
|
||||||
sdtype = "SDSC";
|
|
||||||
break;
|
|
||||||
case CARD_SDHC:
|
|
||||||
sdtype = "SDHC";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
|
|
||||||
LOG_DEBUG(GwLog::LOG,"SD card type %s of size %d MB detected", sdtype, cardSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deep sleep wakeup configuration
|
// Deep sleep wakeup configuration
|
||||||
esp_sleep_enable_ext0_wakeup(OBP_WAKEWUP_PIN, 0); // 1 = High, 0 = Low
|
esp_sleep_enable_ext0_wakeup(OBP_WAKEWUP_PIN, 0); // 1 = High, 0 = Low
|
||||||
rtc_gpio_pullup_en(OBP_WAKEWUP_PIN); // Activate pullup resistor
|
rtc_gpio_pullup_en(OBP_WAKEWUP_PIN); // Activate pullup resistor
|
||||||
@@ -110,7 +65,7 @@ void OBP60Init(GwApi *api){
|
|||||||
|
|
||||||
// Settings for e-paper display
|
// Settings for e-paper display
|
||||||
String fastrefresh = api->getConfig()->getConfigItem(api->getConfig()->fastRefresh,true)->asString();
|
String fastrefresh = api->getConfig()->getConfigItem(api->getConfig()->fastRefresh,true)->asString();
|
||||||
api->getLogger()->logDebug(GwLog::DEBUG,"Fast Refresh Mode is: %s", fastrefresh.c_str());
|
logger->logDebug(GwLog::DEBUG, "Fast Refresh Mode is: %s", fastrefresh.c_str());
|
||||||
#ifdef DISPLAY_GDEY042T81
|
#ifdef DISPLAY_GDEY042T81
|
||||||
if(fastrefresh == "true"){
|
if(fastrefresh == "true"){
|
||||||
static const bool useFastFullUpdate = true; // Enable fast full display update only for GDEY042T81
|
static const bool useFastFullUpdate = true; // Enable fast full display update only for GDEY042T81
|
||||||
@@ -129,11 +84,11 @@ void OBP60Init(GwApi *api){
|
|||||||
|
|
||||||
// Get CPU speed
|
// Get CPU speed
|
||||||
int freq = getCpuFrequencyMhz();
|
int freq = getCpuFrequencyMhz();
|
||||||
api->getLogger()->logDebug(GwLog::LOG,"CPU speed at boot: %i MHz", freq);
|
logger->logDebug(GwLog::LOG,"CPU speed at boot: %i MHz", freq);
|
||||||
|
|
||||||
// Settings for backlight
|
// Settings for backlight
|
||||||
String backlightMode = api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString();
|
String backlightMode = api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString();
|
||||||
api->getLogger()->logDebug(GwLog::DEBUG,"Backlight Mode is: %s", backlightMode.c_str());
|
logger->logDebug(GwLog::DEBUG,"Backlight Mode is: %s", backlightMode.c_str());
|
||||||
uint brightness = uint(api->getConfig()->getConfigItem(api->getConfig()->blBrightness,true)->asInt());
|
uint brightness = uint(api->getConfig()->getConfigItem(api->getConfig()->blBrightness,true)->asInt());
|
||||||
String backlightColor = api->getConfig()->getConfigItem(api->getConfig()->blColor,true)->asString();
|
String backlightColor = api->getConfig()->getConfigItem(api->getConfig()->blColor,true)->asString();
|
||||||
if(String(backlightMode) == "On"){
|
if(String(backlightMode) == "On"){
|
||||||
@@ -148,7 +103,7 @@ void OBP60Init(GwApi *api){
|
|||||||
|
|
||||||
// Settings flash LED mode
|
// Settings flash LED mode
|
||||||
String ledMode = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString();
|
String ledMode = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString();
|
||||||
api->getLogger()->logDebug(GwLog::DEBUG,"LED Mode is: %s", ledMode.c_str());
|
logger->logDebug(GwLog::DEBUG,"LED Mode is: %s", ledMode.c_str());
|
||||||
if(String(ledMode) == "Off"){
|
if(String(ledMode) == "Off"){
|
||||||
setBlinkingLED(false);
|
setBlinkingLED(false);
|
||||||
}
|
}
|
||||||
@@ -166,8 +121,8 @@ void OBP60Init(GwApi *api){
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
int page0=0;
|
int page0=0;
|
||||||
QueueHandle_t queue;
|
QueueHandle_t queue;
|
||||||
GwLog* logger = NULL;
|
GwLog* logger = nullptr;
|
||||||
// GwApi* api = NULL;
|
// GwApi* api = nullptr;
|
||||||
uint sensitivity = 100;
|
uint sensitivity = 100;
|
||||||
bool use_syspage = true;
|
bool use_syspage = true;
|
||||||
} MyData;
|
} MyData;
|
||||||
@@ -192,45 +147,37 @@ void keyboardTask(void *param){
|
|||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
class BoatValueList{
|
// Scorgan: moved class declaration to header file <obp60task.h> to make class available to other functions
|
||||||
public:
|
// --- Class BoatValueList --------------
|
||||||
static const int MAXVALUES=100;
|
bool BoatValueList::addValueToList(GwApi::BoatValue *v){
|
||||||
//we create a list containing all our BoatValues
|
for (int i=0;i<numValues;i++){
|
||||||
//this is the list we later use to let the api fill all the values
|
if (allBoatValues[i] == v){
|
||||||
//additionally we put the necessary values into the paga data - see below
|
//already in list...
|
||||||
GwApi::BoatValue *allBoatValues[MAXVALUES];
|
return true;
|
||||||
int numValues=0;
|
|
||||||
|
|
||||||
bool addValueToList(GwApi::BoatValue *v){
|
|
||||||
for (int i=0;i<numValues;i++){
|
|
||||||
if (allBoatValues[i] == v){
|
|
||||||
//already in list...
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (numValues >= MAXVALUES) return false;
|
|
||||||
allBoatValues[numValues]=v;
|
|
||||||
numValues++;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
//helper to ensure that each BoatValue is only queried once
|
if (numValues >= MAXVALUES) return false;
|
||||||
GwApi::BoatValue *findValueOrCreate(String name){
|
allBoatValues[numValues]=v;
|
||||||
for (int i=0;i<numValues;i++){
|
numValues++;
|
||||||
if (allBoatValues[i]->getName() == name) {
|
return true;
|
||||||
return allBoatValues[i];
|
}
|
||||||
}
|
//helper to ensure that each BoatValue is only queried once
|
||||||
|
GwApi::BoatValue *BoatValueList::findValueOrCreate(String name){
|
||||||
|
for (int i=0;i<numValues;i++){
|
||||||
|
if (allBoatValues[i]->getName() == name) {
|
||||||
|
return allBoatValues[i];
|
||||||
}
|
}
|
||||||
GwApi::BoatValue *rt=new GwApi::BoatValue(name);
|
|
||||||
addValueToList(rt);
|
|
||||||
return rt;
|
|
||||||
}
|
}
|
||||||
};
|
GwApi::BoatValue *rt=new GwApi::BoatValue(name);
|
||||||
|
addValueToList(rt);
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
// --- Class BoatValueList --------------
|
||||||
|
|
||||||
//we want to have a list that has all our page definitions
|
//we want to have a list that has all our page definitions
|
||||||
//this way each page can easily be added here
|
//this way each page can easily be added here
|
||||||
//needs some minor tricks for the safe static initialization
|
//needs some minor tricks for the safe static initialization
|
||||||
typedef std::vector<PageDescription*> Pages;
|
typedef std::vector<PageDescription*> Pages;
|
||||||
//the page list class
|
|
||||||
class PageList{
|
class PageList{
|
||||||
public:
|
public:
|
||||||
Pages pages;
|
Pages pages;
|
||||||
@@ -275,10 +222,12 @@ void registerAllPages(PageList &list){
|
|||||||
list.add(®isterPageFourValues2);
|
list.add(®isterPageFourValues2);
|
||||||
extern PageDescription registerPageWind;
|
extern PageDescription registerPageWind;
|
||||||
list.add(®isterPageWind);
|
list.add(®isterPageWind);
|
||||||
|
extern PageDescription registerPageWindPlot;
|
||||||
|
list.add(®isterPageWindPlot);
|
||||||
extern PageDescription registerPageWindRose;
|
extern PageDescription registerPageWindRose;
|
||||||
list.add(®isterPageWindRose);
|
list.add(®isterPageWindRose);
|
||||||
extern PageDescription registerPageWindRoseFlex;
|
extern PageDescription registerPageWindRoseFlex;
|
||||||
list.add(®isterPageWindRoseFlex); //
|
list.add(®isterPageWindRoseFlex);
|
||||||
extern PageDescription registerPageVoltage;
|
extern PageDescription registerPageVoltage;
|
||||||
list.add(®isterPageVoltage);
|
list.add(®isterPageVoltage);
|
||||||
extern PageDescription registerPageDST810;
|
extern PageDescription registerPageDST810;
|
||||||
@@ -309,67 +258,71 @@ void registerAllPages(PageList &list){
|
|||||||
list.add(®isterPageXTETrack);
|
list.add(®isterPageXTETrack);
|
||||||
extern PageDescription registerPageFluid;
|
extern PageDescription registerPageFluid;
|
||||||
list.add(®isterPageFluid);
|
list.add(®isterPageFluid);
|
||||||
|
extern PageDescription registerPageSkyView;
|
||||||
|
list.add(®isterPageSkyView);
|
||||||
|
extern PageDescription registerPageNavigation;
|
||||||
|
list.add(®isterPageNavigation);
|
||||||
|
extern PageDescription registerPageDigitalOut;
|
||||||
|
list.add(®isterPageDigitalOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Undervoltage detection for shutdown display
|
// Undervoltage detection for shutdown display
|
||||||
void underVoltageDetection(GwApi *api, CommonData &common){
|
void underVoltageError(CommonData &common) {
|
||||||
// Read settings
|
#if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
|
||||||
double voffset = (api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asString()).toFloat();
|
// Switch off all power lines
|
||||||
double vslope = (api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asString()).toFloat();
|
setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
|
||||||
|
setFlashLED(false); // Flash LED Off
|
||||||
|
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
|
||||||
|
// Shutdown EInk display
|
||||||
|
getdisplay().setFullWindow(); // Set full Refresh
|
||||||
|
//getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
|
getdisplay().fillScreen(common.bgcolor);// Clear screen
|
||||||
|
getdisplay().setTextColor(common.fgcolor);
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||||
|
getdisplay().setCursor(65, 150);
|
||||||
|
getdisplay().print("Undervoltage");
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(65, 175);
|
||||||
|
getdisplay().print("Charge battery and restart system");
|
||||||
|
getdisplay().nextPage(); // Partial update
|
||||||
|
getdisplay().powerOff(); // Display power off
|
||||||
|
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
|
||||||
|
setPortPin(OBP_POWER_SD, false); // Power off SD card
|
||||||
|
#else
|
||||||
|
// Switch off all power lines
|
||||||
|
setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
|
||||||
|
setFlashLED(false); // Flash LED Off
|
||||||
|
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
|
||||||
|
setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off
|
||||||
|
// Shutdown EInk display
|
||||||
|
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
|
getdisplay().fillScreen(common.bgcolor);// Clear screen
|
||||||
|
getdisplay().setTextColor(common.fgcolor);
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||||
|
getdisplay().setCursor(65, 150);
|
||||||
|
getdisplay().print("Undervoltage");
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(65, 175);
|
||||||
|
getdisplay().print("To wake up repower system");
|
||||||
|
getdisplay().nextPage(); // Partial update
|
||||||
|
getdisplay().powerOff(); // Display power off
|
||||||
|
#endif
|
||||||
|
while (true) {
|
||||||
|
esp_deep_sleep_start(); // Deep Sleep without wakeup. Wakeup only after power cycle (restart).
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 volatge 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;
|
||||||
#endif
|
#endif
|
||||||
double calVoltage = actVoltage * vslope + voffset; // Calibration
|
float calVoltage = actVoltage * vslope + voffset; // Calibration
|
||||||
if(calVoltage < minVoltage){
|
return (calVoltage < minVoltage);
|
||||||
#if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
|
|
||||||
// Switch off all power lines
|
|
||||||
setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
|
|
||||||
setFlashLED(false); // Flash LED Off
|
|
||||||
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
|
|
||||||
// Shutdown EInk display
|
|
||||||
getdisplay().setFullWindow(); // Set full Refresh
|
|
||||||
//getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
|
||||||
getdisplay().fillScreen(common.bgcolor);// Clear screen
|
|
||||||
getdisplay().setTextColor(common.fgcolor);
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
|
||||||
getdisplay().setCursor(65, 150);
|
|
||||||
getdisplay().print("Undervoltage");
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
|
||||||
getdisplay().setCursor(65, 175);
|
|
||||||
getdisplay().print("Charge battery and restart system");
|
|
||||||
getdisplay().nextPage(); // Partial update
|
|
||||||
getdisplay().powerOff(); // Display power off
|
|
||||||
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
|
|
||||||
setPortPin(OBP_POWER_SD, false); // Power off SD card
|
|
||||||
#else
|
|
||||||
// Switch off all power lines
|
|
||||||
setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
|
|
||||||
setFlashLED(false); // Flash LED Off
|
|
||||||
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
|
|
||||||
setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off
|
|
||||||
// Shutdown EInk display
|
|
||||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
|
||||||
getdisplay().fillScreen(common.bgcolor);// Clear screen
|
|
||||||
getdisplay().setTextColor(common.fgcolor);
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
|
||||||
getdisplay().setCursor(65, 150);
|
|
||||||
getdisplay().print("Undervoltage");
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
|
||||||
getdisplay().setCursor(65, 175);
|
|
||||||
getdisplay().print("To wake up repower system");
|
|
||||||
getdisplay().nextPage(); // Partial update
|
|
||||||
getdisplay().powerOff(); // Display power off
|
|
||||||
#endif
|
|
||||||
// Stop system
|
|
||||||
while(true){
|
|
||||||
esp_deep_sleep_start(); // Deep Sleep without weakup. Weakup only after power cycle (restart).
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OBP60 Task
|
// OBP60 Task
|
||||||
@@ -421,7 +374,7 @@ void OBP60Task(GwApi *api){
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef DISPLAY_GDEY042T81
|
#ifdef DISPLAY_GDEY042T81
|
||||||
getdisplay().init(115200, true, 2, false); // Use this 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
|
||||||
#else
|
#else
|
||||||
getdisplay().init(115200); // Init for normal displays
|
getdisplay().init(115200); // Init for normal displays
|
||||||
#endif
|
#endif
|
||||||
@@ -482,6 +435,8 @@ void OBP60Task(GwApi *api){
|
|||||||
int lastPage=pageNumber;
|
int lastPage=pageNumber;
|
||||||
|
|
||||||
BoatValueList boatValues; //all the boat values for the api query
|
BoatValueList boatValues; //all the boat values for the api query
|
||||||
|
HstryBuf hstryBufList(1920); // Create ring buffers for history storage of some boat data (1920 seconds = 32 minutes)
|
||||||
|
WindUtils trueWind(&boatValues); // Create helper object for true wind calculation
|
||||||
//commonData.distanceformat=config->getString(xxx);
|
//commonData.distanceformat=config->getString(xxx);
|
||||||
//add all necessary data to common data
|
//add all necessary data to common data
|
||||||
|
|
||||||
@@ -505,6 +460,7 @@ void OBP60Task(GwApi *api){
|
|||||||
pages[i].page=description->creator(commonData);
|
pages[i].page=description->creator(commonData);
|
||||||
pages[i].parameters.pageName=pageType;
|
pages[i].parameters.pageName=pageType;
|
||||||
pages[i].parameters.pageNumber = i + 1;
|
pages[i].parameters.pageNumber = i + 1;
|
||||||
|
pages[i].parameters.api = api;
|
||||||
LOG_DEBUG(GwLog::DEBUG,"found page %s for number %d",pageType.c_str(),i);
|
LOG_DEBUG(GwLog::DEBUG,"found page %s for number %d",pageType.c_str(),i);
|
||||||
//fill in all the user defined parameters
|
//fill in all the user defined parameters
|
||||||
for (int uid=0;uid<description->userParam;uid++){
|
for (int uid=0;uid<description->userParam;uid++){
|
||||||
@@ -523,6 +479,8 @@ void OBP60Task(GwApi *api){
|
|||||||
LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i);
|
LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i);
|
||||||
pages[i].parameters.values.push_back(value);
|
pages[i].parameters.values.push_back(value);
|
||||||
}
|
}
|
||||||
|
// Add boat history data to page parameters
|
||||||
|
pages[i].parameters.boatHstry = &hstryBufList;
|
||||||
}
|
}
|
||||||
// add out of band system page (always available)
|
// add out of band system page (always available)
|
||||||
Page *syspage = allPages.pages[0]->creator(commonData);
|
Page *syspage = allPages.pages[0]->creator(commonData);
|
||||||
@@ -530,6 +488,13 @@ void OBP60Task(GwApi *api){
|
|||||||
// Read all calibration data settings from config
|
// Read all calibration data settings from config
|
||||||
calibrationData.readConfig(config, logger);
|
calibrationData.readConfig(config, logger);
|
||||||
|
|
||||||
|
// Check user settings for true wind calculation
|
||||||
|
bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false);
|
||||||
|
bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false);
|
||||||
|
|
||||||
|
// Initialize history buffer for certain boat data
|
||||||
|
hstryBufList.init(&boatValues, logger);
|
||||||
|
|
||||||
// Display screenshot handler for HTTP request
|
// Display screenshot handler for HTTP request
|
||||||
// http://192.168.15.1/api/user/OBP60Task/screenshot
|
// http://192.168.15.1/api/user/OBP60Task/screenshot
|
||||||
api->registerRequestHandler("screenshot", [api, &pageNumber, pages](AsyncWebServerRequest *request) {
|
api->registerRequestHandler("screenshot", [api, &pageNumber, pages](AsyncWebServerRequest *request) {
|
||||||
@@ -563,7 +528,9 @@ void OBP60Task(GwApi *api){
|
|||||||
commonData.backlight.brightness = 2.55 * uint(config->getConfigItem(config->blBrightness,true)->asInt());
|
commonData.backlight.brightness = 2.55 * uint(config->getConfigItem(config->blBrightness,true)->asInt());
|
||||||
commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString();
|
commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString();
|
||||||
|
|
||||||
bool uvoltage = api->getConfig()->getConfigItem(api->getConfig()->underVoltage,true)->asBoolean();
|
bool uvoltage = config->getConfigItem(config->underVoltage, true)->asBoolean();
|
||||||
|
float voffset = (config->getConfigItem(config->vOffset,true)->asString()).toFloat();
|
||||||
|
float vslope = (config->getConfigItem(config->vSlope,true)->asString()).toFloat();
|
||||||
String cpuspeed = api->getConfig()->getConfigItem(api->getConfig()->cpuSpeed,true)->asString();
|
String cpuspeed = api->getConfig()->getConfigItem(api->getConfig()->cpuSpeed,true)->asString();
|
||||||
uint hdopAccuracy = uint(api->getConfig()->getConfigItem(api->getConfig()->hdopAccuracy,true)->asInt());
|
uint hdopAccuracy = uint(api->getConfig()->getConfigItem(api->getConfig()->hdopAccuracy,true)->asInt());
|
||||||
|
|
||||||
@@ -571,7 +538,7 @@ void OBP60Task(GwApi *api){
|
|||||||
double homelon = commonData.config->getString(commonData.config->homeLON).toDouble();
|
double homelon = commonData.config->getString(commonData.config->homeLON).toDouble();
|
||||||
bool homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0;
|
bool homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0;
|
||||||
if (homevalid) {
|
if (homevalid) {
|
||||||
LOG_DEBUG(GwLog::LOG, "Home location set to %f : %f", homelat, homelon);
|
LOG_DEBUG(GwLog::LOG, "Home location set to lat=%f, lon=%f", homelat, homelon);
|
||||||
} else {
|
} else {
|
||||||
LOG_DEBUG(GwLog::LOG, "No valid home location found");
|
LOG_DEBUG(GwLog::LOG, "No valid home location found");
|
||||||
}
|
}
|
||||||
@@ -605,14 +572,18 @@ void OBP60Task(GwApi *api){
|
|||||||
//####################################################################################
|
//####################################################################################
|
||||||
|
|
||||||
bool systemPage = false;
|
bool systemPage = false;
|
||||||
|
bool systemPageNew = false;
|
||||||
Page *currentPage;
|
Page *currentPage;
|
||||||
while (true){
|
while (true){
|
||||||
delay(100); // Delay 100ms (loop time)
|
delay(100); // Delay 100ms (loop time)
|
||||||
bool keypressed = false;
|
bool keypressed = false;
|
||||||
|
|
||||||
// Undervoltage detection
|
// Undervoltage detection
|
||||||
if(uvoltage == true){
|
if (uvoltage == true) {
|
||||||
underVoltageDetection(api, commonData);
|
if (underVoltageDetection(voffset, vslope)) {
|
||||||
|
LOG_DEBUG(GwLog::ERROR, "Undervoltage detected, shutting down!");
|
||||||
|
underVoltageError(commonData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set CPU speed after boot after 1min
|
// Set CPU speed after boot after 1min
|
||||||
@@ -657,6 +628,7 @@ void OBP60Task(GwApi *api){
|
|||||||
systemPage = true; // System page is out of band
|
systemPage = true; // System page is out of band
|
||||||
syspage->setupKeys();
|
syspage->setupKeys();
|
||||||
keyboardMessage = 0;
|
keyboardMessage = 0;
|
||||||
|
systemPageNew = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
currentPage = pages[pageNumber].page;
|
currentPage = pages[pageNumber].page;
|
||||||
@@ -711,7 +683,7 @@ void OBP60Task(GwApi *api){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// #9 or #10 Refresh display after a new page after 4s waiting time and if refresh is disabled
|
// #9 or #10 Refresh display after a new page after 4s waiting time and if refresh is disabled
|
||||||
if(refreshmode == true && (keyboardMessage == 9 || keyboardMessage == 10)){
|
if(refreshmode == true && (keyboardMessage == 9 || keyboardMessage == 10 || keyboardMessage == 4 || keyboardMessage == 3)){
|
||||||
starttime4 = millis();
|
starttime4 = millis();
|
||||||
starttime2 = millis(); // Reset the timer for full display update
|
starttime2 = millis(); // Reset the timer for full display update
|
||||||
delayedDisplayUpdate = true;
|
delayedDisplayUpdate = true;
|
||||||
@@ -742,17 +714,28 @@ void OBP60Task(GwApi *api){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Full display update afer a new selected page and 4s wait time
|
// Full display update afer a new selected page and 8s wait time
|
||||||
if(millis() > starttime4 + 4000 && delayedDisplayUpdate == true){
|
if(millis() > starttime4 + 8000 && delayedDisplayUpdate == true){
|
||||||
starttime1 = millis();
|
starttime1 = millis();
|
||||||
starttime2 = millis();
|
starttime2 = millis();
|
||||||
getdisplay().setFullWindow(); // Set full update
|
getdisplay().setFullWindow(); // Set full update
|
||||||
getdisplay().nextPage();
|
if(fastrefresh == "true"){
|
||||||
if(fastrefresh == "false"){
|
getdisplay().nextPage(); // Full update
|
||||||
|
}
|
||||||
|
else{
|
||||||
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
||||||
|
#ifdef DISPLAY_GDEY042T81
|
||||||
|
getdisplay().hibernate(); // Set display in hybenate mode
|
||||||
|
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
|
||||||
|
#else
|
||||||
|
getdisplay().init(115200); // Init for normal displays
|
||||||
|
#endif
|
||||||
|
getdisplay().firstPage(); // Full update
|
||||||
getdisplay().nextPage(); // Full update
|
getdisplay().nextPage(); // Full update
|
||||||
getdisplay().fillScreen(commonData.bgcolor); // Clear display
|
// getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
getdisplay().nextPage(); // Full update
|
// getdisplay().fillScreen(commonData.bgcolor); // Clear display
|
||||||
|
// getdisplay().nextPage(); // Partial update
|
||||||
|
// getdisplay().nextPage(); // Partial update
|
||||||
}
|
}
|
||||||
delayedDisplayUpdate = false;
|
delayedDisplayUpdate = false;
|
||||||
}
|
}
|
||||||
@@ -764,12 +747,23 @@ void OBP60Task(GwApi *api){
|
|||||||
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
|
getdisplay().setFullWindow(); // Set full update
|
||||||
getdisplay().nextPage();
|
if(fastrefresh == "true"){
|
||||||
if(fastrefresh == "false"){
|
getdisplay().nextPage(); // Full update
|
||||||
|
}
|
||||||
|
else{
|
||||||
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
||||||
|
#ifdef DISPLAY_GDEY042T81
|
||||||
|
getdisplay().hibernate(); // Set display in hybenate mode
|
||||||
|
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
|
||||||
|
#else
|
||||||
|
getdisplay().init(115200); // Init for normal displays
|
||||||
|
#endif
|
||||||
|
getdisplay().firstPage(); // Full update
|
||||||
getdisplay().nextPage(); // Full update
|
getdisplay().nextPage(); // Full update
|
||||||
getdisplay().fillScreen(commonData.bgcolor); // Clear display
|
// getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
getdisplay().nextPage(); // Full update
|
// getdisplay().fillScreen(commonData.bgcolor); // Clear display
|
||||||
|
// getdisplay().nextPage(); // Partial update
|
||||||
|
// getdisplay().nextPage(); // Partial update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -778,12 +772,23 @@ void OBP60Task(GwApi *api){
|
|||||||
starttime2 = millis();
|
starttime2 = millis();
|
||||||
LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh");
|
LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh");
|
||||||
getdisplay().setFullWindow(); // Set full update
|
getdisplay().setFullWindow(); // Set full update
|
||||||
getdisplay().nextPage();
|
if(fastrefresh == "true"){
|
||||||
if(fastrefresh == "false"){
|
getdisplay().nextPage(); // Full update
|
||||||
|
}
|
||||||
|
else{
|
||||||
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
||||||
|
#ifdef DISPLAY_GDEY042T81
|
||||||
|
getdisplay().hibernate(); // Set display in hybenate mode
|
||||||
|
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
|
||||||
|
#else
|
||||||
|
getdisplay().init(115200); // Init for normal displays
|
||||||
|
#endif
|
||||||
|
getdisplay().firstPage(); // Full update
|
||||||
getdisplay().nextPage(); // Full update
|
getdisplay().nextPage(); // Full update
|
||||||
getdisplay().fillScreen(commonData.bgcolor); // Clear display
|
// getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
getdisplay().nextPage(); // Full update
|
// getdisplay().fillScreen(commonData.bgcolor); // Clear display
|
||||||
|
// getdisplay().nextPage(); // Partial update
|
||||||
|
// getdisplay().nextPage(); // Partial update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -802,6 +807,12 @@ void OBP60Task(GwApi *api){
|
|||||||
api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues);
|
api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues);
|
||||||
api->getStatus(commonData.status);
|
api->getStatus(commonData.status);
|
||||||
|
|
||||||
|
if (calcTrueWnds) {
|
||||||
|
trueWind.addTrueWind(api, &boatValues, logger);
|
||||||
|
}
|
||||||
|
// Handle history buffers for certain boat data for windplot page and other usage
|
||||||
|
hstryBufList.handleHstryBuf(useSimuData);
|
||||||
|
|
||||||
// Clear display
|
// Clear display
|
||||||
// getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);
|
// getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);
|
||||||
getdisplay().fillScreen(commonData.bgcolor); // Clear display
|
getdisplay().fillScreen(commonData.bgcolor); // Clear display
|
||||||
@@ -816,6 +827,11 @@ void OBP60Task(GwApi *api){
|
|||||||
if (systemPage) {
|
if (systemPage) {
|
||||||
displayFooter(commonData);
|
displayFooter(commonData);
|
||||||
PageData sysparams; // empty
|
PageData sysparams; // empty
|
||||||
|
sysparams.api = api;
|
||||||
|
if (systemPageNew) {
|
||||||
|
syspage->displayNew(sysparams);
|
||||||
|
systemPageNew = false;
|
||||||
|
}
|
||||||
syspage->displayPage(sysparams);
|
syspage->displayPage(sysparams);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -832,10 +848,11 @@ void OBP60Task(GwApi *api){
|
|||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (lastPage != pageNumber){
|
if (lastPage != pageNumber){
|
||||||
if (hasFRAM) fram.write(FRAM_PAGE_NO, pageNumber); // remember page for device restart
|
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
|
||||||
currentPage->setupKeys();
|
currentPage->setupKeys();
|
||||||
currentPage->displayNew(pages[pageNumber].parameters);
|
currentPage->displayNew(pages[pageNumber].parameters);
|
||||||
lastPage=pageNumber;
|
lastPage = pageNumber;
|
||||||
}
|
}
|
||||||
//call the page code
|
//call the page code
|
||||||
LOG_DEBUG(GwLog::DEBUG,"calling page %d",pageNumber);
|
LOG_DEBUG(GwLog::DEBUG,"calling page %d",pageNumber);
|
||||||
@@ -843,7 +860,16 @@ void OBP60Task(GwApi *api){
|
|||||||
if (pages[pageNumber].description && pages[pageNumber].description->header){
|
if (pages[pageNumber].description && pages[pageNumber].description->header){
|
||||||
displayFooter(commonData);
|
displayFooter(commonData);
|
||||||
}
|
}
|
||||||
currentPage->displayPage(pages[pageNumber].parameters);
|
int ret = currentPage->displayPage(pages[pageNumber].parameters);
|
||||||
|
if (commonData.alarm.active) {
|
||||||
|
displayAlarm(commonData);
|
||||||
|
}
|
||||||
|
if (ret & PAGE_UPDATE) {
|
||||||
|
getdisplay().nextPage(); // Partial update (fast)
|
||||||
|
}
|
||||||
|
if (ret & PAGE_HIBERNATE) {
|
||||||
|
getdisplay().hibernate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,5 +41,24 @@
|
|||||||
#ifdef BOARD_OBP40S3
|
#ifdef BOARD_OBP40S3
|
||||||
DECLARE_CAPABILITY(obp40,true)
|
DECLARE_CAPABILITY(obp40,true)
|
||||||
#endif
|
#endif
|
||||||
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp60-v2-docu.readthedocs.io/de/latest/"); // Link to help pages
|
#ifdef BOARD_OBP60S3
|
||||||
|
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp60-v2-docu.readthedocs.io/en/latest/"); // Link to help pages
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp40-v1-docu.readthedocs.io/en/latest/"); // Link to help pages
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class BoatValueList{
|
||||||
|
public:
|
||||||
|
static const int MAXVALUES=100;
|
||||||
|
//we create a list containing all our BoatValues
|
||||||
|
//this is the list we later use to let the api fill all the values
|
||||||
|
//additionally we put the necessary values into the paga data - see below
|
||||||
|
GwApi::BoatValue *allBoatValues[MAXVALUES];
|
||||||
|
int numValues=0;
|
||||||
|
|
||||||
|
bool addValueToList(GwApi::BoatValue *v);
|
||||||
|
//helper to ensure that each BoatValue is only queried once
|
||||||
|
GwApi::BoatValue *findValueOrCreate(String name);
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ lib_deps =
|
|||||||
Wire
|
Wire
|
||||||
SPI
|
SPI
|
||||||
ESP32time
|
ESP32time
|
||||||
|
HTTPClient
|
||||||
|
WiFiClientSecure
|
||||||
esphome/AsyncTCP-esphome@2.0.1
|
esphome/AsyncTCP-esphome@2.0.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
|
||||||
@@ -40,19 +42,20 @@ lib_deps =
|
|||||||
paulstoffregen/OneWire@2.3.8
|
paulstoffregen/OneWire@2.3.8
|
||||||
milesburton/DallasTemperature@3.11.0
|
milesburton/DallasTemperature@3.11.0
|
||||||
signetica/SunRise@2.0.2
|
signetica/SunRise@2.0.2
|
||||||
adafruit/Adafruit FRAM I2C@^2.0.3
|
adafruit/Adafruit FRAM I2C@2.0.3
|
||||||
build_flags=
|
build_flags=
|
||||||
#https://thingpulse.com/usb-settings-for-logging-with-the-esp32-s3-in-platformio/?srsltid=AfmBOopGskbkr4GoeVkNlFaZXe_zXkLceKF6Rn-tmoXABCeAR2vWsdHL
|
#https://thingpulse.com/usb-settings-for-logging-with-the-esp32-s3-in-platformio/?srsltid=AfmBOopGskbkr4GoeVkNlFaZXe_zXkLceKF6Rn-tmoXABCeAR2vWsdHL
|
||||||
# -D CORE_DEBUG_LEVEL=1 #Debug level for CPU core via CDC (seral device)
|
# -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 TIME=$UNIX_TIME #Set PC time for RTC (only settable via VSC)
|
||||||
-D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib
|
-D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib
|
||||||
-D BOARD_OBP60S3 #Board OBP60 V2.1 with ESP32S3
|
-D BOARD_OBP60S3 #Board OBP60 V2.1 with ESP32S3
|
||||||
# -D HARDWARE_V20 #OBP60 hardware revision V2.0
|
# -D HARDWARE_V20 #OBP60 hardware revision V2.0
|
||||||
-D HARDWARE_V21 #OBP60 hardware revision V2.1
|
-D HARDWARE_V21 #OBP60 hardware revision V2.1
|
||||||
# -D DISPLAY_GDEW042T2 #old E-Ink display from Waveshare, R10 0.47 ohm
|
# -D DISPLAY_GDEW042T2 #old E-Ink display from GoodDisplay (Waveshare), R10 0.47 ohm - very good
|
||||||
-D DISPLAY_GDEY042T81 #new E-Ink display from Waveshare, R10 2.2 ohm
|
-D DISPLAY_GDEY042T81 #new E-Ink display from GoodDisplay (Waveshare), R10 2.2 ohm - good (contast lost by shunshine)
|
||||||
# -D DISPLAY_GYE042A87 #alternativ E-Ink display from Genyo Optical, R10 2.2 ohm
|
# -D DISPLAY_GYE042A87 #alternativ E-Ink display from Genyo Optical, R10 2.2 ohm - medium
|
||||||
# -D DISPLAY_SE0420NQ04 #alternativ E-Ink display from SID Technology, R10 2.2 ohm
|
# -D DISPLAY_SE0420NQ04 #alternativ E-Ink display from SID Technology, R10 2.2 ohm - bad (burn in effects)
|
||||||
|
# -D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
|
||||||
${env.build_flags}
|
${env.build_flags}
|
||||||
#CONFIG_ESP_TASK_WDT_TIMEOUT_S = 10 #Task Watchdog timeout period (seconds) [1...60] 5 default
|
#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_port = /dev/ttyACM0 #OBP60 download via USB-C direct
|
||||||
@@ -73,6 +76,8 @@ lib_deps =
|
|||||||
SPI
|
SPI
|
||||||
SD
|
SD
|
||||||
ESP32time
|
ESP32time
|
||||||
|
HTTPClient
|
||||||
|
WiFiClientSecure
|
||||||
esphome/AsyncTCP-esphome@2.0.1
|
esphome/AsyncTCP-esphome@2.0.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
|
||||||
@@ -91,14 +96,15 @@ lib_deps =
|
|||||||
paulstoffregen/OneWire@2.3.8
|
paulstoffregen/OneWire@2.3.8
|
||||||
milesburton/DallasTemperature@3.11.0
|
milesburton/DallasTemperature@3.11.0
|
||||||
signetica/SunRise@2.0.2
|
signetica/SunRise@2.0.2
|
||||||
adafruit/Adafruit FRAM I2C@^2.0.3
|
adafruit/Adafruit FRAM I2C@2.0.3
|
||||||
build_flags=
|
build_flags=
|
||||||
-D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib
|
-D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib
|
||||||
-D BOARD_OBP40S3 #Board OBP40 with ESP32S3
|
-D BOARD_OBP40S3 #Board OBP40 with ESP32S3
|
||||||
-D HARDWARE_V10 #OBP40 hardware revision V1.0 SKU:DIE07300S V1.1 (CrowPanel 4.2)
|
-D HARDWARE_V10 #OBP40 hardware revision V1.0 SKU:DIE07300S V1.1 (CrowPanel 4.2)
|
||||||
-D DISPLAY_GDEY042T81 #new E-Ink display from Waveshare, R10 2.2 ohm
|
-D DISPLAY_GDEY042T81 #new E-Ink display from Good Display (Waveshare), R10 2.2 ohm - good (contast lost by shunshine)
|
||||||
#-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
|
#-D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
|
||||||
#-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
|
-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
|
||||||
|
-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
|
||||||
${env.build_flags}
|
${env.build_flags}
|
||||||
upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter
|
upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter
|
||||||
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_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
|
||||||
|
|||||||
840
lib/obp60task/puff.c
Normal file
840
lib/obp60task/puff.c
Normal file
@@ -0,0 +1,840 @@
|
|||||||
|
/*
|
||||||
|
* puff.c
|
||||||
|
* Copyright (C) 2002-2013 Mark Adler
|
||||||
|
* For conditions of distribution and use, see copyright notice in puff.h
|
||||||
|
* version 2.3, 21 Jan 2013
|
||||||
|
*
|
||||||
|
* puff.c is a simple inflate written to be an unambiguous way to specify the
|
||||||
|
* deflate format. It is not written for speed but rather simplicity. As a
|
||||||
|
* side benefit, this code might actually be useful when small code is more
|
||||||
|
* important than speed, such as bootstrap applications. For typical deflate
|
||||||
|
* data, zlib's inflate() is about four times as fast as puff(). zlib's
|
||||||
|
* inflate compiles to around 20K on my machine, whereas puff.c compiles to
|
||||||
|
* around 4K on my machine (a PowerPC using GNU cc). If the faster decode()
|
||||||
|
* function here is used, then puff() is only twice as slow as zlib's
|
||||||
|
* inflate().
|
||||||
|
*
|
||||||
|
* All dynamically allocated memory comes from the stack. The stack required
|
||||||
|
* is less than 2K bytes. This code is compatible with 16-bit int's and
|
||||||
|
* assumes that long's are at least 32 bits. puff.c uses the short data type,
|
||||||
|
* assumed to be 16 bits, for arrays in order to conserve memory. The code
|
||||||
|
* works whether integers are stored big endian or little endian.
|
||||||
|
*
|
||||||
|
* In the comments below are "Format notes" that describe the inflate process
|
||||||
|
* and document some of the less obvious aspects of the format. This source
|
||||||
|
* code is meant to supplement RFC 1951, which formally describes the deflate
|
||||||
|
* format:
|
||||||
|
*
|
||||||
|
* http://www.zlib.org/rfc-deflate.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change history:
|
||||||
|
*
|
||||||
|
* 1.0 10 Feb 2002 - First version
|
||||||
|
* 1.1 17 Feb 2002 - Clarifications of some comments and notes
|
||||||
|
* - Update puff() dest and source pointers on negative
|
||||||
|
* errors to facilitate debugging deflators
|
||||||
|
* - Remove longest from struct huffman -- not needed
|
||||||
|
* - Simplify offs[] index in construct()
|
||||||
|
* - Add input size and checking, using longjmp() to
|
||||||
|
* maintain easy readability
|
||||||
|
* - Use short data type for large arrays
|
||||||
|
* - Use pointers instead of long to specify source and
|
||||||
|
* destination sizes to avoid arbitrary 4 GB limits
|
||||||
|
* 1.2 17 Mar 2002 - Add faster version of decode(), doubles speed (!),
|
||||||
|
* but leave simple version for readability
|
||||||
|
* - Make sure invalid distances detected if pointers
|
||||||
|
* are 16 bits
|
||||||
|
* - Fix fixed codes table error
|
||||||
|
* - Provide a scanning mode for determining size of
|
||||||
|
* uncompressed data
|
||||||
|
* 1.3 20 Mar 2002 - Go back to lengths for puff() parameters [Gailly]
|
||||||
|
* - Add a puff.h file for the interface
|
||||||
|
* - Add braces in puff() for else do [Gailly]
|
||||||
|
* - Use indexes instead of pointers for readability
|
||||||
|
* 1.4 31 Mar 2002 - Simplify construct() code set check
|
||||||
|
* - Fix some comments
|
||||||
|
* - Add FIXLCODES #define
|
||||||
|
* 1.5 6 Apr 2002 - Minor comment fixes
|
||||||
|
* 1.6 7 Aug 2002 - Minor format changes
|
||||||
|
* 1.7 3 Mar 2003 - Added test code for distribution
|
||||||
|
* - Added zlib-like license
|
||||||
|
* 1.8 9 Jan 2004 - Added some comments on no distance codes case
|
||||||
|
* 1.9 21 Feb 2008 - Fix bug on 16-bit integer architectures [Pohland]
|
||||||
|
* - Catch missing end-of-block symbol error
|
||||||
|
* 2.0 25 Jul 2008 - Add #define to permit distance too far back
|
||||||
|
* - Add option in TEST code for puff to write the data
|
||||||
|
* - Add option in TEST code to skip input bytes
|
||||||
|
* - Allow TEST code to read from piped stdin
|
||||||
|
* 2.1 4 Apr 2010 - Avoid variable initialization for happier compilers
|
||||||
|
* - Avoid unsigned comparisons for even happier compilers
|
||||||
|
* 2.2 25 Apr 2010 - Fix bug in variable initializations [Oberhumer]
|
||||||
|
* - Add const where appropriate [Oberhumer]
|
||||||
|
* - Split if's and ?'s for coverage testing
|
||||||
|
* - Break out test code to separate file
|
||||||
|
* - Move NIL to puff.h
|
||||||
|
* - Allow incomplete code only if single code length is 1
|
||||||
|
* - Add full code coverage test to Makefile
|
||||||
|
* 2.3 21 Jan 2013 - Check for invalid code length codes in dynamic blocks
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <setjmp.h> /* for setjmp(), longjmp(), and jmp_buf */
|
||||||
|
#include "puff.h" /* prototype for puff() */
|
||||||
|
|
||||||
|
#define local static /* for local function definitions */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Maximums for allocations and loops. It is not useful to change these --
|
||||||
|
* they are fixed by the deflate format.
|
||||||
|
*/
|
||||||
|
#define MAXBITS 15 /* maximum bits in a code */
|
||||||
|
#define MAXLCODES 286 /* maximum number of literal/length codes */
|
||||||
|
#define MAXDCODES 30 /* maximum number of distance codes */
|
||||||
|
#define MAXCODES (MAXLCODES+MAXDCODES) /* maximum codes lengths to read */
|
||||||
|
#define FIXLCODES 288 /* number of fixed literal/length codes */
|
||||||
|
|
||||||
|
/* input and output state */
|
||||||
|
struct state {
|
||||||
|
/* output state */
|
||||||
|
unsigned char *out; /* output buffer */
|
||||||
|
unsigned long outlen; /* available space at out */
|
||||||
|
unsigned long outcnt; /* bytes written to out so far */
|
||||||
|
|
||||||
|
/* input state */
|
||||||
|
const unsigned char *in; /* input buffer */
|
||||||
|
unsigned long inlen; /* available input at in */
|
||||||
|
unsigned long incnt; /* bytes read so far */
|
||||||
|
int bitbuf; /* bit buffer */
|
||||||
|
int bitcnt; /* number of bits in bit buffer */
|
||||||
|
|
||||||
|
/* input limit error return state for bits() and decode() */
|
||||||
|
jmp_buf env;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return need bits from the input stream. This always leaves less than
|
||||||
|
* eight bits in the buffer. bits() works properly for need == 0.
|
||||||
|
*
|
||||||
|
* Format notes:
|
||||||
|
*
|
||||||
|
* - Bits are stored in bytes from the least significant bit to the most
|
||||||
|
* significant bit. Therefore bits are dropped from the bottom of the bit
|
||||||
|
* buffer, using shift right, and new bytes are appended to the top of the
|
||||||
|
* bit buffer, using shift left.
|
||||||
|
*/
|
||||||
|
local int bits(struct state *s, int need)
|
||||||
|
{
|
||||||
|
long val; /* bit accumulator (can use up to 20 bits) */
|
||||||
|
|
||||||
|
/* load at least need bits into val */
|
||||||
|
val = s->bitbuf;
|
||||||
|
while (s->bitcnt < need) {
|
||||||
|
if (s->incnt == s->inlen)
|
||||||
|
longjmp(s->env, 1); /* out of input */
|
||||||
|
val |= (long)(s->in[s->incnt++]) << s->bitcnt; /* load eight bits */
|
||||||
|
s->bitcnt += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* drop need bits and update buffer, always zero to seven bits left */
|
||||||
|
s->bitbuf = (int)(val >> need);
|
||||||
|
s->bitcnt -= need;
|
||||||
|
|
||||||
|
/* return need bits, zeroing the bits above that */
|
||||||
|
return (int)(val & ((1L << need) - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process a stored block.
|
||||||
|
*
|
||||||
|
* Format notes:
|
||||||
|
*
|
||||||
|
* - After the two-bit stored block type (00), the stored block length and
|
||||||
|
* stored bytes are byte-aligned for fast copying. Therefore any leftover
|
||||||
|
* bits in the byte that has the last bit of the type, as many as seven, are
|
||||||
|
* discarded. The value of the discarded bits are not defined and should not
|
||||||
|
* be checked against any expectation.
|
||||||
|
*
|
||||||
|
* - The second inverted copy of the stored block length does not have to be
|
||||||
|
* checked, but it's probably a good idea to do so anyway.
|
||||||
|
*
|
||||||
|
* - A stored block can have zero length. This is sometimes used to byte-align
|
||||||
|
* subsets of the compressed data for random access or partial recovery.
|
||||||
|
*/
|
||||||
|
local int stored(struct state *s)
|
||||||
|
{
|
||||||
|
unsigned len; /* length of stored block */
|
||||||
|
|
||||||
|
/* discard leftover bits from current byte (assumes s->bitcnt < 8) */
|
||||||
|
s->bitbuf = 0;
|
||||||
|
s->bitcnt = 0;
|
||||||
|
|
||||||
|
/* get length and check against its one's complement */
|
||||||
|
if (s->incnt + 4 > s->inlen)
|
||||||
|
return 2; /* not enough input */
|
||||||
|
len = s->in[s->incnt++];
|
||||||
|
len |= s->in[s->incnt++] << 8;
|
||||||
|
if (s->in[s->incnt++] != (~len & 0xff) ||
|
||||||
|
s->in[s->incnt++] != ((~len >> 8) & 0xff))
|
||||||
|
return -2; /* didn't match complement! */
|
||||||
|
|
||||||
|
/* copy len bytes from in to out */
|
||||||
|
if (s->incnt + len > s->inlen)
|
||||||
|
return 2; /* not enough input */
|
||||||
|
if (s->out != NIL) {
|
||||||
|
if (s->outcnt + len > s->outlen)
|
||||||
|
return 1; /* not enough output space */
|
||||||
|
while (len--)
|
||||||
|
s->out[s->outcnt++] = s->in[s->incnt++];
|
||||||
|
}
|
||||||
|
else { /* just scanning */
|
||||||
|
s->outcnt += len;
|
||||||
|
s->incnt += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* done with a valid stored block */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of
|
||||||
|
* each length, which for a canonical code are stepped through in order.
|
||||||
|
* symbol[] are the symbol values in canonical order, where the number of
|
||||||
|
* entries is the sum of the counts in count[]. The decoding process can be
|
||||||
|
* seen in the function decode() below.
|
||||||
|
*/
|
||||||
|
struct huffman {
|
||||||
|
short *count; /* number of symbols of each length */
|
||||||
|
short *symbol; /* canonically ordered symbols */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decode a code from the stream s using huffman table h. Return the symbol or
|
||||||
|
* a negative value if there is an error. If all of the lengths are zero, i.e.
|
||||||
|
* an empty code, or if the code is incomplete and an invalid code is received,
|
||||||
|
* then -10 is returned after reading MAXBITS bits.
|
||||||
|
*
|
||||||
|
* Format notes:
|
||||||
|
*
|
||||||
|
* - The codes as stored in the compressed data are bit-reversed relative to
|
||||||
|
* a simple integer ordering of codes of the same lengths. Hence below the
|
||||||
|
* bits are pulled from the compressed data one at a time and used to
|
||||||
|
* build the code value reversed from what is in the stream in order to
|
||||||
|
* permit simple integer comparisons for decoding. A table-based decoding
|
||||||
|
* scheme (as used in zlib) does not need to do this reversal.
|
||||||
|
*
|
||||||
|
* - The first code for the shortest length is all zeros. Subsequent codes of
|
||||||
|
* the same length are simply integer increments of the previous code. When
|
||||||
|
* moving up a length, a zero bit is appended to the code. For a complete
|
||||||
|
* code, the last code of the longest length will be all ones.
|
||||||
|
*
|
||||||
|
* - Incomplete codes are handled by this decoder, since they are permitted
|
||||||
|
* in the deflate format. See the format notes for fixed() and dynamic().
|
||||||
|
*/
|
||||||
|
#ifdef SLOW
|
||||||
|
local int decode(struct state *s, const struct huffman *h)
|
||||||
|
{
|
||||||
|
int len; /* current number of bits in code */
|
||||||
|
int code; /* len bits being decoded */
|
||||||
|
int first; /* first code of length len */
|
||||||
|
int count; /* number of codes of length len */
|
||||||
|
int index; /* index of first code of length len in symbol table */
|
||||||
|
|
||||||
|
code = first = index = 0;
|
||||||
|
for (len = 1; len <= MAXBITS; len++) {
|
||||||
|
code |= bits(s, 1); /* get next bit */
|
||||||
|
count = h->count[len];
|
||||||
|
if (code - count < first) /* if length len, return symbol */
|
||||||
|
return h->symbol[index + (code - first)];
|
||||||
|
index += count; /* else update for next length */
|
||||||
|
first += count;
|
||||||
|
first <<= 1;
|
||||||
|
code <<= 1;
|
||||||
|
}
|
||||||
|
return -10; /* ran out of codes */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A faster version of decode() for real applications of this code. It's not
|
||||||
|
* as readable, but it makes puff() twice as fast. And it only makes the code
|
||||||
|
* a few percent larger.
|
||||||
|
*/
|
||||||
|
#else /* !SLOW */
|
||||||
|
local int decode(struct state *s, const struct huffman *h)
|
||||||
|
{
|
||||||
|
int len; /* current number of bits in code */
|
||||||
|
int code; /* len bits being decoded */
|
||||||
|
int first; /* first code of length len */
|
||||||
|
int count; /* number of codes of length len */
|
||||||
|
int index; /* index of first code of length len in symbol table */
|
||||||
|
int bitbuf; /* bits from stream */
|
||||||
|
int left; /* bits left in next or left to process */
|
||||||
|
short *next; /* next number of codes */
|
||||||
|
|
||||||
|
bitbuf = s->bitbuf;
|
||||||
|
left = s->bitcnt;
|
||||||
|
code = first = index = 0;
|
||||||
|
len = 1;
|
||||||
|
next = h->count + 1;
|
||||||
|
while (1) {
|
||||||
|
while (left--) {
|
||||||
|
code |= bitbuf & 1;
|
||||||
|
bitbuf >>= 1;
|
||||||
|
count = *next++;
|
||||||
|
if (code - count < first) { /* if length len, return symbol */
|
||||||
|
s->bitbuf = bitbuf;
|
||||||
|
s->bitcnt = (s->bitcnt - len) & 7;
|
||||||
|
return h->symbol[index + (code - first)];
|
||||||
|
}
|
||||||
|
index += count; /* else update for next length */
|
||||||
|
first += count;
|
||||||
|
first <<= 1;
|
||||||
|
code <<= 1;
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
left = (MAXBITS+1) - len;
|
||||||
|
if (left == 0)
|
||||||
|
break;
|
||||||
|
if (s->incnt == s->inlen)
|
||||||
|
longjmp(s->env, 1); /* out of input */
|
||||||
|
bitbuf = s->in[s->incnt++];
|
||||||
|
if (left > 8)
|
||||||
|
left = 8;
|
||||||
|
}
|
||||||
|
return -10; /* ran out of codes */
|
||||||
|
}
|
||||||
|
#endif /* SLOW */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given the list of code lengths length[0..n-1] representing a canonical
|
||||||
|
* Huffman code for n symbols, construct the tables required to decode those
|
||||||
|
* codes. Those tables are the number of codes of each length, and the symbols
|
||||||
|
* sorted by length, retaining their original order within each length. The
|
||||||
|
* return value is zero for a complete code set, negative for an over-
|
||||||
|
* subscribed code set, and positive for an incomplete code set. The tables
|
||||||
|
* can be used if the return value is zero or positive, but they cannot be used
|
||||||
|
* if the return value is negative. If the return value is zero, it is not
|
||||||
|
* possible for decode() using that table to return an error--any stream of
|
||||||
|
* enough bits will resolve to a symbol. If the return value is positive, then
|
||||||
|
* it is possible for decode() using that table to return an error for received
|
||||||
|
* codes past the end of the incomplete lengths.
|
||||||
|
*
|
||||||
|
* Not used by decode(), but used for error checking, h->count[0] is the number
|
||||||
|
* of the n symbols not in the code. So n - h->count[0] is the number of
|
||||||
|
* codes. This is useful for checking for incomplete codes that have more than
|
||||||
|
* one symbol, which is an error in a dynamic block.
|
||||||
|
*
|
||||||
|
* Assumption: for all i in 0..n-1, 0 <= length[i] <= MAXBITS
|
||||||
|
* This is assured by the construction of the length arrays in dynamic() and
|
||||||
|
* fixed() and is not verified by construct().
|
||||||
|
*
|
||||||
|
* Format notes:
|
||||||
|
*
|
||||||
|
* - Permitted and expected examples of incomplete codes are one of the fixed
|
||||||
|
* codes and any code with a single symbol which in deflate is coded as one
|
||||||
|
* bit instead of zero bits. See the format notes for fixed() and dynamic().
|
||||||
|
*
|
||||||
|
* - Within a given code length, the symbols are kept in ascending order for
|
||||||
|
* the code bits definition.
|
||||||
|
*/
|
||||||
|
local int construct(struct huffman *h, const short *length, int n)
|
||||||
|
{
|
||||||
|
int symbol; /* current symbol when stepping through length[] */
|
||||||
|
int len; /* current length when stepping through h->count[] */
|
||||||
|
int left; /* number of possible codes left of current length */
|
||||||
|
short offs[MAXBITS+1]; /* offsets in symbol table for each length */
|
||||||
|
|
||||||
|
/* count number of codes of each length */
|
||||||
|
for (len = 0; len <= MAXBITS; len++)
|
||||||
|
h->count[len] = 0;
|
||||||
|
for (symbol = 0; symbol < n; symbol++)
|
||||||
|
(h->count[length[symbol]])++; /* assumes lengths are within bounds */
|
||||||
|
if (h->count[0] == n) /* no codes! */
|
||||||
|
return 0; /* complete, but decode() will fail */
|
||||||
|
|
||||||
|
/* check for an over-subscribed or incomplete set of lengths */
|
||||||
|
left = 1; /* one possible code of zero length */
|
||||||
|
for (len = 1; len <= MAXBITS; len++) {
|
||||||
|
left <<= 1; /* one more bit, double codes left */
|
||||||
|
left -= h->count[len]; /* deduct count from possible codes */
|
||||||
|
if (left < 0)
|
||||||
|
return left; /* over-subscribed--return negative */
|
||||||
|
} /* left > 0 means incomplete */
|
||||||
|
|
||||||
|
/* generate offsets into symbol table for each length for sorting */
|
||||||
|
offs[1] = 0;
|
||||||
|
for (len = 1; len < MAXBITS; len++)
|
||||||
|
offs[len + 1] = offs[len] + h->count[len];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* put symbols in table sorted by length, by symbol order within each
|
||||||
|
* length
|
||||||
|
*/
|
||||||
|
for (symbol = 0; symbol < n; symbol++)
|
||||||
|
if (length[symbol] != 0)
|
||||||
|
h->symbol[offs[length[symbol]]++] = symbol;
|
||||||
|
|
||||||
|
/* return zero for complete set, positive for incomplete set */
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decode literal/length and distance codes until an end-of-block code.
|
||||||
|
*
|
||||||
|
* Format notes:
|
||||||
|
*
|
||||||
|
* - Compressed data that is after the block type if fixed or after the code
|
||||||
|
* description if dynamic is a combination of literals and length/distance
|
||||||
|
* pairs terminated by and end-of-block code. Literals are simply Huffman
|
||||||
|
* coded bytes. A length/distance pair is a coded length followed by a
|
||||||
|
* coded distance to represent a string that occurs earlier in the
|
||||||
|
* uncompressed data that occurs again at the current location.
|
||||||
|
*
|
||||||
|
* - Literals, lengths, and the end-of-block code are combined into a single
|
||||||
|
* code of up to 286 symbols. They are 256 literals (0..255), 29 length
|
||||||
|
* symbols (257..285), and the end-of-block symbol (256).
|
||||||
|
*
|
||||||
|
* - There are 256 possible lengths (3..258), and so 29 symbols are not enough
|
||||||
|
* to represent all of those. Lengths 3..10 and 258 are in fact represented
|
||||||
|
* by just a length symbol. Lengths 11..257 are represented as a symbol and
|
||||||
|
* some number of extra bits that are added as an integer to the base length
|
||||||
|
* of the length symbol. The number of extra bits is determined by the base
|
||||||
|
* length symbol. These are in the static arrays below, lens[] for the base
|
||||||
|
* lengths and lext[] for the corresponding number of extra bits.
|
||||||
|
*
|
||||||
|
* - The reason that 258 gets its own symbol is that the longest length is used
|
||||||
|
* often in highly redundant files. Note that 258 can also be coded as the
|
||||||
|
* base value 227 plus the maximum extra value of 31. While a good deflate
|
||||||
|
* should never do this, it is not an error, and should be decoded properly.
|
||||||
|
*
|
||||||
|
* - If a length is decoded, including its extra bits if any, then it is
|
||||||
|
* followed a distance code. There are up to 30 distance symbols. Again
|
||||||
|
* there are many more possible distances (1..32768), so extra bits are added
|
||||||
|
* to a base value represented by the symbol. The distances 1..4 get their
|
||||||
|
* own symbol, but the rest require extra bits. The base distances and
|
||||||
|
* corresponding number of extra bits are below in the static arrays dist[]
|
||||||
|
* and dext[].
|
||||||
|
*
|
||||||
|
* - Literal bytes are simply written to the output. A length/distance pair is
|
||||||
|
* an instruction to copy previously uncompressed bytes to the output. The
|
||||||
|
* copy is from distance bytes back in the output stream, copying for length
|
||||||
|
* bytes.
|
||||||
|
*
|
||||||
|
* - Distances pointing before the beginning of the output data are not
|
||||||
|
* permitted.
|
||||||
|
*
|
||||||
|
* - Overlapped copies, where the length is greater than the distance, are
|
||||||
|
* allowed and common. For example, a distance of one and a length of 258
|
||||||
|
* simply copies the last byte 258 times. A distance of four and a length of
|
||||||
|
* twelve copies the last four bytes three times. A simple forward copy
|
||||||
|
* ignoring whether the length is greater than the distance or not implements
|
||||||
|
* this correctly. You should not use memcpy() since its behavior is not
|
||||||
|
* defined for overlapped arrays. You should not use memmove() or bcopy()
|
||||||
|
* since though their behavior -is- defined for overlapping arrays, it is
|
||||||
|
* defined to do the wrong thing in this case.
|
||||||
|
*/
|
||||||
|
local int codes(struct state *s,
|
||||||
|
const struct huffman *lencode,
|
||||||
|
const struct huffman *distcode)
|
||||||
|
{
|
||||||
|
int symbol; /* decoded symbol */
|
||||||
|
int len; /* length for copy */
|
||||||
|
unsigned dist; /* distance for copy */
|
||||||
|
static const short lens[29] = { /* Size base for length codes 257..285 */
|
||||||
|
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
|
||||||
|
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
|
||||||
|
static const short lext[29] = { /* Extra bits for length codes 257..285 */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
|
||||||
|
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
|
||||||
|
static const short dists[30] = { /* Offset base for distance codes 0..29 */
|
||||||
|
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
|
||||||
|
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
|
||||||
|
8193, 12289, 16385, 24577};
|
||||||
|
static const short dext[30] = { /* Extra bits for distance codes 0..29 */
|
||||||
|
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
|
||||||
|
7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
|
||||||
|
12, 12, 13, 13};
|
||||||
|
|
||||||
|
/* decode literals and length/distance pairs */
|
||||||
|
do {
|
||||||
|
symbol = decode(s, lencode);
|
||||||
|
if (symbol < 0)
|
||||||
|
return symbol; /* invalid symbol */
|
||||||
|
if (symbol < 256) { /* literal: symbol is the byte */
|
||||||
|
/* write out the literal */
|
||||||
|
if (s->out != NIL) {
|
||||||
|
if (s->outcnt == s->outlen)
|
||||||
|
return 1;
|
||||||
|
s->out[s->outcnt] = symbol;
|
||||||
|
}
|
||||||
|
s->outcnt++;
|
||||||
|
}
|
||||||
|
else if (symbol > 256) { /* length */
|
||||||
|
/* get and compute length */
|
||||||
|
symbol -= 257;
|
||||||
|
if (symbol >= 29)
|
||||||
|
return -10; /* invalid fixed code */
|
||||||
|
len = lens[symbol] + bits(s, lext[symbol]);
|
||||||
|
|
||||||
|
/* get and check distance */
|
||||||
|
symbol = decode(s, distcode);
|
||||||
|
if (symbol < 0)
|
||||||
|
return symbol; /* invalid symbol */
|
||||||
|
dist = dists[symbol] + bits(s, dext[symbol]);
|
||||||
|
#ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
|
||||||
|
if (dist > s->outcnt)
|
||||||
|
return -11; /* distance too far back */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* copy length bytes from distance bytes back */
|
||||||
|
if (s->out != NIL) {
|
||||||
|
if (s->outcnt + len > s->outlen)
|
||||||
|
return 1;
|
||||||
|
while (len--) {
|
||||||
|
s->out[s->outcnt] =
|
||||||
|
#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
|
||||||
|
dist > s->outcnt ?
|
||||||
|
0 :
|
||||||
|
#endif
|
||||||
|
s->out[s->outcnt - dist];
|
||||||
|
s->outcnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
s->outcnt += len;
|
||||||
|
}
|
||||||
|
} while (symbol != 256); /* end of block symbol */
|
||||||
|
|
||||||
|
/* done with a valid fixed or dynamic block */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process a fixed codes block.
|
||||||
|
*
|
||||||
|
* Format notes:
|
||||||
|
*
|
||||||
|
* - This block type can be useful for compressing small amounts of data for
|
||||||
|
* which the size of the code descriptions in a dynamic block exceeds the
|
||||||
|
* benefit of custom codes for that block. For fixed codes, no bits are
|
||||||
|
* spent on code descriptions. Instead the code lengths for literal/length
|
||||||
|
* codes and distance codes are fixed. The specific lengths for each symbol
|
||||||
|
* can be seen in the "for" loops below.
|
||||||
|
*
|
||||||
|
* - The literal/length code is complete, but has two symbols that are invalid
|
||||||
|
* and should result in an error if received. This cannot be implemented
|
||||||
|
* simply as an incomplete code since those two symbols are in the "middle"
|
||||||
|
* of the code. They are eight bits long and the longest literal/length\
|
||||||
|
* code is nine bits. Therefore the code must be constructed with those
|
||||||
|
* symbols, and the invalid symbols must be detected after decoding.
|
||||||
|
*
|
||||||
|
* - The fixed distance codes also have two invalid symbols that should result
|
||||||
|
* in an error if received. Since all of the distance codes are the same
|
||||||
|
* length, this can be implemented as an incomplete code. Then the invalid
|
||||||
|
* codes are detected while decoding.
|
||||||
|
*/
|
||||||
|
local int fixed(struct state *s)
|
||||||
|
{
|
||||||
|
static int virgin = 1;
|
||||||
|
static short lencnt[MAXBITS+1], lensym[FIXLCODES];
|
||||||
|
static short distcnt[MAXBITS+1], distsym[MAXDCODES];
|
||||||
|
static struct huffman lencode, distcode;
|
||||||
|
|
||||||
|
/* build fixed huffman tables if first call (may not be thread safe) */
|
||||||
|
if (virgin) {
|
||||||
|
int symbol;
|
||||||
|
short lengths[FIXLCODES];
|
||||||
|
|
||||||
|
/* construct lencode and distcode */
|
||||||
|
lencode.count = lencnt;
|
||||||
|
lencode.symbol = lensym;
|
||||||
|
distcode.count = distcnt;
|
||||||
|
distcode.symbol = distsym;
|
||||||
|
|
||||||
|
/* literal/length table */
|
||||||
|
for (symbol = 0; symbol < 144; symbol++)
|
||||||
|
lengths[symbol] = 8;
|
||||||
|
for (; symbol < 256; symbol++)
|
||||||
|
lengths[symbol] = 9;
|
||||||
|
for (; symbol < 280; symbol++)
|
||||||
|
lengths[symbol] = 7;
|
||||||
|
for (; symbol < FIXLCODES; symbol++)
|
||||||
|
lengths[symbol] = 8;
|
||||||
|
construct(&lencode, lengths, FIXLCODES);
|
||||||
|
|
||||||
|
/* distance table */
|
||||||
|
for (symbol = 0; symbol < MAXDCODES; symbol++)
|
||||||
|
lengths[symbol] = 5;
|
||||||
|
construct(&distcode, lengths, MAXDCODES);
|
||||||
|
|
||||||
|
/* do this just once */
|
||||||
|
virgin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* decode data until end-of-block code */
|
||||||
|
return codes(s, &lencode, &distcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process a dynamic codes block.
|
||||||
|
*
|
||||||
|
* Format notes:
|
||||||
|
*
|
||||||
|
* - A dynamic block starts with a description of the literal/length and
|
||||||
|
* distance codes for that block. New dynamic blocks allow the compressor to
|
||||||
|
* rapidly adapt to changing data with new codes optimized for that data.
|
||||||
|
*
|
||||||
|
* - The codes used by the deflate format are "canonical", which means that
|
||||||
|
* the actual bits of the codes are generated in an unambiguous way simply
|
||||||
|
* from the number of bits in each code. Therefore the code descriptions
|
||||||
|
* are simply a list of code lengths for each symbol.
|
||||||
|
*
|
||||||
|
* - The code lengths are stored in order for the symbols, so lengths are
|
||||||
|
* provided for each of the literal/length symbols, and for each of the
|
||||||
|
* distance symbols.
|
||||||
|
*
|
||||||
|
* - If a symbol is not used in the block, this is represented by a zero as the
|
||||||
|
* code length. This does not mean a zero-length code, but rather that no
|
||||||
|
* code should be created for this symbol. There is no way in the deflate
|
||||||
|
* format to represent a zero-length code.
|
||||||
|
*
|
||||||
|
* - The maximum number of bits in a code is 15, so the possible lengths for
|
||||||
|
* any code are 1..15.
|
||||||
|
*
|
||||||
|
* - The fact that a length of zero is not permitted for a code has an
|
||||||
|
* interesting consequence. Normally if only one symbol is used for a given
|
||||||
|
* code, then in fact that code could be represented with zero bits. However
|
||||||
|
* in deflate, that code has to be at least one bit. So for example, if
|
||||||
|
* only a single distance base symbol appears in a block, then it will be
|
||||||
|
* represented by a single code of length one, in particular one 0 bit. This
|
||||||
|
* is an incomplete code, since if a 1 bit is received, it has no meaning,
|
||||||
|
* and should result in an error. So incomplete distance codes of one symbol
|
||||||
|
* should be permitted, and the receipt of invalid codes should be handled.
|
||||||
|
*
|
||||||
|
* - It is also possible to have a single literal/length code, but that code
|
||||||
|
* must be the end-of-block code, since every dynamic block has one. This
|
||||||
|
* is not the most efficient way to create an empty block (an empty fixed
|
||||||
|
* block is fewer bits), but it is allowed by the format. So incomplete
|
||||||
|
* literal/length codes of one symbol should also be permitted.
|
||||||
|
*
|
||||||
|
* - If there are only literal codes and no lengths, then there are no distance
|
||||||
|
* codes. This is represented by one distance code with zero bits.
|
||||||
|
*
|
||||||
|
* - The list of up to 286 length/literal lengths and up to 30 distance lengths
|
||||||
|
* are themselves compressed using Huffman codes and run-length encoding. In
|
||||||
|
* the list of code lengths, a 0 symbol means no code, a 1..15 symbol means
|
||||||
|
* that length, and the symbols 16, 17, and 18 are run-length instructions.
|
||||||
|
* Each of 16, 17, and 18 are followed by extra bits to define the length of
|
||||||
|
* the run. 16 copies the last length 3 to 6 times. 17 represents 3 to 10
|
||||||
|
* zero lengths, and 18 represents 11 to 138 zero lengths. Unused symbols
|
||||||
|
* are common, hence the special coding for zero lengths.
|
||||||
|
*
|
||||||
|
* - The symbols for 0..18 are Huffman coded, and so that code must be
|
||||||
|
* described first. This is simply a sequence of up to 19 three-bit values
|
||||||
|
* representing no code (0) or the code length for that symbol (1..7).
|
||||||
|
*
|
||||||
|
* - A dynamic block starts with three fixed-size counts from which is computed
|
||||||
|
* the number of literal/length code lengths, the number of distance code
|
||||||
|
* lengths, and the number of code length code lengths (ok, you come up with
|
||||||
|
* a better name!) in the code descriptions. For the literal/length and
|
||||||
|
* distance codes, lengths after those provided are considered zero, i.e. no
|
||||||
|
* code. The code length code lengths are received in a permuted order (see
|
||||||
|
* the order[] array below) to make a short code length code length list more
|
||||||
|
* likely. As it turns out, very short and very long codes are less likely
|
||||||
|
* to be seen in a dynamic code description, hence what may appear initially
|
||||||
|
* to be a peculiar ordering.
|
||||||
|
*
|
||||||
|
* - Given the number of literal/length code lengths (nlen) and distance code
|
||||||
|
* lengths (ndist), then they are treated as one long list of nlen + ndist
|
||||||
|
* code lengths. Therefore run-length coding can and often does cross the
|
||||||
|
* boundary between the two sets of lengths.
|
||||||
|
*
|
||||||
|
* - So to summarize, the code description at the start of a dynamic block is
|
||||||
|
* three counts for the number of code lengths for the literal/length codes,
|
||||||
|
* the distance codes, and the code length codes. This is followed by the
|
||||||
|
* code length code lengths, three bits each. This is used to construct the
|
||||||
|
* code length code which is used to read the remainder of the lengths. Then
|
||||||
|
* the literal/length code lengths and distance lengths are read as a single
|
||||||
|
* set of lengths using the code length codes. Codes are constructed from
|
||||||
|
* the resulting two sets of lengths, and then finally you can start
|
||||||
|
* decoding actual compressed data in the block.
|
||||||
|
*
|
||||||
|
* - For reference, a "typical" size for the code description in a dynamic
|
||||||
|
* block is around 80 bytes.
|
||||||
|
*/
|
||||||
|
local int dynamic(struct state *s)
|
||||||
|
{
|
||||||
|
int nlen, ndist, ncode; /* number of lengths in descriptor */
|
||||||
|
int index; /* index of lengths[] */
|
||||||
|
int err; /* construct() return value */
|
||||||
|
short lengths[MAXCODES]; /* descriptor code lengths */
|
||||||
|
short lencnt[MAXBITS+1], lensym[MAXLCODES]; /* lencode memory */
|
||||||
|
short distcnt[MAXBITS+1], distsym[MAXDCODES]; /* distcode memory */
|
||||||
|
struct huffman lencode, distcode; /* length and distance codes */
|
||||||
|
static const short order[19] = /* permutation of code length codes */
|
||||||
|
{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
|
||||||
|
|
||||||
|
/* construct lencode and distcode */
|
||||||
|
lencode.count = lencnt;
|
||||||
|
lencode.symbol = lensym;
|
||||||
|
distcode.count = distcnt;
|
||||||
|
distcode.symbol = distsym;
|
||||||
|
|
||||||
|
/* get number of lengths in each table, check lengths */
|
||||||
|
nlen = bits(s, 5) + 257;
|
||||||
|
ndist = bits(s, 5) + 1;
|
||||||
|
ncode = bits(s, 4) + 4;
|
||||||
|
if (nlen > MAXLCODES || ndist > MAXDCODES)
|
||||||
|
return -3; /* bad counts */
|
||||||
|
|
||||||
|
/* read code length code lengths (really), missing lengths are zero */
|
||||||
|
for (index = 0; index < ncode; index++)
|
||||||
|
lengths[order[index]] = bits(s, 3);
|
||||||
|
for (; index < 19; index++)
|
||||||
|
lengths[order[index]] = 0;
|
||||||
|
|
||||||
|
/* build huffman table for code lengths codes (use lencode temporarily) */
|
||||||
|
err = construct(&lencode, lengths, 19);
|
||||||
|
if (err != 0) /* require complete code set here */
|
||||||
|
return -4;
|
||||||
|
|
||||||
|
/* read length/literal and distance code length tables */
|
||||||
|
index = 0;
|
||||||
|
while (index < nlen + ndist) {
|
||||||
|
int symbol; /* decoded value */
|
||||||
|
int len; /* last length to repeat */
|
||||||
|
|
||||||
|
symbol = decode(s, &lencode);
|
||||||
|
if (symbol < 0)
|
||||||
|
return symbol; /* invalid symbol */
|
||||||
|
if (symbol < 16) /* length in 0..15 */
|
||||||
|
lengths[index++] = symbol;
|
||||||
|
else { /* repeat instruction */
|
||||||
|
len = 0; /* assume repeating zeros */
|
||||||
|
if (symbol == 16) { /* repeat last length 3..6 times */
|
||||||
|
if (index == 0)
|
||||||
|
return -5; /* no last length! */
|
||||||
|
len = lengths[index - 1]; /* last length */
|
||||||
|
symbol = 3 + bits(s, 2);
|
||||||
|
}
|
||||||
|
else if (symbol == 17) /* repeat zero 3..10 times */
|
||||||
|
symbol = 3 + bits(s, 3);
|
||||||
|
else /* == 18, repeat zero 11..138 times */
|
||||||
|
symbol = 11 + bits(s, 7);
|
||||||
|
if (index + symbol > nlen + ndist)
|
||||||
|
return -6; /* too many lengths! */
|
||||||
|
while (symbol--) /* repeat last or zero symbol times */
|
||||||
|
lengths[index++] = len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for end-of-block code -- there better be one! */
|
||||||
|
if (lengths[256] == 0)
|
||||||
|
return -9;
|
||||||
|
|
||||||
|
/* build huffman table for literal/length codes */
|
||||||
|
err = construct(&lencode, lengths, nlen);
|
||||||
|
if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1]))
|
||||||
|
return -7; /* incomplete code ok only for single length 1 code */
|
||||||
|
|
||||||
|
/* build huffman table for distance codes */
|
||||||
|
err = construct(&distcode, lengths + nlen, ndist);
|
||||||
|
if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1]))
|
||||||
|
return -8; /* incomplete code ok only for single length 1 code */
|
||||||
|
|
||||||
|
/* decode data until end-of-block code */
|
||||||
|
return codes(s, &lencode, &distcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inflate source to dest. On return, destlen and sourcelen are updated to the
|
||||||
|
* size of the uncompressed data and the size of the deflate data respectively.
|
||||||
|
* On success, the return value of puff() is zero. If there is an error in the
|
||||||
|
* source data, i.e. it is not in the deflate format, then a negative value is
|
||||||
|
* returned. If there is not enough input available or there is not enough
|
||||||
|
* output space, then a positive error is returned. In that case, destlen and
|
||||||
|
* sourcelen are not updated to facilitate retrying from the beginning with the
|
||||||
|
* provision of more input data or more output space. In the case of invalid
|
||||||
|
* inflate data (a negative error), the dest and source pointers are updated to
|
||||||
|
* facilitate the debugging of deflators.
|
||||||
|
*
|
||||||
|
* puff() also has a mode to determine the size of the uncompressed output with
|
||||||
|
* no output written. For this dest must be (unsigned char *)0. In this case,
|
||||||
|
* the input value of *destlen is ignored, and on return *destlen is set to the
|
||||||
|
* size of the uncompressed output.
|
||||||
|
*
|
||||||
|
* The return codes are:
|
||||||
|
*
|
||||||
|
* 2: available inflate data did not terminate
|
||||||
|
* 1: output space exhausted before completing inflate
|
||||||
|
* 0: successful inflate
|
||||||
|
* -1: invalid block type (type == 3)
|
||||||
|
* -2: stored block length did not match one's complement
|
||||||
|
* -3: dynamic block code description: too many length or distance codes
|
||||||
|
* -4: dynamic block code description: code lengths codes incomplete
|
||||||
|
* -5: dynamic block code description: repeat lengths with no first length
|
||||||
|
* -6: dynamic block code description: repeat more than specified lengths
|
||||||
|
* -7: dynamic block code description: invalid literal/length code lengths
|
||||||
|
* -8: dynamic block code description: invalid distance code lengths
|
||||||
|
* -9: dynamic block code description: missing end-of-block code
|
||||||
|
* -10: invalid literal/length or distance code in fixed or dynamic block
|
||||||
|
* -11: distance is too far back in fixed or dynamic block
|
||||||
|
*
|
||||||
|
* Format notes:
|
||||||
|
*
|
||||||
|
* - Three bits are read for each block to determine the kind of block and
|
||||||
|
* whether or not it is the last block. Then the block is decoded and the
|
||||||
|
* process repeated if it was not the last block.
|
||||||
|
*
|
||||||
|
* - The leftover bits in the last byte of the deflate data after the last
|
||||||
|
* block (if it was a fixed or dynamic block) are undefined and have no
|
||||||
|
* expected values to check.
|
||||||
|
*/
|
||||||
|
int puff(unsigned char *dest, /* pointer to destination pointer */
|
||||||
|
unsigned long *destlen, /* amount of output space */
|
||||||
|
const unsigned char *source, /* pointer to source data pointer */
|
||||||
|
unsigned long *sourcelen) /* amount of input available */
|
||||||
|
{
|
||||||
|
struct state s; /* input/output state */
|
||||||
|
int last, type; /* block information */
|
||||||
|
int err; /* return value */
|
||||||
|
|
||||||
|
/* initialize output state */
|
||||||
|
s.out = dest;
|
||||||
|
s.outlen = *destlen; /* ignored if dest is NIL */
|
||||||
|
s.outcnt = 0;
|
||||||
|
|
||||||
|
/* initialize input state */
|
||||||
|
s.in = source;
|
||||||
|
s.inlen = *sourcelen;
|
||||||
|
s.incnt = 0;
|
||||||
|
s.bitbuf = 0;
|
||||||
|
s.bitcnt = 0;
|
||||||
|
|
||||||
|
/* return if bits() or decode() tries to read past available input */
|
||||||
|
if (setjmp(s.env) != 0) /* if came back here via longjmp() */
|
||||||
|
err = 2; /* then skip do-loop, return error */
|
||||||
|
else {
|
||||||
|
/* process blocks until last block or error */
|
||||||
|
do {
|
||||||
|
last = bits(&s, 1); /* one if last block */
|
||||||
|
type = bits(&s, 2); /* block type 0..3 */
|
||||||
|
err = type == 0 ?
|
||||||
|
stored(&s) :
|
||||||
|
(type == 1 ?
|
||||||
|
fixed(&s) :
|
||||||
|
(type == 2 ?
|
||||||
|
dynamic(&s) :
|
||||||
|
-1)); /* type == 3, invalid */
|
||||||
|
if (err != 0)
|
||||||
|
break; /* return with error */
|
||||||
|
} while (!last);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* update the lengths and return */
|
||||||
|
if (err <= 0) {
|
||||||
|
*destlen = s.outcnt;
|
||||||
|
*sourcelen = s.incnt;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
35
lib/obp60task/puff.h
Normal file
35
lib/obp60task/puff.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/* puff.h
|
||||||
|
Copyright (C) 2002-2013 Mark Adler, all rights reserved
|
||||||
|
version 2.3, 21 Jan 2013
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the author be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
|
||||||
|
Mark Adler madler@alumni.caltech.edu
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See puff.c for purpose and usage.
|
||||||
|
*/
|
||||||
|
#ifndef NIL
|
||||||
|
# define NIL ((unsigned char *)0) /* for no output option */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int puff(unsigned char *dest, /* pointer to destination pointer */
|
||||||
|
unsigned long *destlen, /* amount of output space */
|
||||||
|
const unsigned char *source, /* pointer to source data pointer */
|
||||||
|
unsigned long *sourcelen); /* amount of input available */
|
||||||
Reference in New Issue
Block a user