diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp new file mode 100644 index 0000000..cc6ac2a --- /dev/null +++ b/lib/obp60task/OBPDataOperations.cpp @@ -0,0 +1,158 @@ +#include "OBPDataOperations.h" + +double WindUtils::to2PI(double a) +{ + a = fmod(a, 2 * M_PI); + if (a < 0.0) { + a += 2 * M_PI; + } + return a; +} + +double WindUtils::toPI(double a) +{ + a += M_PI; + a = to2PI(a); + a -= M_PI; + + return a; +} + +double WindUtils::to360(double a) +{ + a = fmod(a, 360); + if (a < 0.0) { + a += 360; + } + return a; +} + +double WindUtils::to180(double a) +{ + a += 180; + a = to360(a); + a -= 180; + + return a; +} + +void WindUtils::toCart(const double* phi, const double* r, double* x, double* y) +{ + *x = *r * sin(*phi); + *y = *r * cos(*phi); +} + +void WindUtils::toPol(const double* x, const double* y, double* phi, double* r) +{ + *phi = (M_PI / 2) - atan2(*y, *x); + *phi = to2PI(*phi); + *r = sqrt(*x * *x + *y * *y); +} + +void WindUtils::addPolar(const double* phi1, const double* r1, + const double* phi2, const double* r2, + double* phi, double* r) +{ + double x1, y1, x2, y2; + toCart(phi1, r1, &x1, &y1); + toCart(phi2, r2, &x2, &y2); + x1 += x2; + y1 += y2; + toPol(&x1, &y1, phi, r); +} + +void WindUtils::calcTwdSA(const double* AWA, const double* AWS, + const double* CTW, const double* STW, const double* HDT, + double* TWD, double* TWS, double* TWA) +{ + double awd = *AWA + *HDT; + awd = to2PI(awd); + double stw = -*STW; + // 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); + + // Normalize TWD and TWA to 0-360° + *TWD = to2PI(*TWD); + *TWA = toPI(*TWD - *HDT); + // Serial.println("calcTwdSA: TWD: " + String(*TWD) + ", TWS: " + String(*TWS)); +} + +bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal, + const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, + const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal) +{ + double stw, hdt, ctw; + double twd, tws, twa; + static const double DBL_MIN = std::numeric_limits::lowest(); + + if (*hdtVal != DBL_MIN) { + hdt = *hdtVal; // Use HDT if 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 { + return false; // Cannot calculate without valid HDT or HDM + } + } + + 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 + return false; + } else { + calcTwdSA(awaVal, awsVal, &ctw, stwVal, &hdt, &twd, &tws, &twa); + *twdVal = twd; + *twsVal = tws; + *twaVal = twa; + + return true; + } +} + +void HstryBuf::fillWndBufSimData(tBoatHstryData& hstryBufs) +// Fill most part of TWD and TWS history buffer with simulated data +{ + double value = 20.0; + int16_t value2 = 0; + for (int i = 0; i < 900; i++) { + value += random(-20, 20); + value = WindUtils::to360(value); + value2 = static_cast(value * DEG_TO_RAD * 1000); + hstryBufs.twdHstry->add(value2); + } +} + +/* double genTwdSimDat() +{ + simTwd += random(-20, 20); + if (simTwd < 0.0) + simTwd += 360.0; + if (simTwd >= 360.0) + simTwd -= 360.0; + + int16_t z = static_cast(DegToRad(simTwd) * 1000.0); + pageData.boatHstry.twdHstry->add(z); // Fill the buffer with some test data + + 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; +}*/ diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h new file mode 100644 index 0000000..c9e4386 --- /dev/null +++ b/lib/obp60task/OBPDataOperations.h @@ -0,0 +1,36 @@ +#pragma once +#include "GwApi.h" +#include "OBPRingBuffer.h" +#include +#include + +typedef struct { + RingBuffer* twdHstry; + RingBuffer* 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 { + +public: + static double to2PI(double a); + static double toPI(double a); + static double to360(double a); + static double to180(double a); + 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 addPolar(const double* phi1, const double* r1, + const double* phi2, const double* r2, + double* phi, double* r); + static void calcTwdSA(const double* AWA, const double* AWS, + const double* CTW, const double* STW, const double* HDT, + 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); +}; \ No newline at end of file diff --git a/lib/obp60task/OBPRingBuffer.h b/lib/obp60task/OBPRingBuffer.h new file mode 100644 index 0000000..4b5e5bd --- /dev/null +++ b/lib/obp60task/OBPRingBuffer.h @@ -0,0 +1,60 @@ +#pragma once +#include "GwSynchronized.h" +#include +#include +#include +#include +#include "WString.h" + +template +class RingBuffer { +private: + mutable SemaphoreHandle_t bufLocker; + std::vector buffer; + size_t capacity; + size_t head; // Points to the next insertion position + size_t first; // Points to the first (oldest) valid element + size_t last; // Points to the last (newest) valid element + size_t count; // Number of valid elements currently in buffer + bool is_Full; // Indicates that all buffer elements are used and ringing is in use + T MIN_VAL; // lowest possible value of buffer + T MAX_VAL; // highest possible value of buffer of type + + // metadata for buffer + String dataName; // Name of boat data in buffer + String dataFmt; // Format of boat data in buffer + int updFreq; // Update frequency in milliseconds + T smallest; // Value range of buffer: smallest value + T largest; // Value range of buffer: biggest value + +public: + RingBuffer(size_t size); + 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 + String getName() const; // Get buffer name + 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 getFirst() const; // Get the first (oldest) value in buffer + T getLast() const; // Get the last (newest) value in buffer + T getMin() const; // Get the lowest value in buffer + T getMin(size_t amount) const; // Get minimum value of the last values of buffer + T getMax() const; // Get the highest value in buffer + T getMax(size_t amount) const; // Get maximum value of the last values of buffer + T getMid() const; // Get mid value between and value in buffer + T getMid(size_t amount) const; // Get mid value between and value of the last values of buffer + T getMedian() const; // Get the median value in buffer + T getMedian(size_t amount) const; // Get the median value of the last values of buffer + size_t getCapacity() const; // Get the buffer capacity (maximum size) + size_t getCurrentSize() const; // Get the current number of elements in buffer + size_t getFirstIdx() const; // Get the index of oldest value in buffer + size_t getLastIdx() const; // Get the index of newest value in buffer + bool isEmpty() const; // Check if buffer is empty + bool isFull() const; // Check if buffer is full + T getMinVal() const; // Get lowest possible value for buffer; used for initialized buffer data + T getMaxVal() const; // Get highest possible value for buffer + void clear(); // Clear buffer + T operator[](size_t index) const; // Operator[] for convenient access (same as get()) + std::vector getAllValues() const; // Get all current values as a vector +}; + +#include "OBPRingBuffer.tpp" \ No newline at end of file diff --git a/lib/obp60task/OBPRingBuffer.tpp b/lib/obp60task/OBPRingBuffer.tpp new file mode 100644 index 0000000..a0da425 --- /dev/null +++ b/lib/obp60task/OBPRingBuffer.tpp @@ -0,0 +1,376 @@ +#include "OBPRingBuffer.h" + +template +RingBuffer::RingBuffer(size_t size) + : capacity(size) + , head(0) + , first(0) + , last(0) + , count(0) + , is_Full(false) +{ + bufLocker = xSemaphoreCreateMutex(); + + if (size == 0) { + // return false; + } + + MIN_VAL = std::numeric_limits::lowest(); + MAX_VAL = std::numeric_limits::max(); + dataName = ""; + dataFmt = ""; + updFreq = -1; + smallest = MIN_VAL; + largest = MAX_VAL; + + buffer.resize(size, MIN_VAL); + + // return true; +} + +// Specify meta data of buffer content +template +void RingBuffer::setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue) +{ + GWSYNCHRONIZED(&bufLocker); + dataName = name; + dataFmt = format; + updFreq = updateFrequency; + smallest = std::max(MIN_VAL, minValue); + largest = std::min(MAX_VAL, maxValue); +} + +// Get meta data of buffer content +template +bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue) +{ + if (dataName == "" || dataFmt == "" || updFreq == -1) { + return false; // Meta data not set + } + + GWSYNCHRONIZED(&bufLocker); + name = dataName; + format = dataFmt; + updateFrequency = updFreq; + minValue = smallest; + maxValue = largest; + return true; +} + +// Get buffer name +template +String RingBuffer::getName() const +{ + return dataName; +} + +// Add a new value to buffer +template +void RingBuffer::add(const T& value) +{ + GWSYNCHRONIZED(&bufLocker); + if (value < smallest || value > largest) { + buffer[head] = MIN_VAL; // Store MIN_VAL if value is out of range + } else { + buffer[head] = value; + } + last = head; + + if (is_Full) { + first = (first + 1) % capacity; // Move pointer to oldest element when overwriting + } else { + count++; + if (count == capacity) { + is_Full = true; + } + } + + head = (head + 1) % capacity; +} + +// Get value at specific position (0-based index from oldest to newest) +template +T RingBuffer::get(size_t index) const +{ + GWSYNCHRONIZED(&bufLocker); + if (isEmpty() || index < 0 || index >= count) { + return MIN_VAL; + } + + size_t realIndex = (first + index) % capacity; + return buffer[realIndex]; +} + +// Operator[] for convenient access (same as get()) +template +T RingBuffer::operator[](size_t index) const +{ + return get(index); +} + +// Get the first (oldest) value in the buffer +template +T RingBuffer::getFirst() const +{ + if (isEmpty()) { + return MIN_VAL; + } + return get(0); +} + +// Get the last (newest) value in the buffer +template +T RingBuffer::getLast() const +{ + if (isEmpty()) { + return MIN_VAL; + } + return get(count - 1); +} + +// Get the lowest value in the buffer +template +T RingBuffer::getMin() const +{ + if (isEmpty()) { + return MIN_VAL; + } + + T minVal = MAX_VAL; + T value; + for (size_t i = 0; i < count; i++) { + value = get(i); + if (value < minVal && value != MIN_VAL) { + minVal = value; + } + } + return minVal; +} + +// Get minimum value of the last values of buffer +template +T RingBuffer::getMin(size_t amount) const +{ + if (isEmpty() || amount <= 0) { + return MIN_VAL; + } + if (amount > count) + amount = count; + + T minVal = MAX_VAL; + T value; + for (size_t i = 0; i < amount; i++) { + value = get(count - 1 - i); + if (value < minVal && value != MIN_VAL) { + minVal = value; + } + } + return minVal; +} + +// Get the highest value in the buffer +template +T RingBuffer::getMax() const +{ + if (isEmpty()) { + return MIN_VAL; + } + + T maxVal = MIN_VAL; + T value; + for (size_t i = 0; i < count; i++) { + value = get(i); + if (value > maxVal && value != MIN_VAL) { + maxVal = value; + } + } + return maxVal; +} + +// Get maximum value of the last values of buffer +template +T RingBuffer::getMax(size_t amount) const +{ + if (isEmpty() || amount <= 0) { + return MIN_VAL; + } + if (amount > count) + amount = count; + + T maxVal = MIN_VAL; + T value; + for (size_t i = 0; i < amount; i++) { + value = get(count - 1 - i); + if (value > maxVal && value != MIN_VAL) { + maxVal = value; + } + } + return maxVal; +} + +// Get mid value between and value in the buffer +template +T RingBuffer::getMid() const +{ + if (isEmpty()) { + return MIN_VAL; + } + + return (getMin() + getMax()) / static_cast(2); +} + +// Get mid value between and value of the last values of buffer +template +T RingBuffer::getMid(size_t amount) const +{ + if (isEmpty() || amount <= 0) { + return MIN_VAL; + } + + if (amount > count) + amount = count; + + return (getMin(amount) + getMax(amount)) / static_cast(2); +} + +// Get the median value in the buffer +template +T RingBuffer::getMedian() const +{ + if (isEmpty()) { + return MIN_VAL; + } + + // Create a temporary vector with current valid elements + std::vector temp; + temp.reserve(count); + + for (size_t i = 0; i < count; i++) { + temp.push_back(get(i)); + } + + // Sort to find median + std::sort(temp.begin(), temp.end()); + + if (count % 2 == 1) { + // Odd number of elements + return temp[count / 2]; + } else { + // Even number of elements - return average of middle two + // Note: For integer types, this truncates. For floating point, it's exact. + return (temp[count / 2 - 1] + temp[count / 2]) / 2; + } +} + +// Get the median value of the last values of buffer +template +T RingBuffer::getMedian(size_t amount) const +{ + if (isEmpty() || amount <= 0) { + return MIN_VAL; + } + if (amount > count) + amount = count; + + // Create a temporary vector with current valid elements + std::vector temp; + temp.reserve(amount); + + for (size_t i = 0; i < amount; i++) { + temp.push_back(get(i)); + } + + // Sort to find median + std::sort(temp.begin(), temp.end()); + + if (amount % 2 == 1) { + // Odd number of elements + return temp[amount / 2]; + } else { + // Even number of elements - return average of middle two + // Note: For integer types, this truncates. For floating point, it's exact. + return (temp[amount / 2 - 1] + temp[amount / 2]) / 2; + } +} + +// Get the buffer capacity (maximum size) +template +size_t RingBuffer::getCapacity() const +{ + return capacity; +} + +// Get the current number of elements in the buffer +template +size_t RingBuffer::getCurrentSize() const +{ + return count; +} + +// Get the first index of buffer +template +size_t RingBuffer::getFirstIdx() const +{ + return first; +} + +// Get the last index of buffer +template +size_t RingBuffer::getLastIdx() const +{ + return last; +} + +// Check if buffer is empty +template +bool RingBuffer::isEmpty() const +{ + return count == 0; +} + +// Check if buffer is full +template +bool RingBuffer::isFull() const +{ + return is_Full; +} + +// Get lowest possible value for buffer; used for non-set buffer data +template +T RingBuffer::getMinVal() const +{ + return MIN_VAL; +} + +// Get highest possible value for buffer +template +T RingBuffer::getMaxVal() const +{ + return MAX_VAL; +} + +// Clear buffer +template +void RingBuffer::clear() +{ + GWSYNCHRONIZED(&bufLocker); + head = 0; + first = 0; + last = 0; + count = 0; + is_Full = false; +} + +// Get all current values as a vector +template +std::vector RingBuffer::getAllValues() const +{ + std::vector result; + result.reserve(count); + + for (size_t i = 0; i < count; i++) { + result.push_back(get(i)); + } + + return result; +} \ No newline at end of file diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp new file mode 100644 index 0000000..cf6430c --- /dev/null +++ b/lib/obp60task/PageWindPlot.cpp @@ -0,0 +1,489 @@ +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 + +#include "BoatDataCalibration.h" +#include "OBP60Extensions.h" +#include "OBPRingBuffer.h" +#include "Pagedata.h" +#include + +static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees + +// Get maximum difference of last of TWD ringbuffer values to center chart +int getRng(const RingBuffer& windDirHstry, int center, size_t amount) +{ + int minVal = windDirHstry.getMinVal(); + size_t count = windDirHstry.getCurrentSize(); + // size_t capacity = windDirHstry.getCapacity(); + // size_t last = windDirHstry.getLastIdx(); + + if (windDirHstry.isEmpty() || amount <= 0) { + return minVal; + } + if (amount > count) + amount = count; + + int value = 0; + int rng = 0; + int maxRng = minVal; + // Start from the newest value (last) and go backwards x times + for (size_t i = 0; i < amount; i++) { + // value = windDirHstry.get(((last - i) % capacity + capacity) % capacity); + value = windDirHstry.get(count - 1 - i); + + if (value == minVal) { + continue; + } + + value = value / 1000.0 * radToDeg; + rng = abs(((value - center + 540) % 360) - 180); + if (rng > maxRng) + maxRng = rng; + } + if (maxRng > 180) { + maxRng = 180; + } + + return maxRng; +} + +// **************************************************************** +class PageWindPlot : public Page { + + bool keylock = false; // Keylock + char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both + int dataIntv = 1; // Update interval for wind history chart: + // (1)|(2)|(3)|(4) seconds for approx. 4, 8, 12, 16 min. history chart + bool showTWS = true; // Show TWS value in chart area + +public: + PageWindPlot(CommonData& common) + { + commonData = &common; + common.logger->logDebug(GwLog::LOG, "Instantiate PageWindPlot"); + } + + virtual void setupKeys() + { + Page::setupKeys(); + // commonData->keydata[0].label = "MODE"; + commonData->keydata[1].label = "INTV"; + commonData->keydata[4].label = "TWS"; + } + + // Key functions + virtual int handleKey(int key) + { + // Set chart mode TWD | TWS -> to be implemented + if (key == 1) { + if (chrtMode == 'D') { + chrtMode = 'S'; + } else if (chrtMode == 'S') { + chrtMode = 'B'; + } else { + chrtMode = 'D'; + } + return 0; // Commit the key + } + + // Set interval for wind history chart update time + if (key == 2) { + if (dataIntv == 1) { + dataIntv = 2; + } else if (dataIntv == 2) { + dataIntv = 3; + } else if (dataIntv == 3) { + dataIntv = 4; + } else { + dataIntv = 1; + } + return 0; // Commit the key + } + + // Switch TWS on/off + if (key == 5) { + showTWS = !showTWS; + return 0; // Commit the key + } + + // Keylock function + if (key == 11) { // Code for keylock + commonData->keylock = !commonData->keylock; + return 0; // Commit the key + } + return key; + } + + int displayPage(PageData& pageData) + { + GwConfigHandler* config = commonData->config; + GwLog* logger = commonData->logger; + + float twsValue; // TWS value in chart area + static String twdName, twdUnit; // TWD name and unit + static int updFreq; // Update frequency for TWD + static int16_t twdLowest, twdHighest; // TWD range + // static int16_t twdBufMinVal; // lowest possible twd buffer value; used for non-set data + + // current boat data values; TWD only for validation test, TWS for display of current value + const int numBoatData = 2; + GwApi::BoatValue* bvalue; + String BDataName[numBoatData]; + double BDataValue[numBoatData]; + bool BDataValid[numBoatData]; + String BDataText[numBoatData]; + String BDataUnit[numBoatData]; + String BDataFormat[numBoatData]; + + static bool isInitialized = false; // Flag to indicate that page is initialized + static bool wndDataValid = false; // Flag to indicate if wind data is valid + static int numNoData; // Counter for multiple invalid data values in a row + static bool simulation = false; + static bool holdValues = false; + + static int width; // Screen width + static int height; // Screen height + static int xCenter; // Center of screen in x direction + static const int yOffset = 48; // Offset for y coordinates of chart area + static int cHeight; // height of chart area + static int bufSize; // History buffer size: 960 values for appox. 16 min. history chart + static int intvBufSize; // Buffer size used for currently selected time interval + int count; // current size of buffer + static int numWndVals; // number of wind values available for current interval selection + static int bufStart; // 1st data value in buffer to show + int numAddedBufVals; // Number of values added to buffer since last display + size_t currIdx; // Current index in TWD history buffer + static size_t lastIdx; // Last index of TWD history buffer + static size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added + static int oldDataIntv; // remember recent user selection of data interval + + static int wndCenter; // chart wind center value position + static int wndLeft; // chart wind left value position + static int wndRight; // chart wind right value position + 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 + 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 float simTws; // Simulation value for TWS + + int x, y; // x and y coordinates for drawing + static int prevX, prevY; // Last x and y coordinates for drawing + static float chrtScl; // Scale for wind values in pixels per degree + int chrtVal; // Current wind value + static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line + + LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); + + // Get config data + simulation = config->getBool(config->useSimuData); + holdValues = config->getBool(config->holdvalues); + String flashLED = config->getString(config->flashLED); + String backlightMode = config->getString(config->backlight); + + if (!isInitialized) { + width = getdisplay().width(); + height = getdisplay().height(); + xCenter = width / 2; + cHeight = height - yOffset - 22; + bufSize = pageData.boatHstry.twdHstry->getCapacity(); + numNoData = 0; + simTwd = pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg; + simTws = 0; + twsValue = 0; + bufStart = 0; + oldDataIntv = 0; + numAddedBufVals, currIdx, lastIdx = 0; + lastAddedIdx = pageData.boatHstry.twdHstry->getLastIdx(); + pageData.boatHstry.twdHstry->getMetaData(twdName, twdUnit, updFreq, twdLowest, twdHighest); + wndCenter = INT_MIN; + midWndDir = 0; + diffRng = dfltRng; + chrtRng = dfltRng; + + isInitialized = true; // Set flag to indicate that page is now initialized + } + + // 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]; + // BDataName[i] = xdrDelete(bvalue->getName()); + BDataName[i] = BDataName[i].substring(0, 6); // String length limit for value name + calibrationData.calibrateInstance(bvalue, logger); // Check if boat data value is to be calibrated + BDataValue[i] = bvalue->value; // Value as double in SI unit + BDataValid[i] = bvalue->valid; + BDataText[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + BDataUnit[i] = formatValue(bvalue, *commonData).unit; + BDataFormat[i] = bvalue->getFormat(); // Unit of value + } + + // Optical warning by limit violation (unused) + if (String(flashLED) == "Limit Violation") { + setBlinkingLED(false); + setFlashLED(false); + } + + // Identify buffer size and buffer start position for chart + count = pageData.boatHstry.twdHstry->getCurrentSize(); + currIdx = pageData.boatHstry.twdHstry->getLastIdx(); + numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display + if (dataIntv != oldDataIntv || count == 1) { + // new data interval selected by user + intvBufSize = cHeight * dataIntv; + numWndVals = min(count, (cHeight - 60) * dataIntv); + bufStart = max(0, count - numWndVals); + lastAddedIdx = currIdx; + oldDataIntv = dataIntv; + } else { + numWndVals = numWndVals + numAddedBufVals; + lastAddedIdx = currIdx; + if (count == bufSize) { + bufStart = max(0, bufStart - numAddedBufVals); + } + } + LOG_DEBUG(GwLog::DEBUG, "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", + count, pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg, pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384, BDataValid[0], + intvBufSize, numWndVals, bufStart, numAddedBufVals, pageData.boatHstry.twdHstry->getLastIdx(), oldDataIntv, dataIntv); + + // Set wndCenter from 1st real buffer value + if (wndCenter == INT_MIN || (wndCenter == 0 && count == 1)) { + midWndDir = pageData.boatHstry.twdHstry->getMid(numWndVals); + if (midWndDir != INT16_MIN) { + midWndDir = midWndDir / 1000.0 * radToDeg; + 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 { + // check and adjust range between left, center, and right chart limit + diffRng = getRng(*pageData.boatHstry.twdHstry, wndCenter, numWndVals); + diffRng = (diffRng == INT16_MIN ? 0 : diffRng); + if (diffRng > chrtRng) { + chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value + } else if (diffRng + 10 < chrtRng) { // Reduce chart range for higher resolution if possible + chrtRng = max(dfltRng, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); + } + } + chrtScl = float(width) / float(chrtRng) / 2.0; // Chart scale: pixels per degree + wndLeft = wndCenter - chrtRng; + if (wndLeft < 0) + wndLeft += 360; + wndRight = (chrtRng < 180 ? wndCenter + chrtRng : wndCenter + chrtRng - 1); + if (wndRight >= 360) + wndRight -= 360; + + // Draw page + //*********************************************************************** + + // Set display in partial refresh mode + getdisplay().setPartialWindow(0, 0, width, height); // Set partial update + getdisplay().setTextColor(commonData->fgcolor); + + // chart lines + getdisplay().fillRect(0, yOffset, width, 2, commonData->fgcolor); + getdisplay().fillRect(xCenter, yOffset, 1, cHeight, commonData->fgcolor); + + // chart labels + char sWndLbl[4]; // char buffer for Wind angle label + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(xCenter - 88, yOffset - 3); + getdisplay().print("TWD"); // Wind data name + snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); + drawTextCenter(xCenter, yOffset - 11, sWndLbl); + getdisplay().drawCircle(xCenter + 25, yOffset - 17, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(xCenter + 25, yOffset - 17, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(1, yOffset - 3); + snprintf(sWndLbl, 4, "%03d", (wndLeft < 0) ? (wndLeft + 360) : wndLeft); + getdisplay().print(sWndLbl); // Wind left value + getdisplay().drawCircle(46, yOffset - 17, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(46, yOffset - 17, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(width - 50, yOffset - 3); + snprintf(sWndLbl, 4, "%03d", (wndRight < 0) ? (wndRight + 360) : wndRight); + getdisplay().print(sWndLbl); // Wind right value + getdisplay().drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // symbol + + if (pageData.boatHstry.twdHstry->getMax() == pageData.boatHstry.twdHstry->getMinVal()) { + // only values in buffer -> no valid wind data available + wndDataValid = false; + } else if (!BDataValid[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; + } + } else { + numNoData = 0; // reset data error counter + wndDataValid = true; // At least some wind data available + } + // Draw wind values in chart + //*********************************************************************** + if (wndDataValid) { + for (int i = 0; i < (numWndVals / dataIntv); i++) { + chrtVal = static_cast(pageData.boatHstry.twdHstry->get(bufStart + (i * dataIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer + if (chrtVal == INT16_MIN) { + chrtPrevVal = INT16_MIN; + /* if (i == linesToShow - 1) { + numNoData++; + // If more than 4 invalid values in a row, reset chart + } else { + numNoData = 0; // Reset invalid value counter + } + if (numNoData > 4) { + // If more than 4 invalid values in a row, send message + 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 { + chrtVal = static_cast((chrtVal / 1000.0 * radToDeg) + 0.5); // Convert to degrees and round + x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; + y = yOffset + cHeight - i; // Position in chart area + + if (i >= (numWndVals / dataIntv) - 10) + 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)) { + // just a dot for 1st chart point or after some invalid values + prevX = x; + prevY = y; + } else { + // cross borders check; shift values to [-180..0..180]; when crossing borders, range is 2x 180 degrees + int wndLeftDlt = -180 - ((wndLeft >= 180) ? (wndLeft - 360) : wndLeft); + int chrtVal180 = ((chrtVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; + int chrtPrevVal180 = ((chrtPrevVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; + if (((chrtPrevVal180 >= -180) && (chrtPrevVal180 < -90) && (chrtVal180 > 90)) || ((chrtPrevVal180 <= 179) && (chrtPrevVal180 > 90) && chrtVal180 <= -90)) { + // If current value crosses chart borders compared to previous value, split line + int xSplit = (((chrtPrevVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; + getdisplay().drawLine(prevX, prevY, xSplit, y, commonData->fgcolor); + getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), commonData->fgcolor); + prevX = (((chrtVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; + } + } + + // Draw line with 2 pixels width + make sure vertical line are drawn correctly + getdisplay().drawLine(prevX, prevY, x, y, commonData->fgcolor); + getdisplay().drawLine(prevX, prevY - 1, ((x != prevX) ? x : x - 1), ((x != prevX) ? y - 1 : y), commonData->fgcolor); + chrtPrevVal = chrtVal; + prevX = x; + prevY = y; + } + // Reaching chart area top end + if (i >= (cHeight - 1)) { + oldDataIntv = 0; // force reset of buffer start and number of values to show in next display loop + + int minWndDir = pageData.boatHstry.twdHstry->getMin(numWndVals) / 1000.0 * radToDeg; + int maxWndDir = pageData.boatHstry.twdHstry->getMax(numWndVals) / 1000.0 * radToDeg; + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: Minimum: %d, Maximum: %d, OldwndCenter: %d", minWndDir, 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 + midWndDir = pageData.boatHstry.twdHstry->getMid(numWndVals) / 1000.0 * radToDeg; + if (midWndDir != INT16_MIN) { + wndCenter = int((midWndDir + (midWndDir >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value + } + } + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: cHeight: %d, bufStart: %d, numWndVals: %d, wndCenter: %d", cHeight, bufStart, numWndVals, wndCenter); + break; + } + } + + } else { + // No valid data available + LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); + getdisplay().setFont(&Ubuntu_Bold10pt8b); + getdisplay().fillRect(xCenter - 33, height / 2 - 20, 66, 24, commonData->bgcolor); // Clear area for message + drawTextCenter(xCenter, height / 2 - 10, "No data"); + } + + // Print TWS value + if (showTWS) { + int currentZone; + static int lastZone = 0; + static bool flipTws = false; + int xPosTws; + static const int yPosTws = yOffset + 40; + + twsValue = pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384; // TWS value in knots + + 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 + if (currentZone != lastZone) { + // Only flip when x moves to a different zone + if ((y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146)) { + flipTws = !flipTws; + xPosTws = flipTws ? 20 : width - 145; + } + } + lastZone = currentZone; + + getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value + getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); + getdisplay().setCursor(xPosTws, yPosTws); + if (!BDataValid[1]) { + getdisplay().print("--.-"); + } else { + double dbl = BDataValue[1] * 3.6 / 1.852; + if (dbl < 10.0) { + getdisplay().printf("!%3.1f", dbl); // Value, round to 1 decimal + } else { + getdisplay().printf("%4.1f", dbl); // Value, round to 1 decimal + } + } + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(xPosTws + 82, yPosTws - 14); +// getdisplay().print("TWS"); // Name + getdisplay().print(BDataName[1]); // Name + getdisplay().setFont(&Ubuntu_Bold8pt8b); +// getdisplay().setCursor(xPosTws + 78, yPosTws + 1); + getdisplay().setCursor(xPosTws + 82, yPosTws + 1); +// getdisplay().printf(" kn"); // Unit + getdisplay().print(BDataUnit[1]); // Unit + } + + // chart Y axis labels; print at last to overwrite potential chart lines in label area + int yPos; + int chrtLbl; + getdisplay().setFont(&Ubuntu_Bold8pt8b); + for (int i = 1; i <= 3; i++) { + yPos = yOffset + (i * 60); + getdisplay().fillRect(0, yPos, width, 1, commonData->fgcolor); + getdisplay().fillRect(0, yPos - 8, 24, 16, commonData->bgcolor); // Clear small area to remove potential chart lines + getdisplay().setCursor(1, yPos + 4); + if (count >= intvBufSize) { + // Calculate minute value for label + chrtLbl = ((i - 1 + (prevY < yOffset + 30)) * dataIntv) * -1; // change label if last data point is more than 30 lines (= seconds) from chart line + } else { + int j = 3 - i; + chrtLbl = (int((((numWndVals / dataIntv) - 50) * dataIntv / 60) + 1) - (j * dataIntv)) * -1; // 50 lines left below last chart line + } + getdisplay().printf("%3d", chrtLbl); // Wind value label + } + + return PAGE_UPDATE; + }; +}; + +static Page* createPage(CommonData& common) +{ + return new PageWindPlot(common); +} +/** + * with the code below we make this page known to the PageTask + * we give it a type (name) that can be selected in the config + * we define which function is to be called + * and we provide the number of user parameters we expect (0 here) + * and will will provide the names of the fixed values we need + */ +PageDescription registerPageWindPlot( + "WindPlot", // Page name + createPage, // Action + 0, // Number of bus values depends on selection in Web configuration + { "TWD", "TWS" }, // Bus values we need in the page +// {}, // Bus values we need in the page + true // Show display header on/off +); + +#endif \ No newline at end of file diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index 9177fff..75a4b8c 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -4,15 +4,19 @@ #include #include #include "LedSpiTask.h" +#include "OBPRingBuffer.h" +#include "OBPDataOperations.h" #define MAX_PAGE_NUMBER 10 // Max number of pages for show data typedef std::vector ValueList; + typedef struct{ String pageName; uint8_t pageNumber; // page number in sequence of visible pages //the values will always contain the user defined values first ValueList values; + tBoatHstryData boatHstry; } PageData; // Sensor data structure (only for extended sensors, not for NMEA bus sensors) diff --git a/lib/obp60task/config.json b/lib/obp60task/config.json index 71f753c..eed7ba2 100644 --- a/lib/obp60task/config.json +++ b/lib/obp60task/config.json @@ -219,6 +219,17 @@ "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", "label": "Length Format", @@ -1313,6 +1324,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -1593,6 +1605,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -1870,6 +1883,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2144,6 +2158,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2415,6 +2430,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2683,6 +2699,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2948,6 +2965,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -3210,6 +3228,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -3469,6 +3488,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -3725,6 +3745,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index 1afb168..92cb0f6 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -219,6 +219,17 @@ "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", "label": "Length Format", @@ -1336,6 +1347,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -1616,6 +1628,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -1893,6 +1906,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2167,6 +2181,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2438,6 +2453,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2706,6 +2722,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2971,6 +2988,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -3233,6 +3251,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -3492,6 +3511,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -3748,6 +3768,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" diff --git a/lib/obp60task/gen_set.py b/lib/obp60task/gen_set.py index 71485fa..fd3a3e0 100755 --- a/lib/obp60task/gen_set.py +++ b/lib/obp60task/gen_set.py @@ -31,6 +31,7 @@ no_of_fields_per_page = { "TwoValues": 2, "Voltage": 0, "WhitePage": 0, + "WindPlot": 0, "WindRose": 0, "WindRoseFlex": 6, } diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 30a79c1..11b986d 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -13,6 +13,8 @@ #include "OBP60Extensions.h" // Functions lib for extension board #include "OBP60Keypad.h" // Functions for keypad #include "BoatDataCalibration.h" // Functions lib for data instance calibration +#include "OBPRingBuffer.h" // Functions lib with ring buffer for history storage of some boat data +#include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation #ifdef BOARD_OBP40S3 #include "driver/rtc_io.h" // Needs for weakup from deep sleep @@ -275,6 +277,8 @@ void registerAllPages(PageList &list){ list.add(®isterPageFourValues2); extern PageDescription registerPageWind; list.add(®isterPageWind); + extern PageDescription registerPageWindPlot; + list.add(®isterPageWindPlot); extern PageDescription registerPageWindRose; list.add(®isterPageWindRose); extern PageDescription registerPageWindRoseFlex; @@ -372,6 +376,125 @@ 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; + double twd, tws, twa; + bool isCalculated = false; + const double DBL_MIN = std::numeric_limits::lowest(); + + GwApi::BoatValue *twdBVal = boatValues->findValueOrCreate("TWD"); + GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate("TWS"); + GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA"); + GwApi::BoatValue *awaBVal = boatValues->findValueOrCreate("AWA"); + GwApi::BoatValue *awsBVal = boatValues->findValueOrCreate("AWS"); + GwApi::BoatValue *cogBVal = boatValues->findValueOrCreate("COG"); + GwApi::BoatValue *stwBVal = boatValues->findValueOrCreate("STW"); + GwApi::BoatValue *sogBVal = boatValues->findValueOrCreate("SOG"); + GwApi::BoatValue *hdtBVal = boatValues->findValueOrCreate("HDT"); + GwApi::BoatValue *hdmBVal = boatValues->findValueOrCreate("HDM"); + GwApi::BoatValue *varBVal = boatValues->findValueOrCreate("VAR"); + awaVal = awaBVal->valid ? awaBVal->value : DBL_MIN; + awsVal = awsBVal->valid ? awsBVal->value : DBL_MIN; + cogVal = cogBVal->valid ? cogBVal->value : DBL_MIN; + stwVal = stwBVal->valid ? stwBVal->value : DBL_MIN; + sogVal = sogBVal->valid ? sogBVal->value : DBL_MIN; + hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MIN; + hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MIN; + varVal = varBVal->valid ? varBVal->value : DBL_MIN; + api->getLogger()->logDebug(GwLog::DEBUG,"obp60task addTrueWind: AWA: %.1f, AWS: %.1f, COG: %.1f, STW: %.1f, 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); + isCalculated = WindUtils::calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa); + + if (isCalculated) { // Replace values only, if successfully calculated and not already available + if (!twdBVal->valid) { + twdBVal->value = twd; + twdBVal->valid = true; + } + if (!twsBVal->valid) { + twsBVal->value = tws; + twsBVal->valid = true; + } + if (!twaBVal->valid) { + twaBVal->value = twa; + twaBVal->valid = true; + } + } + api->getLogger()->logDebug(GwLog::DEBUG,"obp60task 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::DEBUG,"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(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(std::round(calBVal->value * 10)); + if (twsBuf >= twsHstryMin && twsBuf <= twsHstryMax) { + hstryBufList.twsHstry->add(twsBuf); + } + } + delete calBVal; + calBVal = nullptr; +} + // OBP60 Task //#################################################################################### void OBP60Task(GwApi *api){ @@ -485,6 +608,11 @@ void OBP60Task(GwApi *api){ //commonData.distanceformat=config->getString(xxx); //add all necessary data to common data + // Create ring buffers for history storage of some boat data + RingBuffer twdHstry(960); // Circular buffer to store wind direction values; store 960 TWD values for 16 minutes history + RingBuffer twsHstry(960); // Circular buffer to store wind speed values (TWS) + tBoatHstryData hstryBufList = {&twdHstry, &twsHstry}; + //fill the page data from config numPages=config->getInt(config->visiblePages,1); if (numPages < 1) numPages=1; @@ -523,6 +651,10 @@ void OBP60Task(GwApi *api){ LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i); pages[i].parameters.values.push_back(value); } + if (pages[i].description->pageName == "WindPlot") { + // Add boat history data to page parameters + pages[i].parameters.boatHstry = hstryBufList; + } } // add out of band system page (always available) Page *syspage = allPages.pages[0]->creator(commonData); @@ -530,6 +662,13 @@ void OBP60Task(GwApi *api){ // Read all calibration data settings from config calibrationData.readConfig(config, logger); + // Check user setting for true wind calculation + bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false); + // bool simulation = api->getConfig()->getBool(api->getConfig()->useSimuData, false); + + // Initialize history buffer for certain boat data + initHstryBuf(api, &boatValues, hstryBufList); + // Display screenshot handler for HTTP request // http://192.168.15.1/api/user/OBP60Task/screenshot api->registerRequestHandler("screenshot", [api, &pageNumber, pages](AsyncWebServerRequest *request) { @@ -815,8 +954,8 @@ void OBP60Task(GwApi *api){ // getdisplay().nextPage(); // Partial update // getdisplay().nextPage(); // Partial update } - } - + } + // Refresh display data, default all 1s currentPage = pages[pageNumber].page; int pagetime = 1000; @@ -832,6 +971,12 @@ void OBP60Task(GwApi *api){ api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues); 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 // getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor); getdisplay().fillScreen(commonData.bgcolor); // Clear display