1
0
mirror of https://github.com/thooge/esp32-nmea2000-obp60.git synced 2025-12-13 22:13:07 +01:00

Merge branch 'wellenvogel:master' into master

This commit is contained in:
Norbert Walter
2024-10-17 16:49:32 +02:00
committed by GitHub
18 changed files with 2540 additions and 2010 deletions

View File

@@ -47,7 +47,6 @@ GwBoatItemBase::GwBoatItemBase(String name, String format, GwBoatItemBase::TOTyp
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 ){
@@ -375,7 +374,7 @@ GwBoatItem<T> *GwBoatData::getOrCreate(T initial, GwBoatItemNameProvider *provid
provider->getBoatItemFormat(),
provider->getInvalidTime(),
&values);
rt->update(initial);
rt->update(initial,-1);
LOG_DEBUG(GwLog::LOG, "creating boatItem %s, type %d",
name.c_str(), rt->getCurrentType());
return rt;

View File

@@ -105,8 +105,8 @@ template<class T> class GwBoatItem : public GwBoatItemBase{
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(){}
bool update(T nv, int source=-1);
bool updateMax(T nv,int sourceId=-1);
bool update(T nv, int source);
bool updateMax(T nv,int sourceId);
T getData(){
return data;
}
@@ -185,7 +185,6 @@ public:
#define GWSPECBOATDATA(clazz,name,toType,fmt) \
clazz *name=new clazz(#name,GwBoatItemBase::fmt,toType,&values) ;
class GwBoatData{
static const unsigned long DEF_TIME=4000;
private:
GwLog *logger;
GwBoatItemBase::GwBoatItemMap values;

View File

@@ -16,16 +16,63 @@
#define _GWCONVERTERCONFIG_H
#include "GWConfig.h"
#include "N2kTypes.h"
#include <map>
//list of configs for the PGN 130306 wind references
static std::map<tN2kWindReference,String> windConfigs={
{N2kWind_True_water,GwConfigDefinitions::windmtra},
{N2kWind_Apparent,GwConfigDefinitions::windmawa},
{N2kWind_True_boat,GwConfigDefinitions::windmgna},
{N2kWind_Magnetic,GwConfigDefinitions::windmmgd},
{N2kWind_True_North,GwConfigDefinitions::windmtng},
};
class GwConverterConfig{
public:
class WindMapping{
public:
using Wind0183Type=enum{
AWA_AWS,
TWA_TWS,
TWD_TWS,
GWA_GWS,
GWD_GWS
};
tN2kWindReference n2kType;
Wind0183Type nmea0183Type;
bool valid=false;
WindMapping(){}
WindMapping(const tN2kWindReference &n2k,const Wind0183Type &n183):
n2kType(n2k),nmea0183Type(n183),valid(true){}
WindMapping(const tN2kWindReference &n2k,const String &n183):
n2kType(n2k){
if (n183 == "twa_tws"){
nmea0183Type=TWA_TWS;
valid=true;
return;
}
if (n183 == "awa_aws"){
nmea0183Type=AWA_AWS;
valid=true;
return;
}
if (n183 == "twd_tws"){
nmea0183Type=TWD_TWS;
valid=true;
return;
}
}
};
int minXdrInterval=100;
int starboardRudderInstance=0;
int portRudderInstance=-1; //ignore
int min2KInterval=50;
int rmcInterval=1000;
int rmcCheckTime=4000;
void init(GwConfigHandler *config){
int winst312=256;
std::vector<WindMapping> windMappings;
void init(GwConfigHandler *config, GwLog*logger){
minXdrInterval=config->getInt(GwConfigDefinitions::minXdrInterval,100);
starboardRudderInstance=config->getInt(GwConfigDefinitions::stbRudderI,0);
portRudderInstance=config->getInt(GwConfigDefinitions::portRudderI,-1);
@@ -36,6 +83,30 @@ class GwConverterConfig{
rmcInterval=config->getInt(GwConfigDefinitions::sendRMCi,1000);
if (rmcInterval < 0) rmcInterval=0;
if (rmcInterval > 0 && rmcInterval <100) rmcInterval=100;
winst312=config->getInt(GwConfigDefinitions::winst312,256);
for (auto && it:windConfigs){
String cfg=config->getString(it.second);
WindMapping mapping(it.first,cfg);
if (mapping.valid){
LOG_DEBUG(GwLog::ERROR,"add wind mapping n2k=%d,nmea0183=%01d(%s)",
(int)(mapping.n2kType),(int)(mapping.nmea0183Type),cfg.c_str());
windMappings.push_back(mapping);
}
}
}
const WindMapping findWindMapping(const tN2kWindReference &n2k) const{
for (const auto & it:windMappings){
if (it.n2kType == n2k) return it;
}
return WindMapping();
}
const WindMapping findWindMapping(const WindMapping::Wind0183Type &n183) const{
for (const auto & it:windMappings){
if (it.nmea0183Type == n183) return it;
}
return WindMapping();
}
};
#endif

View File

@@ -0,0 +1,3 @@
.examplecss{
background-color: coral;
}

88
lib/exampletask/index.js Normal file
View File

@@ -0,0 +1,88 @@
(function(){
const api=window.esp32nmea2k;
if (! api) return;
//we only do something if a special capability is set
//on our case this is "testboard"
//so we only start any action when we receive the init event
//and we successfully checked that our requested capability is there
let isActive=false;
const tabName="example";
const configName="exampleBDSel";
const infoUrl='https://github.com/wellenvogel/esp32-nmea2000/tree/master/lib/exampletask';
let boatItemName;
let boatItemElement;
api.registerListener((id,data)=>{
if (id === api.EVENTS.init){
//data is capabilities
//check if our requested capability is there (see GwExampleTask.h)
if (data.testboard) isActive=true;
if (isActive){
//add a simple additional tab page
//you will have to build the content of the page dynamically
//using normal dom manipulation methods
//you can use the helper addEl to create elements
let page=api.addTabPage(tabName,"Example");
api.addEl('div','hdg',page,"this is a test tab");
api.addEl('button','',page,'Info').addEventListener('click',function(ev){
window.open(infoUrl,'info');
})
//add a tab for an external URL
api.addTabPage('exhelp','Info',infoUrl);
}
}
if (isActive){
//console.log("exampletask listener",id,data);
if (id === api.EVENTS.tab){
if (data === tabName){
//maybe we need some activity when our page is being activated
console.log("example tab activated");
}
}
if (id == api.EVENTS.config){
//we have a configuration that
//gives us the name of a boat data item we would like to
//handle special
//in our case we just use an own formatter and add some
//css to the display field
//as this item can change we need to keep track of the
//last item we handled
let nextboatItemName=data[configName];
console.log("value of "+configName,nextboatItemName);
if (nextboatItemName){
//register a user formatter that will be called whenever
//there is a new valid value
//we simply add an "X:" in front
api.addUserFormatter(nextboatItemName,"m(x)",function(v,valid){
if (!valid) return;
return "X:"+v;
})
//after this call the item will be recreated
}
if (boatItemName !== undefined && boatItemName != nextboatItemName){
//if the boat item that we handle has changed, remove
//the previous user formatter (this will recreate the item)
api.removeUserFormatter(boatItemName);
}
boatItemName=nextboatItemName;
boatItemElement=undefined;
}
if (id == api.EVENTS.dataItemCreated){
//this event is called whenever a data item has
//been created (or recreated)
//if this is the item we handle, we just add a css class
//we could also completely rebuild the dom below the element
//and use our formatter to directly write/draw the data
//avoid direct manipulation of the element (i.e. changing the classlist)
//as this element remains there all the time
if (boatItemName && boatItemName == data.name){
boatItemElement=data.element;
//use the helper forEl to find elements within the dashboard item
//the value element has the class "dashValue"
api.forEl(".dashValue",function(el){
el.classList.add("examplecss");
},boatItemElement);
}
}
}
})
})();

View File

@@ -103,7 +103,7 @@ private:
if (v != NMEA0183UInt32NA){
return target->update(v,sourceId);
}
return v;
return false;
}
uint32_t getUint32(GwBoatItem<uint32_t> *src){
return src->getDataWithDefault(N2kUInt32NA);
@@ -399,28 +399,29 @@ private:
return;
}
tN2kMsg n2kMsg;
tN2kWindReference n2kRef;
bool shouldSend=false;
WindAngle=formatDegToRad(WindAngle);
GwConverterConfig::WindMapping mapping;
switch(Reference){
case NMEA0183Wind_Apparent:
n2kRef=N2kWind_Apparent;
shouldSend=updateDouble(boatData->AWA,WindAngle,msg.sourceId) &&
updateDouble(boatData->AWS,WindSpeed,msg.sourceId);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxAws->updateMax(WindSpeed);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxAws->updateMax(WindSpeed,msg.sourceId);
mapping=config.findWindMapping(GwConverterConfig::WindMapping::AWA_AWS);
break;
case NMEA0183Wind_True:
n2kRef=N2kWind_True_water;
shouldSend=updateDouble(boatData->TWA,WindAngle,msg.sourceId) &&
updateDouble(boatData->TWS,WindSpeed,msg.sourceId);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed,msg.sourceId);
mapping=config.findWindMapping(GwConverterConfig::WindMapping::TWA_TWS);
break;
default:
LOG_DEBUG(GwLog::DEBUG,"unknown wind reference %d in %s",(int)Reference,msg.line);
}
if (shouldSend){
SetN2kWindSpeed(n2kMsg,1,WindSpeed,WindAngle,n2kRef);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)n2kRef));
//TODO: try to compute TWD and get mapping for this one
if (shouldSend && mapping.valid){
SetN2kWindSpeed(n2kMsg,1,WindSpeed,WindAngle,mapping.n2kType);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)mapping.n2kType));
}
}
void convertVWR(const SNMEA0183Msg &msg)
@@ -457,11 +458,14 @@ private:
bool shouldSend = false;
shouldSend = updateDouble(boatData->AWA, WindAngle, msg.sourceId) &&
updateDouble(boatData->AWS, WindSpeed, msg.sourceId);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxAws->updateMax(WindSpeed);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxAws->updateMax(WindSpeed,msg.sourceId);
if (shouldSend)
{
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_Apparent);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)N2kWind_Apparent));
const GwConverterConfig::WindMapping mapping=config.findWindMapping(GwConverterConfig::WindMapping::AWA_AWS);
if (mapping.valid){
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, mapping.n2kType);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)mapping.n2kType));
}
}
}
@@ -499,13 +503,21 @@ private:
if (WindDirection != NMEA0183DoubleNA){
shouldSend = updateDouble(boatData->TWD, WindDirection, msg.sourceId) &&
updateDouble(boatData->TWS, WindSpeed, msg.sourceId);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed,msg.sourceId);
if(shouldSend && boatData->HDT->isValid()) {
double twa = WindDirection-boatData->HDT->getData();
if(twa<0) { twa+=2*M_PI; }
updateDouble(boatData->TWA, twa, msg.sourceId);
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, twa, N2kWind_True_water);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)N2kWind_True_water));
const GwConverterConfig::WindMapping mapping=config.findWindMapping(GwConverterConfig::WindMapping::TWA_TWS);
if (mapping.valid){
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, twa, mapping.n2kType);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)mapping.n2kType));
}
const GwConverterConfig::WindMapping mapping2=config.findWindMapping(GwConverterConfig::WindMapping::TWD_TWS);
if (mapping2.valid){
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindDirection, mapping2.n2kType);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)mapping2.n2kType));
}
}
}
}
@@ -590,10 +602,10 @@ private:
}
//offset == 0? SK does not allow this
if (Offset != NMEA0183DoubleNA && Offset>=0 ){
if (! boatData->DBS->update(DepthBelowTransducer+Offset)) return;
if (! boatData->DBS->update(DepthBelowTransducer+Offset,msg.sourceId)) return;
}
if (Offset == NMEA0183DoubleNA) Offset=N2kDoubleNA;
if (! boatData->DBT->update(DepthBelowTransducer)) return;
if (! boatData->DBT->update(DepthBelowTransducer,msg.sourceId)) return;
tN2kMsg n2kMsg;
SetN2kWaterDepth(n2kMsg,1,DepthBelowTransducer,Offset);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((Offset != N2kDoubleNA)?1:0));

