add XDR min interval, N2K min interval, add N2K_LOAD_LEVEL and N2K_CERTIFICATION_LEVEL

This commit is contained in:
wellenvogel 2021-12-03 11:47:05 +01:00
parent b01dabf8cc
commit 3e73f6b80c
10 changed files with 116 additions and 36 deletions

View File

@ -20,6 +20,8 @@
#ifdef BOARD_M5ATOM #ifdef BOARD_M5ATOM
#define ESP32_CAN_TX_PIN GPIO_NUM_22 #define ESP32_CAN_TX_PIN GPIO_NUM_22
#define ESP32_CAN_RX_PIN GPIO_NUM_19 #define ESP32_CAN_RX_PIN GPIO_NUM_19
//150mA if we power from the bus
#define N2K_LOAD_LEVEL 3
//if using tail485 //if using tail485
#define GWSERIAL_TX 26 #define GWSERIAL_TX 26
#define GWSERIAL_RX 32 #define GWSERIAL_RX 32

View File

@ -49,7 +49,8 @@ class NMEA0183DataToN2KFunctions : public NMEA0183DataToN2K
private: private:
MyAisDecoder *aisDecoder=NULL; MyAisDecoder *aisDecoder=NULL;
ConverterList<NMEA0183DataToN2KFunctions, SNMEA0183Msg> converters; ConverterList<NMEA0183DataToN2KFunctions, SNMEA0183Msg> converters;
std::map<unsigned long,unsigned long> lastSends; std::map<String,unsigned long> lastSends;
unsigned long minSendInterval=50;
class WaypointNumber{ class WaypointNumber{
public: public:
unsigned long id; unsigned long id;
@ -92,22 +93,26 @@ private:
waypointMap[wpName]=newWp; waypointMap[wpName]=newWp;
return newWp.id; return newWp.id;
} }
bool send(tN2kMsg &msg,unsigned long minDiff=50){ bool send(tN2kMsg &msg,String key,unsigned long minDiff){
unsigned long now=millis(); unsigned long now=millis();
unsigned long pgn=msg.PGN; unsigned long pgn=msg.PGN;
auto it=lastSends.find(pgn); if (key == "") key=String(msg.PGN);
auto it=lastSends.find(key);
if (it == lastSends.end()){ if (it == lastSends.end()){
lastSends[pgn]=now; lastSends[key]=now;
sender(msg); sender(msg);
return true; return true;
} }
if ((it->second + minDiff) <= now){ if ((it->second + minDiff) <= now){
lastSends[pgn]=now; lastSends[key]=now;
sender(msg); sender(msg);
return true; return true;
} }
return false; return false;
} }
bool send(tN2kMsg &msg, String key=""){
send(msg,key,minSendInterval);
}
bool updateDouble(GwBoatItem<double> *target,double v, int sourceId){ bool updateDouble(GwBoatItem<double> *target,double v, int sourceId){
if (v != NMEA0183DoubleNA){ if (v != NMEA0183DoubleNA){
return target->update(v,sourceId); return target->update(v,sourceId);
@ -256,7 +261,7 @@ private:
} }
if (shouldSend){ if (shouldSend){
SetN2kWindSpeed(n2kMsg,1,WindSpeed,WindAngle,n2kRef); SetN2kWindSpeed(n2kMsg,1,WindSpeed,WindAngle,n2kRef);
send(n2kMsg); send(n2kMsg,String(n2kMsg.PGN)+String((int)n2kRef));
} }
} }
void convertVWR(const SNMEA0183Msg &msg) void convertVWR(const SNMEA0183Msg &msg)
@ -297,7 +302,7 @@ private:
if (shouldSend) if (shouldSend)
{ {
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_Apparent); SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_Apparent);
send(n2kMsg); send(n2kMsg,String(n2kMsg.PGN)+String((int)N2kWind_Apparent));
} }
} }
@ -341,11 +346,11 @@ private:
if (shouldSend) if (shouldSend)
{ {
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_True_North); SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_True_North);
send(n2kMsg); send(n2kMsg,String(n2kMsg.PGN)+String((int)N2kWind_True_North));
} }
if (WindAngleMagnetic != NMEA0183DoubleNA && shouldSend){ if (WindAngleMagnetic != NMEA0183DoubleNA && shouldSend){
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngleMagnetic, N2kWind_Magnetic); SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngleMagnetic, N2kWind_Magnetic);
send(n2kMsg,0); //force sending send(n2kMsg,String(n2kMsg.PGN)+String((int)N2kWind_Magnetic));
} }
} }
@ -434,7 +439,7 @@ private:
if (! boatData->DepthTransducer->update(DepthBelowTransducer)) return; if (! boatData->DepthTransducer->update(DepthBelowTransducer)) return;
tN2kMsg n2kMsg; tN2kMsg n2kMsg;
SetN2kWaterDepth(n2kMsg,1,DepthBelowTransducer,Offset); SetN2kWaterDepth(n2kMsg,1,DepthBelowTransducer,Offset);
send(n2kMsg); send(n2kMsg,String(n2kMsg.PGN)+String((Offset != N2kDoubleNA)?1:0));
} }
typedef enum { typedef enum {
DBS, DBS,
@ -469,7 +474,7 @@ private:
if (! boatData->DepthTransducer->update(Depth,msg.sourceId)) return; if (! boatData->DepthTransducer->update(Depth,msg.sourceId)) return;
tN2kMsg n2kMsg; tN2kMsg n2kMsg;
SetN2kWaterDepth(n2kMsg,1,Depth,N2kDoubleNA); SetN2kWaterDepth(n2kMsg,1,Depth,N2kDoubleNA);
send(n2kMsg); send(n2kMsg,String(n2kMsg.PGN)+String(0));
return; return;
} }
//we can only send if we have a valid depth beloww tranducer //we can only send if we have a valid depth beloww tranducer
@ -489,7 +494,7 @@ private:
} }
tN2kMsg n2kMsg; tN2kMsg n2kMsg;
SetN2kWaterDepth(n2kMsg,1,Depth,offset); SetN2kWaterDepth(n2kMsg,1,Depth,offset);
send(n2kMsg); send(n2kMsg,String(n2kMsg.PGN)+String((offset != N2kDoubleNA)?1:0));
} }
} }
} }
@ -853,16 +858,18 @@ public:
return converters.handledKeys(); return converters.handledKeys();
} }
NMEA0183DataToN2KFunctions(GwLog *logger, GwBoatData *boatData, N2kSender callback) NMEA0183DataToN2KFunctions(GwLog *logger, GwBoatData *boatData, N2kSender callback, unsigned long minSendInterval)
: NMEA0183DataToN2K(logger, boatData, callback) : NMEA0183DataToN2K(logger, boatData, callback)
{ {
this->minSendInterval=minSendInterval;
aisDecoder= new MyAisDecoder(logger,this->sender); aisDecoder= new MyAisDecoder(logger,this->sender);
registerConverters(); registerConverters();
LOG_DEBUG(GwLog::LOG, "NMEA0183DataToN2KFunctions: registered %d converters", converters.numConverters()); LOG_DEBUG(GwLog::LOG, "NMEA0183DataToN2KFunctions: registered %d converters", converters.numConverters());
} }
}; };
NMEA0183DataToN2K* NMEA0183DataToN2K::create(GwLog *logger,GwBoatData *boatData,N2kSender callback){ NMEA0183DataToN2K* NMEA0183DataToN2K::create(GwLog *logger,GwBoatData *boatData,N2kSender callback,
return new NMEA0183DataToN2KFunctions(logger, boatData,callback); unsigned long minSendInterval){
return new NMEA0183DataToN2KFunctions(logger, boatData,callback,minSendInterval);
} }

