1
0
mirror of https://github.com/thooge/esp32-nmea2000-obp60.git synced 2025-12-28 05:03:06 +01:00

3 Commits

Author SHA1 Message Date
a8cf34343f Config data for tracker page 2025-09-09 15:32:56 +02:00
c00a0ecbed Prepared page tracker 2025-09-07 08:50:33 +02:00
a16ee74b32 Integrate master branch changes 2025-08-31 14:17:39 +02:00
26 changed files with 1048 additions and 422 deletions

View File

@@ -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;
logger->logDebug(GwLog::LOG, "stored calibration data: %s, offset: %f, slope: %f, smoothing: %f", instance.c_str(), logger->logDebug(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);
} }
logger->logDebug(GwLog::LOG, "all calibration data read"); logger->logDebug(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()) {
logger->logDebug(GwLog::DEBUG, "BoatDataCalibration: %s not found in calibration data list", instance.c_str()); logger->logDebug(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;

View File

@@ -3,7 +3,7 @@
#ifndef _BOATDATACALIBRATION_H #ifndef _BOATDATACALIBRATION_H
#define _BOATDATACALIBRATION_H #define _BOATDATACALIBRATION_H
#include "Pagedata.h" #include "GwApi.h"
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@@ -30,4 +30,4 @@ private:
extern CalibrationDataList calibrationData; // this list holds all calibration data extern CalibrationDataList calibrationData; // this list holds all calibration data
#endif #endif

View File

@@ -25,6 +25,7 @@
#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/Atari6px8b.h" // Very small (6x6) font
// E-Ink Display // E-Ink Display
#define GxEPD_WIDTH 400 // Display width #define GxEPD_WIDTH 400 // Display width
@@ -212,8 +213,8 @@ void deepSleep(CommonData &common){
setFlashLED(false); // Flash LED Off setFlashLED(false); // Flash LED Off
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
// Shutdown EInk display // Shutdown EInk display
epd->setFullWindow(); // Set full Refresh epd->setFullWindow(); // Set full Refresh
epd->fillScreen(common.bgcolor); // Clear screen epd->fillScreen(common.bgcolor); // Clear screen
epd->setTextColor(common.fgcolor); epd->setTextColor(common.fgcolor);
epd->setFont(&Ubuntu_Bold20pt8b); epd->setFont(&Ubuntu_Bold20pt8b);
epd->setCursor(85, 150); epd->setCursor(85, 150);
@@ -221,8 +222,8 @@ void deepSleep(CommonData &common){
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b);
epd->setCursor(65, 175); epd->setCursor(65, 175);
epd->print("To wake up press key and wait 5s"); epd->print("To wake up press key and wait 5s");
epd->nextPage(); // Update display contents epd->nextPage(); // Update display contents
epd->powerOff(); // Display power off epd->powerOff(); // Display power off
setPortPin(OBP_POWER_50, false); // Power off ePaper display setPortPin(OBP_POWER_50, false); // Power off ePaper display
// Stop system // Stop system
esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin

View File

@@ -55,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 Atari6px;
// Global functions // Global functions
#ifdef DISPLAY_GDEW042T2 #ifdef DISPLAY_GDEW042T2

View File

@@ -1,5 +1,147 @@
#include "OBPDataOperations.h" #include "OBPDataOperations.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 hstryMinVal = 0; // Minimum value for these history buffers
twdHstryMax = 6283; // Max value for wind direction (TWD, AWD) in rad [0...2*PI], shifted by 1000 for 3 decimals
twsHstryMax = 65000; // Max value for wind speed (TWS, AWS) in m/s [0..65], shifted by 1000 for 3 decimals
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
hstryBufList.twdHstry->setMetaData("TWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax);
hstryBufList.twsHstry->setMetaData("TWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax);
hstryBufList.awdHstry->setMetaData("AWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax);
hstryBufList.awsHstry->setMetaData("AWS", "formatKnots", hstryUpdFreq, 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 int16_t twd = 20; //initial value only relevant if we use simulation data
static uint16_t tws = 20; //initial value only relevant if we use simulation data
static double 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) {
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 = static_cast<int16_t>(std::round(calBVal->value * 1000.0));
if (twd >= twdHstryMin && twd <= twdHstryMax) {
hstryBufList.twdHstry->add(twd);
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
twd += random(-20, 20);
twd = WindUtils::to360(twd);
hstryBufList.twdHstry->add(static_cast<int16_t>(DegToRad(twd) * 1000.0));
}
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 = static_cast<uint16_t>(std::round(calBVal->value * 1000));
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 = constrain(tws, 0, 25000); // Limit TWS to [0..25] 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 = std::round(calBVal->value * 1000.0);
if (awd >= awdHstryMin && awd <= awdHstryMax) {
hstryBufList.awdHstry->add(static_cast<int16_t>(awd));
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
awd += random(-20, 20);
awd = WindUtils::to360(awd);
hstryBufList.awdHstry->add(static_cast<int16_t>(DegToRad(awd) * 1000.0));
}
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 = std::round(calBVal->value * 1000);
if (aws >= awsHstryMin && aws <= awsHstryMax) {
hstryBufList.awsHstry->add(static_cast<uint16_t>(aws));
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
aws += random(-5000, 5000); // TWS value in m/s; expands to 1 decimal
aws = constrain(aws, 0, 25000); // Limit TWS to [0..25] m/s
hstryBufList.awsHstry->add(aws);
}
}
// --- Class HstryBuf ---------------
// --- Class WindUtils --------------
double WindUtils::to2PI(double a) double WindUtils::to2PI(double a)
{ {
a = fmod(a, 2 * M_PI); a = fmod(a, 2 * M_PI);
@@ -68,29 +210,25 @@ void WindUtils::calcTwdSA(const double* AWA, const double* AWS,
double awd = *AWA + *HDT; double awd = *AWA + *HDT;
awd = to2PI(awd); awd = to2PI(awd);
double stw = -*STW; double stw = -*STW;
// Serial.println("\ncalcTwdSA: AWA: " + String(*AWA) + ", AWS: " + String(*AWS) + ", CTW: " + String(*CTW) + ", STW: " + String(*STW) + ", HDT: " + String(*HDT));
addPolar(&awd, AWS, CTW, &stw, TWD, TWS); addPolar(&awd, AWS, CTW, &stw, TWD, TWS);
// Normalize TWD and TWA to 0-360° // Normalize TWD and TWA to 0-360°
*TWD = to2PI(*TWD); *TWD = to2PI(*TWD);
*TWA = toPI(*TWD - *HDT); *TWA = toPI(*TWD - *HDT);
// Serial.println("calcTwdSA: TWD: " + String(*TWD) + ", TWS: " + String(*TWS));
} }
double WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal) double WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal)
{ {
double hdt; double hdt;
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
static const double DBL_MIN = std::numeric_limits<double>::lowest();
// Serial.println("\ncalcTrueWind: HDT: " + String(*hdtVal) + ", HDM: " + String(*hdmVal) + ", VAR: " + String(*varVal) + ", SOG: " + String(*sogVal) + ", COG: " + String(*cogVal)); if (*hdmVal != DBL_MAX) {
if (*hdmVal != DBL_MIN) { 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 = *hdmVal + (*varVal != DBL_MIN ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available)
hdt = to2PI(hdt); hdt = to2PI(hdt);
} else if (*cogVal != DBL_MIN && *sogVal >= minSogVal) { } 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 hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise
} else { } else {
hdt = DBL_MIN; // Cannot calculate HDT without valid HDM or HDM+VAR or COG hdt = DBL_MAX; // Cannot calculate HDT without valid HDM or HDM+VAR or COG
} }
return hdt; return hdt;
@@ -103,37 +241,23 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal,
double stw, hdt, ctw; double stw, hdt, ctw;
double twd, tws, twa; double twd, tws, twa;
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
static const double DBL_MIN = std::numeric_limits<double>::lowest();
// Serial.println("\ncalcTrueWind: HDT: " + String(*hdtVal) + ", HDM: " + String(*hdmVal) + ", VAR: " + String(*varVal) + ", SOG: " + String(*sogVal) + ", COG: " + String(*cogVal)); if (*hdtVal != DBL_MAX) {
/* if (*hdtVal != DBL_MIN) {
hdt = *hdtVal; // Use HDT if available
} else {
if (*hdmVal != DBL_MIN) {
hdt = *hdmVal + (*varVal != DBL_MIN ? *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_MIN && *sogVal >= minSogVal) {
hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise
} else {
return false; // Cannot calculate without valid HDT or HDM+VAR or COG
}
} */
if (*hdtVal != DBL_MIN) {
hdt = *hdtVal; // Use HDT if available hdt = *hdtVal; // Use HDT if available
} else { } else {
hdt = calcHDT(hdmVal, varVal, cogVal, sogVal); hdt = calcHDT(hdmVal, varVal, cogVal, sogVal);
} }
if (*cogVal != DBL_MIN && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG if (*cogVal != DBL_MAX && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG
ctw = *cogVal; // Use COG for CTW if available ctw = *cogVal; // Use COG for CTW if available
} else { } else {
ctw = hdt; // 2nd approximation for CTW; hdt must exist if we reach this part of the code ctw = hdt; // 2nd approximation for CTW; hdt must exist if we reach this part of the code
} }
if (*stwVal != DBL_MIN) { if (*stwVal != DBL_MAX) {
stw = *stwVal; // Use STW if available stw = *stwVal; // Use STW if available
} else if (*sogVal != DBL_MIN) { } else if (*sogVal != DBL_MAX) {
stw = *sogVal; stw = *sogVal;
} else { } else {
// If STW and SOG are not available, we cannot calculate true wind // If STW and SOG are not available, we cannot calculate true wind
@@ -141,7 +265,7 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal,
} }
// Serial.println("\ncalcTrueWind: HDT: " + String(hdt) + ", CTW: " + String(ctw) + ", STW: " + String(stw)); // Serial.println("\ncalcTrueWind: HDT: " + String(hdt) + ", CTW: " + String(ctw) + ", STW: " + String(stw));
if ((*awaVal == DBL_MIN) || (*awsVal == DBL_MIN)) { if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) {
// Cannot calculate true wind without valid AWA, AWS; other checks are done earlier // Cannot calculate true wind without valid AWA, AWS; other checks are done earlier
return false; return false;
} else { } else {
@@ -153,3 +277,46 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal,
return true; 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 --------------

View File

@@ -1,39 +1,90 @@
#pragma once #pragma once
#include "GwApi.h" #include <N2kMessages.h>
#include "OBPRingBuffer.h" #include "OBPRingBuffer.h"
// #include <Arduino.h> #include "BoatDataCalibration.h" // Functions lib for data instance calibration
#include "obp60task.h"
#include <math.h> #include <math.h>
typedef struct { typedef struct {
RingBuffer<int16_t>* twdHstry; RingBuffer<int16_t>* twdHstry;
RingBuffer<int16_t>* twsHstry; RingBuffer<uint16_t>* twsHstry;
RingBuffer<int16_t>* awdHstry; RingBuffer<int16_t>* awdHstry;
RingBuffer<int16_t>* awsHstry; RingBuffer<uint16_t>* awsHstry;
} tBoatHstryData; // Holds pointers to all history buffers for boat data } tBoatHstryData; // Holds pointers to all history buffers for boat data
class HstryBuf { class HstryBuf {
private:
GwLog *logger;
RingBuffer<int16_t> twdHstry; // Circular buffer to store true wind direction values
RingBuffer<uint16_t> twsHstry; // Circular buffer to store true wind speed values (TWS)
RingBuffer<int16_t> awdHstry; // Circular buffer to store apparant wind direction values
RingBuffer<uint16_t> awsHstry; // Circular buffer to store apparant xwind speed values (AWS)
int16_t twdHstryMin; // Min value for wind direction (TWD) in history buffer
int16_t twdHstryMax; // Max value for wind direction (TWD) in history buffer
uint16_t twsHstryMin;
uint16_t twsHstryMax;
int16_t awdHstryMin;
int16_t awdHstryMax;
uint16_t awsHstryMin;
uint16_t 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: 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(960); // store 960 TWD values for 16 minutes history
hstryBufList.twsHstry->resize(960);
hstryBufList.awdHstry->resize(960);
hstryBufList.awsHstry->resize(960);
};
void init(BoatValueList* boatValues, GwLog *log);
void handleHstryBuf(bool useSimuData);
}; };
class WindUtils { 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: 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 to2PI(double a);
static double toPI(double a); static double toPI(double a);
static double to360(double a); static double to360(double a);
static double to180(double a); static double to180(double a);
static void toCart(const double* phi, const double* r, double* x, double* y); void toCart(const double* phi, const double* r, double* x, double* y);
static void toPol(const double* x, const double* y, double* phi, double* r); void toPol(const double* x, const double* y, double* phi, double* r);
static void addPolar(const double* phi1, const double* r1, void addPolar(const double* phi1, const double* r1,
const double* phi2, const double* r2, const double* phi2, const double* r2,
double* phi, double* r); double* phi, double* r);
static void calcTwdSA(const double* AWA, const double* AWS, void calcTwdSA(const double* AWA, const double* AWS,
const double* CTW, const double* STW, const double* HDT, const double* CTW, const double* STW, const double* HDT,
double* TWD, double* TWS, double* TWA); double* TWD, double* TWS, double* TWA);
static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal); static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal);
static bool calcTrueWind(const double* awaVal, const double* awsVal, bool calcTrueWind(const double* awaVal, const double* awsVal,
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal); const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal);
}; bool addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog *log);
};

View File

@@ -9,28 +9,32 @@
template <typename T> template <typename T>
class RingBuffer { class RingBuffer {
private: private:
mutable SemaphoreHandle_t bufLocker; std::vector<T> buffer; // THE buffer vector
std::vector<T> buffer;
size_t capacity; size_t capacity;
size_t head; // Points to the next insertion position size_t head; // Points to the next insertion position
size_t first; // Points to the first (oldest) valid element size_t first; // Points to the first (oldest) valid element
size_t last; // Points to the last (newest) valid element size_t last; // Points to the last (newest) valid element
size_t count; // Number of valid elements currently in buffer 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 bool is_Full; // Indicates that all buffer elements are used and ringing is in use
T MIN_VAL; // lowest possible value of buffer T MIN_VAL; // lowest possible value of buffer of type <T>
T MAX_VAL; // highest possible value of buffer of type <T> T MAX_VAL; // highest possible value of buffer of type <T> -> indicates invalid value in buffer
mutable SemaphoreHandle_t bufLocker;
// metadata for buffer // metadata for buffer
String dataName; // Name of boat data in buffer String dataName; // Name of boat data in buffer
String dataFmt; // Format of boat data in buffer String dataFmt; // Format of boat data in buffer
int updFreq; // Update frequency in milliseconds int updFreq; // Update frequency in milliseconds
T smallest; // Value range of buffer: smallest value T smallest; // Value range of buffer: smallest value; needs to be => MIN_VAL
T largest; // Value range of buffer: biggest value T largest; // Value range of buffer: biggest value; needs to be < MAX_VAL, since MAX_VAL indicates invalid entries
void initCommon();
public: public:
RingBuffer();
RingBuffer(size_t size); RingBuffer(size_t size);
void setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue); // Set meta data for buffer void setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue); // Set meta data for buffer
bool getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue); // Get meta data of buffer bool getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue); // Get meta data of buffer
bool getMetaData(String& name, String& format);
String getName() const; // Get buffer name String getName() const; // Get buffer name
String getFormat() const; // Get buffer data format String getFormat() const; // Get buffer data format
void add(const T& value); // Add a new value to buffer void add(const T& value); // Add a new value to buffer
@@ -51,11 +55,12 @@ public:
size_t getLastIdx() const; // Get the index of newest value in buffer size_t getLastIdx() const; // Get the index of newest value in buffer
bool isEmpty() const; // Check if buffer is empty bool isEmpty() const; // Check if buffer is empty
bool isFull() const; // Check if buffer is full bool isFull() const; // Check if buffer is full
T getMinVal() const; // Get lowest possible value for buffer; used for initialized buffer data T getMinVal() const; // Get lowest possible value for buffer
T getMaxVal() const; // Get highest possible value for buffer T getMaxVal() const; // Get highest possible value for buffer; used for unset/invalid buffer data
void clear(); // Clear buffer void clear(); // Clear buffer
void resize(size_t size); // Delete buffer and set new size
T operator[](size_t index) const; // Operator[] for convenient access (same as get()) T operator[](size_t index) const; // Operator[] for convenient access (same as get())
std::vector<T> getAllValues() const; // Get all current values as a vector std::vector<T> getAllValues() const; // Get all current values as a vector
}; };
#include "OBPRingBuffer.tpp" #include "OBPRingBuffer.tpp"

