1
0
mirror of https://github.com/thooge/esp32-nmea2000-obp60.git synced 2025-12-16 15:33:05 +01:00

49 Commits

Author SHA1 Message Date
Norbert Walter
470c0e5f4d Merge pull request #208 from thooge/fonts
Added small 8x8px font mainly for use with graphs
2025-10-06 18:25:44 +02:00
Norbert Walter
9a792b49db Merge pull request #206 from TobiasE-github/master
disabe mode x in PageWind
2025-10-06 18:23:56 +02:00
8f851a4b61 Added small 8x8px font mainly for use with graphs
Page skyview improved with the new font as example usage
2025-10-06 13:19:42 +02:00
Norbert Walter
f46a43d7fd Merge pull request #207 from thooge/configfix
Config file fixes and  generation script update
2025-10-06 10:44:58 +02:00
84e99365f7 Config file fixes and generation script update 2025-09-29 14:31:28 +02:00
TobiasE-github
e5950f95fd disabe mode x in PageWind 2025-09-27 20:30:00 +02:00
Norbert Walter
d0076f336d Merge pull request #205 from TobiasE-github/master
use a smaller font on long names in WindRoseFlex
2025-09-25 23:59:28 +02:00
Tobias E
d94c4bbbdb optimize font size 2025-09-20 11:16:17 +00:00
TobiasE-github
6ef7681a40 use a smaller font on long names in WindRoseFlex 2025-09-14 21:00:02 +02:00
norbert-walter
34a289048f Fix HDOP and more delay for page refresh after new page 2025-09-10 18:48:24 +02:00
Norbert Walter
df1bd498ae Merge pull request #204 from Scorgan01/PSRAM
Data History Buffer: Moved buffers to PSRAM; extended wind buffer sizes to 1920 values
2025-09-10 18:17:00 +02:00
norbert-walter
e5eee37b59 Code cleaning 2025-08-29 17:02:25 +02:00
norbert-walter
28b3cfba0b Change design PageSkyView 2025-08-29 16:58:15 +02:00
norbert-walter
674a78b03c Fix PageSkyView 2025-08-29 12:17:03 +02:00
Scorgan01
de448974d9 Delete serial_output.txt 2025-08-27 23:21:21 +02:00
Scorgan01
6b91400cfc Merge branch 'norbert-walter:master' into PSRAM 2025-08-27 23:17:18 +02:00
Norbert Walter
be946440d3 Merge pull request #203 from Scorgan01/PageWindPlot
History Buffer + Wind Calculation CleanUp
2025-08-27 23:15:15 +02:00
Norbert Walter
ac86bfb304 Merge pull request #202 from TobiasE-github/master
fix error in label position
2025-08-27 23:14:37 +02:00
Norbert Walter
d719c7260e Merge pull request #201 from thooge/skyview
Created new page SkyView. Additionally some graphics improvements.
2025-08-27 23:14:04 +02:00
Ulrich Meine
1abcb158ec Moved history buffers to PSRAM; extended buffer to 1920 values each (32 min.) 2025-08-26 23:21:36 +02:00
Tobias E
00ea413411 fix error in label position 2025-08-23 18:55:33 +00:00
Ulrich Meine
851149bae6 Convert invalid marker of ringbuffer to MAX_VAL -> required for unsigned types 2025-08-23 13:43:02 +02:00
Ulrich Meine
c6c2ad537a Merge remote-tracking branch 'upstream/master' into PageWindPlot 2025-08-23 11:58:14 +02:00
3eb2c8093e Created new page SkyView. Additionally some graphics improvements. 2025-08-23 09:53:26 +02:00
Ulrich Meine
636b1596f5 Code cleanup: moved buffer + wind calc to OBPDataOperations; <BoatValueList> header to obp60task.h; tws 3 decimals 2025-08-23 01:41:39 +02:00
Norbert Walter
a21ce00260 Merge pull request #200 from thooge/voltage
Improve and speedup undervoltage detection code
2025-08-22 10:26:00 +02:00
norbert-walter
794cbf1c4f New links for docu, rename new tab 2025-08-22 10:23:53 +02:00
4f6079f418 Improve and speedup undervoltage detection code 2025-08-22 10:14:38 +02:00
norbert-walter
a8f3fbb34d Fix for XTE page 2025-08-18 10:22:09 +02:00
Norbert Walter
748867682c Merge pull request #199 from Scorgan01/PageWindPlot
PageWindPlot: add simulation data and AWD data option; COG validity check for true wind calculation
2025-08-18 00:29:58 +02:00
Norbert Walter
5b5e003836 Merge pull request #198 from TobiasE-github/WindRoseFlex
button in WindRoseFlex to switch true/apparent + 4 user-defined values
2025-08-18 00:29:26 +02:00
Ulrich Meine
07200ad701 Merge branch 'PageWindPlot' of https://github.com/Scorgan01/esp32-nmea2000-obp60 into PageWindPlot 2025-08-17 23:50:24 +02:00
Ulrich Meine
371816f946 PageWindPlot: add simulation data, switch TWD/AWD; diff. setup for OBP40; delete showTWS option 2025-08-17 23:50:19 +02:00
Scorgan01
c8a7f14773 Merge branch 'norbert-walter:master' into PageWindPlot 2025-08-17 23:47:38 +02:00
TobiasE-github
4a97768d0b button in WindRoseFlex to switch true/apparent + 4 user-defined values 2025-08-17 16:34:52 +02:00
Norbert Walter
e19bd0898d Merge pull request #197 from TobiasE-github/Webinterface
New tab in webinterface with a screenshot button
2025-08-16 19:16:00 +02:00
Norbert Walter
d130f7ff78 Merge pull request #196 from thooge/master
Enhancements: leavePage() and displayNew() for system page
2025-08-16 19:14:54 +02:00
Tobias Edler
7c14577bbc Typo 2025-08-16 18:47:45 +02:00
Tobias Edler
ba94fddb80 Add a page to the web interface with a screenshot button 2025-08-16 18:44:55 +02:00
Ulrich Meine
8faead0a1a add simulation data for TWD, TWS history data 2025-08-16 16:49:17 +02:00
bc9d139d19 Enhancement for future use: displayNew() for system page and generic leavePage() method 2025-08-15 09:07:49 +02:00
Norbert Walter
a74ce9e553 Merge pull request #195 from thooge/sdcard
Fixed and finished SD card code. Added uptime feature to system page.
2025-08-14 14:59:33 +02:00
779f557d47 Fixed and finished SD card code. Added uptime feature to system page. 2025-08-14 10:19:15 +02:00
norbert-walter
4a273d2c93 Add hibernate in full page refresh 2025-08-12 15:37:22 +02:00
Norbert Walter
9be1b864f4 Merge pull request #192 from thooge/scripts
Automate gen_set.py with page detection and command line parameters
2025-08-12 15:32:51 +02:00
Norbert Walter
bfc4337417 Merge pull request #191 from thooge/precision
Added config option for display precision and formatter code improvement
2025-08-12 15:31:28 +02:00
Ulrich Meine
398b8e0d02 another wndCenter fix; TWD calc with HDM and no VAR; COG valid check; dflt range 60° 2025-08-11 20:49:39 +02:00
28a7e58e27 Automate gen_set.py with page detection and command line parameters 2025-08-01 11:01:23 +02:00
eb51092b23 Added config option for display precision and formatter code improvements 2025-07-31 12:31:57 +02:00
25 changed files with 4735 additions and 3706 deletions

View File

@@ -101,7 +101,7 @@ void CalibrationDataList::readConfig(GwConfigHandler* config, GwLog* logger)
calibMap[instance].slope = slope; calibMap[instance].slope = slope;
calibMap[instance].smooth = smooth; calibMap[instance].smooth = smooth;
calibMap[instance].isCalibrated = false; calibMap[instance].isCalibrated = false;
LOG_DEBUG(GwLog::LOG, "stored calibration data: %s, offset: %f, slope: %f, smoothing: %f", instance.c_str(), LOG_DEBUG(GwLog::LOG, "calibration data: %s, offset: %f, slope: %f, smoothing: %f", instance.c_str(),
calibMap[instance].offset, calibMap[instance].slope, calibMap[instance].smooth); calibMap[instance].offset, calibMap[instance].slope, calibMap[instance].smooth);
} }
LOG_DEBUG(GwLog::LOG, "all calibration data read"); LOG_DEBUG(GwLog::LOG, "all calibration data read");
@@ -117,7 +117,7 @@ void CalibrationDataList::calibrateInstance(GwApi::BoatValue* boatDataValue, GwL
std::string format = ""; std::string format = "";
if (calibMap.find(instance) == calibMap.end()) { if (calibMap.find(instance) == calibMap.end()) {
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not found in calibration data list", instance.c_str()); LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
return; return;
} else if (!boatDataValue->valid) { // no valid boat data value, so we don't want to apply calibration data } else if (!boatDataValue->valid) { // no valid boat data value, so we don't want to apply calibration data
calibMap[instance].isCalibrated = false; calibMap[instance].isCalibrated = false;
@@ -173,7 +173,7 @@ void CalibrationDataList::smoothInstance(GwApi::BoatValue* boatDataValue, GwLog*
if (!boatDataValue->valid) { // no valid boat data value, so we don't want to smoothen value if (!boatDataValue->valid) { // no valid boat data value, so we don't want to smoothen value
return; return;
} else if (calibMap.find(instance) == calibMap.end()) { } else if (calibMap.find(instance) == calibMap.end()) {
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: smooth factor for %s not found in calibration data list", instance.c_str()); LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: smooth factor for %s not found in calibration list", instance.c_str());
return; return;
} else { } else {
smoothFactor = calibMap[instance].smooth; smoothFactor = calibMap[instance].smooth;
@@ -184,8 +184,6 @@ void CalibrationDataList::smoothInstance(GwApi::BoatValue* boatDataValue, GwLog*
} }
lastValue[instance] = dataValue; // store the new value for next cycle; first time, store only the current value and return lastValue[instance] = dataValue; // store the new value for next cycle; first time, store only the current value and return
boatDataValue->value = dataValue; // set the smoothed value to the boat data value boatDataValue->value = dataValue; // set the smoothed value to the boat data value
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: Smoothing factor: %f, Smoothed value: %f", instance.c_str(), smoothFactor, dataValue);
} }
} }

View File

@@ -3,7 +3,8 @@
#ifndef _BOATDATACALIBRATION_H #ifndef _BOATDATACALIBRATION_H
#define _BOATDATACALIBRATION_H #define _BOATDATACALIBRATION_H
#include "Pagedata.h" // #include "Pagedata.h"
#include "GwApi.h"
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>

View File

@@ -0,0 +1,25 @@
/*
Generic graphics functions
*/
#include <math.h>
#include "Graphics.h"
Point rotatePoint(const Point& origin, const Point& p, double angle) {
// rotate poind around origin by degrees
Point rotated;
double phi = angle * M_PI / 180.0;
double dx = p.x - origin.x;
double dy = p.y - origin.y;
rotated.x = origin.x + cos(phi) * dx - sin(phi) * dy;
rotated.y = origin.y + sin(phi) * dx + cos(phi) * dy;
return rotated;
}
std::vector<Point> rotatePoints(const Point& origin, const std::vector<Point>& pts, double angle) {
std::vector<Point> rotatedPoints;
for (const auto& p : pts) {
rotatedPoints.push_back(rotatePoint(origin, p, angle));
}
return rotatedPoints;
}

17
lib/obp60task/Graphics.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include <vector>
struct Point {
double x;
double y;
};
struct Rect {
double x;
double y;
double w;
double h;
};
Point rotatePoint(const Point& origin, const Point& p, double angle);
std::vector<Point> rotatePoints(const Point& origin, const std::vector<Point>& pts, double angle);

View File