View File

@ -17,6 +17,7 @@ class NMEA0183DataToN2K{
virtual unsigned long *handledPgns()=0; virtual unsigned long *handledPgns()=0;
virtual int numConverters()=0; virtual int numConverters()=0;
virtual String handledKeys()=0; virtual String handledKeys()=0;
static NMEA0183DataToN2K* create(GwLog *logger,GwBoatData *boatData,N2kSender callback); static NMEA0183DataToN2K* create(GwLog *logger,GwBoatData *boatData,N2kSender callback,
unsigned long minSendInterval);
}; };
#endif #endif

View File

@ -36,7 +36,6 @@
N2kDataToNMEA0183::N2kDataToNMEA0183(GwLog * logger, GwBoatData *boatData, N2kDataToNMEA0183::N2kDataToNMEA0183(GwLog * logger, GwBoatData *boatData,
tSendNMEA0183MessageCallback callback, int id,String talkerId) tSendNMEA0183MessageCallback callback, int id,String talkerId)
{ {
SendNMEA0183MessageCallback=0;
this->SendNMEA0183MessageCallback=callback; this->SendNMEA0183MessageCallback=callback;
strncpy(this->talkerId,talkerId.c_str(),2); strncpy(this->talkerId,talkerId.c_str(),2);
this->talkerId[2]=0; this->talkerId[2]=0;
@ -65,27 +64,45 @@ class N2kToNMEA0183Functions : public N2kDataToNMEA0183
{ {
private: private:
int minXdrInterval=100; //minimal interval between 2 sends of the same transducer
GwXDRMappings *xdrMappings; GwXDRMappings *xdrMappings;
ConverterList<N2kToNMEA0183Functions,tN2kMsg> converters; ConverterList<N2kToNMEA0183Functions,tN2kMsg> converters;
std::map<String,unsigned long> lastSendTransducers;
static const unsigned long RMCPeriod = 500; static const unsigned long RMCPeriod = 500;
tNMEA0183Msg xdrMessage; tNMEA0183Msg xdrMessage;
bool xdrOpened=false; bool xdrOpened=false;
int xdrCount=0;
bool addToXdr(String entry){ bool addToXdr(GwXDRFoundMapping::XdrEntry entry){
auto it=lastSendTransducers.find(entry.transducer);
unsigned long now=millis();
if (it != lastSendTransducers.end()){
if ((it->second + minXdrInterval) > now) return false;
}
lastSendTransducers[entry.transducer]=now;
if (! xdrOpened){ if (! xdrOpened){
xdrMessage.Init("XDR",talkerId); xdrMessage.Init("XDR",talkerId);
xdrOpened=true; xdrOpened=true;
xdrCount=0;
} }
int len=entry.length(); int len=entry.entry.length();
if (! xdrMessage.AddStrField(entry.c_str())){ if (! xdrMessage.AddStrField(entry.entry.c_str())){
SendMessage(xdrMessage); SendMessage(xdrMessage);
xdrMessage.Init("XDR",talkerId); xdrMessage.Init("XDR",talkerId);
xdrMessage.AddStrField(entry.c_str()); xdrMessage.AddStrField(entry.entry.c_str());
xdrCount=1;
}
else{
xdrCount++;
} }
return true; return true;
} }
bool finalizeXdr(){ bool finalizeXdr(){
if (! xdrOpened) return false; if (! xdrOpened) return false;
if ( xdrCount < 1){
xdrOpened=false;
return false;
}
SendMessage(xdrMessage); SendMessage(xdrMessage);
xdrOpened=false; xdrOpened=false;
return true; return true;
@ -1451,7 +1468,7 @@ private:
public: public:
N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData, N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData,
tSendNMEA0183MessageCallback callback, int sourceId, tSendNMEA0183MessageCallback callback, int sourceId,
String talkerId, GwXDRMappings *xdrMappings) String talkerId, GwXDRMappings *xdrMappings, int minXdrInterval)
: N2kDataToNMEA0183(logger, boatData, callback,sourceId,talkerId) : N2kDataToNMEA0183(logger, boatData, callback,sourceId,talkerId)
{ {
LastPosSend = 0; LastPosSend = 0;
@ -1461,6 +1478,7 @@ private:
this->logger = logger; this->logger = logger;
this->boatData = boatData; this->boatData = boatData;
this->xdrMappings=xdrMappings; this->xdrMappings=xdrMappings;
this->minXdrInterval=minXdrInterval;
registerConverters(); registerConverters();
} }
virtual void loop() virtual void loop()
@ -1476,8 +1494,9 @@ private:
N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData, N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData,
tSendNMEA0183MessageCallback callback, int sourceId,String talkerId, GwXDRMappings *xdrMappings){ tSendNMEA0183MessageCallback callback, int sourceId,String talkerId, GwXDRMappings *xdrMappings,
int minXdrInterval){
LOG_DEBUG(GwLog::LOG,"creating N2kToNMEA0183"); LOG_DEBUG(GwLog::LOG,"creating N2kToNMEA0183");
return new N2kToNMEA0183Functions(logger,boatData,callback, sourceId,talkerId,xdrMappings); return new N2kToNMEA0183Functions(logger,boatData,callback, sourceId,talkerId,xdrMappings,minXdrInterval);
} }
//***************************************************************************** //*****************************************************************************

View File

@ -48,7 +48,7 @@ protected:
public: public:
static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, tSendNMEA0183MessageCallback callback, static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, tSendNMEA0183MessageCallback callback,
int sourceId,String talkerId, GwXDRMappings *xdrMappings); int sourceId,String talkerId, GwXDRMappings *xdrMappings,int minXdrInterval=100);
virtual void HandleMsg(const tN2kMsg &N2kMsg) = 0; virtual void HandleMsg(const tN2kMsg &N2kMsg) = 0;
virtual void loop(); virtual void loop();
virtual ~N2kDataToNMEA0183(){} virtual ~N2kDataToNMEA0183(){}

