mirror of
https://github.com/thooge/esp32-nmea2000-obp60.git
synced 2026-02-24 20:53:07 +01:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6fd3ef599 | |||
|
|
66e71acac3 | ||
|
|
b85504bf50 | ||
|
|
3043be8e1d | ||
|
|
02b2c888ee | ||
|
|
00b06f458b | ||
|
|
6c7997e369 | ||
|
|
7f747e9b35 | ||
|
|
71512e7262 | ||
|
|
4468c0555b | ||
|
|
99404991a3 | ||
|
|
ee5077e0a5 |
@@ -2,6 +2,9 @@
|
||||
#define _GWWIFI_H
|
||||
#include <WiFi.h>
|
||||
#include <GWConfig.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
class GwWifi{
|
||||
private:
|
||||
const GwConfigHandler *config;
|
||||
@@ -16,13 +19,19 @@ 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();
|
||||
bool connectClient(); // Blocking version
|
||||
bool connectClientAsync(); // Non-blocking version for other tasks
|
||||
String apIP();
|
||||
bool isApActive(){return apActive;}
|
||||
bool isClientActive(){return wifiClient->asBoolean();}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <esp_wifi.h>
|
||||
#include "GWWifi.h"
|
||||
|
||||
|
||||
GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
|
||||
this->config=config;
|
||||
this->logger=log;
|
||||
@@ -9,6 +8,28 @@ 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");
|
||||
@@ -85,8 +106,14 @@ 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;
|
||||
@@ -104,9 +131,21 @@ void GwWifi::loop(){
|
||||
if (lastConnectStart > now || (lastConnectStart + RETRY_MILLIS) < now)
|
||||
{
|
||||
LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString());
|
||||
WiFi.disconnect();
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
if (! clientIsConnected){
|
||||
@@ -126,11 +165,42 @@ void GwWifi::loop(){
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GwWifi::clientConnected(){
|
||||
return WiFi.status() == WL_CONNECTED;
|
||||
// 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;
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include "NetworkClient.h"
|
||||
#include "GWWifi.h" // WiFi management (thread-safe)
|
||||
|
||||
extern GwWifi gwWifi; // Extern declaration of global WiFi instance
|
||||
|
||||
extern "C" {
|
||||
#include "puff.h"
|
||||
@@ -51,8 +54,13 @@ 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 (DEBUG) {Serial.println("Malloc failed (buffer");}
|
||||
if (DEBUGING) {Serial.println("Malloc failed buffer");}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -106,7 +114,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
|
||||
len += read;
|
||||
lastData = millis();
|
||||
|
||||
if (DEBUG) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
|
||||
if (DEBUGING) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
|
||||
|
||||
if (len < 20) continue; // Not enough data for header
|
||||
|
||||
@@ -122,7 +130,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 (DEBUG) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
|
||||
if (DEBUGING) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
|
||||
outData = test;
|
||||
outLen = testLen;
|
||||
complete = true;
|
||||
@@ -167,7 +175,7 @@ bool NetworkClient::fetchAndDecompressJson(const String& url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DEBUG) {Serial.println("JSON OK!");}
|
||||
if (DEBUGING) {Serial.println("JSON OK!");}
|
||||
_valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <WiFi.h>
|
||||
#include <HTTPClient.h>
|
||||
|
||||
#define DEBUG false // Debug flag for NetworkClient for more live information
|
||||
#define DEBUGING 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
|
||||
|
||||
@@ -923,7 +923,7 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor){
|
||||
|
||||
}
|
||||
|
||||
// Generator graphic with fill level
|
||||
// Generator graphic
|
||||
void generatorGraphic(uint x, uint y, int pcolor, int bcolor){
|
||||
// Show battery
|
||||
int xb = x; // X position
|
||||
@@ -940,6 +940,74 @@ 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) {
|
||||
|
||||
@@ -128,6 +128,10 @@ 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
|
||||
|
||||
@@ -371,7 +371,7 @@ void sensorTask(void *param){
|
||||
GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP);
|
||||
GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop};
|
||||
|
||||
// Internal RTC with NTP init
|
||||
// Internal iRTC with NTP init
|
||||
ESP32Time rtc(0);
|
||||
if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") {
|
||||
GwApi::Status status;
|
||||
@@ -432,13 +432,13 @@ 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
|
||||
|
||||
*/
|
||||
|
||||
@@ -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, 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+1, 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,25 +533,26 @@ void sensorTask(void *param){
|
||||
}
|
||||
// Send date and time from software RTC (iRTC)
|
||||
if (iRTC_ready == true && RTC_ready == false && GPS_ready == false) {
|
||||
// Use internal RTC feature
|
||||
sensors.rtcTime = rtc.getTimeStruct(); // Save software RTC values in SensorData
|
||||
// TODO implement daysAt1970 and sysTime as methods of DateTime
|
||||
sensors.rtcTime = rtc.getTimeStruct();
|
||||
|
||||
const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334};
|
||||
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))) {
|
||||
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))) {
|
||||
daysAt1970 += 1;
|
||||
}
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Send 1Wire data for all temperature sensors to N2K all 2s
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
|
||||
// These constants have to match the declaration below in :
|
||||
// PageDescription registerPageAutopilot(
|
||||
// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page
|
||||
const int HowManyValues = 9;
|
||||
// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page
|
||||
|
||||
const int HowManyValues = 11;
|
||||
|
||||
const int AverageValues = 4;
|
||||
|
||||
@@ -19,10 +20,13 @@ 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 = 220; // Y position of compass lines
|
||||
const int Compass_LineLength = 22; // Length of compass lines
|
||||
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 float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
|
||||
|
||||
class PageAutopilot : public Page
|
||||
@@ -38,8 +42,11 @@ class PageAutopilot : public Page
|
||||
|
||||
virtual void setupKeys(){
|
||||
Page::setupKeys();
|
||||
commonData->keydata[0].label = "CMP";
|
||||
commonData->keydata[1].label = "SRC";
|
||||
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";
|
||||
}
|
||||
|
||||
virtual int handleKey(int key){
|
||||
@@ -69,8 +76,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);
|
||||
@@ -107,14 +114,12 @@ class PageAutopilot : public Page
|
||||
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);
|
||||
@@ -138,7 +143,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];
|
||||
@@ -152,13 +157,13 @@ class PageAutopilot : public Page
|
||||
buffer[0]=0;
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
||||
getdisplay().setCursor(10, Compass_Y0-60);
|
||||
getdisplay().setCursor(10, Compass_Y0-40);
|
||||
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-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);
|
||||
// Draw trendlines
|
||||
for ( int i = 1; i < abs(TheTrend) / 2; i++){
|
||||
int x1;
|
||||
@@ -238,6 +243,8 @@ class PageAutopilot : public Page
|
||||
// if ( x_test > 390)
|
||||
// x_test = 320;
|
||||
|
||||
displayRudderPosition(DataValue[ShowSOG], 20, 200, 160, commonData->fgcolor, commonData->bgcolor);
|
||||
|
||||
return PAGE_UPDATE;
|
||||
};
|
||||
|
||||
@@ -256,7 +263,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"}, // Bus values we need in the page
|
||||
{"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page
|
||||
true // Show display header on/off
|
||||
);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "images/logo64.xbm"
|
||||
#include <esp32/clk.h>
|
||||
#include "qrcode.h"
|
||||
#include <vector>
|
||||
|
||||
#ifdef BOARD_OBP40S3
|
||||
#include "dirent.h"
|
||||
@@ -40,6 +41,7 @@ private:
|
||||
String rtc_module;
|
||||
String gps_module;
|
||||
String env_module;
|
||||
String flashLED;
|
||||
|
||||
String batt_sensor;
|
||||
String solar_sensor;
|
||||
@@ -50,6 +52,17 @@ 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;
|
||||
@@ -76,6 +89,7 @@ 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() {
|
||||
@@ -168,18 +182,42 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
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"){
|
||||
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;
|
||||
|
||||
// Logging boat values
|
||||
logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
|
||||
@@ -461,12 +499,54 @@ public:
|
||||
getdisplay().print("NMEA2000 device list");
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
getdisplay().setCursor(20, 80);
|
||||
getdisplay().setCursor(20, 70);
|
||||
getdisplay().print("RxD: ");
|
||||
getdisplay().print(String(commonData->status.n2kRx));
|
||||
getdisplay().setCursor(20, 100);
|
||||
getdisplay().setCursor(120, 70);
|
||||
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
|
||||
|
||||
@@ -1245,8 +1245,8 @@
|
||||
"name": "timeSource",
|
||||
"label": "Status Time Source",
|
||||
"type": "list",
|
||||
"default": "GPS",
|
||||
"description": "Data source for date and time display in status line [RTC|iRTC|GPS]",
|
||||
"default": "iRTC",
|
||||
"description": "Data source for date and time display in status line [iRTC|RTC|GPS]",
|
||||
"list": [
|
||||
{"l":"Internal real time clock (iRTC)","v":"iRTC"},
|
||||
{"l":"External real time clock (RTC)","v":"RTC"},
|
||||
@@ -1517,6 +1517,7 @@
|
||||
"default": "Voltage",
|
||||
"description": "Type of page for page 1",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -1847,6 +1848,7 @@
|
||||
"default": "WindRose",
|
||||
"description": "Type of page for page 2",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2168,6 +2170,7 @@
|
||||
"default": "OneValue",
|
||||
"description": "Type of page for page 3",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2480,6 +2483,7 @@
|
||||
"default": "TwoValues",
|
||||
"description": "Type of page for page 4",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2783,6 +2787,7 @@
|
||||
"default": "ThreeValues",
|
||||
"description": "Type of page for page 5",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3077,6 +3082,7 @@
|
||||
"default": "FourValues",
|
||||
"description": "Type of page for page 6",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3362,6 +3368,7 @@
|
||||
"default": "FourValues2",
|
||||
"description": "Type of page for page 7",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3638,6 +3645,7 @@
|
||||
"default": "Clock",
|
||||
"description": "Type of page for page 8",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3905,6 +3913,7 @@
|
||||
"default": "RollPitch",
|
||||
"description": "Type of page for page 9",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -4163,6 +4172,7 @@
|
||||
"default": "Battery2",
|
||||
"description": "Type of page for page 10",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
|
||||
@@ -1235,8 +1235,9 @@
|
||||
"label": "Status Time Source",
|
||||
"type": "list",
|
||||
"default": "GPS",
|
||||
"description": "Data source for date and time display in status line [RTC|GPS]",
|
||||
"description": "Data source for date and time display in status line [iRTC|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"}
|
||||
],
|
||||
@@ -1494,6 +1495,7 @@
|
||||
"default": "Voltage",
|
||||
"description": "Type of page for page 1",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -1794,6 +1796,7 @@
|
||||
"default": "WindRose",
|
||||
"description": "Type of page for page 2",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2086,6 +2089,7 @@
|
||||
"default": "OneValue",
|
||||
"description": "Type of page for page 3",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2370,6 +2374,7 @@
|
||||
"default": "TwoValues",
|
||||
"description": "Type of page for page 4",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2646,6 +2651,7 @@
|
||||
"default": "ThreeValues",
|
||||
"description": "Type of page for page 5",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2914,6 +2920,7 @@
|
||||
"default": "FourValues",
|
||||
"description": "Type of page for page 6",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3174,6 +3181,7 @@
|
||||
"default": "FourValues2",
|
||||
"description": "Type of page for page 7",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3426,6 +3434,7 @@
|
||||
"default": "Clock",
|
||||
"description": "Type of page for page 8",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3670,6 +3679,7 @@
|
||||
"default": "RollPitch",
|
||||
"description": "Type of page for page 9",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3906,6 +3916,7 @@
|
||||
"default": "Battery2",
|
||||
"description": "Type of page for page 10",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
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"
|
||||
@@ -46,11 +54,13 @@ 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)
|
||||
else:
|
||||
print("no patches found")
|
||||
|
||||
103
lib/obp60task/patches/01-nmea2000.patch
Normal file
103
lib/obp60task/patches/01-nmea2000.patch
Normal file
@@ -0,0 +1,103 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user