added calibration to buffer; separated buffer and wind code in opb60task; prepared simulation; getMin/Max fix for ringbuffer for invalid data; fix for chart center; cleanup code

This commit is contained in:
Ulrich Meine 2025-07-25 08:42:43 +02:00
parent c48c6a2e48
commit fe2223839f
9 changed files with 367 additions and 324 deletions

View File

@ -1,45 +1,51 @@
#include "OBPDataOperations.h" #include "OBPDataOperations.h"
void WindUtils::to2PI(double* a) double WindUtils::to2PI(double a)
{ {
while (*a < 0) { a = fmod(a, 2 * M_PI);
*a += 2 * M_PI; if (a < 0.0) {
a += 2 * M_PI;
} }
*a = fmod(*a, 2 * M_PI); return a;
} }
void WindUtils::toPI(double* a) double WindUtils::toPI(double a)
{ {
*a += M_PI; a += M_PI;
to2PI(a); a = to2PI(a);
*a -= M_PI; a -= M_PI;
return a;
} }
void WindUtils::to360(double* a) double WindUtils::to360(double a)
{ {
while (*a < 0) { a = fmod(a, 360);
*a += 360; if (a < 0.0) {
a += 360;
} }
*a = fmod(*a, 360); return a;
} }
void WindUtils::to180(double* a) double WindUtils::to180(double a)
{ {
*a += 180; a += 180;
to360(a); a = to360(a);
*a -= 180; a -= 180;
return a;
} }
void WindUtils::toCart(const double* phi, const double* r, double* x, double* y) void WindUtils::toCart(const double* phi, const double* r, double* x, double* y)
{ {
*x = *r * sin(radians(*phi)); *x = *r * sin(*phi);
*y = *r * cos(radians(*phi)); *y = *r * cos(*phi);
} }
void WindUtils::toPol(const double* x, const double* y, double* phi, double* r) void WindUtils::toPol(const double* x, const double* y, double* phi, double* r)
{ {
*phi = 90 - degrees(atan2(*y, *x)); *phi = (M_PI / 2) - atan2(*y, *x);
to360(phi); *phi = to2PI(*phi);
*r = sqrt(*x * *x + *y * *y); *r = sqrt(*x * *x + *y * *y);
} }
@ -57,80 +63,96 @@ void WindUtils::addPolar(const double* phi1, const double* r1,
void WindUtils::calcTwdSA(const double* AWA, const double* AWS, void WindUtils::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* TWD, double* TWS, double* TWA)
{ {
double AWD = *AWA + *HDT; double awd = *AWA + *HDT;
awd = to2PI(awd);
double stw = -*STW; double stw = -*STW;
Serial.println("calcTwdSA: AWA: " + String(*AWA) + ", AWS: " + String(*AWS) + ", CTW: " + String(*CTW) + ", STW: " + String(*STW) + ", HDT: " + String(*HDT)); // 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 to 0-360° // Normalize TWD and TWA to 0-360°
while (*TWD < 0) *TWD = to2PI(*TWD);
*TWD += 360; *TWA = toPI(*TWD - *HDT);
while (*TWD >= 360) // Serial.println("calcTwdSA: TWD: " + String(*TWD) + ", TWS: " + String(*TWS));
*TWD -= 360;
Serial.println("calcTwdSA: TWD: " + String(*TWD) + ", TWS: " + String(*TWS));
} }
bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal, bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal,
const double* cogVal, const double* stwVal, const double* hdtVal, const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
const double* hdmVal, double* twdVal, double* twsVal) const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal)
{ {
double hdt, ctw; double stw, hdt, ctw;
double hdmVar = 3.0; // Magnetic declination, can be set from config if needed double twd, tws, twa;
double twd, tws; static const double DBL_MIN = std::numeric_limits<double>::lowest();
if (*hdtVal == __DBL_MIN__) { if (*hdtVal != DBL_MIN) {
if (*hdmVal != __DBL_MIN__) { hdt = *hdtVal; // Use HDT if available
hdt = *hdmVal + hdmVar; // Use corrected HDM if HDT is not available } else {
if (*hdmVal != DBL_MIN && *varVal != DBL_MIN) {
hdt = *hdmVal + *varVal; // Use corrected HDM if HDT is not available
hdt = to2PI(hdt);
} else if (*cogVal != DBL_MIN) {
hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available
} else { } else {
return false; // Cannot calculate without valid HDT or HDM return false; // Cannot calculate without valid HDT or HDM
} }
} }
ctw = *hdtVal + ((*cogVal - *hdtVal) / 2); // Estimate CTW from COG
if ((*awaVal == __DBL_MIN__) || (*awsVal == __DBL_MIN__) || (*cogVal == __DBL_MIN__) || (*stwVal == __DBL_MIN__)) { if (*cogVal != DBL_MIN) {
ctw = *cogVal; // Use COG as CTW if available
// ctw = *cogVal + ((*cogVal - hdt) / 2); // Estimate CTW from COG
} else {
ctw = hdt; // 2nd approximation for CTW;
return false;
}
if (*stwVal != DBL_MIN) {
stw = *stwVal; // Use STW if available
} else if (*sogVal != DBL_MIN) {
stw = *sogVal;
} else {
// If STW and SOG are not available, we cannot calculate true wind
return false;
}
if ((*awaVal == DBL_MIN) || (*awsVal == DBL_MIN) || (*cogVal == DBL_MIN) || (*stwVal == DBL_MIN)) {
// Cannot calculate true wind without valid AWA, AWS, COG, or STW // Cannot calculate true wind without valid AWA, AWS, COG, or STW
return false; return false;
} else { } else {
calcTwdSA(awaVal, awsVal, cogVal, stwVal, hdtVal, &twd, &tws); calcTwdSA(awaVal, awsVal, &ctw, stwVal, &hdt, &twd, &tws, &twa);
*twdVal = twd; *twdVal = twd;
*twsVal = tws; *twsVal = tws;
*twaVal = twa;
return true; return true;
} }
} }
/* void HstryBuf::fillWndBufSimData(tBoatHstryData& hstryBufs)
// make function available in Python for testing // Fill most part of TWD and TWS history buffer with simulated data
static PyObject* true_wind(PyObject* self, PyObject* args) { {
double AWA,AWS,CTW,STW,HDT,TWS,TWD; double value = 20.0;
if (!PyArg_ParseTuple(args, "ddddd", &AWA, &AWS, &CTW, &STW, &HDT)) { int16_t value2 = 0;
return NULL; for (int i = 0; i < 900; i++) {
value += random(-20, 20);
value = WindUtils::to360(value);
value2 = static_cast<int16_t>(value * DEG_TO_RAD * 1000);
hstryBufs.twdHstry->add(value2);
} }
calc_true_wind(&AWA, &AWS, &CTW, &STW, &HDT, &TWD, &TWS);
PyObject* twd = PyFloat_FromDouble(TWD);
PyObject* tws = PyFloat_FromDouble(TWS);
PyObject* tw = PyTuple_Pack(2,twd,tws);
return tw;
} }
static PyMethodDef methods[] = { /* double genTwdSimDat()
{"true_wind", true_wind, METH_VARARGS, NULL}, {
{NULL, NULL, 0, NULL} simTwd += random(-20, 20);
}; if (simTwd < 0.0)
simTwd += 360.0;
if (simTwd >= 360.0)
simTwd -= 360.0;
static struct PyModuleDef module = { int16_t z = static_cast<int16_t>(DegToRad(simTwd) * 1000.0);
PyModuleDef_HEAD_INIT, pageData.boatHstry.twdHstry->add(z); // Fill the buffer with some test data
"truewind", // Module name
NULL, // Optional docstring
-1,
methods
};
PyMODINIT_FUNC PyInit_truewind(void) { simTws += random(-200, 150) / 10.0; // TWS value in knots
return PyModule_Create(&module); simTws = constrain(simTws, 0.0f, 50.0f); // Ensure TWS is between 0 and 50 knots
twsValue = simTws;
}*/ }*/