@@ -24,6 +24,7 @@
#include "fonts/Ubuntu_Bold20pt8b.h" #include "fonts/Ubuntu_Bold20pt8b.h"
#include "fonts/Ubuntu_Bold32pt8b.h" #include "fonts/Ubuntu_Bold32pt8b.h"
#include "fonts/Atari16px8b.h" // Key label font #include "fonts/Atari16px8b.h" // Key label font
#include "fonts/IBM8x8px.h"
// E-Ink Display // E-Ink Display
#define GxEPD_WIDTH 400 // Display width #define GxEPD_WIDTH 400 // Display width
@@ -64,6 +65,12 @@ PCF8574 pcf8574_Out(PCF8574_I2C_ADDR1); // First digital output modul PCF8574 fr
Adafruit_FRAM_I2C fram; Adafruit_FRAM_I2C fram;
bool hasFRAM = false; bool hasFRAM = false;
// SD Card
#ifdef BOARD_OBP40S3
sdmmc_card_t *sdcard;
#endif
bool hasSDCard = false;
// Global vars // Global vars
bool blinkingLED = false; // Enable / disable blinking flash LED bool blinkingLED = false; // Enable / disable blinking flash LED
bool statusLED = false; // Actual status of flash LED on/off bool statusLED = false; // Actual status of flash LED on/off
@@ -78,6 +85,9 @@ LedTaskData *ledTaskData=nullptr;
void hardwareInit(GwApi *api) void hardwareInit(GwApi *api)
{ {
GwLog *logger = api->getLogger();
GwConfigHandler *config = api->getConfig();
Wire.begin(); Wire.begin();
// Init PCF8574 digital outputs // Init PCF8574 digital outputs
Wire.setClock(I2C_SPEED); // Set I2C clock on 10 kHz Wire.setClock(I2C_SPEED); // Set I2C clock on 10 kHz
@@ -87,7 +97,7 @@ void hardwareInit(GwApi *api)
fram = Adafruit_FRAM_I2C(); fram = Adafruit_FRAM_I2C();
if (esp_reset_reason() == ESP_RST_POWERON) { if (esp_reset_reason() == ESP_RST_POWERON) {
// help initialize FRAM // help initialize FRAM
api->getLogger()->logDebug(GwLog::LOG,"Delaying I2C init for 250ms due to cold boot"); logger->logDebug(GwLog::LOG, "Delaying I2C init for 250ms due to cold boot");
delay(250); delay(250);
} }
// FRAM (e.g. MB85RC256V) // FRAM (e.g. MB85RC256V)
@@ -99,11 +109,88 @@ void hardwareInit(GwApi *api)
// Boot counter // Boot counter
uint8_t framcounter = fram.read(0x0000); uint8_t framcounter = fram.read(0x0000);
fram.write(0x0000, framcounter+1); fram.write(0x0000, framcounter+1);
api->getLogger()->logDebug(GwLog::LOG,"FRAM detected: 0x%04x/0x%04x (counter=%d)", manufacturerID, productID, framcounter); logger->logDebug(GwLog::LOG, "FRAM detected: 0x%04x/0x%04x (counter=%d)", manufacturerID, productID, framcounter);
} }
else { else {
hasFRAM = false; hasFRAM = false;
api->getLogger()->logDebug(GwLog::LOG,"NO FRAM detected"); logger->logDebug(GwLog::LOG, "NO FRAM detected");
}
// SD Card
hasSDCard = false;
#ifdef BOARD_OBP40S3
if (config->getBool(config->useSDCard)) {
esp_err_t ret;
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
host.slot = SPI3_HOST;
logger->logDebug(GwLog::DEBUG, "SDSPI_HOST: max_freq_khz=%d" , host.max_freq_khz);
spi_bus_config_t bus_cfg = {
.mosi_io_num = SD_SPI_MOSI,
.miso_io_num = SD_SPI_MISO,
.sclk_io_num = SD_SPI_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4000,
};
ret = spi_bus_initialize((spi_host_device_t) host.slot, &bus_cfg, SDSPI_DEFAULT_DMA);
if (ret != ESP_OK) {
logger->logDebug(GwLog::ERROR, "Failed to initialize SPI bus for SD card");
} else {
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.gpio_cs = SD_SPI_CS;
slot_config.host_id = (spi_host_device_t) host.slot;
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
ret = esp_vfs_fat_sdspi_mount(MOUNT_POINT, &host, &slot_config, &mount_config, &sdcard);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
logger->logDebug(GwLog::ERROR, "Failed to mount SD card filesystem");
} else {
// ret == 263 could be not powered up yet
logger->logDebug(GwLog::ERROR, "Failed to initialize SD card (error #%d)", ret);
}
} else {
logger->logDebug(GwLog::LOG, "SD card filesystem mounted at '%s'", MOUNT_POINT);
hasSDCard = true;
}
}
if (hasSDCard) {
// read some stats
String features = "";
if (sdcard->is_mem) features += "MEM "; // Memory card
if (sdcard->is_sdio) features += "IO "; // IO Card
if (sdcard->is_mmc) features += "MMC "; // MMC Card
if (sdcard->is_ddr) features += "DDR ";
// if (sdcard->is_uhs1) features += "UHS-1 ";
// ext_csd. Extended information
// uint8_t rev, uint8_t power_class
logger->logDebug(GwLog::LOG, "SD card features: %s", features);
logger->logDebug(GwLog::LOG, "SD card size: %lluMB", ((uint64_t) sdcard->csd.capacity) * sdcard->csd.sector_size / (1024 * 1024));
}
}
#endif
}
void powerInit(String powermode) {
// Max Power | Only 5.0V | Min Power
if (powermode == "Max Power" || powermode == "Only 5.0V") {
#ifdef HARDWARE_V21
setPortPin(OBP_POWER_50, true); // Power on 5.0V rail
#endif
#ifdef BOARD_OBP40S3
setPortPin(OBP_POWER_EPD, true);// Power on ePaper display
setPortPin(OBP_POWER_SD, true); // Power on SD card
#endif
} else { // Min Power
#ifdef HARDWARE_V21
setPortPin(OBP_POWER_50, false); // Power off 5.0V rail
#endif
#ifdef BOARD_OBP40S3
setPortPin(OBP_POWER_EPD, false);// Power off ePaper display
setPortPin(OBP_POWER_SD, false); // Power off SD card
#endif
} }
} }
@@ -276,30 +363,20 @@ String xdrDelete(String input){
return input; return input;
} }
Point rotatePoint(const Point& origin, const Point& p, double angle) {
// rotate poind around origin by degrees
Point rotated;
double phi = angle * M_PI / 180.0;
double dx = p.x - origin.x;
double dy = p.y - origin.y;
rotated.x = origin.x + cos(phi) * dx - sin(phi) * dy;
rotated.y = origin.y + sin(phi) * dx + cos(phi) * dy;
return rotated;
}
std::vector<Point> rotatePoints(const Point& origin, const std::vector<Point>& pts, double angle) {
std::vector<Point> rotatedPoints;
for (const auto& p : pts) {
rotatedPoints.push_back(rotatePoint(origin, p, angle));
}
return rotatedPoints;
}
void fillPoly4(const std::vector<Point>& p4, uint16_t color) { void fillPoly4(const std::vector<Point>& p4, uint16_t color) {
getdisplay().fillTriangle(p4[0].x, p4[0].y, p4[1].x, p4[1].y, p4[2].x, p4[2].y, color); getdisplay().fillTriangle(p4[0].x, p4[0].y, p4[1].x, p4[1].y, p4[2].x, p4[2].y, color);
getdisplay().fillTriangle(p4[0].x, p4[0].y, p4[2].x, p4[2].y, p4[3].x, p4[3].y, color); getdisplay().fillTriangle(p4[0].x, p4[0].y, p4[2].x, p4[2].y, p4[3].x, p4[3].y, color);
} }
void drawPoly(const std::vector<Point>& points, uint16_t color) {
size_t polysize = points.size();
for (size_t i = 0; i < polysize - 1; i++) {
getdisplay().drawLine(points[i].x, points[i].y, points[i+1].x, points[i+1].y, color);
}
// close path
getdisplay().drawLine(points[polysize-1].x, points[polysize-1].y, points[0].x, points[0].y, color);
}
// Split string into words, whitespace separated // Split string into words, whitespace separated
std::vector<String> split(const String &s) { std::vector<String> split(const String &s) {
std::vector<String> words; std::vector<String> words;
@@ -361,6 +438,24 @@ void drawTextRalign(int16_t x, int16_t y, String text) {
getdisplay().print(text); getdisplay().print(text);
} }
// Draw text inside box, normal or inverted
void drawTextBoxed(Rect box, String text, uint16_t fg, uint16_t bg, bool inverted, bool border) {
if (inverted) {
getdisplay().fillRect(box.x, box.y, box.w, box.h, fg);
getdisplay().setTextColor(bg);
} else {
if (border) {
getdisplay().fillRect(box.x + 1, box.y + 1, box.w - 2, box.h - 2, bg);
getdisplay().drawRect(box.x, box.y, box.w, box.h, fg);
}
getdisplay().setTextColor(fg);
}
uint16_t border_offset = box.h / 4; // 25% of box height
getdisplay().setCursor(box.x + border_offset, box.y + box.h - border_offset);
getdisplay().print(text);
getdisplay().setTextColor(fg);
}
// Show a triangle for trend direction high (x, y is the left edge) // Show a triangle for trend direction high (x, y is the left edge)
void displayTrendHigh(int16_t x, int16_t y, uint16_t size, uint16_t color){ void displayTrendHigh(int16_t x, int16_t y, uint16_t size, uint16_t color){
getdisplay().fillTriangle(x, y, x+size*2, y, x+size, y-size*2, color); getdisplay().fillTriangle(x, y, x+size*2, y, x+size, y-size*2, color);

View File

@@ -4,9 +4,16 @@
#include <Arduino.h> #include <Arduino.h>
#include "OBP60Hardware.h" #include "OBP60Hardware.h"
#include "LedSpiTask.h" #include "LedSpiTask.h"
#include "Graphics.h"
#include <GxEPD2_BW.h> // E-paper lib V2 #include <GxEPD2_BW.h> // E-paper lib V2
#include <Adafruit_FRAM_I2C.h> // I2C FRAM #include <Adafruit_FRAM_I2C.h> // I2C FRAM
#ifdef BOARD_OBP40S3
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#define MOUNT_POINT "/sdcard"
#endif
// FRAM address reservations 32kB: 0x0000 - 0x7FFF // FRAM address reservations 32kB: 0x0000 - 0x7FFF
// 0x0000 - 0x03ff: single variables // 0x0000 - 0x03ff: single variables
#define FRAM_PAGE_NO 0x0002 #define FRAM_PAGE_NO 0x0002
@@ -15,6 +22,7 @@
#define FRAM_VOLTAGE_AVG 0x000A #define FRAM_VOLTAGE_AVG 0x000A
#define FRAM_VOLTAGE_TREND 0x000B #define FRAM_VOLTAGE_TREND 0x000B
#define FRAM_VOLTAGE_MODE 0x000C #define FRAM_VOLTAGE_MODE 0x000C
// Wind page
#define FRAM_WIND_SIZE 0x000D #define FRAM_WIND_SIZE 0x000D
#define FRAM_WIND_SRC 0x000E #define FRAM_WIND_SRC 0x000E
#define FRAM_WIND_MODE 0x000F #define FRAM_WIND_MODE 0x000F
@@ -24,6 +32,10 @@
extern Adafruit_FRAM_I2C fram; extern Adafruit_FRAM_I2C fram;
extern bool hasFRAM; extern bool hasFRAM;
extern bool hasSDCard;
#ifdef BOARD_OBP40S3
extern sdmmc_card_t *sdcard;
#endif
// Fonts declarations for display (#includes see OBP60Extensions.cpp) // Fonts declarations for display (#includes see OBP60Extensions.cpp)
extern const GFXfont DSEG7Classic_BoldItalic16pt7b; extern const GFXfont DSEG7Classic_BoldItalic16pt7b;
@@ -39,6 +51,7 @@ extern const GFXfont Ubuntu_Bold16pt8b;
extern const GFXfont Ubuntu_Bold20pt8b; extern const GFXfont Ubuntu_Bold20pt8b;
extern const GFXfont Ubuntu_Bold32pt8b; extern const GFXfont Ubuntu_Bold32pt8b;
extern const GFXfont Atari16px; extern const GFXfont Atari16px;
extern const GFXfont IBM8x8px;
// Global functions // Global functions
#ifdef DISPLAY_GDEW042T2 #ifdef DISPLAY_GDEW042T2
@@ -62,19 +75,15 @@ GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> & getdisplay();
#define PAGE_UPDATE 1 // page wants display to update #define PAGE_UPDATE 1 // page wants display to update
#define PAGE_HIBERNATE 2 // page wants displey to hibernate #define PAGE_HIBERNATE 2 // page wants displey to hibernate
struct Point {
double x;
double y;
};
Point rotatePoint(const Point& origin, const Point& p, double angle);
std::vector<Point> rotatePoints(const Point& origin, const std::vector<Point>& pts, double angle);
void fillPoly4(const std::vector<Point>& p4, uint16_t color); void fillPoly4(const std::vector<Point>& p4, uint16_t color);
void drawPoly(const std::vector<Point>& points, uint16_t color);
void deepSleep(CommonData &common); void deepSleep(CommonData &common);
uint8_t getLastPage(); uint8_t getLastPage();
void hardwareInit(GwApi *api); void hardwareInit(GwApi *api);
void powerInit(String powermode);
void setPortPin(uint pin, bool value); // Set port pin for extension port void setPortPin(uint pin, bool value); // Set port pin for extension port
@@ -96,6 +105,7 @@ String xdrDelete(String input); // Delete xdr prefix from string
void drawTextCenter(int16_t cx, int16_t cy, String text); void drawTextCenter(int16_t cx, int16_t cy, String text);
void drawTextRalign(int16_t x, int16_t y, String text); void drawTextRalign(int16_t x, int16_t y, String text);
void drawTextBoxed(Rect box, String text, uint16_t fg, uint16_t bg, bool inverted, bool border);
void displayTrendHigh(int16_t x, int16_t y, uint16_t size, uint16_t color); void displayTrendHigh(int16_t x, int16_t y, uint16_t size, uint16_t color);
void displayTrendLow(int16_t x, int16_t y, uint16_t size, uint16_t color); void displayTrendLow(int16_t x, int16_t y, uint16_t size, uint16_t color);

View File

@@ -65,13 +65,27 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
String tempFormat = commondata.config->getString(commondata.config->tempFormat); // [K|°C|°F] String tempFormat = commondata.config->getString(commondata.config->tempFormat); // [K|°C|°F]
String dateFormat = commondata.config->getString(commondata.config->dateFormat); // [DE|GB|US] String dateFormat = commondata.config->getString(commondata.config->dateFormat); // [DE|GB|US]
bool usesimudata = commondata.config->getBool(commondata.config->useSimuData); // [on|off] bool usesimudata = commondata.config->getBool(commondata.config->useSimuData); // [on|off]
String precision = commondata.config->getString(commondata.config->valueprecision); // [1|2]
// If boat value not valid // If boat value not valid
if (! value->valid && !usesimudata){ if (! value->valid && !usesimudata){
result.svalue = "---"; result.svalue = "---";
return result; return result;
} }
const char* fmt_dec_1;
const char* fmt_dec_10;
const char* fmt_dec_100;
if (precision == "1") {
fmt_dec_1 = "%3.1f";
fmt_dec_10 = "%3.0f";
fmt_dec_100 = "%3.0f";
} else {
fmt_dec_1 = "%3.2f";
fmt_dec_10 = "%3.1f";
fmt_dec_100 = "%3.0f";
}
// LOG_DEBUG(GwLog::DEBUG,"formatValue init: getFormat: %s date->value: %f time->value: %f", value->getFormat(), commondata.date->value, commondata.time->value); // LOG_DEBUG(GwLog::DEBUG,"formatValue init: getFormat: %s date->value: %f time->value: %f", value->getFormat(), commondata.date->value, commondata.time->value);
static const int bsize = 30; static const int bsize = 30;
char buffer[bsize+1]; char buffer[bsize+1];
@@ -91,25 +105,25 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
tmElements_t parts; tmElements_t parts;
time_t tv=tNMEA0183Msg::daysToTime_t(value->value + dayoffset); time_t tv=tNMEA0183Msg::daysToTime_t(value->value + dayoffset);
tNMEA0183Msg::breakTime(tv,parts); tNMEA0183Msg::breakTime(tv,parts);
if(usesimudata == false) { if (usesimudata == false) {
if(String(dateFormat) == "DE"){ if (String(dateFormat) == "DE") {
snprintf(buffer,bsize,"%02d.%02d.%04d",parts.tm_mday,parts.tm_mon+1,parts.tm_year+1900); snprintf(buffer,bsize, "%02d.%02d.%04d", parts.tm_mday, parts.tm_mon+1, parts.tm_year+1900);
} }
else if(String(dateFormat) == "GB"){ else if(String(dateFormat) == "GB") {
snprintf(buffer,bsize,"%02d/%02d/%04d",parts.tm_mday,parts.tm_mon+1,parts.tm_year+1900); snprintf(buffer, bsize, "%02d/%02d/%04d", parts.tm_mday, parts.tm_mon+1, parts.tm_year+1900);
} }
else if(String(dateFormat) == "US"){ else if(String(dateFormat) == "US") {
snprintf(buffer,bsize,"%02d/%02d/%04d",parts.tm_mon+1,parts.tm_mday,parts.tm_year+1900); snprintf(buffer, bsize, "%02d/%02d/%04d", parts.tm_mon+1, parts.tm_mday, parts.tm_year+1900);
} }
else if(String(dateFormat) == "ISO"){ else if(String(dateFormat) == "ISO") {
snprintf(buffer,bsize,"%04d-%02d-%02d",parts.tm_year+1900,parts.tm_mon+1,parts.tm_mday); snprintf(buffer, bsize, "%04d-%02d-%02d", parts.tm_year+1900, parts.tm_mon+1, parts.tm_mday);
} }
else{ else {
snprintf(buffer,bsize,"%02d.%02d.%04d",parts.tm_mday,parts.tm_mon+1,parts.tm_year+1900); snprintf(buffer, bsize, "%02d.%02d.%04d", parts.tm_mday, parts.tm_mon+1, parts.tm_year+1900);
} }
} }
else{ else{
snprintf(buffer,bsize,"01.01.2022"); snprintf(buffer, bsize, "01.01.2022");
} }
if(timeZone == 0){ if(timeZone == 0){
result.unit = "UTC"; result.unit = "UTC";
@@ -130,11 +144,11 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
if (timeInSeconds > 86400) {timeInSeconds = timeInSeconds - 86400;} if (timeInSeconds > 86400) {timeInSeconds = timeInSeconds - 86400;}
if (timeInSeconds < 0) {timeInSeconds = timeInSeconds + 86400;} if (timeInSeconds < 0) {timeInSeconds = timeInSeconds + 86400;}
// LOG_DEBUG(GwLog::DEBUG,"... formatTime value: %f tz: %f corrected timeInSeconds: %f ", value->value, timeZone, timeInSeconds); // LOG_DEBUG(GwLog::DEBUG,"... formatTime value: %f tz: %f corrected timeInSeconds: %f ", value->value, timeZone, timeInSeconds);
if(usesimudata == false) { if (usesimudata == false) {
val=modf(timeInSeconds/3600.0,&inthr); val = modf(timeInSeconds/3600.0, &inthr);
val=modf(val*3600.0/60.0,&intmin); val = modf(val*3600.0/60.0, &intmin);
modf(val*60.0,&intsec); modf(val*60.0,&intsec);
snprintf(buffer,bsize,"%02.0f:%02.0f:%02.0f",inthr,intmin,intsec); snprintf(buffer, bsize, "%02.0f:%02.0f:%02.0f", inthr, intmin, intsec);
} }
else{ else{
static long sec; static long sec;
@@ -143,7 +157,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
sec ++; sec ++;
} }
sec = sec % 60; sec = sec % 60;
snprintf(buffer,bsize,"11:36:%02i", int(sec)); snprintf(buffer, bsize, "11:36:%02i", int(sec));
lasttime = millis(); lasttime = millis();
} }
if(timeZone == 0){ if(timeZone == 0){
@@ -156,26 +170,26 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
//######################################################## //########################################################
else if (value->getFormat() == "formatFixed0"){ else if (value->getFormat() == "formatFixed0"){
if(usesimudata == false) { if(usesimudata == false) {
snprintf(buffer,bsize,"%3.0f",value->value); snprintf(buffer, bsize, "%3.0f", value->value);
rawvalue = value->value; rawvalue = value->value;
} }
else{ else{
rawvalue = 8.0 + float(random(0, 10)) / 10.0; rawvalue = 8.0 + float(random(0, 10)) / 10.0;
snprintf(buffer,bsize,"%3.0f", rawvalue); snprintf(buffer, bsize, "%3.0f", rawvalue);
} }
result.unit = ""; result.unit = "";
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatCourse" || value->getFormat() == "formatWind"){ else if (value->getFormat() == "formatCourse" || value->getFormat() == "formatWind"){
double course = 0; double course = 0;
if(usesimudata == false) { if (usesimudata == false) {
course = value->value; course = value->value;
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
course = 2.53 + float(random(0, 10) / 100.0); course = 2.53 + float(random(0, 10) / 100.0);
rawvalue = course; rawvalue = course;
} }
course = course * 57.2958; // Unit conversion form rad to deg course = course * 57.2958; // Unit conversion form rad to deg
// Format 3 numbers with prefix zero // Format 3 numbers with prefix zero
@@ -185,7 +199,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
//######################################################## //########################################################
else if (value->getFormat() == "formatKnots" && (value->getName() == "SOG" || value->getName() == "STW")){ else if (value->getFormat() == "formatKnots" && (value->getName() == "SOG" || value->getName() == "STW")){
double speed = 0; double speed = 0;
if(usesimudata == false) { if (usesimudata == false) {
speed = value->value; speed = value->value;
rawvalue = value->value; rawvalue = value->value;
} }
@@ -193,85 +207,85 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 4.0 + float(random(0, 40)); rawvalue = 4.0 + float(random(0, 40));
speed = rawvalue; speed = rawvalue;
} }
if(String(speedFormat) == "km/h"){ if (String(speedFormat) == "km/h"){
speed = speed * 3.6; // Unit conversion form m/s to km/h speed = speed * 3.6; // Unit conversion form m/s to km/h
result.unit = "km/h"; result.unit = "km/h";
} }
else if(String(speedFormat) == "kn"){ else if (String(speedFormat) == "kn"){
speed = speed * 1.94384; // Unit conversion form m/s to kn speed = speed * 1.94384; // Unit conversion form m/s to kn
result.unit = "kn"; result.unit = "kn";
} }
else{ else {
speed = speed; // Unit conversion form m/s to m/s speed = speed; // Unit conversion form m/s to m/s
result.unit = "m/s"; result.unit = "m/s";
} }
if(speed < 10){ if(speed < 10) {
snprintf(buffer,bsize,"%3.2f",speed); snprintf(buffer, bsize, fmt_dec_1, speed);
} }
if(speed >= 10 && speed < 100){ else if (speed < 100) {
snprintf(buffer,bsize,"%3.1f",speed); snprintf(buffer, bsize, fmt_dec_10, speed);
} }
if(speed >= 100){ else {
snprintf(buffer,bsize,"%3.0f",speed); snprintf(buffer, bsize, fmt_dec_100, speed);
} }
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatKnots" && (value->getName() == "AWS" || value->getName() == "TWS" || value->getName() == "MaxAws" || value->getName() == "MaxTws")){ else if (value->getFormat() == "formatKnots" && (value->getName() == "AWS" || value->getName() == "TWS" || value->getName() == "MaxAws" || value->getName() == "MaxTws")){
double speed = 0; double speed = 0;
if(usesimudata == false) { if (usesimudata == false) {
speed = value->value; speed = value->value;
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 4.0 + float(random(0, 40)); rawvalue = 4.0 + float(random(0, 40));
speed = rawvalue; speed = rawvalue;
} }
if(String(windspeedFormat) == "km/h"){ if (String(windspeedFormat) == "km/h"){
speed = speed * 3.6; // Unit conversion form m/s to km/h speed = speed * 3.6; // Unit conversion form m/s to km/h
result.unit = "km/h"; result.unit = "km/h";
} }
else if(String(windspeedFormat) == "kn"){ else if (String(windspeedFormat) == "kn"){
speed = speed * 1.94384; // Unit conversion form m/s to kn speed = speed * 1.94384; // Unit conversion form m/s to kn
result.unit = "kn"; result.unit = "kn";
} }
else if(String(windspeedFormat) == "bft"){ else if(String(windspeedFormat) == "bft"){
if(speed < 0.3){ if (speed < 0.3) {
speed = 0; speed = 0;
} }
if(speed >=0.3 && speed < 1.5){ else if (speed < 1.5) {
speed = 1; speed = 1;
} }
if(speed >=1.5 && speed < 3.3){ else if (speed < 3.3) {
speed = 2; speed = 2;
} }
if(speed >=3.3 && speed < 5.4){ else if (speed < 5.4) {
speed = 3; speed = 3;
} }
if(speed >=5.4 && speed < 7.9){ else if (speed < 7.9) {
speed = 4; speed = 4;
} }
if(speed >=7.9 && speed < 10.7){ else if (speed < 10.7) {
speed = 5; speed = 5;
} }
if(speed >=10.7 && speed < 13.8){ else if (speed < 13.8) {
speed = 6; speed = 6;
} }
if(speed >=13.8 && speed < 17.1){ else if (speed < 17.1) {
speed = 7; speed = 7;
} }
if(speed >=17.1 && speed < 20.7){ else if (speed < 20.7) {
speed = 8; speed = 8;
} }
if(speed >=20.7 && speed < 24.4){ else if (speed < 24.4) {
speed = 9; speed = 9;
} }
if(speed >=24.4 && speed < 28.4){ else if (speed < 28.4) {
speed = 10; speed = 10;
} }
if(speed >=28.4 && speed < 32.6){ else if (speed < 32.6) {
speed = 11; speed = 11;
} }
if(speed >=32.6){ else {
speed = 12; speed = 12;
} }
result.unit = "bft"; result.unit = "bft";
@@ -280,82 +294,85 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
speed = speed; // Unit conversion form m/s to m/s speed = speed; // Unit conversion form m/s to m/s
result.unit = "m/s"; result.unit = "m/s";
} }
if(String(windspeedFormat) == "bft"){ if (String(windspeedFormat) == "bft"){
snprintf(buffer,bsize,"%2.0f",speed); snprintf(buffer, bsize, "%2.0f", speed);
} }
else{ else{
if(speed < 10){ if (speed < 10){
snprintf(buffer,bsize,"%3.2f",speed); snprintf(buffer, bsize, fmt_dec_1, speed);
} }
if(speed >= 10 && speed < 100){ else if (speed < 100){
snprintf(buffer,bsize,"%3.1f",speed); snprintf(buffer, bsize, fmt_dec_10, speed);
} }
if(speed >= 100){ else {
snprintf(buffer,bsize,"%3.0f",speed); snprintf(buffer, bsize, fmt_dec_100, speed);
} }
} }
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatRot"){ else if (value->getFormat() == "formatRot"){
double rotation = 0; double rotation = 0;
if(usesimudata == false) { if (usesimudata == false) {
rotation = value->value; rotation = value->value;
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 0.04 + float(random(0, 10)) / 100.0; rawvalue = 0.04 + float(random(0, 10)) / 100.0;
rotation = rawvalue; rotation = rawvalue;
} }
rotation = rotation * 57.2958; // Unit conversion form rad/s to deg/s rotation = rotation * 57.2958; // Unit conversion form rad/s to deg/s
result.unit = "Deg/s"; result.unit = "Deg/s";
if(rotation < -100){ if (rotation < -100){
rotation = -99; rotation = -99;
} }
if(rotation > 100){ if (rotation > 100){
rotation = 99; rotation = 99;
} }
if(rotation > -10 && rotation < 10){ if (rotation > -10 && rotation < 10){
snprintf(buffer,bsize,"%3.2f",rotation); snprintf(buffer, bsize, "%3.2f", rotation);
} }
if(rotation <= -10 || rotation >= 10){ if (rotation <= -10 || rotation >= 10){
snprintf(buffer,bsize,"%3.0f",rotation); snprintf(buffer, bsize, "%3.0f", rotation);
} }
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatDop"){ else if (value->getFormat() == "formatDop"){
double dop = 0; double dop = 0;
if(usesimudata == false) { if (usesimudata == false) {
dop = value->value; dop = value->value;
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 2.0 + float(random(0, 40)) / 10.0; rawvalue = 2.0 + float(random(0, 40)) / 10.0;
dop = rawvalue; dop = rawvalue;
} }
result.unit = "m"; result.unit = "m";
if(dop > 99.9){ if (dop > 99.9){
dop = 99.9; dop = 99.9;
} }
if(dop < 10){ if (dop < 10){
snprintf(buffer,bsize,"%3.2f",dop); snprintf(buffer, bsize, fmt_dec_1, dop);
} }
if(dop >= 10 && dop < 100){ else if(dop < 100){
snprintf(buffer,bsize,"%3.1f",dop); snprintf(buffer, bsize, fmt_dec_10, dop);
}
else {
snprintf(buffer, bsize, fmt_dec_100, dop);
} }
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatLatitude"){ else if (value->getFormat() == "formatLatitude"){
if(usesimudata == false) { if (usesimudata == false) {
double lat = value->value; double lat = value->value;
rawvalue = value->value; rawvalue = value->value;
String latitude = ""; String latitude = "";
String latdir = ""; String latdir = "";
float degree = abs(int(lat)); float degree = abs(int(lat));
float minute = abs((lat - int(lat)) * 60); float minute = abs((lat - int(lat)) * 60);
if(lat > 0){ if (lat > 0){
latdir = "N"; latdir = "N";
} }
else{ else {
latdir = "S"; latdir = "S";
} }
latitude = String(degree,0) + "\x90 " + String(minute,4) + "' " + latdir; latitude = String(degree,0) + "\x90 " + String(minute,4) + "' " + latdir;
@@ -364,41 +381,41 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
} }
else{ else{
rawvalue = 35.0 + float(random(0, 10)) / 10000.0; rawvalue = 35.0 + float(random(0, 10)) / 10000.0;
snprintf(buffer,bsize," 51\" %2.4f' N", rawvalue); snprintf(buffer, bsize, " 51\" %2.4f' N", rawvalue);
} }
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatLongitude"){ else if (value->getFormat() == "formatLongitude"){
if(usesimudata == false) { if (usesimudata == false) {
double lon = value->value; double lon = value->value;
rawvalue = value->value; rawvalue = value->value;
String longitude = ""; String longitude = "";
String londir = ""; String londir = "";
float degree = abs(int(lon)); float degree = abs(int(lon));
float minute = abs((lon - int(lon)) * 60); float minute = abs((lon - int(lon)) * 60);
if(lon > 0){ if (lon > 0){
londir = "E"; londir = "E";
} }
else{ else {
londir = "W"; londir = "W";
} }
longitude = String(degree,0) + "\x90 " + String(minute,4) + "' " + londir; longitude = String(degree,0) + "\x90 " + String(minute,4) + "' " + londir;
result.unit = ""; result.unit = "";
strcpy(buffer, longitude.c_str()); strcpy(buffer, longitude.c_str());
} }
else{ else {
rawvalue = 6.0 + float(random(0, 10)) / 100000.0; rawvalue = 6.0 + float(random(0, 10)) / 100000.0;
snprintf(buffer,bsize," 15\" %2.4f'", rawvalue); snprintf(buffer, bsize, " 15\" %2.4f'", rawvalue);
} }
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatDepth"){ else if (value->getFormat() == "formatDepth"){
double depth = 0; double depth = 0;
if(usesimudata == false) { if (usesimudata == false) {
depth = value->value; depth = value->value;
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 18.0 + float(random(0, 100)) / 10.0; rawvalue = 18.0 + float(random(0, 100)) / 10.0;
depth = rawvalue; depth = rawvalue;
} }
@@ -409,71 +426,84 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
else{ else{
result.unit = "m"; result.unit = "m";
} }
if(depth < 10){ if (depth < 10) {
snprintf(buffer,bsize,"%3.2f",depth); snprintf(buffer, bsize, fmt_dec_1, depth);
} }
if(depth >= 10 && depth < 100){ else if (depth < 100){
snprintf(buffer,bsize,"%3.1f",depth); snprintf(buffer, bsize, fmt_dec_10, depth);
} }
if(depth >= 100){ else {
snprintf(buffer,bsize,"%3.0f",depth); snprintf(buffer, bsize, fmt_dec_100, depth);
} }
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXte"){ else if (value->getFormat() == "formatXte"){
double xte = 0; double xte = 0;
if (!usesimudata) { if(usesimudata == false) {
xte = abs(value->value); xte = value->value;
rawvalue = value->value; rawvalue = value->value;
} else { }
else{
rawvalue = 6.0 + float(random(0, 4)); rawvalue = 6.0 + float(random(0, 4));
xte = rawvalue; xte = rawvalue;
} }
if (xte >= 100) { if(String(distanceFormat) == "km"){
snprintf(buffer,bsize,"%3.0f",value->value); xte = xte * 0.001;
} else if (xte >= 10) { result.unit = "km";
snprintf(buffer,bsize,"%3.1f",value->value); }
} else { else if(String(distanceFormat) == "nm"){
snprintf(buffer,bsize,"%3.2f",value->value); xte = xte * 0.000539957;
result.unit = "nm";
}
else{;
result.unit = "m";
}
if(xte < 10){
snprintf(buffer,bsize,"%3.2f",xte);
}
if(xte >= 10 && xte < 100){
snprintf(buffer,bsize,"%3.1f",xte);
}
if(xte >= 100){
snprintf(buffer,bsize,"%3.0f",xte);
} }
result.unit = "nm";
} }
//######################################################## //########################################################
else if (value->getFormat() == "kelvinToC"){ else if (value->getFormat() == "kelvinToC"){
double temp = 0; double temp = 0;
if(usesimudata == false) { if (usesimudata == false) {
temp = value->value; temp = value->value;
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 296.0 + float(random(0, 10)) / 10.0; rawvalue = 296.0 + float(random(0, 10)) / 10.0;
temp = rawvalue; temp = rawvalue;
} }
if(String(tempFormat) == "C"){ if (String(tempFormat) == "C") {
temp = temp - 273.15; temp = temp - 273.15;
result.unit = "C"; result.unit = "C";
} }
else if(String(tempFormat) == "F"){ else if (String(tempFormat) == "F") {
temp = (temp - 273.15) * 9 / 5 + 32; temp = (temp - 273.15) * 9 / 5 + 32;
result.unit = "F"; result.unit = "F";
} }
else{ else{
result.unit = "K"; result.unit = "K";
} }
if(temp < 10){ if(temp < 10) {
snprintf(buffer,bsize,"%3.2f",temp); snprintf(buffer, bsize, fmt_dec_1, temp);
} }
if(temp >= 10 && temp < 100){ else if (temp < 100) {
snprintf(buffer,bsize,"%3.1f",temp); snprintf(buffer, bsize, fmt_dec_10, temp);
} }
if(temp >= 100){ else {
snprintf(buffer,bsize,"%3.0f",temp); snprintf(buffer, bsize, fmt_dec_100, temp);
} }
} }
//######################################################## //########################################################
else if (value->getFormat() == "mtr2nm"){ else if (value->getFormat() == "mtr2nm"){
double distance = 0; double distance = 0;
if(usesimudata == false) { if (usesimudata == false) {
distance = value->value; distance = value->value;
rawvalue = value->value; rawvalue = value->value;
} }
@@ -481,25 +511,25 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 2960.0 + float(random(0, 10)); rawvalue = 2960.0 + float(random(0, 10));
distance = rawvalue; distance = rawvalue;
} }
if(String(distanceFormat) == "km"){ if (String(distanceFormat) == "km") {
distance = distance * 0.001; distance = distance * 0.001;
result.unit = "km"; result.unit = "km";
} }
else if(String(distanceFormat) == "nm"){ else if (String(distanceFormat) == "nm") {
distance = distance * 0.000539957; distance = distance * 0.000539957;
result.unit = "nm"; result.unit = "nm";
} }
else{; else {
result.unit = "m"; result.unit = "m";
} }
if(distance < 10){ if (distance < 10){
snprintf(buffer,bsize,"%3.2f",distance); snprintf(buffer, bsize, fmt_dec_1, distance);
} }
if(distance >= 10 && distance < 100){ else if (distance < 100){
snprintf(buffer,bsize,"%3.1f",distance); snprintf(buffer, bsize, fmt_dec_10, distance);
} }
if(distance >= 100){ else {
snprintf(buffer,bsize,"%3.0f",distance); snprintf(buffer, bsize, fmt_dec_100, distance);
} }
} }
//######################################################## //########################################################
@@ -508,122 +538,122 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:P:P"){ else if (value->getFormat() == "formatXdr:P:P"){
double pressure = 0; double pressure = 0;
if(usesimudata == false) { if (usesimudata == false) {
pressure = value->value; pressure = value->value;
rawvalue = value->value; rawvalue = value->value;
pressure = pressure / 100.0; // Unit conversion form Pa to hPa pressure = pressure / 100.0; // Unit conversion form Pa to hPa
} }
else{ else {
rawvalue = 968 + float(random(0, 10)); rawvalue = 968 + float(random(0, 10));
pressure = rawvalue; pressure = rawvalue;
} }
snprintf(buffer,bsize,"%4.0f",pressure); snprintf(buffer, bsize, "%4.0f", pressure);
result.unit = "hPa"; result.unit = "hPa";
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:P:B"){ else if (value->getFormat() == "formatXdr:P:B"){
double pressure = 0; double pressure = 0;
if(usesimudata == false) { if (usesimudata == false) {
pressure = value->value; pressure = value->value;
rawvalue = value->value; rawvalue = value->value;
pressure = pressure / 100.0; // Unit conversion form Pa to mBar pressure = pressure / 100.0; // Unit conversion form Pa to mBar
} }
else{ else {
rawvalue = value->value; rawvalue = value->value;
pressure = 968 + float(random(0, 10)); pressure = 968 + float(random(0, 10));
} }
snprintf(buffer,bsize,"%4.0f",pressure); snprintf(buffer, bsize, "%4.0f", pressure);
result.unit = "mBar"; result.unit = "mBar";
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:U:V"){ else if (value->getFormat() == "formatXdr:U:V"){
double voltage = 0; double voltage = 0;
if(usesimudata == false) { if (usesimudata == false) {
voltage = value->value; voltage = value->value;
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 12 + float(random(0, 30)) / 10.0; rawvalue = 12 + float(random(0, 30)) / 10.0;
voltage = rawvalue; voltage = rawvalue;
} }
if(voltage < 10){ if (voltage < 10) {
snprintf(buffer,bsize,"%3.2f",voltage); snprintf(buffer, bsize, fmt_dec_1, voltage);
} }
else{ else {
snprintf(buffer,bsize,"%3.1f",voltage); snprintf(buffer, bsize, fmt_dec_10, voltage);
} }
result.unit = "V"; result.unit = "V";
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:I:A"){ else if (value->getFormat() == "formatXdr:I:A"){
double current = 0; double current = 0;
if(usesimudata == false) { if (usesimudata == false) {
current = value->value; current = value->value;
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 8.2 + float(random(0, 50)) / 10.0; rawvalue = 8.2 + float(random(0, 50)) / 10.0;
current = rawvalue; current = rawvalue;
} }
if(current < 10){ if (current < 10) {
snprintf(buffer,bsize,"%3.2f",current); snprintf(buffer, bsize, fmt_dec_1, current);
} }
if(current >= 10 && current < 100){ else if(current < 100) {
snprintf(buffer,bsize,"%3.1f",current); snprintf(buffer, bsize, fmt_dec_10, current);
} }
if(current >= 100){ else {
snprintf(buffer,bsize,"%3.0f",current); snprintf(buffer, bsize, fmt_dec_100, current);
} }
result.unit = "A"; result.unit = "A";
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:C:K"){ else if (value->getFormat() == "formatXdr:C:K"){
double temperature = 0; double temperature = 0;
if(usesimudata == false) { if (usesimudata == false) {
temperature = value->value - 273.15; // Convert K to C temperature = value->value - 273.15; // Convert K to C
rawvalue = value->value - 273.15; rawvalue = value->value - 273.15;
} }
else{ else {
rawvalue = 21.8 + float(random(0, 50)) / 10.0; rawvalue = 21.8 + float(random(0, 50)) / 10.0;
temperature = rawvalue; temperature = rawvalue;
} }
if(temperature < 10){ if (temperature < 10) {
snprintf(buffer,bsize,"%3.2f",temperature); snprintf(buffer, bsize, fmt_dec_1, temperature);
} }
if(temperature >= 10 && temperature < 100){ else if (temperature < 100) {
snprintf(buffer,bsize,"%3.1f",temperature); snprintf(buffer, bsize, fmt_dec_10, temperature);
} }
if(temperature >= 100){ else {
snprintf(buffer,bsize,"%3.0f",temperature); snprintf(buffer, bsize, fmt_dec_100, temperature);
} }
result.unit = "Deg C"; result.unit = "Deg C";
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:C:C"){ else if (value->getFormat() == "formatXdr:C:C"){
double temperature = 0; double temperature = 0;
if(usesimudata == false) { if (usesimudata == false) {
temperature = value->value; // Value in C temperature = value->value; // Value in C
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 21.8 + float(random(0, 50)) / 10.0; rawvalue = 21.8 + float(random(0, 50)) / 10.0;
temperature = rawvalue; temperature = rawvalue;
} }
if(temperature < 10){ if (temperature < 10) {
snprintf(buffer,bsize,"%3.2f",temperature); snprintf(buffer, bsize, fmt_dec_1, temperature);
} }
if(temperature >= 10 && temperature < 100){ else if(temperature < 100) {
snprintf(buffer,bsize,"%3.1f",temperature); snprintf(buffer, bsize, fmt_dec_10, temperature);
} }
if(temperature >= 100){ else {
snprintf(buffer,bsize,"%3.0f",temperature); snprintf(buffer, bsize, fmt_dec_100, temperature);
} }
result.unit = "Deg C"; result.unit = "Deg C";
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:H:P"){ else if (value->getFormat() == "formatXdr:H:P"){
double humidity = 0; double humidity = 0;
if(usesimudata == false) { if (usesimudata == false) {
humidity = value->value; // Value in % humidity = value->value; // Value in %
rawvalue = value->value; rawvalue = value->value;
} }
@@ -631,143 +661,143 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 41.3 + float(random(0, 50)) / 10.0; rawvalue = 41.3 + float(random(0, 50)) / 10.0;
humidity = rawvalue; humidity = rawvalue;
} }
if(humidity < 10){ if (humidity < 10) {
snprintf(buffer,bsize,"%3.2f",humidity); snprintf(buffer, bsize, fmt_dec_1, humidity);
} }
if(humidity >= 10 && humidity < 100){ else if(humidity < 100) {
snprintf(buffer,bsize,"%3.1f",humidity); snprintf(buffer, bsize, fmt_dec_10, humidity);
} }
if(humidity >= 100){ else {
snprintf(buffer,bsize,"%3.0f",humidity); snprintf(buffer, bsize, fmt_dec_100, humidity);
} }
result.unit = "%"; result.unit = "%";
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:V:P"){ else if (value->getFormat() == "formatXdr:V:P"){
double volume = 0; double volume = 0;
if(usesimudata == false) { if (usesimudata == false) {
volume = value->value; // Value in % volume = value->value; // Value in %
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 85.8 + float(random(0, 50)) / 10.0; rawvalue = 85.8 + float(random(0, 50)) / 10.0;
volume = rawvalue; volume = rawvalue;
} }
if(volume < 10){ if (volume < 10) {
snprintf(buffer,bsize,"%3.2f",volume); snprintf(buffer, bsize, fmt_dec_1, volume);
} }
if(volume >= 10 && volume < 100){ else if (volume < 100) {
snprintf(buffer,bsize,"%3.1f",volume); snprintf(buffer, bsize, fmt_dec_10, volume);
} }
if(volume >= 100){ else if (volume >= 100) {
snprintf(buffer,bsize,"%3.0f",volume); snprintf(buffer, bsize, fmt_dec_100, volume);
} }
result.unit = "%"; result.unit = "%";
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:V:M"){ else if (value->getFormat() == "formatXdr:V:M"){
double volume = 0; double volume = 0;
if(usesimudata == false) { if (usesimudata == false) {
volume = value->value; // Value in l volume = value->value; // Value in l
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 75.2 + float(random(0, 50)) / 10.0; rawvalue = 75.2 + float(random(0, 50)) / 10.0;
volume = rawvalue; volume = rawvalue;
} }
if(volume < 10){ if (volume < 10) {
snprintf(buffer,bsize,"%3.2f",volume); snprintf(buffer, bsize, fmt_dec_1, volume);
} }
if(volume >= 10 && volume < 100){ else if (volume < 100) {
snprintf(buffer,bsize,"%3.1f",volume); snprintf(buffer, bsize, fmt_dec_10, volume);
} }
if(volume >= 100){ else {
snprintf(buffer,bsize,"%3.0f",volume); snprintf(buffer, bsize, fmt_dec_100, volume);
} }
result.unit = "l"; result.unit = "l";
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:R:I"){ else if (value->getFormat() == "formatXdr:R:I"){
double flow = 0; double flow = 0;
if(usesimudata == false) { if (usesimudata == false) {
flow = value->value; // Value in l/min flow = value->value; // Value in l/min
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 7.5 + float(random(0, 20)) / 10.0; rawvalue = 7.5 + float(random(0, 20)) / 10.0;
flow = rawvalue; flow = rawvalue;
} }
if(flow < 10){ if (flow < 10) {
snprintf(buffer,bsize,"%3.2f",flow); snprintf(buffer, bsize, fmt_dec_1, flow);
} }
if(flow >= 10 && flow < 100){ else if (flow < 100) {
snprintf(buffer,bsize,"%3.1f",flow); snprintf(buffer, bsize, fmt_dec_10, flow);
} }
if(flow >= 100){ else {
snprintf(buffer,bsize,"%3.0f",flow); snprintf(buffer, bsize, fmt_dec_100, flow);
} }
result.unit = "l/min"; result.unit = "l/min";
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:G:"){ else if (value->getFormat() == "formatXdr:G:"){
double generic = 0; double generic = 0;
if(usesimudata == false) { if (usesimudata == false) {
generic = value->value; // Value in l/min generic = value->value;
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 18.5 + float(random(0, 20)) / 10.0; rawvalue = 18.5 + float(random(0, 20)) / 10.0;
generic = rawvalue; generic = rawvalue;
} }
if(generic < 10){ if (generic < 10) {
snprintf(buffer,bsize,"%3.2f",generic); snprintf(buffer, bsize, fmt_dec_1, generic);
} }
if(generic >= 10 && generic < 100){ else if (generic < 100) {
snprintf(buffer,bsize,"%3.1f",generic); snprintf(buffer, bsize, fmt_dec_10, generic);
} }
if(generic >= 100){ else {
snprintf(buffer,bsize,"%3.0f",generic); snprintf(buffer, bsize, fmt_dec_100, generic);
} }
result.unit = ""; result.unit = "";
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:A:P"){ else if (value->getFormat() == "formatXdr:A:P"){
double dplace = 0; double dplace = 0;
if(usesimudata == false) { if (usesimudata == false) {
dplace = value->value; // Value in % dplace = value->value; // Value in %
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 55.3 + float(random(0, 20)) / 10.0; rawvalue = 55.3 + float(random(0, 20)) / 10.0;
dplace = rawvalue; dplace = rawvalue;
} }
if(dplace < 10){ if (dplace < 10) {
snprintf(buffer,bsize,"%3.2f",dplace); snprintf(buffer, bsize, fmt_dec_1, dplace);
} }
if(dplace >= 10 && dplace < 100){ else if (dplace < 100) {
snprintf(buffer,bsize,"%3.1f",dplace); snprintf(buffer, bsize, fmt_dec_10, dplace);
} }
if(dplace >= 100){ else {
snprintf(buffer,bsize,"%3.0f",dplace); snprintf(buffer, bsize, fmt_dec_100, dplace);
} }
result.unit = "%"; result.unit = "%";
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:A:D"){ else if (value->getFormat() == "formatXdr:A:D"){
double angle = 0; double angle = 0;
if(usesimudata == false) { if (usesimudata == false) {
angle = value->value; angle = value->value;
angle = angle * 57.2958; // Unit conversion form rad to deg angle = angle * 57.2958; // Unit conversion form rad to deg
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = PI / 100 + (random(-5, 5) / 360 * 2* PI); rawvalue = PI / 100 + (random(-5, 5) / 360 * 2* PI);
angle = rawvalue * 57.2958; angle = rawvalue * 57.2958;
} }
if(angle > -10 && angle < 10){ if (angle > -10 && angle < 10) {
snprintf(buffer,bsize,"%3.1f",angle); snprintf(buffer,bsize,"%3.1f",angle);
} }
else{ else {
snprintf(buffer,bsize,"%3.0f",angle); snprintf(buffer,bsize,"%3.0f",angle);
} }
result.unit = "Deg"; result.unit = "Deg";
@@ -775,41 +805,41 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:T:R"){ else if (value->getFormat() == "formatXdr:T:R"){
double rpm = 0; double rpm = 0;
if(usesimudata == false) { if (usesimudata == false) {
rpm = value->value; // Value in rpm rpm = value->value; // Value in rpm
rawvalue = value->value; rawvalue = value->value;
} }
else{ else {
rawvalue = 2505 + random(0, 20); rawvalue = 2505 + random(0, 20);
rpm = rawvalue; rpm = rawvalue;
} }
if(rpm < 10){ if (rpm < 10) {
snprintf(buffer,bsize,"%3.2f",rpm); snprintf(buffer, bsize, fmt_dec_1, rpm);
} }
if(rpm >= 10 && rpm < 100){ else if (rpm < 100) {
snprintf(buffer,bsize,"%3.1f",rpm); snprintf(buffer, bsize, fmt_dec_10, rpm);
} }
if(rpm >= 100){ else {
snprintf(buffer,bsize,"%3.0f",rpm); snprintf(buffer, bsize, fmt_dec_100, rpm);
} }
result.unit = "rpm"; result.unit = "rpm";
} }
//######################################################## //########################################################
// Default format // Default format
//######################################################## //########################################################
else{ else {
if(value->value < 10){ if (value->value < 10) {
snprintf(buffer,bsize,"%3.2f",value->value); snprintf(buffer, bsize, fmt_dec_1, value->value);
} }
if(value->value >= 10 && value->value < 100){ else if (value->value < 100) {
snprintf(buffer,bsize,"%3.1f",value->value); snprintf(buffer, bsize, fmt_dec_10, value->value);
} }
if(value->value >= 100){ else {
snprintf(buffer,bsize,"%3.0f",value->value); snprintf(buffer, bsize, fmt_dec_100, value->value);
} }
result.unit = ""; result.unit = "";
} }
buffer[bsize]=0; buffer[bsize] = 0;
result.value = rawvalue; // Return value is only necessary in case of simulation of graphic pointer result.value = rawvalue; // Return value is only necessary in case of simulation of graphic pointer
result.svalue = String(buffer); result.svalue = String(buffer);
return result; return result;

