mirror of
https://github.com/thooge/esp32-nmea2000-obp60.git
synced 2026-08-18 18:32:30 +02:00
Merge pull request #234 from Scorgan01/TrueWind-opt
Fixes and new 'smoothing' feature for data charts
This commit is contained in:
+109
-108
@@ -1,5 +1,4 @@
|
|||||||
#include "OBPDataOperations.h"
|
#include "OBPDataOperations.h"
|
||||||
//#include "BoatDataCalibration.h" // Functions lib for data instance calibration
|
|
||||||
|
|
||||||
// --- Class CalibrationData ---------------
|
// --- Class CalibrationData ---------------
|
||||||
CalibrationData::CalibrationData(GwLog* log)
|
CalibrationData::CalibrationData(GwLog* log)
|
||||||
@@ -155,8 +154,7 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
|||||||
|
|
||||||
if (format == "formatWind") { // instance is of type angle
|
if (format == "formatWind") { // instance is of type angle
|
||||||
dataValue = (dataValue * slope) + offset;
|
dataValue = (dataValue * slope) + offset;
|
||||||
// dataValue = WindUtils::toPI(dataValue);
|
dataValue = WindUtils::to2PI(dataValue); // wind angle data is stored in [0..2PI] format
|
||||||
dataValue = WindUtils::to2PI(dataValue); // we should call <toPI> for format of [-180..180], but pages cannot display negative values properly yet
|
|
||||||
|
|
||||||
} else if (format == "formatCourse") { // instance is of type direction
|
} else if (format == "formatCourse") { // instance is of type direction
|
||||||
dataValue = (dataValue * slope) + offset;
|
dataValue = (dataValue * slope) + offset;
|
||||||
@@ -169,7 +167,6 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
|||||||
dataValue = (dataValue * slope) + offset;
|
dataValue = (dataValue * slope) + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
boatDataValue->value = dataValue; // update boat data value with calibrated value
|
boatDataValue->value = dataValue; // update boat data value with calibrated value
|
||||||
calibrationMap[instance].value = dataValue; // store the calibrated value in the list
|
calibrationMap[instance].value = dataValue; // store the calibrated value in the list
|
||||||
calibrationMap[instance].isCalibrated = true;
|
calibrationMap[instance].isCalibrated = true;
|
||||||
@@ -247,23 +244,15 @@ void HstryBuf::add(double value)
|
|||||||
|
|
||||||
void HstryBuf::handle(bool useSimuData, CommonData& common)
|
void HstryBuf::handle(bool useSimuData, CommonData& common)
|
||||||
{
|
{
|
||||||
// GwApi::BoatValue* tmpBVal;
|
|
||||||
std::unique_ptr<GwApi::BoatValue> tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter
|
std::unique_ptr<GwApi::BoatValue> tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter
|
||||||
|
|
||||||
// create temporary boat value for calibration purposes and retrieval of simulation value
|
|
||||||
// tmpBVal = new GwApi::BoatValue(boatDataName.c_str());
|
|
||||||
tmpBVal = std::unique_ptr<GwApi::BoatValue>(new GwApi::BoatValue(boatDataName));
|
|
||||||
tmpBVal->setFormat(boatValue->getFormat());
|
|
||||||
tmpBVal->value = boatValue->value;
|
|
||||||
tmpBVal->valid = boatValue->valid;
|
|
||||||
|
|
||||||
if (boatValue->valid) {
|
if (boatValue->valid) {
|
||||||
// Calibrate boat value before adding it to history buffer
|
|
||||||
//calibrationData.calibrateInstance(tmpBVal.get(), logger);
|
|
||||||
//add(tmpBVal->value);
|
|
||||||
add(boatValue->value);
|
add(boatValue->value);
|
||||||
|
|
||||||
} else if (useSimuData) { // add simulated value to history buffer
|
} else if (useSimuData) { // add simulated value to history buffer
|
||||||
|
tmpBVal = std::unique_ptr<GwApi::BoatValue>(new GwApi::BoatValue(boatDataName)); // create temporary boat value for retrieval of simulation value
|
||||||
|
tmpBVal->setFormat(boatValue->getFormat());
|
||||||
|
tmpBVal->value = boatValue->value;
|
||||||
|
tmpBVal->valid = boatValue->valid;
|
||||||
double simSIValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at <formatValue>; here: retreive SI value
|
double simSIValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at <formatValue>; here: retreive SI value
|
||||||
add(simSIValue);
|
add(simSIValue);
|
||||||
} else {
|
} else {
|
||||||
@@ -276,20 +265,7 @@ void HstryBuf::handle(bool useSimuData, CommonData& common)
|
|||||||
HstryBuffers::HstryBuffers(int size, BoatValueList* boatValues, GwLog* log)
|
HstryBuffers::HstryBuffers(int size, BoatValueList* boatValues, GwLog* log)
|
||||||
: size(size)
|
: size(size)
|
||||||
, boatValueList(boatValues)
|
, boatValueList(boatValues)
|
||||||
, logger(log)
|
, logger(log) { };
|
||||||
{
|
|
||||||
|
|
||||||
// collect boat values for true wind calculation
|
|
||||||
// should all have been already created at true wind object initialization
|
|
||||||
// potentially to be moved to history buffer handling
|
|
||||||
awaBVal = boatValueList->findValueOrCreate("AWA");
|
|
||||||
hdtBVal = boatValueList->findValueOrCreate("HDT");
|
|
||||||
hdmBVal = boatValueList->findValueOrCreate("HDM");
|
|
||||||
varBVal = boatValueList->findValueOrCreate("VAR");
|
|
||||||
cogBVal = boatValueList->findValueOrCreate("COG");
|
|
||||||
sogBVal = boatValueList->findValueOrCreate("SOG");
|
|
||||||
awdBVal = boatValueList->findValueOrCreate("AWD");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create history buffer for boat data type
|
// Create history buffer for boat data type
|
||||||
void HstryBuffers::addBuffer(const String& name)
|
void HstryBuffers::addBuffer(const String& name)
|
||||||
@@ -301,8 +277,6 @@ void HstryBuffers::addBuffer(const String& name)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
hstryBuffers[name] = std::unique_ptr<HstryBuf>(new HstryBuf(name, size, boatValueList, logger));
|
|
||||||
|
|
||||||
// Initialize metadata for buffer
|
// Initialize metadata for buffer
|
||||||
String valueFormat = bufferParams[name].format; // Data format of boat data type
|
String valueFormat = bufferParams[name].format; // Data format of boat data type
|
||||||
// String valueFormat = boatValueList->findValueOrCreate(name)->getFormat().c_str(); // Unfortunately, format is not yet available during system initialization
|
// String valueFormat = boatValueList->findValueOrCreate(name)->getFormat().c_str(); // Unfortunately, format is not yet available during system initialization
|
||||||
@@ -311,6 +285,7 @@ void HstryBuffers::addBuffer(const String& name)
|
|||||||
double bufferMinVal = bufferParams[name].bufferMinVal; // Min value for this history buffer
|
double bufferMinVal = bufferParams[name].bufferMinVal; // Min value for this history buffer
|
||||||
double bufferMaxVal = bufferParams[name].bufferMaxVal; // Max value for this history buffer
|
double bufferMaxVal = bufferParams[name].bufferMaxVal; // Max value for this history buffer
|
||||||
|
|
||||||
|
hstryBuffers[name] = std::unique_ptr<HstryBuf>(new HstryBuf(name, size, boatValueList, logger));
|
||||||
hstryBuffers[name]->init(valueFormat, hstryUpdFreq, mltplr, bufferMinVal, bufferMaxVal);
|
hstryBuffers[name]->init(valueFormat, hstryUpdFreq, mltplr, bufferMinVal, bufferMaxVal);
|
||||||
LOG_DEBUG(GwLog::DEBUG, "HstryBuffers: new buffer added: name: %s, format: %s, multiplier: %d, min value: %.2f, max value: %.2f", name, valueFormat, mltplr, bufferMinVal, bufferMaxVal);
|
LOG_DEBUG(GwLog::DEBUG, "HstryBuffers: new buffer added: name: %s, format: %s, multiplier: %d, min value: %.2f, max value: %.2f", name, valueFormat, mltplr, bufferMinVal, bufferMaxVal);
|
||||||
}
|
}
|
||||||
@@ -329,8 +304,9 @@ RingBuffer<uint16_t>* HstryBuffers::getBuffer(const String& name)
|
|||||||
auto it = hstryBuffers.find(name);
|
auto it = hstryBuffers.find(name);
|
||||||
if (it != hstryBuffers.end()) {
|
if (it != hstryBuffers.end()) {
|
||||||
return &it->second->hstryBuf;
|
return &it->second->hstryBuf;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
// --- End Class HstryBuffers ---------------
|
// --- End Class HstryBuffers ---------------
|
||||||
|
|
||||||
@@ -396,56 +372,75 @@ void WindUtils::addPolar(const double* phi1, const double* r1,
|
|||||||
toPol(&x1, &y1, phi, r);
|
toPol(&x1, &y1, phi, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindUtils::calcTwdSA(const double* AWA, const double* AWS,
|
void WindUtils::calcTwdSA(const double* AWA, const double* AWS, const double* AWD, const double* CTW, const double* STW, const double* HDT,
|
||||||
const double* CTW, const double* STW, const double* HDT,
|
double* TWA, double* TWS, double* TWD)
|
||||||
double* TWD, double* TWS, double* TWA, double* AWD)
|
|
||||||
{
|
{
|
||||||
*AWD = *AWA + *HDT;
|
|
||||||
*AWD = to2PI(*AWD);
|
|
||||||
double stw = -*STW;
|
double stw = -*STW;
|
||||||
addPolar(AWD, AWS, CTW, &stw, TWD, TWS);
|
addPolar(AWD, AWS, CTW, &stw, TWD, TWS);
|
||||||
|
|
||||||
// Normalize TWD to [0..360°] (2PI) and TWA to [-180..180] (PI)
|
// Normalize to [0..2PI]
|
||||||
*TWD = to2PI(*TWD);
|
*TWD = to2PI(*TWD);
|
||||||
*TWA = toPI(*TWD - *HDT);
|
*TWA = to2PI(*TWD - *HDT); // for internal storage we normalize wind angle to [0..2PI], as well
|
||||||
}
|
}
|
||||||
|
|
||||||
double WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal)
|
// calculate HDT from either HDM + VAR, HDM only, or COG
|
||||||
|
bool WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal, double* hdtVal)
|
||||||
{
|
{
|
||||||
double hdt;
|
|
||||||
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
|
|
||||||
|
|
||||||
if (*hdmVal != DBL_MAX) {
|
|
||||||
hdt = *hdmVal + (*varVal != DBL_MAX ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available)
|
|
||||||
hdt = to2PI(hdt);
|
|
||||||
} else if (*cogVal != DBL_MAX && *sogVal >= minSogVal) {
|
|
||||||
hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise
|
|
||||||
} else {
|
|
||||||
hdt = DBL_MAX; // Cannot calculate HDT without valid HDM or HDM+VAR or COG
|
|
||||||
}
|
|
||||||
|
|
||||||
return hdt;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WindUtils::calcWinds(const double* awaVal, const double* awsVal,
|
|
||||||
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
|
|
||||||
const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal, double* awdVal)
|
|
||||||
{
|
|
||||||
double stw, hdt, ctw;
|
|
||||||
double twd, tws, twa, awd;
|
|
||||||
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
|
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
|
||||||
|
|
||||||
if (*hdtVal != DBL_MAX) {
|
if (*hdtVal != DBL_MAX) {
|
||||||
hdt = *hdtVal; // Use HDT if available
|
// HDT already available-> nothing to do
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*hdmVal != DBL_MAX) {
|
||||||
|
*hdtVal = *hdmVal + (*varVal != DBL_MAX ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available)
|
||||||
|
*hdtVal = to2PI(*hdtVal);
|
||||||
|
} else if (*cogVal != DBL_MAX && *sogVal >= minSogVal) {
|
||||||
|
*hdtVal = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise
|
||||||
} else {
|
} else {
|
||||||
hdt = calcHDT(hdmVal, varVal, cogVal, sogVal);
|
*hdtVal = DBL_MAX; // Cannot calculate HDT without valid HDM or HDM+VAR or COG
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcHDT: HDT: %.1f, HDM %.1f, VAR %.1f, COG %.1f, SOG %.1f", *hdtVal * RAD_TO_DEG, *hdmVal * RAD_TO_DEG, *varVal * RAD_TO_DEG,
|
||||||
|
// *cogVal * RAD_TO_DEG, *sogVal * 3.6 / 1.852);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate wind direction (either TWD or AWD) from wind angle (TWA or AWA) and HDT
|
||||||
|
bool WindUtils::calcWD(const double* waVal, const double* hdtVal, double* wdVal)
|
||||||
|
{
|
||||||
|
if (*waVal != DBL_MAX && *hdtVal != DBL_MAX) {
|
||||||
|
*wdVal = *waVal + *hdtVal;
|
||||||
|
*wdVal = to2PI(*wdVal);
|
||||||
|
} else {
|
||||||
|
*wdVal = DBL_MAX; // Cannot calculate wind direction WD without valid wind angle WA and HDT
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate full set of true winds (TWA, TWS, TWD) apparent winds and more data
|
||||||
|
bool WindUtils::calcTrueWinds(const double* awaVal, const double* awsVal, const double* awd,
|
||||||
|
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
|
||||||
|
double* twaVal, double* twsVal, double* twdVal)
|
||||||
|
{
|
||||||
|
double stw, ctw;
|
||||||
|
double twd, tws, twa;
|
||||||
|
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
|
||||||
|
|
||||||
|
if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) {
|
||||||
|
// Cannot calculate true winds at all without valid AWA, AWS
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*cogVal != DBL_MAX && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG
|
if (*cogVal != DBL_MAX && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG
|
||||||
|
|
||||||
ctw = *cogVal; // Use COG for CTW if available
|
ctw = *cogVal; // Use COG for CTW if available
|
||||||
} else {
|
} else {
|
||||||
ctw = hdt; // 2nd approximation for CTW; hdt must exist if we reach this part of the code
|
ctw = *hdtVal; // 2nd approximation for CTW; HDT must exist if we reach this part of code
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*stwVal != DBL_MAX) {
|
if (*stwVal != DBL_MAX) {
|
||||||
@@ -456,28 +451,21 @@ bool WindUtils::calcWinds(const double* awaVal, const double* awsVal,
|
|||||||
// If STW and SOG are not available, we cannot calculate true wind
|
// If STW and SOG are not available, we cannot calculate true wind
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcWinds: HDT: %.1f, CTW %.1f, STW %.1f", hdt, ctw, stw);
|
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcTrueWinds: HDT: %.1f, CTW %.1f, STW %.1f", *hdtVal * RAD_TO_DEG, ctw * RAD_TO_DEG, stw * 3.6 / 1.852);
|
||||||
|
|
||||||
if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) {
|
calcTwdSA(awaVal, awsVal, awd, &ctw, &stw, hdtVal, &twa, &tws, &twd);
|
||||||
// Cannot calculate true wind without valid AWA, AWS; other checks are done earlier
|
*twaVal = twa;
|
||||||
return false;
|
*twsVal = tws;
|
||||||
} else {
|
*twdVal = twd;
|
||||||
calcTwdSA(awaVal, awsVal, &ctw, &stw, &hdt, &twd, &tws, &twa, &awd);
|
|
||||||
*twdVal = twd;
|
|
||||||
*twsVal = tws;
|
|
||||||
*twaVal = twa;
|
|
||||||
*awdVal = awd;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate true wind data and add to obp60task boat data list
|
// Calculate true wind data and add to obp60task boat data list
|
||||||
bool WindUtils::addWinds()
|
bool WindUtils::handleWinds(bool calcWinds)
|
||||||
{
|
{
|
||||||
double twd, tws, twa, awd, hdt;
|
double twd, tws, twa, awd;
|
||||||
bool twCalculated = false;
|
bool twCalculated = false;
|
||||||
bool awdCalculated = false;
|
|
||||||
|
|
||||||
double awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX;
|
double awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX;
|
||||||
double awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX;
|
double awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX;
|
||||||
@@ -487,48 +475,61 @@ bool WindUtils::addWinds()
|
|||||||
double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
|
double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
|
||||||
double hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX;
|
double hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX;
|
||||||
double varVal = varBVal->valid ? varBVal->value : DBL_MAX;
|
double varVal = varBVal->valid ? varBVal->value : DBL_MAX;
|
||||||
//LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852,
|
double twaVal = twaBVal->valid ? twaBVal->value : DBL_MAX;
|
||||||
// cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG);
|
double twsVal = twsBVal->valid ? twsBVal->value : DBL_MAX;
|
||||||
|
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:handleWinds: AWA %.1f, AWS %.1f, AWD %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaVal * RAD_TO_DEG, awsVal * 3.6 / 1.852,
|
||||||
|
// awd * RAD_TO_DEG, cogVal * RAD_TO_DEG, stwVal * 3.6 / 1.852, sogVal * 3.6 / 1.852, hdtVal * RAD_TO_DEG, hdmVal * RAD_TO_DEG, varVal * RAD_TO_DEG);
|
||||||
|
|
||||||
// Check if TWD can be calculated from TWA and HDT/HDM
|
if (calcHDT(&hdmVal, &varVal, &cogVal, &sogVal, &hdtVal)) {
|
||||||
if (twaBVal->valid) {
|
hdtBVal->value = hdtVal;
|
||||||
if (!twdBVal->valid) {
|
hdtBVal->valid = true;
|
||||||
if (hdtVal != DBL_MAX) {
|
} else {
|
||||||
hdt = hdtVal; // Use HDT if available
|
// determination of HDT was not possible due to missing prerequisite data
|
||||||
} else {
|
hdtBVal->valid = false;
|
||||||
hdt = calcHDT(&hdmVal, &varVal, &cogVal, &sogVal);
|
return false;
|
||||||
}
|
}
|
||||||
twd = twaBVal->value + hdt;
|
|
||||||
twd = to2PI(twd);
|
// calculate AWD if not existing and if possible
|
||||||
|
if (!awdBVal->valid) {
|
||||||
|
if (calcWD(&awaVal, &hdtVal, &awd)) {
|
||||||
|
awdBVal->value = awd;
|
||||||
|
awdBVal->valid = true;
|
||||||
|
} else {
|
||||||
|
awdBVal->valid = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate TWD if not existing and if possible
|
||||||
|
if (!twdBVal->valid) {
|
||||||
|
// calculate TWD if it does not exist yet and TWA is available
|
||||||
|
if (calcWD(&twaVal, &hdtVal, &twd)) {
|
||||||
twdBVal->value = twd;
|
twdBVal->value = twd;
|
||||||
twdBVal->valid = true;
|
twdBVal->valid = true;
|
||||||
|
} else {
|
||||||
|
twdBVal->valid = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
if (calcWinds && (!twaBVal->valid || !twsBVal->valid || !twdBVal->valid)) {
|
||||||
// Calculate true winds and AWD; if true winds exist, use at least AWD calculation
|
// calculate true winds if user setting and at least one of three true wind values does not exist
|
||||||
twCalculated = calcWinds(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa, &awd);
|
twCalculated = calcTrueWinds(&awaVal, &awsVal, &awd, &cogVal, &stwVal, &sogVal, &hdtVal, &twa, &tws, &twd);
|
||||||
|
|
||||||
if (twCalculated) { // Replace values only, if successfully calculated and not already available
|
if (twCalculated) { // Replace values only, if successfully calculated and not already available
|
||||||
if (!twdBVal->valid) {
|
if (!twaBVal->valid) {
|
||||||
twdBVal->value = twd;
|
twaBVal->value = twa;
|
||||||
twdBVal->valid = true;
|
twaBVal->valid = true;
|
||||||
}
|
}
|
||||||
if (!twsBVal->valid) {
|
if (!twsBVal->valid) {
|
||||||
twsBVal->value = tws;
|
twsBVal->value = tws;
|
||||||
twsBVal->valid = true;
|
twsBVal->valid = true;
|
||||||
}
|
}
|
||||||
if (!twaBVal->valid) {
|
if (!twdBVal->valid) {
|
||||||
//twaBVal->value = twa;
|
twdBVal->value = twd;
|
||||||
twaBVal->value = to2PI(twa); // convert to [0..360], because pages cannot display negative values properly yet
|
twdBVal->valid = true;
|
||||||
twaBVal->valid = true;
|
|
||||||
}
|
|
||||||
if (!awdBVal->valid) {
|
|
||||||
awdBVal->value = awd;
|
|
||||||
awdBVal->valid = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG,
|
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:handleWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG,
|
||||||
// twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG);
|
// twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG);
|
||||||
|
|
||||||
return twCalculated;
|
return twCalculated;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ private:
|
|||||||
} tCalibrationData;
|
} tCalibrationData;
|
||||||
|
|
||||||
std::unordered_map<std::string, tCalibrationData> calibrationMap; // list of calibration data instances
|
std::unordered_map<std::string, tCalibrationData> calibrationMap; // list of calibration data instances
|
||||||
std::unordered_map<std::string, double> lastValue; // array for last smoothed values of boat data values
|
std::unordered_map<std::string, double> lastValue; // array for last smoothed value of boat data values
|
||||||
GwLog* logger;
|
GwLog* logger;
|
||||||
|
|
||||||
static constexpr int8_t MAX_CALIBRATION_DATA = 4; // maximum number of calibration data instances
|
static constexpr int8_t MAX_CALIBRATION_DATA = 4; // maximum number of calibration data instances
|
||||||
@@ -32,6 +32,7 @@ public:
|
|||||||
bool smoothInstance(GwApi::BoatValue* boatDataValue); // Smooth single boat data value
|
bool smoothInstance(GwApi::BoatValue* boatDataValue); // Smooth single boat data value
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Class for a single history buffer of boat values
|
||||||
class HstryBuf {
|
class HstryBuf {
|
||||||
private:
|
private:
|
||||||
RingBuffer<uint16_t> hstryBuf; // Circular buffer to store history values
|
RingBuffer<uint16_t> hstryBuf; // Circular buffer to store history values
|
||||||
@@ -50,13 +51,13 @@ public:
|
|||||||
void handle(bool useSimuData, CommonData& common);
|
void handle(bool useSimuData, CommonData& common);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Manage list of history buffers for supported boat data; used by boat data charts
|
||||||
class HstryBuffers {
|
class HstryBuffers {
|
||||||
private:
|
private:
|
||||||
std::map<String, std::unique_ptr<HstryBuf>> hstryBuffers;
|
std::map<String, std::unique_ptr<HstryBuf>> hstryBuffers;
|
||||||
int size; // size of all history buffers
|
int size; // size of all history buffers
|
||||||
BoatValueList* boatValueList;
|
BoatValueList* boatValueList;
|
||||||
GwLog* logger;
|
GwLog* logger;
|
||||||
GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal, *awdBVal; // boat values for true wind calculation
|
|
||||||
|
|
||||||
struct HistoryParams {
|
struct HistoryParams {
|
||||||
int hstryUpdFreq; // update frequency of history buffer (documentation only)
|
int hstryUpdFreq; // update frequency of history buffer (documentation only)
|
||||||
@@ -97,7 +98,8 @@ public:
|
|||||||
class WindUtils {
|
class WindUtils {
|
||||||
private:
|
private:
|
||||||
GwApi::BoatValue *twaBVal, *twsBVal, *twdBVal;
|
GwApi::BoatValue *twaBVal, *twsBVal, *twdBVal;
|
||||||
GwApi::BoatValue *awaBVal, *awsBVal, *awdBVal, *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal;
|
GwApi::BoatValue *awaBVal, *awsBVal, *awdBVal;
|
||||||
|
GwApi::BoatValue *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal;
|
||||||
static constexpr double DBL_MAX = std::numeric_limits<double>::max();
|
static constexpr double DBL_MAX = std::numeric_limits<double>::max();
|
||||||
GwLog* logger;
|
GwLog* logger;
|
||||||
|
|
||||||
@@ -128,12 +130,12 @@ public:
|
|||||||
void addPolar(const double* phi1, const double* r1,
|
void addPolar(const double* phi1, const double* r1,
|
||||||
const double* phi2, const double* r2,
|
const double* phi2, const double* r2,
|
||||||
double* phi, double* r);
|
double* phi, double* r);
|
||||||
void calcTwdSA(const double* AWA, const double* AWS,
|
void calcTwdSA(const double* AWA, const double* AWS, const double* AWD, const double* CTW, const double* STW, const double* HDT,
|
||||||
const double* CTW, const double* STW, const double* HDT,
|
double* TWA, double* TWS, double* TWD);
|
||||||
double* TWD, double* TWS, double* TWA, double* AWD);
|
bool calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal, double* hdtVal);
|
||||||
static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal);
|
bool calcWD(const double* waVal, const double* hdtVal, double* wdVal);
|
||||||
bool calcWinds(const double* awaVal, const double* awsVal,
|
bool calcTrueWinds(const double* awaVal, const double* awsVal, const double* awd,
|
||||||
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
|
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
|
||||||
const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal, double* awdVal);
|
double* twdVal, double* twsVal, double* twaVal);
|
||||||
bool addWinds();
|
bool handleWinds(bool calcWinds);
|
||||||
};
|
};
|
||||||
@@ -66,8 +66,8 @@ void sensorTask(void *param){
|
|||||||
const int avgsize = 300;
|
const int avgsize = 300;
|
||||||
constexpr int arrayBatV{avgsize};
|
constexpr int arrayBatV{avgsize};
|
||||||
constexpr int arrayBatC{avgsize};
|
constexpr int arrayBatC{avgsize};
|
||||||
movingAvg batV(arrayBatV);
|
movingAvg<int> batV(arrayBatV);
|
||||||
movingAvg batC(arrayBatC);
|
movingAvg<int> batC(arrayBatC);
|
||||||
batV.begin();
|
batV.begin();
|
||||||
batC.begin();
|
batC.begin();
|
||||||
|
|
||||||
|
|||||||
+49
-21
@@ -6,9 +6,9 @@
|
|||||||
std::map<String, ChartProps> Chart::dfltChrtDta = {
|
std::map<String, ChartProps> Chart::dfltChrtDta = {
|
||||||
{ "formatWind", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees
|
{ "formatWind", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees
|
||||||
{ "formatCourse", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees
|
{ "formatCourse", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees
|
||||||
{ "formatKnots", { 7.71, 2.56 } }, // default speed range in m/s
|
{ "formatKnots", { 2.57, 2.57 } }, // default speed range in m/s
|
||||||
{ "formatDepth", { 15.0, 5.0 } }, // default depth range in m
|
{ "formatDepth", { 10.0, 5.0 } }, // default depth range in m
|
||||||
{ "kelvinToC", { 30.0, 5.0 } } // default temp range in °C/K
|
{ "kelvinToC", { 20.0, 5.0 } } // default temp range in °C/K
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Class Chart ---------------
|
// --- Class Chart ---------------
|
||||||
@@ -42,6 +42,11 @@ Chart::Chart(RingBuffer<uint16_t>& dataBuf, double dfltRng, CommonData& common,
|
|||||||
dbMAX_VAL = dataBuf.getMaxVal();
|
dbMAX_VAL = dataBuf.getMaxVal();
|
||||||
bufSize = dataBuf.getCapacity();
|
bufSize = dataBuf.getCapacity();
|
||||||
|
|
||||||
|
smoothCharts = common.config->getBool(common.config->smoothCharts);
|
||||||
|
if (smoothCharts) {
|
||||||
|
chrtAvg.begin();
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize chart data format; shorter version of standard format indicator
|
// Initialize chart data format; shorter version of standard format indicator
|
||||||
if (dbFormat == "formatCourse" || dbFormat == "formatWind" || dbFormat == "formatRot") {
|
if (dbFormat == "formatCourse" || dbFormat == "formatWind" || dbFormat == "formatRot") {
|
||||||
chrtDataFmt = WIND; // Chart is showing data of course / wind <degree> format
|
chrtDataFmt = WIND; // Chart is showing data of course / wind <degree> format
|
||||||
@@ -337,6 +342,23 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
|
|||||||
double chrtVal; // Current data value
|
double chrtVal; // Current data value
|
||||||
Pos point, prevPoint; // current and previous chart point
|
Pos point, prevPoint; // current and previous chart point
|
||||||
|
|
||||||
|
if (smoothCharts) {
|
||||||
|
// prime moving average filter to ensure the first plotted point is already averaged
|
||||||
|
|
||||||
|
chrtAvg.reset();
|
||||||
|
|
||||||
|
// Feed the filter the 10 values preceding bufStart
|
||||||
|
for (int p = 10; p > 0; p--) {
|
||||||
|
// Calculate index with wrapping: (start - offset + size) % size
|
||||||
|
int primeIdx = (bufStart - (p * chrtIntv));
|
||||||
|
double primeVal = dataBuf.get(primeIdx);
|
||||||
|
|
||||||
|
if (primeVal != dbMAX_VAL) {
|
||||||
|
chrtAvg.reading(primeVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < (numBufVals / chrtIntv); i++) {
|
for (int i = 0; i < (numBufVals / chrtIntv); i++) {
|
||||||
|
|
||||||
chrtVal = dataBuf.get(bufStart + (i * chrtIntv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer
|
chrtVal = dataBuf.get(bufStart + (i * chrtIntv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer
|
||||||
@@ -345,6 +367,11 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
|
|||||||
chrtPrevVal = dbMAX_VAL;
|
chrtPrevVal = dbMAX_VAL;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
if (smoothCharts) {
|
||||||
|
// if chart lines shall be smoothed, apply moving average filter of the last 10 values
|
||||||
|
chrtVal = chrtAvg.reading(chrtVal);
|
||||||
|
}
|
||||||
|
|
||||||
point = setCurrentChartPoint(i, direction, chrtVal, chrtScale);
|
point = setCurrentChartPoint(i, direction, chrtVal, chrtScale);
|
||||||
|
|
||||||
// if (i >= (numBufVals / chrtIntv) - 5) // log chart data of 1 line (adjust for test purposes)
|
// if (i >= (numBufVals / chrtIntv) - 5) // log chart data of 1 line (adjust for test purposes)
|
||||||
@@ -399,7 +426,7 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
|
|||||||
|
|
||||||
if (chrtDataFmt == WIND) { // degree of course or wind
|
if (chrtDataFmt == WIND) { // degree of course or wind
|
||||||
recalcRngMid = true;
|
recalcRngMid = true;
|
||||||
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chart end: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngMid);
|
// LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chart end: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngMid);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -440,46 +467,47 @@ Pos Chart::setCurrentChartPoint(const int i, const char direction, const double
|
|||||||
// chart time axis label + lines
|
// chart time axis label + lines
|
||||||
void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8_t chrtIntv)
|
void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8_t chrtIntv)
|
||||||
{
|
{
|
||||||
float axSlots, intv, i;
|
int axSlots, intv, i, timeRng;
|
||||||
char sTime[6];
|
char sTime[6];
|
||||||
int timeRng = chrtIntv * 4; // chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min.
|
int tOffset;
|
||||||
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
getdisplay().setTextColor(fgColor);
|
getdisplay().setTextColor(fgColor);
|
||||||
|
|
||||||
axSlots = 5; // number of axis labels
|
intv = 60; // print axis label each 60 pixels = seconds
|
||||||
intv = timAxis / (axSlots - 1); // minutes per chart axis interval (interval is 1 less than axSlots)
|
axSlots = timAxis / intv; // number of axis labels; value will be truncated to full number
|
||||||
i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes
|
i = axSlots * chrtIntv * -1; // current minute label
|
||||||
|
|
||||||
if (chrtDir == HORIZONTAL) {
|
if (chrtDir == HORIZONTAL) {
|
||||||
getdisplay().fillRect(0, cRoot.y, dWidth, 2, fgColor);
|
getdisplay().fillRect(0, cRoot.y, dWidth, 2, fgColor);
|
||||||
|
|
||||||
for (float j = 0; j < timAxis - 1; j += intv) { // fill time axis with values but keep area free on right hand side for value label
|
// LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtTimeAxis: intv: %d, axSlots: %d, timAxis: %d, chrtIntv: %d", intv, axSlots, timAxis, chrtIntv);
|
||||||
|
for (int j = intv; j < timAxis - 1; j += intv) { // fill time axis with values but keep area free on right hand side for value label
|
||||||
|
|
||||||
// draw text with appropriate offset
|
// draw text with appropriate offset
|
||||||
int tOffset = j == 0 ? 13 : -4;
|
tOffset = j == 0 ? 13 : -4;
|
||||||
snprintf(sTime, sizeof(sTime), "-%.0f", i);
|
snprintf(sTime, sizeof(sTime), "%d", i);
|
||||||
drawTextCenter(cRoot.x + j + tOffset, cRoot.y - 8, sTime);
|
drawTextCenter(cRoot.x + j + tOffset, cRoot.y - 8, sTime);
|
||||||
getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + 5, fgColor); // draw short vertical time mark
|
getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + 5, fgColor); // draw short vertical time mark
|
||||||
|
|
||||||
i -= chrtIntv;
|
i += chrtIntv;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else { // vertical chart
|
} else { // vertical chart
|
||||||
|
|
||||||
for (float j = intv; j < timAxis - 1; j += intv) { // don't print time label at upper and lower end of time axis
|
for (float j = intv; j < timAxis - 1; j += intv) { // don't print time label at upper and lower end of time axis
|
||||||
|
|
||||||
i -= chrtIntv; // we start not at top chart position
|
snprintf(sTime, sizeof(sTime), "%d", i);
|
||||||
snprintf(sTime, sizeof(sTime), "-%.0f", i);
|
|
||||||
getdisplay().drawLine(cRoot.x, cRoot.y + j, cRoot.x + valAxis, cRoot.y + j, fgColor); // Grid line
|
getdisplay().drawLine(cRoot.x, cRoot.y + j, cRoot.x + valAxis, cRoot.y + j, fgColor); // Grid line
|
||||||
|
|
||||||
if (chrtSz == FULL_SIZE) { // full size chart
|
if (chrtSz == FULL_SIZE) { // full size chart
|
||||||
getdisplay().fillRect(0, cRoot.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines
|
getdisplay().fillRect(0, cRoot.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines
|
||||||
getdisplay().setCursor((4 - strlen(sTime)) * 7, cRoot.y + j + 3); // time value; print left screen; value right-formated
|
getdisplay().setCursor((4 - strlen(sTime)) * 7, cRoot.y + j + 3); // time value; print left screen; value right-formated
|
||||||
getdisplay().printf("%s", sTime); // Range value
|
getdisplay().printf("%s", sTime); // time value
|
||||||
} else if (chrtSz == HALF_SIZE_RIGHT) { // half size chart; right side
|
} else if (chrtSz == HALF_SIZE_RIGHT) { // half size chart; right side
|
||||||
drawTextCenter(dWidth / 2, cRoot.y + j, sTime); // time value; print mid screen
|
drawTextCenter(dWidth / 2, cRoot.y + j, sTime); // time value; print mid screen
|
||||||
}
|
}
|
||||||
|
i += chrtIntv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -497,11 +525,11 @@ void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntNa
|
|||||||
|
|
||||||
if (chrtSz == FULL_SIZE) {
|
if (chrtSz == FULL_SIZE) {
|
||||||
|
|
||||||
|
// print buffer data name on left hand side of time axis (max. size 5 characters)
|
||||||
font = &Ubuntu_Bold12pt8b;
|
font = &Ubuntu_Bold12pt8b;
|
||||||
|
|
||||||
// print buffer data name on right hand side of time axis (max. size 5 characters)
|
|
||||||
getdisplay().setFont(font);
|
getdisplay().setFont(font);
|
||||||
drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5));
|
getdisplay().fillRect(cRoot.x + timAxis - 57, cRoot.y + 2, 58, 20, bgColor); // clear small area to remove potential chart lines
|
||||||
|
drawTextRalign(cRoot.x + timAxis, cRoot.y + 19, dbName.substring(0, 5));
|
||||||
|
|
||||||
if (chrtDataFmt == WIND) {
|
if (chrtDataFmt == WIND) {
|
||||||
prntHorizChartThreeValueAxisLabel(font);
|
prntHorizChartThreeValueAxisLabel(font);
|
||||||
@@ -515,11 +543,11 @@ void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntNa
|
|||||||
} else { // half size chart -> just print edge values + middle chart line
|
} else { // half size chart -> just print edge values + middle chart line
|
||||||
|
|
||||||
font = &Ubuntu_Bold10pt8b;
|
font = &Ubuntu_Bold10pt8b;
|
||||||
|
|
||||||
if (prntName) {
|
if (prntName) {
|
||||||
// print buffer data name on right hand side of time axis (max. size 5 characters)
|
// print buffer data name on right hand side of time axis (max. size 5 characters)
|
||||||
getdisplay().setFont(font);
|
getdisplay().setFont(font);
|
||||||
drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5));
|
getdisplay().fillRect(cRoot.x + timAxis - 57, cRoot.y + 2, 58, 20, bgColor); // clear small area to remove potential chart lines
|
||||||
|
drawTextRalign(cRoot.x + timAxis, cRoot.y + 16, dbName.substring(0, 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
prntHorizChartThreeValueAxisLabel(font);
|
prntHorizChartThreeValueAxisLabel(font);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "Pagedata.h"
|
#include "Pagedata.h"
|
||||||
#include "OBP60Extensions.h"
|
#include "OBP60Extensions.h"
|
||||||
|
#include "movingAvg.h"
|
||||||
|
|
||||||
struct Pos {
|
struct Pos {
|
||||||
int x;
|
int x;
|
||||||
@@ -44,12 +45,11 @@ protected:
|
|||||||
static constexpr bool NO_SIMUDATA = true; // switch off simulation feature of <formatValue> function
|
static constexpr bool NO_SIMUDATA = true; // switch off simulation feature of <formatValue> function
|
||||||
|
|
||||||
RingBuffer<uint16_t>& dataBuf; // Buffer to display
|
RingBuffer<uint16_t>& dataBuf; // Buffer to display
|
||||||
//char chrtDir; // Chart timeline direction: 'H' = horizontal, 'V' = vertical
|
|
||||||
//int8_t chrtSz; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom
|
|
||||||
double dfltRng; // Default range of chart, e.g. 30 = [0..30]
|
double dfltRng; // Default range of chart, e.g. 30 = [0..30]
|
||||||
uint16_t fgColor; // color code for any screen writing
|
uint16_t fgColor; // color code for any screen writing
|
||||||
uint16_t bgColor; // color code for screen background
|
uint16_t bgColor; // color code for screen background
|
||||||
bool useSimuData; // flag to indicate if simulation data is active
|
bool useSimuData; // flag to indicate if simulation data is active
|
||||||
|
bool smoothCharts; // flag to indicate if charts shall be printed with smoothed gradient
|
||||||
String tempFormat; // user defined format for temperature
|
String tempFormat; // user defined format for temperature
|
||||||
double zeroValue; // "0" SI value for temperature
|
double zeroValue; // "0" SI value for temperature
|
||||||
|
|
||||||
@@ -84,6 +84,7 @@ protected:
|
|||||||
bool bufDataValid = false; // Flag to indicate if buffer data is valid
|
bool bufDataValid = false; // Flag to indicate if buffer data is valid
|
||||||
int oldChrtIntv = 0; // remember recent user selection of data interval
|
int oldChrtIntv = 0; // remember recent user selection of data interval
|
||||||
|
|
||||||
|
movingAvg<double> chrtAvg{7}; // Store average of the last 7 chart values if chart gradient shall be smoothed
|
||||||
double chrtPrevVal; // Last data value in chart area
|
double chrtPrevVal; // Last data value in chart area
|
||||||
int x, y; // x and y coordinates for drawing
|
int x, y; // x and y coordinates for drawing
|
||||||
int prevX, prevY; // Last x and y coordinates for drawing
|
int prevX, prevY; // Last x and y coordinates for drawing
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "Pagedata.h"
|
#include "Pagedata.h"
|
||||||
#include "OBP60Extensions.h"
|
#include "OBP60Extensions.h"
|
||||||
#include "movingAvg.h" // Lib for moving average building
|
|
||||||
|
|
||||||
class PageBattery2 : public Page
|
class PageBattery2 : public Page
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "Pagedata.h"
|
#include "Pagedata.h"
|
||||||
#include "OBP60Extensions.h"
|
#include "OBP60Extensions.h"
|
||||||
#include "movingAvg.h" // Lib for moving average building
|
|
||||||
|
|
||||||
class PageGenerator : public Page
|
class PageGenerator : public Page
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -209,8 +209,6 @@ public:
|
|||||||
dataIntv = 3;
|
dataIntv = 3;
|
||||||
} else if (dataIntv == 3) {
|
} else if (dataIntv == 3) {
|
||||||
dataIntv = 4;
|
dataIntv = 4;
|
||||||
} else if (dataIntv == 4) {
|
|
||||||
dataIntv = 8;
|
|
||||||
} else {
|
} else {
|
||||||
dataIntv = 1;
|
dataIntv = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "Pagedata.h"
|
#include "Pagedata.h"
|
||||||
#include "OBP60Extensions.h"
|
#include "OBP60Extensions.h"
|
||||||
#include "movingAvg.h" // Lib for moving average building
|
|
||||||
|
|
||||||
class PageSolar : public Page
|
class PageSolar : public Page
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -211,8 +211,6 @@ public:
|
|||||||
dataIntv = 3;
|
dataIntv = 3;
|
||||||
} else if (dataIntv == 3) {
|
} else if (dataIntv == 3) {
|
||||||
dataIntv = 4;
|
dataIntv = 4;
|
||||||
} else if (dataIntv == 4) {
|
|
||||||
dataIntv = 8;
|
|
||||||
} else {
|
} else {
|
||||||
dataIntv = 1;
|
dataIntv = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "Pagedata.h"
|
#include "Pagedata.h"
|
||||||
#include "OBP60Extensions.h"
|
#include "OBP60Extensions.h"
|
||||||
#include "movingAvg.h" // Lib for moving average building
|
|
||||||
|
|
||||||
class PageVoltage : public Page
|
class PageVoltage : public Page
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ private:
|
|||||||
bool oldShowTruW = false; // remember recent user selection of wind data type
|
bool oldShowTruW = false; // remember recent user selection of wind data type
|
||||||
|
|
||||||
int8_t dataIntv = 1; // Update interval for wind history chart:
|
int8_t dataIntv = 1; // Update interval for wind history chart:
|
||||||
// (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart
|
// (1)|(2)|(3)|(4)|(8) seconds for up to 32 min. history chart
|
||||||
bool useSimuData;
|
bool useSimuData;
|
||||||
// bool holdValues;
|
// bool holdValues;
|
||||||
String flashLED;
|
String flashLED;
|
||||||
@@ -231,6 +231,9 @@ public:
|
|||||||
|
|
||||||
} else if (chrtMode == SPEED) {
|
} else if (chrtMode == SPEED) {
|
||||||
if (wsChart) {
|
if (wsChart) {
|
||||||
|
if (dataIntv == 8) {
|
||||||
|
dataIntv = 1; // horizontal charts show max. 4 x 7 min. only; no factor 8 multiplier
|
||||||
|
}
|
||||||
wsChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *wsBVal);
|
wsChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *wsBVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -230,6 +230,17 @@
|
|||||||
"obp40": "true"
|
"obp40": "true"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "smoothCharts",
|
||||||
|
"label": "Smooth chart lines",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "false",
|
||||||
|
"description": "Print chart lines with a smoothed gradient",
|
||||||
|
"category": "OBP40 Settings",
|
||||||
|
"capabilities": {
|
||||||
|
"obp40": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "lengthFormat",
|
"name": "lengthFormat",
|
||||||
"label": "Length Format",
|
"label": "Length Format",
|
||||||
|
|||||||
@@ -230,6 +230,17 @@
|
|||||||
"obp60": "true"
|
"obp60": "true"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "smoothCharts",
|
||||||
|
"label": "Smooth chart lines",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "false",
|
||||||
|
"description": "Print chart lines with a smoothed gradient",
|
||||||
|
"category": "OBP60 Settings",
|
||||||
|
"capabilities": {
|
||||||
|
"obp40": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "lengthFormat",
|
"name": "lengthFormat",
|
||||||
"label": "Length Format",
|
"label": "Length Format",
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
// Arduino Moving Average Library
|
|
||||||
// https://github.com/JChristensen/movingAvg
|
|
||||||
// Copyright (C) 2018 by Jack Christensen and licensed under
|
|
||||||
// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
|
|
||||||
|
|
||||||
#include <movingAvg.h>
|
|
||||||
|
|
||||||
// initialize - allocate the interval array
|
|
||||||
void movingAvg::begin()
|
|
||||||
{
|
|
||||||
m_readings = new int[m_interval];
|
|
||||||
}
|
|
||||||
|
|
||||||
// add a new reading and return the new moving average
|
|
||||||
int movingAvg::reading(int newReading)
|
|
||||||
{
|
|
||||||
// add each new data point to the sum until the m_readings array is filled
|
|
||||||
if (m_nbrReadings < m_interval) {
|
|
||||||
++m_nbrReadings;
|
|
||||||
m_sum += newReading;
|
|
||||||
}
|
|
||||||
// once the array is filled, subtract the oldest data point and add the new one
|
|
||||||
else {
|
|
||||||
m_sum = m_sum - m_readings[m_next] + newReading;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_readings[m_next] = newReading;
|
|
||||||
if (++m_next >= m_interval) m_next = 0;
|
|
||||||
return (m_sum + m_nbrReadings / 2) / m_nbrReadings;
|
|
||||||
}
|
|
||||||
|
|
||||||
// just return the current moving average
|
|
||||||
int movingAvg::getAvg()
|
|
||||||
{
|
|
||||||
return (m_sum + m_nbrReadings / 2) / m_nbrReadings;
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the average for a subset of the data, the most recent nPoints readings.
|
|
||||||
// for invalid values of nPoints, return zero.
|
|
||||||
int movingAvg::getAvg(int nPoints)
|
|
||||||
{
|
|
||||||
if (nPoints < 1 || nPoints > m_interval || nPoints > m_nbrReadings) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
long sum{0};
|
|
||||||
int i = m_next;
|
|
||||||
for (int n=0; n<nPoints; ++n) {
|
|
||||||
if (i == 0) {
|
|
||||||
i = m_interval - 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
--i;
|
|
||||||
}
|
|
||||||
sum += m_readings[i];
|
|
||||||
}
|
|
||||||
return (sum + nPoints / 2) / nPoints;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// start the moving average over again
|
|
||||||
void movingAvg::reset()
|
|
||||||
{
|
|
||||||
m_nbrReadings = 0;
|
|
||||||
m_sum = 0;
|
|
||||||
m_next = 0;
|
|
||||||
}
|
|
||||||
@@ -3,27 +3,37 @@
|
|||||||
// Copyright (C) 2018 by Jack Christensen and licensed under
|
// Copyright (C) 2018 by Jack Christensen and licensed under
|
||||||
// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
|
// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
|
||||||
|
|
||||||
|
// Extended to template class for handling of multiple data types
|
||||||
|
|
||||||
#ifndef MOVINGAVG_H_INCLUDED
|
#ifndef MOVINGAVG_H_INCLUDED
|
||||||
#define MOVINGAVG_H_INCLUDED
|
#define MOVINGAVG_H_INCLUDED
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
class movingAvg
|
class movingAvg
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
movingAvg(int interval)
|
movingAvg(int interval)
|
||||||
: m_interval{interval}, m_nbrReadings{0}, m_sum{0}, m_next{0} {}
|
: m_interval{interval}, m_nbrReadings{0}, m_sum{0}, m_next{0}, m_readings{nullptr} {}
|
||||||
|
~movingAvg() { delete[] m_readings; }
|
||||||
void begin();
|
void begin();
|
||||||
int reading(int newReading);
|
T reading(T newReading);
|
||||||
int getAvg();
|
T getAvg();
|
||||||
int getAvg(int nPoints);
|
T getAvg(int nPoints);
|
||||||
int getCount() {return m_nbrReadings;}
|
int getCount() { return m_nbrReadings; }
|
||||||
void reset();
|
void reset();
|
||||||
int* getReadings() {return m_readings;}
|
T* getReadings() { return m_readings; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_interval; // number of data points for the moving average
|
int m_interval; // number of data points for the moving average
|
||||||
int m_nbrReadings; // number of readings
|
int m_nbrReadings; // number of readings
|
||||||
long m_sum; // sum of the m_readings array
|
// Sum type adapts to T: long for integers, T for floating point
|
||||||
|
using SumType = typename std::conditional<std::is_floating_point<T>::value, T, long>::type;
|
||||||
|
SumType m_sum;
|
||||||
int m_next; // index to the next reading
|
int m_next; // index to the next reading
|
||||||
int* m_readings; // pointer to the dynamically allocated interval array
|
T* m_readings; // pointer to the dynamically allocated interval array
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Include the implementation to satisfy template instantiation requirements
|
||||||
|
#include "movingAvg.tpp"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// Arduino Moving Average Library
|
||||||
|
// https://github.com/JChristensen/movingAvg
|
||||||
|
// Copyright (C) 2018 by Jack Christensen and licensed under
|
||||||
|
// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
|
||||||
|
|
||||||
|
// Extended to template class for handling of multiple data types
|
||||||
|
|
||||||
|
//template <typename T>
|
||||||
|
//movingAvg<T>::movingAvg(int interval)
|
||||||
|
// : m_interval{interval}, m_nbrReadings{0}, m_sum{0}, m_next{0}, m_readings{nullptr}
|
||||||
|
//{}
|
||||||
|
|
||||||
|
// initialize - allocate the interval array
|
||||||
|
template <typename T>
|
||||||
|
void movingAvg<T>::begin()
|
||||||
|
{
|
||||||
|
m_readings = new T[m_interval];
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a new reading and return the new moving average
|
||||||
|
template <typename T>
|
||||||
|
T movingAvg<T>::reading(T newReading)
|
||||||
|
{
|
||||||
|
// add each new data point to the sum until the m_readings array is filled
|
||||||
|
if (m_nbrReadings < m_interval) {
|
||||||
|
++m_nbrReadings;
|
||||||
|
m_sum += newReading;
|
||||||
|
}
|
||||||
|
// once the array is filled, subtract the oldest data point and add the new one
|
||||||
|
else {
|
||||||
|
m_sum = m_sum - m_readings[m_next] + newReading;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_readings[m_next] = newReading;
|
||||||
|
if (++m_next >= m_interval) m_next = 0;
|
||||||
|
return getAvg();
|
||||||
|
}
|
||||||
|
|
||||||
|
// just return the current moving average
|
||||||
|
template <typename T>
|
||||||
|
T movingAvg<T>::getAvg()
|
||||||
|
{
|
||||||
|
if (m_nbrReadings == 0) return 0;
|
||||||
|
|
||||||
|
// Apply rounding for integers only
|
||||||
|
if (std::is_floating_point<T>::value) {
|
||||||
|
return m_sum / m_nbrReadings;
|
||||||
|
} else {
|
||||||
|
return (m_sum + (SumType)m_nbrReadings / 2) / m_nbrReadings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the average for a subset of the data, the most recent nPoints readings.
|
||||||
|
// for invalid values of nPoints, return zero.
|
||||||
|
template <typename T>
|
||||||
|
T movingAvg<T>::getAvg(int nPoints)
|
||||||
|
{
|
||||||
|
if (nPoints < 1 || nPoints > m_interval || nPoints > m_nbrReadings) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SumType sum{0};
|
||||||
|
int i = m_next;
|
||||||
|
for (int n=0; n<nPoints; ++n) {
|
||||||
|
if (i == 0) {
|
||||||
|
i = m_interval - 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
sum += m_readings[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::is_floating_point<T>::value) {
|
||||||
|
return sum / nPoints;
|
||||||
|
} else {
|
||||||
|
return (sum + (SumType)nPoints / 2) / nPoints; //
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start the moving average over again
|
||||||
|
template <typename T>
|
||||||
|
void movingAvg<T>::reset()
|
||||||
|
{
|
||||||
|
m_nbrReadings = 0;
|
||||||
|
m_sum = 0;
|
||||||
|
m_next = 0;
|
||||||
|
}
|
||||||
@@ -498,7 +498,8 @@ void OBP60Task(GwApi *api){
|
|||||||
pages[i].parameters.values.push_back(value);
|
pages[i].parameters.values.push_back(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the specified boat data types of relevant pages and create a history buffer for each type
|
// Read the specified boat data types of relevant pages and create a history buffer for each type for later use in charts
|
||||||
|
// applies only for pages that uses charts
|
||||||
if (pages[i].parameters.pageName == "OneValue" || pages[i].parameters.pageName == "TwoValues" || pages[i].parameters.pageName == "WindPlot") {
|
if (pages[i].parameters.pageName == "OneValue" || pages[i].parameters.pageName == "TwoValues" || pages[i].parameters.pageName == "WindPlot") {
|
||||||
for (auto pVal : pages[i].parameters.values) {
|
for (auto pVal : pages[i].parameters.values) {
|
||||||
hstryBufferList.addBuffer(pVal->getName());
|
hstryBufferList.addBuffer(pVal->getName());
|
||||||
@@ -843,11 +844,9 @@ void OBP60Task(GwApi *api){
|
|||||||
api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues);
|
api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues);
|
||||||
api->getStatus(commonData.status);
|
api->getStatus(commonData.status);
|
||||||
|
|
||||||
if (calcTrueWnds) {
|
trueWind.handleWinds(calcTrueWnds); // calculate true wind data from apparent wind values
|
||||||
trueWind.addWinds(); // calculate true wind data from apparent wind values
|
|
||||||
}
|
|
||||||
calibrationDataList.handleCalibration(&boatValues); // Process calibration for all boat data in <calibrationDataList>
|
calibrationDataList.handleCalibration(&boatValues); // Process calibration for all boat data in <calibrationDataList>
|
||||||
hstryBufferList.handleHstryBufs(useSimuData, commonData); // Handle history buffers for certain boat data for windplot page and other usage
|
hstryBufferList.handleHstryBufs(useSimuData, commonData); // Handle history buffers for certain boat data for charts and other usage
|
||||||
|
|
||||||
// Clear display
|
// Clear display
|
||||||
// getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);
|
// getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);
|
||||||
|
|||||||
Reference in New Issue
Block a user