View File

@ -1,48 +1,36 @@
#pragma once #pragma once
// #include <Python.h>
#include "GwApi.h" #include "GwApi.h"
#include "OBPRingBuffer.h"
#include <Arduino.h> #include <Arduino.h>
#include <math.h> #include <math.h>
// #define radians(a) (a*0.017453292519943295) typedef struct {
// #define degrees(a) (a*57.29577951308232) RingBuffer<int16_t>* twdHstry;
RingBuffer<int16_t>* twsHstry;
} tBoatHstryData; // Holds pointers to all history buffers for boat data
class HstryBuf {
public:
void fillWndBufSimData(tBoatHstryData& hstryBufs); // Fill most part of the TWD and TWS history buffer with simulated data
};
class WindUtils { class WindUtils {
public: public:
static void to360(double* a); static double to2PI(double a);
static void to180(double* a); static double toPI(double a);
static void to2PI(double* a); static double to360(double a);
static void toPI(double* a); static double to180(double a);
static void toCart(const double* phi, const double* r, double* x, double* y); static 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); static void toPol(const double* x, const double* y, double* phi, double* r);
static void addPolar(const double* phi1, const double* r1, static 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 bool calcTrueWind(const double* awaVal, const double* awsVal,
const double* cogVal, const double* stwVal, const double* hdtVal,
const double* hdmVal, double* twdVal, double* twsVal);
static void calcTwdSA(const double* AWA, const double* AWS, static 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* TWD, double* TWS, double* TWA);
static bool calcTrueWind(const double* awaVal, const double* awsVal,
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal);
}; };
/*
// make function available in Python for testing
static PyObject* true_wind(PyObject* self, PyObject* args);
static PyMethodDef methods[] = {
{"true_wind", true_wind, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT,
"truewind", // Module name
NULL, // Optional docstring
-1,
methods
};
PyMODINIT_FUNC PyInit_truewind(void) {
return PyModule_Create(&module);
} */

View File

@ -31,6 +31,7 @@ public:
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
String getName() const; // Get buffer name
void add(const T& value); // Add a new value to buffer void add(const T& value); // Add a new value to buffer
T get(size_t index) const; // Get value at specific position (0-based index from oldest to newest) T get(size_t index) const; // Get value at specific position (0-based index from oldest to newest)
T getFirst() const; // Get the first (oldest) value in buffer T getFirst() const; // Get the first (oldest) value in buffer

View File