View File

@@ -82,7 +82,7 @@
// Direction pin for RS485 NMEA0183 // Direction pin for RS485 NMEA0183
#define OBP_DIRECTION_PIN 8 #define OBP_DIRECTION_PIN 8
// I2C // I2C
#define I2C_SPEED 10000UL // 10kHz clock speed on I2C bus #define I2C_SPEED 100000UL // 100kHz clock speed on I2C bus
#define OBP_I2C_SDA 21 #define OBP_I2C_SDA 21
#define OBP_I2C_SCL 38 #define OBP_I2C_SCL 38
// DS1388 RTC // DS1388 RTC
@@ -120,10 +120,10 @@
#define SHOW_TIME 6000 // Show time in [ms] for logo and WiFi QR code #define SHOW_TIME 6000 // Show time in [ms] for logo and WiFi QR code
#define FULL_REFRESH_TIME 600 // Refresh cycle time in [s][600...3600] for full display update (very important healcy function) #define FULL_REFRESH_TIME 600 // Refresh cycle time in [s][600...3600] for full display update (very important healcy function)
// SPI SD-Card // SPI SD-Card
#define SD_SPI_CS 10 #define SD_SPI_CS GPIO_NUM_10
#define SD_SPI_MOSI 40 #define SD_SPI_MOSI GPIO_NUM_40
#define SD_SPI_CLK 39 #define SD_SPI_CLK GPIO_NUM_39
#define SD_SPI_MISO 13 #define SD_SPI_MISO GPIO_NUM_13
// GPS (NEO-6M, NEO-M8N, ATGM336H) // GPS (NEO-6M, NEO-M8N, ATGM336H)
#define OBP_GPS_RX 19 #define OBP_GPS_RX 19

View File