View File

@@ -1,5 +1,30 @@
#include "OBPRingBuffer.h" #include "OBPRingBuffer.h"
template <typename T>
void RingBuffer<T>::initCommon() {
MIN_VAL = std::numeric_limits<T>::lowest();
MAX_VAL = std::numeric_limits<T>::max();
dataName = "";
dataFmt = "";
updFreq = -1;
smallest = MIN_VAL;
largest = MAX_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> template <typename T>
RingBuffer<T>::RingBuffer(size_t size) RingBuffer<T>::RingBuffer(size_t size)
: capacity(size) : capacity(size)
@@ -9,23 +34,8 @@ RingBuffer<T>::RingBuffer(size_t size)
, count(0) , count(0)
, is_Full(false) , is_Full(false)
{ {
bufLocker = xSemaphoreCreateMutex(); initCommon();
buffer.resize(size, MAX_VAL); // MAX_VAL indicate invalid values
if (size == 0) {
// return false;
}
MIN_VAL = std::numeric_limits<T>::lowest();
MAX_VAL = std::numeric_limits<T>::max();
dataName = "";
dataFmt = "";
updFreq = -1;
smallest = MIN_VAL;
largest = MAX_VAL;
buffer.resize(size, MIN_VAL);
// return true;
} }
// Specify meta data of buffer content // Specify meta data of buffer content
@@ -57,6 +67,20 @@ bool RingBuffer<T>::getMetaData(String& name, String& format, int& updateFrequen
return true; 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 // Get buffer name
template <typename T> template <typename T>
String RingBuffer<T>::getName() const String RingBuffer<T>::getName() const
@@ -77,7 +101,7 @@ void RingBuffer<T>::add(const T& value)
{ {
GWSYNCHRONIZED(&bufLocker); GWSYNCHRONIZED(&bufLocker);
if (value < smallest || value > largest) { if (value < smallest || value > largest) {
buffer[head] = MIN_VAL; // Store MIN_VAL if value is out of range buffer[head] = MAX_VAL; // Store MAX_VAL if value is out of range
} else { } else {
buffer[head] = value; buffer[head] = value;
} }
@@ -101,7 +125,7 @@ T RingBuffer<T>::get(size_t index) const
{ {
GWSYNCHRONIZED(&bufLocker); GWSYNCHRONIZED(&bufLocker);
if (isEmpty() || index < 0 || index >= count) { if (isEmpty() || index < 0 || index >= count) {
return MIN_VAL; return MAX_VAL;
} }
size_t realIndex = (first + index) % capacity; size_t realIndex = (first + index) % capacity;
@@ -120,7 +144,7 @@ template <typename T>
T RingBuffer<T>::getFirst() const T RingBuffer<T>::getFirst() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
return get(0); return get(0);
} }
@@ -130,7 +154,7 @@ template <typename T>
T RingBuffer<T>::getLast() const T RingBuffer<T>::getLast() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
return get(count - 1); return get(count - 1);
} }
@@ -140,14 +164,14 @@ template <typename T>
T RingBuffer<T>::getMin() const T RingBuffer<T>::getMin() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
T minVal = MAX_VAL; T minVal = MAX_VAL;
T value; T value;
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
value = get(i); value = get(i);
if (value < minVal && value != MIN_VAL) { if (value < minVal && value != MAX_VAL) {
minVal = value; minVal = value;
} }
} }
@@ -159,7 +183,7 @@ template <typename T>
T RingBuffer<T>::getMin(size_t amount) const T RingBuffer<T>::getMin(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return MIN_VAL; return MAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
@@ -168,7 +192,7 @@ T RingBuffer<T>::getMin(size_t amount) const
T value; T value;
for (size_t i = 0; i < amount; i++) { for (size_t i = 0; i < amount; i++) {
value = get(count - 1 - i); value = get(count - 1 - i);
if (value < minVal && value != MIN_VAL) { if (value < minVal && value != MAX_VAL) {
minVal = value; minVal = value;
} }
} }
@@ -180,14 +204,14 @@ template <typename T>
T RingBuffer<T>::getMax() const T RingBuffer<T>::getMax() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
T maxVal = MIN_VAL; T maxVal = MIN_VAL;
T value; T value;
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
value = get(i); value = get(i);
if (value > maxVal && value != MIN_VAL) { if (value > maxVal && value != MAX_VAL) {
maxVal = value; maxVal = value;
} }
} }
@@ -199,7 +223,7 @@ template <typename T>
T RingBuffer<T>::getMax(size_t amount) const T RingBuffer<T>::getMax(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return MIN_VAL; return MAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
@@ -208,7 +232,7 @@ T RingBuffer<T>::getMax(size_t amount) const
T value; T value;
for (size_t i = 0; i < amount; i++) { for (size_t i = 0; i < amount; i++) {
value = get(count - 1 - i); value = get(count - 1 - i);
if (value > maxVal && value != MIN_VAL) { if (value > maxVal && value != MAX_VAL) {
maxVal = value; maxVal = value;
} }
} }
@@ -220,7 +244,7 @@ template <typename T>
T RingBuffer<T>::getMid() const T RingBuffer<T>::getMid() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
return (getMin() + getMax()) / static_cast<T>(2); return (getMin() + getMax()) / static_cast<T>(2);
@@ -231,7 +255,7 @@ template <typename T>
T RingBuffer<T>::getMid(size_t amount) const T RingBuffer<T>::getMid(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return MIN_VAL; return MAX_VAL;
} }
if (amount > count) if (amount > count)
@@ -245,7 +269,7 @@ template <typename T>
T RingBuffer<T>::getMedian() const T RingBuffer<T>::getMedian() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
// Create a temporary vector with current valid elements // Create a temporary vector with current valid elements
@@ -274,7 +298,7 @@ template <typename T>
T RingBuffer<T>::getMedian(size_t amount) const T RingBuffer<T>::getMedian(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return MIN_VAL; return MAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
@@ -342,14 +366,14 @@ bool RingBuffer<T>::isFull() const
return is_Full; return is_Full;
} }
// Get lowest possible value for buffer; used for non-set buffer data // Get lowest possible value for buffer
template <typename T> template <typename T>
T RingBuffer<T>::getMinVal() const T RingBuffer<T>::getMinVal() const
{ {
return MIN_VAL; return MIN_VAL;
} }
// Get highest possible value for buffer // Get highest possible value for buffer; used for unset/invalid buffer data
template <typename T> template <typename T>
T RingBuffer<T>::getMaxVal() const T RingBuffer<T>::getMaxVal() const
{ {
@@ -368,6 +392,22 @@ void RingBuffer<T>::clear()
is_Full = false; 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.resize(newSize, MAX_VAL);
}
// Get all current values as a vector // Get all current values as a vector
template <typename T> template <typename T>
std::vector<T> RingBuffer<T>::getAllValues() const std::vector<T> RingBuffer<T>::getAllValues() const
@@ -380,4 +420,4 @@ std::vector<T> RingBuffer<T>::getAllValues() const
} }
return result; return result;
} }

View File

@@ -13,6 +13,9 @@
Feature possibilities Feature possibilities
- switch between North up / Heading up - switch between North up / Heading up
- filter
- zoom
- special vessel symbols
*/ */

View File

@@ -39,6 +39,10 @@
Drop / raise function in device OBP40 has to be done inside Drop / raise function in device OBP40 has to be done inside
config mode because of limited number of buttons. config mode because of limited number of buttons.
Save position in FRAM
Alarm: gps fix lost
switch unit feet/meter
*/ */
#define anchor_width 16 #define anchor_width 16

View File

@@ -7,6 +7,14 @@
/* /*
Electric propulsion Electric propulsion
- Current, voltage, power
- 12, 24, 48 etc. Voltage
- rpm
- throttle position
- controller state
- error codes
- temperature engine, controller, batteries
*/ */
class PageEPropulsion : public Page class PageEPropulsion : public Page