View File

@@ -469,38 +469,65 @@ private:
unsigned char SID;
tN2kWindReference WindReference;
double WindAngle=N2kDoubleNA, WindSpeed=N2kDoubleNA;
tNMEA0183WindReference NMEA0183Reference;
if (ParseN2kWindSpeed(N2kMsg, SID, WindSpeed, WindAngle, WindReference)) {
tNMEA0183Msg NMEA0183Msg;
tNMEA0183WindReference NMEA0183Reference;
GwConverterConfig::WindMapping mapping=config.findWindMapping(WindReference);
bool shouldSend = false;
// 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;
updateDouble(boatData->AWA, WindAngle);
updateDouble(boatData->AWS, WindSpeed);
setMax(boatData->MaxAws, boatData->AWS);
shouldSend = true;
}
if (WindReference == N2kWind_True_water) {
NMEA0183Reference = NMEA0183Wind_True;
updateDouble(boatData->TWA, WindAngle);
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 (mapping.valid)
{
if (mapping.nmea0183Type == GwConverterConfig::WindMapping::AWA_AWS)
{
NMEA0183Reference = NMEA0183Wind_Apparent;
updateDouble(boatData->AWA, WindAngle);
updateDouble(boatData->AWS, WindSpeed);
setMax(boatData->MaxAws, boatData->AWS);
shouldSend = true;
}
if (mapping.nmea0183Type == GwConverterConfig::WindMapping::TWA_TWS)
{
NMEA0183Reference = NMEA0183Wind_True;
updateDouble(boatData->TWA, WindAngle);
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 (mapping.nmea0183Type == GwConverterConfig::WindMapping::TWD_TWS)
{
NMEA0183Reference = NMEA0183Wind_True;
updateDouble(boatData->TWD, WindAngle);
updateDouble(boatData->TWS, WindSpeed);
setMax(boatData->MaxTws, boatData->TWS);
if (boatData->HDT->isValid())
{
shouldSend = true;
double twa = WindAngle - boatData->HDT->getData();
if (twa > 2 * M_PI)
{
twa -= 2 * M_PI;
}
updateDouble(boatData->TWA, twa);
WindAngle=twa;
}
}
}
if (shouldSend && NMEA0183SetMWV(NMEA0183Msg, formatCourse(WindAngle), NMEA0183Reference, WindSpeed, talkerId)) {
SendMessage(NMEA0183Msg);
if (shouldSend && NMEA0183SetMWV(NMEA0183Msg, formatCourse(WindAngle), NMEA0183Reference, WindSpeed, talkerId))
{
SendMessage(NMEA0183Msg);
}
}
/* if (WindReference == N2kWind_Apparent && boatData->SOG->isValid())
@@ -1305,6 +1332,20 @@ private:
return;
}
int i=0;
if (TempSource == N2kts_SeaTemperature) {
updateDouble(boatData->WTemp, Temperature);
tNMEA0183Msg NMEA0183Msg;
if (!NMEA0183Msg.Init("MTW", talkerId))
return;
if (!NMEA0183Msg.AddDoubleField(KelvinToC(Temperature)))
return;
if (!NMEA0183Msg.AddStrField("C"))
return;
SendMessage(NMEA0183Msg);
}
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,TempSource,0,0);
if (updateDouble(&mapping,Temperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
@@ -1337,6 +1378,21 @@ private:
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
return;
}
if (TemperatureSource == N2kts_SeaTemperature &&
(config.winst312 == TemperatureInstance || config.winst312 == 256)) {
updateDouble(boatData->WTemp, Temperature);
tNMEA0183Msg NMEA0183Msg;
if (!NMEA0183Msg.Init("MTW", talkerId))
return;
if (!NMEA0183Msg.AddDoubleField(KelvinToC(Temperature)))
return;
if (!NMEA0183Msg.AddStrField("C"))
return;
SendMessage(NMEA0183Msg);
}
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,(int)TemperatureSource,0,TemperatureInstance);
if (updateDouble(&mapping,Temperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());