mirror of
https://github.com/thooge/esp32-nmea2000-obp60.git
synced 2026-02-24 20:53:07 +01:00
- change control of key settings on PageOneValue + PageTwoValues
- added taskYIELD() to chart loop to be nice to other tasks - fix typo in config_obp40.json - fine tune chart labels - disable debug messages
This commit is contained in:
@@ -135,7 +135,7 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
||||
double dataValue = 0;
|
||||
std::string format = "";
|
||||
|
||||
// we test this earlier, but for safety reason ...
|
||||
// we test this earlier, but for safety reasons ...
|
||||
if (calibrationMap.find(instance) == calibrationMap.end()) {
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
||||
return false;
|
||||
@@ -151,7 +151,7 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
||||
slope = calibrationMap[instance].slope;
|
||||
dataValue = boatDataValue->value;
|
||||
format = boatDataValue->getFormat().c_str();
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: value: %f, format: %s", instance.c_str(), dataValue, format.c_str());
|
||||
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: value: %f, format: %s", instance.c_str(), dataValue, format.c_str());
|
||||
|
||||
if (format == "formatWind") { // instance is of type angle
|
||||
dataValue = (dataValue * slope) + offset;
|
||||
@@ -174,7 +174,7 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
||||
calibrationMap[instance].value = dataValue; // store the calibrated value in the list
|
||||
calibrationMap[instance].isCalibrated = true;
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: Offset: %f, Slope: %f, Result: %f", instance.c_str(), offset, slope, calibrationMap[instance].value);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: Offset: %f, Slope: %f, Result: %f", instance.c_str(), offset, slope, calibrationMap[instance].value);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ bool CalibrationData::smoothInstance(GwApi::BoatValue* boatDataValue)
|
||||
|
||||
// we test this earlier, but for safety reason ...
|
||||
if (calibrationMap.find(instance) == calibrationMap.end()) {
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
||||
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ bool CalibrationData::smoothInstance(GwApi::BoatValue* boatDataValue)
|
||||
calibrationMap[instance].value = dataValue; // store the smoothed value in the list
|
||||
calibrationMap[instance].isCalibrated = true;
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: smooth: %f, oldValue: %f, result: %f", instance.c_str(), smoothFactor, oldValue, calibrationMap[instance].value);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: smooth: %f, oldValue: %f, result: %f", instance.c_str(), smoothFactor, oldValue, calibrationMap[instance].value);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -241,7 +241,7 @@ void HstryBuf::add(double value)
|
||||
{
|
||||
if (value >= hstryMin && value <= hstryMax) {
|
||||
hstryBuf.add(value);
|
||||
LOG_DEBUG(GwLog::DEBUG, "HstryBuf::add: name: %s, value: %.3f", hstryBuf.getName(), value);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "HstryBuf::add: name: %s, value: %.3f", hstryBuf.getName(), value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ void WindUtils::calcTwdSA(const double* AWA, const double* AWS,
|
||||
double stw = -*STW;
|
||||
addPolar(AWD, AWS, CTW, &stw, TWD, TWS);
|
||||
|
||||
// Normalize TWD and TWA to 0-360°/2PI
|
||||
// Normalize TWD to [0..360°] (2PI) and TWA to [-180..180] (PI)
|
||||
*TWD = to2PI(*TWD);
|
||||
*TWA = toPI(*TWD - *HDT);
|
||||
}
|
||||
@@ -487,8 +487,8 @@ bool WindUtils::addWinds()
|
||||
double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
|
||||
double hdmVal = hdmBVal->valid ? hdmBVal->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,
|
||||
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);
|
||||
//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,
|
||||
// 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);
|
||||
|
||||
// Check if TWD can be calculated from TWA and HDT/HDM
|
||||
if (twaBVal->valid) {
|
||||
@@ -528,8 +528,8 @@ bool WindUtils::addWinds()
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: 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);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: 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);
|
||||
|
||||
return twCalculated;
|
||||
}
|
||||
|
||||
@@ -161,8 +161,8 @@ bool Chart::setChartDimensions(const char direction, const int8_t size)
|
||||
break;
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(GwLog::ERROR, "obp60:setChartDimensions %s: direction: %c, size: %d, dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot{%d, %d}, top: %d, bottom: %d, hGap: %d, vGap: %d",
|
||||
dataBuf.getName(), direction, size, dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, top, bottom, hGap, vGap);
|
||||
//LOG_DEBUG(GwLog::DEBUG, "obp60:setChartDimensions %s: direction: %c, size: %d, dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot{%d, %d}, top: %d, bottom: %d, hGap: %d, vGap: %d",
|
||||
// dataBuf.getName(), direction, size, dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, top, bottom, hGap, vGap);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ void Chart::drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue
|
||||
// LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
|
||||
calcChrtBorders(chrtMin, chrtMid, chrtMax, chrtRng);
|
||||
chrtScale = double(valAxis) / chrtRng; // Chart scale: pixels per value step
|
||||
LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
|
||||
|
||||
// Do we have valid buffer data?
|
||||
if (dataBuf.getMax() == dbMAX_VAL) { // only <MAX_VAL> values in buffer -> no valid wind data available
|
||||
@@ -261,8 +261,8 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
|
||||
}
|
||||
recalcRngMid = false; // Reset flag for <rngMid> determination
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "calcChrtRange: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||
rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "calcChrtRange: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||
// rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,8 +287,8 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
|
||||
|
||||
rng = halfRng * 2.0;
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, tmpRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||
tmpRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, tmpRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||
// tmpRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||
|
||||
} else { // chart data is of any other type
|
||||
|
||||
@@ -320,8 +320,8 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
|
||||
rngMid = (rngMin + rngMax) / 2.0;
|
||||
rng = rngMax - rngMin;
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f",
|
||||
currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f",
|
||||
// currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +397,8 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
taskYIELD(); // we run for 50-150ms; be polite to other tasks with same priority
|
||||
}
|
||||
}
|
||||
|
||||
@@ -656,7 +658,7 @@ void Chart::prntHorizChartThreeValueAxisLabel(const GFXfont* font)
|
||||
|
||||
if (font == &Ubuntu_Bold10pt8b) {
|
||||
xOffset = 39;
|
||||
yOffset = 15;
|
||||
yOffset = 16;
|
||||
} else if (font == &Ubuntu_Bold12pt8b) {
|
||||
xOffset = 51;
|
||||
yOffset = 18;
|
||||
@@ -718,7 +720,7 @@ void Chart::prntHorizChartMultiValueAxisLabel(const GFXfont* font)
|
||||
axSlots = valAxis / static_cast<double>(VALAXIS_STEP); // number of axis labels (and we want to have a double calculation, no integer)
|
||||
axIntv = chrtRng / axSlots;
|
||||
axLabel = chrtMin + axIntv;
|
||||
LOG_DEBUG(GwLog::DEBUG, "Chart::printHorizMultiValueAxisLabel: chrtRng: %.2f, th-chrtRng: %.2f, axSlots: %.2f, axIntv: %.2f, axLabel: %.2f, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, this->chrtRng, axSlots, axIntv, axLabel, this->chrtMin, chrtMid, chrtMax);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "Chart::printHorizMultiValueAxisLabel: chrtRng: %.2f, th-chrtRng: %.2f, axSlots: %.2f, axIntv: %.2f, axLabel: %.2f, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, this->chrtRng, axSlots, axIntv, axLabel, this->chrtMin, chrtMid, chrtMax);
|
||||
|
||||
int loopStrt, loopEnd, loopStp;
|
||||
if (chrtDataFmt == SPEED || chrtDataFmt == TEMPERATURE || chrtDataFmt == OTHER) {
|
||||
|
||||
@@ -162,9 +162,13 @@ public:
|
||||
constexpr int ZOOM_KEY = 1;
|
||||
#endif
|
||||
|
||||
if (dataHstryBuf) { // show "Mode" key only if chart supported boat data type is available
|
||||
if (dataHstryBuf) { // show "Mode" key only if chart-supported boat data type is available
|
||||
commonData->keydata[0].label = "MODE";
|
||||
if (pageMode != VALUE) { // show "ZOOM" key only if chart is visible
|
||||
commonData->keydata[ZOOM_KEY].label = "ZOOM";
|
||||
} else {
|
||||
commonData->keydata[ZOOM_KEY].label = "";
|
||||
}
|
||||
} else {
|
||||
commonData->keydata[0].label = "";
|
||||
commonData->keydata[ZOOM_KEY].label = "";
|
||||
@@ -189,14 +193,15 @@ public:
|
||||
pageMode = VALUE;
|
||||
break;
|
||||
}
|
||||
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||
return 0; // Commit the key
|
||||
}
|
||||
|
||||
// Set time frame to show for history chart
|
||||
// Set time frame to show for chart
|
||||
#if defined BOARD_OBP60S3
|
||||
if (key == 5) {
|
||||
if (key == 5 && pageMode != VALUE) {
|
||||
#elif defined BOARD_OBP40S3
|
||||
if (key == 2) {
|
||||
if (key == 2 && pageMode != VALUE) {
|
||||
#endif
|
||||
if (dataIntv == 1) {
|
||||
dataIntv = 2;
|
||||
@@ -247,7 +252,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
setupKeys(); // adjust <mode> key depending on chart supported boat data type
|
||||
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||
}
|
||||
|
||||
int displayPage(PageData& pageData)
|
||||
|
||||
@@ -149,7 +149,11 @@ public:
|
||||
|
||||
if (dataHstryBuf[0] || dataHstryBuf[1]) { // show "Mode" key only if at least 1 chart supported boat data type is available
|
||||
commonData->keydata[0].label = "MODE";
|
||||
if (pageMode != VALUES) { // show "ZOOM" key only if chart is visible
|
||||
commonData->keydata[ZOOM_KEY].label = "ZOOM";
|
||||
} else {
|
||||
commonData->keydata[ZOOM_KEY].label = "";
|
||||
}
|
||||
} else {
|
||||
commonData->keydata[0].label = "";
|
||||
commonData->keydata[ZOOM_KEY].label = "";
|
||||
@@ -191,14 +195,15 @@ public:
|
||||
pageMode = VALUES;
|
||||
break;
|
||||
}
|
||||
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||
return 0; // Commit the key
|
||||
}
|
||||
|
||||
// Set time frame to show for history chart
|
||||
// Set time frame to show for chart
|
||||
#if defined BOARD_OBP60S3
|
||||
if (key == 5) {
|
||||
if (key == 5 && pageMode != VALUES) {
|
||||
#elif defined BOARD_OBP40S3
|
||||
if (key == 2) {
|
||||
if (key == 2 && pageMode != VALUES) {
|
||||
#endif
|
||||
if (dataIntv == 1) {
|
||||
dataIntv = 2;
|
||||
@@ -251,7 +256,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
setupKeys(); // adjust <mode> key depending on chart supported boat data type
|
||||
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||
}
|
||||
|
||||
int displayPage(PageData& pageData)
|
||||
@@ -285,13 +290,13 @@ public:
|
||||
showData(bValue, FULL);
|
||||
|
||||
} else if (pageMode == VAL1_CHART) { // show data value 1 and chart
|
||||
showData({bValue[0]}, HALF);
|
||||
showData({ bValue[0] }, HALF);
|
||||
if (dataChart[0]) {
|
||||
dataChart[0]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[0]);
|
||||
}
|
||||
|
||||
} else if (pageMode == VAL2_CHART) { // show data value 2 and chart
|
||||
showData({bValue[1]}, HALF);
|
||||
showData({ bValue[1] }, HALF);
|
||||
if (dataChart[1]) {
|
||||
dataChart[1]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[1]);
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ public:
|
||||
int displayPage(PageData& pageData)
|
||||
{
|
||||
LOG_DEBUG(GwLog::LOG, "Display PageWindPlot");
|
||||
ulong pageTime = millis();
|
||||
// ulong pageTime = millis();
|
||||
|
||||
if (showTruW != oldShowTruW) {
|
||||
|
||||
@@ -243,7 +243,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: page time %ldms", millis() - pageTime);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: page time %ldms", millis() - pageTime);
|
||||
return PAGE_UPDATE;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1818,7 +1818,7 @@
|
||||
"description": "Wind source for page 1: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 1",
|
||||
"capabilities": {
|
||||
@@ -2140,7 +2140,7 @@
|
||||
"description": "Wind source for page 2: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 2",
|
||||
"capabilities": {
|
||||
@@ -2453,7 +2453,7 @@
|
||||
"description": "Wind source for page 3: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 3",
|
||||
"capabilities": {
|
||||
@@ -2757,7 +2757,7 @@
|
||||
"description": "Wind source for page 4: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 4",
|
||||
"capabilities": {
|
||||
@@ -3052,7 +3052,7 @@
|
||||
"description": "Wind source for page 5: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 5",
|
||||
"capabilities": {
|
||||
@@ -3338,7 +3338,7 @@
|
||||
"description": "Wind source for page 6: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 6",
|
||||
"capabilities": {
|
||||
@@ -3615,7 +3615,7 @@
|
||||
"description": "Wind source for page 7: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 7",
|
||||
"capabilities": {
|
||||
@@ -3883,7 +3883,7 @@
|
||||
"description": "Wind source for page 8: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 8",
|
||||
"capabilities": {
|
||||
@@ -4142,7 +4142,7 @@
|
||||
"description": "Wind source for page 9: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 9",
|
||||
"capabilities": {
|
||||
@@ -4392,7 +4392,7 @@
|
||||
"description": "Wind source for page 10: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 10",
|
||||
"capabilities": {
|
||||
|
||||
Reference in New Issue
Block a user