@ -57,6 +57,13 @@ bool RingBuffer<T>::getMetaData(String& name, String& format, int& updateFrequen
return true; return true;
} }
// Get buffer name
template <typename T>
String RingBuffer<T>::getName() const
{
return dataName;
}
// Add a new value to buffer // Add a new value to buffer
template <typename T> template <typename T>
void RingBuffer<T>::add(const T& value) void RingBuffer<T>::add(const T& value)
@ -129,9 +136,9 @@ T RingBuffer<T>::getMin() const
return MIN_VAL; return MIN_VAL;
} }
T minVal = getFirst(); T minVal = MAX_VAL;
T value; T value;
for (size_t i = 1; 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 != MIN_VAL) {
minVal = value; minVal = value;
@ -150,7 +157,7 @@ T RingBuffer<T>::getMin(size_t amount) const
if (amount > count) if (amount > count)
amount = count; amount = count;
T minVal = getLast(); T minVal = MAX_VAL;
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);
@ -169,9 +176,9 @@ T RingBuffer<T>::getMax() const
return MIN_VAL; return MIN_VAL;
} }
T maxVal = getFirst(); T maxVal = MIN_VAL;
T value; T value;
for (size_t i = 1; 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 != MIN_VAL) {
maxVal = value; maxVal = value;
@ -190,7 +197,7 @@ T RingBuffer<T>::getMax(size_t amount) const
if (amount > count) if (amount > count)
amount = count; amount = count;
T maxVal = getLast(); T maxVal = MIN_VAL;
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);
@ -328,7 +335,7 @@ bool RingBuffer<T>::isFull() const
return is_Full; return is_Full;
} }
// Get lowest possible value for buffer; used for initialized buffer data // Get lowest possible value for buffer; used for non-set buffer data
template <typename T> template <typename T>
T RingBuffer<T>::getMinVal() const T RingBuffer<T>::getMinVal() const
{ {

View File

@ -4,20 +4,17 @@
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "OBPRingBuffer.h" #include "OBPRingBuffer.h"
#include "Pagedata.h" #include "Pagedata.h"
#include <N2kMessages.h> // just for RadToDeg function
#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
// #define radians(a) (a * 0.017453292519943295)
// #define degrees(a) (a * 57.29577951308232)
// Get maximum difference of last <amount> of TWD ringbuffer values to center chart // Get maximum difference of last <amount> of TWD ringbuffer values to center chart
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();
size_t count = windDirHstry.getCurrentSize(); size_t count = windDirHstry.getCurrentSize();
// size_t capacity = windDirHstry.getCapacity(); // size_t capacity = windDirHstry.getCapacity();
// size_t last = windDirHstry.getLastIdx(); // size_t last = windDirHstry.getLastIdx();
if (windDirHstry.isEmpty() || amount <= 0) { if (windDirHstry.isEmpty() || amount <= 0) {
return minVal; return minVal;
@ -30,7 +27,7 @@ int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount)
int maxRng = minVal; int maxRng = minVal;
// Start from the newest value (last) and go backwards x times // Start from the newest value (last) and go backwards x times
for (size_t i = 0; i < amount; i++) { for (size_t i = 0; i < amount; i++) {
// value = windDirHstry.get(((last - i) % capacity + capacity) % capacity); // value = windDirHstry.get(((last - i) % capacity + capacity) % capacity);
value = windDirHstry.get(count - 1 - i); value = windDirHstry.get(count - 1 - i);
if (value == minVal) { if (value == minVal) {
@ -49,43 +46,6 @@ int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount)
return maxRng; return maxRng;
} }
void fillSimData(PageData& pageData)
// Fill part of the TWD history buffer with simulated data
{
int value = 20;
int16_t value2 = 0;
for (int i = 0; i < 900; i++) {
value += random(-20, 20);
if (value < 0)
value += 360;
if (value >= 360)
value -= 360;
value2 = static_cast<int16_t>(DegToRad(value) * 1000.0);
pageData.boatHstry.twdHstry->add(value2);
}
}
void fillTstBuffer(PageData& pageData)
{
float value = 0;
int value2 = 0;
for (int i = 0; i < 60; i++) {
pageData.boatHstry.twdHstry->add(-10); // -> irregular data
}
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 20; j++) {
value += 10;
value2 = static_cast<int>(DegToRad(value) * 1000.0);
pageData.boatHstry.twdHstry->add(value2);
}
for (int j = 0; j < 20; j++) {
value -= 10;
value2 = static_cast<int>(DegToRad(value) * 1000.0);
pageData.boatHstry.twdHstry->add(value2);
}
}
}
// **************************************************************** // ****************************************************************
class PageWindPlot : public Page { class PageWindPlot : public Page {
@ -105,7 +65,7 @@ public:
virtual void setupKeys() virtual void setupKeys()
{ {
Page::setupKeys(); Page::setupKeys();
commonData->keydata[0].label = "MODE"; // commonData->keydata[0].label = "MODE";
commonData->keydata[1].label = "INTV"; commonData->keydata[1].label = "INTV";
commonData->keydata[4].label = "TWS"; commonData->keydata[4].label = "TWS";
} }
@ -113,7 +73,7 @@ public:
// Key functions // Key functions
virtual int handleKey(int key) virtual int handleKey(int key)
{ {
// Set chart mode TWD | TWS // Set chart mode TWD | TWS -> to be implemented
if (key == 1) { if (key == 1) {
if (chrtMode == 'D') { if (chrtMode == 'D') {
chrtMode = 'S'; chrtMode = 'S';
@ -159,9 +119,10 @@ public:
GwLog* logger = commonData->logger; GwLog* logger = commonData->logger;
float twsValue; // TWS value in chart area float twsValue; // TWS value in chart area
String twdName, twdUnit; // TWD name and unit static String twdName, twdUnit; // TWD name and unit
int updFreq; // Update frequency for TWD static int updFreq; // Update frequency for TWD
int16_t twdLowest, twdHighest; // TWD range static int16_t twdLowest, twdHighest; // TWD range
// static int16_t twdBufMinVal; // lowest possible twd buffer value; used for non-set data
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
@ -191,6 +152,7 @@ public:
static int chrtRng; // Range of wind values from mid wind value to min/max wind value in degrees static int chrtRng; // Range of wind values from mid wind value to min/max wind value in degrees
int diffRng; // Difference between mid and current wind value int diffRng; // Difference between mid and current wind value
static const int dfltRng = 40; // Default range for chart static const int dfltRng = 40; // Default range for chart
int midWndDir; // New value for wndCenter after chart start / shift
static int simTwd; // Simulation value for TWD static int simTwd; // Simulation value for TWD
static float simTws; // Simulation value for TWS static float simTws; // Simulation value for TWS
@ -201,7 +163,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
LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); LOG_DEBUG(GwLog::LOG, "Display page WindPlot");
unsigned long start = millis(); unsigned long WndPlotStart = millis();
// Get config data // Get config data
simulation = config->getBool(config->useSimuData); simulation = config->getBool(config->useSimuData);
@ -210,11 +172,6 @@ public:
String backlightMode = config->getString(config->backlight); String backlightMode = config->getString(config->backlight);
if (!isInitialized) { if (!isInitialized) {
if (simulation) {
fillSimData(pageData); // Fill the buffer with some test data
}
width = getdisplay().width(); width = getdisplay().width();
height = getdisplay().height(); height = getdisplay().height();
xCenter = width / 2; xCenter = width / 2;
@ -230,8 +187,33 @@ public:
lastAddedIdx = pageData.boatHstry.twdHstry->getLastIdx(); lastAddedIdx = pageData.boatHstry.twdHstry->getLastIdx();
pageData.boatHstry.twdHstry->getMetaData(twdName, twdUnit, updFreq, twdLowest, twdHighest); pageData.boatHstry.twdHstry->getMetaData(twdName, twdUnit, updFreq, twdLowest, twdHighest);
wndCenter = INT_MIN; wndCenter = INT_MIN;
midWndDir = 0;
diffRng = dfltRng;
chrtRng = dfltRng;
isInitialized = true; // Set flag to indicate that page is now initialized isInitialized = true; // Set flag to indicate that page is now initialized
LOG_DEBUG(GwLog::ERROR, "PageWindPlot Start1: lastAddedIdx: %d, simTwd: %.1f, isInitialized: %d, SimData: %d", lastAddedIdx, simTwd / 1000 + radToDeg, isInitialized, simulation); }
const int numBoatData = 2;
GwApi::BoatValue* bvalue;
String DataName[numBoatData];
double DataValue[numBoatData];
bool DataValid[numBoatData];
String DataText[numBoatData];
String DataUnit[numBoatData];
String DataFormat[numBoatData];
// read boat data values; TWD only for validation test, TWS for display of current value
for (int i = 0; i < numBoatData; i++) {
bvalue = pageData.values[i];
DataName[i] = xdrDelete(bvalue->getName());
DataName[i] = DataName[i].substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue, logger); // Check if boat data value is to be calibrated
DataValue[i] = bvalue->value; // Value as double in SI unit
DataValid[i] = bvalue->valid;
DataText[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
DataUnit[i] = formatValue(bvalue, *commonData).unit;
DataFormat[i] = bvalue->getFormat(); // Unit of value
} }
// Optical warning by limit violation (unused) // Optical warning by limit violation (unused)
@ -240,30 +222,11 @@ public:
setFlashLED(false); setFlashLED(false);
} }
if (simulation) {
simTwd += random(-20, 20);
if (simTwd < 0.0)
simTwd += 360.0;
if (simTwd >= 360.0)
simTwd -= 360.0;
int16_t z = static_cast<int16_t>(DegToRad(simTwd) * 1000.0);
LOG_DEBUG(GwLog::ERROR, "PageWindPlot Simulation: getLast TWD: %.0f, lastIdx: %d", pageData.boatHstry.twdHstry->getLast() / 1000.0 * RAD_TO_DEG, pageData.boatHstry.twdHstry->getLastIdx());
pageData.boatHstry.twdHstry->add(z); // Fill the buffer with some test data
LOG_DEBUG(GwLog::ERROR, "PageWindPlot Simulation: getAdded TWD: %.0f, lastIdx: %d", pageData.boatHstry.twdHstry->getLast() / 1000.0 * RAD_TO_DEG, pageData.boatHstry.twdHstry->getLastIdx());
simTws += random(-200, 150) / 10.0; // TWS value in knots
simTws = constrain(simTws, 0.0f, 50.0f); // Ensure TWS is between 0 and 50 knots
twsValue = simTws;
} else {
twsValue = pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384; // TWS value in knots
}
// Identify buffer size and buffer start position for chart // Identify buffer size and buffer start position for chart
count = pageData.boatHstry.twdHstry->getCurrentSize(); count = pageData.boatHstry.twdHstry->getCurrentSize();
currIdx = pageData.boatHstry.twdHstry->getLastIdx(); currIdx = pageData.boatHstry.twdHstry->getLastIdx();
numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display
if (dataIntv != oldDataIntv) { if (dataIntv != oldDataIntv || count == 1) {
// new data interval selected by user // new data interval selected by user
intvBufSize = cHeight * dataIntv; intvBufSize = cHeight * dataIntv;
numWndVals = min(count, (cHeight - 60) * dataIntv); numWndVals = min(count, (cHeight - 60) * dataIntv);
@ -277,16 +240,21 @@ public:
bufStart = max(0, bufStart - numAddedBufVals); bufStart = max(0, bufStart - numAddedBufVals);
} }
} }
LOG_DEBUG(GwLog::ERROR, "PageWindPlot Dataset: TWD: %.0f, TWS: %.1f, DBT: %.1f, count: %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d", LOG_DEBUG(GwLog::ERROR, "PageWindPlot Dataset: count: %d, TWD: %.0f, TWS: %.1f, TWD_valid? %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d, old: %d, act: %d",
pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg, pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384, pageData.boatHstry.dbtHstry->getLast() / 10.0, count, pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg, pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384, DataValid[0],
count, intvBufSize, numWndVals, bufStart, numAddedBufVals, pageData.boatHstry.twdHstry->getLastIdx()); intvBufSize, numWndVals, bufStart, numAddedBufVals, pageData.boatHstry.twdHstry->getLastIdx(), oldDataIntv, dataIntv);
// initialize chart range values // Set wndCenter from 1st real buffer value
if (wndCenter == INT_MIN) { if (wndCenter == INT_MIN || (wndCenter == 0 && count == 1)) {
wndCenter = max(0, int(pageData.boatHstry.twdHstry->get(numWndVals - intvBufSize) / 1000.0 * radToDeg)); // get 1st value of current data interval midWndDir = pageData.boatHstry.twdHstry->getMid(numWndVals);
wndCenter = (int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10) % 360; // Set new center value; round to nearest 10 degree value; 360° -> 0° if (midWndDir != INT16_MIN) {
diffRng = dfltRng; midWndDir = midWndDir / 1000.0 * radToDeg;
chrtRng = dfltRng; wndCenter = int((midWndDir + (midWndDir >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value
} else {
wndCenter = 0;
}
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Range Init: count: %d, TWD: %.0f, wndCenter: %d, diffRng: %d, chrtRng: %d", count, pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg,
wndCenter, diffRng, chrtRng);
} 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(*pageData.boatHstry.twdHstry, wndCenter, numWndVals); diffRng = getRng(*pageData.boatHstry.twdHstry, wndCenter, numWndVals);
@ -304,8 +272,6 @@ public:
wndRight = (chrtRng < 180 ? wndCenter + chrtRng : wndCenter + chrtRng - 1); wndRight = (chrtRng < 180 ? wndCenter + chrtRng : wndCenter + chrtRng - 1);
if (wndRight >= 360) if (wndRight >= 360)
wndRight -= 360; wndRight -= 360;
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FirstVal: %f, LastVal: %d, count: %d, diffRng: %d, chartRng: %d, Center: %d, scale: %f", pageData.boatHstry.twdHstry->getFirst() / 1000.0 * radToDeg,
pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg, count, diffRng, chrtRng, wndCenter, chrtScl);
// Draw page // Draw page
//*********************************************************************** //***********************************************************************
@ -322,11 +288,9 @@ public:
char sWndLbl[4]; // char buffer for Wind angle label char sWndLbl[4]; // char buffer for Wind angle label
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(xCenter - 88, yOffset - 3); getdisplay().setCursor(xCenter - 88, yOffset - 3);
getdisplay().print("TWD"); // Wind name getdisplay().print("TWD"); // Wind data name
// getdisplay().setCursor(xCenter - 20, yOffset - 3);
snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter);
drawTextCenter(xCenter, yOffset - 11, sWndLbl); drawTextCenter(xCenter, yOffset - 11, sWndLbl);
// getdisplay().print(sWndLbl); // Wind center value
getdisplay().drawCircle(xCenter + 25, yOffset - 17, 2, commonData->fgcolor); // <degree> symbol getdisplay().drawCircle(xCenter + 25, yOffset - 17, 2, commonData->fgcolor); // <degree> symbol
getdisplay().drawCircle(xCenter + 25, yOffset - 17, 3, commonData->fgcolor); // <degree> symbol getdisplay().drawCircle(xCenter + 25, yOffset - 17, 3, commonData->fgcolor); // <degree> symbol
getdisplay().setCursor(1, yOffset - 3); getdisplay().setCursor(1, yOffset - 3);
@ -343,7 +307,19 @@ public:
if (pageData.boatHstry.twdHstry->getMax() == pageData.boatHstry.twdHstry->getMinVal()) { if (pageData.boatHstry.twdHstry->getMax() == pageData.boatHstry.twdHstry->getMinVal()) {
// only <INT16_MIN> values in buffer -> no valid wind data available // only <INT16_MIN> values in buffer -> no valid wind data available
wndDataValid = false; wndDataValid = false;
} else if (!DataValid[0]) {
// currently no valid TWD data available
numNoData++;
wndDataValid = true;
if (numNoData > 3) {
// If more than 4 invalid values in a row, send message
wndDataValid = false;
getdisplay().setFont(&Ubuntu_Bold10pt8b);
getdisplay().fillRect(xCenter - 66, height / 2 - 20, 146, 24, commonData->bgcolor); // Clear area for TWS value
drawTextCenter(xCenter, height / 2 - 10, "No sensor data");
}
} else { } else {
numNoData = 0; // reset data error counter
wndDataValid = true; // At least some wind data available wndDataValid = true; // At least some wind data available
} }
// Draw wind values in chart // Draw wind values in chart
@ -361,17 +337,17 @@ public:
} }
if (numNoData > 4) { if (numNoData > 4) {
// If more than 4 invalid values in a row, send message // If more than 4 invalid values in a row, send message
getdisplay().setFont(&Ubuntu_Bold10pt7b); getdisplay().setFont(&Ubuntu_Bold10pt8b);
getdisplay().fillRect(xCenter - 66, height / 2 - 20, 146, 24, commonData->bgcolor); // Clear area for TWS value getdisplay().fillRect(xCenter - 66, height / 2 - 20, 146, 24, commonData->bgcolor); // Clear area for TWS value
drawTextCenter(xCenter, height / 2 - 10, "No sensor data"); drawTextCenter(xCenter, height / 2 - 10, "No sensor data");
} */ } */
} else { } else {
chrtVal = (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;
y = yOffset + cHeight - i; // Position in chart area y = yOffset + cHeight - i; // Position in chart area
if (i >= (numWndVals / dataIntv) - 10) if (i >= (numWndVals / dataIntv) - 10)
LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d count: %d, linesToShow: %d", i, chrtVal, bufStart, count, (numWndVals / dataIntv)); LOG_DEBUG(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 == INT16_MIN)) {
// just a dot for 1st chart point or after some invalid values // just a dot for 1st chart point or after some invalid values
@ -404,15 +380,15 @@ public:
int minWndDir = pageData.boatHstry.twdHstry->getMin(numWndVals) / 1000.0 * radToDeg; int minWndDir = pageData.boatHstry.twdHstry->getMin(numWndVals) / 1000.0 * radToDeg;
int maxWndDir = pageData.boatHstry.twdHstry->getMax(numWndVals) / 1000.0 * radToDeg; int maxWndDir = pageData.boatHstry.twdHstry->getMax(numWndVals) / 1000.0 * radToDeg;
LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: Minimum: %d, Maximum: %d, OldwndCenter: %d", minWndDir, maxWndDir, wndCenter); LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: Minimum: %d, Maximum: %d, OldwndCenter: %d", minWndDir, maxWndDir, wndCenter);
if ((minWndDir > wndCenter) || (maxWndDir < wndCenter)) { if ((minWndDir + 540 >= wndCenter + 540) || (maxWndDir + 540 <= wndCenter + 540)) {
// Check if all wind value are left or right of center value -> optimize chart range // Check if all wind value are left or right of center value -> optimize chart range
int midWndDir = pageData.boatHstry.twdHstry->getMid(numWndVals) / 1000.0 * radToDeg; midWndDir = pageData.boatHstry.twdHstry->getMid(numWndVals) / 1000.0 * radToDeg;
if (midWndDir != INT16_MIN) { if (midWndDir != INT16_MIN) {
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
} }
} }
LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: cHeight: %d, bufStart: %d, numWndVals: %d, wndCenter: %d", cHeight, bufStart, numWndVals, wndCenter); LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: cHeight: %d, bufStart: %d, numWndVals: %d, wndCenter: %d", cHeight, bufStart, numWndVals, wndCenter);
break; break;
} }
} }
@ -433,6 +409,8 @@ public:
int xPosTws; int xPosTws;
static const int yPosTws = yOffset + 40; static const int yPosTws = yOffset + 40;
twsValue = pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384; // TWS value in knots
xPosTws = flipTws ? 20 : width - 138; xPosTws = flipTws ? 20 : width - 138;
currentZone = (y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146) ? 1 : 0; // Define current zone for TWS value currentZone = (y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146) ? 1 : 0; // Define current zone for TWS value
if (currentZone != lastZone) { if (currentZone != lastZone) {
@ -447,21 +425,24 @@ public:
getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
getdisplay().setCursor(xPosTws, yPosTws); getdisplay().setCursor(xPosTws, yPosTws);
if (twsValue < 0 || twsValue >= 100) { if (!DataValid[1]) {
getdisplay().print("--.-"); getdisplay().print("--.-");
} else { } else {
if (twsValue < 10.0) { if (DataValue[1] < 9.95) {
getdisplay().printf("!%3.1f", twsValue); // Value getdisplay().printf("!%3.1f", DataValue[1] + 0.05); // Value, round to 1 decimal
} else { } else {
getdisplay().printf("%4.1f", twsValue); // Value} getdisplay().printf("%4.1f", DataValue[1] + 0.05); // Value, round to 1 decimal
} }
} }
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(xPosTws + 82, yPosTws - 14); getdisplay().setCursor(xPosTws + 82, yPosTws - 14);
getdisplay().print("TWS"); // Name // getdisplay().print("TWS"); // Name
getdisplay().print(DataName[1]); // Name
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(xPosTws + 78, yPosTws + 1); // getdisplay().setCursor(xPosTws + 78, yPosTws + 1);
getdisplay().printf(" kn"); // Unit getdisplay().setCursor(xPosTws + 82, yPosTws + 1);
// getdisplay().printf(" kn"); // Unit
getdisplay().print(DataUnit[1]); // Unit
} }
// chart Y axis labels; print at last to overwrite potential chart lines in label area // chart Y axis labels; print at last to overwrite potential chart lines in label area
@ -471,7 +452,7 @@ public:
for (int i = 1; i <= 3; i++) { for (int i = 1; i <= 3; i++) {
yPos = yOffset + (i * 60); yPos = yOffset + (i * 60);
getdisplay().fillRect(0, yPos, width, 1, commonData->fgcolor); getdisplay().fillRect(0, yPos, width, 1, commonData->fgcolor);
getdisplay().fillRect(0, yPos - 8, 26, 16, commonData->bgcolor); // Clear small area to remove potential chart lines getdisplay().fillRect(0, yPos - 8, 24, 16, commonData->bgcolor); // Clear small area to remove potential chart lines
getdisplay().setCursor(1, yPos + 4); getdisplay().setCursor(1, yPos + 4);
if (count >= intvBufSize) { if (count >= intvBufSize) {
// Calculate minute value for label // Calculate minute value for label
@ -483,8 +464,8 @@ public:
getdisplay().printf("%3d", chrtLbl); // Wind value label getdisplay().printf("%3d", chrtLbl); // Wind value label
} }
unsigned long finish = millis() - start; unsigned long finish = millis() - WndPlotStart;
// LOG_DEBUG(GwLog::ERROR, "PageWindPlot Time: %lu", finish); LOG_DEBUG(GwLog::ERROR, "PageWindPlot Time: %lu", finish);
// Update display // Update display
getdisplay().nextPage(); // Partial update (fast) getdisplay().nextPage(); // Partial update (fast)
}; };
@ -505,8 +486,8 @@ 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", "TWA", "TWS", "HDM", "AWA", "AWS", "STW", "COG", "SOG" }, // Bus values we need in the page { "TWD", "TWS" }, // Bus values we need in the page
{ }, // Bus values we need in the page // {}, // Bus values we need in the page
true // Show display header on/off true // Show display header on/off
); );

