1
0
mirror of https://github.com/thooge/esp32-nmea2000-obp60.git synced 2026-02-24 20:53:07 +01:00

7 Commits

16 changed files with 703 additions and 461 deletions

View File

@@ -2,9 +2,6 @@
#define _GWWIFI_H
#include <WiFi.h>
#include <GWConfig.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
class GwWifi{
private:
const GwConfigHandler *config;
@@ -19,19 +16,13 @@ class GwWifi{
bool apActive=false;
bool fixedApPass=true;
bool clientIsConnected=false;
SemaphoreHandle_t wifiMutex=nullptr;
static const TickType_t WIFI_MUTEX_TIMEOUT=pdMS_TO_TICKS(1000);
bool acquireMutex();
void releaseMutex();
public:
const char *AP_password = "esp32nmea2k";
GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass=true);
~GwWifi();
void setup();
void loop();
bool clientConnected();
bool connectClient(); // Blocking version
bool connectClientAsync(); // Non-blocking version for other tasks
bool connectClient();
String apIP();
bool isApActive(){return apActive;}
bool isClientActive(){return wifiClient->asBoolean();}

View File

@@ -1,6 +1,7 @@
#include <esp_wifi.h>
#include "GWWifi.h"
GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
this->config=config;
this->logger=log;
@@ -8,28 +9,6 @@ GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
wifiSSID=config->getConfigItem(config->wifiSSID,true);
wifiPass=config->getConfigItem(config->wifiPass,true);
this->fixedApPass=fixedApPass;
wifiMutex=xSemaphoreCreateMutex();
if (wifiMutex==nullptr){
LOG_DEBUG(GwLog::ERROR,"GwWifi: unable to create mutex");
}
}
GwWifi::~GwWifi(){
if (wifiMutex!=nullptr){
vSemaphoreDelete(wifiMutex);
wifiMutex=nullptr;
}
}
bool GwWifi::acquireMutex(){
if (wifiMutex==nullptr) return false;
return xSemaphoreTake(wifiMutex,WIFI_MUTEX_TIMEOUT)==pdTRUE;
}
void GwWifi::releaseMutex(){
if (wifiMutex!=nullptr){
xSemaphoreGive(wifiMutex);
}
}
void GwWifi::setup(){
LOG_DEBUG(GwLog::LOG,"Wifi setup");
@@ -106,14 +85,8 @@ bool GwWifi::connectInternal(){
if (wifiClient->asBoolean()){
clientIsConnected=false;
LOG_DEBUG(GwLog::LOG,"creating wifiClient ssid=%s",wifiSSID->asString().c_str());
// CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden
if (!acquireMutex()){
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in connectInternal");
return false;
}
WiFi.setAutoReconnect(false); //#102
wl_status_t rt=WiFi.begin(wifiSSID->asCString(),wifiPass->asCString());
releaseMutex();
LOG_DEBUG(GwLog::LOG,"wifiClient connect returns %d",(int)rt);
lastConnectStart=millis();
return true;
@@ -131,20 +104,8 @@ void GwWifi::loop(){
if (lastConnectStart > now || (lastConnectStart + RETRY_MILLIS) < now)
{
LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString());
// CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden
if (acquireMutex()){
WiFi.disconnect(true);
delay(300);
esp_wifi_stop();
delay(100);
esp_wifi_start();
releaseMutex();
connectInternal();
}
else{
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop");
}
WiFi.disconnect();
connectInternal();
}
}
else{
@@ -165,42 +126,11 @@ void GwWifi::loop(){
}
}
}
bool GwWifi::clientConnected(){
// CRITICAL SECTION: WiFi.status() muss geschützt werden
if (!acquireMutex()){
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in clientConnected");
return false; // Conservative: nehme an, nicht verbunden
}
bool result = WiFi.status() == WL_CONNECTED;
releaseMutex();
return result;
return WiFi.status() == WL_CONNECTED;
};
bool GwWifi::connectClient(){
// CRITICAL SECTION: Disconnect und Connect müssen atomar sein
if (!acquireMutex()){
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in connectClient");
return false;
}
WiFi.disconnect();
releaseMutex();
return connectInternal();
}
bool GwWifi::connectClientAsync(){
// Non-blocking version: Versuche Mutex zu nehmen, gib aber sofort auf
// Ideal für Tasks, die nicht blockieren dürfen
if (wifiMutex==nullptr){
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex not initialized in connectClientAsync");
return false;
}
if (xSemaphoreTake(wifiMutex, 0)!=pdTRUE){
LOG_DEBUG(GwLog::LOG,"GwWifi: connectClientAsync skipped - WiFi busy");
return false; // WiFi ist aktuell busy, versuche es später nochmal
}
WiFi.disconnect();
xSemaphoreGive(wifiMutex);
return connectInternal();
}

View File

@@ -1,7 +1,4 @@
#include "NetworkClient.h"
#include "GWWifi.h" // WiFi management (thread-safe)
extern GwWifi gwWifi; // Extern declaration of global WiFi instance
extern "C" {
#include "puff.h"
@@ -54,13 +51,8 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
const size_t capacity = READLIMIT; // Read limit for data (can be adjusted in NetworkClient.h)
uint8_t* buffer = (uint8_t*)malloc(capacity);
if (!gwWifi.clientConnected()) {
if (DEBUGING) {Serial.println("No WiFi connection");}
return false;
}
if (!buffer) {
if (DEBUGING) {Serial.println("Malloc failed buffer");}
if (DEBUG) {Serial.println("Malloc failed (buffer");}
return false;
}
@@ -114,7 +106,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
len += read;
lastData = millis();
if (DEBUGING) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
if (DEBUG) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
if (len < 20) continue; // Not enough data for header
@@ -130,7 +122,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
int res = puff(test, &testLen, buffer + headerOffset, &srcLen);
if (res == 0) {
if (DEBUGING) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
if (DEBUG) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
outData = test;
outLen = testLen;
complete = true;
@@ -175,7 +167,7 @@ bool NetworkClient::fetchAndDecompressJson(const String& url) {
return false;
}
if (DEBUGING) {Serial.println("JSON OK!");}
if (DEBUG) {Serial.println("JSON OK!");}
_valid = true;
return true;
}

View File

@@ -3,7 +3,7 @@
#include <WiFi.h>
#include <HTTPClient.h>
#define DEBUGING false // Debug flag for NetworkClient for more live information
#define DEBUG false // Debug flag for NetworkClient for more live information
#define READLIMIT 200000 // HTTP read limit in byte for gzip content (can be adjusted)
#define CONNECTIONTIMEOUT 3000 // Timeout in ms for HTTP connection
#define TCPREADTIMEOUT 2000 // Timeout in ms for read HTTP client stack

View File

@@ -923,7 +923,7 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor){
}
// Generator graphic
// Generator graphic with fill level
void generatorGraphic(uint x, uint y, int pcolor, int bcolor){
// Show battery
int xb = x; // X position
@@ -940,74 +940,6 @@ void generatorGraphic(uint x, uint y, int pcolor, int bcolor){
getdisplay().print("G");
}
// Display rudder position as horizontal bargraph with configurable +/- range (degrees)
void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg){
const int w = 360;
const int h = 20;
const int t = 3; // Line thickness
const int halfw = w/2;
const int halfh = h/2;
// Calculate top-left of bar (cx,cy are center of 0°)
int left = int(cx) - halfw;
int top = int(cy) - halfh;
// clamp provided range to allowed bounds [10,45]
if (rangeDeg < 10) rangeDeg = 10;
if (rangeDeg > 45) rangeDeg = 45;
// Pixels per degree for +/-rangeDeg -> total span = 2*rangeDeg
const float pxPerDeg = float(w) / (2.0f * float(rangeDeg));
// Draw outer border (thickness t)
for (int i = 0; i < t; i++) {
getdisplay().drawRect(left + i, top + i, w - 2 * i, h - 2 * i, fg);
}
// Fill inner area with background
getdisplay().fillRect(left + t, top + t, w - 2 * t, h - 2 * t, bg);
// Draw center line
getdisplay().drawRect(cx - 1, top + 1, 3 , h - 2, fg);
// Clamp rudder position to -rangeDeg..rangeDeg
if (rudderPosition > (int)rangeDeg) rudderPosition = (int)rangeDeg;
if (rudderPosition < -((int)rangeDeg)) rudderPosition = -((int)rangeDeg);
// Compute fill width in pixels
int fillPx = int(round(rudderPosition * pxPerDeg)); // positive -> right
// Fill area from center to position (if non-zero)
int centerx = cx;
int innerTop = top + t;
int innerH = h - 2 * t;
if (fillPx > 0) {
// Right side
getdisplay().fillRect(centerx, innerTop, fillPx, innerH, fg);
} else if (fillPx < 0) {
// Left side
getdisplay().fillRect(centerx + fillPx, innerTop, -fillPx, innerH, fg);
}
// Draw tick marks every 5° and labels outside the bar
getdisplay().setTextColor(fg);
getdisplay().setFont(&Ubuntu_Bold8pt8b);
for (int angle = -((int)rangeDeg); angle <= (int)rangeDeg; angle += 5) {
int xpos = int(round(centerx + angle * pxPerDeg));
// Vertical tick inside bar
getdisplay().drawLine(xpos, top, xpos, top + h + 2, fg);
// Label outside: below the bar
String lbl = String(angle);
int16_t bx, by;
uint16_t bw, bh;
getdisplay().getTextBounds(lbl, 0, 0, &bx, &by, &bw, &bh);
int16_t tx = xpos - bw/2;
int16_t ty = top + h + bh + 5; // A little spacing
getdisplay().setCursor(tx, ty);
getdisplay().print(lbl);
}
}
// Function to handle HTTP image request
// http://192.168.15.1/api/user/OBP60Task/screenshot
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request) {

View File

@@ -128,10 +128,6 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor); // S
void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic
void startLedTask(GwApi *api);
// Display rudder position as horizontal bargraph with configurable +/- range (degrees)
// 'rangeDeg' is unsigned and will be clamped to [10,45]
void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg);
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request);
// Icons

