diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2562875..c373801 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,8 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} + env: + PIP_BREAK_SYSTEM_PACKAGES: 1 steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f7a26bc..2aeae17 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,6 +18,9 @@ jobs: # The type of runner that the job will run on runs-on: ubuntu-latest + env: + PIP_BREAK_SYSTEM_PACKAGES: 1 + # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it diff --git a/doc/Conversions.odt b/doc/Conversions.odt index 975520d..9ca2adb 100644 Binary files a/doc/Conversions.odt and b/doc/Conversions.odt differ diff --git a/doc/Conversions.pdf b/doc/Conversions.pdf index 75b2bd2..3de736b 100644 Binary files a/doc/Conversions.pdf and b/doc/Conversions.pdf differ diff --git a/extra_script.py b/extra_script.py index 7713451..b8b5c97 100644 --- a/extra_script.py +++ b/extra_script.py @@ -18,6 +18,8 @@ OWN_FILE="extra_script.py" GEN_DIR='lib/generated' CFG_FILE='web/config.json' XDR_FILE='web/xdrconfig.json' +INDEXJS="index.js" +INDEXCSS="index.css" CFG_INCLUDE='GwConfigDefinitions.h' CFG_INCLUDE_IMPL='GwConfigDefImpl.h' XDR_INCLUDE='GwXdrTypeMappings.h' @@ -66,6 +68,7 @@ def isCurrent(infile,outfile): def compressFile(inFile,outfile): if isCurrent(inFile,outfile): return + print("compressing %s"%inFile) with open(inFile, 'rb') as f_in: with gzip.open(outfile, 'wb') as f_out: shutil.copyfileobj(f_in, f_out) @@ -372,6 +375,16 @@ def getLibs(): rt.append(e) return rt +def joinFiles(target,pattern,dirlist): + with gzip.open(target,"wb") as oh: + for dir in dirlist: + fn=os.path.join(dir,pattern) + if os.path.exists(fn): + print("adding %s to %s"%(fn,target)) + with open(fn,"rb") as rh: + shutil.copyfileobj(rh,oh) + + OWNLIBS=getLibs()+["FS","WiFi"] GLOBAL_INCLUDES=[] @@ -440,6 +453,8 @@ def prebuild(env): compressFile(mergedConfig,mergedConfig+".gz") generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False) generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True) + joinFiles(os.path.join(outPath(),INDEXJS+".gz"),INDEXJS,["web"]+userTaskDirs) + joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),INDEXCSS,["web"]+userTaskDirs) embedded=getEmbeddedFiles(env) filedefs=[] for ef in embedded: @@ -453,7 +468,6 @@ def prebuild(env): filedefs.append((pureName,usname,ct)) inFile=os.path.join(basePath(),"web",pureName) if os.path.exists(inFile): - print("compressing %s"%inFile) compressFile(inFile,ef) else: print("#WARNING: infile %s for %s not found"%(inFile,ef)) diff --git a/lib/boatData/GwBoatData.cpp b/lib/boatData/GwBoatData.cpp index f7be617..4be1450 100644 --- a/lib/boatData/GwBoatData.cpp +++ b/lib/boatData/GwBoatData.cpp @@ -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 *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; diff --git a/lib/boatData/GwBoatData.h b/lib/boatData/GwBoatData.h index 1c4394c..18a5f08 100644 --- a/lib/boatData/GwBoatData.h +++ b/lib/boatData/GwBoatData.h @@ -105,8 +105,8 @@ template 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; diff --git a/lib/config/GwConverterConfig.h b/lib/config/GwConverterConfig.h index a3e9c81..d0dbc6a 100644 --- a/lib/config/GwConverterConfig.h +++ b/lib/config/GwConverterConfig.h @@ -16,16 +16,63 @@ #define _GWCONVERTERCONFIG_H #include "GWConfig.h" +#include "N2kTypes.h" +#include + +//list of configs for the PGN 130306 wind references +static std::map 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 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 \ No newline at end of file diff --git a/lib/exampletask/index.css b/lib/exampletask/index.css new file mode 100644 index 0000000..b5b9328 --- /dev/null +++ b/lib/exampletask/index.css @@ -0,0 +1,3 @@ +.examplecss{ + background-color: coral; +} \ No newline at end of file diff --git a/lib/exampletask/index.js b/lib/exampletask/index.js new file mode 100644 index 0000000..a85fe10 --- /dev/null +++ b/lib/exampletask/index.js @@ -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); + } + } + } + }) +})(); diff --git a/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp b/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp index 7868eaf..be01cac 100644 --- a/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp +++ b/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp @@ -103,7 +103,7 @@ private: if (v != NMEA0183UInt32NA){ return target->update(v,sourceId); } - return v; + return false; } uint32_t getUint32(GwBoatItem *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)); diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp index 659a5c3..1056c87 100644 --- a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp +++ b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp @@ -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()); diff --git a/src/main.cpp b/src/main.cpp index 17d2fa1..714c0a5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -850,7 +850,7 @@ void setup() { xdrMappings.begin(); logger.flush(); GwConverterConfig converterConfig; - converterConfig.init(&config); + converterConfig.init(&config,&logger); nmea0183Converter= N2kDataToNMEA0183::create(&logger, &boatData, [](const tNMEA0183Msg &msg, int sourceId){ SendNMEA0183Message(msg,sourceId,false); diff --git a/tools/testServer.py b/tools/testServer.py index ecbfb4d..d00c6cd 100755 --- a/tools/testServer.py +++ b/tools/testServer.py @@ -55,8 +55,17 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): super().do_GET() def do_POST(self): if not self.do_proxy(): - super().do_POST() + super().do_POST() + def guess_type(self,path): + if path.endswith('.gz'): + return super().guess_type(path[0:-3]) + return super().guess_type(path) + def end_headers(self): + if hasattr(self,"isgz") and self.isgz: + self.send_header("Content-Encoding","gzip") + super().end_headers() def translate_path(self, path): + self.isgz=False """Translate a /-separated PATH to the local filename syntax. Components that mean special things to the local file system @@ -90,6 +99,9 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): rpath += '/' if os.path.exists(rpath): return rpath + if os.path.exists(rpath+".gz"): + self.isgz=True + return rpath+".gz" if isSecond: return rpath isSecond=True diff --git a/web/config.json b/web/config.json index 7dad6ca..5c21eb5 100644 --- a/web/config.json +++ b/web/config.json @@ -219,16 +219,56 @@ "category":"converter" }, { - "name":"checkRMCt", + "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", + "default": "4000", "min": 1000, - "check":"checkMinMax", - "category":"converter" - }, - { + "check": "checkMinMax", + "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": "stbRudderI", "label":"stb rudder instance", "type": "number", @@ -251,45 +291,70 @@ "category": "converter" }, { - "name": "timeouts", + "name": "windmappings", "type": "array", "replace":[ { - "n":"Default", - "d":"4000", - "l": "default", - "t": "NMEA" + "n": "tng", + "l": "true north ground", + "t": "True_North=0", + "d": "twa_tws" }, { - "n":"Sensor", - "d":"60000", - "l": "sensor", - "t": "sensor" + "n": "mgd", + "l": "magnetic ground dir", + "t": "Magnetic=1", + "d":"" }, { - "n":"Long", - "d":"32000", - "l": "long", - "t": "special NMEA" + "n": "awa", + "l": "apparent angle", + "t": "Apparent=2", + "d":"awa_aws" }, { - "n":"Ais", - "d":"120000", - "l": "ais", - "t": "ais" + "n": "gna", + "l": "ground angle", + "t": "True_boat=3", + "d": "" + }, + { + "n": "tra", + "l": "true angle", + "t": "True_water=4", + "d":"" } + ], "children":[ { - "name":"timo$n", - "label":"timeout $l", - "default": "$d", - "type": "number", - "description": "data timeouts(ms) for $t data", - "category": "converter" + "name":"windm$n", + "type":"list", + "description": "mapping of the PGN 130306 wind reference $t", + "label":"wind $l", + "list":[ + {"l": "-unset-","v":""}, + {"l": "TWA/TWS","v":"twa_tws"}, + {"l": "AWA/AWS", "v":"awa_aws"}, + {"l": "TWD/TWS","v":"twd_tws"} + ], + "category":"converter", + "default":"$d" + } ] }, + { + "name": "winst312", + "label": "130312 WTemp iid", + "type": "number", + "check": "checkMinMax", + "min": -1, + "max": 256, + "description": "the temp instance of PGN 130312 used for water temperature (0...255), use -1 for none, 256 for any", + "default": "256", + "category":"converter" + }, { "name": "usbActisense", "label": "USB mode", diff --git a/web/index.css b/web/index.css index c3ea7ee..257b739 100644 --- a/web/index.css +++ b/web/index.css @@ -22,7 +22,7 @@ body { overflow: hidden; } -.tabPage{ +#tabPages{ overflow: auto; } @@ -120,6 +120,9 @@ body { .hidden{ display: none !important; } + .dash.invalid{ + display: none; + } #xdrPage .row>.label{ display: none; } @@ -220,6 +223,7 @@ body { } #tabs { display: flex; + flex-wrap: wrap; border-bottom: 1px solid grey; margin-bottom: 0.5em; } @@ -340,4 +344,7 @@ body { } .error{ color: red; +} +input.error{ + background-color: rgba(255, 0, 0, 0.329); } \ No newline at end of file diff --git a/web/index.html b/web/index.html index e7d784d..0862868 100644 --- a/web/index.html +++ b/web/index.html @@ -23,104 +23,106 @@
Update
Help
-
-
-
- VERSION - --- - -
+
+
+
+
+ VERSION + --- + +
-
- Access Point IP - --- +
+ Access Point IP + --- +
+
+ wifi client connected + --- [---] +
+
+ wifi client IP + --- +
+
+ # clients + --- +
+
+ TCP client connected + --- +
+
+ TCP client error + --- +
+
+ Free heap + --- +
+
+ NMEA2000 State + [---]  + UNKNOWN +
-
- wifi client connected - --- [---] -
-
- wifi client IP - --- -
-
- # clients - --- -
-
- TCP client connected - --- -
-
- TCP client error - --- -
-
- Free heap - --- -
-
- NMEA2000 State - [---]  - UNKNOWN -
+
- -
-