diff --git a/lib/hardware/GwHardware.h b/lib/hardware/GwHardware.h index 7c0f97a..08a6a8f 100644 --- a/lib/hardware/GwHardware.h +++ b/lib/hardware/GwHardware.h @@ -20,6 +20,8 @@ #ifdef BOARD_M5ATOM #define ESP32_CAN_TX_PIN GPIO_NUM_22 #define ESP32_CAN_RX_PIN GPIO_NUM_19 +//150mA if we power from the bus +#define N2K_LOAD_LEVEL 3 //if using tail485 #define GWSERIAL_TX 26 #define GWSERIAL_RX 32 diff --git a/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp b/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp index bd139da..ead8f29 100644 --- a/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp +++ b/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp @@ -49,7 +49,8 @@ class NMEA0183DataToN2KFunctions : public NMEA0183DataToN2K private: MyAisDecoder *aisDecoder=NULL; ConverterList converters; - std::map lastSends; + std::map lastSends; + unsigned long minSendInterval=50; class WaypointNumber{ public: unsigned long id; @@ -92,22 +93,26 @@ private: waypointMap[wpName]=newWp; 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 pgn=msg.PGN; - auto it=lastSends.find(pgn); + if (key == "") key=String(msg.PGN); + auto it=lastSends.find(key); if (it == lastSends.end()){ - lastSends[pgn]=now; + lastSends[key]=now; sender(msg); return true; } if ((it->second + minDiff) <= now){ - lastSends[pgn]=now; + lastSends[key]=now; sender(msg); return true; } return false; } + bool send(tN2kMsg &msg, String key=""){ + send(msg,key,minSendInterval); + } bool updateDouble(GwBoatItem *target,double v, int sourceId){ if (v != NMEA0183DoubleNA){ return target->update(v,sourceId); @@ -256,7 +261,7 @@ private: } if (shouldSend){ SetN2kWindSpeed(n2kMsg,1,WindSpeed,WindAngle,n2kRef); - send(n2kMsg); + send(n2kMsg,String(n2kMsg.PGN)+String((int)n2kRef)); } } void convertVWR(const SNMEA0183Msg &msg) @@ -297,7 +302,7 @@ private: if (shouldSend) { 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) { SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_True_North); - send(n2kMsg); + send(n2kMsg,String(n2kMsg.PGN)+String((int)N2kWind_True_North)); } if (WindAngleMagnetic != NMEA0183DoubleNA && shouldSend){ 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; tN2kMsg n2kMsg; SetN2kWaterDepth(n2kMsg,1,DepthBelowTransducer,Offset); - send(n2kMsg); + send(n2kMsg,String(n2kMsg.PGN)+String((Offset != N2kDoubleNA)?1:0)); } typedef enum { DBS, @@ -469,7 +474,7 @@ private: if (! boatData->DepthTransducer->update(Depth,msg.sourceId)) return; tN2kMsg n2kMsg; SetN2kWaterDepth(n2kMsg,1,Depth,N2kDoubleNA); - send(n2kMsg); + send(n2kMsg,String(n2kMsg.PGN)+String(0)); return; } //we can only send if we have a valid depth beloww tranducer @@ -489,7 +494,7 @@ private: } tN2kMsg n2kMsg; 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(); } - NMEA0183DataToN2KFunctions(GwLog *logger, GwBoatData *boatData, N2kSender callback) + NMEA0183DataToN2KFunctions(GwLog *logger, GwBoatData *boatData, N2kSender callback, unsigned long minSendInterval) : NMEA0183DataToN2K(logger, boatData, callback) { + this->minSendInterval=minSendInterval; aisDecoder= new MyAisDecoder(logger,this->sender); registerConverters(); LOG_DEBUG(GwLog::LOG, "NMEA0183DataToN2KFunctions: registered %d converters", converters.numConverters()); } }; -NMEA0183DataToN2K* NMEA0183DataToN2K::create(GwLog *logger,GwBoatData *boatData,N2kSender callback){ - return new NMEA0183DataToN2KFunctions(logger, boatData,callback); +NMEA0183DataToN2K* NMEA0183DataToN2K::create(GwLog *logger,GwBoatData *boatData,N2kSender callback, + unsigned long minSendInterval){ + return new NMEA0183DataToN2KFunctions(logger, boatData,callback,minSendInterval); } diff --git a/lib/nmea0183ton2k/NMEA0183DataToN2K.h b/lib/nmea0183ton2k/NMEA0183DataToN2K.h index f329fd5..b897bba 100644 --- a/lib/nmea0183ton2k/NMEA0183DataToN2K.h +++ b/lib/nmea0183ton2k/NMEA0183DataToN2K.h @@ -17,6 +17,7 @@ class NMEA0183DataToN2K{ virtual unsigned long *handledPgns()=0; virtual int numConverters()=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 \ No newline at end of file diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp index eb6b62d..cc3cee9 100644 --- a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp +++ b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp @@ -36,7 +36,6 @@ N2kDataToNMEA0183::N2kDataToNMEA0183(GwLog * logger, GwBoatData *boatData, tSendNMEA0183MessageCallback callback, int id,String talkerId) { - SendNMEA0183MessageCallback=0; this->SendNMEA0183MessageCallback=callback; strncpy(this->talkerId,talkerId.c_str(),2); this->talkerId[2]=0; @@ -65,27 +64,45 @@ class N2kToNMEA0183Functions : public N2kDataToNMEA0183 { private: + int minXdrInterval=100; //minimal interval between 2 sends of the same transducer GwXDRMappings *xdrMappings; ConverterList converters; + std::map lastSendTransducers; static const unsigned long RMCPeriod = 500; tNMEA0183Msg xdrMessage; 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){ xdrMessage.Init("XDR",talkerId); xdrOpened=true; + xdrCount=0; } - int len=entry.length(); - if (! xdrMessage.AddStrField(entry.c_str())){ + int len=entry.entry.length(); + if (! xdrMessage.AddStrField(entry.entry.c_str())){ SendMessage(xdrMessage); xdrMessage.Init("XDR",talkerId); - xdrMessage.AddStrField(entry.c_str()); + xdrMessage.AddStrField(entry.entry.c_str()); + xdrCount=1; + } + else{ + xdrCount++; } return true; } bool finalizeXdr(){ if (! xdrOpened) return false; + if ( xdrCount < 1){ + xdrOpened=false; + return false; + } SendMessage(xdrMessage); xdrOpened=false; return true; @@ -1451,7 +1468,7 @@ private: public: N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData, tSendNMEA0183MessageCallback callback, int sourceId, - String talkerId, GwXDRMappings *xdrMappings) + String talkerId, GwXDRMappings *xdrMappings, int minXdrInterval) : N2kDataToNMEA0183(logger, boatData, callback,sourceId,talkerId) { LastPosSend = 0; @@ -1461,6 +1478,7 @@ private: this->logger = logger; this->boatData = boatData; this->xdrMappings=xdrMappings; + this->minXdrInterval=minXdrInterval; registerConverters(); } virtual void loop() @@ -1476,8 +1494,9 @@ private: 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"); - return new N2kToNMEA0183Functions(logger,boatData,callback, sourceId,talkerId,xdrMappings); + return new N2kToNMEA0183Functions(logger,boatData,callback, sourceId,talkerId,xdrMappings,minXdrInterval); } //***************************************************************************** diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.h b/lib/nmea2kto0183/N2kDataToNMEA0183.h index db1b704..0fe40f3 100644 --- a/lib/nmea2kto0183/N2kDataToNMEA0183.h +++ b/lib/nmea2kto0183/N2kDataToNMEA0183.h @@ -48,7 +48,7 @@ protected: public: 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 loop(); virtual ~N2kDataToNMEA0183(){} diff --git a/lib/xdrmappings/GwXDRMappings.cpp b/lib/xdrmappings/GwXDRMappings.cpp index bf55a72..927b060 100644 --- a/lib/xdrmappings/GwXDRMappings.cpp +++ b/lib/xdrmappings/GwXDRMappings.cpp @@ -208,10 +208,11 @@ String GwXDRMappingDef::getTransducerName(int instance) return name; } -String GwXDRFoundMapping::buildXdrEntry(double value) +GwXDRFoundMapping::XdrEntry GwXDRFoundMapping::buildXdrEntry(double value) { char buffer[40]; - String name = getTransducerName(); + XdrEntry rt; + rt.transducer = getTransducerName(); if (type->tonmea) { value = (*(type->tonmea))(value); @@ -220,9 +221,10 @@ String GwXDRFoundMapping::buildXdrEntry(double value) type->xdrtype.c_str(), value, type->xdrunit.c_str(), - name.c_str()); + rt.transducer.c_str()); buffer[39] = 0; - return String(buffer); + rt.entry=String(buffer); + return rt; } GwXDRMappings::GwXDRMappings(GwLog *logger, GwConfigHandler *config) @@ -441,7 +443,7 @@ String GwXDRMappings::getXdrEntry(String mapping, double value,int instance){ found.instanceId=instance; if (first) first=false; else rt+=","; - rt+=found.buildXdrEntry(value); + rt+=found.buildXdrEntry(value).entry; type = findType(code, &typeIndex); } delete def; diff --git a/lib/xdrmappings/GwXDRMappings.h b/lib/xdrmappings/GwXDRMappings.h index b766cbc..4e09d8e 100644 --- a/lib/xdrmappings/GwXDRMappings.h +++ b/lib/xdrmappings/GwXDRMappings.h @@ -147,6 +147,11 @@ class GwXDRMapping{ }; class GwXDRFoundMapping : public GwBoatItemNameProvider{ public: + class XdrEntry{ + public: + String entry; + String transducer; + }; GwXDRMappingDef *definition=NULL; GwXDRType *type=NULL; int instanceId=-1; @@ -166,7 +171,7 @@ class GwXDRFoundMapping : public GwBoatItemNameProvider{ String getTransducerName(){ return definition->getTransducerName(instanceId); } - String buildXdrEntry(double value); + XdrEntry buildXdrEntry(double value); //boat Data info virtual String getBoatItemName(){ return String("xdr")+getTransducerName(); diff --git a/src/main.cpp b/src/main.cpp index 95acd32..d342ddc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,8 +31,17 @@ //#define FALLBACK_SERIAL const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting #include +#include "GwApi.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 // This will automatically choose right CAN library and create suitable NMEA2000 object #include #include @@ -65,8 +74,6 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting #include "GwUserCode.h" -#include "GwApi.h" - //NMEA message channels #define N2K_CHANNEL_ID 0 #define USB_CHANNEL_ID 1 @@ -699,13 +706,19 @@ void setup() { [](const tNMEA0183Msg &msg, int sourceId){ 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{ logger.logDebug(GwLog::DEBUG+2,"send N2K %ld",msg.PGN); handleN2kMessage(msg,N2KT_MSGOUT); return true; - }); + }, + config.getInt(config.min2KInterval,50) + ); NMEA2000.SetN2kCANMsgBufSize(8); NMEA2000.SetN2kCANReceiveFrameBufSize(250); @@ -719,7 +732,10 @@ void setup() { 100, // Manufacturer's product code systemName->asCString(), // Manufacturer's Model ID 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 NMEA2000.SetDeviceInformation(id, // Unique number. Use e.g. Serial number. Id is generated from MAC-Address diff --git a/web/config.json b/web/config.json index 11ddeb2..294e8f8 100644 --- a/web/config.json +++ b/web/config.json @@ -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", "label": "USB mode", diff --git a/web/index.js b/web/index.js index 09ededb..cd9e3e7 100644 --- a/web/index.js +++ b/web/index.js @@ -124,6 +124,16 @@ function checkApPass(v) { 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){ if (! v) return; let parts=v.split(',');