View File

@ -5,15 +5,10 @@
#include <vector> #include <vector>
#include "LedSpiTask.h" #include "LedSpiTask.h"
#include "OBPRingBuffer.h" #include "OBPRingBuffer.h"
#include "OBPDataOperations.h"
#define MAX_PAGE_NUMBER 10 // Max number of pages for show data #define MAX_PAGE_NUMBER 10 // Max number of pages for show data
typedef struct{
RingBuffer<int16_t>* twdHstry;
RingBuffer<int16_t>* twsHstry;
RingBuffer<int16_t>* dbtHstry;
} tBoatHstryData;
typedef std::vector<GwApi::BoatValue *> ValueList; typedef std::vector<GwApi::BoatValue *> ValueList;
typedef struct{ typedef struct{

View File

@ -219,6 +219,17 @@
"obp60":"true" "obp60":"true"
} }
}, },
{
"name": "calcTrueWnds",
"label": "Calculate True Wind",
"type": "boolean",
"default": "false",
"description": "If not available, calculate true wind data from appearant wind and other boat data",
"category": "OBP60 Settings",
"capabilities": {
"obp60": "true"
}
},
{ {
"name": "lengthFormat", "name": "lengthFormat",
"label": "Length Format", "label": "Length Format",

View File

@ -219,6 +219,17 @@
"obp40": "true" "obp40": "true"
} }
}, },
{
"name": "calcTrueWnds",
"label": "Calculate True Wind",
"type": "boolean",
"default": "false",
"description": "If not available, calculate true wind data from appearant wind and other boat data",
"category": "OBP40 Settings",
"capabilities": {
"obp40": "true"
}
},
{ {
"name": "lengthFormat", "name": "lengthFormat",
"label": "Length Format", "label": "Length Format",

View File

@ -376,6 +376,112 @@ void underVoltageDetection(GwApi *api, CommonData &common){
} }
} }
//bool addTrueWind(GwApi* api, BoatValueList* boatValues, double *twd, double *tws, double *twa) {
bool addTrueWind(GwApi* api, BoatValueList* boatValues) {
// Calculate true wind data and add to obp60task boat data list
double awaVal, awsVal, cogVal, stwVal, sogVal, hdtVal, hdmVal, varVal;
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::ERROR,"obp60task addTrueWind: AWA: %.1f, AWS: %.1f, COG: %.1f, STW: %.1f, 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, 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, &twdBVal->value, &twsBVal->value, &twaBVal->value);
twdBVal->valid = isCalculated;
twsBVal->valid = isCalculated;
twaBVal->valid = isCalculated;
api->getLogger()->logDebug(GwLog::ERROR,"obp60task calcTrueWind: TWD_Valid? %d, TWD=%.1f, TWS=%.1f, TWA=%.1f, isCalculated? %d", twdBVal->valid, twdBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852,
twaBVal->value * RAD_TO_DEG, isCalculated);
return isCalculated;
}
void initHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryBufList) {
// Init history buffers for TWD, TWS
GwApi::BoatValue *calBVal; // temp variable just for data calibration -> we don't want to calibrate the original data here
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) in rad (0...2*PI), shifted by 1000 for 3 decimals
int twsHstryMax = 1000; // Max value for wind speed (TWS) 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);
GwApi::BoatValue *twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName());
GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName());
GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA");
}
void handleHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryBufList) {
// 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 twdBuf, twsBuf;
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");
api->getLogger()->logDebug(GwLog::ERROR,"obp60task handleHstryBuf: twdBVal: %f, twsBVal: %f, twaBVal: %f, TWD_isValid? %d", twdBVal->value * RAD_TO_DEG,
twsBVal->value * 3.6 / 1.852, twaBVal->value * RAD_TO_DEG, twdBVal->valid);
calBVal = new GwApi::BoatValue("TWD"); // temporary solution for calibration of history buffer values
calBVal->setFormat(twdBVal->getFormat());
if (twdBVal->valid) {
calBVal->value = twdBVal->value;
calBVal->valid = twdBVal->valid;
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
twdBuf = static_cast<int16_t>(std::round(calBVal->value * 1000));
if (twdBuf >= twdHstryMin && twdBuf <= twdHstryMax) {
hstryBufList.twdHstry->add(twdBuf);
}
}
delete calBVal;
calBVal = nullptr;
calBVal = new GwApi::BoatValue("TWS"); // temporary solution for calibration of history buffer values
calBVal->setFormat(twsBVal->getFormat());
if (twsBVal->valid) {
calBVal->value = twsBVal->value;
calBVal->valid = twsBVal->valid;
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
twsBuf = static_cast<int16_t>(std::round(calBVal->value * 10));
if (twsBuf >= twsHstryMin && twsBuf <= twsHstryMax) {
hstryBufList.twsHstry->add(twsBuf);
}
}
delete calBVal;
calBVal = nullptr;
}
// OBP60 Task // OBP60 Task
//#################################################################################### //####################################################################################
void OBP60Task(GwApi *api){ void OBP60Task(GwApi *api){
@ -392,11 +498,6 @@ void OBP60Task(GwApi *api){
commonData.logger=logger; commonData.logger=logger;
commonData.config=config; commonData.config=config;
// Create ring buffers for history storage of some boat data
RingBuffer<int16_t> twdHstry(960); // Circular buffer to store wind direction values; store 960 TWD values for 16 minutes history
RingBuffer<int16_t> twsHstry(960); // Circular buffer to store wind speed values (TWS)
RingBuffer<int16_t> dbtHstry(960); // Circular buffer to store water depth values (DBT)
#ifdef HARDWARE_V21 #ifdef HARDWARE_V21
// Keyboard coordinates for page footer // Keyboard coordinates for page footer
initKeys(commonData); initKeys(commonData);
@ -494,6 +595,11 @@ void OBP60Task(GwApi *api){
//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 wind direction values; store 960 TWD values for 16 minutes history
RingBuffer<int16_t> twsHstry(960); // Circular buffer to store wind speed values (TWS)
tBoatHstryData hstryBufList = {&twdHstry, &twsHstry};
//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;
@ -532,8 +638,10 @@ void OBP60Task(GwApi *api){
LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i); LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i);
pages[i].parameters.values.push_back(value); pages[i].parameters.values.push_back(value);
} }
// Add boat history data to page parameters if (pages[i].description->pageName == "WindPlot") {
pages[i].parameters.boatHstry = {&twdHstry, &twsHstry, &dbtHstry}; // Add boat history data to page parameters
pages[i].parameters.boatHstry = hstryBufList;
}
} }
// add out of band system page (always available) // add out of band system page (always available)
Page *syspage = allPages.pages[0]->creator(commonData); Page *syspage = allPages.pages[0]->creator(commonData);
@ -541,32 +649,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);
// List of boat values for history storage // Check user setting for true wind calculation
GwApi::BoatValue *twdBVal=new GwApi::BoatValue(GwBoatData::_TWD); bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false);
GwApi::BoatValue *twsBVal=new GwApi::BoatValue(GwBoatData::_TWS); // bool simulation = api->getConfig()->getBool(api->getConfig()->useSimuData, false);
GwApi::BoatValue *dbtBVal=new GwApi::BoatValue(GwBoatData::_DBT);
GwApi::BoatValue *hstryValList[]={twdBVal, twsBVal, dbtBVal};
api->getBoatDataValues(3, hstryValList);
// Boat data history buffer initialization
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) in rad (0...2*PI), shifted by 1000 for 3 decimals
int twsHstryMax = 1000; // Max value for wind speed (TWS) in m/s, shifted by 10 for 1 decimal
int dbtHstryMax = 3276; // Max value for depth in m (=327), shifted by 10 for 1 decimal
// Initialize history buffers with meta data
twdHstry.setMetaData(twdBVal->getName(), twdBVal->getFormat(), hstryUpdFreq, hstryMinVal, twdHstryMax);
twsHstry.setMetaData(twsBVal->getName(), twsBVal->getFormat(), hstryUpdFreq, hstryMinVal, twsHstryMax);
dbtHstry.setMetaData(dbtBVal->getName(), dbtBVal->getFormat(), hstryUpdFreq, hstryMinVal, dbtHstryMax);
bool simulation = api->getConfig()->getBool(api->getConfig()->useSimuData, false);
// List of boat values for true winds calculation // Initialize history buffer for certain boat data
GwApi::BoatValue *awaBVal=new GwApi::BoatValue(GwBoatData::_AWA); initHstryBuf(api, &boatValues, hstryBufList);
GwApi::BoatValue *awsBVal=new GwApi::BoatValue(GwBoatData::_AWS);
GwApi::BoatValue *cogBVal=new GwApi::BoatValue(GwBoatData::_COG);
GwApi::BoatValue *stwBVal=new GwApi::BoatValue(GwBoatData::_STW);
GwApi::BoatValue *hdtBVal=new GwApi::BoatValue(GwBoatData::_HDT);
GwApi::BoatValue *hdmBVal=new GwApi::BoatValue(GwBoatData::_HDM);
GwApi::BoatValue *WndCalcValList[]={awaBVal, awsBVal, cogBVal, stwBVal, hdtBVal, hdmBVal};
// 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
@ -623,18 +711,6 @@ void OBP60Task(GwApi *api){
GwApi::BoatValue *lon = boatValues.findValueOrCreate("LON"); // Load GpsLongitude GwApi::BoatValue *lon = boatValues.findValueOrCreate("LON"); // Load GpsLongitude
GwApi::BoatValue *hdop = boatValues.findValueOrCreate("HDOP"); // Load GpsHDOP GwApi::BoatValue *hdop = boatValues.findValueOrCreate("HDOP"); // Load GpsHDOP
/* // Boat values for wind conversion for main loop; will be calculated in case values are not available by sensors
GwApi::BoatValue *twd = boatValues.findValueOrCreate("TWD"); // True Wind Direction
GwApi::BoatValue *tws = boatValues.findValueOrCreate("TWS"); // True Wind Speed
GwApi::BoatValue *twa = boatValues.findValueOrCreate("TWA"); // True Wind Angle
GwApi::BoatValue *awaBVal = boatValues.findValueOrCreate("AWA"); // Apparent Wind Angle
GwApi::BoatValue *awsBVal = boatValues.findValueOrCreate("AWS"); // Apparent Wind Speed
GwApi::BoatValue *cogBVal = boatValues.findValueOrCreate("COG"); // Course Over Ground
GwApi::BoatValue *stwBVal = boatValues.findValueOrCreate("STW"); // Speed Through Water
GwApi::BoatValue *hdtBVal = boatValues.findValueOrCreate("HDT"); // Heading True
GwApi::BoatValue *ctwBVal = boatValues.findValueOrCreate("CTW"); // Course Through Water
GwApi::BoatValue *hdmBVal = boatValues.findValueOrCreate("HDM"); // Heading Magnetic
*/
LOG_DEBUG(GwLog::LOG,"obp60task: start mainloop"); LOG_DEBUG(GwLog::LOG,"obp60task: start mainloop");
commonData.time = boatValues.findValueOrCreate("GPST"); // Load GpsTime commonData.time = boatValues.findValueOrCreate("GPST"); // Load GpsTime
@ -648,7 +724,6 @@ void OBP60Task(GwApi *api){
long starttime3 = millis(); // Display update all 1s long starttime3 = millis(); // Display update all 1s
long starttime4 = millis(); // Delayed display update after 4s when select a new page long starttime4 = millis(); // Delayed display update after 4s when select a new page
long starttime5 = millis(); // Calculate sunrise and sunset all 1s long starttime5 = millis(); // Calculate sunrise and sunset all 1s
unsigned long starttime10 = millis(); // Get history TWD, TWS, DBT data and calculate true winds each 1s
pages[pageNumber].page->setupKeys(); // Initialize keys for first page pages[pageNumber].page->setupKeys(); // Initialize keys for first page
@ -838,60 +913,6 @@ void OBP60Task(GwApi *api){
} }
} }
// Read TWD, TWS, DBT data from boatData each 1000ms for history and windplot display
if((millis() > starttime10 + 1000) && !simulation) {
double twdVal, twsVal, dbtVal;
double awaVal, awsVal, cogVal, stwVal, hdtVal, hdmVal;
double DBL_MIN = std::numeric_limits<double>::lowest();
starttime10 = millis();
LOG_DEBUG(GwLog::DEBUG,"History buffer write cycle");
api->getBoatDataValues(3, hstryValList);
if (!twdBVal->valid || !twsBVal->valid) {
api->getBoatDataValues(6, WndCalcValList); // Get all values for true wind calculation
awaVal = awaBVal->valid ? awaBVal->value * RAD_TO_DEG : __DBL_MIN__;
awsVal = awsBVal->valid ? awsBVal->value : __DBL_MIN__;
cogVal = cogBVal->valid ? cogBVal->value * RAD_TO_DEG : __DBL_MIN__;
stwVal = stwBVal->valid ? stwBVal->value : __DBL_MIN__;
hdtVal = hdtBVal->valid ? hdtBVal->value * RAD_TO_DEG : __DBL_MIN__;
hdmVal = hdmBVal->valid ? hdmBVal->value * RAD_TO_DEG : __DBL_MIN__;
LOG_DEBUG(GwLog::ERROR,"obp60task - Read data: AWA: %f, AWS: %f, COG: %f, STW: %f, HDT: %f, TWD=%f, TWS=%f", awaBVal->value, awsBVal->value,
cogBVal->value, stwBVal->value, hdtBVal->value, twdBVal->value, twsBVal->value);
bool isCalculated = WindUtils::calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &cogVal, &hdmVal, &twdVal, &twsVal); // Calculate true wind if TWD not available
// bool isCalculated = WindUtils::calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &hdtVal, &hdmVal, &twdVal, &twsVal); // Calculate true wind if TWD not available
LOG_DEBUG(GwLog::ERROR,"obp60task - calc Wind: AWA: %f, AWS: %f, COG: %f, STW: %f, HDT: %f, TWD=%f, TWS=%f, converted? %d", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852,
cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, twdVal, twsVal, isCalculated);
}
if (twdBVal->valid) {
twdVal = std::round(twdBVal->value * 1000); // Shift value to store decimals in int16_t);
if (twdVal < hstryMinVal || twdVal > twdHstryMax) {
twdVal = INT16_MIN; // Add invalid value
}
}
twdHstry.add(static_cast<int16_t>(twdVal));
if (twsBVal->valid) {
twsVal = static_cast<int16_t>(twsBVal->value * 10); // Shift value to store decimals in int16_t
if (twsVal < hstryMinVal || twsVal > twsHstryMax) {
twsVal = INT16_MIN; // Add invalid value
}
}
twsHstry.add(twsVal);
if (dbtBVal->valid) {
dbtVal = dbtBVal->value * 10; // Shift value to store decimals in int16_t
if (dbtVal < hstryMinVal || dbtVal > dbtHstryMax) {
dbtVal = INT16_MIN; // Add invalid value
}
dbtHstry.add(dbtVal);
}
int counttime = millis() - starttime10;
LOG_DEBUG(GwLog::ERROR,"obp60task: History buffer write time: %d", counttime);
}
// Refresh display data, default all 1s // Refresh display data, default all 1s
currentPage = pages[pageNumber].page; currentPage = pages[pageNumber].page;
int pagetime = 1000; int pagetime = 1000;
@ -907,6 +928,12 @@ void OBP60Task(GwApi *api){
api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues); api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues);
api->getStatus(commonData.status); api->getStatus(commonData.status);
if (calcTrueWnds) {
addTrueWind(api, &boatValues);
}
// Handle history buffers for TWD, TWS for wind plot page and other usage
handleHstryBuf(api, &boatValues, hstryBufList);
// Clear display // Clear display
// getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor); // getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);
getdisplay().fillScreen(commonData.bgcolor); // Clear display getdisplay().fillScreen(commonData.bgcolor); // Clear display