From 47a2eb52f5c096011c2c3b426e17ec5ecd8ea0cd Mon Sep 17 00:00:00 2001 From: andreas Date: Sun, 3 Mar 2024 18:37:38 +0100 Subject: [PATCH] allow to configure sendRMC --- lib/config/GwConverterConfig.h | 41 ++++++ lib/nmea0183ton2k/NMEA0183DataToN2K.cpp | 40 ++++-- lib/nmea0183ton2k/NMEA0183DataToN2K.h | 6 +- lib/nmea2kto0183/N2kDataToNMEA0183.cpp | 52 +++---- lib/nmea2kto0183/N2kDataToNMEA0183.h | 19 +-- src/main.cpp | 13 +- web/config.json | 180 +++++++++++++----------- 7 files changed, 206 insertions(+), 145 deletions(-) create mode 100644 lib/config/GwConverterConfig.h diff --git a/lib/config/GwConverterConfig.h b/lib/config/GwConverterConfig.h new file mode 100644 index 0000000..a3e9c81 --- /dev/null +++ b/lib/config/GwConverterConfig.h @@ -0,0 +1,41 @@ +/* + (C) Andreas Vogel andreas@wellenvogel.de + This code is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This code is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef _GWCONVERTERCONFIG_H +#define _GWCONVERTERCONFIG_H + +#include "GWConfig.h" + +class GwConverterConfig{ + public: + int minXdrInterval=100; + int starboardRudderInstance=0; + int portRudderInstance=-1; //ignore + int min2KInterval=50; + int rmcInterval=1000; + int rmcCheckTime=4000; + void init(GwConfigHandler *config){ + minXdrInterval=config->getInt(GwConfigDefinitions::minXdrInterval,100); + starboardRudderInstance=config->getInt(GwConfigDefinitions::stbRudderI,0); + portRudderInstance=config->getInt(GwConfigDefinitions::portRudderI,-1); + min2KInterval=config->getInt(GwConfigDefinitions::min2KInterval,50); + if (min2KInterval < 10)min2KInterval=10; + rmcCheckTime=config->getInt(GwConfigDefinitions::checkRMCt,4000); + if (rmcCheckTime < 1000) rmcCheckTime=1000; + rmcInterval=config->getInt(GwConfigDefinitions::sendRMCi,1000); + if (rmcInterval < 0) rmcInterval=0; + if (rmcInterval > 0 && rmcInterval <100) rmcInterval=100; + } + }; +#endif \ No newline at end of file diff --git a/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp b/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp index 9d66e08..bfed8fd 100644 --- a/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp +++ b/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp @@ -29,7 +29,6 @@ private: MyAisDecoder *aisDecoder=NULL; ConverterList converters; std::map lastSends; - unsigned long minSendInterval=50; GwXDRMappings *xdrMappings; class WaypointNumber{ public: @@ -92,7 +91,7 @@ private: return false; } bool send(tN2kMsg &msg, int sourceId,String key=""){ - return send(msg,key,minSendInterval,sourceId); + return send(msg,key,config.min2KInterval,sourceId); } bool updateDouble(GwBoatItem *target,double v, int sourceId){ if (v != NMEA0183DoubleNA){ @@ -304,7 +303,7 @@ private: LOG_DEBUG(GwLog::DEBUG + 1, "convert RMB"); tRMB rmb; if (! NMEA0183ParseRMB_nc(msg,rmb)){ - LOG_DEBUG(GwLog::DEBUG, "failed to parse RMC %s", msg.line); + LOG_DEBUG(GwLog::DEBUG, "failed to parse RMB %s", msg.line); return; } tN2kMsg n2kMsg; @@ -359,6 +358,7 @@ private: LOG_DEBUG(GwLog::DEBUG, "invalid status %c for RMC %s",status, msg.line); return; } + lastRmc=millis(); //we received an RMC that is not from us tN2kMsg n2kMsg; if ( UD(GPST) && @@ -666,21 +666,33 @@ private: void convertDBT(const SNMEA0183Msg &msg){ return convertDBKx(msg,DBT); } - + #define validInstance(name) (name >= 0 && name <= 253) void convertRSA(const SNMEA0183Msg &msg){ double RPOS=NMEA0183DoubleNA; + double PRPOS=NMEA0183DoubleNA; if (msg.FieldCount() < 4) { LOG_DEBUG(GwLog::DEBUG, "failed to parse RSA %s", msg.line); return; } + tN2kMsg n2kMsg; if (msg.FieldLen(0)>0){ - if (msg.Field(1)[0] != 'A') return; - RPOS=degToRad*atof(msg.Field(0)); - tN2kMsg n2kMsg; - if (! UD(RPOS)) return; - SetN2kRudder(n2kMsg,RPOS); - send(n2kMsg,msg.sourceId); + if (msg.Field(1)[0] == 'A'){ + RPOS=degToRad*atof(msg.Field(0)); + if (UD(RPOS) && validInstance(config.starboardRudderInstance)) { + SetN2kRudder(n2kMsg,RPOS,config.starboardRudderInstance); + send(n2kMsg,msg.sourceId,"127245S"); + } + } + } + if (msg.FieldLen(2)>0){ + if (msg.Field(3)[0] == 'A'){ + PRPOS=degToRad*atof(msg.Field(2)); + if (UD(PRPOS) && validInstance(config.portRudderInstance)){ + SetN2kRudder(n2kMsg,PRPOS,config.portRudderInstance); + send(n2kMsg,msg.sourceId,"127245P"); + } + } } } @@ -1061,10 +1073,10 @@ public: NMEA0183DataToN2KFunctions(GwLog *logger, GwBoatData *boatData, N2kSender callback, GwXDRMappings *xdrMappings, - unsigned long minSendInterval) + const GwConverterConfig &cfg) : NMEA0183DataToN2K(logger, boatData, callback) { - this->minSendInterval=minSendInterval; + this->config=cfg; this->xdrMappings=xdrMappings; aisDecoder= new MyAisDecoder(logger,this->sender); registerConverters(); @@ -1074,7 +1086,7 @@ public: NMEA0183DataToN2K* NMEA0183DataToN2K::create(GwLog *logger,GwBoatData *boatData,N2kSender callback, GwXDRMappings *xdrMappings, - unsigned long minSendInterval){ - return new NMEA0183DataToN2KFunctions(logger, boatData,callback,xdrMappings,minSendInterval); + const GwConverterConfig &config){ + return new NMEA0183DataToN2KFunctions(logger, boatData,callback,xdrMappings,config); } diff --git a/lib/nmea0183ton2k/NMEA0183DataToN2K.h b/lib/nmea0183ton2k/NMEA0183DataToN2K.h index d56b80a..f01d648 100644 --- a/lib/nmea0183ton2k/NMEA0183DataToN2K.h +++ b/lib/nmea0183ton2k/NMEA0183DataToN2K.h @@ -4,6 +4,7 @@ #include "GwBoatData.h" #include "N2kMessages.h" #include "GwXDRMappings.h" +#include "GwConverterConfig.h" class NMEA0183DataToN2K{ public: @@ -12,14 +13,17 @@ class NMEA0183DataToN2K{ GwLog * logger; GwBoatData *boatData; N2kSender sender; + GwConverterConfig config; + unsigned long lastRmc=millis(); public: NMEA0183DataToN2K(GwLog *logger,GwBoatData *boatData,N2kSender callback); virtual bool parseAndSend(const char *buffer, int sourceId)=0; virtual unsigned long *handledPgns()=0; virtual int numConverters()=0; virtual String handledKeys()=0; + unsigned long getLastRmc()const {return lastRmc; } static NMEA0183DataToN2K* create(GwLog *logger,GwBoatData *boatData,N2kSender callback, GwXDRMappings *xdrMappings, - unsigned long minSendInterval); + const GwConverterConfig &config); }; #endif \ No newline at end of file diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp index e8b150f..26c6811 100644 --- a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp +++ b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp @@ -43,7 +43,7 @@ N2kDataToNMEA0183::N2kDataToNMEA0183(GwLog * logger, GwBoatData *boatData, //***************************************************************************** -void N2kDataToNMEA0183::loop() { +void N2kDataToNMEA0183::loop(unsigned long) { } //***************************************************************************** @@ -65,10 +65,10 @@ private: GwXDRMappings *xdrMappings; ConverterList converters; std::map lastSendTransducers; - static const unsigned long RMCPeriod = 500; tNMEA0183Msg xdrMessage; bool xdrOpened=false; int xdrCount=0; + unsigned long lastRmcSent=0; bool addToXdr(GwXDRFoundMapping::XdrEntry entry){ auto it=lastSendTransducers.find(entry.transducer); @@ -133,9 +133,6 @@ private: return boatData->update((double)value,sourceId,mapping); } - unsigned long LastPosSend; - unsigned long NextRMCSend; - unsigned long lastLoopTime; virtual unsigned long *handledPgns() { @@ -165,7 +162,6 @@ private: { return converters.numConverters(); } - void SetNextRMCSend() { NextRMCSend = millis() + RMCPeriod; } //*************** the converters *********************** void HandleHeading(const tN2kMsg &N2kMsg) @@ -545,11 +541,9 @@ private: void SendRMC() { long now = millis(); - if (NextRMCSend <= millis() && - boatData->LAT->isValid(now) && - boatData->LAT->getLastSource() == sourceId - ) + if (boatData->LAT->isValid(now) && boatData->LON->isValid(now)) { + lastRmcSent=now; tNMEA0183Msg NMEA0183Msg; if (NMEA0183SetRMC(NMEA0183Msg, @@ -564,7 +558,6 @@ private: { SendMessage(NMEA0183Msg); } - SetNextRMCSend(); } } @@ -610,10 +603,8 @@ private: if (ParseN2kRudder(N2kMsg, RudderPosition, Instance, RudderDirectionOrder, AngleOrder)) { - bool main=false; if (Instance == config.starboardRudderInstance){ updateDouble(boatData->RPOS, RudderPosition); - main=true; } else if (Instance == config.portRudderInstance){ updateDouble(boatData->PRPOS, RudderPosition); @@ -626,18 +617,24 @@ private: if (!NMEA0183Msg.Init("RSA", talkerId)) return; - if (main){ - if (!NMEA0183Msg.AddDoubleField(formatWind(RudderPosition)))return; + auto rpos=boatData->RPOS; + if (rpos->isValid()){ + if (!NMEA0183Msg.AddDoubleField(formatWind(rpos->getData())))return; if (!NMEA0183Msg.AddStrField("A"))return; - if (!NMEA0183Msg.AddDoubleField(0.0))return; - if (!NMEA0183Msg.AddStrField("V"))return; } else{ if (!NMEA0183Msg.AddDoubleField(0.0))return; if (!NMEA0183Msg.AddStrField("V"))return; - if (!NMEA0183Msg.AddDoubleField(formatWind(RudderPosition)))return; + } + auto prpos=boatData->PRPOS; + if (prpos->isValid()){ + if (!NMEA0183Msg.AddDoubleField(formatWind(prpos->getData())))return; if (!NMEA0183Msg.AddStrField("A"))return; } + else{ + if (!NMEA0183Msg.AddDoubleField(0.0))return; + if (!NMEA0183Msg.AddStrField("V"))return; + } SendMessage(NMEA0183Msg); } } @@ -1517,34 +1514,29 @@ private: public: N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData, SendNMEA0183MessageCallback callback, - String talkerId, GwXDRMappings *xdrMappings, const Config &cfg) + String talkerId, GwXDRMappings *xdrMappings, const GwConverterConfig &cfg) : N2kDataToNMEA0183(logger, boatData, callback,talkerId) { - LastPosSend = 0; - lastLoopTime = 0; - NextRMCSend = millis() + RMCPeriod; - this->logger = logger; this->boatData = boatData; this->xdrMappings=xdrMappings; this->config=cfg; registerConverters(); } - virtual void loop() + virtual void loop(unsigned long lastExtRmc) override { - N2kDataToNMEA0183::loop(); + N2kDataToNMEA0183::loop(lastExtRmc); unsigned long now = millis(); - if (now < (lastLoopTime + 100)) - return; - lastLoopTime = now; - SendRMC(); + if (config.rmcInterval > 0 && (lastExtRmc + config.rmcCheckTime) <= now && (lastRmcSent + config.rmcInterval) <= now){ + SendRMC(); + } } }; N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData, SendNMEA0183MessageCallback callback, String talkerId, GwXDRMappings *xdrMappings, - const N2kDataToNMEA0183::Config &cfg){ + const GwConverterConfig &cfg){ LOG_DEBUG(GwLog::LOG,"creating N2kToNMEA0183"); return new N2kToNMEA0183Functions(logger,boatData,callback, talkerId,xdrMappings,cfg); } diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.h b/lib/nmea2kto0183/N2kDataToNMEA0183.h index ec37429..61e8ee2 100644 --- a/lib/nmea2kto0183/N2kDataToNMEA0183.h +++ b/lib/nmea2kto0183/N2kDataToNMEA0183.h @@ -26,9 +26,10 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -#include -#include -#include +#include "GwLog.h" +#include "GwBoatData.h" +#include "GwXDRMappings.h" +#include "GwConverterConfig.h" //------------------------------------------------------------------------------ class GwJsonDocument; @@ -36,14 +37,8 @@ class N2kDataToNMEA0183 { public: typedef std::function SendNMEA0183MessageCallback; - class Config{ - public: - int minXdrInterval=100; - int starboardRudderInstance=0; - int portRudderInstance=-1; //ignore - }; protected: - Config config; + GwConverterConfig config; GwLog *logger; GwBoatData *boatData; int sourceId=0; @@ -55,9 +50,9 @@ protected: public: static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, SendNMEA0183MessageCallback callback, - String talkerId, GwXDRMappings *xdrMappings,const Config &cfg); + String talkerId, GwXDRMappings *xdrMappings,const GwConverterConfig &cfg); virtual void HandleMsg(const tN2kMsg &N2kMsg, int sourceId) = 0; - virtual void loop(); + virtual void loop(unsigned long lastRmc); virtual ~N2kDataToNMEA0183(){} virtual unsigned long* handledPgns()=0; virtual int numPgns()=0; diff --git a/src/main.cpp b/src/main.cpp index 30026c0..5ce1b7a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -808,10 +808,8 @@ void setup() { webserver.begin(); xdrMappings.begin(); logger.flush(); - N2kDataToNMEA0183::Config n2kTo183cfg; - n2kTo183cfg.minXdrInterval=config.getInt(config.minXdrInterval,100); - n2kTo183cfg.starboardRudderInstance=config.getInt(config.stbRudderI,0); - n2kTo183cfg.portRudderInstance=config.getInt(config.portRudderI,-1); + GwConverterConfig converterConfig; + converterConfig.init(&config); nmea0183Converter= N2kDataToNMEA0183::create(&logger, &boatData, [](const tNMEA0183Msg &msg, int sourceId){ SendNMEA0183Message(msg,sourceId,false); @@ -819,7 +817,7 @@ void setup() { , config.getString(config.talkerId,String("GP")), &xdrMappings, - n2kTo183cfg + converterConfig ); toN2KConverter= NMEA0183DataToN2K::create(&logger,&boatData,[](const tN2kMsg &msg, int sourceId)->bool{ @@ -828,7 +826,7 @@ void setup() { return true; }, &xdrMappings, - config.getInt(config.min2KInterval,50) + converterConfig ); NMEA2000.SetN2kCANMsgBufSize(8); @@ -950,7 +948,8 @@ void loopRun() { preferences.end(); logger.logDebug(GwLog::LOG,"Address Change: New Address=%d\n", SourceAddress); } - nmea0183Converter->loop(); + //potentially send out an own RMC if we did not receive one + nmea0183Converter->loop(toN2KConverter->getLastRmc()); monitor.setTime(8); //read channels diff --git a/web/config.json b/web/config.json index 2b74c0d..ebc5a27 100644 --- a/web/config.json +++ b/web/config.json @@ -8,6 +8,86 @@ "description": "system name, used for the access point and for services", "category": "system" }, + { + "name": "stopApTime", + "type": "number", + "default": "0", + "check": "checkMinMax", + "description": "stop the access point after that many minutes if not used", + "category": "system" + }, + { + "name": "apPassword", + "type": "password", + "default": "esp32nmea2k", + "check": "checkApPass", + "description": "set the password for the Wifi access point", + "category": "system", + "capabilities":{"apPwChange":["true"]} + }, + { + "name": "apIp", + "type": "string", + "default":"192.168.15.1", + "check": "checkApIp", + "description": "The IP address for the access point. Clients will get addresses within the same subnet.", + "category":"system" + }, + { + "name": "apMask", + "type": "string", + "default":"255.255.255.0", + "check": "checkNetMask", + "description": "The net mask for the access point", + "category":"system" + }, + { + "name": "useAdminPass", + "type": "boolean", + "default": "true", + "description": "use a password for config modifications", + "category": "system" + }, + { + "name": "adminPassword", + "type": "password", + "default": "esp32admin", + "check": "checkAdminPass", + "description": "set the password for config modifications", + "category": "system" + }, + { + "name": "showInvalidData", + "label": "show all data", + "type": "boolean", + "default": "true", + "description": "show also not received items on data page", + "category": "system" + }, + { + "name":"logLevel", + "label": "log level", + "type":"list", + "default":"0", + "list": [ + {"l":"off","v":"-1"}, + {"l":"error","v":"0"}, + {"l":"log","v":"1"}, + {"l":"debug","v":"3"} + ], + "description": "log level at the USB port", + "category":"system" + }, + { + "name":"ledBrightness", + "label":"led brightness", + "type":"number", + "default":64, + "min":0, + "max":255, + "description":"the brightness of the led (0..255)", + "category":"system" + }, { "name": "talkerId", "label": "NMEA0183 ID", @@ -100,87 +180,7 @@ "ZV" ], "description": "the talkerId used in generated NMEA0183 records", - "category": "system" - }, - { - "name": "stopApTime", - "type": "number", - "default": "0", - "check": "checkMinMax", - "description": "stop the access point after that many minutes if not used", - "category": "system" - }, - { - "name": "apPassword", - "type": "password", - "default": "esp32nmea2k", - "check": "checkApPass", - "description": "set the password for the Wifi access point", - "category": "system", - "capabilities":{"apPwChange":["true"]} - }, - { - "name": "apIp", - "type": "string", - "default":"192.168.15.1", - "check": "checkApIp", - "description": "The IP address for the access point. Clients will get addresses within the same subnet.", - "category":"system" - }, - { - "name": "apMask", - "type": "string", - "default":"255.255.255.0", - "check": "checkNetMask", - "description": "The net mask for the access point", - "category":"system" - }, - { - "name": "useAdminPass", - "type": "boolean", - "default": "true", - "description": "use a password for config modifications", - "category": "system" - }, - { - "name": "adminPassword", - "type": "password", - "default": "esp32admin", - "check": "checkAdminPass", - "description": "set the password for config modifications", - "category": "system" - }, - { - "name": "showInvalidData", - "label": "show all data", - "type": "boolean", - "default": "true", - "description": "show also not received items on data page", - "category": "system" - }, - { - "name":"logLevel", - "label": "log level", - "type":"list", - "default":"0", - "list": [ - {"l":"off","v":"-1"}, - {"l":"error","v":"0"}, - {"l":"log","v":"1"}, - {"l":"debug","v":"3"} - ], - "description": "log level at the USB port", - "category":"system" - }, - { - "name":"ledBrightness", - "label":"led brightness", - "type":"number", - "default":64, - "min":0, - "max":255, - "description":"the brightness of the led (0..255)", - "category":"system" + "category": "converter" }, { "name": "minXdrInterval", @@ -210,6 +210,24 @@ "description":"send out the converted data on the NMEA2000 bus\nIf set to off the converted data will still be shown at the data tab.", "category":"converter" }, + { + "name":"sendRMCi", + "label":"send RMC interval", + "type": "number", + "description":"interval (ms) to automatically send an RMC if we have valid position data (min 100ms, set to 0 to disable)", + "default":"1000", + "category":"converter" + }, + { + "name":"checkRMCt", + "label": "check RMC time", + "type": "number", + "description": "start sending RMC if we did not see an external RMC after this much ms", + "default":"4000", + "min": 1000, + "check":"checkMinMax", + "category":"converter" + }, { "name": "stbRudderI", "label":"stb rudder instance",