View File

@ -208,10 +208,11 @@ String GwXDRMappingDef::getTransducerName(int instance)
return name; return name;
} }
String GwXDRFoundMapping::buildXdrEntry(double value) GwXDRFoundMapping::XdrEntry GwXDRFoundMapping::buildXdrEntry(double value)
{ {
char buffer[40]; char buffer[40];
String name = getTransducerName(); XdrEntry rt;
rt.transducer = getTransducerName();
if (type->tonmea) if (type->tonmea)
{ {
value = (*(type->tonmea))(value); value = (*(type->tonmea))(value);
@ -220,9 +221,10 @@ String GwXDRFoundMapping::buildXdrEntry(double value)
type->xdrtype.c_str(), type->xdrtype.c_str(),
value, value,
type->xdrunit.c_str(), type->xdrunit.c_str(),
name.c_str()); rt.transducer.c_str());
buffer[39] = 0; buffer[39] = 0;
return String(buffer); rt.entry=String(buffer);
return rt;
} }
GwXDRMappings::GwXDRMappings(GwLog *logger, GwConfigHandler *config) GwXDRMappings::GwXDRMappings(GwLog *logger, GwConfigHandler *config)
@ -441,7 +443,7 @@ String GwXDRMappings::getXdrEntry(String mapping, double value,int instance){
found.instanceId=instance; found.instanceId=instance;
if (first) first=false; if (first) first=false;
else rt+=","; else rt+=",";
rt+=found.buildXdrEntry(value); rt+=found.buildXdrEntry(value).entry;
type = findType(code, &typeIndex); type = findType(code, &typeIndex);
} }
delete def; delete def;

