Merge branch 'wellenvogel:master' into master

This commit is contained in:
Norbert Walter 2024-10-06 11:56:54 +02:00 committed by GitHub
commit 6a4d6229aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 260 additions and 130 deletions

View File

@ -1,6 +1,7 @@
#include "GwBoatData.h" #include "GwBoatData.h"
#include <GwJsonDocument.h> #include <GwJsonDocument.h>
#include <ArduinoJson/Json/TextFormatter.hpp> #include <ArduinoJson/Json/TextFormatter.hpp>
#include "GWConfig.h"
#define GWTYPE_DOUBLE 1 #define GWTYPE_DOUBLE 1
#define GWTYPE_UINT32 2 #define GWTYPE_UINT32 2
#define GWTYPE_UINT16 3 #define GWTYPE_UINT16 3
@ -35,6 +36,23 @@ GwBoatItemBase::GwBoatItemBase(String name, String format, unsigned long invalid
this->format = format; this->format = format;
this->type = 0; this->type = 0;
this->lastUpdateSource = -1; this->lastUpdateSource = -1;
this->toType=TOType::user;
}
GwBoatItemBase::GwBoatItemBase(String name, String format, GwBoatItemBase::TOType toType)
{
lastSet = 0;
this->invalidTime = INVALID_TIME;
this->toType=toType;
this->name = name;
this->format = format;
this->type = 0;
this->lastUpdateSource = -1;
this->toType=TOType::user;
}
void GwBoatItemBase::setInvalidTime(unsigned long it, bool force){
if (toType != TOType::user || force ){
invalidTime=it;
}
} }
size_t GwBoatItemBase::getJsonSize() { return JSON_OBJECT_SIZE(10); } size_t GwBoatItemBase::getJsonSize() { return JSON_OBJECT_SIZE(10); }
#define STRING_SIZE 40 #define STRING_SIZE 40
@ -113,6 +131,16 @@ GwBoatItem<T>::GwBoatItem(String name, String formatInfo, unsigned long invalidT
(*map)[name] = this; (*map)[name] = this;
} }
} }
template <class T>
GwBoatItem<T>::GwBoatItem(String name, String formatInfo, GwBoatItemBase::TOType toType, GwBoatItemMap *map) : GwBoatItemBase(name, formatInfo, toType)
{
T dummy;
this->type = GwBoatItemTypes::getType(dummy);
if (map)
{
(*map)[name] = this;
}
}
template <class T> template <class T>
bool GwBoatItem<T>::update(T nv, int source) bool GwBoatItem<T>::update(T nv, int source)
@ -246,14 +274,13 @@ void GwSatInfoList::houseKeeping(unsigned long ts)
sats.end(), sats.end(),
[ts, this](const GwSatInfo &info) [ts, this](const GwSatInfo &info)
{ {
return (info.timestamp + lifeTime) < ts; return info.validTill < ts;
}), }),
sats.end()); sats.end());
} }
void GwSatInfoList::update(GwSatInfo entry) void GwSatInfoList::update(GwSatInfo entry, unsigned long validTill)
{ {
unsigned long now = millis(); entry.validTill = validTill;
entry.timestamp = now;
for (auto it = sats.begin(); it != sats.end(); it++) for (auto it = sats.begin(); it != sats.end(); it++)
{ {
if (it->PRN == entry.PRN) if (it->PRN == entry.PRN)
@ -267,7 +294,7 @@ void GwSatInfoList::update(GwSatInfo entry)
sats.push_back(entry); sats.push_back(entry);
} }
GwBoatDataSatList::GwBoatDataSatList(String name, String formatInfo, unsigned long invalidTime, GwBoatItemMap *map) : GwBoatItem<GwSatInfoList>(name, formatInfo, invalidTime, map) {} GwBoatDataSatList::GwBoatDataSatList(String name, String formatInfo, GwBoatItemBase::TOType toType, GwBoatItemMap *map) : GwBoatItem<GwSatInfoList>(name, formatInfo, toType, map) {}
bool GwBoatDataSatList::update(GwSatInfo info, int source) bool GwBoatDataSatList::update(GwSatInfo info, int source)
{ {
@ -284,7 +311,7 @@ bool GwBoatDataSatList::update(GwSatInfo info, int source)
} }
lastUpdateSource = source; lastUpdateSource = source;
uls(now); uls(now);
data.update(info); data.update(info,now+invalidTime);
return true; return true;
} }
void GwBoatDataSatList::toJsonDoc(GwJsonDocument *doc, unsigned long minTime) void GwBoatDataSatList::toJsonDoc(GwJsonDocument *doc, unsigned long minTime)
@ -293,9 +320,31 @@ void GwBoatDataSatList::toJsonDoc(GwJsonDocument *doc, unsigned long minTime)
GwBoatItem<GwSatInfoList>::toJsonDoc(doc, minTime); GwBoatItem<GwSatInfoList>::toJsonDoc(doc, minTime);
} }
GwBoatData::GwBoatData(GwLog *logger) GwBoatData::GwBoatData(GwLog *logger, GwConfigHandler *cfg)
{ {
this->logger = logger; this->logger = logger;
for (auto &&it : values){
unsigned long timeout=GwBoatItemBase::INVALID_TIME;
switch(it.second->getToType()){
case GwBoatItemBase::TOType::ais:
timeout=cfg->getInt(GwConfigDefinitions::timoAis);
break;
case GwBoatItemBase::TOType::def:
timeout=cfg->getInt(GwConfigDefinitions::timoDefault);
break;
case GwBoatItemBase::TOType::lng:
timeout=cfg->getInt(GwConfigDefinitions::timoLong);
break;
case GwBoatItemBase::TOType::sensor:
timeout=cfg->getInt(GwConfigDefinitions::timoSensor);
break;
case GwBoatItemBase::TOType::keep:
timeout=0;
break;
}
it.second->setInvalidTime(timeout);
}
} }
GwBoatData::~GwBoatData() GwBoatData::~GwBoatData()
{ {

View File

@ -2,6 +2,7 @@
#define _GWBOATDATA_H #define _GWBOATDATA_H
#include "GwLog.h" #include "GwLog.h"
#include "GWConfig.h"
#include <Arduino.h> #include <Arduino.h>
#include <map> #include <map>
#include <vector> #include <vector>
@ -15,6 +16,14 @@
class GwJsonDocument; class GwJsonDocument;
class GwBoatItemBase{ class GwBoatItemBase{
public: public:
using TOType=enum{
def=1,
ais=2,
sensor=3,
lng=4,
user=5,
keep=6
};
class StringWriter{ class StringWriter{
uint8_t *buffer =NULL; uint8_t *buffer =NULL;
uint8_t *wp=NULL; uint8_t *wp=NULL;
@ -31,7 +40,7 @@ class GwBoatItemBase{
bool baseFilled(); bool baseFilled();
void reset(); void reset();
}; };
static const unsigned long INVALID_TIME=60000; static const long INVALID_TIME=60000;
//the formatter names that must be known in js //the formatter names that must be known in js
GWSC(formatCourse); GWSC(formatCourse);
GWSC(formatKnots); GWSC(formatKnots);
@ -51,10 +60,11 @@ class GwBoatItemBase{
protected: protected:
int type; int type;
unsigned long lastSet=0; unsigned long lastSet=0;
unsigned long invalidTime=INVALID_TIME; long invalidTime=INVALID_TIME;
String name; String name;
String format; String format;
StringWriter writer; StringWriter writer;
TOType toType=TOType::def;
void uls(unsigned long ts=0){ void uls(unsigned long ts=0){
if (ts) lastSet=ts; if (ts) lastSet=ts;
else lastSet=millis(); else lastSet=millis();
@ -65,7 +75,8 @@ class GwBoatItemBase{
int getCurrentType(){return type;} int getCurrentType(){return type;}
unsigned long getLastSet() const {return lastSet;} unsigned long getLastSet() const {return lastSet;}
bool isValid(unsigned long now=0) const ; bool isValid(unsigned long now=0) const ;
GwBoatItemBase(String name,String format,unsigned long invalidTime=INVALID_TIME); GwBoatItemBase(String name,String format,TOType toType);
GwBoatItemBase(String name,String format,unsigned long invalidTime);
virtual ~GwBoatItemBase(){} virtual ~GwBoatItemBase(){}
void invalidate(){ void invalidate(){
lastSet=0; lastSet=0;
@ -82,6 +93,8 @@ class GwBoatItemBase{
virtual double getDoubleValue()=0; virtual double getDoubleValue()=0;
String getName(){return name;} String getName(){return name;}
const String & getFormat() const{return format;} const String & getFormat() const{return format;}
virtual void setInvalidTime(unsigned long it, bool force=true);
TOType getToType(){return toType;}
}; };
class GwBoatData; class GwBoatData;
template<class T> class GwBoatItem : public GwBoatItemBase{ template<class T> class GwBoatItem : public GwBoatItemBase{
@ -90,6 +103,7 @@ template<class T> class GwBoatItem : public GwBoatItemBase{
bool lastStringValid=false; bool lastStringValid=false;
public: public:
GwBoatItem(String name,String formatInfo,unsigned long invalidTime=INVALID_TIME,GwBoatItemMap *map=NULL); GwBoatItem(String name,String formatInfo,unsigned long invalidTime=INVALID_TIME,GwBoatItemMap *map=NULL);
GwBoatItem(String name,String formatInfo,TOType toType,GwBoatItemMap *map=NULL);
virtual ~GwBoatItem(){} virtual ~GwBoatItem(){}
bool update(T nv, int source=-1); bool update(T nv, int source=-1);
bool updateMax(T nv,int sourceId=-1); bool updateMax(T nv,int sourceId=-1);
@ -118,14 +132,14 @@ class GwSatInfo{
uint32_t Elevation; uint32_t Elevation;
uint32_t Azimut; uint32_t Azimut;
uint32_t SNR; uint32_t SNR;
unsigned long timestamp; unsigned long validTill;
}; };
class GwSatInfoList{ class GwSatInfoList{
public: public:
static const unsigned long lifeTime=32000; static const GwBoatItemBase::TOType toType=GwBoatItemBase::TOType::lng;
std::vector<GwSatInfo> sats; std::vector<GwSatInfo> sats;
void houseKeeping(unsigned long ts=0); void houseKeeping(unsigned long ts=0);
void update(GwSatInfo entry); void update(GwSatInfo entry, unsigned long validTill);
int getNumSats() const{ int getNumSats() const{
return sats.size(); return sats.size();
} }
@ -139,7 +153,7 @@ class GwSatInfoList{
class GwBoatDataSatList : public GwBoatItem<GwSatInfoList> class GwBoatDataSatList : public GwBoatItem<GwSatInfoList>
{ {
public: public:
GwBoatDataSatList(String name, String formatInfo, unsigned long invalidTime = INVALID_TIME, GwBoatItemMap *map = NULL); GwBoatDataSatList(String name, String formatInfo, GwBoatItemBase::TOType toType, GwBoatItemMap *map = NULL);
bool update(GwSatInfo info, int source); bool update(GwSatInfo info, int source);
virtual void toJsonDoc(GwJsonDocument *doc, unsigned long minTime); virtual void toJsonDoc(GwJsonDocument *doc, unsigned long minTime);
GwSatInfo *getAt(int idx){ GwSatInfo *getAt(int idx){
@ -164,55 +178,59 @@ public:
virtual unsigned long getInvalidTime(){ return GwBoatItemBase::INVALID_TIME;} virtual unsigned long getInvalidTime(){ return GwBoatItemBase::INVALID_TIME;}
virtual ~GwBoatItemNameProvider() {} virtual ~GwBoatItemNameProvider() {}
}; };
#define GWBOATDATA(type,name,time,fmt) \ #define GWBOATDATAT(type,name,toType,fmt) \
static constexpr const char* _##name=#name; \ static constexpr const char* _##name=#name; \
GwBoatItem<type> *name=new GwBoatItem<type>(#name,GwBoatItemBase::fmt,time,&values) ; GwBoatItem<type> *name=new GwBoatItem<type>(#name,GwBoatItemBase::fmt,toType,&values) ;
#define GWSPECBOATDATA(clazz,name,time,fmt) \ #define GWBOATDATA(type,name,fmt) GWBOATDATAT(type,name,GwBoatItemBase::TOType::def,fmt)
clazz *name=new clazz(#name,GwBoatItemBase::fmt,time,&values) ; #define GWSPECBOATDATA(clazz,name,toType,fmt) \
clazz *name=new clazz(#name,GwBoatItemBase::fmt,toType,&values) ;
class GwBoatData{ class GwBoatData{
static const unsigned long DEF_TIME=4000;
private: private:
GwLog *logger; GwLog *logger;
GwBoatItemBase::GwBoatItemMap values; GwBoatItemBase::GwBoatItemMap values;
public: public:
GWBOATDATA(double,COG,4000,formatCourse) GWBOATDATA(double,COG,formatCourse) // course over ground
GWBOATDATA(double,TWD,4000,formatCourse) GWBOATDATA(double,SOG,formatKnots) // speed over ground
GWBOATDATA(double,SOG,4000,formatKnots) GWBOATDATA(double,HDT,formatCourse) // true heading
GWBOATDATA(double,STW,4000,formatKnots) GWBOATDATA(double,HDM,formatCourse) // magnetic heading
GWBOATDATA(double,TWS,4000,formatKnots) GWBOATDATA(double,STW,formatKnots) // water speed
GWBOATDATA(double,AWS,4000,formatKnots) GWBOATDATA(double,VAR,formatWind) // variation
GWBOATDATA(double,MaxTws,0,formatKnots) GWBOATDATA(double,DEV,formatWind) // deviation
GWBOATDATA(double,MaxAws,0,formatKnots) GWBOATDATA(double,AWA,formatWind) // apparent wind ANGLE
GWBOATDATA(double,AWA,4000,formatWind) GWBOATDATA(double,AWS,formatKnots) // apparent wind speed
GWBOATDATA(double,HDG,4000,formatCourse) //true heading GWBOATDATAT(double,MaxAws,GwBoatItemBase::TOType::keep,formatKnots)
GWBOATDATA(double,MHDG,4000,formatCourse) //magnetic heading GWBOATDATA(double,TWD,formatCourse) // true wind DIRECTION
GWBOATDATA(double,ROT,4000,formatRot) GWBOATDATA(double,TWA,formatWind) // true wind ANGLE
GWBOATDATA(double,VAR,4000,formatCourse) //Variation GWBOATDATA(double,TWS,formatKnots) // true wind speed
GWBOATDATA(double,DEV,4000,formatCourse) //Deviation
GWBOATDATA(double,HDOP,4000,formatDop) GWBOATDATAT(double,MaxTws,GwBoatItemBase::TOType::keep,formatKnots)
GWBOATDATA(double,PDOP,4000,formatDop) GWBOATDATA(double,ROT,formatRot) // rate of turn
GWBOATDATA(double,VDOP,4000,formatDop) GWBOATDATA(double,RPOS,formatWind) // rudder position
GWBOATDATA(double,RPOS,4000,formatWind) //RudderPosition GWBOATDATA(double,PRPOS,formatWind) // secondary rudder position
GWBOATDATA(double,PRPOS,4000,formatWind) //second rudder pos GWBOATDATA(double,LAT,formatLatitude)
GWBOATDATA(double,LAT,4000,formatLatitude) GWBOATDATA(double,LON,formatLongitude)
GWBOATDATA(double,LON,4000,formatLongitude) GWBOATDATA(double,ALT,formatFixed0) //altitude
GWBOATDATA(double,ALT,4000,formatFixed0) //altitude GWBOATDATA(double,HDOP,formatDop)
GWBOATDATA(double,DBS,4000,formatDepth) //waterDepth (below surface) GWBOATDATA(double,PDOP,formatDop)
GWBOATDATA(double,DBT,4000,formatDepth) //DepthTransducer GWBOATDATA(double,VDOP,formatDop)
GWBOATDATA(double,GPST,4000,formatTime) //GpsTime GWBOATDATA(double,DBS,formatDepth) //waterDepth (below surface)
GWBOATDATA(double,WTemp,4000,kelvinToC) GWBOATDATA(double,DBT,formatDepth) //DepthTransducer
GWBOATDATA(double,XTE,4000,formatXte) GWBOATDATA(double,GPST,formatTime) // GPS time (seconds of day)
GWBOATDATA(double,DTW,4000,mtr2nm) //distance wp GWBOATDATA(uint32_t,GPSD,formatDate) // GPS date (days since 1979-01-01)
GWBOATDATA(double,BTW,4000,formatCourse) //bearing wp GWBOATDATAT(int16_t,TZ,GwBoatItemBase::TOType::lng,formatFixed0)
GWBOATDATA(double,WPLat,4000,formatLatitude) GWBOATDATA(double,WTemp,kelvinToC)
GWBOATDATA(double,WPLon,4000,formatLongitude) GWBOATDATAT(uint32_t,Log,GwBoatItemBase::TOType::lng,mtr2nm)
GWBOATDATA(uint32_t,Log,16000,mtr2nm) GWBOATDATAT(uint32_t,TripLog,GwBoatItemBase::TOType::lng,mtr2nm)
GWBOATDATA(uint32_t,TripLog,16000,mtr2nm) GWBOATDATA(double,DTW,mtr2nm) // distance to waypoint
GWBOATDATA(uint32_t,GPSD,4000,formatDate) //Date GWBOATDATA(double,BTW,formatCourse) // bearing to waypoint
GWBOATDATA(int16_t,TZ,8000,formatFixed0) GWBOATDATA(double,XTE,formatXte) // cross track error
GWSPECBOATDATA(GwBoatDataSatList,SatInfo,GwSatInfoList::lifeTime,formatFixed0); GWBOATDATA(double,WPLat,formatLatitude) // waypoint latitude
GWBOATDATA(double,WPLon,formatLongitude) // waypoint longitude
GWSPECBOATDATA(GwBoatDataSatList,SatInfo,GwSatInfoList::toType,formatFixed0);
public: public:
GwBoatData(GwLog *logger); GwBoatData(GwLog *logger, GwConfigHandler *cfg);
~GwBoatData(); ~GwBoatData();
template<class T> GwBoatItem<T> *getOrCreate(T initial,GwBoatItemNameProvider *provider); template<class T> GwBoatItem<T> *getOrCreate(T initial,GwBoatItemNameProvider *provider);
template<class T> bool update(T value,int source,GwBoatItemNameProvider *provider); template<class T> bool update(T value,int source,GwBoatItemNameProvider *provider);

View File

@ -399,20 +399,20 @@ private:
return; return;
} }
tN2kMsg n2kMsg; tN2kMsg n2kMsg;
tN2kWindReference n2kRef; tN2kWindReference n2kRef;
bool shouldSend=false; bool shouldSend=false;
WindAngle=formatDegToRad(WindAngle); WindAngle=formatDegToRad(WindAngle);
switch(Reference){ switch(Reference){
case NMEA0183Wind_Apparent: case NMEA0183Wind_Apparent:
n2kRef=N2kWind_Apparent; n2kRef=N2kWind_Apparent;
shouldSend=updateDouble(boatData->AWA,WindAngle,msg.sourceId) && shouldSend=updateDouble(boatData->AWA,WindAngle,msg.sourceId) &&
updateDouble(boatData->AWS,WindSpeed,msg.sourceId); updateDouble(boatData->AWS,WindSpeed,msg.sourceId);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxAws->updateMax(WindSpeed); if (WindSpeed != NMEA0183DoubleNA) boatData->MaxAws->updateMax(WindSpeed);
break; break;
case NMEA0183Wind_True: case NMEA0183Wind_True:
n2kRef=N2kWind_True_North; n2kRef=N2kWind_True_water;
shouldSend=updateDouble(boatData->TWD,WindAngle,msg.sourceId) && shouldSend=updateDouble(boatData->TWA,WindAngle,msg.sourceId) &&
updateDouble(boatData->TWS,WindSpeed,msg.sourceId); updateDouble(boatData->TWS,WindSpeed,msg.sourceId);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed); if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed);
break; break;
default: default:
@ -467,8 +467,7 @@ private:
void convertMWD(const SNMEA0183Msg &msg) void convertMWD(const SNMEA0183Msg &msg)
{ {
double WindAngle = NMEA0183DoubleNA, WindAngleMagnetic=NMEA0183DoubleNA, double WindDirection = NMEA0183DoubleNA, WindDirectionMagnetic=NMEA0183DoubleNA, WindSpeed = NMEA0183DoubleNA;
WindSpeed = NMEA0183DoubleNA;
if (msg.FieldCount() < 8 ) if (msg.FieldCount() < 8 )
{ {
LOG_DEBUG(GwLog::DEBUG, "failed to parse MWD %s", msg.line); LOG_DEBUG(GwLog::DEBUG, "failed to parse MWD %s", msg.line);
@ -476,11 +475,11 @@ private:
} }
if (msg.FieldLen(0) > 0 && msg.Field(1)[0] == 'T') if (msg.FieldLen(0) > 0 && msg.Field(1)[0] == 'T')
{ {
WindAngle = formatDegToRad(atof(msg.Field(0))); WindDirection = formatDegToRad(atof(msg.Field(0)));
} }
if (msg.FieldLen(2) > 0 && msg.Field(3)[0] == 'M') if (msg.FieldLen(2) > 0 && msg.Field(3)[0] == 'M')
{ {
WindAngleMagnetic = formatDegToRad(atof(msg.Field(2))); WindDirectionMagnetic = formatDegToRad(atof(msg.Field(2)));
} }
if (msg.FieldLen(4) > 0 && msg.Field(5)[0] == 'N') if (msg.FieldLen(4) > 0 && msg.Field(5)[0] == 'N')
{ {
@ -497,32 +496,30 @@ private:
} }
tN2kMsg n2kMsg; tN2kMsg n2kMsg;
bool shouldSend = false; bool shouldSend = false;
if (WindAngle != NMEA0183DoubleNA){ if (WindDirection != NMEA0183DoubleNA){
shouldSend = updateDouble(boatData->TWD, WindAngle, msg.sourceId) && shouldSend = updateDouble(boatData->TWD, WindDirection, msg.sourceId) &&
updateDouble(boatData->TWS, WindSpeed, msg.sourceId); updateDouble(boatData->TWS, WindSpeed, msg.sourceId);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed); if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed);
} if(shouldSend && boatData->HDT->isValid()) {
if (shouldSend) double twa = WindDirection-boatData->HDT->getData();
{ if(twa<0) { twa+=2*M_PI; }
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_True_North); updateDouble(boatData->TWA, twa, msg.sourceId);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)N2kWind_True_North)); SetN2kWindSpeed(n2kMsg, 1, WindSpeed, twa, N2kWind_True_water);
} send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)N2kWind_True_water));
if (WindAngleMagnetic != NMEA0183DoubleNA && shouldSend){ }
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngleMagnetic, N2kWind_Magnetic);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)N2kWind_Magnetic));
} }
} }
void convertHDM(const SNMEA0183Msg &msg){ void convertHDM(const SNMEA0183Msg &msg){
double MHDG=NMEA0183DoubleNA; double HDM=NMEA0183DoubleNA;
if (!NMEA0183ParseHDM_nc(msg, MHDG)) if (!NMEA0183ParseHDM_nc(msg, HDM))
{ {
LOG_DEBUG(GwLog::DEBUG, "failed to parse HDM %s", msg.line); LOG_DEBUG(GwLog::DEBUG, "failed to parse HDM %s", msg.line);
return; return;
} }
if (! UD(MHDG)) return; if (! UD(HDM)) return;
tN2kMsg n2kMsg; tN2kMsg n2kMsg;
SetN2kMagneticHeading(n2kMsg,1,MHDG, SetN2kMagneticHeading(n2kMsg,1,HDM,
boatData->VAR->getDataWithDefault(N2kDoubleNA), boatData->VAR->getDataWithDefault(N2kDoubleNA),
boatData->DEV->getDataWithDefault(N2kDoubleNA) boatData->DEV->getDataWithDefault(N2kDoubleNA)
); );
@ -530,28 +527,29 @@ private:
} }
void convertHDT(const SNMEA0183Msg &msg){ void convertHDT(const SNMEA0183Msg &msg){
double HDG=NMEA0183DoubleNA; double HDT=NMEA0183DoubleNA;
if (!NMEA0183ParseHDT_nc(msg, HDG)) if (!NMEA0183ParseHDT_nc(msg, HDT))
{ {
LOG_DEBUG(GwLog::DEBUG, "failed to parse HDT %s", msg.line); LOG_DEBUG(GwLog::DEBUG, "failed to parse HDT %s", msg.line);
return; return;
} }
if (! UD(HDG)) return; if (! UD(HDT)) return;
tN2kMsg n2kMsg; tN2kMsg n2kMsg;
SetN2kTrueHeading(n2kMsg,1,HDG); SetN2kTrueHeading(n2kMsg,1,HDT);
send(n2kMsg,msg.sourceId); send(n2kMsg,msg.sourceId);
} }
void convertHDG(const SNMEA0183Msg &msg){ void convertHDG(const SNMEA0183Msg &msg){
double MHDG=NMEA0183DoubleNA; double HDM=NMEA0183DoubleNA;
double VAR=NMEA0183DoubleNA;
double DEV=NMEA0183DoubleNA; double DEV=NMEA0183DoubleNA;
double VAR=NMEA0183DoubleNA;
if (msg.FieldCount() < 5) if (msg.FieldCount() < 5)
{ {
LOG_DEBUG(GwLog::DEBUG, "failed to parse HDG %s", msg.line); LOG_DEBUG(GwLog::DEBUG, "failed to parse HDG %s", msg.line);
return; return;
} }
if (msg.FieldLen(0)>0){ if (msg.FieldLen(0)>0){
MHDG=formatDegToRad(atof(msg.Field(0))); HDM=formatDegToRad(atof(msg.Field(0)));
} }
else{ else{
return; return;
@ -565,11 +563,11 @@ private:
if (msg.Field(4)[0] == 'W') VAR=-VAR; if (msg.Field(4)[0] == 'W') VAR=-VAR;
} }
if (! UD(MHDG)) return; if (! UD(HDM)) return;
UD(VAR); UD(VAR);
UD(DEV); UD(DEV);
tN2kMsg n2kMsg; tN2kMsg n2kMsg;
SetN2kMagneticHeading(n2kMsg,1,MHDG,DEV,VAR); SetN2kMagneticHeading(n2kMsg,1,HDM,DEV,VAR);
send(n2kMsg,msg.sourceId,"127250M"); send(n2kMsg,msg.sourceId,"127250M");
} }
@ -705,11 +703,11 @@ private:
return; return;
} }
tN2kMsg n2kMsg; tN2kMsg n2kMsg;
if (updateDouble(boatData->HDG,TrueHeading,msg.sourceId)){ if (updateDouble(boatData->HDT,TrueHeading,msg.sourceId)){
SetN2kTrueHeading(n2kMsg,1,TrueHeading); SetN2kTrueHeading(n2kMsg,1,TrueHeading);
send(n2kMsg,msg.sourceId); send(n2kMsg,msg.sourceId);
} }
if(updateDouble(boatData->MHDG,MagneticHeading,msg.sourceId)){ if(updateDouble(boatData->HDM,MagneticHeading,msg.sourceId)){
SetN2kMagneticHeading(n2kMsg,1,MagneticHeading, SetN2kMagneticHeading(n2kMsg,1,MagneticHeading,
boatData->DEV->getDataWithDefault(N2kDoubleNA), boatData->DEV->getDataWithDefault(N2kDoubleNA),
boatData->VAR->getDataWithDefault(N2kDoubleNA) boatData->VAR->getDataWithDefault(N2kDoubleNA)

View File

@ -185,13 +185,13 @@ private:
if (N2kIsNA(Variation)){ if (N2kIsNA(Variation)){
//no variation //no variation
if (ref == N2khr_magnetic){ if (ref == N2khr_magnetic){
updateDouble(boatData->MHDG,Heading); updateDouble(boatData->HDM,Heading);
if (NMEA0183SetHDM(NMEA0183Msg,Heading,talkerId)){ if (NMEA0183SetHDM(NMEA0183Msg,Heading,talkerId)){
SendMessage(NMEA0183Msg); SendMessage(NMEA0183Msg);
} }
} }
if (ref == N2khr_true){ if (ref == N2khr_true){
updateDouble(boatData->HDG,Heading); updateDouble(boatData->HDT,Heading);
if (NMEA0183SetHDT(NMEA0183Msg,Heading,talkerId)){ if (NMEA0183SetHDT(NMEA0183Msg,Heading,talkerId)){
SendMessage(NMEA0183Msg); SendMessage(NMEA0183Msg);
} }
@ -206,8 +206,8 @@ private:
if (ref == N2khr_true){ if (ref == N2khr_true){
MagneticHeading=Heading-Variation; MagneticHeading=Heading-Variation;
} }
updateDouble(boatData->MHDG,MagneticHeading); updateDouble(boatData->HDM,MagneticHeading);
updateDouble(boatData->HDG,Heading); updateDouble(boatData->HDT,Heading);
if (!N2kIsNA(MagneticHeading)){ if (!N2kIsNA(MagneticHeading)){
if (NMEA0183SetHDG(NMEA0183Msg, MagneticHeading,_Deviation, if (NMEA0183SetHDG(NMEA0183Msg, MagneticHeading,_Deviation,
Variation,talkerId)) Variation,talkerId))
@ -252,8 +252,8 @@ private:
tNMEA0183Msg NMEA0183Msg; tNMEA0183Msg NMEA0183Msg;
updateDouble(boatData->STW, WaterReferenced); updateDouble(boatData->STW, WaterReferenced);
unsigned long now = millis(); unsigned long now = millis();
double MagneticHeading = (boatData->HDG->isValid(now) && boatData->VAR->isValid(now)) ? boatData->HDG->getData() + boatData->VAR->getData() : NMEA0183DoubleNA; double MagneticHeading = (boatData->HDT->isValid(now) && boatData->VAR->isValid(now)) ? boatData->HDT->getData() + boatData->VAR->getData() : NMEA0183DoubleNA;
if (NMEA0183SetVHW(NMEA0183Msg, boatData->HDG->getDataWithDefault(NMEA0183DoubleNA), MagneticHeading, WaterReferenced,talkerId)) if (NMEA0183SetVHW(NMEA0183Msg, boatData->HDT->getDataWithDefault(NMEA0183DoubleNA), MagneticHeading, WaterReferenced,talkerId))
{ {
SendMessage(NMEA0183Msg); SendMessage(NMEA0183Msg);
} }
@ -468,37 +468,46 @@ private:
{ {
unsigned char SID; unsigned char SID;
tN2kWindReference WindReference; tN2kWindReference WindReference;
tNMEA0183WindReference NMEA0183Reference = NMEA0183Wind_True;
double x, y;
double WindAngle=N2kDoubleNA, WindSpeed=N2kDoubleNA; double WindAngle=N2kDoubleNA, WindSpeed=N2kDoubleNA;
if (ParseN2kWindSpeed(N2kMsg, SID, WindSpeed, WindAngle, WindReference)) if (ParseN2kWindSpeed(N2kMsg, SID, WindSpeed, WindAngle, WindReference)) {
{
tNMEA0183Msg NMEA0183Msg; tNMEA0183Msg NMEA0183Msg;
tNMEA0183WindReference NMEA0183Reference;
bool shouldSend = false;
if (WindReference == N2kWind_Apparent) // MWV sentence contains apparent/true ANGLE and SPEED
{ // https://gpsd.gitlab.io/gpsd/NMEA.html#_mwv_wind_speed_and_angle
// https://docs.vaisala.com/r/M211109EN-L/en-US/GUID-7402DEF8-5E82-446F-B63E-998F49F3D743/GUID-C77934C7-2A72-466E-BC52-CE6B8CC7ACB6
if (WindReference == N2kWind_Apparent) {
NMEA0183Reference = NMEA0183Wind_Apparent; NMEA0183Reference = NMEA0183Wind_Apparent;
updateDouble(boatData->AWA, WindAngle); updateDouble(boatData->AWA, WindAngle);
updateDouble(boatData->AWS, WindSpeed); updateDouble(boatData->AWS, WindSpeed);
setMax(boatData->MaxAws, boatData->AWS); setMax(boatData->MaxAws, boatData->AWS);
} shouldSend = true;
if (WindReference == N2kWind_True_North) }
{ if (WindReference == N2kWind_True_water) {
NMEA0183Reference = NMEA0183Wind_True; NMEA0183Reference = NMEA0183Wind_True;
updateDouble(boatData->TWD, WindAngle); updateDouble(boatData->TWA, WindAngle);
updateDouble(boatData->TWS, WindSpeed); updateDouble(boatData->TWS, WindSpeed);
setMax(boatData->MaxTws, boatData->TWS);
shouldSend = true;
if (boatData->HDT->isValid()) {
double twd = WindAngle+boatData->HDT->getData();
if (twd>2*M_PI) { twd-=2*M_PI; }
updateDouble(boatData->TWD, twd);
}
} }
if (NMEA0183SetMWV(NMEA0183Msg, formatCourse(WindAngle), NMEA0183Reference, WindSpeed,talkerId)) if (shouldSend && NMEA0183SetMWV(NMEA0183Msg, formatCourse(WindAngle), NMEA0183Reference, WindSpeed, talkerId)) {
SendMessage(NMEA0183Msg); SendMessage(NMEA0183Msg);
}
if (WindReference == N2kWind_Apparent && boatData->SOG->isValid()) /* if (WindReference == N2kWind_Apparent && boatData->SOG->isValid())
{ // Lets calculate and send TWS/TWA if SOG is available { // Lets calculate and send TWS/TWA if SOG is available
x = WindSpeed * cos(WindAngle); double x = WindSpeed * cos(WindAngle);
y = WindSpeed * sin(WindAngle); double y = WindSpeed * sin(WindAngle);
updateDouble(boatData->TWD, atan2(y, -boatData->SOG->getData() + x)); updateDouble(boatData->TWD, atan2(y, -boatData->SOG->getData() + x));
updateDouble(boatData->TWS, sqrt((y * y) + ((-boatData->SOG->getData() + x) * (-boatData->SOG->getData() + x)))); updateDouble(boatData->TWS, sqrt((y * y) + ((-boatData->SOG->getData() + x) * (-boatData->SOG->getData() + x))));
@ -534,7 +543,7 @@ private:
return; return;
SendMessage(NMEA0183Msg); SendMessage(NMEA0183Msg);
} } */
} }
} }
//***************************************************************************** //*****************************************************************************
@ -657,12 +666,14 @@ private:
double _Heading=N2kDoubleNA; double _Heading=N2kDoubleNA;
double _ROT=N2kDoubleNA; double _ROT=N2kDoubleNA;
tN2kAISNavStatus _NavStatus; tN2kAISNavStatus _NavStatus;
tN2kAISTransceiverInformation _AISTransceiverInformation;
uint8_t _SID;
uint8_t _MessageType = 1; uint8_t _MessageType = 1;
tNMEA0183AISMsg NMEA0183AISMsg; tNMEA0183AISMsg NMEA0183AISMsg;
if (ParseN2kPGN129038(N2kMsg, SID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM, _Seconds, if (ParseN2kPGN129038(N2kMsg, SID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM, _Seconds,
_COG, _SOG, _Heading, _ROT, _NavStatus)) _COG, _SOG, _Heading, _ROT, _NavStatus,_AISTransceiverInformation,_SID))
{ {
// Debug // Debug
@ -746,12 +757,13 @@ private:
tN2kGNSStype _GNSStype; tN2kGNSStype _GNSStype;
tN2kAISTransceiverInformation _AISinfo; tN2kAISTransceiverInformation _AISinfo;
tN2kAISDTE _DTE; tN2kAISDTE _DTE;
uint8_t _SID;
tNMEA0183AISMsg NMEA0183AISMsg; tNMEA0183AISMsg NMEA0183AISMsg;
if (ParseN2kPGN129794(N2kMsg, _MessageID, _Repeat, _UserID, _IMONumber, _Callsign, _Name, _VesselType, if (ParseN2kPGN129794(N2kMsg, _MessageID, _Repeat, _UserID, _IMONumber, _Callsign, 8, _Name,21, _VesselType,
_Length, _Beam, _PosRefStbd, _PosRefBow, _ETAdate, _ETAtime, _Draught, _Destination, _Length, _Beam, _PosRefStbd, _PosRefBow, _ETAdate, _ETAtime, _Draught, _Destination,21,
_AISversion, _GNSStype, _DTE, _AISinfo)) _AISversion, _GNSStype, _DTE, _AISinfo,_SID))
{ {
#ifdef SERIAL_PRINT_AIS_FIELDS #ifdef SERIAL_PRINT_AIS_FIELDS
@ -855,9 +867,10 @@ private:
bool _Display, _DSC, _Band, _Msg22, _State; bool _Display, _DSC, _Band, _Msg22, _State;
tN2kAISMode _Mode; tN2kAISMode _Mode;
tN2kAISTransceiverInformation _AISTranceiverInformation; tN2kAISTransceiverInformation _AISTranceiverInformation;
uint8_t _SID;
if (ParseN2kPGN129039(N2kMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM, if (ParseN2kPGN129039(N2kMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM,
_Seconds, _COG, _SOG, _AISTranceiverInformation, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State)) _Seconds, _COG, _SOG, _AISTranceiverInformation, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State,_SID))
{ {
tNMEA0183AISMsg NMEA0183AISMsg; tNMEA0183AISMsg NMEA0183AISMsg;
@ -896,8 +909,10 @@ private:
tN2kAISRepeat _Repeat; tN2kAISRepeat _Repeat;
uint32_t _UserID; // MMSI uint32_t _UserID; // MMSI
char _Name[21]; char _Name[21];
tN2kAISTransceiverInformation _AISInfo;
uint8_t _SID;
if (ParseN2kPGN129809(N2kMsg, _MessageID, _Repeat, _UserID, _Name)) if (ParseN2kPGN129809(N2kMsg, _MessageID, _Repeat, _UserID, _Name,21,_AISInfo,_SID))
{ {
tNMEA0183AISMsg NMEA0183AISMsg; tNMEA0183AISMsg NMEA0183AISMsg;
@ -923,9 +938,11 @@ private:
double _Beam=N2kDoubleNA; double _Beam=N2kDoubleNA;
double _PosRefStbd=N2kDoubleNA; double _PosRefStbd=N2kDoubleNA;
double _PosRefBow=N2kDoubleNA; double _PosRefBow=N2kDoubleNA;
tN2kAISTransceiverInformation _AISInfo;
uint8_t _SID;
if (ParseN2kPGN129810(N2kMsg, _MessageID, _Repeat, _UserID, _VesselType, _Vendor, _Callsign, if (ParseN2kPGN129810(N2kMsg, _MessageID, _Repeat, _UserID, _VesselType, _Vendor,4, _Callsign,8,
_Length, _Beam, _PosRefStbd, _PosRefBow, _MothershipID)) _Length, _Beam, _PosRefStbd, _PosRefBow, _MothershipID,_AISInfo,_SID))
{ {
// //
@ -1121,8 +1138,8 @@ private:
int16_t ETADate=0; int16_t ETADate=0;
double BearingOriginToDestinationWaypoint=N2kDoubleNA; double BearingOriginToDestinationWaypoint=N2kDoubleNA;
double BearingPositionToDestinationWaypoint=N2kDoubleNA; double BearingPositionToDestinationWaypoint=N2kDoubleNA;
uint8_t OriginWaypointNumber; uint32_t OriginWaypointNumber;
uint8_t DestinationWaypointNumber; uint32_t DestinationWaypointNumber;
double DestinationLatitude=N2kDoubleNA; double DestinationLatitude=N2kDoubleNA;
double DestinationLongitude=N2kDoubleNA; double DestinationLongitude=N2kDoubleNA;
double WaypointClosingVelocity=N2kDoubleNA; double WaypointClosingVelocity=N2kDoubleNA;

View File

@ -355,6 +355,7 @@ void GwXDRMappings::begin()
GwXDRFoundMapping GwXDRMappings::selectMapping(GwXDRMapping::MappingList *list, int instance, const char *key) GwXDRFoundMapping GwXDRMappings::selectMapping(GwXDRMapping::MappingList *list, int instance, const char *key)
{ {
GwXDRMapping *candidate = NULL; GwXDRMapping *candidate = NULL;
unsigned long invalidTime=config->getInt(GwConfigDefinitions::timoSensor);
for (auto mit = list->begin(); mit != list->end(); mit++) for (auto mit = list->begin(); mit != list->end(); mit++)
{ {
GwXDRMappingDef *def = (*mit)->definition; GwXDRMappingDef *def = (*mit)->definition;
@ -369,7 +370,7 @@ GwXDRFoundMapping GwXDRMappings::selectMapping(GwXDRMapping::MappingList *list,
{ {
LOG_DEBUG(GwLog::DEBUG + 1, "selected mapping %s for %s, i=%d", LOG_DEBUG(GwLog::DEBUG + 1, "selected mapping %s for %s, i=%d",
def->toString().c_str(), key, instance); def->toString().c_str(), key, instance);
return GwXDRFoundMapping(*mit, instance); return GwXDRFoundMapping(*mit,invalidTime, instance);
} }
if (instance < 0) if (instance < 0)
{ {
@ -393,7 +394,7 @@ GwXDRFoundMapping GwXDRMappings::selectMapping(GwXDRMapping::MappingList *list,
{ {
LOG_DEBUG(GwLog::DEBUG + 1, "selected mapping %s for %s, i=%d", LOG_DEBUG(GwLog::DEBUG + 1, "selected mapping %s for %s, i=%d",
candidate->definition->toString().c_str(), key, instance); candidate->definition->toString().c_str(), key, instance);
return GwXDRFoundMapping(candidate, instance>=0?instance:candidate->definition->instanceId); return GwXDRFoundMapping(candidate, invalidTime,instance>=0?instance:candidate->definition->instanceId);
} }
LOG_DEBUG(GwLog::DEBUG + 1, "no instance mapping found for key=%s, i=%d", key, instance); LOG_DEBUG(GwLog::DEBUG + 1, "no instance mapping found for key=%s, i=%d", key, instance);
return GwXDRFoundMapping(); return GwXDRFoundMapping();
@ -472,8 +473,9 @@ String GwXDRMappings::getXdrEntry(String mapping, double value,int instance){
} }
GwXDRType *type = findType(code, &typeIndex); GwXDRType *type = findType(code, &typeIndex);
bool first=true; bool first=true;
unsigned long invalidTime=config->getInt(GwConfigDefinitions::timoSensor);
while (type){ while (type){
GwXDRFoundMapping found(def,type); GwXDRFoundMapping found(def,type,invalidTime);
found.instanceId=instance; found.instanceId=instance;
if (first) first=false; if (first) first=false;
else rt+=","; else rt+=",";

View File

@ -167,15 +167,18 @@ class GwXDRFoundMapping : public GwBoatItemNameProvider{
GwXDRType *type=NULL; GwXDRType *type=NULL;
int instanceId=-1; int instanceId=-1;
bool empty=true; bool empty=true;
GwXDRFoundMapping(GwXDRMappingDef *definition,GwXDRType *type){ unsigned long timeout=0;
GwXDRFoundMapping(GwXDRMappingDef *definition,GwXDRType *type, unsigned long timeout){
this->definition=definition; this->definition=definition;
this->type=type; this->type=type;
this->timeout=timeout;
empty=false; empty=false;
} }
GwXDRFoundMapping(GwXDRMapping* mapping,int instance=0){ GwXDRFoundMapping(GwXDRMapping* mapping,unsigned long timeout,int instance){
this->definition=mapping->definition; this->definition=mapping->definition;
this->type=mapping->type; this->type=mapping->type;
this->instanceId=instance; this->instanceId=instance;
this->timeout=timeout;
empty=false; empty=false;
} }
GwXDRFoundMapping(){} GwXDRFoundMapping(){}
@ -195,6 +198,9 @@ class GwXDRFoundMapping : public GwBoatItemNameProvider{
return "formatXdr:"+type->xdrtype+":"+type->boatDataUnit; return "formatXdr:"+type->xdrtype+":"+type->boatDataUnit;
}; };
virtual ~GwXDRFoundMapping(){} virtual ~GwXDRFoundMapping(){}
virtual unsigned long getInvalidTime() override{
return timeout;
}
}; };
//the class GwXDRMappings is not intended to be deleted //the class GwXDRMappings is not intended to be deleted

View File

@ -18,8 +18,8 @@ extra_configs=
[basedeps] [basedeps]
lib_deps = lib_deps =
ttlappalainen/NMEA2000-library @ 4.18.9 ttlappalainen/NMEA2000-library @ 4.22.0
ttlappalainen/NMEA0183 @ 1.9.1 ttlappalainen/NMEA0183 @ 1.10.1
ArduinoJson @ 6.18.5 ArduinoJson @ 6.18.5
AsyncTCP-esphome @ 2.0.1 AsyncTCP-esphome @ 2.0.1
ottowinter/ESPAsyncWebServer-esphome@2.0.1 ottowinter/ESPAsyncWebServer-esphome@2.0.1

View File

@ -138,7 +138,7 @@ bool fixedApPass=true;
#endif #endif
GwWifi gwWifi(&config,&logger,fixedApPass); GwWifi gwWifi(&config,&logger,fixedApPass);
GwChannelList channels(&logger,&config); GwChannelList channels(&logger,&config);
GwBoatData boatData(&logger); GwBoatData boatData(&logger,&config);
GwXDRMappings xdrMappings(&logger,&config); GwXDRMappings xdrMappings(&logger,&config);
bool sendOutN2k=true; bool sendOutN2k=true;

View File

@ -250,6 +250,46 @@
"description": "the n2k instance to be used as port rudder 0...253, -1 to disable", "description": "the n2k instance to be used as port rudder 0...253, -1 to disable",
"category": "converter" "category": "converter"
}, },
{
"name": "timeouts",
"type": "array",
"replace":[
{
"n":"Default",
"d":"4000",
"l": "default",
"t": "NMEA"
},
{
"n":"Sensor",
"d":"60000",
"l": "sensor",
"t": "sensor"
},
{
"n":"Long",
"d":"32000",
"l": "long",
"t": "special NMEA"
},
{
"n":"Ais",
"d":"120000",
"l": "ais",
"t": "ais"
}
],
"children":[
{
"name":"timo$n",
"label":"timeout $l",
"default": "$d",
"type": "number",
"description": "data timeouts(ms) for $t data",
"category": "converter"
}
]
},
{ {
"name": "usbActisense", "name": "usbActisense",
"label": "USB mode", "label": "USB mode",