@@ -1,5 +1,147 @@
#include "OBPDataOperations.h" #include "OBPDataOperations.h"
// --- Class HstryBuf ---------------
// Init history buffers for selected boat data
void HstryBuf::init(BoatValueList* boatValues, GwLog *log) {
logger = log;
int hstryUpdFreq = 1000; // Update frequency for history buffers in ms
int hstryMinVal = 0; // Minimum value for these history buffers
twdHstryMax = 6283; // Max value for wind direction (TWD, AWD) in rad [0...2*PI], shifted by 1000 for 3 decimals
twsHstryMax = 65000; // Max value for wind speed (TWS, AWS) in m/s [0..65], shifted by 1000 for 3 decimals
awdHstryMax = twdHstryMax;
awsHstryMax = twsHstryMax;
twdHstryMin = hstryMinVal;
twsHstryMin = hstryMinVal;
awdHstryMin = hstryMinVal;
awsHstryMin = hstryMinVal;
const double DBL_MAX = std::numeric_limits<double>::max();
// Initialize history buffers with meta data
hstryBufList.twdHstry->setMetaData("TWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax);
hstryBufList.twsHstry->setMetaData("TWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax);
hstryBufList.awdHstry->setMetaData("AWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax);
hstryBufList.awsHstry->setMetaData("AWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax);
// create boat values for history data types, if they don't exist yet
twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName());
twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName());
twaBVal = boatValues->findValueOrCreate("TWA");
awdBVal = boatValues->findValueOrCreate(hstryBufList.awdHstry->getName());
awsBVal = boatValues->findValueOrCreate(hstryBufList.awsHstry->getName());
if (!awdBVal->valid) { // AWD usually does not exist
awdBVal->setFormat(hstryBufList.awdHstry->getFormat());
awdBVal->value = DBL_MAX;
}
// collect boat values for true wind calculation
awaBVal = boatValues->findValueOrCreate("AWA");
hdtBVal = boatValues->findValueOrCreate("HDT");
hdmBVal = boatValues->findValueOrCreate("HDM");
varBVal = boatValues->findValueOrCreate("VAR");
cogBVal = boatValues->findValueOrCreate("COG");
sogBVal = boatValues->findValueOrCreate("SOG");
}
// Handle history buffers for TWD, TWS, AWD, AWS
//void HstryBuf::handleHstryBuf(GwApi* api, BoatValueList* boatValues, bool useSimuData) {
void HstryBuf::handleHstryBuf(bool useSimuData) {
static int16_t twd = 20; //initial value only relevant if we use simulation data
static uint16_t tws = 20; //initial value only relevant if we use simulation data
static double awd, aws, hdt = 20; //initial value only relevant if we use simulation data
GwApi::BoatValue *calBVal; // temp variable just for data calibration -> we don't want to calibrate the original data here
LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf: TWD_isValid? %d, twdBVal: %.1f, twaBVal: %.1f, twsBVal: %.1f", twdBVal->valid, twdBVal->value * RAD_TO_DEG,
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852);
if (twdBVal->valid) {
calBVal = new GwApi::BoatValue("TWD"); // temporary solution for calibration of history buffer values
calBVal->setFormat(twdBVal->getFormat());
calBVal->value = twdBVal->value;
calBVal->valid = twdBVal->valid;
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
twd = static_cast<int16_t>(std::round(calBVal->value * 1000.0));
if (twd >= twdHstryMin && twd <= twdHstryMax) {
hstryBufList.twdHstry->add(twd);
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
twd += random(-20, 20);
twd = WindUtils::to360(twd);
hstryBufList.twdHstry->add(static_cast<int16_t>(DegToRad(twd) * 1000.0));
}
if (twsBVal->valid) {
calBVal = new GwApi::BoatValue("TWS"); // temporary solution for calibration of history buffer values
calBVal->setFormat(twsBVal->getFormat());
calBVal->value = twsBVal->value;
calBVal->valid = twsBVal->valid;
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
tws = static_cast<uint16_t>(std::round(calBVal->value * 1000));
if (tws >= twsHstryMin && tws <= twsHstryMax) {
hstryBufList.twsHstry->add(tws);
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
tws += random(-5000, 5000); // TWS value in m/s; expands to 3 decimals
tws = constrain(tws, 0, 25000); // Limit TWS to [0..25] m/s
hstryBufList.twsHstry->add(tws);
}
if (awaBVal->valid) {
if (hdtBVal->valid) {
hdt = hdtBVal->value; // Use HDT if available
} else {
hdt = WindUtils::calcHDT(&hdmBVal->value, &varBVal->value, &cogBVal->value, &sogBVal->value);
}
awd = awaBVal->value + hdt;
awd = WindUtils::to2PI(awd);
calBVal = new GwApi::BoatValue("AWD"); // temporary solution for calibration of history buffer values
calBVal->value = awd;
calBVal->setFormat(awdBVal->getFormat());
calBVal->valid = true;
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
awdBVal->value = calBVal->value;
awdBVal->valid = true;
awd = std::round(calBVal->value * 1000.0);
if (awd >= awdHstryMin && awd <= awdHstryMax) {
hstryBufList.awdHstry->add(static_cast<int16_t>(awd));
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
awd += random(-20, 20);
awd = WindUtils::to360(awd);
hstryBufList.awdHstry->add(static_cast<int16_t>(DegToRad(awd) * 1000.0));
}
if (awsBVal->valid) {
calBVal = new GwApi::BoatValue("AWS"); // temporary solution for calibration of history buffer values
calBVal->setFormat(awsBVal->getFormat());
calBVal->value = awsBVal->value;
calBVal->valid = awsBVal->valid;
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
aws = std::round(calBVal->value * 1000);
if (aws >= awsHstryMin && aws <= awsHstryMax) {
hstryBufList.awsHstry->add(static_cast<uint16_t>(aws));
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
aws += random(-5000, 5000); // TWS value in m/s; expands to 1 decimal
aws = constrain(aws, 0, 25000); // Limit TWS to [0..25] m/s
hstryBufList.awsHstry->add(aws);
}
}
// --- Class HstryBuf ---------------
// --- Class WindUtils --------------
double WindUtils::to2PI(double a) double WindUtils::to2PI(double a)
{ {
a = fmod(a, 2 * M_PI); a = fmod(a, 2 * M_PI);
@@ -68,13 +210,28 @@ void WindUtils::calcTwdSA(const double* AWA, const double* AWS,
double awd = *AWA + *HDT; double awd = *AWA + *HDT;
awd = to2PI(awd); awd = to2PI(awd);
double stw = -*STW; double stw = -*STW;
// Serial.println("\ncalcTwdSA: AWA: " + String(*AWA) + ", AWS: " + String(*AWS) + ", CTW: " + String(*CTW) + ", STW: " + String(*STW) + ", HDT: " + String(*HDT));
addPolar(&awd, AWS, CTW, &stw, TWD, TWS); addPolar(&awd, AWS, CTW, &stw, TWD, TWS);
// Normalize TWD and TWA to 0-360° // Normalize TWD and TWA to 0-360°
*TWD = to2PI(*TWD); *TWD = to2PI(*TWD);
*TWA = toPI(*TWD - *HDT); *TWA = toPI(*TWD - *HDT);
// Serial.println("calcTwdSA: TWD: " + String(*TWD) + ", TWS: " + String(*TWS)); }
double WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal)
{
double 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::calcTrueWind(const double* awaVal, const double* awsVal, bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal,
@@ -83,38 +240,32 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal,
{ {
double stw, hdt, ctw; double stw, hdt, ctw;
double twd, tws, twa; double twd, tws, twa;
static const double DBL_MIN = std::numeric_limits<double>::lowest(); double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
if (*hdtVal != DBL_MIN) { if (*hdtVal != DBL_MAX) {
hdt = *hdtVal; // Use HDT if available hdt = *hdtVal; // Use HDT if available
} else { } else {
if (*hdmVal != DBL_MIN && *varVal != DBL_MIN) { hdt = calcHDT(hdmVal, varVal, cogVal, sogVal);
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+VAR or COG
}
} }
if (*cogVal != DBL_MIN) { if (*cogVal != DBL_MAX && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG
ctw = *cogVal; // Use COG as CTW if available
// ctw = *cogVal + ((*cogVal - hdt) / 2); // Estimate CTW from COG ctw = *cogVal; // Use COG for CTW if available
} else { } else {
ctw = hdt; // 2nd approximation for CTW; hdt must exist if we reach this part of the code ctw = hdt; // 2nd approximation for CTW; hdt must exist if we reach this part of the code
} }
if (*stwVal != DBL_MIN) { if (*stwVal != DBL_MAX) {
stw = *stwVal; // Use STW if available stw = *stwVal; // Use STW if available
} else if (*sogVal != DBL_MIN) { } else if (*sogVal != DBL_MAX) {
stw = *sogVal; stw = *sogVal;
} else { } else {
// If STW and SOG are not available, we cannot calculate true wind // If STW and SOG are not available, we cannot calculate true wind
return false; return false;
} }
// Serial.println("\ncalcTrueWind: HDT: " + String(hdt) + ", CTW: " + String(ctw) + ", STW: " + String(stw));
if ((*awaVal == DBL_MIN) || (*awsVal == DBL_MIN)) { if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) {
// Cannot calculate true wind without valid AWA, AWS; other checks are done earlier // Cannot calculate true wind without valid AWA, AWS; other checks are done earlier
return false; return false;
} else { } else {
@@ -127,31 +278,45 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal,
} }
} }
void HstryBuf::fillWndBufSimData(tBoatHstryData& hstryBufs) // Calculate true wind data and add to obp60task boat data list
// Fill most part of TWD and TWS history buffer with simulated data bool WindUtils::addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog* log) {
{
double value = 20.0; GwLog* logger = log;
int16_t value2 = 0;
for (int i = 0; i < 900; i++) { double awaVal, awsVal, cogVal, stwVal, sogVal, hdtVal, hdmVal, varVal;
value += random(-20, 20); double twd, tws, twa;
value = WindUtils::to360(value); bool isCalculated = false;
value2 = static_cast<int16_t>(value * DEG_TO_RAD * 1000);
hstryBufs.twdHstry->add(value2); awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX;
awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX;
cogVal = cogBVal->valid ? cogBVal->value : DBL_MAX;
stwVal = stwBVal->valid ? stwBVal->value : DBL_MAX;
sogVal = sogBVal->valid ? sogBVal->value : DBL_MAX;
hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX;
varVal = varBVal->valid ? varBVal->value : DBL_MAX;
LOG_DEBUG(GwLog::DEBUG,"obp60task addTrueWind: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852,
cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG);
isCalculated = calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa);
if (isCalculated) { // Replace values only, if successfully calculated and not already available
if (!twdBVal->valid) {
twdBVal->value = twd;
twdBVal->valid = true;
}
if (!twsBVal->valid) {
twsBVal->value = tws;
twsBVal->valid = true;
}
if (!twaBVal->valid) {
twaBVal->value = twa;
twaBVal->valid = true;
}
} }
LOG_DEBUG(GwLog::DEBUG,"obp60task addTrueWind: isCalculated %d, TWD %.1f, TWA %.1f, TWS %.1f", isCalculated, twdBVal->value * RAD_TO_DEG,
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852);
return isCalculated;
} }
// --- Class WindUtils --------------
/* 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<int16_t>(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;
}*/

View File

@@ -1,36 +1,91 @@
#pragma once #pragma once
#include "GwApi.h" #include <N2kMessages.h>
#include "OBPRingBuffer.h" #include "OBPRingBuffer.h"
#include <Arduino.h> #include "BoatDataCalibration.h" // Functions lib for data instance calibration
#include "obp60task.h"
#include <math.h> #include <math.h>
typedef struct { typedef struct {
RingBuffer<int16_t>* twdHstry; RingBuffer<int16_t>* twdHstry;
RingBuffer<int16_t>* twsHstry; RingBuffer<uint16_t>* twsHstry;
RingBuffer<int16_t>* awdHstry;
RingBuffer<uint16_t>* awsHstry;
} tBoatHstryData; // Holds pointers to all history buffers for boat data } tBoatHstryData; // Holds pointers to all history buffers for boat data
class HstryBuf { class HstryBuf {
private:
GwLog *logger;
RingBuffer<int16_t> twdHstry; // Circular buffer to store true wind direction values
RingBuffer<uint16_t> twsHstry; // Circular buffer to store true wind speed values (TWS)
RingBuffer<int16_t> awdHstry; // Circular buffer to store apparant wind direction values
RingBuffer<uint16_t> awsHstry; // Circular buffer to store apparant xwind speed values (AWS)
int16_t twdHstryMin; // Min value for wind direction (TWD) in history buffer
int16_t twdHstryMax; // Max value for wind direction (TWD) in history buffer
uint16_t twsHstryMin;
uint16_t twsHstryMax;
int16_t awdHstryMin;
int16_t awdHstryMax;
uint16_t awsHstryMin;
uint16_t awsHstryMax;
// boat values for buffers and for true wind calculation
GwApi::BoatValue *twdBVal, *twsBVal, *twaBVal, *awdBVal, *awsBVal;
GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal;
public: public:
void fillWndBufSimData(tBoatHstryData& hstryBufs); // Fill most part of the TWD and TWS history buffer with simulated data tBoatHstryData hstryBufList;
HstryBuf(){
hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry}; // Generate history buffers of zero size
};
HstryBuf(int size) {
hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry};
hstryBufList.twdHstry->resize(size); // store <size> xWD values for <size>/60 minutes history
hstryBufList.twsHstry->resize(size);
hstryBufList.awdHstry->resize(size);
hstryBufList.awsHstry->resize(size);
};
void init(BoatValueList* boatValues, GwLog *log);
void handleHstryBuf(bool useSimuData);
}; };
class WindUtils { class WindUtils {
private:
GwApi::BoatValue *twdBVal, *twsBVal, *twaBVal;
GwApi::BoatValue *awaBVal, *awsBVal, *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal;
static constexpr double DBL_MAX = std::numeric_limits<double>::max();
public: public:
WindUtils(BoatValueList* boatValues){
twdBVal = boatValues->findValueOrCreate("TWD");
twsBVal = boatValues->findValueOrCreate("TWS");
twaBVal = boatValues->findValueOrCreate("TWA");
awaBVal = boatValues->findValueOrCreate("AWA");
awsBVal = boatValues->findValueOrCreate("AWS");
cogBVal = boatValues->findValueOrCreate("COG");
stwBVal = boatValues->findValueOrCreate("STW");
sogBVal = boatValues->findValueOrCreate("SOG");
hdtBVal = boatValues->findValueOrCreate("HDT");
hdmBVal = boatValues->findValueOrCreate("HDM");
varBVal = boatValues->findValueOrCreate("VAR");
};
static double to2PI(double a); static double to2PI(double a);
static double toPI(double a); static double toPI(double a);
static double to360(double a); static double to360(double a);
static double to180(double a); static double to180(double a);
static void toCart(const double* phi, const double* r, double* x, double* y); void toCart(const double* phi, const double* r, double* x, double* y);
static void toPol(const double* x, const double* y, double* phi, double* r); void toPol(const double* x, const double* y, double* phi, double* r);
static void addPolar(const double* phi1, const double* r1, void addPolar(const double* phi1, const double* r1,
const double* phi2, const double* r2, const double* phi2, const double* r2,
double* phi, double* r); double* phi, double* r);
static void calcTwdSA(const double* AWA, const double* AWS, void calcTwdSA(const double* AWA, const double* AWS,
const double* CTW, const double* STW, const double* HDT, const double* CTW, const double* STW, const double* HDT,
double* TWD, double* TWS, double* TWA); double* TWD, double* TWS, double* TWA);
static bool calcTrueWind(const double* awaVal, const double* awsVal, static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal);
bool calcTrueWind(const double* awaVal, const double* awsVal,
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal); const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal);
bool addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog *log);
}; };

View File

@@ -1,37 +1,75 @@
#pragma once #pragma once
#include "GwSynchronized.h" #include "GwSynchronized.h"
#include "WString.h"
#include "esp_heap_caps.h"
#include <algorithm> #include <algorithm>
#include <limits> #include <limits>
#include <stdexcept> #include <stdexcept>
#include <vector> #include <vector>
#include "WString.h"
template <typename T>
struct PSRAMAllocator {
using value_type = T;
PSRAMAllocator() = default;
template <class U>
constexpr PSRAMAllocator(const PSRAMAllocator<U>&) noexcept { }
T* allocate(std::size_t n)
{
void* ptr = heap_caps_malloc(n * sizeof(T), MALLOC_CAP_SPIRAM);
if (!ptr) {
return nullptr;
} else {
return static_cast<T*>(ptr);
}
}
void deallocate(T* p, std::size_t) noexcept
{
heap_caps_free(p);
}
};
template <class T, class U>
bool operator==(const PSRAMAllocator<T>&, const PSRAMAllocator<U>&) { return true; }
template <class T, class U>
bool operator!=(const PSRAMAllocator<T>&, const PSRAMAllocator<U>&) { return false; }
template <typename T> template <typename T>
class RingBuffer { class RingBuffer {
private: private:
mutable SemaphoreHandle_t bufLocker; // std::vector<T> buffer; // THE buffer vector
std::vector<T> buffer; std::vector<T, PSRAMAllocator<T>> buffer; // THE buffer vector, allocated in PSRAM
size_t capacity; size_t capacity;
size_t head; // Points to the next insertion position size_t head; // Points to the next insertion position
size_t first; // Points to the first (oldest) valid element size_t first; // Points to the first (oldest) valid element
size_t last; // Points to the last (newest) valid element size_t last; // Points to the last (newest) valid element
size_t count; // Number of valid elements currently in buffer size_t count; // Number of valid elements currently in buffer
bool is_Full; // Indicates that all buffer elements are used and ringing is in use bool is_Full; // Indicates that all buffer elements are used and ringing is in use
T MIN_VAL; // lowest possible value of buffer T MIN_VAL; // lowest possible value of buffer of type <T>
T MAX_VAL; // highest possible value of buffer of type <T> T MAX_VAL; // highest possible value of buffer of type <T> -> indicates invalid value in buffer
mutable SemaphoreHandle_t bufLocker;
// metadata for buffer // metadata for buffer
String dataName; // Name of boat data in buffer String dataName; // Name of boat data in buffer
String dataFmt; // Format of boat data in buffer String dataFmt; // Format of boat data in buffer
int updFreq; // Update frequency in milliseconds int updFreq; // Update frequency in milliseconds
T smallest; // Value range of buffer: smallest value T smallest; // Value range of buffer: smallest value; needs to be => MIN_VAL
T largest; // Value range of buffer: biggest value T largest; // Value range of buffer: biggest value; needs to be < MAX_VAL, since MAX_VAL indicates invalid entries
void initCommon();
public: public:
RingBuffer();
RingBuffer(size_t size); RingBuffer(size_t size);
void setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue); // Set meta data for buffer void setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue); // Set meta data for buffer
bool getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue); // Get meta data of buffer bool getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue); // Get meta data of buffer
bool getMetaData(String& name, String& format);
String getName() const; // Get buffer name String getName() const; // Get buffer name
String getFormat() const; // Get buffer data format
void add(const T& value); // Add a new value to buffer void add(const T& value); // Add a new value to buffer
T get(size_t index) const; // Get value at specific position (0-based index from oldest to newest) T get(size_t index) const; // Get value at specific position (0-based index from oldest to newest)
T getFirst() const; // Get the first (oldest) value in buffer T getFirst() const; // Get the first (oldest) value in buffer
@@ -50,9 +88,10 @@ public:
size_t getLastIdx() const; // Get the index of newest value in buffer size_t getLastIdx() const; // Get the index of newest value in buffer
bool isEmpty() const; // Check if buffer is empty bool isEmpty() const; // Check if buffer is empty
bool isFull() const; // Check if buffer is full bool isFull() const; // Check if buffer is full
T getMinVal() const; // Get lowest possible value for buffer; used for initialized buffer data T getMinVal() const; // Get lowest possible value for buffer
T getMaxVal() const; // Get highest possible value for buffer T getMaxVal() const; // Get highest possible value for buffer; used for unset/invalid buffer data
void clear(); // Clear buffer void clear(); // Clear buffer
void resize(size_t size); // Delete buffer and set new size
T operator[](size_t index) const; // Operator[] for convenient access (same as get()) T operator[](size_t index) const; // Operator[] for convenient access (same as get())
std::vector<T> getAllValues() const; // Get all current values as a vector std::vector<T> getAllValues() const; // Get all current values as a vector
}; };

View File

@@ -1,5 +1,30 @@
#include "OBPRingBuffer.h" #include "OBPRingBuffer.h"
template <typename T>
void RingBuffer<T>::initCommon() {
MIN_VAL = std::numeric_limits<T>::lowest();
MAX_VAL = std::numeric_limits<T>::max();
dataName = "";
dataFmt = "";
updFreq = -1;
smallest = MIN_VAL;
largest = MAX_VAL;
bufLocker = xSemaphoreCreateMutex();
}
template <typename T>
RingBuffer<T>::RingBuffer()
: capacity(0)
, head(0)
, first(0)
, last(0)
, count(0)
, is_Full(false)
{
initCommon();
// <buffer> stays empty
}
template <typename T> template <typename T>
RingBuffer<T>::RingBuffer(size_t size) RingBuffer<T>::RingBuffer(size_t size)
: capacity(size) : capacity(size)
@@ -9,23 +34,10 @@ RingBuffer<T>::RingBuffer(size_t size)
, count(0) , count(0)
, is_Full(false) , is_Full(false)
{ {
bufLocker = xSemaphoreCreateMutex(); initCommon();
if (size == 0) { buffer.reserve(size);
// return false; buffer.resize(size, MAX_VAL); // MAX_VAL indicate invalid values
}
MIN_VAL = std::numeric_limits<T>::lowest();
MAX_VAL = std::numeric_limits<T>::max();
dataName = "";
dataFmt = "";
updFreq = -1;
smallest = MIN_VAL;
largest = MAX_VAL;
buffer.resize(size, MIN_VAL);
// return true;
} }
// Specify meta data of buffer content // Specify meta data of buffer content
@@ -57,6 +69,20 @@ bool RingBuffer<T>::getMetaData(String& name, String& format, int& updateFrequen
return true; return true;
} }
// Get meta data of buffer content
template <typename T>
bool RingBuffer<T>::getMetaData(String& name, String& format)
{
if (dataName == "" || dataFmt == "") {
return false; // Meta data not set
}
GWSYNCHRONIZED(&bufLocker);
name = dataName;
format = dataFmt;
return true;
}
// Get buffer name // Get buffer name
template <typename T> template <typename T>
String RingBuffer<T>::getName() const String RingBuffer<T>::getName() const
@@ -64,13 +90,20 @@ String RingBuffer<T>::getName() const
return dataName; return dataName;
} }
// Get buffer data format
template <typename T>
String RingBuffer<T>::getFormat() const
{
return dataFmt;
}
// Add a new value to buffer // Add a new value to buffer
template <typename T> template <typename T>
void RingBuffer<T>::add(const T& value) void RingBuffer<T>::add(const T& value)
{ {
GWSYNCHRONIZED(&bufLocker); GWSYNCHRONIZED(&bufLocker);
if (value < smallest || value > largest) { if (value < smallest || value > largest) {
buffer[head] = MIN_VAL; // Store MIN_VAL if value is out of range buffer[head] = MAX_VAL; // Store MAX_VAL if value is out of range
} else { } else {
buffer[head] = value; buffer[head] = value;
} }
@@ -94,7 +127,7 @@ T RingBuffer<T>::get(size_t index) const
{ {
GWSYNCHRONIZED(&bufLocker); GWSYNCHRONIZED(&bufLocker);
if (isEmpty() || index < 0 || index >= count) { if (isEmpty() || index < 0 || index >= count) {
return MIN_VAL; return MAX_VAL;
} }
size_t realIndex = (first + index) % capacity; size_t realIndex = (first + index) % capacity;
@@ -113,7 +146,7 @@ template <typename T>
T RingBuffer<T>::getFirst() const T RingBuffer<T>::getFirst() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
return get(0); return get(0);
} }
@@ -123,7 +156,7 @@ template <typename T>
T RingBuffer<T>::getLast() const T RingBuffer<T>::getLast() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
return get(count - 1); return get(count - 1);
} }
@@ -133,14 +166,14 @@ template <typename T>
T RingBuffer<T>::getMin() const T RingBuffer<T>::getMin() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
T minVal = MAX_VAL; T minVal = MAX_VAL;
T value; T value;
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
value = get(i); value = get(i);
if (value < minVal && value != MIN_VAL) { if (value < minVal && value != MAX_VAL) {
minVal = value; minVal = value;
} }
} }
@@ -152,7 +185,7 @@ template <typename T>
T RingBuffer<T>::getMin(size_t amount) const T RingBuffer<T>::getMin(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return MIN_VAL; return MAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
@@ -161,7 +194,7 @@ T RingBuffer<T>::getMin(size_t amount) const
T value; T value;
for (size_t i = 0; i < amount; i++) { for (size_t i = 0; i < amount; i++) {
value = get(count - 1 - i); value = get(count - 1 - i);
if (value < minVal && value != MIN_VAL) { if (value < minVal && value != MAX_VAL) {
minVal = value; minVal = value;
} }
} }
@@ -173,14 +206,14 @@ template <typename T>
T RingBuffer<T>::getMax() const T RingBuffer<T>::getMax() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
T maxVal = MIN_VAL; T maxVal = MIN_VAL;
T value; T value;
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
value = get(i); value = get(i);
if (value > maxVal && value != MIN_VAL) { if (value > maxVal && value != MAX_VAL) {
maxVal = value; maxVal = value;
} }
} }
@@ -192,7 +225,7 @@ template <typename T>
T RingBuffer<T>::getMax(size_t amount) const T RingBuffer<T>::getMax(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return MIN_VAL; return MAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
@@ -201,7 +234,7 @@ T RingBuffer<T>::getMax(size_t amount) const
T value; T value;
for (size_t i = 0; i < amount; i++) { for (size_t i = 0; i < amount; i++) {
value = get(count - 1 - i); value = get(count - 1 - i);
if (value > maxVal && value != MIN_VAL) { if (value > maxVal && value != MAX_VAL) {
maxVal = value; maxVal = value;
} }
} }
@@ -213,7 +246,7 @@ template <typename T>
T RingBuffer<T>::getMid() const T RingBuffer<T>::getMid() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
return (getMin() + getMax()) / static_cast<T>(2); return (getMin() + getMax()) / static_cast<T>(2);
@@ -224,7 +257,7 @@ template <typename T>
T RingBuffer<T>::getMid(size_t amount) const T RingBuffer<T>::getMid(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return MIN_VAL; return MAX_VAL;
} }
if (amount > count) if (amount > count)
@@ -238,7 +271,7 @@ template <typename T>
T RingBuffer<T>::getMedian() const T RingBuffer<T>::getMedian() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
// Create a temporary vector with current valid elements // Create a temporary vector with current valid elements
@@ -267,7 +300,7 @@ template <typename T>
T RingBuffer<T>::getMedian(size_t amount) const T RingBuffer<T>::getMedian(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return MIN_VAL; return MAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
@@ -335,14 +368,14 @@ bool RingBuffer<T>::isFull() const
return is_Full; return is_Full;
} }
// Get lowest possible value for buffer; used for non-set buffer data // Get lowest possible value for buffer
template <typename T> template <typename T>
T RingBuffer<T>::getMinVal() const T RingBuffer<T>::getMinVal() const
{ {
return MIN_VAL; return MIN_VAL;
} }
// Get highest possible value for buffer // Get highest possible value for buffer; used for unset/invalid buffer data
template <typename T> template <typename T>
T RingBuffer<T>::getMaxVal() const T RingBuffer<T>::getMaxVal() const
{ {
@@ -361,6 +394,23 @@ void RingBuffer<T>::clear()
is_Full = false; is_Full = false;
} }
// Delete buffer and set new size
template <typename T>
void RingBuffer<T>::resize(size_t newSize)
{
GWSYNCHRONIZED(&bufLocker);
capacity = newSize;
head = 0;
first = 0;
last = 0;
count = 0;
is_Full = false;
buffer.clear();
buffer.reserve(newSize);
buffer.resize(newSize, MAX_VAL);
}
// Get all current values as a vector // Get all current values as a vector
template <typename T> template <typename T>
std::vector<T> RingBuffer<T>::getAllValues() const std::vector<T> RingBuffer<T>::getAllValues() const

View File