View File

@ -147,6 +147,11 @@ class GwXDRMapping{
}; };
class GwXDRFoundMapping : public GwBoatItemNameProvider{ class GwXDRFoundMapping : public GwBoatItemNameProvider{
public: public:
class XdrEntry{
public:
String entry;
String transducer;
};
GwXDRMappingDef *definition=NULL; GwXDRMappingDef *definition=NULL;
GwXDRType *type=NULL; GwXDRType *type=NULL;
int instanceId=-1; int instanceId=-1;
@ -166,7 +171,7 @@ class GwXDRFoundMapping : public GwBoatItemNameProvider{
String getTransducerName(){ String getTransducerName(){
return definition->getTransducerName(instanceId); return definition->getTransducerName(instanceId);
} }
String buildXdrEntry(double value); XdrEntry buildXdrEntry(double value);
//boat Data info //boat Data info
virtual String getBoatItemName(){ virtual String getBoatItemName(){
return String("xdr")+getTransducerName(); return String("xdr")+getTransducerName();

View File

@ -31,8 +31,17 @@
//#define FALLBACK_SERIAL //#define FALLBACK_SERIAL
const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
#include <Arduino.h> #include <Arduino.h>
#include "GwApi.h"
#include "GwHardware.h" #include "GwHardware.h"
#ifndef N2K_LOAD_LEVEL
#define N2K_LOAD_LEVEL 0
#endif
#ifndef N2K_CERTIFICATION_LEVEL
#define N2K_CERTIFICATION_LEVEL 0xff
#endif
#include <NMEA2000_CAN.h> // This will automatically choose right CAN library and create suitable NMEA2000 object #include <NMEA2000_CAN.h> // This will automatically choose right CAN library and create suitable NMEA2000 object
#include <ActisenseReader.h> #include <ActisenseReader.h>
#include <Seasmart.h> #include <Seasmart.h>
@ -65,8 +74,6 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
#include "GwUserCode.h" #include "GwUserCode.h"
#include "GwApi.h"
//NMEA message channels //NMEA message channels
#define N2K_CHANNEL_ID 0 #define N2K_CHANNEL_ID 0
#define USB_CHANNEL_ID 1 #define USB_CHANNEL_ID 1
@ -699,13 +706,19 @@ void setup() {
[](const tNMEA0183Msg &msg, int sourceId){ [](const tNMEA0183Msg &msg, int sourceId){
SendNMEA0183Message(msg,sourceId,false); SendNMEA0183Message(msg,sourceId,false);
} }
, N2K_CHANNEL_ID,config.getString(config.talkerId,String("GP")),&xdrMappings); , N2K_CHANNEL_ID,
config.getString(config.talkerId,String("GP")),
&xdrMappings,
config.getInt(config.minXdrInterval,100)
);
toN2KConverter= NMEA0183DataToN2K::create(&logger,&boatData,[](const tN2kMsg &msg)->bool{ toN2KConverter= NMEA0183DataToN2K::create(&logger,&boatData,[](const tN2kMsg &msg)->bool{
logger.logDebug(GwLog::DEBUG+2,"send N2K %ld",msg.PGN); logger.logDebug(GwLog::DEBUG+2,"send N2K %ld",msg.PGN);
handleN2kMessage(msg,N2KT_MSGOUT); handleN2kMessage(msg,N2KT_MSGOUT);
return true; return true;
}); },
config.getInt(config.min2KInterval,50)
);
NMEA2000.SetN2kCANMsgBufSize(8); NMEA2000.SetN2kCANMsgBufSize(8);
NMEA2000.SetN2kCANReceiveFrameBufSize(250); NMEA2000.SetN2kCANReceiveFrameBufSize(250);
@ -719,7 +732,10 @@ void setup() {
100, // Manufacturer's product code 100, // Manufacturer's product code
systemName->asCString(), // Manufacturer's Model ID systemName->asCString(), // Manufacturer's Model ID
VERSION, // Manufacturer's Software version code VERSION, // Manufacturer's Software version code
VERSION // Manufacturer's Model version VERSION, // Manufacturer's Model version,
N2K_LOAD_LEVEL,
0xffff, //Version
N2K_CERTIFICATION_LEVEL
); );
// Set device information // Set device information
NMEA2000.SetDeviceInformation(id, // Unique number. Use e.g. Serial number. Id is generated from MAC-Address NMEA2000.SetDeviceInformation(id, // Unique number. Use e.g. Serial number. Id is generated from MAC-Address

View File

@ -123,6 +123,24 @@
] ]
} }
}, },
{
"name": "minXdrInterval",
"label":"min XDR interval",
"type": "number",
"default": "100",
"check": "checkMinXdrInterval",
"description": "min interval in ms between 2 XDR records with the same transducer (> 10)",
"category": "converter"
},
{
"name": "min2KInterval",
"label":"min N2K interval",
"type": "number",
"default": "50",
"check": "checkMin2KInterval",
"description": "min interval in ms between 2 NMEA 2000 records with the same PGN (> 5)",
"category": "converter"
},
{ {
"name": "usbActisense", "name": "usbActisense",
"label": "USB mode", "label": "USB mode",

View File

@ -124,6 +124,16 @@ function checkApPass(v) {
return "password must be at least 8 characters"; return "password must be at least 8 characters";
} }
} }
function checkMinXdrInterval(v){
let vv=parseInt(v);
if (isNaN(vv)) return "is not a number";
if (vv < 10) return "must be >= 10";
}
function checkMin2KInterval(v){
let vv=parseInt(v);
if (isNaN(vv)) return "is not a number";
if (vv < 5) return "must be >= 5";
}
function checkXDR(v,allValues){ function checkXDR(v,allValues){
if (! v) return; if (! v) return;
let parts=v.split(','); let parts=v.split(',');