View File

@@ -309,8 +309,6 @@ public:
}; };
static Page *createPage(CommonData &common){ static Page *createPage(CommonData &common){
return new PageFourValues(common); return new PageFourValues(common);
}/** }/**

View File

@@ -132,18 +132,23 @@ public:
epd->setCursor(c.x - r + 2 , c.y + h / 2); epd->setCursor(c.x - r + 2 , c.y + h / 2);
epd->print("W"); epd->print("W");
epd->setFont(&Ubuntu_Bold8pt8b);
// show satellites in "map" // show satellites in "map"
epd->setFont(&Atari6px);
for (int i = 0; i < nSat; i++) { for (int i = 0; i < nSat; i++) {
float arad = sats[i].Azimut * M_PI / 180.0; float arad = sats[i].Azimut * M_PI / 180.0;
float erad = sats[i].Elevation * M_PI / 180.0; float erad = sats[i].Elevation * M_PI / 180.0;
uint16_t x = c.x + sin(arad) * erad * r; uint16_t x = c.x + sin(arad) * erad * r;
uint16_t y = c.y + cos(arad) * erad * r; uint16_t y = c.y + cos(arad) * erad * r;
epd->drawRect(x-4, y-4, 8, 8, commonData->fgcolor); epd->drawRect(x-4, y-4, 8, 8, commonData->fgcolor);
// Add Sat number
epd->setCursor(x+5, y);
char buffer[3];
snprintf(buffer, 3, "%02d", static_cast<int>(sats[i].PRN));
epd->print(String(buffer));
} }
// Signal / Noise bars // Signal / Noise bars
epd->setFont(&Ubuntu_Bold8pt8b);
epd->setCursor(325, 34); epd->setCursor(325, 34);
epd->print("SNR"); epd->print("SNR");
epd->drawRect(270, 20, 125, 257, commonData->fgcolor); epd->drawRect(270, 20, 125, 257, commonData->fgcolor);

View File

@@ -0,0 +1,121 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#include "Pagedata.h"
#include "OBP60Extensions.h"
/*
Tracker
- standalone with SD card backend
- standalone with server backend
- Regatta Hero integration
*/
class PageTracker : public Page
{
private:
char mode = 'N'; // (N)ormal, (C)onfig
void displayModeNormal(PageData &pageData) {
// TBD Boatvalues: ...
logger->logDebug(GwLog::DEBUG,"Drawing at PageTracker");
// Title
epd->setTextColor(commonData->fgcolor);
epd->setFont(&Ubuntu_Bold12pt8b);
epd->setCursor(8, 48);
epd->print("Tracker");
}
void displayModeConfig() {
epd->setTextColor(commonData->fgcolor);
epd->setFont(&Ubuntu_Bold12pt8b);
epd->setCursor(8, 48);
epd->print("Tracker configuration");
epd->setFont(&Ubuntu_Bold8pt8b);
// TODO
}
public:
PageTracker(CommonData &common) : Page(common)
{
logger->logDebug(GwLog::LOG, "Instantiate PageTracker");
}
void setupKeys(){
Page::setupKeys();
commonData->keydata[0].label = "START";
commonData->keydata[1].label = "STOP";
}
int handleKey(int key){
if (key == 1) { // Switch between normal and config mode
if (mode == 'N') {
mode = 'C';
} else {
mode = 'N';
}
return 0;
}
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
};
int displayPage(PageData &pageData){
// Logging boat values
logger->logDebug(GwLog::LOG, "Drawing at PageTracker; Mode=%c", mode);
// Set display in partial refresh mode
epd->setPartialWindow(0, 0, epd->width(), epd->height());
if (mode == 'N') {
displayModeNormal(pageData);
} else if (mode == 'C') {
displayModeConfig();
}
return PAGE_UPDATE;
};
};
static Page *createPage(CommonData &common){
return new PageTracker(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 registerPageTracker(
"Tracker", // Page name
createPage, // Action
0, // Number of bus values depends on selection in Web configuration
{"LAT", "LON"}, // Names of bus values undepends on selection in Web configuration (refer GwBoatData.h)
true // Show display header on/off
);
#endif

View File

@@ -1,10 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 #if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#include "BoatDataCalibration.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "OBPRingBuffer.h" #include "OBPRingBuffer.h"
#include "Pagedata.h" #include "OBPDataOperations.h"
#include "BoatDataCalibration.h"
#include <vector> #include <vector>
static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees
@@ -12,7 +13,7 @@ static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians t
// Get maximum difference of last <amount> of TWD ringbuffer values to center chart; returns "0" if data is not valid // Get maximum difference of last <amount> of TWD ringbuffer values to center chart; returns "0" if data is not valid
int getCntr(const RingBuffer<int16_t>& windDirHstry, size_t amount) int getCntr(const RingBuffer<int16_t>& windDirHstry, size_t amount)
{ {
int minVal = windDirHstry.getMinVal(); const int MAX_VAL = windDirHstry.getMaxVal();
size_t count = windDirHstry.getCurrentSize(); size_t count = windDirHstry.getCurrentSize();
if (windDirHstry.isEmpty() || amount <= 0) { if (windDirHstry.isEmpty() || amount <= 0) {
@@ -21,11 +22,11 @@ int getCntr(const RingBuffer<int16_t>& windDirHstry, size_t amount)
if (amount > count) if (amount > count)
amount = count; amount = count;
int16_t midWndDir, minWndDir, maxWndDir = 0; uint16_t midWndDir, minWndDir, maxWndDir = 0;
int wndCenter = 0; int wndCenter = 0;
midWndDir = windDirHstry.getMid(amount); midWndDir = windDirHstry.getMid(amount);
if (midWndDir != INT16_MIN) { if (midWndDir != MAX_VAL) {
midWndDir = midWndDir / 1000.0 * radToDeg; midWndDir = midWndDir / 1000.0 * radToDeg;
wndCenter = int((midWndDir + (midWndDir >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value wndCenter = int((midWndDir + (midWndDir >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value
minWndDir = windDirHstry.getMin(amount) / 1000.0 * radToDeg; minWndDir = windDirHstry.getMin(amount) / 1000.0 * radToDeg;
@@ -42,10 +43,11 @@ int getCntr(const RingBuffer<int16_t>& windDirHstry, size_t amount)
int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount) int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount)
{ {
int minVal = windDirHstry.getMinVal(); int minVal = windDirHstry.getMinVal();
const int MAX_VAL = windDirHstry.getMaxVal();
size_t count = windDirHstry.getCurrentSize(); size_t count = windDirHstry.getCurrentSize();
if (windDirHstry.isEmpty() || amount <= 0) { if (windDirHstry.isEmpty() || amount <= 0) {
return minVal; return MAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
@@ -57,8 +59,8 @@ int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount)
for (size_t i = 0; i < amount; i++) { for (size_t i = 0; i < amount; i++) {
value = windDirHstry.get(count - 1 - i); value = windDirHstry.get(count - 1 - i);
if (value == minVal) { if (value == MAX_VAL) {
continue; continue; // ignore invalid values
} }
value = value / 1000.0 * radToDeg; value = value / 1000.0 * radToDeg;
@@ -70,7 +72,7 @@ int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount)
maxRng = 180; maxRng = 180;
} }
return maxRng; return (maxRng != minVal ? maxRng : MAX_VAL);
} }
// **************************************************************** // ****************************************************************
@@ -79,6 +81,8 @@ private:
bool keylock = false; // Keylock bool keylock = false; // Keylock
char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both
bool showTruW = true; // Show true wind or apparant wind in chart area bool showTruW = true; // Show true wind or apparant wind in chart area
bool oldShowTruW = false; // remember recent user selection of wind data type
int dataIntv = 1; // Update interval for wind history chart: int dataIntv = 1; // Update interval for wind history chart:
// (1)|(2)|(3)|(4) seconds for approx. 4, 8, 12, 16 min. history chart // (1)|(2)|(3)|(4) seconds for approx. 4, 8, 12, 16 min. history chart
@@ -163,31 +167,25 @@ public:
showTruW = false; // Wind source is apparant wind showTruW = false; // Wind source is apparant wind
} }
commonData->logger->logDebug(GwLog::LOG,"New PageWindPlot: wind source=%s", wndSrc); commonData->logger->logDebug(GwLog::LOG,"New PageWindPlot: wind source=%s", wndSrc);
#endif #endif
}; oldShowTruW = !showTruW; // makes wind source being initialized at initial page call
}
int displayPage(PageData& pageData) { int displayPage(PageData& pageData) {
static RingBuffer<int16_t>* wdHstry; // Wind direction data buffer static RingBuffer<int16_t>* wdHstry; // Wind direction data buffer
static RingBuffer<int16_t>* wsHstry; // Wind speed data buffer static RingBuffer<uint16_t>* wsHstry; // Wind speed data buffer
static String wdName, wdFormat; // Wind direction name and format static String wdName, wdFormat; // Wind direction name and format
static String wsName, wsFormat; // Wind speed name and format static String wsName, wsFormat; // Wind speed name and format
static int updFreq; // Update frequency for wind direction static int16_t wdMAX_VAL; // Max. value of wd history buffer, indicating invalid values
static int16_t wdLowest, wdHighest; // Wind direction range
float wsValue; // Wind speed value in chart area float wsValue; // Wind speed value in chart area
String wsUnit; // Wind speed unit in chart area String wsUnit; // Wind speed unit in chart area
static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater
// current boat data values; TWD/AWD only for validation test, TWS/AWS for display of current value // current boat data values; TWD/AWD only for validation test
const int numBoatData = 4; const int numBoatData = 2;
GwApi::BoatValue* bvalue; GwApi::BoatValue* bvalue;
String BDataName[numBoatData];
double BDataValue[numBoatData];
bool BDataValid[numBoatData]; bool BDataValid[numBoatData];
String BDataText[numBoatData];
String BDataUnit[numBoatData];
String BDataFormat[numBoatData];
static bool isInitialized = false; // Flag to indicate that page is initialized static bool isInitialized = false; // Flag to indicate that page is initialized
static bool wndDataValid = false; // Flag to indicate if wind data is valid static bool wndDataValid = false; // Flag to indicate if wind data is valid
@@ -208,7 +206,6 @@ public:
static size_t lastIdx; // Last index of TWD history buffer static size_t lastIdx; // Last index of TWD history buffer
static size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added static size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added
static int oldDataIntv; // remember recent user selection of data interval static int oldDataIntv; // remember recent user selection of data interval
static bool oldShowTruW; // remember recent user selection of wind data type
static int wndCenter; // chart wind center value position static int wndCenter; // chart wind center value position
static int wndLeft; // chart wind left value position static int wndLeft; // chart wind left value position
@@ -225,6 +222,7 @@ public:
static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line
logger->logDebug(GwLog::LOG, "Display PageWindPlot"); logger->logDebug(GwLog::LOG, "Display PageWindPlot");
ulong timer = millis();
if (!isInitialized) { if (!isInitialized) {
width = epd->width(); width = epd->width();
@@ -234,18 +232,9 @@ public:
numNoData = 0; numNoData = 0;
bufStart = 0; bufStart = 0;
oldDataIntv = 0; oldDataIntv = 0;
oldShowTruW = false; // we want to initialize wind buffers at 1st time routine runs
wdHstry = pageData.boatHstry.twdHstry;
bufSize = wdHstry->getCapacity();
wsHstry = pageData.boatHstry.twsHstry;
bufSize = wsHstry->getCapacity();
wdHstry->getMetaData(wdName, wdFormat, updFreq, wdLowest, wdHighest);
wsHstry->getMetaData(wsName, wsFormat, updFreq, wdLowest, wdHighest);
wsValue = 0; wsValue = 0;
wsBVal->setFormat(wsHstry->getFormat());
numAddedBufVals, currIdx, lastIdx = 0; numAddedBufVals, currIdx, lastIdx = 0;
lastAddedIdx = wdHstry->getLastIdx(); wndCenter = INT_MAX;
wndCenter = INT_MIN;
midWndDir = 0; midWndDir = 0;
diffRng = dfltRng; diffRng = dfltRng;
chrtRng = dfltRng; chrtRng = dfltRng;
@@ -256,14 +245,7 @@ public:
// read boat data values; TWD only for validation test, TWS for display of current value // read boat data values; TWD only for validation test, TWS for display of current value
for (int i = 0; i < numBoatData; i++) { for (int i = 0; i < numBoatData; i++) {
bvalue = pageData.values[i]; bvalue = pageData.values[i];
BDataName[i] = xdrDelete(bvalue->getName());
BDataName[i] = BDataName[i].substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue, logger); // Check if boat data value is to be calibrated
BDataValue[i] = bvalue->value; // Value as double in SI unit
BDataValid[i] = bvalue->valid; BDataValid[i] = bvalue->valid;
BDataText[i] = commonData->fmt->formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
BDataUnit[i] = commonData->fmt->formatValue(bvalue, *commonData).unit;
BDataFormat[i] = bvalue->getFormat(); // Unit of value
} }
// Optical warning by limit violation (unused) // Optical warning by limit violation (unused)
@@ -274,16 +256,18 @@ public:
if (showTruW != oldShowTruW) { if (showTruW != oldShowTruW) {
if (showTruW) { if (showTruW) {
wdHstry = pageData.boatHstry.twdHstry; wdHstry = pageData.boatHstry->hstryBufList.twdHstry;
wsHstry = pageData.boatHstry.twsHstry; wsHstry = pageData.boatHstry->hstryBufList.twsHstry;
} else { } else {
wdHstry = pageData.boatHstry.awdHstry; wdHstry = pageData.boatHstry->hstryBufList.awdHstry;
wsHstry = pageData.boatHstry.awsHstry; wsHstry = pageData.boatHstry->hstryBufList.awsHstry;
} }
wdHstry->getMetaData(wdName, wdFormat, updFreq, wdLowest, wdHighest); wdHstry->getMetaData(wdName, wdFormat);
wsHstry->getMetaData(wsName, wsFormat, updFreq, wdLowest, wdHighest); wsHstry->getMetaData(wsName, wsFormat);
wdMAX_VAL = wdHstry->getMaxVal();
bufSize = wdHstry->getCapacity(); bufSize = wdHstry->getCapacity();
wsBVal->setFormat(wsHstry->getFormat()); wsBVal->setFormat(wsHstry->getFormat());
lastAddedIdx = wdHstry->getLastIdx();
oldShowTruW = showTruW; oldShowTruW = showTruW;
} }
@@ -306,19 +290,19 @@ public:
bufStart = max(0, bufStart - numAddedBufVals); bufStart = max(0, bufStart - numAddedBufVals);
} }
} }
logger->logDebug(GwLog::DEBUG, "PageWindPlot Dataset: count: %d, xWD: %.0f, xWS: %.1f, xWD_valid? %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d, wind source: %s", logger->logDebug(GwLog::DEBUG, "PageWindPlot Dataset: count: %d, xWD: %.1f, xWS: %.2f, xWD_valid? %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d, wind source: %s",
count, wdHstry->getLast() / 1000.0 * radToDeg, wsHstry->getLast() / 10.0 * 1.94384, BDataValid[0], intvBufSize, numWndVals, bufStart, numAddedBufVals, wdHstry->getLastIdx(), count, wdHstry->getLast() / 1000.0 * radToDeg, wsHstry->getLast() / 1000.0 * 1.94384, BDataValid[0], intvBufSize, numWndVals, bufStart, numAddedBufVals, wdHstry->getLastIdx(),
showTruW ? "True" : "App"); showTruW ? "True" : "App");
// Set wndCenter from 1st real buffer value // Set wndCenter from 1st real buffer value
if (wndCenter == INT_MIN || (wndCenter == 0 && count == 1)) { if (wndCenter == INT_MAX || (wndCenter == 0 && count == 1)) {
wndCenter = getCntr(*wdHstry, numWndVals); wndCenter = getCntr(*wdHstry, numWndVals);
logger->logDebug(GwLog::DEBUG, "PageWindPlot Range Init: count: %d, xWD: %.0f, wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", count, wdHstry->getLast() / 1000.0 * radToDeg, logger->logDebug(GwLog::DEBUG, "PageWindPlot Range Init: count: %d, xWD: %.1f, wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", count, wdHstry->getLast() / 1000.0 * radToDeg,
wndCenter, diffRng, chrtRng, wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 1000.0 * radToDeg); wndCenter, diffRng, chrtRng, wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 1000.0 * radToDeg);
} else { } else {
// check and adjust range between left, center, and right chart limit // check and adjust range between left, center, and right chart limit
diffRng = getRng(*wdHstry, wndCenter, numWndVals); diffRng = getRng(*wdHstry, wndCenter, numWndVals);
diffRng = (diffRng == INT16_MIN ? 0 : diffRng); diffRng = (diffRng == wdMAX_VAL ? 0 : diffRng);
if (diffRng > chrtRng) { if (diffRng > chrtRng) {
chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value
} else if (diffRng + 10 < chrtRng) { // Reduce chart range for higher resolution if possible } else if (diffRng + 10 < chrtRng) { // Reduce chart range for higher resolution if possible
@@ -366,11 +350,11 @@ public:
epd->drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // <degree> symbol epd->drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // <degree> symbol
epd->drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // <degree> symbol epd->drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // <degree> symbol
if (wdHstry->getMax() == wdHstry->getMinVal()) { if (wdHstry->getMax() == wdMAX_VAL) {
// only <INT16_MIN> values in buffer -> no valid wind data available // only <MAX_VAL> values in buffer -> no valid wind data available
wndDataValid = false; wndDataValid = false;
} else if (!BDataValid[0] && !simulation) { } else if (!BDataValid[0] && !simulation) {
// currently no valid TWD data available and no simulation mode // currently no valid xWD data available and no simulation mode
numNoData++; numNoData++;
wndDataValid = true; wndDataValid = true;
if (numNoData > 3) { if (numNoData > 3) {
@@ -386,8 +370,8 @@ public:
if (wndDataValid) { if (wndDataValid) {
for (int i = 0; i < (numWndVals / dataIntv); i++) { for (int i = 0; i < (numWndVals / dataIntv); i++) {
chrtVal = static_cast<int>(wdHstry->get(bufStart + (i * dataIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer chrtVal = static_cast<int>(wdHstry->get(bufStart + (i * dataIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer
if (chrtVal == INT16_MIN) { if (chrtVal == wdMAX_VAL) {
chrtPrevVal = INT16_MIN; chrtPrevVal = wdMAX_VAL;
} else { } else {
chrtVal = static_cast<int>((chrtVal / 1000.0 * radToDeg) + 0.5); // Convert to degrees and round chrtVal = static_cast<int>((chrtVal / 1000.0 * radToDeg) + 0.5); // Convert to degrees and round
x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; x = ((chrtVal - wndLeft + 360) % 360) * chrtScl;
@@ -396,7 +380,7 @@ public:
if (i >= (numWndVals / dataIntv) - 1) // log chart data of 1 line (adjust for test purposes) if (i >= (numWndVals / dataIntv) - 1) // log chart data of 1 line (adjust for test purposes)
logger->logDebug(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d, count: %d, linesToShow: %d", i, chrtVal, bufStart, count, (numWndVals / dataIntv)); logger->logDebug(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d, count: %d, linesToShow: %d", i, chrtVal, bufStart, count, (numWndVals / dataIntv));
if ((i == 0) || (chrtPrevVal == INT16_MIN)) { if ((i == 0) || (chrtPrevVal == wdMAX_VAL)) {
// just a dot for 1st chart point or after some invalid values // just a dot for 1st chart point or after some invalid values
prevX = x; prevX = x;
prevY = y; prevY = y;
@@ -457,13 +441,15 @@ public:
lastZone = currentZone; lastZone = currentZone;
wsValue = wsHstry->getLast(); wsValue = wsHstry->getLast();
wsBVal->value = wsValue; // temp variable to retreive data unit from OBP60Formater wsBVal->value = wsValue / 1000.0; // temp variable to retreive data unit from OBP60Formater
wsBVal->valid = (static_cast<int16_t>(wsValue) != wsHstry->getMinVal()); wsBVal->valid = (static_cast<uint16_t>(wsValue) != wsHstry->getMinVal());
String swsValue = commonData->fmt->formatValue(wsBVal, *commonData).svalue; // value (string)
wsUnit = commonData->fmt->formatValue(wsBVal, *commonData).unit; // Unit of value wsUnit = commonData->fmt->formatValue(wsBVal, *commonData).unit; // Unit of value
epd->fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value epd->fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value
epd->setFont(&DSEG7Classic_BoldItalic16pt7b); epd->setFont(&DSEG7Classic_BoldItalic16pt7b);
epd->setCursor(xPosTws, yPosTws); epd->setCursor(xPosTws, yPosTws);
if (!wsBVal->valid) { epd->print(swsValue); // Value
/* if (!wsBVal->valid) {
epd->print("--.-"); epd->print("--.-");
} else { } else {
wsValue = wsValue / 10.0 * 1.94384; // Wind speed value in knots wsValue = wsValue / 10.0 * 1.94384; // Wind speed value in knots
@@ -472,7 +458,7 @@ public:
} else { } else {
epd->printf("%4.1f", wsValue); // Value, round to 1 decimal epd->printf("%4.1f", wsValue); // Value, round to 1 decimal
} }
} } */
epd->setFont(&Ubuntu_Bold12pt8b); epd->setFont(&Ubuntu_Bold12pt8b);
epd->setCursor(xPosTws + 82, yPosTws - 14); epd->setCursor(xPosTws + 82, yPosTws - 14);
epd->print(wsName); // Name epd->print(wsName); // Name
@@ -507,6 +493,7 @@ public:
epd->printf("%3d", chrtLbl); // Wind value label epd->printf("%3d", chrtLbl); // Wind value label
} }
logger->logDebug(GwLog::DEBUG, "PageWindPlot time: %ld", millis() - timer);
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
}; };
@@ -525,7 +512,7 @@ PageDescription registerPageWindPlot(
"WindPlot", // Page name "WindPlot", // 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
{ "TWD", "TWS", "AWD", "AWS" }, // Bus values we need in the page { "TWD", "AWD"}, // Bus values we need in the page
true // Show display header on/off true // Show display header on/off
); );

View File

@@ -52,18 +52,6 @@ public:
static FormattedData bvf_tws_old; static FormattedData bvf_tws_old;
static FormattedData bvf_dbt_old; static FormattedData bvf_dbt_old;
static FormattedData bvf_stw_old; static FormattedData bvf_stw_old;
/* static String svalue1old = "";
static String unit1old = "";
static String svalue2old = "";
static String unit2old = "";
static String svalue3old = "";
static String unit3old = "";
static String svalue4old = "";
static String unit4old = "";
static String svalue5old = "";
static String unit5old = "";
static String svalue6old = "";
static String unit6old = ""; */
// Get boat value for AWA // Get boat value for AWA
GwApi::BoatValue *bv_awa = pageData.values[0]; // First element in list GwApi::BoatValue *bv_awa = pageData.values[0]; // First element in list
@@ -247,7 +235,7 @@ public:
epd->getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string epd->getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
epd->setCursor(x-w/2, y+h/2); epd->setCursor(x-w/2, y+h/2);
if (i % 30 == 0) { if (i % 30 == 0) {
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b); // TODO move out of loop
epd->print(ii); epd->print(ii);
} }
@@ -276,7 +264,7 @@ public:
// Draw wind pointer // Draw wind pointer
float startwidth = 8; // Start width of pointer float startwidth = 8; // Start width of pointer
if (bv_aws->valid|| holdvalues || simulation) { if (bv_aws->valid || holdvalues || simulation) {
float sinx = sin(bv_awa->value); // Wind direction float sinx = sin(bv_awa->value); // Wind direction
float cosx = cos(bv_awa->value); float cosx = cos(bv_awa->value);
// Normal pointer // Normal pointer
@@ -306,7 +294,7 @@ public:
// ********************************************************************* // *********************************************************************
// Show values DBT // Show value DBT
epd->setFont(&DSEG7Classic_BoldItalic16pt7b); epd->setFont(&DSEG7Classic_BoldItalic16pt7b);
epd->setCursor(160, 200); epd->setCursor(160, 200);
epd->print(holdvalues ? bvf_dbt_old.value : bvf_dbt.value); epd->print(holdvalues ? bvf_dbt_old.value : bvf_dbt.value);
@@ -315,7 +303,7 @@ public:
epd->print(" "); epd->print(" ");
epd->print(holdvalues ? bvf_dbt_old.unit : bvf_dbt.unit); epd->print(holdvalues ? bvf_dbt_old.unit : bvf_dbt.unit);
// Show values STW // Show value STW
epd->setFont(&DSEG7Classic_BoldItalic16pt7b); epd->setFont(&DSEG7Classic_BoldItalic16pt7b);
epd->setCursor(160, 130); epd->setCursor(160, 130);
epd->print(holdvalues ? bvf_stw_old.value : bvf_stw.value); epd->print(holdvalues ? bvf_stw_old.value : bvf_stw.value);
@@ -326,6 +314,11 @@ public:
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
void leavePage(PageData &pageData) {
logger->logDebug(GwLog::LOG, "Leaving PageWindRose");
}
}; };
static Page *createPage(CommonData &common){ static Page *createPage(CommonData &common){

View File

@@ -6,7 +6,6 @@
#include <functional> #include <functional>
#include <vector> #include <vector>
#include "LedSpiTask.h" #include "LedSpiTask.h"
#include "OBPRingBuffer.h"
#include "OBPDataOperations.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
@@ -19,7 +18,7 @@ typedef struct{
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;
tBoatHstryData boatHstry; 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)

View File

@@ -1368,6 +1368,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -1679,6 +1680,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -1981,6 +1983,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2274,6 +2277,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2558,6 +2562,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2833,6 +2838,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3099,6 +3105,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3356,6 +3363,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3604,6 +3612,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3843,6 +3852,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",

View File

@@ -1391,6 +1391,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -1722,6 +1723,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2044,6 +2046,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2357,6 +2360,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2661,6 +2665,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2956,6 +2961,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3242,6 +3248,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3519,6 +3526,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3787,6 +3795,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -4046,6 +4055,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",

View File

@@ -0,0 +1,273 @@
const uint8_t Atari6pxBitmaps[] PROGMEM = {
0x00, 0xF0, 0x30, 0xCF, 0x38, 0x80, 0x53, 0xF5, 0x14, 0xFD, 0x40, 0x7E,
0x47, 0x85, 0x13, 0xE1, 0x00, 0xC7, 0x21, 0x00, 0x4E, 0x30, 0x63, 0x26,
0x39, 0xC3, 0x26, 0x40, 0x6F, 0x00, 0x7B, 0x24, 0xC0, 0xCD, 0xA5, 0x80,
0x8B, 0x3E, 0x00, 0x44, 0x10, 0x4F, 0xC4, 0x10, 0x40, 0x6F, 0x00, 0xF8,
0xF0, 0x0C, 0x66, 0x18, 0x82, 0x00, 0x7A, 0x39, 0x58, 0x61, 0xE0, 0x75,
0x50, 0xF8, 0x17, 0xA0, 0x83, 0xF0, 0xF8, 0x17, 0x80, 0x07, 0xE0, 0x39,
0x28, 0xA2, 0xFC, 0x20, 0xFE, 0x0F, 0x80, 0x07, 0xE0, 0x7A, 0x0F, 0xC0,
0x01, 0xE0, 0xF8, 0x44, 0x02, 0x10, 0x7A, 0x17, 0x80, 0x85, 0xE0, 0x7A,
0x17, 0xC0, 0x01, 0xE0, 0xF0, 0xF0, 0xF3, 0x58, 0x1B, 0x30, 0x42, 0x0C,
0xF8, 0x3E, 0xC3, 0x06, 0xC4, 0x60, 0x7A, 0x31, 0x86, 0x00, 0x01, 0x80,
0x7A, 0x19, 0xE6, 0x82, 0x07, 0xC0, 0x7A, 0x1F, 0xE1, 0x86, 0x10, 0xFA,
0x1F, 0xA0, 0x87, 0xE0, 0x7E, 0x08, 0x00, 0x01, 0xF0, 0xFA, 0x18, 0x60,
0x83, 0xE0, 0xFE, 0x0F, 0xA0, 0x83, 0xF0, 0xFE, 0x0F, 0xA0, 0x82, 0x00,
0x7E, 0x08, 0xC1, 0x05, 0xF0, 0x86, 0x1F, 0xE1, 0x86, 0x10, 0xF4, 0x44,
0x4F, 0x04, 0x10, 0x41, 0x85, 0xE0, 0x8C, 0xB9, 0x09, 0x44, 0x84, 0x21,
0x08, 0x7C, 0x83, 0xDE, 0x4C, 0x18, 0x30, 0x40, 0x83, 0xC6, 0x4C, 0x18,
0xF0, 0x40, 0x7A, 0x18, 0x40, 0x01, 0xE0, 0xFA, 0x1F, 0xA0, 0x82, 0x00,
0x7A, 0x18, 0x40, 0x01, 0xE0, 0xC0, 0xFA, 0x1F, 0xA4, 0x92, 0x30, 0x7E,
0x07, 0x80, 0x07, 0xE0, 0xFC, 0x41, 0x04, 0x10, 0x40, 0x86, 0x18, 0x61,
0x85, 0xE0, 0x86, 0x10, 0x00, 0x48, 0x40, 0x83, 0x06, 0x4C, 0x1E, 0xF0,
0x40, 0x85, 0x21, 0x00, 0x4A, 0x10, 0x86, 0x14, 0x80, 0x10, 0x40, 0xFC,
0x21, 0x10, 0x43, 0xF0, 0xFC, 0xCC, 0xCF, 0xC1, 0x81, 0x86, 0x04, 0x10,
0xF3, 0x33, 0x3F, 0x11, 0xEC, 0xC0, 0xFC, 0xD9, 0x80, 0x78, 0x10, 0x7F,
0x7C, 0x83, 0xE8, 0x61, 0x83, 0xE0, 0x7E, 0x08, 0x00, 0x7C, 0x05, 0xF8,
0x61, 0x05, 0xF0, 0x7B, 0xF8, 0x00, 0x78, 0x1A, 0x3E, 0x84, 0x20, 0x7E,
0x17, 0xC1, 0x07, 0xE0, 0x81, 0x33, 0x8C, 0x18, 0x30, 0x40, 0xC4, 0x44,
0xF0, 0x08, 0x42, 0x10, 0x87, 0xC0, 0x82, 0x2F, 0x20, 0x8A, 0x10, 0xC4,
0x44, 0x4F, 0x4B, 0xF9, 0x61, 0x84, 0xFA, 0x18, 0x61, 0x84, 0x7A, 0x18,
0x40, 0x78, 0xFA, 0x18, 0x60, 0xFA, 0x00, 0x7E, 0x18, 0x41, 0x7C, 0x10,
0xF4, 0x61, 0x08, 0x00, 0x7E, 0x00, 0x3F, 0x00, 0x4F, 0x44, 0x41, 0x86,
0x10, 0x41, 0x7C, 0x86, 0x10, 0x12, 0x10, 0x83, 0x26, 0x4C, 0x1E, 0xE0,
0x8B, 0x18, 0x08, 0x80, 0x86, 0x17, 0xC1, 0x07, 0xE0, 0xF8, 0x94, 0x8F,
0x80, 0x76, 0xC6, 0x67, 0xFF, 0xFC, 0xE6, 0x36, 0x6E, 0x41, 0x0B, 0x6F,
0x08, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
0xFC, 0x11, 0xEC, 0x71, 0xFC, 0x11, 0xEC, 0xC4, 0x10, 0x41, 0x00, 0x10,
0x40, 0x33, 0x49, 0xE1, 0x00, 0x08, 0x1B, 0xD8, 0x40, 0x81, 0x00, 0x10,
0xE3, 0x39, 0x01, 0x02, 0x00, 0x7A, 0x5E, 0xC4, 0x11, 0xE0, 0xFF, 0xEF,
0x3C, 0xC6, 0x30, 0xEE, 0x57, 0xA1, 0x87, 0xB0, 0x06, 0x1A, 0x64, 0x86,
0x08, 0x00, 0x7D, 0x06, 0x4C, 0xD8, 0x30, 0x5F, 0x00, 0x18, 0xF9, 0xF7,
0xF0, 0x00, 0x06, 0x00, 0x08, 0x30, 0x9E, 0x7B, 0xE6, 0x00, 0xF1, 0x03,
0xBC, 0x48, 0x81, 0x82, 0x00, 0xF1, 0x02, 0x77, 0xA1, 0x43, 0x85, 0x80,
0x12, 0x24, 0x48, 0x97, 0x3E, 0x70, 0x80, 0x92, 0x4D, 0xD8, 0xFF, 0x1C,
0x73, 0xCF, 0x3F, 0xC0, 0xC4, 0x4F, 0xFF, 0xF0, 0x7C, 0x1F, 0xF0, 0xC3,
0x0F, 0xC0, 0xF8, 0x2F, 0xC3, 0x0C, 0x3F, 0xC0, 0xC3, 0x0C, 0xFF, 0x0C,
0x30, 0xC0, 0xFB, 0x0F, 0xC3, 0x0C, 0x3F, 0xC0, 0xC3, 0x0F, 0xE3, 0xCF,
0x3F, 0xC0, 0xFC, 0x13, 0xCC, 0x30, 0xC3, 0x00, 0x71, 0x47, 0x37, 0xDF,
0x7F, 0xC0, 0xFF, 0x3C, 0x7F, 0x0C, 0x30, 0xC0, 0x78, 0x00, 0x7F, 0x78,
0xF0, 0x80, 0xEF, 0x88, 0x88, 0xF8, 0x0F, 0x1E, 0xFF, 0x84, 0x08, 0x1E,
0x26, 0x00, 0xF3, 0x20, 0xC1, 0x05, 0xF1, 0x40, 0x42, 0x2C, 0x50, 0x40,
0x1E, 0xF0, 0x00, 0x07, 0x24, 0x80, 0x93, 0x80, 0xFC, 0x7F, 0xDD, 0xFC,
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
0xFC, 0x7F, 0xDD, 0xFC };
const GFXglyph Atari6pxGlyphs[] PROGMEM = {
{ 0, 1, 1, 7, 0, 0 }, // 0x20 ' ' U+0020
{ 1, 2, 6, 6, 2, -5 }, // 0x21 '!' U+0021
{ 3, 6, 3, 7, 0, -5 }, // 0x22 '"' U+0022
{ 6, 6, 6, 7, 0, -5 }, // 0x23 '#' U+0023
{ 11, 6, 7, 7, 0, -5 }, // 0x24 '$' U+0024
{ 17, 6, 6, 7, 0, -5 }, // 0x25 '%' U+0025
{ 22, 6, 7, 7, 0, -6 }, // 0x26 '&' U+0026
{ 28, 3, 3, 6, 1, -5 }, // 0x27 ''' U+0027
{ 30, 3, 6, 6, 1, -5 }, // 0x28 '(' U+0028
{ 33, 3, 6, 6, 1, -5 }, // 0x29 ')' U+0029
{ 36, 5, 6, 7, 1, -5 }, // 0x2a '*' U+002A
{ 40, 6, 6, 7, 0, -5 }, // 0x2b '+' U+002B
{ 45, 3, 3, 6, 1, -1 }, // 0x2c ',' U+002C
{ 47, 5, 1, 7, 1, -3 }, // 0x2d '-' U+002D
{ 48, 2, 2, 6, 2, -1 }, // 0x2e '.' U+002E
{ 49, 6, 6, 7, 0, -5 }, // 0x2f '/' U+002F
{ 54, 6, 6, 7, 0, -5 }, // 0x30 '0' U+0030
{ 59, 2, 6, 7, 3, -5 }, // 0x31 '1' U+0031
{ 61, 6, 6, 7, 0, -5 }, // 0x32 '2' U+0032
{ 66, 6, 6, 7, 0, -5 }, // 0x33 '3' U+0033
{ 71, 6, 6, 7, 0, -5 }, // 0x34 '4' U+0034
{ 76, 6, 6, 7, 0, -5 }, // 0x35 '5' U+0035
{ 81, 6, 6, 7, 0, -5 }, // 0x36 '6' U+0036
{ 86, 5, 6, 7, 0, -5 }, // 0x37 '7' U+0037
{ 90, 6, 6, 7, 0, -5 }, // 0x38 '8' U+0038
{ 95, 6, 6, 7, 0, -5 }, // 0x39 '9' U+0039
{ 100, 2, 6, 6, 2, -5 }, // 0x3a ':' U+003A
{ 102, 2, 7, 6, 2, -5 }, // 0x3b ';' U+003B
{ 104, 5, 6, 7, 1, -5 }, // 0x3c '<' U+003C
{ 108, 5, 3, 7, 1, -4 }, // 0x3d '=' U+003D
{ 110, 5, 6, 7, 1, -5 }, // 0x3e '>' U+003E
{ 114, 6, 7, 7, 0, -5 }, // 0x3f '?' U+003F
{ 120, 6, 7, 7, 0, -5 }, // 0x40 '@' U+0040
{ 126, 6, 6, 7, 0, -5 }, // 0x41 'A' U+0041
{ 131, 6, 6, 7, 0, -5 }, // 0x42 'B' U+0042
{ 136, 6, 6, 7, 0, -5 }, // 0x43 'C' U+0043
{ 141, 6, 6, 7, 0, -5 }, // 0x44 'D' U+0044
{ 146, 6, 6, 7, 0, -5 }, // 0x45 'E' U+0045
{ 151, 6, 6, 7, 0, -5 }, // 0x46 'F' U+0046
{ 156, 6, 6, 7, 0, -5 }, // 0x47 'G' U+0047
{ 161, 6, 6, 7, 0, -5 }, // 0x48 'H' U+0048
{ 166, 4, 6, 7, 1, -5 }, // 0x49 'I' U+0049
{ 169, 6, 6, 7, 0, -5 }, // 0x4a 'J' U+004A
{ 174, 5, 6, 7, 1, -5 }, // 0x4b 'K' U+004B
{ 178, 5, 6, 7, 1, -5 }, // 0x4c 'L' U+004C
{ 182, 7, 6, 8, 0, -5 }, // 0x4d 'M' U+004D
{ 188, 7, 6, 8, 0, -5 }, // 0x4e 'N' U+004E
{ 194, 6, 6, 7, 0, -5 }, // 0x4f 'O' U+004F
{ 199, 6, 6, 7, 0, -5 }, // 0x50 'P' U+0050
{ 204, 6, 7, 7, 0, -5 }, // 0x51 'Q' U+0051
{ 210, 6, 6, 7, 0, -5 }, // 0x52 'R' U+0052
{ 215, 6, 6, 7, 0, -5 }, // 0x53 'S' U+0053
{ 220, 6, 6, 7, 0, -5 }, // 0x54 'T' U+0054
{ 225, 6, 6, 7, 0, -5 }, // 0x55 'U' U+0055
{ 230, 6, 6, 7, 0, -5 }, // 0x56 'V' U+0056
{ 235, 7, 6, 8, 0, -5 }, // 0x57 'W' U+0057
{ 241, 6, 6, 7, 0, -5 }, // 0x58 'X' U+0058
{ 246, 6, 6, 7, 0, -5 }, // 0x59 'Y' U+0059
{ 251, 6, 6, 7, 0, -5 }, // 0x5a 'Z' U+005A
{ 256, 4, 6, 7, 1, -5 }, // 0x5b '[' U+005B
{ 259, 6, 6, 7, 0, -5 }, // 0x5c '\' U+005C
{ 264, 4, 6, 7, 2, -5 }, // 0x5d ']' U+005D
{ 267, 6, 3, 7, 0, -5 }, // 0x5e '^' U+005E
{ 270, 6, 1, 7, 0, 0 }, // 0x5f '_' U+005F
{ 271, 3, 3, 6, 1, -5 }, // 0x60 '`' U+0060
{ 273, 6, 5, 7, 0, -4 }, // 0x61 'a' U+0061
{ 277, 6, 6, 7, 0, -5 }, // 0x62 'b' U+0062
{ 282, 6, 5, 7, 0, -4 }, // 0x63 'c' U+0063
{ 286, 6, 6, 7, 0, -5 }, // 0x64 'd' U+0064
{ 291, 6, 5, 7, 0, -4 }, // 0x65 'e' U+0065
{ 295, 5, 6, 7, 1, -5 }, // 0x66 'f' U+0066
{ 299, 6, 6, 7, 0, -4 }, // 0x67 'g' U+0067
{ 304, 7, 6, 8, 0, -5 }, // 0x68 'h' U+0068
{ 310, 4, 5, 7, 1, -4 }, // 0x69 'i' U+0069
{ 313, 5, 7, 7, 0, -5 }, // 0x6a 'j' U+006A
{ 318, 6, 6, 7, 0, -5 }, // 0x6b 'k' U+006B
{ 323, 4, 6, 7, 1, -5 }, // 0x6c 'l' U+006C
{ 326, 6, 5, 7, 0, -4 }, // 0x6d 'm' U+006D
{ 330, 6, 5, 7, 0, -4 }, // 0x6e 'n' U+006E
{ 334, 6, 5, 7, 0, -4 }, // 0x6f 'o' U+006F
{ 338, 6, 6, 7, 0, -4 }, // 0x70 'p' U+0070
{ 343, 6, 6, 7, 0, -4 }, // 0x71 'q' U+0071
{ 348, 5, 5, 7, 1, -4 }, // 0x72 'r' U+0072
{ 352, 5, 5, 7, 1, -4 }, // 0x73 's' U+0073
{ 356, 4, 6, 7, 1, -5 }, // 0x74 't' U+0074
{ 359, 6, 5, 7, 0, -4 }, // 0x75 'u' U+0075
{ 363, 6, 5, 7, 0, -4 }, // 0x76 'v' U+0076
{ 367, 7, 5, 8, 0, -4 }, // 0x77 'w' U+0077
{ 372, 5, 5, 7, 0, -4 }, // 0x78 'x' U+0078
{ 376, 6, 6, 7, 0, -4 }, // 0x79 'y' U+0079
{ 381, 5, 5, 7, 1, -4 }, // 0x7a 'z' U+007A
{ 385, 4, 6, 6, 1, -5 }, // 0x7b '{' U+007B
{ 388, 2, 7, 7, 4, -5 }, // 0x7c '|' U+007C
{ 390, 4, 6, 6, 1, -5 }, // 0x7d '}' U+007D
{ 393, 6, 5, 7, 0, -5 }, // 0x7e '~' U+007E
{ 397, 5, 6, 6, 0, -5 }, // 0x7f 'REPLACEMENT CHARACTER *' U+2370
{ 401, 5, 6, 6, 0, -5 }, // 0x80 'NO-BREAK SPACE' U+00A0
{ 405, 5, 6, 6, 0, -5 }, // 0x81 'INVERTED EXCLAMATION MARK' U+00A1
{ 409, 5, 6, 6, 0, -5 }, // 0x82 'CENT SIGN' U+00A2
{ 413, 5, 6, 6, 0, -5 }, // 0x83 'POUND SIGN' U+00A3
{ 417, 5, 6, 6, 0, -5 }, // 0x84 'CURRENCY SIGN' U+00A4
{ 421, 5, 6, 6, 0, -5 }, // 0x85 'YEN SIGN' U+00A5
{ 425, 5, 6, 6, 0, -5 }, // 0x86 'BROKEN BAR' U+00A6
{ 429, 5, 6, 6, 0, -5 }, // 0x87 'SECTION SIGN' U+00A7
{ 433, 5, 6, 6, 0, -5 }, // 0x88 'DIAERESIS' U+00A8
{ 437, 5, 6, 6, 0, -5 }, // 0x89 'COPYRIGHT SIGN' U+00A9
{ 441, 5, 6, 6, 0, -5 }, // 0x8a 'FEMININE ORDINAL INDICATOR' U+00AA
{ 445, 5, 6, 6, 0, -5 }, // 0x8b 'LEFT-POINTING DOUBLE ANGLE QUOTATION MARK' U+00AB
{ 449, 5, 6, 6, 0, -5 }, // 0x8c 'NOT SIGN' U+00AC
{ 453, 5, 6, 6, 0, -5 }, // 0x8d 'SOFT HYPHEN' U+00AD
{ 457, 5, 6, 6, 0, -5 }, // 0x8e 'REGISTERED SIGN' U+00AE
{ 461, 5, 6, 6, 0, -5 }, // 0x8f 'MACRON' U+00AF
{ 465, 5, 6, 6, 0, -5 }, // 0x90 'DEGREE SIGN' U+00B0
{ 469, 5, 6, 6, 0, -5 }, // 0x91 'PLUS-MINUS SIGN' U+00B1
{ 473, 5, 6, 6, 0, -5 }, // 0x92 'SUPERSCRIPT TWO' U+00B2
{ 477, 5, 6, 6, 0, -5 }, // 0x93 'SUPERSCRIPT THREE' U+00B3
{ 481, 5, 6, 6, 0, -5 }, // 0x94 'ACUTE ACCENT' U+00B4
{ 485, 5, 6, 6, 0, -5 }, // 0x95 'MICRO SIGN' U+00B5
{ 489, 5, 6, 6, 0, -5 }, // 0x96 'PILCROW SIGN' U+00B6
{ 493, 5, 6, 6, 0, -5 }, // 0x97 'MIDDLE DOT' U+00B7
{ 497, 5, 6, 6, 0, -5 }, // 0x98 'CEDILLA' U+00B8
{ 501, 5, 6, 6, 0, -5 }, // 0x99 'SUPERSCRIPT ONE' U+00B9
{ 505, 5, 6, 6, 0, -5 }, // 0x9a 'MASCULINE ORDINAL INDICATOR' U+00BA
{ 509, 5, 6, 6, 0, -5 }, // 0x9b 'RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK' U+00BB
{ 513, 5, 6, 6, 0, -5 }, // 0x9c 'VULGAR FRACTION ONE QUARTER' U+00BC
{ 517, 5, 6, 6, 0, -5 }, // 0x9d 'VULGAR FRACTION ONE HALF' U+00BD
{ 521, 5, 6, 6, 0, -5 }, // 0x9e 'VULGAR FRACTION THREE QUARTERS' U+00BE
{ 525, 5, 6, 6, 0, -5 }, // 0x9f 'INVERTED QUESTION MARK' U+00BF
{ 529, 6, 5, 7, 0, -4 }, // 0xa0 'LATIN CAPITAL LETTER A WITH GRAVE' U+00C0
{ 533, 6, 7, 7, 0, -5 }, // 0xa1 'LATIN CAPITAL LETTER A WITH ACUTE' U+00C1
{ 539, 6, 7, 7, 0, -5 }, // 0xa2 'LATIN CAPITAL LETTER A WITH CIRCUMFLEX' U+00C2
{ 545, 7, 6, 7, 0, -5 }, // 0xa3 'LATIN CAPITAL LETTER A WITH TILDE' U+00C3
{ 551, 7, 6, 7, 0, -5 }, // 0xa4 'LATIN CAPITAL LETTER A WITH DIAERESIS' U+00C4
{ 557, 6, 6, 7, 0, -5 }, // 0xa5 'LATIN CAPITAL LETTER A WITH RING ABOVE' U+00C5
{ 562, 6, 6, 7, 0, -5 }, // 0xa6 'LATIN CAPITAL LETTER AE' U+00C6
{ 567, 6, 6, 7, 0, -5 }, // 0xa7 'LATIN CAPITAL LETTER C WITH CEDILLA' U+00C7
{ 572, 7, 6, 7, 0, -5 }, // 0xa8 'LATIN CAPITAL LETTER E WITH GRAVE' U+00C8
{ 578, 7, 7, 7, 0, -5 }, // 0xa9 'LATIN CAPITAL LETTER E WITH ACUTE' U+00C9
{ 585, 7, 7, 7, 0, -5 }, // 0xaa 'LATIN CAPITAL LETTER E WITH CIRCUMFLEX' U+00CA
{ 592, 6, 7, 7, 0, -5 }, // 0xab 'LATIN CAPITAL LETTER E WITH DIAERESIS' U+00CB
{ 598, 7, 7, 7, 0, -5 }, // 0xac 'LATIN CAPITAL LETTER I WITH GRAVE' U+00CC
{ 605, 7, 7, 7, 0, -5 }, // 0xad 'LATIN CAPITAL LETTER I WITH ACUTE' U+00CD
{ 612, 7, 7, 8, 1, -5 }, // 0xae 'LATIN CAPITAL LETTER I WITH CIRCUMFLEX' U+00CE
{ 619, 3, 7, 6, 1, -5 }, // 0xaf 'LATIN CAPITAL LETTER I WITH DIAERESIS' U+00CF
{ 622, 6, 7, 7, 0, -5 }, // 0xb0 'LATIN CAPITAL LETTER ETH' U+00D0
{ 628, 4, 7, 7, 1, -5 }, // 0xb1 'LATIN CAPITAL LETTER N WITH TILDE' U+00D1
{ 632, 6, 7, 7, 0, -5 }, // 0xb2 'LATIN CAPITAL LETTER O WITH GRAVE' U+00D2
{ 638, 6, 7, 7, 0, -5 }, // 0xb3 'LATIN CAPITAL LETTER O WITH ACUTE' U+00D3
{ 644, 6, 7, 7, 0, -5 }, // 0xb4 'LATIN CAPITAL LETTER O WITH CIRCUMFLEX' U+00D4
{ 650, 6, 7, 7, 0, -5 }, // 0xb5 'LATIN CAPITAL LETTER O WITH TILDE' U+00D5
{ 656, 6, 7, 7, 0, -5 }, // 0xb6 'LATIN CAPITAL LETTER O WITH DIAERESIS' U+00D6
{ 662, 6, 7, 7, 0, -5 }, // 0xb7 'MULTIPLICATION SIGN' U+00D7
{ 668, 6, 7, 7, 0, -5 }, // 0xb8 'LATIN CAPITAL LETTER O WITH STROKE' U+00D8
{ 674, 6, 7, 7, 0, -5 }, // 0xb9 'LATIN CAPITAL LETTER U WITH GRAVE' U+00D9
{ 680, 6, 5, 7, 0, -4 }, // 0xba 'LATIN CAPITAL LETTER U WITH ACUTE' U+00DA
{ 684, 8, 7, 8, 0, -5 }, // 0xbb 'LATIN CAPITAL LETTER U WITH CIRCUMFLEX' U+00DB
{ 691, 7, 7, 8, 1, -5 }, // 0xbc 'LATIN CAPITAL LETTER U WITH DIAERESIS' U+00DC
{ 698, 6, 7, 7, 0, -5 }, // 0xbd 'LATIN CAPITAL LETTER Y WITH ACUTE' U+00DD
{ 704, 7, 7, 7, 0, -5 }, // 0xbe 'LATIN CAPITAL LETTER THORN' U+00DE
{ 711, 6, 6, 7, 0, -5 }, // 0xbf 'LATIN SMALL LETTER SHARP S' U+00DF
{ 716, 5, 6, 6, 0, -5 }, // 0xc0 'LATIN SMALL LETTER A WITH GRAVE' U+00E0
{ 720, 5, 6, 6, 0, -5 }, // 0xc1 'LATIN SMALL LETTER A WITH ACUTE' U+00E1
{ 724, 5, 6, 6, 0, -5 }, // 0xc2 'LATIN SMALL LETTER A WITH CIRCUMFLEX' U+00E2
{ 728, 5, 6, 6, 0, -5 }, // 0xc3 'LATIN SMALL LETTER A WITH TILDE' U+00E3
{ 732, 5, 6, 6, 0, -5 }, // 0xc4 'LATIN SMALL LETTER A WITH DIAERESIS' U+00E4
{ 736, 5, 6, 6, 0, -5 }, // 0xc5 'LATIN SMALL LETTER A WITH RING ABOVE' U+00E5
{ 740, 5, 6, 6, 0, -5 }, // 0xc6 'LATIN SMALL LETTER AE' U+00E6
{ 744, 5, 6, 6, 0, -5 }, // 0xc7 'LATIN SMALL LETTER C WITH CEDILLA' U+00E7
{ 748, 5, 6, 6, 0, -5 }, // 0xc8 'LATIN SMALL LETTER E WITH GRAVE' U+00E8
{ 752, 5, 6, 6, 0, -5 }, // 0xc9 'LATIN SMALL LETTER E WITH ACUTE' U+00E9
{ 756, 5, 6, 6, 0, -5 }, // 0xca 'LATIN SMALL LETTER E WITH CIRCUMFLEX' U+00EA
{ 760, 5, 6, 6, 0, -5 }, // 0xcb 'LATIN SMALL LETTER E WITH DIAERESIS' U+00EB
{ 764, 5, 6, 6, 0, -5 }, // 0xcc 'LATIN SMALL LETTER I WITH GRAVE' U+00EC
{ 768, 5, 6, 6, 0, -5 }, // 0xcd 'LATIN SMALL LETTER I WITH ACUTE' U+00ED
{ 772, 5, 6, 6, 0, -5 }, // 0xce 'LATIN SMALL LETTER I WITH CIRCUMFLEX' U+00EE
{ 776, 5, 6, 6, 0, -5 }, // 0xcf 'LATIN SMALL LETTER I WITH DIAERESIS' U+00EF
{ 780, 5, 6, 6, 0, -5 }, // 0xd0 'LATIN SMALL LETTER ETH' U+00F0
{ 784, 5, 6, 6, 0, -5 }, // 0xd1 'LATIN SMALL LETTER N WITH TILDE' U+00F1
{ 788, 5, 6, 6, 0, -5 }, // 0xd2 'LATIN SMALL LETTER O WITH GRAVE' U+00F2
{ 792, 5, 6, 6, 0, -5 }, // 0xd3 'LATIN SMALL LETTER O WITH ACUTE' U+00F3
{ 796, 5, 6, 6, 0, -5 }, // 0xd4 'LATIN SMALL LETTER O WITH CIRCUMFLEX' U+00F4
{ 800, 5, 6, 6, 0, -5 }, // 0xd5 'LATIN SMALL LETTER O WITH TILDE' U+00F5
{ 804, 5, 6, 6, 0, -5 }, // 0xd6 'LATIN SMALL LETTER O WITH DIAERESIS' U+00F6
{ 808, 5, 6, 6, 0, -5 }, // 0xd7 'DIVISION SIGN' U+00F7
{ 812, 5, 6, 6, 0, -5 }, // 0xd8 'LATIN SMALL LETTER O WITH STROKE' U+00F8
{ 816, 5, 6, 6, 0, -5 }, // 0xd9 'LATIN SMALL LETTER U WITH GRAVE' U+00F9
{ 820, 5, 6, 6, 0, -5 }, // 0xda 'LATIN SMALL LETTER U WITH ACUTE' U+00FA
{ 824, 5, 6, 6, 0, -5 }, // 0xdb 'LATIN SMALL LETTER U WITH CIRCUMFLEX' U+00FB
{ 828, 5, 6, 6, 0, -5 }, // 0xdc 'LATIN SMALL LETTER U WITH DIAERESIS' U+00FC
{ 832, 5, 6, 6, 0, -5 }, // 0xdd 'LATIN SMALL LETTER Y WITH ACUTE' U+00FD
{ 836, 5, 6, 6, 0, -5 }, // 0xde 'LATIN SMALL LETTER THORN' U+00FE
{ 840, 5, 6, 6, 0, -5 } }; // 0xdf 'LATIN SMALL LETTER Y WITH DIAERESIS' U+000FF
const GFXfont Atari6px PROGMEM = {
(uint8_t *)Atari6pxBitmaps,
(GFXglyph *)Atari6pxGlyphs,
0x20, 0xDF, 9 };
// Approx. 2195 bytes

View File

@@ -1,10 +1,12 @@
// Add a new register card in web configuration interface
// This is a Java Script!
(function(){ (function(){
const api=window.esp32nmea2k; const api=window.esp32nmea2k;
if (! api) return; if (! api) return;
const tabName="OBP60"; const tabName="Screen";
api.registerListener((id, data) => { api.registerListener((id, data) => {
// if (!data.testboard) return; //do nothing if we are not active // if (!data.testboard) return; //do nothing if we are not active
let page = api.addTabPage(tabName, "OBP60"); let page = api.addTabPage(tabName, "Screen");
api.addEl('button', '', page, 'Screenshot').addEventListener('click', function (ev) { api.addEl('button', '', page, 'Screenshot').addEventListener('click', function (ev) {
window.open('/api/user/OBP60Task/screenshot', 'screenshot'); window.open('/api/user/OBP60Task/screenshot', 'screenshot');
}) })

View File

@@ -788,3 +788,70 @@ dict =
BMP:Windows bitmap (BMP) BMP:Windows bitmap (BMP)
category = OBP60 Pages category = OBP60 Pages
capabilities = obp60:true capabilities = obp60:true
# WIP
[trackerType]
label = Tracker Type
type = list
default = off
description = Type of tracker to use [OFF|SDCARD|SERVER|HERO]
dict = OFF:No tracker
SDCARD:Log to SD-Card
SERVER:Log to Server
HERO:Connect with Regatta Hero
category = OBP60 Pages
capabilities = obp60:true
[trackerOrganization]
label = Tracker team
type = string
default = demo
description = Tracker organization for login
category = OBP60 Pages
[trackerPasscode]
label = Tracker team
type = password
default = 291758
description = Your tracker team you belong to. E.g. short name of association
category = OBP60 Pages
[trackerTeam]
label = Tracker team
type = string
default = none
description = Your tracker team you belong to. E.g. short name of association
category = OBP60 Pages
[trackerHandicap]
label = Boat handicap
type = number
default = 100
check = checkMinMax
min = 50
max = 1000
description = The handicap value of your boat. E.g. yardstick value
category = OBP60 Pages
[boatName]
label = Boat Name
type = string
default = Unsinkbar II
description = name of your boat
category = OBP60 Pages
[boatClass]
label = Boat Class
type = string
default = One off
description = Class name of your boat if available or "One off"
category = OBP60 Pages
[sailNumber]
label = Sail number
type = string
default = GER 11
description = Identification number on sail
category = OBP60 Pages

View File

@@ -15,7 +15,6 @@
#include "OBP60Extensions.h" // Functions lib for extension board #include "OBP60Extensions.h" // Functions lib for extension board
#include "OBPKeyboardTask.h" // Functions lib for keyboard handling #include "OBPKeyboardTask.h" // Functions lib for keyboard handling
#include "BoatDataCalibration.h" // Functions lib for data instance calibration #include "BoatDataCalibration.h" // Functions lib for data instance calibration
#include "OBPRingBuffer.h" // Functions lib with ring buffer for history storage of some boat data
#include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation #include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation
#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
@@ -142,39 +141,30 @@ bool listTasks(GwLog *logger) {
return false; return false;
} */ } */
class BoatValueList{ bool BoatValueList::addValueToList(GwApi::BoatValue *v){
public: for (int i=0;i<numValues;i++){
static const int MAXVALUES=100; if (allBoatValues[i] == v){
//we create a list containing all our BoatValues //already in list...
//this is the list we later use to let the api fill all the values return true;
//additionally we put the necessary values into the paga data - see below }
GwApi::BoatValue *allBoatValues[MAXVALUES]; }
int numValues=0; if (numValues >= MAXVALUES) return false;
allBoatValues[numValues]=v;
numValues++;
return true;
}
bool addValueToList(GwApi::BoatValue *v){ //helper to ensure that each BoatValue is only queried once
for (int i=0;i<numValues;i++){ GwApi::BoatValue *BoatValueList::findValueOrCreate(String name){
if (allBoatValues[i] == v){ for (int i=0;i<numValues;i++){
//already in list... if (allBoatValues[i]->getName() == name) {
return true; return allBoatValues[i];
}
} }
if (numValues >= MAXVALUES) return false;
allBoatValues[numValues]=v;
numValues++;
return true;
} }
//helper to ensure that each BoatValue is only queried once GwApi::BoatValue *rt=new GwApi::BoatValue(name);
GwApi::BoatValue *findValueOrCreate(String name){ addValueToList(rt);
for (int i=0;i<numValues;i++){ return rt;
if (allBoatValues[i]->getName() == name) { }
return allBoatValues[i];
}
}
GwApi::BoatValue *rt=new GwApi::BoatValue(name);
addValueToList(rt);
return rt;
}
};
//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
@@ -269,6 +259,8 @@ void registerAllPages(GwLog *logger, PageList &list){
list.add(&registerPageAIS); list.add(&registerPageAIS);
extern PageDescription registerPageBarograph; extern PageDescription registerPageBarograph;
list.add(&registerPageBarograph); list.add(&registerPageBarograph);
extern PageDescription registerPageTracker;
list.add(&registerPageTracker);
logger->logDebug(GwLog::LOG,"Memory after registering pages: stack=%d, heap=%d", uxTaskGetStackHighWaterMark(NULL), ESP.getFreeHeap()); logger->logDebug(GwLog::LOG,"Memory after registering pages: stack=%d, heap=%d", uxTaskGetStackHighWaterMark(NULL), ESP.getFreeHeap());
} }
@@ -331,203 +323,6 @@ inline bool underVoltageDetection(float voffset, float vslope) {
return (calVoltage < minVoltage); return (calVoltage < minVoltage);
} }
// Calculate true wind data and add to obp60task boat data list
bool addTrueWind(GwApi* api, BoatValueList* boatValues) {
double awaVal, awsVal, cogVal, stwVal, sogVal, hdtVal, hdmVal, varVal;
double twd, tws, twa;
bool isCalculated = false;
const double DBL_MIN = std::numeric_limits<double>::lowest();
GwApi::BoatValue *twdBVal = boatValues->findValueOrCreate("TWD");
GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate("TWS");
GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA");
GwApi::BoatValue *awaBVal = boatValues->findValueOrCreate("AWA");
GwApi::BoatValue *awsBVal = boatValues->findValueOrCreate("AWS");
GwApi::BoatValue *cogBVal = boatValues->findValueOrCreate("COG");
GwApi::BoatValue *stwBVal = boatValues->findValueOrCreate("STW");
GwApi::BoatValue *sogBVal = boatValues->findValueOrCreate("SOG");
GwApi::BoatValue *hdtBVal = boatValues->findValueOrCreate("HDT");
GwApi::BoatValue *hdmBVal = boatValues->findValueOrCreate("HDM");
GwApi::BoatValue *varBVal = boatValues->findValueOrCreate("VAR");
awaVal = awaBVal->valid ? awaBVal->value : DBL_MIN;
awsVal = awsBVal->valid ? awsBVal->value : DBL_MIN;
cogVal = cogBVal->valid ? cogBVal->value : DBL_MIN;
stwVal = stwBVal->valid ? stwBVal->value : DBL_MIN;
sogVal = sogBVal->valid ? sogBVal->value : DBL_MIN;
hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MIN;
hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MIN;
varVal = varBVal->valid ? varBVal->value : DBL_MIN;
api->getLogger()->logDebug(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 = WindUtils::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;
}
}
api->getLogger()->logDebug(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;
}
// Init history buffers for selected boat data
void initHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryBufList) {
GwApi::BoatValue *calBVal; // temp variable just for data calibration -> we don't want to calibrate the original data here
const double DBL_MIN = std::numeric_limits<double>::lowest();
int hstryUpdFreq = 1000; // Update frequency for history buffers in ms
int hstryMinVal = 0; // Minimum value for these history buffers
int twdHstryMax = 6283; // Max value for wind direction (TWD, AWD) in rad (0...2*PI), shifted by 1000 for 3 decimals
int twsHstryMax = 1000; // Max value for wind speed (TWS, AWS) in m/s, shifted by 10 for 1 decimal
// Initialize history buffers with meta data
hstryBufList.twdHstry->setMetaData("TWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax);
hstryBufList.twsHstry->setMetaData("TWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax);
hstryBufList.awdHstry->setMetaData("AWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax);
hstryBufList.awsHstry->setMetaData("AWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax);
// create boat values for history data types, if they don't exist yet
GwApi::BoatValue *twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName());
GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName());
GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA");
GwApi::BoatValue *awdBVal = boatValues->findValueOrCreate(hstryBufList.awdHstry->getName());
GwApi::BoatValue *awsBVal = boatValues->findValueOrCreate(hstryBufList.awsHstry->getName());
if (!awdBVal->valid) { // AWD usually does not exist
awdBVal->setFormat(hstryBufList.awdHstry->getFormat());
awdBVal->value = DBL_MIN;
}
}
void handleHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryBufList, bool useSimuData) {
// Handle history buffers for TWD, TWS
GwLog *logger = api->getLogger();
int16_t twdHstryMin = hstryBufList.twdHstry->getMinVal();
int16_t twdHstryMax = hstryBufList.twdHstry->getMaxVal();
int16_t twsHstryMin = hstryBufList.twsHstry->getMinVal();
int16_t twsHstryMax = hstryBufList.twsHstry->getMaxVal();
int16_t awdHstryMin = hstryBufList.awdHstry->getMinVal();
int16_t awdHstryMax = hstryBufList.awdHstry->getMaxVal();
int16_t awsHstryMin = hstryBufList.awsHstry->getMinVal();
int16_t awsHstryMax = hstryBufList.awsHstry->getMaxVal();
static int16_t twd, tws = 20; //initial value only relevant if we use simulation data
static double 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
GwApi::BoatValue *twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName());
GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName());
GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA");
GwApi::BoatValue *awdBVal = boatValues->findValueOrCreate(hstryBufList.awdHstry->getName());
GwApi::BoatValue *awsBVal = boatValues->findValueOrCreate(hstryBufList.awsHstry->getName());
GwApi::BoatValue *awaBVal = boatValues->findValueOrCreate("AWA");
GwApi::BoatValue *hdtBVal = boatValues->findValueOrCreate("HDT");
GwApi::BoatValue *hdmBVal = boatValues->findValueOrCreate("HDM");
GwApi::BoatValue *varBVal = boatValues->findValueOrCreate("VAR");
GwApi::BoatValue *cogBVal = boatValues->findValueOrCreate("COG");
GwApi::BoatValue *sogBVal = boatValues->findValueOrCreate("SOG");
api->getLogger()->logDebug(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) {
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 = static_cast<int16_t>(std::round(calBVal->value * 1000));
if (twd >= twdHstryMin && twd <= twdHstryMax) {
hstryBufList.twdHstry->add(twd);
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
twd += random(-20, 20);
twd = WindUtils::to360(twd);
hstryBufList.twdHstry->add(static_cast<int16_t>(DegToRad(twd) * 1000.0));
}
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 = static_cast<int16_t>(std::round(calBVal->value * 10));
if (tws >= twsHstryMin && tws <= twsHstryMax) {
hstryBufList.twsHstry->add(tws);
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
tws += random(-50, 50); // TWS value in m/s; expands to 1 decimal
tws = constrain(tws, 0, 250); // Limit TWS to [0..25] 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 = std::round(calBVal->value * 1000);
if (awd >= awdHstryMin && awd <= awdHstryMax) {
hstryBufList.awdHstry->add(static_cast<int16_t>(awd));
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
awd += random(-20, 20);
awd = WindUtils::to360(awd);
hstryBufList.awdHstry->add(static_cast<int16_t>(DegToRad(awd) * 1000.0));
}
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 = std::round(calBVal->value * 10);
if (aws >= awsHstryMin && aws <= awsHstryMax) {
hstryBufList.awsHstry->add(static_cast<int16_t>(aws));
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
aws += random(-50, 50); // TWS value in m/s; expands to 1 decimal
aws = constrain(aws, 0, 250); // Limit TWS to [0..25] m/s
hstryBufList.awsHstry->add(aws);
}
}
// OBP60 Task // OBP60 Task
//#################################################################################### //####################################################################################
void OBP60Task(GwApi *api){ void OBP60Task(GwApi *api){
@@ -640,16 +435,11 @@ 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(960); // Create ring buffers for history storage of some boat data
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
// Create ring buffers for history storage of some boat data
RingBuffer<int16_t> twdHstry(960); // Circular buffer to store true wind direction values; store 960 TWD values for 16 minutes history
RingBuffer<int16_t> twsHstry(960); // Circular buffer to store true wind speed values (TWS)
RingBuffer<int16_t> awdHstry(960); // Circular buffer to store appearant wind direction values; store 960 AWD values for 16 minutes history
RingBuffer<int16_t> awsHstry(960); // Circular buffer to store appearant xwind speed values (AWS)
tBoatHstryData hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry};
//fill the page data from config //fill the page data from config
numPages=config->getInt(config->visiblePages,1); numPages=config->getInt(config->visiblePages,1);
if (numPages < 1) numPages=1; if (numPages < 1) numPages=1;
@@ -689,10 +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);
} }
if (pages[i].description->pageName == "WindPlot") { // Add boat history data to page parameters
// Add boat history data to page parameters pages[i].parameters.boatHstry = &hstryBufList;
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);
@@ -700,12 +488,12 @@ 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 setting for true wind calculation // Check user settings for true wind calculation
//bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false); bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false);
bool simulation = api->getConfig()->getBool(api->getConfig()->useSimuData, false); bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false);
// Initialize history buffer for certain boat data // Initialize history buffer for certain boat data
//initHstryBuf(api, &boatValues, hstryBufList); 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
@@ -1018,11 +806,11 @@ 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) { if (calcTrueWnds) {
addTrueWind(api, &boatValues); trueWind.addTrueWind(api, &boatValues, logger);
}*/ }
// Handle history buffers for TWD, TWS for wind plot page and other usage // Handle history buffers for TWD, TWS for wind plot page and other usage
//handleHstryBuf(api, &boatValues, hstryBufList, simulation); hstryBufList.handleHstryBuf(useSimuData);
// Clear display // Clear display
// epd->fillRect(0, 0, epd->width(), epd->height(), commonData.bgcolor); // epd->fillRect(0, 0, epd->width(), epd->height(), commonData.bgcolor);

View File

@@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "GwApi.h" #include "GwApi.h"
//we only compile for some boards //we only compile for some boards
@@ -47,4 +48,18 @@
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp40-v1-docu.readthedocs.io/en/latest/"); // Link to help pages DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp40-v1-docu.readthedocs.io/en/latest/"); // Link to help pages
#endif #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

View File

@@ -0,0 +1,17 @@
import subprocess
# Import("env")
def get_firmware_specifier_build_flag():
#ret = subprocess.run(["git", "describe"], stdout=subprocess.PIPE, text=True) #Uses only annotated tags
ret = subprocess.run(["git", "describe", "--tags"], stdout=subprocess.PIPE, text=True) #Uses any tags
build_version = ret.stdout.strip()
build_flag = "-D AUTO_VERSION=\\\"" + build_version + "\\\""
print ("Firmware Revision: " + build_version)
return (build_flag)
#env.Append(
# BUILD_FLAGS=[get_firmware_specifier_build_flag()]
#)
get_firmware_specifier_build_flag()

View File

@@ -0,0 +1,61 @@
#!/usr/bin/python
#
# Convert a Gimp-created XBM file to bitmap useable by drawBitmap()
#
import os
import sys
import re
from PIL import Image
if len(sys.argv) < 2:
print("Usage: xbmconvert.py <filename>")
sys.exit(1)
xbmfilename = sys.argv[1]
if not os.path.isfile(xbmfilename):
print(f"The file '{xbmfilename}' does not exists.")
sys.exit(1)
im = Image.open(xbmfilename)
imname = "image"
with open(xbmfilename, 'r') as fh:
pattern = r'static\s+unsigned\s+char\s+(\w+)_bits$$$$'
for line in fh:
match = re.search(pattern, line)
if match:
imname = match.group(1)
break
bytecount = int(im.width * im.height / 8)
print(f"#ifndef _{imname.upper()}_H_")
print(f"#define _{imname.upper()}_H_ 1\n")
print(f"#define {imname}_width {im.width}")
print(f"#define {imname}_height {im.height}")
print(f"const unsigned char {imname}_bits[{bytecount}] PROGMEM = {{")
n = 0
print(" ", end='')
f = im.tobytes()
switched_bytes = bytearray()
for i in range(0, len(f), 2):
# Switch LSB and MSB
switched_bytes.append(f[i + 1]) # Append MSB
switched_bytes.append(f[i]) # Append LSB
#for b in im.tobytes():
for b in switched_bytes:
#b2 = 0
#for i in range(8):
# # b2 |= ((b >> i) & 1) << (7 - i)
# b2 <<= 1
# b2 |= b & 1
# b >>= 1
n += 1
print(f"0x{b:02x}", end='')
if n < bytecount:
print(', ', end='')
if n % 12 == 0:
print("\n ", end='')
print("};\n\n#endif")