@@ -0,0 +1,204 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include <vector>
#include <algorithm> // for vector sorting
/*
* SkyView / Satellites
*/
class PageSkyView : public Page
{
private:
String flashLED;
GwBoatData *bd;
public:
PageSkyView(CommonData &common)
{
commonData = &common;
// task name access is for example purpose only
TaskHandle_t currentTaskHandle = xTaskGetCurrentTaskHandle();
const char* taskName = pcTaskGetName(currentTaskHandle);
common.logger->logDebug(GwLog::LOG, "Instantiate PageSkyView in task '%s'", taskName);
flashLED = common.config->getString(common.config->flashLED);
}
int handleKey(int key) {
// return 0 to mark the key handled completely
// return the key to allow further action
if (key == 11) {
commonData->keylock = !commonData->keylock;
return 0;
}
return key;
}
void displayNew(PageData &pageData) {
#ifdef BOARD_OBP60S3
// Clear optical warning
if (flashLED == "Limit Violation") {
setBlinkingLED(false);
setFlashLED(false);
}
#endif
bd = pageData.api->getBoatData();
};
// Comparator function to sort by SNR
static bool compareBySNR(const GwSatInfo& a, const GwSatInfo& b) {
return a.SNR > b.SNR; // Sort in descending order
}
int displayPage(PageData &pageData) {
GwLog *logger = commonData->logger;
std::vector<GwSatInfo> sats;
int nSat = bd->SatInfo->getNumSats();
logger->logDebug(GwLog::LOG, "Drawing at PageSkyView, %d satellites", nSat);
for (int i = 0; i < nSat; i++) {
sats.push_back(*bd->SatInfo->getAt(i));
}
std::sort(sats.begin(), sats.end(), compareBySNR);
// Draw page
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// current position
getdisplay().setFont(&Ubuntu_Bold8pt8b);
// sky view
Point c = {130, 148};
uint16_t r = 120;
uint16_t r1 = r / 2;
getdisplay().fillCircle(c.x, c.y, r + 2, commonData->fgcolor);
getdisplay().fillCircle(c.x, c.y, r - 1, commonData->bgcolor);
getdisplay().drawCircle(c.x, c.y, r1, commonData->fgcolor);
// separation lines
getdisplay().drawLine(c.x - r, c.y, c.x + r, c.y, commonData->fgcolor);
getdisplay().drawLine(c.x, c.y - r, c.x, c.y + r, commonData->fgcolor);
Point p = {c.x, c.y - r};
Point p1, p2;
p1 = rotatePoint(c, p, 45);
p2 = rotatePoint(c, p, 45 + 180);
getdisplay().drawLine(p1.x, p1.y, p2.x, p2.y, commonData->fgcolor);
p1 = rotatePoint(c, p, -45);
p2 = rotatePoint(c, p, -45 + 180);
getdisplay().drawLine(p1.x, p1.y, p2.x, p2.y, commonData->fgcolor);
// directions
int16_t x1, y1;
uint16_t w, h;
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().getTextBounds("N", 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(c.x - w / 2, c.y - r + h + 3);
getdisplay().print("N");
getdisplay().getTextBounds("S", 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(c.x - w / 2, c.y + r - 3);
getdisplay().print("S");
getdisplay().getTextBounds("E", 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(c.x + r - w - 3, c.y + h / 2);
getdisplay().print("E");
getdisplay().getTextBounds("W", 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(c.x - r + 3 , c.y + h / 2);
getdisplay().print("W");
// show satellites in "map"
getdisplay().setFont(&IBM8x8px);
for (int i = 0; i < nSat; i++) {
float arad = (sats[i].Azimut * M_PI / 180.0) + M_PI;
float erad = sats[i].Elevation * M_PI / 180.0;
uint16_t x = c.x + sin(arad) * erad * r1;
uint16_t y = c.y + cos(arad) * erad * r1;
getdisplay().fillRect(x-4, y-4, 8, 8, commonData->fgcolor);
getdisplay().setCursor(x-7, y+12);
getdisplay().printf("%02d", static_cast<int>(sats[i].PRN));
}
// Signal / Noise bars
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(325, 34);
getdisplay().print("SNR");
// getdisplay().drawRect(270, 20, 125, 257, commonData->fgcolor);
int maxsat = std::min(nSat, 12);
for (int i = 0; i < maxsat; i++) {
uint16_t y = 29 + (i + 1) * 20;
getdisplay().setCursor(276, y);
char buffer[3];
snprintf(buffer, 3, "%02d", static_cast<int>(sats[i].PRN));
getdisplay().print(String(buffer));
getdisplay().drawRect(305, y-12, 85, 14, commonData->fgcolor);
getdisplay().setCursor(315, y);
// TODO SNR as number or as bar via mode key?
if (sats[i].SNR <= 100) {
// getdisplay().print(sats[i].SNR);
getdisplay().fillRect(307, y-10, int(81 * sats[i].SNR / 100.0), 10, commonData->fgcolor);
} else {
getdisplay().print("n/a");
}
}
// Show SatInfo and HDOP
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(220, 34);
getdisplay().print("Sat:");
GwApi::BoatValue *bv_satinfo = pageData.values[0]; // SatInfo
String sval_satinfo = formatValue(bv_satinfo, *commonData).svalue;
getdisplay().setCursor(220, 49);
getdisplay().print(sval_satinfo);
getdisplay().setCursor(220, 254);
getdisplay().print("HDOP:");
GwApi::BoatValue *bv_hdop = pageData.values[1]; // HDOP
double hdop = formatValue(bv_hdop, *commonData).value * 4; // 4 is factor for UERE (translation in meter)
char sval_hdop[20];
dtostrf(hdop, 0, 1, sval_hdop); // Only one prefix
strcat(sval_hdop, "m");
getdisplay().setCursor(220, 269);
getdisplay().print(sval_hdop);
return PAGE_UPDATE;
};
};
static Page* createPage(CommonData &common){
return new PageSkyView(common);
}
/**
* with the code below we make this page known to the PageTask
* we give it a type (name) that can be selected in the config
* we define which function is to be called
* and we provide the number of user parameters we expect
* this will be number of BoatValue pointers in pageData.values
*/
PageDescription registerPageSkyView(
"SkyView", // Page name
createPage, // Action
0, // Number of bus values depends on selection in Web configuration
{"SatInfo", "HDOP"}, // Bus values we need in the page
true // Show display header on/off
);
#endif

View File

@@ -1,5 +1,15 @@
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 #if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
/*
* Special system page, called directly with fast key sequence 5,4
* Out of normal page order.
* Consists of some sub-pages with following content:
* 1. Hard and software information
* 2. System settings
* 3. NMEA2000 device list
* 4. SD Card information if available
*/
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "images/logo64.xbm" #include "images/logo64.xbm"
@@ -7,8 +17,7 @@
#include "qrcode.h" #include "qrcode.h"
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
#include <SD.h> #include "dirent.h"
#include <FS.h>
#endif #endif
#define STRINGIZE_IMPL(x) #x #define STRINGIZE_IMPL(x) #x
@@ -19,35 +28,27 @@
#define DISPLAYINFO STRINGIZE(EPDTYPE) #define DISPLAYINFO STRINGIZE(EPDTYPE)
#define GXEPD2INFO STRINGIZE(GXEPD2VERS) #define GXEPD2INFO STRINGIZE(GXEPD2VERS)
/*
* Special system page, called directly with fast key sequence 5,4
* Out of normal page order.
* Consists of some sub-pages with following content:
* 1. Hard and software information
* 2. System settings
* 3. NMEA2000 device list
*/
class PageSystem : public Page class PageSystem : public Page
{ {
uint64_t chipid; private:
bool simulation; uint64_t chipid;
bool sdcard; bool simulation;
String buzzer_mode; bool use_sdcard;
uint8_t buzzer_power; String buzzer_mode;
String cpuspeed; uint8_t buzzer_power;
String rtc_module; String cpuspeed;
String gps_module; String rtc_module;
String env_module; String gps_module;
String env_module;
String batt_sensor; String batt_sensor;
String solar_sensor; String solar_sensor;
String gen_sensor; String gen_sensor;
String rot_sensor; String rot_sensor;
double homelat; double homelat;
double homelon; double homelon;
char mode = 'N'; // (N)ormal, (S)ettings, (D)evice list, (C)ard char mode = 'N'; // (N)ormal, (S)ettings, (D)evice list, (C)ard
public: public:
PageSystem(CommonData &common){ PageSystem(CommonData &common){
@@ -55,11 +56,12 @@ public:
common.logger->logDebug(GwLog::LOG,"Instantiate PageSystem"); common.logger->logDebug(GwLog::LOG,"Instantiate PageSystem");
if (hasFRAM) { if (hasFRAM) {
mode = fram.read(FRAM_SYSTEM_MODE); mode = fram.read(FRAM_SYSTEM_MODE);
common.logger->logDebug(GwLog::DEBUG, "Loaded mode '%c' from FRAM", mode);
} }
chipid = ESP.getEfuseMac(); chipid = ESP.getEfuseMac();
simulation = common.config->getBool(common.config->useSimuData); simulation = common.config->getBool(common.config->useSimuData);
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
sdcard = common.config->getBool(common.config->useSDCard); use_sdcard = common.config->getBool(common.config->useSDCard);
#endif #endif
buzzer_mode = common.config->getString(common.config->buzzerMode); buzzer_mode = common.config->getString(common.config->buzzerMode);
buzzer_mode.toLowerCase(); buzzer_mode.toLowerCase();
@@ -76,7 +78,7 @@ public:
homelon = common.config->getString(common.config->homeLON).toDouble(); homelon = common.config->getString(common.config->homeLON).toDouble();
} }
virtual void setupKeys(){ void setupKeys() {
commonData->keydata[0].label = "EXIT"; commonData->keydata[0].label = "EXIT";
commonData->keydata[1].label = "MODE"; commonData->keydata[1].label = "MODE";
commonData->keydata[2].label = ""; commonData->keydata[2].label = "";
@@ -85,7 +87,7 @@ public:
commonData->keydata[5].label = "ILUM"; commonData->keydata[5].label = "ILUM";
} }
virtual int handleKey(int key){ int handleKey(int key) {
// do *NOT* handle key #1 this handled by obp60task as exit // do *NOT* handle key #1 this handled by obp60task as exit
// Switch display mode // Switch display mode
commonData->logger->logDebug(GwLog::LOG, "System keyboard handler"); commonData->logger->logDebug(GwLog::LOG, "System keyboard handler");
@@ -95,7 +97,7 @@ public:
} else if (mode == 'S') { } else if (mode == 'S') {
mode = 'D'; mode = 'D';
} else if (mode == 'D') { } else if (mode == 'D') {
if (sdcard) { if (hasSDCard) {
mode = 'C'; mode = 'C';
} else { } else {
mode = 'N'; mode = 'N';
@@ -117,7 +119,8 @@ public:
} }
// standby / deep sleep // standby / deep sleep
if (key == 5) { if (key == 5) {
deepSleep(*commonData); commonData->logger->logDebug(GwLog::LOG, "System going into deep sleep mode...");
deepSleep(*commonData);
} }
// Code for keylock // Code for keylock
if (key == 11) { if (key == 11) {
@@ -132,6 +135,7 @@ public:
} }
// standby / deep sleep // standby / deep sleep
if (key == 12) { if (key == 12) {
commonData->logger->logDebug(GwLog::LOG, "System going into deep sleep mode...");
deepSleep(*commonData); deepSleep(*commonData);
} }
#endif #endif
@@ -178,7 +182,7 @@ public:
} }
// Logging boat values // Logging boat values
LOG_DEBUG(GwLog::LOG,"Drawing at PageSystem"); logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
// Draw page // Draw page
//*********************************************************** //***********************************************************
@@ -257,14 +261,37 @@ public:
getdisplay().setCursor(8, y0 + 48); getdisplay().setCursor(8, y0 + 48);
getdisplay().print("SD-Card:"); getdisplay().print("SD-Card:");
getdisplay().setCursor(90, y0 + 48); getdisplay().setCursor(90, y0 + 48);
if (sdcard) { if (hasSDCard) {
uint64_t cardsize = SD.cardSize() / (1024 * 1024); uint64_t cardsize = ((uint64_t) sdcard->csd.capacity) * sdcard->csd.sector_size / (1024 * 1024);
getdisplay().print(String(cardsize) + String(" MB")); getdisplay().printf("%llu MB", cardsize);
} else { } else {
getdisplay().print("off"); getdisplay().print("off");
} }
#endif #endif
// Uptime
int64_t uptime = esp_timer_get_time() / 1000000;
String uptime_unit;
if (uptime < 120) {
uptime_unit = " seconds";
} else {
if (uptime < 2 * 3600) {
uptime /= 60;
uptime_unit = " minutes";
} else if (uptime < 2 * 3600 * 24) {
uptime /= 3600;
uptime_unit = " hours";
} else {
uptime /= 86400;
uptime_unit = " days";
}
}
getdisplay().setCursor(8, y0 + 80);
getdisplay().print("Uptime:");
getdisplay().setCursor(90, y0 + 80);
getdisplay().print(uptime);
getdisplay().print(uptime_unit);
// CPU speed config / active // CPU speed config / active
getdisplay().setCursor(202, y0); getdisplay().setCursor(202, y0);
getdisplay().print("CPU speed:"); getdisplay().print("CPU speed:");
@@ -371,8 +398,61 @@ public:
x0 = 20; x0 = 20;
y0 = 72; y0 = 72;
getdisplay().setCursor(x0, y0); getdisplay().setCursor(x0, y0);
#ifdef BOARD_OBP60S3
// This mode should not be callable by devices without card hardware
// In case of accidential reaching this, display a friendly message
getdisplay().print("This mode is not indended to be reached!\n");
getdisplay().print("There's nothing to see here. Move on.");
#endif
#ifdef BOARD_OBP40S3
getdisplay().print("Work in progress..."); getdisplay().print("Work in progress...");
/* TODO
this code should go somewhere else. only for testing purposes here
identify card as OBP-Card:
magic.dat
version.dat
readme.txt
IMAGES/
CHARTS/
LOGS/
DATA/
hint: file access with fopen, fgets, fread, fclose
*/
// Simple test for magic file in root
getdisplay().setCursor(x0, y0 + 32);
String file_magic = MOUNT_POINT "/magic.dat";
logger->logDebug(GwLog::LOG, "Test magicfile: %s", file_magic.c_str());
struct stat st;
if (stat(file_magic.c_str(), &st) == 0) {
getdisplay().printf("File %s exists", file_magic.c_str());
} else {
getdisplay().printf("File %s not found", file_magic.c_str());
}
// Root directory check
DIR* dir = opendir(MOUNT_POINT);
int dy = 0;
if (dir != NULL) {
logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT);
struct dirent* entry;
while (((entry = readdir(dir)) != NULL) and (dy < 140)) {
getdisplay().setCursor(x0, y0 + 64 + dy);
getdisplay().print(entry->d_name);
// type 1 is file, type 2 is dir
if (entry->d_type == 2) {
getdisplay().print("/");
}
dy += 20;
logger->logDebug(GwLog::DEBUG, " %s type %d", entry->d_name, entry->d_type);
}
closedir(dir);
} else {
logger->logDebug(GwLog::LOG, "Failed to open root directory");
}
#endif
} else { } else {
// NMEA2000 device list // NMEA2000 device list

View File

@@ -247,8 +247,8 @@ public:
if(key == 1){ // Mode switch if(key == 1){ // Mode switch
if(mode == 'N'){ if(mode == 'N'){
mode = 'L'; mode = 'L';
} else if (mode == 'L') { // } else if (mode == 'L') {
mode = 'X'; // mode = 'X';
} else { } else {
mode = 'N'; mode = 'N';
} }

View File

@@ -1,23 +1,52 @@
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 #if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#include "BoatDataCalibration.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "OBPRingBuffer.h" #include "OBPRingBuffer.h"
#include "Pagedata.h" #include "OBPDataOperations.h"
#include "BoatDataCalibration.h"
#include <vector> #include <vector>
static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees
// Get maximum difference of last <amount> of TWD ringbuffer values to center chart; returns "0" if data is not valid
int getCntr(const RingBuffer<int16_t>& windDirHstry, size_t amount)
{
const int MAX_VAL = windDirHstry.getMaxVal();
size_t count = windDirHstry.getCurrentSize();
if (windDirHstry.isEmpty() || amount <= 0) {
return 0;
}
if (amount > count)
amount = count;
uint16_t midWndDir, minWndDir, maxWndDir = 0;
int wndCenter = 0;
midWndDir = windDirHstry.getMid(amount);
if (midWndDir != MAX_VAL) {
midWndDir = midWndDir / 1000.0 * radToDeg;
wndCenter = int((midWndDir + (midWndDir >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value
minWndDir = windDirHstry.getMin(amount) / 1000.0 * radToDeg;
maxWndDir = windDirHstry.getMax(amount) / 1000.0 * radToDeg;
if ((maxWndDir - minWndDir) > 180 && !(minWndDir > maxWndDir)) { // if wind range is > 180 and no 0° crossover, adjust wndCenter to smaller wind range end
wndCenter = WindUtils::to360(wndCenter + 180);
}
}
return wndCenter;
}
// Get maximum difference of last <amount> of TWD ringbuffer values to center chart // Get maximum difference of last <amount> of TWD ringbuffer values to center chart
int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount) int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount)
{ {
int minVal = windDirHstry.getMinVal(); int minVal = windDirHstry.getMinVal();
const int MAX_VAL = windDirHstry.getMaxVal();
size_t count = windDirHstry.getCurrentSize(); size_t count = windDirHstry.getCurrentSize();
// size_t capacity = windDirHstry.getCapacity();
// size_t last = windDirHstry.getLastIdx();
if (windDirHstry.isEmpty() || amount <= 0) { if (windDirHstry.isEmpty() || amount <= 0) {
return minVal; return MAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
@@ -27,11 +56,10 @@ int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount)
int maxRng = minVal; int maxRng = minVal;
// Start from the newest value (last) and go backwards x times // Start from the newest value (last) and go backwards x times
for (size_t i = 0; i < amount; i++) { for (size_t i = 0; i < amount; i++) {
// value = windDirHstry.get(((last - i) % capacity + capacity) % capacity);
value = windDirHstry.get(count - 1 - i); value = windDirHstry.get(count - 1 - i);
if (value == minVal) { if (value == MAX_VAL) {
continue; continue; // ignore invalid values
} }
value = value / 1000.0 * radToDeg; value = value / 1000.0 * radToDeg;
@@ -43,7 +71,7 @@ int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount)
maxRng = 180; maxRng = 180;
} }
return maxRng; return (maxRng != minVal ? maxRng : MAX_VAL);
} }
// **************************************************************** // ****************************************************************
@@ -51,23 +79,39 @@ class PageWindPlot : public Page {
bool keylock = false; // Keylock bool keylock = false; // Keylock
char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both
bool showTruW = true; // Show true wind or apparant wind in chart area
bool oldShowTruW = false; // remember recent user selection of wind data type
int dataIntv = 1; // Update interval for wind history chart: int dataIntv = 1; // Update interval for wind history chart:
// (1)|(2)|(3)|(4) seconds for approx. 4, 8, 12, 16 min. history chart // (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart
bool showTWS = true; // Show TWS value in chart area bool useSimuData;
String flashLED;
String backlightMode;
public: public:
PageWindPlot(CommonData& common) PageWindPlot(CommonData& common)
{ {
commonData = &common; commonData = &common;
common.logger->logDebug(GwLog::LOG, "Instantiate PageWindPlot"); common.logger->logDebug(GwLog::LOG, "Instantiate PageWindPlot");
// Get config data
useSimuData = common.config->getBool(common.config->useSimuData);
// holdValues = common.config->getBool(common.config->holdvalues);
flashLED = common.config->getString(common.config->flashLED);
backlightMode = common.config->getString(common.config->backlight);
} }
virtual void setupKeys() virtual void setupKeys()
{ {
Page::setupKeys(); Page::setupKeys();
// commonData->keydata[0].label = "MODE"; // commonData->keydata[0].label = "MODE";
#if defined BOARD_OBP60S3
commonData->keydata[1].label = "SRC";
commonData->keydata[4].label = "INTV";
#elif defined BOARD_OBP40S3
commonData->keydata[1].label = "INTV"; commonData->keydata[1].label = "INTV";
commonData->keydata[4].label = "TWS"; #endif
} }
// Key functions // Key functions
@@ -85,26 +129,32 @@ public:
return 0; // Commit the key return 0; // Commit the key
} }
// Set interval for wind history chart update time #if defined BOARD_OBP60S3
// Set data source TRUE | APP
if (key == 2) { if (key == 2) {
showTruW = !showTruW;
return 0; // Commit the key
}
// Set interval for wind history chart update time (interval)
if (key == 5) {
#elif defined BOARD_OBP40S3
if (key == 2) {
#endif
if (dataIntv == 1) { if (dataIntv == 1) {
dataIntv = 2; dataIntv = 2;
} else if (dataIntv == 2) { } else if (dataIntv == 2) {
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;
} }
return 0; // Commit the key return 0; // Commit the key
} }
// Switch TWS on/off
if (key == 5) {
showTWS = !showTWS;
return 0; // Commit the key
}
// Keylock function // Keylock function
if (key == 11) { // Code for keylock if (key == 11) { // Code for keylock
commonData->keylock = !commonData->keylock; commonData->keylock = !commonData->keylock;
@@ -113,39 +163,50 @@ public:
return key; return key;
} }
virtual void displayNew(PageData &pageData){
#ifdef BOARD_OBP40S3
String wndSrc; // Wind source true/apparant wind - preselection for OBP40
wndSrc = commonData->config->getString("page" + String(pageData.pageNumber) + "wndsrc");
if (wndSrc =="True wind") {
showTruW = true;
} else {
showTruW = false; // Wind source is apparant wind
}
commonData->logger->logDebug(GwLog::LOG,"New PageWindPlot: wind source=%s", wndSrc);
#endif
oldShowTruW = !showTruW; // makes wind source being initialized at initial page call
}
int displayPage(PageData& pageData) int displayPage(PageData& pageData)
{ {
GwConfigHandler* config = commonData->config; GwConfigHandler* config = commonData->config;
GwLog* logger = commonData->logger; GwLog* logger = commonData->logger;
float twsValue; // TWS value in chart area static RingBuffer<int16_t>* wdHstry; // Wind direction data buffer
static String twdName, twdUnit; // TWD name and unit static RingBuffer<uint16_t>* wsHstry; // Wind speed data buffer
static int updFreq; // Update frequency for TWD static String wdName, wdFormat; // Wind direction name and format
static int16_t twdLowest, twdHighest; // TWD range static String wsName, wsFormat; // Wind speed name and format
// static int16_t twdBufMinVal; // lowest possible twd buffer value; used for non-set data static int16_t wdMAX_VAL; // Max. value of wd history buffer, indicating invalid values
float wsValue; // Wind speed value in chart area
String wsUnit; // Wind speed unit in chart area
static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater
// current boat data values; TWD only for validation test, TWS for display of current value // current boat data values; TWD/AWD only for validation test
const int numBoatData = 2; const int numBoatData = 2;
GwApi::BoatValue* bvalue; GwApi::BoatValue* bvalue;
String BDataName[numBoatData];
double BDataValue[numBoatData];
bool BDataValid[numBoatData]; bool BDataValid[numBoatData];
String BDataText[numBoatData];
String BDataUnit[numBoatData];
String BDataFormat[numBoatData];
static bool isInitialized = false; // Flag to indicate that page is initialized static bool isInitialized = false; // Flag to indicate that page is initialized
static bool wndDataValid = false; // Flag to indicate if wind data is valid static bool wndDataValid = false; // Flag to indicate if wind data is valid
static int numNoData; // Counter for multiple invalid data values in a row 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 width; // Screen width
static int height; // Screen height static int height; // Screen height
static int xCenter; // Center of screen in x direction static int xCenter; // Center of screen in x direction
static const int yOffset = 48; // Offset for y coordinates of chart area static const int yOffset = 48; // Offset for y coordinates of chart area
static int cHeight; // height 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 bufSize; // History buffer size: 1.920 values for 32 min. history chart
static int intvBufSize; // Buffer size used for currently selected time interval static int intvBufSize; // Buffer size used for currently selected time interval
int count; // current size of buffer int count; // current size of buffer
static int numWndVals; // number of wind values available for current interval selection static int numWndVals; // number of wind values available for current interval selection
@@ -161,10 +222,8 @@ public:
static int wndRight; // chart wind right 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 static int chrtRng; // Range of wind values from mid wind value to min/max wind value in degrees
int diffRng; // Difference between mid and current wind value int diffRng; // Difference between mid and current wind value
static const int dfltRng = 40; // Default range for chart static const int dfltRng = 60; // Default range for chart
int midWndDir; // New value for wndCenter after chart start / shift 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 int x, y; // x and y coordinates for drawing
static int prevX, prevY; // Last x and y coordinates for drawing static int prevX, prevY; // Last x and y coordinates for drawing
@@ -172,30 +231,20 @@ public:
int chrtVal; // Current wind value int chrtVal; // Current wind value
static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line
LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); LOG_DEBUG(GwLog::LOG, "Display PageWindPlot");
ulong timer = millis();
// 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) { if (!isInitialized) {
width = getdisplay().width(); width = getdisplay().width();
height = getdisplay().height(); height = getdisplay().height();
xCenter = width / 2; xCenter = width / 2;
cHeight = height - yOffset - 22; cHeight = height - yOffset - 22;
bufSize = pageData.boatHstry.twdHstry->getCapacity();
numNoData = 0; numNoData = 0;
simTwd = pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg;
simTws = 0;
twsValue = 0;
bufStart = 0; bufStart = 0;
oldDataIntv = 0; oldDataIntv = 0;
wsValue = 0;
numAddedBufVals, currIdx, lastIdx = 0; numAddedBufVals, currIdx, lastIdx = 0;
lastAddedIdx = pageData.boatHstry.twdHstry->getLastIdx(); wndCenter = INT_MAX;
pageData.boatHstry.twdHstry->getMetaData(twdName, twdUnit, updFreq, twdLowest, twdHighest);
wndCenter = INT_MIN;
midWndDir = 0; midWndDir = 0;
diffRng = dfltRng; diffRng = dfltRng;
chrtRng = dfltRng; chrtRng = dfltRng;
@@ -206,14 +255,7 @@ public:
// read boat data values; TWD only for validation test, TWS for display of current value // read boat data values; TWD only for validation test, TWS for display of current value
for (int i = 0; i < numBoatData; i++) { for (int i = 0; i < numBoatData; i++) {
bvalue = pageData.values[i]; bvalue = pageData.values[i];
BDataName[i] = xdrDelete(bvalue->getName());
BDataName[i] = BDataName[i].substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue, logger); // Check if boat data value is to be calibrated
BDataValue[i] = bvalue->value; // Value as double in SI unit
BDataValid[i] = bvalue->valid; BDataValid[i] = bvalue->valid;
BDataText[i] = 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) // Optical warning by limit violation (unused)
@@ -222,12 +264,30 @@ public:
setFlashLED(false); setFlashLED(false);
} }
if (showTruW != oldShowTruW) {
if (showTruW) {
wdHstry = pageData.boatHstry->hstryBufList.twdHstry;
wsHstry = pageData.boatHstry->hstryBufList.twsHstry;
} else {
wdHstry = pageData.boatHstry->hstryBufList.awdHstry;
wsHstry = pageData.boatHstry->hstryBufList.awsHstry;
}
wdHstry->getMetaData(wdName, wdFormat);
wsHstry->getMetaData(wsName, wsFormat);
wdMAX_VAL = wdHstry->getMaxVal();
bufSize = wdHstry->getCapacity();
wsBVal->setFormat(wsHstry->getFormat());
lastAddedIdx = wdHstry->getLastIdx();
oldShowTruW = showTruW;
}
// Identify buffer size and buffer start position for chart // Identify buffer size and buffer start position for chart
count = pageData.boatHstry.twdHstry->getCurrentSize(); count = wdHstry->getCurrentSize();
currIdx = pageData.boatHstry.twdHstry->getLastIdx(); currIdx = wdHstry->getLastIdx();
numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display
if (dataIntv != oldDataIntv || count == 1) { if (dataIntv != oldDataIntv || count == 1) {
// new data interval selected by user // new data interval selected by user; this is only x * 230 values instead of 240 seconds (4 minutes) per interval step
intvBufSize = cHeight * dataIntv; intvBufSize = cHeight * dataIntv;
numWndVals = min(count, (cHeight - 60) * dataIntv); numWndVals = min(count, (cHeight - 60) * dataIntv);
bufStart = max(0, count - numWndVals); bufStart = max(0, count - numWndVals);
@@ -240,29 +300,26 @@ public:
bufStart = max(0, bufStart - numAddedBufVals); 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", // LOG_DEBUG(GwLog::DEBUG,"PSRAM Size: %d kByte; free: %d Byte", ESP.getPsramSize()/1024, ESP.getFreePsram());
count, pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg, pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384, BDataValid[0], LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Dataset: count: %d, xWD: %.1f, xWS: %.2f, xWD_valid? %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d, wind source: %s",
intvBufSize, numWndVals, bufStart, numAddedBufVals, pageData.boatHstry.twdHstry->getLastIdx(), oldDataIntv, dataIntv); count, wdHstry->getLast() / 1000.0 * radToDeg, wsHstry->getLast() / 1000.0 * 1.94384, BDataValid[0], intvBufSize, numWndVals, bufStart, numAddedBufVals, wdHstry->getLastIdx(),
showTruW ? "True" : "App");
// Set wndCenter from 1st real buffer value // Set wndCenter from 1st real buffer value
if (wndCenter == INT_MIN || (wndCenter == 0 && count == 1)) { if (wndCenter == INT_MAX || (wndCenter == 0 && count == 1)) {
midWndDir = pageData.boatHstry.twdHstry->getMid(numWndVals); wndCenter = getCntr(*wdHstry, numWndVals);
if (midWndDir != INT16_MIN) { LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Range Init: count: %d, xWD: %.1f, wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", count, wdHstry->getLast() / 1000.0 * radToDeg,
midWndDir = midWndDir / 1000.0 * radToDeg; wndCenter, diffRng, chrtRng, wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 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 { } else {
// check and adjust range between left, center, and right chart limit // check and adjust range between left, center, and right chart limit
diffRng = getRng(*pageData.boatHstry.twdHstry, wndCenter, numWndVals); diffRng = getRng(*wdHstry, wndCenter, numWndVals);
diffRng = (diffRng == INT16_MIN ? 0 : diffRng); diffRng = (diffRng == wdMAX_VAL ? 0 : diffRng);
if (diffRng > chrtRng) { if (diffRng > chrtRng) {
chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value
} else if (diffRng + 10 < chrtRng) { // Reduce chart range for higher resolution if possible } else if (diffRng + 10 < chrtRng) { // Reduce chart range for higher resolution if possible
chrtRng = max(dfltRng, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); chrtRng = max(dfltRng, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10);
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Range adjust: wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", wndCenter, diffRng, chrtRng,
wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 1000.0 * radToDeg);
} }
} }
chrtScl = float(width) / float(chrtRng) / 2.0; // Chart scale: pixels per degree chrtScl = float(width) / float(chrtRng) / 2.0; // Chart scale: pixels per degree
@@ -288,7 +345,7 @@ public:
char sWndLbl[4]; // char buffer for Wind angle label char sWndLbl[4]; // char buffer for Wind angle label
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(xCenter - 88, yOffset - 3); getdisplay().setCursor(xCenter - 88, yOffset - 3);
getdisplay().print("TWD"); // Wind data name getdisplay().print(wdName); // Wind data name
snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter);
drawTextCenter(xCenter, yOffset - 11, sWndLbl); drawTextCenter(xCenter, yOffset - 11, sWndLbl);
getdisplay().drawCircle(xCenter + 25, yOffset - 17, 2, commonData->fgcolor); // <degree> symbol getdisplay().drawCircle(xCenter + 25, yOffset - 17, 2, commonData->fgcolor); // <degree> symbol
@@ -304,11 +361,11 @@ public:
getdisplay().drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // <degree> symbol getdisplay().drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // <degree> symbol
getdisplay().drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // <degree> symbol getdisplay().drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // <degree> symbol
if (pageData.boatHstry.twdHstry->getMax() == pageData.boatHstry.twdHstry->getMinVal()) { if (wdHstry->getMax() == wdMAX_VAL) {
// only <INT16_MIN> values in buffer -> no valid wind data available // only <MAX_VAL> values in buffer -> no valid wind data available
wndDataValid = false; wndDataValid = false;
} else if (!BDataValid[0]) { } else if (!BDataValid[0] && !useSimuData) {
// currently no valid TWD data available // currently no valid xWD data available and no simulation mode
numNoData++; numNoData++;
wndDataValid = true; wndDataValid = true;
if (numNoData > 3) { if (numNoData > 3) {
@@ -323,19 +380,18 @@ public:
//*********************************************************************** //***********************************************************************
if (wndDataValid) { if (wndDataValid) {
for (int i = 0; i < (numWndVals / dataIntv); i++) { for (int i = 0; i < (numWndVals / dataIntv); i++) {
chrtVal = static_cast<int>(pageData.boatHstry.twdHstry->get(bufStart + (i * dataIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer chrtVal = static_cast<int>(wdHstry->get(bufStart + (i * dataIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer
if (chrtVal == INT16_MIN) { if (chrtVal == wdMAX_VAL) {
chrtPrevVal = INT16_MIN; chrtPrevVal = wdMAX_VAL;
} else { } else {
chrtVal = static_cast<int>((chrtVal / 1000.0 * radToDeg) + 0.5); // Convert to degrees and round chrtVal = static_cast<int>((chrtVal / 1000.0 * radToDeg) + 0.5); // Convert to degrees and round
x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; x = ((chrtVal - wndLeft + 360) % 360) * chrtScl;
y = yOffset + cHeight - i; // Position in chart area y = yOffset + cHeight - i; // Position in chart area
// if (i >= (numWndVals / dataIntv) - 10) if (i >= (numWndVals / dataIntv) - 1) // log chart data of 1 line (adjust for test purposes)
if (i >= (numWndVals / dataIntv) - 1)
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d, count: %d, linesToShow: %d", i, chrtVal, bufStart, count, (numWndVals / dataIntv)); LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d, count: %d, linesToShow: %d", i, chrtVal, bufStart, count, (numWndVals / dataIntv));
if ((i == 0) || (chrtPrevVal == INT16_MIN)) { if ((i == 0) || (chrtPrevVal == wdMAX_VAL)) {
// just a dot for 1st chart point or after some invalid values // just a dot for 1st chart point or after some invalid values
prevX = x; prevX = x;
prevY = y; prevY = y;
@@ -364,41 +420,27 @@ public:
if (i >= (cHeight - 1)) { if (i >= (cHeight - 1)) {
oldDataIntv = 0; // force reset of buffer start and number of values to show in next display loop 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 minWndDir = wdHstry->getMin(numWndVals) / 1000.0 * radToDeg;
int maxWndDir = pageData.boatHstry.twdHstry->getMax(numWndVals) / 1000.0 * radToDeg; int maxWndDir = wdHstry->getMax(numWndVals) / 1000.0 * radToDeg;
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: Minimum: %d, Maximum: %d, OldwndCenter: %d", minWndDir, maxWndDir, wndCenter); LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: Minimum: %d, Maximum: %d, OldwndCenter: %d", minWndDir, maxWndDir, wndCenter);
// if ((minWndDir + 540 >= wndCenter + 540) || (maxWndDir + 540 <= wndCenter + 540)) { // if (((minWndDir - wndCenter >= 0) && (minWndDir - wndCenter < 180)) || ((maxWndDir - wndCenter <= 0) && (maxWndDir - wndCenter >=180))) {
if (((minWndDir - wndCenter >= 0) && (minWndDir - wndCenter < 180)) || ((maxWndDir - wndCenter <= 0) && (maxWndDir - wndCenter >=180))) { if ((wndRight > wndCenter && (minWndDir >= wndCenter && minWndDir <= wndRight)) || (wndRight <= wndCenter && (minWndDir >= wndCenter || minWndDir <= wndRight)) || (wndLeft < wndCenter && (maxWndDir <= wndCenter && maxWndDir >= wndLeft)) || (wndLeft >= wndCenter && (maxWndDir <= wndCenter || maxWndDir >= wndLeft))) {
// Check if all wind value are left or right of center value -> optimize chart range // Check if all wind value are left or right of center value -> optimize chart center
midWndDir = pageData.boatHstry.twdHstry->getMid(numWndVals) / 1000.0 * radToDeg; wndCenter = getCntr(*wdHstry, numWndVals);
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); LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: cHeight: %d, bufStart: %d, numWndVals: %d, wndCenter: %d", cHeight, bufStart, numWndVals, wndCenter);
break; break;
} }
} }
} else { // Print wind speed value
// 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; int currentZone;
static int lastZone = 0; static int lastZone = 0;
static bool flipTws = false; static bool flipTws = false;
int xPosTws; int xPosTws;
static const int yPosTws = yOffset + 40; static const int yPosTws = yOffset + 40;
twsValue = pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384; // TWS value in knots xPosTws = flipTws ? 20 : width - 145;
xPosTws = flipTws ? 20 : width - 138;
currentZone = (y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146) ? 1 : 0; // Define current zone for TWS value currentZone = (y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146) ? 1 : 0; // Define current zone for TWS value
if (currentZone != lastZone) { if (currentZone != lastZone) {
// Only flip when x moves to a different zone // Only flip when x moves to a different zone
@@ -409,28 +451,38 @@ public:
} }
lastZone = currentZone; lastZone = currentZone;
wsValue = wsHstry->getLast();
wsBVal->value = wsValue / 1000.0; // temp variable to retreive data unit from OBP60Formater
wsBVal->valid = (static_cast<uint16_t>(wsValue) != wsHstry->getMinVal());
String swsValue = formatValue(wsBVal, *commonData).svalue; // value (string)
wsUnit = formatValue(wsBVal, *commonData).unit; // Unit of value
getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
getdisplay().setCursor(xPosTws, yPosTws); getdisplay().setCursor(xPosTws, yPosTws);
if (!BDataValid[1]) { getdisplay().print(swsValue); // Value
/* if (!wsBVal->valid) {
getdisplay().print("--.-"); getdisplay().print("--.-");
} else { } else {
double dbl = BDataValue[1] * 3.6 / 1.852; wsValue = wsValue / 1000.0 * 1.94384; // Wind speed value in knots
if (dbl < 10.0) { if (wsValue < 10.0) {
getdisplay().printf("!%3.1f", dbl); // Value, round to 1 decimal getdisplay().printf("!%3.1f", wsValue); // Value, round to 1 decimal
} else { } else {
getdisplay().printf("%4.1f", dbl); // Value, round to 1 decimal getdisplay().printf("%4.1f", wsValue); // Value, round to 1 decimal
} }
} } */
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(xPosTws + 82, yPosTws - 14); getdisplay().setCursor(xPosTws + 82, yPosTws - 14);
// getdisplay().print("TWS"); // Name getdisplay().print(wsName); // Name
getdisplay().print(BDataName[1]); // Name
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
// getdisplay().setCursor(xPosTws + 78, yPosTws + 1);
getdisplay().setCursor(xPosTws + 82, yPosTws + 1); getdisplay().setCursor(xPosTws + 82, yPosTws + 1);
// getdisplay().printf(" kn"); // Unit getdisplay().print(wsUnit); // Unit
getdisplay().print(BDataUnit[1]); // Unit
} 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");
} }
// chart Y axis labels; print at last to overwrite potential chart lines in label area // chart Y axis labels; print at last to overwrite potential chart lines in label area
@@ -452,6 +504,7 @@ public:
getdisplay().printf("%3d", chrtLbl); // Wind value label getdisplay().printf("%3d", chrtLbl); // Wind value label
} }
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot time: %ld", millis() - timer);
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
}; };
@@ -460,19 +513,17 @@ static Page* createPage(CommonData& common)
{ {
return new PageWindPlot(common); return new PageWindPlot(common);
} }
/**
* with the code below we make this page known to the PageTask /* 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 give it a type (name) that can be selected in the config
* we define which function is to be called * we define which function is to be called
* and we provide the number of user parameters we expect (0 here) * and we provide the number of user parameters we expect (0 here)
* and will will provide the names of the fixed values we need * and will will provide the names of the fixed values we need */
*/
PageDescription registerPageWindPlot( PageDescription registerPageWindPlot(
"WindPlot", // Page name "WindPlot", // Page name
createPage, // Action createPage, // Action
0, // Number of bus values depends on selection in Web configuration 0, // Number of bus values depends on selection in Web configuration
{ "TWD", "TWS" }, // Bus values we need in the page { "TWD", "AWD" }, // Bus values we need in the page
// {}, // Bus values we need in the page
true // Show display header on/off true // Show display header on/off
); );

View File

@@ -7,15 +7,33 @@
class PageWindRoseFlex : public Page class PageWindRoseFlex : public Page
{ {
int16_t lp = 80; // Pointer length int16_t lp = 80; // Pointer length
char source = 'A'; // data source (A)pparent | (T)rue
String ssource="App."; // String for Data Source
public: public:
PageWindRoseFlex(CommonData &common){ PageWindRoseFlex(CommonData &common){
commonData = &common; commonData = &common;
common.logger->logDebug(GwLog::LOG,"Instantiate PageWindRoseFlex"); common.logger->logDebug(GwLog::LOG,"Instantiate PageWindRoseFlex");
} }
virtual void setupKeys(){
Page::setupKeys();
commonData->keydata[1].label = "SRC";
}
// Key functions // Key functions
virtual int handleKey(int key){ virtual int handleKey(int key){
if(key == 2){
// Code for set source
if(source == 'A'){
source = 'T';
ssource = "True"; // String to display
} else {
source = 'A';
ssource = "App."; // String to display
}
}
return key; // Commit the key
// Code for keylock // Code for keylock
if(key == 11){ if(key == 11){
commonData->keylock = !commonData->keylock; commonData->keylock = !commonData->keylock;
@@ -40,6 +58,11 @@ public:
static String unit5old = ""; static String unit5old = "";
static String svalue6old = ""; static String svalue6old = "";
static String unit6old = ""; static String unit6old = "";
static GFXfont name3font;
static GFXfont name4font;
static GFXfont name5font;
static GFXfont name6font;
// Get config data // Get config data
String lengthformat = config->getString(config->lengthFormat); String lengthformat = config->getString(config->lengthFormat);
@@ -48,42 +71,63 @@ public:
String flashLED = config->getString(config->flashLED); String flashLED = config->getString(config->flashLED);
String backlightMode = config->getString(config->backlight); String backlightMode = config->getString(config->backlight);
// Get boat values #1 GwApi::BoatValue *bvalue1; // Value 1 for angle
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) GwApi::BoatValue *bvalue2; // Value 2 for speed
String name1 = xdrDelete(bvalue1->getName()); // Value name
// Get boat value for wind angle (AWA/TWA), shown by pointer
if (source == 'A') {
bvalue1 = pageData.values[4];
} else {
bvalue1 = pageData.values[6];
}
String name1 = bvalue1->getName().c_str(); // Value name
name1 = name1.substring(0, 6); // String length limit for value name name1 = name1.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
double value1 = bvalue1->value; // Value as double in SI unit double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information bool valid1 = bvalue1->valid; // Valid information
value1 = formatValue(bvalue1, *commonData).value;// Format only nesaccery for simulation data for pointer
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
if(valid1 == true){ if(valid1 == true){
svalue1old = svalue1; // Save old value svalue1old = svalue1; // Save old value
unit1old = unit1; // Save old unit unit1old = unit1; // Save old unit
} }
// Get boat values #2 // Get boat value for wind speed (AWS/TWS), shown in top left corner
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list if (source == 'A') {
String name2 = xdrDelete(bvalue2->getName()); // Value name bvalue2 =pageData.values[5];
} else {
bvalue2 = pageData.values[7];
}
String name2 = bvalue2->getName().c_str(); // Value name
name2 = name2.substring(0, 6); // String length limit for value name name2 = name2.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
double value2 = bvalue2->value; // Value as double in SI unit double value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information bool valid2 = bvalue2->valid; // Valid information
if (simulation) {
value2 = 0.62731; // some random value
}
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
if(valid2 == true){ if(valid2 == true){
svalue2old = svalue2; // Save old value svalue2old = svalue2; // Save old value
unit2old = unit2; // Save old unit unit2old = unit2; // Save old unit
} }
// Get boat values #3
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
// Get boat value for bottom left corner
GwApi::BoatValue *bvalue3 = pageData.values[0];
String name3 = xdrDelete(bvalue3->getName()); // Value name String name3 = xdrDelete(bvalue3->getName()); // Value name
name3 = name3.substring(0, 6); // String length limit for value name name3 = name3.substring(0, 6); // String length limit for value name
if (name3.length()>3){
name3font=Ubuntu_Bold8pt8b;
}
else{
name3font=Ubuntu_Bold12pt8b;
}
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
double value3 = bvalue3->value; // Value as double in SI unit double value3 = bvalue3->value; // Value as double in SI unit
bool valid3 = bvalue3->valid; // Valid information bool valid3 = bvalue3->valid; // Valid information
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value
if(valid3 == true){ if(valid3 == true){
@@ -91,13 +135,19 @@ public:
unit3old = unit3; // Save old unit unit3old = unit3; // Save old unit
} }
// Get boat values #4 // Get boat value for top right corner
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Fourth element in list GwApi::BoatValue *bvalue4 = pageData.values[1];
String name4 = xdrDelete(bvalue4->getName()); // Value name String name4 = xdrDelete(bvalue4->getName()); // Value name
name4 = name4.substring(0, 6); // String length limit for value name name4 = name4.substring(0, 6); // String length limit for value name
if (name4.length()>3){
name4font=Ubuntu_Bold8pt8b;
}
else{
name4font=Ubuntu_Bold12pt8b;
}
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
double value4 = bvalue4->value; // Value as double in SI unit double value4 = bvalue4->value; // Value as double in SI unit
bool valid4 = bvalue4->valid; // Valid information bool valid4 = bvalue4->valid; // Valid information
String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
String unit4 = formatValue(bvalue4, *commonData).unit; // Unit of value String unit4 = formatValue(bvalue4, *commonData).unit; // Unit of value
if(valid4 == true){ if(valid4 == true){
@@ -105,13 +155,19 @@ public:
unit4old = unit4; // Save old unit unit4old = unit4; // Save old unit
} }
// Get boat values #5 // Get boat value bottom right corner
GwApi::BoatValue *bvalue5 = pageData.values[4]; // Fifth element in list GwApi::BoatValue *bvalue5 = pageData.values[2];
String name5 = xdrDelete(bvalue5->getName()); // Value name String name5 = xdrDelete(bvalue5->getName()); // Value name
name5 = name5.substring(0, 6); // String length limit for value name name5 = name5.substring(0, 6); // String length limit for value name
if (name5.length()>3){
name5font=Ubuntu_Bold8pt8b;
}
else{
name5font=Ubuntu_Bold12pt8b;
}
calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
double value5 = bvalue5->value; // Value as double in SI unit double value5 = bvalue5->value; // Value as double in SI unit
bool valid5 = bvalue5->valid; // Valid information bool valid5 = bvalue5->valid; // Valid information
String svalue5 = formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue5 = formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
String unit5 = formatValue(bvalue5, *commonData).unit; // Unit of value String unit5 = formatValue(bvalue5, *commonData).unit; // Unit of value
if(valid5 == true){ if(valid5 == true){
@@ -119,13 +175,19 @@ public:
unit5old = unit5; // Save old unit unit5old = unit5; // Save old unit
} }
// Get boat values #5 // Get boat value for center (name is not displayed)
GwApi::BoatValue *bvalue6 = pageData.values[5]; // Sixth element in list GwApi::BoatValue *bvalue6 = pageData.values[3];
String name6 = xdrDelete(bvalue6->getName()); // Value name String name6 = xdrDelete(bvalue6->getName()); // Value name
name6 = name6.substring(0, 6); // String length limit for value name name6 = name6.substring(0, 6); // String length limit for value name
if (name6.length()>3){
name6font=Ubuntu_Bold8pt8b;
}
else{
name6font=Ubuntu_Bold8pt8b;
}
calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
double value6 = bvalue6->value; // Value as double in SI unit double value6 = bvalue6->value; // Value as double in SI unit
bool valid6 = bvalue6->valid; // Valid information bool valid6 = bvalue6->valid; // Valid information
String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
String unit6 = formatValue(bvalue6, *commonData).unit; // Unit of value String unit6 = formatValue(bvalue6, *commonData).unit; // Unit of value
if(valid6 == true){ if(valid6 == true){
@@ -133,6 +195,7 @@ public:
unit6old = unit6; // Save old unit unit6old = unit6; // Save old unit
} }
// Optical warning by limit violation (unused) // Optical warning by limit violation (unused)
if(String(flashLED) == "Limit Violation"){ if(String(flashLED) == "Limit Violation"){
setBlinkingLED(false); setBlinkingLED(false);
@@ -151,7 +214,7 @@ public:
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
// Show value 2 at position of value 1 (top left) // Show AWS or TWS top left
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
getdisplay().setCursor(10, 65); getdisplay().setCursor(10, 65);
getdisplay().print(svalue2); // Value getdisplay().print(svalue2); // Value
@@ -171,11 +234,11 @@ public:
// Horizintal separator left // Horizintal separator left
getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor); getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor);
// Show value 3 at bottom left // Show value 3 (=first user-configured parameter) at bottom left
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
getdisplay().setCursor(10, 270); getdisplay().setCursor(10, 270);
getdisplay().print(svalue3); // Value getdisplay().print(svalue3); // Value
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&name3font);
getdisplay().setCursor(10, 220); getdisplay().setCursor(10, 220);
getdisplay().print(name3); // Name getdisplay().print(name3); // Name
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
@@ -188,21 +251,15 @@ public:
getdisplay().print(unit3old); // Unit getdisplay().print(unit3old); // Unit
} }
// Show value 4 at top right // Show value 4 (=second user-configured parameter) at top right
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
getdisplay().setCursor(295, 65); getdisplay().setCursor(295, 65);
if(valid3 == true){ getdisplay().print(svalue4); // Value
// getdisplay().print(abs(value3 * 180 / M_PI), 0); // Value getdisplay().setFont(&name4font);
getdisplay().print(svalue4); // Value getdisplay().setCursor(325, 95);
}
else{
getdisplay().print("---"); // Value
}
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(335, 95);
getdisplay().print(name4); // Name getdisplay().print(name4); // Name
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(335, 115); getdisplay().setCursor(325, 115);
getdisplay().print(" "); getdisplay().print(" ");
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(unit4); // Unit getdisplay().print(unit4); // Unit
@@ -214,15 +271,15 @@ public:
// Horizintal separator right // Horizintal separator right
getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor); getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor);
// Show value 5 at bottom right // Show value 5 (=third user-configured parameter) at bottom right
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
getdisplay().setCursor(295, 270); getdisplay().setCursor(295, 270);
getdisplay().print(svalue5); // Value getdisplay().print(svalue5); // Value
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&name5font);
getdisplay().setCursor(335, 220); getdisplay().setCursor(325, 220);
getdisplay().print(name5); // Name getdisplay().print(name5); // Name
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(335, 190); getdisplay().setCursor(325, 190);
getdisplay().print(" "); getdisplay().print(" ");
if(holdvalues == false){ if(holdvalues == false){
getdisplay().print(unit5); // Unit getdisplay().print(unit5); // Unit
@@ -329,26 +386,51 @@ public:
//******************************************************************************************* //*******************************************************************************************
// Show value6, so that it does not collide with the wind pointer // Show value6 (=fourth user-configured parameter) and ssource, so that they do not collide with the wind pointer
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); if ( cos(value1) > 0){
if (cos(value1) > 0){ //pointer points upwards
getdisplay().setCursor(160, 200); getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
getdisplay().print(svalue6); // Value getdisplay().setCursor(160, 200);
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().print(svalue6); // Value
getdisplay().setCursor(190, 215); getdisplay().setFont(&Ubuntu_Bold8pt8b);
} else{ getdisplay().setCursor(190, 215);
getdisplay().setCursor(160, 130); getdisplay().print(" ");
getdisplay().print(svalue6); // Value if(holdvalues == false){
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().print(unit6); // Unit
getdisplay().setCursor(190, 90); }
} else{
getdisplay().print(" "); getdisplay().print(unit6old); // Unit
if(holdvalues == false){ }
getdisplay().print(unit6); // Unit if (sin(value1)>0){
} getdisplay().setCursor(160, 130);
else{ }
getdisplay().print(unit6old); // Unit else{
} getdisplay().setCursor(220, 130);
}
getdisplay().print(ssource); // true or app.
}
else{
// pointer points downwards
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
getdisplay().setCursor(160, 130);
getdisplay().print(svalue6); // Value
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(190, 90);
getdisplay().print(" ");
if(holdvalues == false){
getdisplay().print(unit6); // Unit
}
else{
getdisplay().print(unit6old); // Unit
}
if (sin(value1)>0){
getdisplay().setCursor(160, 200);
}
else{
getdisplay().setCursor(220, 200);
}
getdisplay().print(ssource); //true or app.
}
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
@@ -361,13 +443,14 @@ static Page *createPage(CommonData &common){
* with the code below we make this page known to the PageTask * 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 give it a type (name) that can be selected in the config
* we define which function is to be called * we define which function is to be called
* and we provide the number of user parameters we expect (0 here) * and we provide the number of user parameters we expect (4 here)
* and will will provide the names of the fixed values we need * and will will provide the names of the fixed values we need
*/ */
PageDescription registerPageWindRoseFlex( PageDescription registerPageWindRoseFlex(
"WindRoseFlex", // Page name "WindRoseFlex", // Page name
createPage, // Action createPage, // Action
6, // Number of bus values depends on selection in Web configuration; was zero 4, // Number of bus values depends on selection in Web configuration
{"AWA", "AWS", "TWA", "TWS"}, // fixed values we need in the page. They are inserted AFTER the web-configured values.
true // Show display header on/off true // Show display header on/off
); );

View File

@@ -4,7 +4,6 @@
#include <functional> #include <functional>
#include <vector> #include <vector>
#include "LedSpiTask.h" #include "LedSpiTask.h"
#include "OBPRingBuffer.h"
#include "OBPDataOperations.h" #include "OBPDataOperations.h"
#define MAX_PAGE_NUMBER 10 // Max number of pages for show data #define MAX_PAGE_NUMBER 10 // Max number of pages for show data
@@ -12,11 +11,12 @@
typedef std::vector<GwApi::BoatValue *> ValueList; typedef std::vector<GwApi::BoatValue *> ValueList;
typedef struct{ typedef struct{
GwApi *api;
String pageName; String pageName;
uint8_t pageNumber; // page number in sequence of visible pages uint8_t pageNumber; // page number in sequence of visible pages
//the values will always contain the user defined values first //the values will always contain the user defined values first
ValueList values; ValueList values;
tBoatHstryData boatHstry; HstryBuf* boatHstry;
} PageData; } PageData;
// Sensor data structure (only for extended sensors, not for NMEA bus sensors) // Sensor data structure (only for extended sensors, not for NMEA bus sensors)
@@ -99,20 +99,20 @@ typedef struct{
} AlarmData; } AlarmData;
typedef struct{ typedef struct{
GwApi::Status status; GwApi::Status status;
GwLog *logger=NULL; GwLog *logger = nullptr;
GwConfigHandler *config=NULL; GwConfigHandler *config = nullptr;
SensorData data; SensorData data;
SunData sundata; SunData sundata;
TouchKeyData keydata[6]; TouchKeyData keydata[6];
BacklightData backlight; BacklightData backlight;
AlarmData alarm; AlarmData alarm;
GwApi::BoatValue *time=NULL; GwApi::BoatValue *time = nullptr;
GwApi::BoatValue *date=NULL; GwApi::BoatValue *date = nullptr;
uint16_t fgcolor; uint16_t fgcolor;
uint16_t bgcolor; uint16_t bgcolor;
bool keylock = false; bool keylock = false;
String powermode; String powermode;
} CommonData; } CommonData;
//a base class that all pages must inherit from //a base class that all pages must inherit from
@@ -123,6 +123,7 @@ class Page{
int refreshtime = 1000; int refreshtime = 1000;
virtual int displayPage(PageData &pageData)=0; virtual int displayPage(PageData &pageData)=0;
virtual void displayNew(PageData &pageData){} virtual void displayNew(PageData &pageData){}
virtual void leavePage(PageData &pageData){}
virtual void setupKeys() { virtual void setupKeys() {
#ifdef HARDWARE_V21 #ifdef HARDWARE_V21
commonData->keydata[0].label = ""; commonData->keydata[0].label = "";
@@ -181,9 +182,9 @@ class PageDescription{
class PageStruct{ class PageStruct{
public: public:
Page *page=NULL; Page *page = nullptr;
PageData parameters; PageData parameters;
PageDescription *description=NULL; PageDescription *description = nullptr;
}; };
// Standard format functions without overhead // Standard format functions without overhead

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,202 @@
const uint8_t IBM8x8pxBitmaps[] PROGMEM = {
0x00, /* 0x20 space */
0x6F, 0xF6, 0x60, 0x60, /* 0x21 exclam */
0xDE, 0xF6, /* 0x22 quotedbl */
0x6C, 0xDB, 0xFB, 0x6F, 0xED, 0x9B, 0x00, /* 0x23 numbersign */
0x31, 0xFC, 0x1E, 0x0F, 0xE3, 0x00, /* 0x24 dollar */
0xC7, 0x98, 0x61, 0x86, 0x78, 0xC0, /* 0x25 percent */
0x38, 0xD8, 0xE3, 0xBD, 0xD9, 0x9D, 0x80, /* 0x26 ampersand */
0x6F, 0x00, /* 0x27 quotesingle */
0x36, 0xCC, 0xC6, 0x30, /* 0x28 parenleft */
0xC6, 0x33, 0x36, 0xC0, /* 0x29 parenright */
0x66, 0x3C, 0xFF, 0x3C, 0x66, /* 0x2A asterisk */
0x30, 0xCF, 0xCC, 0x30, /* 0x2B plus */
0x6F, 0x00, /* 0x2C comma */
0xFC, /* 0x2D hyphen */
0xF0, /* 0x2E period */
0x06, 0x18, 0x61, 0x86, 0x18, 0x20, 0x00, /* 0x2F slash */
0x7D, 0x8F, 0x3E, 0xFF, 0x7C, 0xDF, 0x00, /* 0x30 zero */
0x31, 0xC3, 0x0C, 0x30, 0xCF, 0xC0, /* 0x31 one */
0x7B, 0x30, 0xCE, 0x63, 0x1F, 0xC0, /* 0x32 two */
0x7B, 0x30, 0xCE, 0x0F, 0x37, 0x80, /* 0x33 three */
0x1C, 0x79, 0xB6, 0x6F, 0xE1, 0x87, 0x80, /* 0x34 four */
0xFF, 0x0F, 0x83, 0x0F, 0x37, 0x80, /* 0x35 five */
0x39, 0x8C, 0x3E, 0xCF, 0x37, 0x80, /* 0x36 six */
0xFF, 0x30, 0xC6, 0x30, 0xC3, 0x00, /* 0x37 seven */
0x7B, 0x3C, 0xDE, 0xCF, 0x37, 0x80, /* 0x38 eight */
0x7B, 0x3C, 0xDF, 0x0C, 0x67, 0x00, /* 0x39 nine */
0xF0, 0xF0, /* 0x3A colon */
0x6C, 0x37, 0x80, /* 0x3B semicolon */
0x19, 0x99, 0x86, 0x18, 0x60, /* 0x3C less */
0xFC, 0x00, 0x3F, /* 0x3D equal */
0xC3, 0x0C, 0x33, 0x33, 0x00, /* 0x3E greater */
0x7B, 0x30, 0xC6, 0x30, 0x03, 0x00, /* 0x3F question */
0x7D, 0x8F, 0x7E, 0xFD, 0xF8, 0x1E, 0x00, /* 0x40 at */
0x31, 0xEC, 0xF3, 0xFF, 0x3C, 0xC0, /* 0x41 A */
0xFC, 0xCD, 0x9B, 0xE6, 0x6C, 0xFF, 0x00, /* 0x42 B */
0x3C, 0xCF, 0x06, 0x0C, 0x0C, 0xCF, 0x00, /* 0x43 C */
0xF8, 0xD9, 0x9B, 0x36, 0x6D, 0xBE, 0x00, /* 0x44 D */
0xFE, 0xC5, 0xA3, 0xC6, 0x8C, 0x7F, 0x80, /* 0x45 E */
0xFE, 0xC5, 0xA3, 0xC6, 0x8C, 0x3C, 0x00, /* 0x46 F */
0x3C, 0xCF, 0x06, 0x0C, 0xEC, 0xCF, 0x80, /* 0x47 G */
0xCF, 0x3C, 0xFF, 0xCF, 0x3C, 0xC0, /* 0x48 H */
0xF6, 0x66, 0x66, 0xF0, /* 0x49 I */
0x1E, 0x18, 0x30, 0x6C, 0xD9, 0x9E, 0x00, /* 0x4A J */
0xE6, 0xCD, 0xB3, 0xC6, 0xCC, 0xF9, 0x80, /* 0x4B K */
0xF0, 0xC1, 0x83, 0x06, 0x2C, 0xFF, 0x80, /* 0x4C L */
0xC7, 0xDF, 0xFF, 0xFD, 0x78, 0xF1, 0x80, /* 0x4D M */
0xC7, 0xCF, 0xDE, 0xFC, 0xF8, 0xF1, 0x80, /* 0x4E N */
0x38, 0xDB, 0x1E, 0x3C, 0x6D, 0x8E, 0x00, /* 0x4F O */
0xFC, 0xCD, 0x9B, 0xE6, 0x0C, 0x3C, 0x00, /* 0x50 P */
0x7B, 0x3C, 0xF3, 0xDD, 0xE1, 0xC0, /* 0x51 Q */
0xFC, 0xCD, 0x9B, 0xE6, 0xCC, 0xF9, 0x80, /* 0x52 R */
0x7B, 0x3E, 0x1C, 0x1F, 0x37, 0x80, /* 0x53 S */
0xFE, 0xD3, 0x0C, 0x30, 0xC7, 0x80, /* 0x54 T */
0xCF, 0x3C, 0xF3, 0xCF, 0x3F, 0xC0, /* 0x55 U */
0xCF, 0x3C, 0xF3, 0xCD, 0xE3, 0x00, /* 0x56 V */
0xC7, 0x8F, 0x1E, 0xBF, 0xFD, 0xF1, 0x80, /* 0x57 W */
0xC7, 0x8D, 0xB1, 0xC3, 0x8D, 0xB1, 0x80, /* 0x58 X */
0xCF, 0x3C, 0xDE, 0x30, 0xC7, 0x80, /* 0x59 Y */
0xFF, 0x8E, 0x30, 0xC3, 0x2C, 0xFF, 0x80, /* 0x5A Z */
0xFC, 0xCC, 0xCC, 0xF0, /* 0x5B bracketleft */
0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x80, /* 0x5C backslash */
0xF3, 0x33, 0x33, 0xF0, /* 0x5D bracketright */
0x10, 0x71, 0xB6, 0x30, /* 0x5E asciicircum */
0xFF, /* 0x5F underscore */
0xD9, 0x80, /* 0x60 grave */
0x78, 0x19, 0xF6, 0x67, 0x60, /* 0x61 a */
0xE0, 0xC1, 0x83, 0xE6, 0x6C, 0xF7, 0x00, /* 0x62 b */
0x7B, 0x3C, 0x33, 0x78, /* 0x63 c */
0x1C, 0x18, 0x33, 0xEC, 0xD9, 0x9D, 0x80, /* 0x64 d */
0x7B, 0x3F, 0xF0, 0x78, /* 0x65 e */
0x39, 0xB6, 0x3C, 0x61, 0x8F, 0x00, /* 0x66 f */
0x77, 0x9B, 0x33, 0xE0, 0xDF, 0x00, /* 0x67 g */
0xE0, 0xC1, 0xB3, 0xB6, 0x6C, 0xF9, 0x80, /* 0x68 h */
0x60, 0xE6, 0x66, 0xF0, /* 0x69 i */
0x0C, 0x00, 0xC3, 0x0F, 0x3C, 0xDE, /* 0x6A j */
0xE0, 0xC1, 0x9B, 0x67, 0x8D, 0xB9, 0x80, /* 0x6B k */
0xE6, 0x66, 0x66, 0xF0, /* 0x6C l */
0xCD, 0xFF, 0xFE, 0xBC, 0x60, /* 0x6D m */
0xFB, 0x3C, 0xF3, 0xCC, /* 0x6E n */
0x7B, 0x3C, 0xF3, 0x78, /* 0x6F o */
0xDC, 0xCD, 0x9B, 0xE6, 0x1E, 0x00, /* 0x70 p */
0x77, 0x9B, 0x33, 0xE0, 0xC3, 0xC0, /* 0x71 q */
0xDC, 0xED, 0x9B, 0x0F, 0x00, /* 0x72 r */
0x7F, 0x07, 0x83, 0xF8, /* 0x73 s */
0x23, 0x3E, 0xC6, 0x34, 0xC0, /* 0x74 t */
0xCD, 0x9B, 0x36, 0x67, 0x60, /* 0x75 u */
0xCF, 0x3C, 0xDE, 0x30, /* 0x76 v */
0xC7, 0xAF, 0xFF, 0xF6, 0xC0, /* 0x77 w */
0xC6, 0xD8, 0xE3, 0x6C, 0x60, /* 0x78 x */
0xCF, 0x3C, 0xDF, 0x0F, 0xE0, /* 0x79 y */
0xFE, 0x63, 0x19, 0xFC, /* 0x7A z */
0x1C, 0xC3, 0x38, 0x30, 0xC1, 0xC0, /* 0x7B braceleft */
0xFC, 0xFC, /* 0x7C bar */
0xE0, 0xC3, 0x07, 0x30, 0xCE, 0x00, /* 0x7D braceright */
0x77, 0xB8, /* 0x7E asciitilde */
0x10, 0x71, 0xB6, 0x3C, 0x7F, 0xC0 /* 0x7F uni007F */
};
const GFXglyph IBM8x8pxGlyphs[] PROGMEM = {
{ 0, 1, 1, 2, 0, -1 }, /* 0x20 space */
{ 1, 4, 7, 5, 0, -7 }, /* 0x21 exclam */
{ 5, 5, 3, 6, 0, -7 }, /* 0x22 quotedbl */
{ 7, 7, 7, 8, 0, -7 }, /* 0x23 numbersign */
{ 14, 6, 7, 7, 0, -7 }, /* 0x24 dollar */
{ 20, 7, 6, 8, 0, -6 }, /* 0x25 percent */
{ 26, 7, 7, 8, 0, -7 }, /* 0x26 ampersand */
{ 33, 3, 3, 4, 0, -7 }, /* 0x27 quotesingle */
{ 35, 4, 7, 5, 0, -7 }, /* 0x28 parenleft */
{ 39, 4, 7, 5, 0, -7 }, /* 0x29 parenright */
{ 43, 8, 5, 9, 0, -6 }, /* 0x2A asterisk */
{ 48, 6, 5, 7, 0, -6 }, /* 0x2B plus */
{ 52, 3, 3, 4, 0, -2 }, /* 0x2C comma */
{ 54, 6, 1, 7, 0, -4 }, /* 0x2D hyphen */
{ 55, 2, 2, 3, 0, -2 }, /* 0x2E period */
{ 56, 7, 7, 8, 0, -7 }, /* 0x2F slash */
{ 63, 7, 7, 8, 0, -7 }, /* 0x30 zero */
{ 70, 6, 7, 7, 0, -7 }, /* 0x31 one */
{ 76, 6, 7, 7, 0, -7 }, /* 0x32 two */
{ 82, 6, 7, 7, 0, -7 }, /* 0x33 three */
{ 88, 7, 7, 8, 0, -7 }, /* 0x34 four */
{ 95, 6, 7, 7, 0, -7 }, /* 0x35 five */
{ 101, 6, 7, 7, 0, -7 }, /* 0x36 six */
{ 107, 6, 7, 7, 0, -7 }, /* 0x37 seven */
{ 113, 6, 7, 7, 0, -7 }, /* 0x38 eight */
{ 119, 6, 7, 7, 0, -7 }, /* 0x39 nine */
{ 125, 2, 6, 3, 0, -6 }, /* 0x3A colon */
{ 127, 3, 6, 4, 0, -6 }, /* 0x3B semicolon */
{ 130, 5, 7, 6, 0, -7 }, /* 0x3C less */
{ 135, 6, 4, 7, 0, -5 }, /* 0x3D equal */
{ 138, 5, 7, 6, 0, -7 }, /* 0x3E greater */
{ 143, 6, 7, 7, 0, -7 }, /* 0x3F question */
{ 149, 7, 7, 8, 0, -7 }, /* 0x40 at */
{ 156, 6, 7, 7, 0, -7 }, /* 0x41 A */
{ 162, 7, 7, 8, 0, -7 }, /* 0x42 B */
{ 169, 7, 7, 8, 0, -7 }, /* 0x43 C */
{ 176, 7, 7, 8, 0, -7 }, /* 0x44 D */
{ 183, 7, 7, 8, 0, -7 }, /* 0x45 E */
{ 190, 7, 7, 8, 0, -7 }, /* 0x46 F */
{ 197, 7, 7, 8, 0, -7 }, /* 0x47 G */
{ 204, 6, 7, 7, 0, -7 }, /* 0x48 H */
{ 210, 4, 7, 5, 0, -7 }, /* 0x49 I */
{ 214, 7, 7, 8, 0, -7 }, /* 0x4A J */
{ 221, 7, 7, 8, 0, -7 }, /* 0x4B K */
{ 228, 7, 7, 8, 0, -7 }, /* 0x4C L */
{ 235, 7, 7, 8, 0, -7 }, /* 0x4D M */
{ 242, 7, 7, 8, 0, -7 }, /* 0x4E N */
{ 249, 7, 7, 8, 0, -7 }, /* 0x4F O */
{ 256, 7, 7, 8, 0, -7 }, /* 0x50 P */
{ 263, 6, 7, 7, 0, -7 }, /* 0x51 Q */
{ 269, 7, 7, 8, 0, -7 }, /* 0x52 R */
{ 276, 6, 7, 7, 0, -7 }, /* 0x53 S */
{ 282, 6, 7, 7, 0, -7 }, /* 0x54 T */
{ 288, 6, 7, 7, 0, -7 }, /* 0x55 U */
{ 294, 6, 7, 7, 0, -7 }, /* 0x56 V */
{ 300, 7, 7, 8, 0, -7 }, /* 0x57 W */
{ 307, 7, 7, 8, 0, -7 }, /* 0x58 X */
{ 314, 6, 7, 7, 0, -7 }, /* 0x59 Y */
{ 320, 7, 7, 8, 0, -7 }, /* 0x5A Z */
{ 327, 4, 7, 5, 0, -7 }, /* 0x5B bracketleft */
{ 331, 7, 7, 8, 0, -7 }, /* 0x5C backslash */
{ 338, 4, 7, 5, 0, -7 }, /* 0x5D bracketright */
{ 342, 7, 4, 8, 0, -7 }, /* 0x5E asciicircum */
{ 346, 8, 1, 9, 0, 0 }, /* 0x5F underscore */
{ 347, 3, 3, 4, 0, -7 }, /* 0x60 grave */
{ 349, 7, 5, 8, 0, -5 }, /* 0x61 a */
{ 354, 7, 7, 8, 0, -7 }, /* 0x62 b */
{ 361, 6, 5, 7, 0, -5 }, /* 0x63 c */
{ 365, 7, 7, 8, 0, -7 }, /* 0x64 d */
{ 372, 6, 5, 7, 0, -5 }, /* 0x65 e */
{ 376, 6, 7, 7, 0, -7 }, /* 0x66 f */
{ 382, 7, 6, 8, 0, -5 }, /* 0x67 g */
{ 388, 7, 7, 8, 0, -7 }, /* 0x68 h */
{ 395, 4, 7, 5, 0, -7 }, /* 0x69 i */
{ 399, 6, 8, 7, 0, -7 }, /* 0x6A j */
{ 405, 7, 7, 8, 0, -7 }, /* 0x6B k */
{ 412, 4, 7, 5, 0, -7 }, /* 0x6C l */
{ 416, 7, 5, 8, 0, -5 }, /* 0x6D m */
{ 421, 6, 5, 7, 0, -5 }, /* 0x6E n */
{ 425, 6, 5, 7, 0, -5 }, /* 0x6F o */
{ 429, 7, 6, 8, 0, -5 }, /* 0x70 p */
{ 435, 7, 6, 8, 0, -5 }, /* 0x71 q */
{ 441, 7, 5, 8, 0, -5 }, /* 0x72 r */
{ 446, 6, 5, 7, 0, -5 }, /* 0x73 s */
{ 450, 5, 7, 6, 0, -7 }, /* 0x74 t */
{ 455, 7, 5, 8, 0, -5 }, /* 0x75 u */
{ 460, 6, 5, 7, 0, -5 }, /* 0x76 v */
{ 464, 7, 5, 8, 0, -5 }, /* 0x77 w */
{ 469, 7, 5, 8, 0, -5 }, /* 0x78 x */
{ 474, 6, 6, 7, 0, -5 }, /* 0x79 y */
{ 479, 6, 5, 7, 0, -5 }, /* 0x7A z */
{ 483, 6, 7, 7, 0, -7 }, /* 0x7B braceleft */
{ 489, 2, 7, 3, 0, -7 }, /* 0x7C bar */
{ 491, 6, 7, 7, 0, -7 }, /* 0x7D braceright */
{ 497, 7, 2, 8, 0, -7 }, /* 0x7E asciitilde */
{ 499, 7, 6, 8, 0, -6 } /* 0x7F uni007F */
};
const GFXfont IBM8x8px PROGMEM = {
(uint8_t *)IBM8x8pxBitmaps,
(GFXglyph *)IBM8x8pxGlyphs,
0x20, 0x7F, 8 };

View File

@@ -1,132 +1,206 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# A tool to generate that part of config.json that deals with pages and fields.
#
#Usage: 1. modify this script (e.g.add a page, change number of fields, etc.)
# 2. Delete all lines from config.json from the curly backet before "name": "page1type" to o the end of the file (as of today, delete from line 917 to the end of the File)
# 3. run ./gen_set.py >> config.json
"""
A tool to generate that part of config.json that deals with pages and fields.
Usage example:
1. Delete all lines from config.json from the curly backet before
"name": "page1type" to the end of the file
2. run ./gen_set.py -d obp60 -p 10 >> config.json
TODO Better handling of default pages
"""
import os
import sys
import getopt
import re
import json import json
# List of all pages and the number of parameters they expect. __version__ = "0.3"
no_of_fields_per_page = {
"Wind": 0,
"XTETrack": 0,
"Battery2": 0,
"Battery": 0,
"BME280": 0,
"Clock": 0,
"Compass" : 0,
"DST810": 0,
"Fluid": 1,
"FourValues2": 4,
"FourValues": 4,
"Generator": 0,
"KeelPosition": 0,
"OneValue": 1,
"RollPitch": 2,
"RudderPosition": 0,
"SixValues" : 6,
"Solar": 0,
"ThreeValues": 3,
"TwoValues": 2,
"Voltage": 0,
"WhitePage": 0,
"WindPlot": 0,
"WindRose": 0,
"WindRoseFlex": 6,
}
# No changes needed beyond this point def detect_pages(filename):
# max number of pages supported by OBP60 # returns a dictionary with page name and the number of gui fields
no_of_pages = 10 pagefiles = []
# Default selection for each page with open(filename, 'r') as fh:
default_pages = [ pattern = r'extern PageDescription\s*register(Page[^;\s]*)'
"Voltage", for line in fh:
"WindRose", if "extern PageDescription" in line:
"OneValue", match = re.search(pattern, line)
"TwoValues", if match:
"ThreeValues", pagefiles.append(match.group(1))
"FourValues", try:
"FourValues2", pagefiles.remove('PageSystem')
"Clock", except ValueError:
"RollPitch", pass
"Battery2", pagedata = {}
] for pf in pagefiles:
numbers = [ filename = pf + ".cpp"
"one", with open(filename, 'r') as fh:
"two", content = fh.read()
"three", pattern = r'PageDescription\s*?register' + pf + r'\s*\(\s*"([^"]+)".*?\n\s*(\d+)'
"four", match = re.search(pattern, content, re.DOTALL)
"five", if match:
"six", pagedata[match.group(1)] = int(match.group(2))
"seven", return pagedata
"eight",
"nine",
"ten",
]
pages = sorted(no_of_fields_per_page.keys())
max_no_of_fields_per_page = max(no_of_fields_per_page.values())
output = [] def get_default_page(pageno):
# Default selection for each page
default_pages = (
"Voltage",
"WindRose",
"OneValue",
"TwoValues",
"ThreeValues",
"FourValues",
"FourValues2",
"Clock",
"RollPitch",
"Battery2"
)
if pageno > len(default_pages):
return "OneValue"
return default_pages[pageno - 1]
for page_no in range(1, no_of_pages + 1): def number_to_text(number):
page_data = { if number < 0 or number > 99:
"name": f"page{page_no}type", raise ValueError("Only numbers from 0 to 99 are allowed.")
"label": "Type", numbers = ("zero", "one", "two", "three", "four", "five", "six", "seven",
"type": "list", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
"default": default_pages[page_no - 1], "fifteen", "sixteen", "seventeen", "eighteen", "nineteen")
"description": f"Type of page for page {page_no}", tens = ("", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy",
"list": pages, "eighty", "ninety")
"category": f"OBP60 Page {page_no}", if number < 20:
"capabilities": {"obp60": "true"}, return numbers[number]
"condition": [{"visiblePages": vp} for vp in range(page_no, no_of_pages + 1)], else:
#"fields": [], q, r = divmod(number, 10)
} return tens[q] + numbers[r]
output.append(page_data)
for field_no in range(1, max_no_of_fields_per_page + 1): def create_json(device, no_of_pages, pagedata):
field_data = {
"name": f"page{page_no}value{field_no}", pages = sorted(pagedata.keys())
"label": f"Field {field_no}", max_no_of_fields_per_page = max(pagedata.values())
"type": "boatData",
"default": "", output = []
"description": f"The display for field {numbers[field_no - 1]}",
"category": f"OBP60 Page {page_no}", for page_no in range(1, no_of_pages + 1):
"capabilities": {"obp60": "true"},
"condition": [ category = f"{device.upper()} Page {page_no}"
{f"page{page_no}type": page} capabilities = {device.lower(): "true"}
for page in pages visiblepages = [str(vp) for vp in range(page_no, no_of_pages + 1)]
if no_of_fields_per_page[page] >= field_no
], page_data = {
"name": f"page{page_no}type",
"label": "Type",
"type": "list",
"default": get_default_page(page_no),
"description": f"Type of page for page {page_no}",
"list": pages,
"category": category,
"capabilities": {device.lower(): "true"},
"condition": {
"visiblePages": visiblepages
},
#"fields": [],
} }
output.append(field_data) output.append(page_data)
fluid_data ={ for field_no in range(1, max_no_of_fields_per_page + 1):
"name": f"page{page_no}fluid", field_data = {
"label": "Fluid type", "name": f"page{page_no}value{field_no}",
"type": "list", "label": f"Field {field_no}",
"default": "0", "type": "boatData",
"list": [ "default": "",
{"l":"Fuel (0)","v":"0"}, "description": "The display for field {}".format(number_to_text(field_no)),
{"l":"Water (1)","v":"1"}, "category": category,
{"l":"Gray Water (2)","v":"2"}, "capabilities": capabilities,
{"l":"Live Well (3)","v":"3"}, "condition": {
{"l":"Oil (4)","v":"4"}, f"page{page_no}type": [ p for p in pages if pagedata[p] >= field_no ]
{"l":"Black Water (5)","v":"5"}, ,"visiblePages": visiblepages
{"l":"Fuel Gasoline (6)","v":"6"} }
], }
"description": "Fluid type in tank", output.append(field_data)
"category": f"OBP60 Page {page_no}",
"capabilities": {
"obp60":"true"
},
"condition":[{f"page{page_no}type":"Fluid"}]
}
output.append(fluid_data)
json_output = json.dumps(output, indent=4) fluid_data = {
# print omitting first and last line containing [ ] of JSON array "name": f"page{page_no}fluid",
#print(json_output[1:-1]) "label": "Fluid type",
# print omitting first line containing [ of JSON array "type": "list",
print(json_output[1:]) "default": "0",
# print(",") "list": [
{"l":"Fuel (0)","v":"0"},
{"l":"Water (1)","v":"1"},
{"l":"Gray Water (2)","v":"2"},
{"l":"Live Well (3)","v":"3"},
{"l":"Oil (4)","v":"4"},
{"l":"Black Water (5)","v":"5"},
{"l":"Fuel Gasoline (6)","v":"6"}
],
"description": "Fluid type in tank",
"category": category,
"capabilities": capabilities,
"condition": {
f"page{page_no}type": "Fluid",
"visiblePages": visiblepages
}
}
output.append(fluid_data)
if device.upper() == 'OBP40':
windsource = {
"name": f"page{page_no}wndsrc",
"label": "Wind source",
"type": "list",
"default": "True wind",
"description": f"Wind source for page {page_no}: [true|apparent]",
"list": [
"True wind",
"Apparant wind"
],
"category": category,
"capabilities": capabilities,
"condition": {
f"page{page_no}type": "WindPlot",
"visiblePages": visiblepages
}
}
output.append(windsource)
return json.dumps(output, indent=4)
def usage():
print("{} v{}".format(os.path.basename(__file__), __version__))
print()
print("Command line options")
print(" -d --device device name to use e.g. obp60")
print(" -p --pages number of pages to create")
print(" -h show this help")
print()
if __name__ == '__main__':
try:
options, remainder = getopt.getopt(sys.argv[1:], 'd:p:', ['device=','--pages='])
except getopt.GetoptError as err:
print(err)
usage()
sys.exit(2)
device = "obp60"
no_of_pages = 10
for opt, arg in options:
if opt in ('-d', '--device'):
device = arg
elif opt in ('-p', '--pages'):
no_of_pages = int(arg)
elif opt == '-h':
usage()
sys.exit(0)
# automatic detect pages and number of fields from sourcecode
pagedata = detect_pages("obp60task.cpp")
json_output = create_json(device, no_of_pages, pagedata)
# print omitting first line containing [ of JSON array
print(json_output[1:])

14
lib/obp60task/index.js Normal file
View File

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

View File

@@ -13,19 +13,13 @@
#include "OBP60Extensions.h" // Functions lib for extension board #include "OBP60Extensions.h" // Functions lib for extension board
#include "OBP60Keypad.h" // Functions for keypad #include "OBP60Keypad.h" // Functions for keypad
#include "BoatDataCalibration.h" // Functions lib for data instance calibration #include "BoatDataCalibration.h" // Functions lib for data instance calibration
#include "OBPRingBuffer.h" // Functions lib with ring buffer for history storage of some boat data
#include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation #include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
#include "driver/rtc_io.h" // Needs for weakup from deep sleep #include "driver/rtc_io.h" // Needs for weakup from deep sleep
#include <FS.h> // SD-Card access
#include <SD.h>
#include <SPI.h> #include <SPI.h>
#endif #endif
// True type character sets includes
// See OBP60ExtensionPort.cpp
// Pictures // Pictures
//#include GxEPD_BitmapExamples // Example picture //#include GxEPD_BitmapExamples // Example picture
#include "MFD_OBP60_400x300_sw.h" // MFD with logo #include "MFD_OBP60_400x300_sw.h" // MFD with logo
@@ -34,7 +28,6 @@
#include "OBP60QRWiFi.h" // Functions lib for WiFi QR code #include "OBP60QRWiFi.h" // Functions lib for WiFi QR code
#include "OBPSensorTask.h" // Functions lib for sensor data #include "OBPSensorTask.h" // Functions lib for sensor data
// Global vars // Global vars
bool initComplete = false; // Initialization complete bool initComplete = false; // Initialization complete
int taskRunCounter = 0; // Task couter for loop section int taskRunCounter = 0; // Task couter for loop section
@@ -47,63 +40,23 @@ void OBP60Init(GwApi *api){
GwConfigHandler *config = api->getConfig(); GwConfigHandler *config = api->getConfig();
// Set a new device name and hidden the original name in the main config // Set a new device name and hidden the original name in the main config
String devicename = api->getConfig()->getConfigItem(api->getConfig()->deviceName,true)->asString(); String devicename = config->getConfigItem(config->deviceName, true)->asString();
api->getConfig()->setValue(GwConfigDefinitions::systemName, devicename, GwConfigInterface::ConfigType::HIDDEN); config->setValue(GwConfigDefinitions::systemName, devicename, GwConfigInterface::ConfigType::HIDDEN);
logger->prefix = devicename + ":";
logger->logDebug(GwLog::LOG,"obp60init running");
api->getLogger()->logDebug(GwLog::LOG,"obp60init running");
// Check I2C devices // Check I2C devices
// Init power
String powermode = config->getConfigItem(config->powerMode,true)->asString();
logger->logDebug(GwLog::DEBUG, "Power Mode is: %s", powermode.c_str());
powerInit(powermode);
// Init hardware // Init hardware
hardwareInit(api); hardwareInit(api);
// Init power rail 5.0V #ifdef BOARD_OBP40S3
String powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString();
api->getLogger()->logDebug(GwLog::DEBUG,"Power Mode is: %s", powermode.c_str());
if(powermode == "Max Power" || powermode == "Only 5.0V"){
#ifdef HARDWARE_V21
setPortPin(OBP_POWER_50, true); // Power on 5.0V rail
#endif
#ifdef BOARD_OBP40S3
setPortPin(OBP_POWER_EPD, true);// Power on ePaper display
setPortPin(OBP_POWER_SD, true); // Power on SD card
#endif
}
else{
#ifdef HARDWARE_V21
setPortPin(OBP_POWER_50, false); // Power off 5.0V rail
#endif
#ifdef BOARD_OBP40S3
setPortPin(OBP_POWER_EPD, false);// Power off ePaper display
setPortPin(OBP_POWER_SD, false); // Power off SD card
#endif
}
#ifdef BOARD_OBP40S3
bool sdcard = config->getBool(config->useSDCard);
if (sdcard) {
SPIClass SD_SPI = SPIClass(HSPI);
SD_SPI.begin(SD_SPI_CLK, SD_SPI_MISO, SD_SPI_MOSI);
if (SD.begin(SD_SPI_CS, SD_SPI, 80000000)) {
String sdtype = "unknown";
uint8_t cardType = SD.cardType();
switch (cardType) {
case CARD_MMC:
sdtype = "MMC";
break;
case CARD_SD:
sdtype = "SDSC";
break;
case CARD_SDHC:
sdtype = "SDHC";
break;
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
LOG_DEBUG(GwLog::LOG,"SD card type %s of size %d MB detected", sdtype, cardSize);
}
}
// Deep sleep wakeup configuration // Deep sleep wakeup configuration
esp_sleep_enable_ext0_wakeup(OBP_WAKEWUP_PIN, 0); // 1 = High, 0 = Low esp_sleep_enable_ext0_wakeup(OBP_WAKEWUP_PIN, 0); // 1 = High, 0 = Low
rtc_gpio_pullup_en(OBP_WAKEWUP_PIN); // Activate pullup resistor rtc_gpio_pullup_en(OBP_WAKEWUP_PIN); // Activate pullup resistor
@@ -112,7 +65,7 @@ void OBP60Init(GwApi *api){
// Settings for e-paper display // Settings for e-paper display
String fastrefresh = api->getConfig()->getConfigItem(api->getConfig()->fastRefresh,true)->asString(); String fastrefresh = api->getConfig()->getConfigItem(api->getConfig()->fastRefresh,true)->asString();
api->getLogger()->logDebug(GwLog::DEBUG,"Fast Refresh Mode is: %s", fastrefresh.c_str()); logger->logDebug(GwLog::DEBUG, "Fast Refresh Mode is: %s", fastrefresh.c_str());
#ifdef DISPLAY_GDEY042T81 #ifdef DISPLAY_GDEY042T81
if(fastrefresh == "true"){ if(fastrefresh == "true"){
static const bool useFastFullUpdate = true; // Enable fast full display update only for GDEY042T81 static const bool useFastFullUpdate = true; // Enable fast full display update only for GDEY042T81
@@ -131,11 +84,11 @@ void OBP60Init(GwApi *api){
// Get CPU speed // Get CPU speed
int freq = getCpuFrequencyMhz(); int freq = getCpuFrequencyMhz();
api->getLogger()->logDebug(GwLog::LOG,"CPU speed at boot: %i MHz", freq); logger->logDebug(GwLog::LOG,"CPU speed at boot: %i MHz", freq);
// Settings for backlight // Settings for backlight
String backlightMode = api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString(); String backlightMode = api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString();
api->getLogger()->logDebug(GwLog::DEBUG,"Backlight Mode is: %s", backlightMode.c_str()); logger->logDebug(GwLog::DEBUG,"Backlight Mode is: %s", backlightMode.c_str());
uint brightness = uint(api->getConfig()->getConfigItem(api->getConfig()->blBrightness,true)->asInt()); uint brightness = uint(api->getConfig()->getConfigItem(api->getConfig()->blBrightness,true)->asInt());
String backlightColor = api->getConfig()->getConfigItem(api->getConfig()->blColor,true)->asString(); String backlightColor = api->getConfig()->getConfigItem(api->getConfig()->blColor,true)->asString();
if(String(backlightMode) == "On"){ if(String(backlightMode) == "On"){
@@ -150,7 +103,7 @@ void OBP60Init(GwApi *api){
// Settings flash LED mode // Settings flash LED mode
String ledMode = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString(); String ledMode = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString();
api->getLogger()->logDebug(GwLog::DEBUG,"LED Mode is: %s", ledMode.c_str()); logger->logDebug(GwLog::DEBUG,"LED Mode is: %s", ledMode.c_str());
if(String(ledMode) == "Off"){ if(String(ledMode) == "Off"){
setBlinkingLED(false); setBlinkingLED(false);
} }
@@ -168,8 +121,8 @@ void OBP60Init(GwApi *api){
typedef struct { typedef struct {
int page0=0; int page0=0;
QueueHandle_t queue; QueueHandle_t queue;
GwLog* logger = NULL; GwLog* logger = nullptr;
// GwApi* api = NULL; // GwApi* api = nullptr;
uint sensitivity = 100; uint sensitivity = 100;
bool use_syspage = true; bool use_syspage = true;
} MyData; } MyData;
@@ -194,45 +147,37 @@ void keyboardTask(void *param){
vTaskDelete(NULL); vTaskDelete(NULL);
} }
class BoatValueList{ // Scorgan: moved class declaration to header file <obp60task.h> to make class available to other functions
public: // --- Class BoatValueList --------------
static const int MAXVALUES=100; bool BoatValueList::addValueToList(GwApi::BoatValue *v){
//we create a list containing all our BoatValues for (int i=0;i<numValues;i++){
//this is the list we later use to let the api fill all the values if (allBoatValues[i] == v){
//additionally we put the necessary values into the paga data - see below //already in list...
GwApi::BoatValue *allBoatValues[MAXVALUES]; return true;
int numValues=0;
bool addValueToList(GwApi::BoatValue *v){
for (int i=0;i<numValues;i++){
if (allBoatValues[i] == v){
//already in list...
return true;
}
} }
if (numValues >= MAXVALUES) return false;
allBoatValues[numValues]=v;
numValues++;
return true;
} }
//helper to ensure that each BoatValue is only queried once if (numValues >= MAXVALUES) return false;
GwApi::BoatValue *findValueOrCreate(String name){ allBoatValues[numValues]=v;
for (int i=0;i<numValues;i++){ numValues++;
if (allBoatValues[i]->getName() == name) { return true;
return allBoatValues[i]; }
} //helper to ensure that each BoatValue is only queried once
GwApi::BoatValue *BoatValueList::findValueOrCreate(String name){
for (int i=0;i<numValues;i++){
if (allBoatValues[i]->getName() == name) {
return allBoatValues[i];
} }
GwApi::BoatValue *rt=new GwApi::BoatValue(name);
addValueToList(rt);
return rt;
} }
}; GwApi::BoatValue *rt=new GwApi::BoatValue(name);
addValueToList(rt);
return rt;
}
// --- Class BoatValueList --------------
//we want to have a list that has all our page definitions //we want to have a list that has all our page definitions
//this way each page can easily be added here //this way each page can easily be added here
//needs some minor tricks for the safe static initialization //needs some minor tricks for the safe static initialization
typedef std::vector<PageDescription*> Pages; typedef std::vector<PageDescription*> Pages;
//the page list class
class PageList{ class PageList{
public: public:
Pages pages; Pages pages;
@@ -282,7 +227,7 @@ void registerAllPages(PageList &list){
extern PageDescription registerPageWindRose; extern PageDescription registerPageWindRose;
list.add(&registerPageWindRose); list.add(&registerPageWindRose);
extern PageDescription registerPageWindRoseFlex; extern PageDescription registerPageWindRoseFlex;
list.add(&registerPageWindRoseFlex); // list.add(&registerPageWindRoseFlex);
extern PageDescription registerPageVoltage; extern PageDescription registerPageVoltage;
list.add(&registerPageVoltage); list.add(&registerPageVoltage);
extern PageDescription registerPageDST810; extern PageDescription registerPageDST810;
@@ -313,185 +258,67 @@ void registerAllPages(PageList &list){
list.add(&registerPageXTETrack); list.add(&registerPageXTETrack);
extern PageDescription registerPageFluid; extern PageDescription registerPageFluid;
list.add(&registerPageFluid); list.add(&registerPageFluid);
extern PageDescription registerPageSkyView;
list.add(&registerPageSkyView);
} }
// Undervoltage detection for shutdown display // Undervoltage detection for shutdown display
void underVoltageDetection(GwApi *api, CommonData &common){ void underVoltageError(CommonData &common) {
// Read settings #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
double voffset = (api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asString()).toFloat(); // Switch off all power lines
double vslope = (api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asString()).toFloat(); setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
setFlashLED(false); // Flash LED Off
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
// Shutdown EInk display
getdisplay().setFullWindow(); // Set full Refresh
//getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().fillScreen(common.bgcolor);// Clear screen
getdisplay().setTextColor(common.fgcolor);
getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(65, 150);
getdisplay().print("Undervoltage");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(65, 175);
getdisplay().print("Charge battery and restart system");
getdisplay().nextPage(); // Partial update
getdisplay().powerOff(); // Display power off
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
setPortPin(OBP_POWER_SD, false); // Power off SD card
#else
// Switch off all power lines
setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
setFlashLED(false); // Flash LED Off
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off
// Shutdown EInk display
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().fillScreen(common.bgcolor);// Clear screen
getdisplay().setTextColor(common.fgcolor);
getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(65, 150);
getdisplay().print("Undervoltage");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(65, 175);
getdisplay().print("To wake up repower system");
getdisplay().nextPage(); // Partial update
getdisplay().powerOff(); // Display power off
#endif
while (true) {
esp_deep_sleep_start(); // Deep Sleep without wakeup. Wakeup only after power cycle (restart).
}
}
inline bool underVoltageDetection(float voffset, float vslope) {
// Read supply voltage // Read supply voltage
#if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200 #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40
float minVoltage = 3.65; // Absolut minimum volatge for 3,7V LiPo accu float minVoltage = 3.65; // Absolut minimum volatge for 3,7V LiPo accu
#else #else
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60
float minVoltage = MIN_VOLTAGE; float minVoltage = MIN_VOLTAGE;
#endif #endif
double calVoltage = actVoltage * vslope + voffset; // Calibration float calVoltage = actVoltage * vslope + voffset; // Calibration
if(calVoltage < minVoltage){ return (calVoltage < minVoltage);
#if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
// Switch off all power lines
setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
setFlashLED(false); // Flash LED Off
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
// Shutdown EInk display
getdisplay().setFullWindow(); // Set full Refresh
//getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().fillScreen(common.bgcolor);// Clear screen
getdisplay().setTextColor(common.fgcolor);
getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(65, 150);
getdisplay().print("Undervoltage");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(65, 175);
getdisplay().print("Charge battery and restart system");
getdisplay().nextPage(); // Partial update
getdisplay().powerOff(); // Display power off
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
setPortPin(OBP_POWER_SD, false); // Power off SD card
#else
// Switch off all power lines
setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
setFlashLED(false); // Flash LED Off
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off
// Shutdown EInk display
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().fillScreen(common.bgcolor);// Clear screen
getdisplay().setTextColor(common.fgcolor);
getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(65, 150);
getdisplay().print("Undervoltage");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(65, 175);
getdisplay().print("To wake up repower system");
getdisplay().nextPage(); // Partial update
getdisplay().powerOff(); // Display power off
#endif
// Stop system
while(true){
esp_deep_sleep_start(); // Deep Sleep without weakup. Weakup only after power cycle (restart).
}
}
}
//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<double>::lowest();
GwApi::BoatValue *twdBVal = boatValues->findValueOrCreate("TWD");
GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate("TWS");
GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA");
GwApi::BoatValue *awaBVal = boatValues->findValueOrCreate("AWA");
GwApi::BoatValue *awsBVal = boatValues->findValueOrCreate("AWS");
GwApi::BoatValue *cogBVal = boatValues->findValueOrCreate("COG");
GwApi::BoatValue *stwBVal = boatValues->findValueOrCreate("STW");
GwApi::BoatValue *sogBVal = boatValues->findValueOrCreate("SOG");
GwApi::BoatValue *hdtBVal = boatValues->findValueOrCreate("HDT");
GwApi::BoatValue *hdmBVal = boatValues->findValueOrCreate("HDM");
GwApi::BoatValue *varBVal = boatValues->findValueOrCreate("VAR");
awaVal = awaBVal->valid ? awaBVal->value : DBL_MIN;
awsVal = awsBVal->valid ? awsBVal->value : DBL_MIN;
cogVal = cogBVal->valid ? cogBVal->value : DBL_MIN;
stwVal = stwBVal->valid ? stwBVal->value : DBL_MIN;
sogVal = sogBVal->valid ? sogBVal->value : DBL_MIN;
hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MIN;
hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MIN;
varVal = varBVal->valid ? varBVal->value : DBL_MIN;
api->getLogger()->logDebug(GwLog::DEBUG,"obp60task addTrueWind: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.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, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG);
isCalculated = WindUtils::calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa);
if (isCalculated) { // Replace values only, if successfully calculated and not already available
if (!twdBVal->valid) {
twdBVal->value = twd;
twdBVal->valid = true;
}
if (!twsBVal->valid) {
twsBVal->value = tws;
twsBVal->valid = true;
}
if (!twaBVal->valid) {
twaBVal->value = twa;
twaBVal->valid = true;
}
}
api->getLogger()->logDebug(GwLog::DEBUG,"obp60task addTrueWind: TWD_Valid %d, isCalculated %d, TWD %.1f, TWA %.1f, TWS %.1f", twdBVal->valid, isCalculated, twdBVal->value * RAD_TO_DEG,
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852);
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: %.1f, twaBVal: %.1f, twsBVal: %.1f, TWD_isValid? %d", twdBVal->value * RAD_TO_DEG,
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, twdBVal->valid);
calBVal = new GwApi::BoatValue("TWD"); // temporary solution for calibration of history buffer values
calBVal->setFormat(twdBVal->getFormat());
if (twdBVal->valid) {
calBVal->value = twdBVal->value;
calBVal->valid = twdBVal->valid;
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
twdBuf = static_cast<int16_t>(std::round(calBVal->value * 1000));
if (twdBuf >= twdHstryMin && twdBuf <= twdHstryMax) {
hstryBufList.twdHstry->add(twdBuf);
}
}
delete calBVal;
calBVal = nullptr;
calBVal = new GwApi::BoatValue("TWS"); // temporary solution for calibration of history buffer values
calBVal->setFormat(twsBVal->getFormat());
if (twsBVal->valid) {
calBVal->value = twsBVal->value;
calBVal->valid = twsBVal->valid;
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
twsBuf = static_cast<int16_t>(std::round(calBVal->value * 10));
if (twsBuf >= twsHstryMin && twsBuf <= twsHstryMax) {
hstryBufList.twsHstry->add(twsBuf);
}
}
delete calBVal;
calBVal = nullptr;
} }
// OBP60 Task // OBP60 Task
@@ -604,14 +431,11 @@ void OBP60Task(GwApi *api){
int lastPage=pageNumber; int lastPage=pageNumber;
BoatValueList boatValues; //all the boat values for the api query BoatValueList boatValues; //all the boat values for the api query
HstryBuf hstryBufList(1920); // Create ring buffers for history storage of some boat data (1920 seconds = 32 minutes)
WindUtils trueWind(&boatValues); // Create helper object for true wind calculation
//commonData.distanceformat=config->getString(xxx); //commonData.distanceformat=config->getString(xxx);
//add all necessary data to common data //add all necessary data to common data
// Create ring buffers for history storage of some boat data
RingBuffer<int16_t> twdHstry(960); // Circular buffer to store wind direction values; store 960 TWD values for 16 minutes history
RingBuffer<int16_t> twsHstry(960); // Circular buffer to store wind speed values (TWS)
tBoatHstryData hstryBufList = {&twdHstry, &twsHstry};
//fill the page data from config //fill the page data from config
numPages=config->getInt(config->visiblePages,1); numPages=config->getInt(config->visiblePages,1);
if (numPages < 1) numPages=1; if (numPages < 1) numPages=1;
@@ -632,6 +456,7 @@ void OBP60Task(GwApi *api){
pages[i].page=description->creator(commonData); pages[i].page=description->creator(commonData);
pages[i].parameters.pageName=pageType; pages[i].parameters.pageName=pageType;
pages[i].parameters.pageNumber = i + 1; pages[i].parameters.pageNumber = i + 1;
pages[i].parameters.api = api;
LOG_DEBUG(GwLog::DEBUG,"found page %s for number %d",pageType.c_str(),i); LOG_DEBUG(GwLog::DEBUG,"found page %s for number %d",pageType.c_str(),i);
//fill in all the user defined parameters //fill in all the user defined parameters
for (int uid=0;uid<description->userParam;uid++){ for (int uid=0;uid<description->userParam;uid++){
@@ -650,10 +475,8 @@ void OBP60Task(GwApi *api){
LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i); LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i);
pages[i].parameters.values.push_back(value); pages[i].parameters.values.push_back(value);
} }
if (pages[i].description->pageName == "WindPlot") { // Add boat history data to page parameters
// Add boat history data to page parameters pages[i].parameters.boatHstry = &hstryBufList;
pages[i].parameters.boatHstry = hstryBufList;
}
} }
// add out of band system page (always available) // add out of band system page (always available)
Page *syspage = allPages.pages[0]->creator(commonData); Page *syspage = allPages.pages[0]->creator(commonData);
@@ -661,12 +484,12 @@ void OBP60Task(GwApi *api){
// Read all calibration data settings from config // Read all calibration data settings from config
calibrationData.readConfig(config, logger); calibrationData.readConfig(config, logger);
// Check user setting for true wind calculation // Check user settings for true wind calculation
bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false); bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false);
// bool simulation = api->getConfig()->getBool(api->getConfig()->useSimuData, false); bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false);
// Initialize history buffer for certain boat data // Initialize history buffer for certain boat data
initHstryBuf(api, &boatValues, hstryBufList); hstryBufList.init(&boatValues, logger);
// Display screenshot handler for HTTP request // Display screenshot handler for HTTP request
// http://192.168.15.1/api/user/OBP60Task/screenshot // http://192.168.15.1/api/user/OBP60Task/screenshot
@@ -701,7 +524,9 @@ void OBP60Task(GwApi *api){
commonData.backlight.brightness = 2.55 * uint(config->getConfigItem(config->blBrightness,true)->asInt()); commonData.backlight.brightness = 2.55 * uint(config->getConfigItem(config->blBrightness,true)->asInt());
commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString(); commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString();
bool uvoltage = api->getConfig()->getConfigItem(api->getConfig()->underVoltage,true)->asBoolean(); bool uvoltage = config->getConfigItem(config->underVoltage, true)->asBoolean();
float voffset = (config->getConfigItem(config->vOffset,true)->asString()).toFloat();
float vslope = (config->getConfigItem(config->vSlope,true)->asString()).toFloat();
String cpuspeed = api->getConfig()->getConfigItem(api->getConfig()->cpuSpeed,true)->asString(); String cpuspeed = api->getConfig()->getConfigItem(api->getConfig()->cpuSpeed,true)->asString();
uint hdopAccuracy = uint(api->getConfig()->getConfigItem(api->getConfig()->hdopAccuracy,true)->asInt()); uint hdopAccuracy = uint(api->getConfig()->getConfigItem(api->getConfig()->hdopAccuracy,true)->asInt());
@@ -709,7 +534,7 @@ void OBP60Task(GwApi *api){
double homelon = commonData.config->getString(commonData.config->homeLON).toDouble(); double homelon = commonData.config->getString(commonData.config->homeLON).toDouble();
bool homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0; bool homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0;
if (homevalid) { if (homevalid) {
LOG_DEBUG(GwLog::LOG, "Home location set to %f : %f", homelat, homelon); LOG_DEBUG(GwLog::LOG, "Home location set to lat=%f, lon=%f", homelat, homelon);
} else { } else {
LOG_DEBUG(GwLog::LOG, "No valid home location found"); LOG_DEBUG(GwLog::LOG, "No valid home location found");
} }
@@ -743,14 +568,18 @@ void OBP60Task(GwApi *api){
//#################################################################################### //####################################################################################
bool systemPage = false; bool systemPage = false;
bool systemPageNew = false;
Page *currentPage; Page *currentPage;
while (true){ while (true){
delay(100); // Delay 100ms (loop time) delay(100); // Delay 100ms (loop time)
bool keypressed = false; bool keypressed = false;
// Undervoltage detection // Undervoltage detection
if(uvoltage == true){ if (uvoltage == true) {
underVoltageDetection(api, commonData); if (underVoltageDetection(voffset, vslope)) {
LOG_DEBUG(GwLog::ERROR, "Undervoltage detected, shutting down!");
underVoltageError(commonData);
}
} }
// Set CPU speed after boot after 1min // Set CPU speed after boot after 1min
@@ -795,6 +624,7 @@ void OBP60Task(GwApi *api){
systemPage = true; // System page is out of band systemPage = true; // System page is out of band
syspage->setupKeys(); syspage->setupKeys();
keyboardMessage = 0; keyboardMessage = 0;
systemPageNew = true;
} }
else { else {
currentPage = pages[pageNumber].page; currentPage = pages[pageNumber].page;
@@ -880,8 +710,8 @@ void OBP60Task(GwApi *api){
} }
} }
// Full display update afer a new selected page and 4s wait time // Full display update afer a new selected page and 8s wait time
if(millis() > starttime4 + 4000 && delayedDisplayUpdate == true){ if(millis() > starttime4 + 8000 && delayedDisplayUpdate == true){
starttime1 = millis(); starttime1 = millis();
starttime2 = millis(); starttime2 = millis();
getdisplay().setFullWindow(); // Set full update getdisplay().setFullWindow(); // Set full update
@@ -891,6 +721,7 @@ void OBP60Task(GwApi *api){
else{ else{
getdisplay().fillScreen(commonData.fgcolor); // Clear display getdisplay().fillScreen(commonData.fgcolor); // Clear display
#ifdef DISPLAY_GDEY042T81 #ifdef DISPLAY_GDEY042T81
getdisplay().hibernate(); // Set display in hybenate mode
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
#else #else
getdisplay().init(115200); // Init for normal displays getdisplay().init(115200); // Init for normal displays
@@ -918,6 +749,7 @@ void OBP60Task(GwApi *api){
else{ else{
getdisplay().fillScreen(commonData.fgcolor); // Clear display getdisplay().fillScreen(commonData.fgcolor); // Clear display
#ifdef DISPLAY_GDEY042T81 #ifdef DISPLAY_GDEY042T81
getdisplay().hibernate(); // Set display in hybenate mode
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
#else #else
getdisplay().init(115200); // Init for normal displays getdisplay().init(115200); // Init for normal displays
@@ -942,6 +774,7 @@ void OBP60Task(GwApi *api){
else{ else{
getdisplay().fillScreen(commonData.fgcolor); // Clear display getdisplay().fillScreen(commonData.fgcolor); // Clear display
#ifdef DISPLAY_GDEY042T81 #ifdef DISPLAY_GDEY042T81
getdisplay().hibernate(); // Set display in hybenate mode
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
#else #else
getdisplay().init(115200); // Init for normal displays getdisplay().init(115200); // Init for normal displays
@@ -971,10 +804,10 @@ void OBP60Task(GwApi *api){
api->getStatus(commonData.status); api->getStatus(commonData.status);
if (calcTrueWnds) { if (calcTrueWnds) {
addTrueWind(api, &boatValues); trueWind.addTrueWind(api, &boatValues, logger);
} }
// Handle history buffers for TWD, TWS for wind plot page and other usage // Handle history buffers for TWD, TWS for wind plot page and other usage
handleHstryBuf(api, &boatValues, hstryBufList); hstryBufList.handleHstryBuf(useSimuData);
// Clear display // Clear display
// getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor); // getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);
@@ -990,6 +823,11 @@ void OBP60Task(GwApi *api){
if (systemPage) { if (systemPage) {
displayFooter(commonData); displayFooter(commonData);
PageData sysparams; // empty PageData sysparams; // empty
sysparams.api = api;
if (systemPageNew) {
syspage->displayNew(sysparams);
systemPageNew = false;
}
syspage->displayPage(sysparams); syspage->displayPage(sysparams);
} }
else { else {
@@ -1006,10 +844,11 @@ void OBP60Task(GwApi *api){
} }
else{ else{
if (lastPage != pageNumber){ if (lastPage != pageNumber){
if (hasFRAM) fram.write(FRAM_PAGE_NO, pageNumber); // remember page for device restart pages[lastPage].page->leavePage(pages[lastPage].parameters); // call page cleanup code
if (hasFRAM) fram.write(FRAM_PAGE_NO, pageNumber); // remember new page for device restart
currentPage->setupKeys(); currentPage->setupKeys();
currentPage->displayNew(pages[pageNumber].parameters); currentPage->displayNew(pages[pageNumber].parameters);
lastPage=pageNumber; lastPage = pageNumber;
} }
//call the page code //call the page code
LOG_DEBUG(GwLog::DEBUG,"calling page %d",pageNumber); LOG_DEBUG(GwLog::DEBUG,"calling page %d",pageNumber);

View File

@@ -41,5 +41,24 @@
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
DECLARE_CAPABILITY(obp40,true) DECLARE_CAPABILITY(obp40,true)
#endif #endif
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp60-v2-docu.readthedocs.io/de/latest/"); // Link to help pages #ifdef BOARD_OBP60S3
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp60-v2-docu.readthedocs.io/en/latest/"); // Link to help pages
#endif
#ifdef BOARD_OBP40S3
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp40-v1-docu.readthedocs.io/en/latest/"); // Link to help pages
#endif
class BoatValueList{
public:
static const int MAXVALUES=100;
//we create a list containing all our BoatValues
//this is the list we later use to let the api fill all the values
//additionally we put the necessary values into the paga data - see below
GwApi::BoatValue *allBoatValues[MAXVALUES];
int numValues=0;
bool addValueToList(GwApi::BoatValue *v);
//helper to ensure that each BoatValue is only queried once
GwApi::BoatValue *findValueOrCreate(String name);
};
#endif #endif