View File

@@ -371,7 +371,7 @@ void sensorTask(void *param){
GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP);
GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop};
// Internal iRTC with NTP init
// Internal RTC with NTP init
ESP32Time rtc(0);
if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") {
GwApi::Status status;
@@ -432,17 +432,17 @@ void sensorTask(void *param){
iRTC RTC GPS N2K
0 0 0 (1)
0 0 (1) X
0 (1) 0 X
0 1 <-(1) X
(1) 0 0 X
1 0 (1) X
1 ->(1) 0 X
1 1 <-(1) X
0 0 (1) (X)
0 (1) 0 (X)
0 1 <-(1) (X)
(1) 0 0 (X)
1 0 (1) (X)
1 ->(1) 0 (X)
1 1 <-(1) (X)
*/
// If RTC DS1388 ready, then copy iRTC and GPS data to RTC all 1 min
// If RTC DS1388 ready, then copy iRTC and GPS data to RTC all 1min
if(millis() > starttime11 + 1*60*1000){
starttime11 = millis();
// Set RTC chip via iRTC (NTP)
@@ -475,7 +475,7 @@ void sensorTask(void *param){
// Adjust RTC time as unix time value
ds1388.adjust(adjusttime);
}
}
}
}
// Set RTC chip via N2K or 183 in case the internal GPS is off (only one time)
@@ -524,7 +524,7 @@ void sensorTask(void *param){
// N2K sysTime is double in n2klib
double sysTime = (dt.hour() * 3600) + (dt.minute() * 60) + dt.second();
if(!isnan(daysAt1970) && !isnan(sysTime)){
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon+1, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
api->sendN2kMessage(N2kMsg);
@@ -533,26 +533,25 @@ void sensorTask(void *param){
}
// Send date and time from software RTC (iRTC)
if (iRTC_ready == true && RTC_ready == false && GPS_ready == false) {
sensors.rtcTime = rtc.getTimeStruct();
// Use internal RTC feature
sensors.rtcTime = rtc.getTimeStruct(); // Save software RTC values in SensorData
// TODO implement daysAt1970 and sysTime as methods of DateTime
const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334};
int year = sensors.rtcTime.tm_year + 1900;
int month = sensors.rtcTime.tm_mon;
int day = sensors.rtcTime.tm_mday;
uint16_t switchYear = ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 + ((year - 1) - 1600) / 400;
long daysAt1970 = (year - 1970) * 365L + switchYear + daysOfYear[month] + day - 1;
// Leap day add if date is after Feb (i.e. month >= March)
if (month >= 2 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) {
uint16_t switchYear = ((sensors.rtcTime.tm_year-1)-1968)/4 - ((sensors.rtcTime.tm_year-1)-1900)/100 + ((sensors.rtcTime.tm_year-1)-1600)/400;
long daysAt1970 = (sensors.rtcTime.tm_year-1970)*365 + switchYear + daysOfYear[sensors.rtcTime.tm_mon-1] + sensors.rtcTime.tm_mday-1;
// If switch year then add one day
if ((sensors.rtcTime.tm_mon > 2) && (sensors.rtcTime.tm_year % 4 == 0 && (sensors.rtcTime.tm_year % 100 != 0 || sensors.rtcTime.tm_year % 400 == 0))) {
daysAt1970 += 1;
}
double sysTime = sensors.rtcTime.tm_hour * 3600.0 + sensors.rtcTime.tm_min * 60.0 + sensors.rtcTime.tm_sec;
//api->getLogger()->logDebug(GwLog::LOG, "iRTC time: %04d/%02d/%02d %02d:%02d:%02d", year, month + 1, day, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
SetN2kPGN126992(N2kMsg, 0, daysAt1970, sysTime, N2ktimes_LocalCrystalClock);
api->sendN2kMessage(N2kMsg);
// N2K sysTime is double in n2klib
double sysTime = (sensors.rtcTime.tm_hour * 3600) + (sensors.rtcTime.tm_min * 60) + sensors.rtcTime.tm_sec;
if(!isnan(daysAt1970) && !isnan(sysTime)){
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
api->sendN2kMessage(N2kMsg);
}
}
}
// Send 1Wire data for all temperature sensors to N2K all 2s

View File

@@ -0,0 +1,497 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
/*
This page is in experimental stage so be warned!
North is up.
Anchor page with background map from mapservice
Boatdata used
DBS - Water depth
HDT - Boat heading
AWS - Wind strength; Boat not moving so we assume AWS=TWS and AWD=TWD
AWD - Wind direction
LAT/LON - Boat position, current
HDOP - Position error
Drop / raise function in device OBP40 has to be done inside
config mode because of limited number of buttons.
TODO
gzip for data transfer,
manually inflating with tinflate from ROM
Save position in FRAM
Alarm: gps fix lost
switch unit feet/meter
force map update if new position is different from old position by
a certain level (e.g. 10m)
Map service options / URL parameters
- mandatory
lat: latitude
lon: longitude
width: image width in px
height: image height in px
- optional
zoom: zoom level, default 15
mrot: map rotation angle in degrees
mtype: map type, default="Open Street Map"
dtype: dithering type, default="Atkinson"
cutout: image cutout type 0=none
tab: tab size, 0=none
border: border line zize in px, default 2
symbol: synmol number, default=2 triangle
srot: symbol rotation in degrees
ssize: symbol size in px, default=15
grid: show map grid
*/
#include <WiFi.h>
#include <HTTPClient.h>
#include "Pagedata.h"
#include "OBP60Extensions.h"
#define anchor_width 16
#define anchor_height 16
static unsigned char anchor_bits[] PROGMEM = {
0x80, 0x01, 0x40, 0x02, 0x40, 0x02, 0x80, 0x01, 0xf0, 0x0f, 0x80, 0x01,
0x80, 0x01, 0x88, 0x11, 0x8c, 0x31, 0x8e, 0x71, 0x84, 0x21, 0x86, 0x61,
0x86, 0x61, 0xfc, 0x3f, 0xf8, 0x1f, 0x80, 0x01 };
class PageAnchor : public Page
{
private:
char mode = 'N'; // (N)ormal, (C)onfig
int8_t editmode = -1; // marker for menu/edit/set function
//uint8_t *mapbuf = new uint8_t[10000]; // 8450 Byte without header
//int mapbuf_size = 10000;
//uint8_t *mapbuf = (uint8_t*) heap_caps_malloc(mapbuf_size, MALLOC_CAP_SPIRAM);
GFXcanvas1 *canvas;
const uint16_t map_width = 264;
const uint16_t map_height = 260;
bool map_valid = false;
char map_service = 'R'; // (O)BP Service, (L)ocal Service, (R)emote Service
double map_lat = 0; // current center of valid map
double map_lon = 0;
String server_name; // server with map service
uint16_t server_port = 80;
String tile_path;
String lengthformat;
double scale = 50; // Radius of display circle in meter, depends on lat
uint8_t zoom = 15; // map zoom level
bool alarm = false;
bool alarm_enabled = false;
uint8_t alarm_range;
uint8_t chain_length;
uint8_t chain = 0;
bool anchor_set = false;
double anchor_lat;
double anchor_lon;
double anchor_depth;
int anchor_ts; // time stamp anchor dropped
void displayModeNormal(PageData &pageData) {
// Boatvalues: DBS, HDT, AWS, AWD, LAT, LON, HDOP
GwApi::BoatValue *bv_dbs = pageData.values[0]; // DBS
String sval_dbs = formatValue(bv_dbs, *commonData).svalue;
String sunit_dbs = formatValue(bv_dbs, *commonData).unit;
GwApi::BoatValue *bv_hdt = pageData.values[1]; // HDT
String sval_hdt = formatValue(bv_hdt, *commonData).svalue;
GwApi::BoatValue *bv_aws = pageData.values[2]; // AWS
String sval_aws = formatValue(bv_aws, *commonData).svalue;
String sunit_aws = formatValue(bv_aws, *commonData).unit;
GwApi::BoatValue *bv_awd = pageData.values[3]; // AWD
String sval_awd = formatValue(bv_awd, *commonData).svalue;
GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT
String sval_lat = formatValue(bv_lat, *commonData).svalue;
GwApi::BoatValue *bv_lon = pageData.values[5]; // LON
String sval_lon = formatValue(bv_lon, *commonData).svalue;
GwApi::BoatValue *bv_hdop = pageData.values[6]; // HDOP
String sval_hdop = formatValue(bv_hdop, *commonData).svalue;
String sunit_hdop = formatValue(bv_hdop, *commonData).unit;
commonData->logger->logDebug(GwLog::DEBUG, "Drawing at PageAnchor; DBS=%f, HDT=%f, AWS=%f", bv_dbs->value, bv_hdt->value, bv_aws->value);
// Draw canvas with background map
// rhumb(map_lat, map_lon, bv_lat->value, bv_lon->value)
int posdiff = 0;
if (map_valid) {
if (bv_lat->valid and bv_lon->valid) {
// calculate movement since last map refresh
posdiff = rhumb(map_lat, map_lon, bv_lat->value, bv_lon->value);
if (posdiff > 25) {
map_lat = bv_lat->value;
map_lon = bv_lon->value;
map_valid = getBackgroundMap(map_lat, map_lon, zoom);
if (map_valid) {
// prepare visible space for anchor-symbol or boat
canvas->fillCircle(132, 130, 12, commonData->fgcolor);
}
}
}
getdisplay().drawBitmap(68, 20, canvas->getBuffer(), map_width, map_height, commonData->fgcolor);
}
Point c = {200, 150}; // center = anchor position
uint16_t r = 125;
// Circle as map border
getdisplay().drawCircle(c.x, c.y, r, commonData->fgcolor);
getdisplay().drawCircle(c.x, c.y, r + 1, commonData->fgcolor);
Point b = {200, 180}; // boat position while dropping anchor
const std::vector<Point> pts_boat = { // polygon lines
{b.x - 5, b.y},
{b.x - 5, b.y - 10},
{b.x, b.y - 16},
{b.x + 5, b.y - 10},
{b.x + 5, b.y}
};
//rotatePoints und dann Linien zeichnen
// TODO rotate boat according to current heading
if (bv_hdt->valid) {
if (map_valid) {
Point b1 = rotatePoint(c, {b.x, b.y - 8}, bv_hdt->value * RAD_TO_DEG);
getdisplay().fillCircle(b1.x, b1.y, 10, commonData->bgcolor);
}
drawPoly(rotatePoints(c, pts_boat, bv_hdt->value * RAD_TO_DEG), commonData->fgcolor);
} else {
// no heading available draw north oriented
if (map_valid) {
getdisplay().fillCircle(b.x, b.y - 8, 10, commonData->bgcolor);
}
drawPoly(pts_boat, commonData->fgcolor);
}
// Draw wind arrow
const std::vector<Point> pts_wind = {
{c.x, c.y - r + 25},
{c.x - 12, c.y - r - 4},
{c.x, c.y - r + 6},
{c.x + 12, c.y - r - 4}
};
if (bv_awd->valid) {
fillPoly4(rotatePoints(c, pts_wind, bv_awd->value), commonData->fgcolor);
}
// Title and corner value headings
getdisplay().setTextColor(commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold10pt8b);
// Left
getdisplay().setCursor(8, 36);
getdisplay().print("Anchor");
getdisplay().setCursor(8, 210);
getdisplay().print("Depth");
// Right
drawTextRalign(392, 80, "Chain");
drawTextRalign(392, 210, "Wind");
// Units
getdisplay().setCursor(8, 272);
getdisplay().print(sunit_dbs);
drawTextRalign(392, 272, sunit_aws);
// drawTextRalign(392, 100, lengthformat); // chain unit not implemented
// Corner values
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(8, 54);
getdisplay().print(anchor_set ? "Dropped" : "Ready"); // Anchor state
getdisplay().setCursor(8, 72);
getdisplay().print("Alarm: "); // Alarm state
getdisplay().print(alarm_enabled ? "on" : "off");
getdisplay().setCursor(8, 120);
getdisplay().print("Zoom");
getdisplay().setCursor(8, 136);
getdisplay().print(zoom);
getdisplay().setCursor(8, 160);
getdisplay().print("diff");
getdisplay().setCursor(8, 176);
if (map_valid and bv_lat->valid and bv_lon->valid) {
getdisplay().print(String(posdiff));
} else {
getdisplay().print("n/a");
}
// Chain out TODO lengthformat ft/m
drawTextRalign(392, 96, String(chain) + " m");
drawTextRalign(392, 96+16, "of " + String(chain_length) + " m");
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
// Depth
getdisplay().setCursor(8, 250);
getdisplay().print(sval_dbs);
// Wind
getdisplay().setCursor(320, 250);
getdisplay().print(sval_aws);
// Position of boat in center of map
getdisplay().setFont(&IBM8x8px);
drawTextRalign(392, 34, sval_lat);
drawTextRalign(392, 44, sval_lon);
// quality
String hdop = "HDOP: ";
if (bv_hdop->valid) {
hdop += String(round(bv_hdop->value));
} else {
hdop += " n/a";
}
drawTextRalign(392, 54, hdop);
// zoom scale
getdisplay().drawLine(c.x + 10, c.y, c.x + r - 4, c.y, commonData->fgcolor);
// arrow left
getdisplay().drawLine(c.x + 10, c.y, c.x + 16, c.y - 4, commonData->fgcolor);
getdisplay().drawLine(c.x + 10, c.y, c.x + 16, c.y + 4, commonData->fgcolor);
// arrow right
getdisplay().drawLine(c.x + r - 4, c.y, c.x + r - 10, c.y - 4, commonData->fgcolor);
getdisplay().drawLine(c.x + r - 4, c.y, c.x + r - 10, c.y + 4, commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold8pt8b);
drawTextCenter(c.x + r / 2, c.y + 8, String(scale, 0) + "m");
// draw anchor symbol (as bitmap)
getdisplay().drawXBitmap(c.x - anchor_width / 2, c.y - anchor_height / 2,
anchor_bits, anchor_width, anchor_height, commonData->fgcolor);
}
void displayModeConfig(PageData &pageData) {
getdisplay().setTextColor(commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(8, 48);
getdisplay().print("Anchor configuration");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(8, 250);
getdisplay().print("Press MODE to leave config");
getdisplay().setCursor(8, 68);
getdisplay().printf("Server: %s", server_name.c_str());
getdisplay().setCursor(8, 88);
getdisplay().printf("Port: %d", server_port);
getdisplay().setCursor(8, 108);
getdisplay().printf("Tilepath: %s", tile_path.c_str());
GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT
GwApi::BoatValue *bv_lon = pageData.values[5]; // LON
if (!bv_lat->valid or !bv_lon->valid) {
getdisplay().setCursor(8, 128);
getdisplay().printf("No valid position: background map disabled");
}
}
public:
PageAnchor(CommonData &common)
{
commonData = &common;
common.logger->logDebug(GwLog::LOG,"Instantiate PageAnchor");
String mapsource = common.config->getString(common.config->mapsource);
if (mapsource == "Local Service") {
map_service = 'L';
server_name = common.config->getString(common.config->ipAddress);
server_port = common.config->getInt(common.config->localPort);
tile_path = "";
} else if (mapsource == "Remote Service") {
map_service = 'R';
server_name = common.config->getString(common.config->mapServer);
tile_path = common.config->getString(common.config->mapTilePath);
} else { // OBP Service or undefined
map_service = 'O';
server_name = "norbert-walter.dnshome.de";
tile_path = "";
}
zoom = common.config->getInt(common.config->zoomlevel);
lengthformat = common.config->getString(common.config->lengthFormat);
chain_length = common.config->getInt(common.config->chainLength);
canvas = new GFXcanvas1(264, 260); // Byte aligned, no padding!
}
void setupKeys(){
Page::setupKeys();
commonData->keydata[0].label = "MODE";
#ifdef BOARD_OBP40S3
commonData->keydata[1].label = "DROP";
#endif
#ifdef BOARD_OBP60S3
commonData->keydata[4].label = "DROP";
#endif
}
// TODO OBP40 / OBP60 different handling
int handleKey(int key) {
if (key == 1) { // Switch between normal and config mode
if (mode == 'N') {
mode = 'C';
commonData->keydata[1].label = "EDIT";
} else {
mode = 'N';
#ifdef BOARD_OBP40S3
commonData->keydata[1].label = anchor_set ? "RAISE": "DROP";
#endif
#ifdef BOARD_OBP60S3
commonData->keydata[4].label = anchor_set ? "RAISE": "DROP";
#endif
}
return 0;
}
if (key == 2) {
anchor_set = !anchor_set;
commonData->keydata[1].label = anchor_set ? "RAISE": "DROP";
return 0;
}
// Code for keylock
if (key == 11){
commonData->keylock = !commonData->keylock;
return 0;
}
return key;
}
int rhumb(double lat1, double lon1, double lat2, double lon2) {
// calc distance in m between two geo points
static const double degToRad = M_PI / 180.0;
lat1 = degToRad * lat1;
lon1 = degToRad * lon1;
lat2 = degToRad * lat2;
lon2 = degToRad * lon2;
double dlon = lon2 - lon1;
double dlat = lat2 - lat1;
double mlat = (lat1 + lat2) / 2;
return (int) (6371000 * sqrt(pow(dlat, 2) + pow(cos(mlat) * dlon, 2)));
}
bool getBackgroundMap(double lat, double lon, uint8_t zoom) {
// HTTP-Request for map
// TODO über pagedata -> status abfragen?
if (WiFi.status() != WL_CONNECTED) {
return false;
}
bool valid = false;
HTTPClient http;
String url = "http://" + server_name + "/" + tile_path;
String parameter = "?lat=" + String(lat, 6) + "&lon=" + String(lon, 6)+ "&zoom=" + String(zoom)
+ "&width=" + String(map_width) + "&height=" + String(map_height);
commonData->logger->logDebug(GwLog::LOG, "HTTP query: %s", String(url + parameter).c_str());
http.begin(url + parameter);
// http.SetAcceptEncoding("gzip");
// TODO miniz.c from ROM
int httpCode = http.GET();
if (httpCode > 0) {
if (httpCode == HTTP_CODE_OK) {
WiFiClient* stream = http.getStreamPtr();
int size = http.getSize();
commonData->logger->logDebug(GwLog::LOG, "HTTP get size: %d", size);
// header: P4<LF><width> <height><LF> (e.g. 11 byte)
uint8_t header[14]; // max: P4<LF>wwww wwww<LF>
bool header_read = false;
int header_size = 0;
uint8_t* buf = canvas->getBuffer();
int n = 0;
int ix = 0;
while (stream->available()) {
uint8_t b = stream->read();
n += 1;
if ((! header_read) and (n < 13) ) {
header[n-1] = b;
if ((n > 3) and (b == 0x0a)) {
header_read = true;
header_size = n;
header[n] = 0;
}
} else {
// write image data to canvas buffer
buf[ix++] = b;
}
}
if (n == size) {
valid = true;
}
commonData->logger->logDebug(GwLog::LOG, "HTTP: final bytesRead=%d, header-size=%d", n, header_size);
} else {
commonData->logger->logDebug(GwLog::LOG, "HTTP result #%d", httpCode);
}
} else {
commonData->logger->logDebug(GwLog::ERROR, "HTTP error #%d", httpCode);
}
http.end();
return valid;
}
void displayNew(PageData &pageData){
GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT
GwApi::BoatValue *bv_lon = pageData.values[5]; // LON
// check if valid data available
if (!bv_lat->valid or !bv_lon->valid) {
map_valid = false;
return;
}
map_lat = bv_lat->value; // save for later comparison
map_lon = bv_lon->value;
map_valid = getBackgroundMap(map_lat, map_lon, zoom);
if (map_valid) {
// prepare visible space for anchor-symbol or boat
canvas->fillCircle(132, 130, 10, commonData->fgcolor);
}
};
int displayPage(PageData &pageData) {
GwLog *logger = commonData->logger;
// Logging boat values
logger->logDebug(GwLog::LOG, "Drawing at PageAnchor; Mode=%c", mode);
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
if (mode == 'N') {
displayModeNormal(pageData);
} else if (mode == 'C') {
displayModeConfig(pageData);
}
return PAGE_UPDATE;
};
};
static Page *createPage(CommonData &common){
return new PageAnchor(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 registerPageAnchor(
"Anchor", // Page name
createPage, // Action
0, // Number of bus values depends on selection in Web configuration
{"DBS", "HDT", "AWS", "AWD", "LAT", "LON", "HDOP"}, // Names of bus values undepends on selection in Web configuration (refer GwBoatData.h)
true // Show display header on/off
);
#endif

View File

@@ -5,9 +5,8 @@
// These constants have to match the declaration below in :
// PageDescription registerPageAutopilot(
// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page
const int HowManyValues = 11;
// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page
const int HowManyValues = 9;
const int AverageValues = 4;
@@ -20,13 +19,10 @@ const int ShowDBT = 5;
const int ShowXTE = 6;
const int ShowDTW = 7;
const int ShowBTW = 8;
const int ShowRPOS = 9;
const int ShowROT = 10;
const int Compass_X0 = 200; // X center point of compass band
const int Compass_Y0 = 90; // Y position of compass lines
//const int Compass_LineLength = 22; // Length of compass lines
const int Compass_LineLength = 15; // Length of compass lines
const int Compass_Y0 = 220; // Y position of compass lines
const int Compass_LineLength = 22; // Length of compass lines
const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
class PageAutopilot : public Page
@@ -42,11 +38,8 @@ class PageAutopilot : public Page
virtual void setupKeys(){
Page::setupKeys();
commonData->keydata[0].label = "-10";
commonData->keydata[1].label = "-1";
commonData->keydata[2].label = "Auto";
commonData->keydata[3].label = "+1";
commonData->keydata[4].label = "+10";
commonData->keydata[0].label = "CMP";
commonData->keydata[1].label = "SRC";
}
virtual int handleKey(int key){
@@ -76,8 +69,8 @@ class PageAutopilot : public Page
GwLog *logger = commonData->logger;
// Old values for hold function
static String OldDataText[HowManyValues] = {"", "", "", "", "", "","", "", "", "", ""};
static String OldDataUnits[HowManyValues] = {"", "", "", "", "", "","", "", "", "", ""};
static String OldDataText[HowManyValues] = {"", "", "","", "", "","", "", ""};
static String OldDataUnits[HowManyValues] = {"", "", "","", "", "","", "", ""};
// Get config data
String lengthformat = config->getString(config->lengthFormat);
@@ -113,13 +106,15 @@ class PageAutopilot : public Page
setBlinkingLED(false);
setFlashLED(false);
}
if (bvalue == NULL) return PAGE_OK; // WTF why this statement?
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
/*
// Horizontal line 2 pix top & bottom
// Print data on top half
getdisplay().fillRect(0, 130, 400, 2, commonData->fgcolor);
@@ -143,7 +138,7 @@ class PageAutopilot : public Page
OldDataText[WhichDataDisplay] = DataText[WhichDataDisplay]; // Save the old value
OldDataUnits[WhichDataDisplay] = DataUnits[WhichDataDisplay]; // Save the old unit
}
*/
// Now draw compass band
// Get the data
double TheAngle = DataValue[WhichDataCompass];
@@ -157,13 +152,13 @@ class PageAutopilot : public Page
buffer[0]=0;
getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().setCursor(10, Compass_Y0-40);
getdisplay().setCursor(10, Compass_Y0-60);
getdisplay().print(DataName[WhichDataCompass]); // Page name
// Draw compass base line and pointer
getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor);
//getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor);
getdisplay().fillTriangle(Compass_X0,Compass_Y0-30,Compass_X0-10,Compass_Y0-60,Compass_X0+10,Compass_Y0-60,commonData->fgcolor);
getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor);
// Draw trendlines
for ( int i = 1; i < abs(TheTrend) / 2; i++){
int x1;
@@ -243,8 +238,6 @@ class PageAutopilot : public Page
// if ( x_test > 390)
// x_test = 320;
displayRudderPosition(DataValue[ShowSOG], 20, 200, 160, commonData->fgcolor, commonData->bgcolor);
return PAGE_UPDATE;
};
@@ -263,7 +256,7 @@ PageDescription registerPageAutopilot(
"Autopilot", // Page name
createPage, // Action
0, // Number of bus values depends on selection in Web configuration
{"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page
{"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page
true // Show display header on/off
);

View File

@@ -15,7 +15,6 @@
#include "images/logo64.xbm"
#include <esp32/clk.h>
#include "qrcode.h"
#include <vector>
#ifdef BOARD_OBP40S3
#include "dirent.h"
@@ -41,7 +40,6 @@ private:
String rtc_module;
String gps_module;
String env_module;
String flashLED;
String batt_sensor;
String solar_sensor;
@@ -52,17 +50,6 @@ private:
char mode = 'N'; // (N)ormal, (S)ettings, (D)evice list, (C)ard
#ifdef PATCH_N2K
struct device {
uint64_t NAME;
uint8_t id;
char hex_name[17];
uint16_t manuf_code;
const char *model;
};
std::vector<device> devicelist;
#endif
public:
PageSystem(CommonData &common){
commonData = &common;
@@ -89,7 +76,6 @@ public:
rot_sensor = common.config->getString(common.config->useRotSensor);
homelat = common.config->getString(common.config->homeLAT).toDouble();
homelon = common.config->getString(common.config->homeLON).toDouble();
flashLED = common.config->getString(common.config->flashLED);
}
void setupKeys() {
@@ -182,43 +168,19 @@ public:
}
}
void displayNew(PageData &pageData) {
#ifdef BOARD_OBP60S3
// Clear optical warning
if (flashLED == "Limit Violation") {
setBlinkingLED(false);
setFlashLED(false);
}
#endif
#ifdef PATCH_N2K
// load current device list
tN2kDeviceList *pDevList = pageData.api->getN2kDeviceList();
// TODO check if changed
if (pDevList->ReadResetIsListUpdated()) {
// only reload if changed
devicelist.clear();
for (uint8_t i = 0; i <= 252; i++) {
const tNMEA2000::tDevice *d = pDevList->FindDeviceBySource(i);
if (d == nullptr) {
continue;
}
device dev;
dev.id = i;
dev.NAME = d->GetName();
snprintf(dev.hex_name, sizeof(dev.hex_name), "%08X%08X", (uint32_t)(dev.NAME >> 32), (uint32_t)(dev.NAME & 0xFFFFFFFF));
dev.manuf_code = d->GetManufacturerCode();
dev.model = d->GetModelID();
devicelist.push_back(dev);
}
}
#endif
};
int displayPage(PageData &pageData){
GwConfigHandler *config = commonData->config;
GwLog *logger = commonData->logger;
// Get config data
String flashLED = config->getString(config->flashLED);
// Optical warning by limit violation (unused)
if(String(flashLED) == "Limit Violation"){
setBlinkingLED(false);
setFlashLED(false);
}
// Logging boat values
logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
@@ -499,54 +461,12 @@ public:
getdisplay().print("NMEA2000 device list");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(20, 70);
getdisplay().setCursor(20, 80);
getdisplay().print("RxD: ");
getdisplay().print(String(commonData->status.n2kRx));
getdisplay().setCursor(120, 70);
getdisplay().setCursor(20, 100);
getdisplay().print("TxD: ");
getdisplay().print(String(commonData->status.n2kTx));
#ifdef PATCH_N2K
x0 = 20;
y0 = 100;
getdisplay().setFont(&Ubuntu_Bold10pt8b);
getdisplay().setCursor(x0, y0);
getdisplay().print("ID");
getdisplay().setCursor(x0 + 50, y0);
getdisplay().print("Model");
getdisplay().setCursor(x0 + 250, y0);
getdisplay().print("Manuf.");
getdisplay().drawLine(18, y0 + 4, 360 , y0 + 4 , commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold8pt8b);
y0 = 120;
uint8_t n_dev = 0;
for (const device& item : devicelist) {
if (n_dev > 8) {
break;
}
getdisplay().setCursor(x0, y0 + n_dev * 20);
getdisplay().print(item.id);
getdisplay().setCursor(x0 + 50, y0 + n_dev * 20);
getdisplay().print(item.model);
getdisplay().setCursor(x0 + 250, y0 + n_dev * 20);
getdisplay().print(item.manuf_code);
n_dev++;
}
getdisplay().setCursor(x0, y0 + (n_dev + 1) * 20);
if (n_dev == 0) {
getdisplay().printf("no devices found on bus");
} else {
getdisplay().drawLine(18, y0 + n_dev * 20, 360 , y0 + n_dev * 20, commonData->fgcolor);
getdisplay().printf("%d devices of %d in total", n_dev, devicelist.size());
}
#else
getdisplay().setCursor(20, 100);
getdisplay().print("NMEA2000 not exposed to obp60 task");
#endif
}
// Update display

View File

@@ -19,6 +19,28 @@
"obp40": "true"
}
},
{
"name": "mapServer",
"label": "map server",
"type": "string",
"default": "",
"description": "Server for converting map tiles. Use only one hostname or IP address",
"category": "wifi client",
"capabilities": {
"obp40": "true"
}
},
{
"name": "mapTilePath",
"label": "map tile path",
"type": "string",
"default": "map.php",
"description": "Path to converter access e.g. index.php or map.php",
"category": "wifi client",
"capabilities": {
"obp40": "true"
}
},
{
"name": "timeZone",
"label": "Time Zone",
@@ -75,6 +97,20 @@
"obp40": "true"
}
},
{
"name": "chainLength",
"label": "Anchor Chain Length [m]",
"type": "number",
"default": "0",
"check": "checkMinMax",
"min": 0,
"max": 255,
"description": "The length of the anchor chain [0...255m]",
"category": "OBP40 Settings",
"capabilities": {
"obp40": "true"
}
},
{
"name": "fuelTank",
"label": "Fuel Tank [l]",
@@ -678,7 +714,7 @@
"type": "string",
"default": "text1",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"category": "OBP40 IO-Modul1",
"capabilities": {
"obp40":"true"
}
@@ -689,7 +725,7 @@
"type": "string",
"default": "text2",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"category": "OBP40 IO-Modul1",
"capabilities": {
"obp40":"true"
}
@@ -700,7 +736,7 @@
"type": "string",
"default": "text3",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"category": "OBP40 IO-Modul1",
"capabilities": {
"obp40":"true"
}
@@ -711,7 +747,7 @@
"type": "string",
"default": "text4",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"category": "OBP40 IO-Modul1",
"capabilities": {
"obp40":"true"
}
@@ -722,7 +758,7 @@
"type": "string",
"default": "text5",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"category": "OBP40 IO-Modul1",
"capabilities": {
"obp40":"true"
}
@@ -1067,7 +1103,8 @@
"description": "Type of map source, cloud service or local service",
"list": [
"OBP Service",
"Local Service"
"Local Service",
"Remote Service"
],
"category": "OBP40 Navigation",
"capabilities": {
@@ -1104,6 +1141,34 @@
{ "mapsource": ["Local Service"] }
]
},
{
"name": "mapServer",
"label": "map server",
"type": "string",
"default": "",
"description": "Server for converting map tiles. Use only one hostname or IP address",
"category": "OBP40 Navigation",
"capabilities": {
"obp40": "true"
},
"condition": [
{ "mapsource": ["Remote Service"] }
]
},
{
"name": "mapTilePath",
"label": "map tile path",
"type": "string",
"default": "map.php",
"description": "Path to converter access e.g. index.php or map.php",
"category": "OBP40 Navigation",
"capabilities": {
"obp40": "true"
},
"condition": [
{ "mapsource": ["Remote Service"] }
]
},
{
"name": "maptype",
"label": "Map Type",
@@ -1245,8 +1310,8 @@
"name": "timeSource",
"label": "Status Time Source",
"type": "list",
"default": "iRTC",
"description": "Data source for date and time display in status line [iRTC|RTC|GPS]",
"default": "GPS",
"description": "Data source for date and time display in status line [RTC|iRTC|GPS]",
"list": [
{"l":"Internal real time clock (iRTC)","v":"iRTC"},
{"l":"External real time clock (RTC)","v":"RTC"},
@@ -1517,7 +1582,7 @@
"default": "Voltage",
"description": "Type of page for page 1",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -1848,7 +1913,7 @@
"default": "WindRose",
"description": "Type of page for page 2",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -2170,7 +2235,7 @@
"default": "OneValue",
"description": "Type of page for page 3",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -2483,7 +2548,7 @@
"default": "TwoValues",
"description": "Type of page for page 4",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -2787,7 +2852,7 @@
"default": "ThreeValues",
"description": "Type of page for page 5",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -3082,7 +3147,7 @@
"default": "FourValues",
"description": "Type of page for page 6",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -3368,7 +3433,7 @@
"default": "FourValues2",
"description": "Type of page for page 7",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -3645,7 +3710,7 @@
"default": "Clock",
"description": "Type of page for page 8",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -3913,7 +3978,7 @@
"default": "RollPitch",
"description": "Type of page for page 9",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -4172,7 +4237,7 @@
"default": "Battery2",
"description": "Type of page for page 10",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",

View File

@@ -19,6 +19,28 @@
"obp60": "true"
}
},
{
"name": "mapServer",
"label": "map server",
"type": "string",
"default": "",
"description": "Server for converting map tiles. Use only one hostname or IP address",
"category": "wifi client",
"capabilities": {
"obp40": "true"
}
},
{
"name": "mapTilePath",
"label": "map tile path",
"type": "string",
"default": "map.php",
"description": "Path to converter access e.g. index.php or map.php",
"category": "wifi client",
"capabilities": {
"obp40": "true"
}
},
{
"name": "timeZone",
"label": "Time Zone",
@@ -75,6 +97,20 @@
"obp60":"true"
}
},
{
"name": "chainLength",
"label": "Anchor Chain Length [m]",
"type": "number",
"default": "0",
"check": "checkMinMax",
"min": 0,
"max": 255,
"description": "The length of the anchor chain [0...255m]",
"category": "OBP60 Settings",
"capabilities": {
"obp60":"true"
}
},
{
"name": "fuelTank",
"label": "Fuel Tank [l]",
@@ -1235,9 +1271,8 @@
"label": "Status Time Source",
"type": "list",
"default": "GPS",
"description": "Data source for date and time display in status line [iRTC|RTC|GPS]",
"description": "Data source for date and time display in status line [RTC|GPS]",
"list": [
{"l":"Internal real time clock (iRTC)","v":"iRTC"},
{"l":"Real time clock (RTC)","v":"RTC"},
{"l":"Time via bus (GPS)","v":"GPS"}
],
@@ -1487,7 +1522,6 @@
"obp60":"true"
}
},
{
"name": "page1type",
"label": "Type",
@@ -1495,7 +1529,7 @@
"default": "Voltage",
"description": "Type of page for page 1",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -1796,7 +1830,7 @@
"default": "WindRose",
"description": "Type of page for page 2",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -2089,7 +2123,7 @@
"default": "OneValue",
"description": "Type of page for page 3",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -2374,7 +2408,7 @@
"default": "TwoValues",
"description": "Type of page for page 4",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -2651,7 +2685,7 @@
"default": "ThreeValues",
"description": "Type of page for page 5",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -2920,7 +2954,7 @@
"default": "FourValues",
"description": "Type of page for page 6",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -3181,7 +3215,7 @@
"default": "FourValues2",
"description": "Type of page for page 7",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -3434,7 +3468,7 @@
"default": "Clock",
"description": "Type of page for page 8",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -3679,7 +3713,7 @@
"default": "RollPitch",
"description": "Type of page for page 9",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",
@@ -3916,7 +3950,7 @@
"default": "Battery2",
"description": "Type of page for page 10",
"list": [
"Autopilot",
"Anchor",
"BME280",
"Battery",
"Battery2",

View File

@@ -2,14 +2,6 @@
import subprocess
def cleanup_patches(source, target, env):
for p in patchfiles:
patch = os.path.join(patchdir, p)
print(f"removing {patch}")
res = subprocess.run(["git", "apply", "-R", patch], capture_output=True, text=True)
if res.returncode != 0:
print(res.stderr)
patching = False
epdtype = "unknown"
@@ -54,13 +46,11 @@ if patching:
print("patchdir not found, no patches applied")
else:
patchfiles = [f for f in os.listdir(patchdir)]
if len(patchfiles) > 0:
for p in patchfiles:
patch = os.path.join(patchdir, p)
print(f"applying {patch}")
res = subprocess.run(["git", "apply", patch], capture_output=True, text=True)
if res.returncode != 0:
print(res.stderr)
env.AddPostAction("$PROGPATH", cleanup_patches)
for p in patchfiles:
patch = os.path.join(patchdir, p)
print(f"applying {patch}")
res = subprocess.run(["git", "apply", patch], capture_output=True, text=True)
if res.returncode != 0:
print(res.stderr)
else:
print("no patches found")

View File

@@ -264,6 +264,8 @@ void registerAllPages(PageList &list){
list.add(&registerPageDigitalOut);
extern PageDescription registerPageAutopilot;
list.add(&registerPageAutopilot);
extern PageDescription registerPageAnchor;
list.add(&registerPageAnchor);
}
// Undervoltage detection for shutdown display

View File

@@ -1,103 +0,0 @@
diff --git a/lib/api/GwApi.h b/lib/api/GwApi.h
index 88f9690..9663a65 100644
--- a/lib/api/GwApi.h
+++ b/lib/api/GwApi.h
@@ -2,6 +2,8 @@
#define _GWAPI_H
#include "GwMessage.h"
#include "N2kMsg.h"
+#include "Nmea2kTwai.h"
+#include "N2kDeviceList.h"
#include "NMEA0183Msg.h"
#include "GWConfig.h"
#include "GwBoatData.h"
@@ -222,6 +224,8 @@ class GwApi{
* accessing boat data must only be executed from within the main thread
* you need to use the request pattern as shown in GwExampleTask.cpp
*/
+ virtual Nmea2kTwai *getNMEA2000()=0;
+ virtual tN2kDeviceList *getN2kDeviceList()=0;
virtual GwBoatData *getBoatData()=0;
virtual ~GwApi(){}
};
diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h
index 604c356..2fe4496 100644
--- a/lib/obp60task/OBP60Extensions.h
+++ b/lib/obp60task/OBP60Extensions.h
@@ -15,6 +15,9 @@
#define MOUNT_POINT "/sdcard"
#endif
+// Patches to apply to gateway code
+#define PATCH_N2K
+
// FRAM address reservations 32kB: 0x0000 - 0x7FFF
// 0x0000 - 0x03ff: single variables
#define FRAM_PAGE_NO 0x0002
diff --git a/lib/usercode/GwUserCode.cpp b/lib/usercode/GwUserCode.cpp
index 1b007f8..90087d4 100644
--- a/lib/usercode/GwUserCode.cpp
+++ b/lib/usercode/GwUserCode.cpp
@@ -216,6 +216,14 @@ public:
{
return api->getLogger();
}
+ virtual Nmea2kTwai *getNMEA2000()
+ {
+ return api->getNMEA2000();
+ }
+ virtual tN2kDeviceList *getN2kDeviceList()
+ {
+ return api->getN2kDeviceList();
+ }
virtual GwBoatData *getBoatData()
{
return api->getBoatData();
@@ -428,4 +436,4 @@ void GwUserCode::handleWebRequest(const String &url,AsyncWebServerRequest *req){
}
LOG_DEBUG(GwLog::DEBUG,"no task found for web request %s[%s]",url.c_str(),tname.c_str());
req->send(404, "text/plain", "not found");
-}
\ No newline at end of file
+}
diff --git a/src/main.cpp b/src/main.cpp
index 44c715f..fdb0366 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -100,6 +100,7 @@ GwLog logger(LOGLEVEL,NULL);
GwConfigHandler config(&logger);
#include "Nmea2kTwai.h"
+#include <N2kDeviceList.h>
static const unsigned long CAN_RECOVERY_PERIOD=3000; //ms
static const unsigned long NMEA2000_HEARTBEAT_INTERVAL=5000;
class Nmea2kTwaiLog : public Nmea2kTwai{
@@ -126,6 +127,7 @@ class Nmea2kTwaiLog : public Nmea2kTwai{
#endif
Nmea2kTwai &NMEA2000=*(new Nmea2kTwaiLog((gpio_num_t)ESP32_CAN_TX_PIN,(gpio_num_t)ESP32_CAN_RX_PIN,CAN_RECOVERY_PERIOD,&logger));
+tN2kDeviceList *pN2kDeviceList;
#ifdef GWBUTTON_PIN
bool fixedApPass=false;
@@ -333,6 +335,12 @@ public:
status.n2kTx=countNMEA2KOut.getGlobal();
channels.fillStatus(status);
}
+ virtual Nmea2kTwai *getNMEA2000(){
+ return &NMEA2000;
+ }
+ virtual tN2kDeviceList *getN2kDeviceList(){
+ return pN2kDeviceList;
+ }
virtual GwBoatData *getBoatData(){
return &boatData;
}
@@ -935,6 +943,7 @@ void setup() {
NMEA2000.SetMsgHandler([](const tN2kMsg &n2kMsg){
handleN2kMessage(n2kMsg,N2K_CHANNEL_ID);
});
+ pN2kDeviceList = new tN2kDeviceList(&NMEA2000);
NMEA2000.Open();
logger.logDebug(GwLog::LOG,"starting addon tasks");
logger.flush();

View File

@@ -45,6 +45,8 @@ lib_deps =
milesburton/DallasTemperature@3.11.0
signetica/SunRise@2.0.2
adafruit/Adafruit FRAM I2C@2.0.3
WifiClientSecure
HTTPClient
build_flags=
#https://thingpulse.com/usb-settings-for-logging-with-the-esp32-s3-in-platformio/?srsltid=AfmBOopGskbkr4GoeVkNlFaZXe_zXkLceKF6Rn-tmoXABCeAR2vWsdHL
# -D CORE_DEBUG_LEVEL=1 #Debug level for CPU core via CDC (serial device)
@@ -101,6 +103,8 @@ lib_deps =
milesburton/DallasTemperature@3.11.0
signetica/SunRise@2.0.2
adafruit/Adafruit FRAM I2C@2.0.3
WifiClientSecure
HTTPClient
build_flags=
-D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib
-D BOARD_OBP40S3 #Board OBP40 with ESP32S3