From 166d8d826b98f47166b9130d5d4021fcfb417fd9 Mon Sep 17 00:00:00 2001 From: wellenvogel Date: Wed, 24 Nov 2021 12:47:41 +0100 Subject: [PATCH 1/3] intermediate: dynamic boat data for xdr --- lib/boatData/GwBoatData.cpp | 40 ++++++++++--- lib/boatData/GwBoatData.h | 77 ++++++++++++-------------- lib/nmea2kto0183/N2kDataToNMEA0183.cpp | 1 + 3 files changed, 67 insertions(+), 51 deletions(-) diff --git a/lib/boatData/GwBoatData.cpp b/lib/boatData/GwBoatData.cpp index 533f931..f808cd9 100644 --- a/lib/boatData/GwBoatData.cpp +++ b/lib/boatData/GwBoatData.cpp @@ -6,20 +6,42 @@ GwBoatData::GwBoatData(GwLog *logger){ GwBoatData::~GwBoatData(){ GwBoatItemBase::GwBoatItemMap::iterator it; for (it=values.begin() ; it != values.end();it++){ - delete *it; + delete it->second; } } -template GwBoatItem *GwBoatData::getOrCreate(T dummy, String name, String format, - unsigned long invalidTime) +template GwBoatItem *GwBoatData::getOrCreate(T initial, GwBoatItemNameProvider *provider) { - for (auto it=values.begin();it != values.end();it++){ - if ((*it)->getName() == name){ - return *it; + String name=provider->getBoatItemName(); + auto it=values.find(name); + if (it != values.end()) { + int expectedType=GwBoatItemTypes::getType(initial); + if (expectedType != it->second->getCurrentType()){ + return NULL; } + return (GwBoatItem*)(it->second); } - return new GwBoatItem(name,format,invalidTime,&values); + GwBoatItem *rt=new GwBoatItem(GwBoatItemTypes::getType(initial), name, + provider->getBoatItemFormat(), + provider->getInvalidTime(), + &values); + rt->update(initial); + return rt; } +template bool GwBoatData::update(T value,int source,GwBoatItemNameProvider *provider){ + GwBoatItem *item=getOrCreate(value,provider); + if (! item) return false; + return item->update(value,source); +} +template bool GwBoatData::update(double value,int source,GwBoatItemNameProvider *provider); +template T GwBoatData::getDataWithDefault(T defaultv, GwBoatItemNameProvider *provider){ + auto it=values.find(provider->getBoatItemName()); + if (it == values.end()) return defaultv; + int expectedType=GwBoatItemTypes::getType(defaultv); + if (expectedType != it->second->getCurrentType()) return defaultv; + return ((GwBoatItem *)(it->second))->getDataWithDefault(defaultv); +} +template double GwBoatData::getDataWithDefault(double defaultv, GwBoatItemNameProvider *provider); String GwBoatData::toJson() const { unsigned long minTime=millis(); GwBoatItemBase::GwBoatItemMap::const_iterator it; @@ -27,11 +49,11 @@ String GwBoatData::toJson() const { size_t elementSizes=0; for (it=values.begin() ; it != values.end();it++){ count++; - elementSizes+=(*it)->getJsonSize(); + elementSizes+=it->second->getJsonSize(); } DynamicJsonDocument json(JSON_OBJECT_SIZE(count)+elementSizes+10); for (it=values.begin() ; it != values.end();it++){ - (*it)->toJsonDoc(&json,minTime); + it->second->toJsonDoc(&json,minTime); } String buf; serializeJson(json,buf); diff --git a/lib/boatData/GwBoatData.h b/lib/boatData/GwBoatData.h index d2e0db3..cf70dd7 100644 --- a/lib/boatData/GwBoatData.h +++ b/lib/boatData/GwBoatData.h @@ -4,9 +4,21 @@ #include "GwLog.h" #include #include -#include +#include #define GW_BOAT_VALUE_LEN 32 #define GWSC(name) static constexpr const __FlashStringHelper* name=F(#name) +#define GWTYPE_DOUBLE 1 +#define GWTYPE_UINT32 2 +#define GWTYPE_UINT16 3 +#define GWTYPE_INT16 4 +#define GWTYPE_USER 100 +class GwBoatItemTypes{ + public: + static int getType(const uint32_t &x){return GWTYPE_UINT32;} + static int getType(const uint16_t &x){return GWTYPE_UINT16;} + static int getType(const int16_t &x){return GWTYPE_INT16;} + static int getType(const double &x){return GWTYPE_DOUBLE;} +}; class GwBoatItemBase{ public: static const unsigned long INVALID_TIME=60000; @@ -23,8 +35,9 @@ class GwBoatItemBase{ GWSC(mtr2nm); GWSC(formatDop); GWSC(formatRot); - typedef std::vector GwBoatItemMap; + typedef std::map GwBoatItemMap; protected: + int type; unsigned long lastSet=0; unsigned long invalidTime=INVALID_TIME; String name; @@ -34,6 +47,7 @@ class GwBoatItemBase{ else lastSet=millis(); } public: + int getCurrentType(){return type;} unsigned long getLastSet() const {return lastSet;} bool isValid(unsigned long now=0) const { if (lastSet == 0) return false; @@ -46,6 +60,7 @@ class GwBoatItemBase{ this->invalidTime=invalidTime; this->name=name; this->format=format; + this->type=0; } virtual ~GwBoatItemBase(){} void invalidate(){ @@ -63,10 +78,11 @@ template class GwBoatItem : public GwBoatItemBase{ T data; int lastUpdateSource; public: - GwBoatItem(String name,String formatInfo,unsigned long invalidTime=INVALID_TIME,GwBoatItemMap *map=NULL): + GwBoatItem(int type,String name,String formatInfo,unsigned long invalidTime=INVALID_TIME,GwBoatItemMap *map=NULL): GwBoatItemBase(name,formatInfo,invalidTime){ + type=type; if (map){ - map->push_back(this); + (*map)[name]=this; } lastUpdateSource=-1; } @@ -171,7 +187,8 @@ bool convertToJson(const GwSatInfoList &si,JsonVariant &variant); class GwBoatDataSatList : public GwBoatItem { public: - GwBoatDataSatList(String name, String formatInfo, unsigned long invalidTime = INVALID_TIME, GwBoatItemMap *map = NULL) : GwBoatItem(name, formatInfo, invalidTime, map) {} + GwBoatDataSatList(String name, String formatInfo, unsigned long invalidTime = INVALID_TIME, GwBoatItemMap *map = NULL) : + GwBoatItem(GWTYPE_USER+1, name, formatInfo, invalidTime, map) {} bool update(GwSatInfo info, int source) { unsigned long now = millis(); @@ -205,8 +222,16 @@ public: }; +class GwBoatItemNameProvider +{ +public: + virtual String getBoatItemName() = 0; + virtual String getBoatItemFormat() = 0; + virtual unsigned long getInvalidTime(){ return GwBoatItemBase::INVALID_TIME;} + virtual ~GwBoatItemNameProvider() {} +}; #define GWBOATDATA(type,name,time,fmt) \ - GwBoatItem *name=new GwBoatItem(F(#name),GwBoatItemBase::fmt,time,&values) ; + GwBoatItem *name=new GwBoatItem(GwBoatItemTypes::getType((type)0),F(#name),GwBoatItemBase::fmt,time,&values) ; #define GWSPECBOATDATA(clazz,name,time,fmt) \ clazz *name=new clazz(F(#name),GwBoatItemBase::fmt,time,&values) ; class GwBoatData{ @@ -254,43 +279,11 @@ class GwBoatData{ public: GwBoatData(GwLog *logger); ~GwBoatData(); - template GwBoatItem *getOrCreate(T dummy,String name,String format, - unsigned long invalidTime=GwBoatItemBase::INVALID_TIME); + template GwBoatItem *getOrCreate(T initial,GwBoatItemNameProvider *provider); + template bool update(T value,int source,GwBoatItemNameProvider *provider); + template T getDataWithDefault(T defaultv, GwBoatItemNameProvider *provider); String toJson() const; }; -/** - * class for lazy creation of a boat item - * once we have someone that knows the name for it - * and providing fast access without searching all the time trough the map - * xdr mappings implement such a provider - */ -class GwBoatItemNameProvider -{ -public: - virtual String getBoatItemName() = 0; - virtual String getBoatItemFormat() = 0; - virtual ~GwBoatItemNameProvider() {} -}; -template class GwBoatItemHolder{ - private: - GwBoatItem *item=NULL; - GwBoatData *data; - unsigned long invalidTime=GwBoatItemBase::INVALID_TIME; - public: - GwBoatItemHolder(GwBoatData *data,unsigned long invalidTime=GwBoatItemBase::INVALID_TIME){ - this->data=data; - this->invalidTime=invalidTime; - } - GwBoatItem *getItem(GwBoatItemNameProvider *provider){ - if (item) return item; - T dummy; - item=data->getOrCreate(dummy, - provider->getBoatItemName() , - provider->getBoatItemFormat(), - invalidTime); - return item; - } - GwBoatItem *getItem(){return item;} -}; + #endif \ No newline at end of file diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp index d3253ad..7074ccf 100644 --- a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp +++ b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp @@ -1222,6 +1222,7 @@ private: } GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRPRESSURE,(int)PressureSource,0,PressureInstance); if (mapping.empty) return; + if (! boatData->update(ActualPressure,sourceId,&mapping)) return; LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str()); addToXdr(mapping.buildXdrEntry(ActualPressure)); finalizeXdr(); From fb13a70ce6a2381c76b1e69a31601ba9e856c1f0 Mon Sep 17 00:00:00 2001 From: wellenvogel Date: Wed, 24 Nov 2021 18:52:33 +0100 Subject: [PATCH 2/3] dynamic adding of xdr boat data for implemented n2k conversions --- lib/boatData/GwBoatData.cpp | 4 +++ lib/boatData/GwBoatData.h | 4 +-- lib/nmea2kto0183/N2kDataToNMEA0183.cpp | 11 ++++--- lib/xdrmappings/GwXDRMappings.cpp | 4 +++ lib/xdrmappings/GwXDRMappings.h | 2 +- web/index.css | 2 +- web/index.js | 40 ++++++++++++++++++++++---- 7 files changed, 54 insertions(+), 13 deletions(-) diff --git a/lib/boatData/GwBoatData.cpp b/lib/boatData/GwBoatData.cpp index f808cd9..8ecba94 100644 --- a/lib/boatData/GwBoatData.cpp +++ b/lib/boatData/GwBoatData.cpp @@ -17,6 +17,8 @@ template GwBoatItem *GwBoatData::getOrCreate(T initial, GwBoatItemNa if (it != values.end()) { int expectedType=GwBoatItemTypes::getType(initial); if (expectedType != it->second->getCurrentType()){ + LOG_DEBUG(GwLog::DEBUG,"invalid type for boat item %s, expected %d, got %d", + name.c_str(),expectedType,it->second->getCurrentType()); return NULL; } return (GwBoatItem*)(it->second); @@ -26,6 +28,8 @@ template GwBoatItem *GwBoatData::getOrCreate(T initial, GwBoatItemNa provider->getInvalidTime(), &values); rt->update(initial); + LOG_DEBUG(GwLog::LOG,"creating boatItem %s, type %d", + name.c_str(),rt->getCurrentType()); return rt; } template bool GwBoatData::update(T value,int source,GwBoatItemNameProvider *provider){ diff --git a/lib/boatData/GwBoatData.h b/lib/boatData/GwBoatData.h index cf70dd7..1bfdc50 100644 --- a/lib/boatData/GwBoatData.h +++ b/lib/boatData/GwBoatData.h @@ -80,7 +80,7 @@ template class GwBoatItem : public GwBoatItemBase{ public: GwBoatItem(int type,String name,String formatInfo,unsigned long invalidTime=INVALID_TIME,GwBoatItemMap *map=NULL): GwBoatItemBase(name,formatInfo,invalidTime){ - type=type; + this->type=type; if (map){ (*map)[name]=this; } @@ -93,7 +93,7 @@ template class GwBoatItem : public GwBoatItemBase{ //priority handling //sources with lower ids will win //and we will not overwrite their value - if (lastUpdateSource < source){ + if (lastUpdateSource < source && lastUpdateSource >= 0){ return false; } } diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp index 7074ccf..61053fd 100644 --- a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp +++ b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp @@ -105,6 +105,10 @@ private: return false; return item->update(value,sourceId); } + bool updateDouble(GwXDRFoundMapping * mapping, double &value){ + if (mapping->empty) return false; + return boatData->update(value,sourceId,mapping); + } unsigned long LastPosSend; unsigned long NextRMCSend; @@ -1187,7 +1191,7 @@ private: return; } GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,(int)TemperatureSource,0,TemperatureInstance); - if (mapping.empty) return; + if (! updateDouble(&mapping,Temperature)) return; LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str()); addToXdr(mapping.buildXdrEntry(Temperature)); finalizeXdr(); @@ -1204,7 +1208,7 @@ private: return; } GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRHUMIDITY,(int)HumiditySource,0,HumidityInstance); - if (mapping.empty) return; + if (! updateDouble(&mapping,ActualHumidity)) return; LOG_DEBUG(GwLog::DEBUG+1,"found humidity mapping %s",mapping.definition->toString().c_str()); addToXdr(mapping.buildXdrEntry(ActualHumidity)); finalizeXdr(); @@ -1221,8 +1225,7 @@ private: return; } GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRPRESSURE,(int)PressureSource,0,PressureInstance); - if (mapping.empty) return; - if (! boatData->update(ActualPressure,sourceId,&mapping)) return; + if (! updateDouble(&mapping,ActualPressure)) return; LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str()); addToXdr(mapping.buildXdrEntry(ActualPressure)); finalizeXdr(); diff --git a/lib/xdrmappings/GwXDRMappings.cpp b/lib/xdrmappings/GwXDRMappings.cpp index 6ae224c..8b460c4 100644 --- a/lib/xdrmappings/GwXDRMappings.cpp +++ b/lib/xdrmappings/GwXDRMappings.cpp @@ -188,6 +188,10 @@ GwXDRMappingDef *GwXDRMappingDef::fromString(String s) return NULL; } } + if (rt->direction == GwXDRMappingDef::M_DISABLED || rt->xdrName == ""){ + delete rt; + return NULL; + } return rt; } String GwXDRMappingDef::getTransducerName(int instance) diff --git a/lib/xdrmappings/GwXDRMappings.h b/lib/xdrmappings/GwXDRMappings.h index 8dffacc..9b79181 100644 --- a/lib/xdrmappings/GwXDRMappings.h +++ b/lib/xdrmappings/GwXDRMappings.h @@ -166,7 +166,7 @@ class GwXDRFoundMapping : public GwBoatItemNameProvider{ String buildXdrEntry(double value); //boat Data info virtual String getBoatItemName(){ - return getTransducerName(); + return String("xdr")+getTransducerName(); }; virtual String getBoatItemFormat(){ return "formatXdr"+type->xdrunit; //TODO: use the type def for the correct format diff --git a/web/index.css b/web/index.css index 67fd23a..0b8ce2f 100644 --- a/web/index.css +++ b/web/index.css @@ -242,7 +242,7 @@ body{ } .dash { - width: 6em; + width: 6.5em; height: 4em; display: flex; flex-direction: column; diff --git a/web/index.js b/web/index.js index 0f67327..2b76e50 100644 --- a/web/index.js +++ b/web/index.js @@ -1025,6 +1025,15 @@ let valueFormatters = { u:'m' } } +function resizeFont(el,reset,maxIt){ + if (maxIt === undefined) maxIt=10; + if (! el) return; + if (reset) el.style.fontSize=''; + while (el.scrollWidth > el.clientWidth && maxIt){ + let next=parseFloat(window.getComputedStyle(el).fontSize)*0.9; + el.style.fontSize=next+"px"; + } +} function createDashboardItem(name, def, parent) { let frame = addEl('div', 'dash', parent); let title = addEl('span', 'dashTitle', frame, name); @@ -1035,7 +1044,11 @@ function createDashboardItem(name, def, parent) { let footer = addEl('div','footer',frame); let src= addEl('span','source',footer); src.setAttribute('id','source_'+name); - addEl('span','unit',footer,fmt?fmt.u:''); + let u=fmt?fmt.u:''; + if (! fmt && def.format.match(/formatXdr/)){ + u=def.format.replace(/formatXdr/,''); + } + addEl('span','unit',footer,u); return value; } function createDashboard() { @@ -1057,9 +1070,14 @@ function sourceName(v){ return "---"; } function updateDashboard(data) { + let frame = document.getElementById('dashboardPage'); for (let n in data) { let de = document.getElementById('data_' + n); + if (! de && frame){ + de=createDashboardItem(n,data[n],frame); + } if (de) { + let newContent='----'; if (data[n].valid) { let formatter; if (data[n].format && data[n].format != "NULL") { @@ -1067,26 +1085,38 @@ function updateDashboard(data) { formatter = valueFormatters[key]; } if (formatter) { - de.textContent = formatter.f(data[n].value); + newContent = formatter.f(data[n].value); } else { let v = parseFloat(data[n].value); if (!isNaN(v)) { v = v.toFixed(3) - de.textContent = v; + newContent = v; } else { - de.textContent = data[n].value; + newContent = data[n].value; } } } - else de.textContent = "---"; + else newContent = "---"; + if (newContent !== de.textContent){ + de.textContent=newContent; + resizeFont(de,true); + } } let src=document.getElementById('source_'+n); if (src){ src.textContent=sourceName(data[n].source); } } + forEl('.dashValue',function(el){ + let id=el.getAttribute('id'); + if (id){ + if (! data[id.replace(/^data_/,'')]){ + el.parentElement.remove(); + } + } + }); } window.setInterval(update, 1000); From f5fcfa25c363433c474019500448ed0c9400b987 Mon Sep 17 00:00:00 2001 From: wellenvogel Date: Wed, 24 Nov 2021 19:10:28 +0100 Subject: [PATCH 3/3] less memory for boatData request --- lib/boatData/GwBoatData.cpp | 4 +++- lib/boatData/GwBoatData.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/boatData/GwBoatData.cpp b/lib/boatData/GwBoatData.cpp index 8ecba94..467d71b 100644 --- a/lib/boatData/GwBoatData.cpp +++ b/lib/boatData/GwBoatData.cpp @@ -55,7 +55,9 @@ String GwBoatData::toJson() const { count++; elementSizes+=it->second->getJsonSize(); } - DynamicJsonDocument json(JSON_OBJECT_SIZE(count)+elementSizes+10); + int sz=JSON_OBJECT_SIZE(count)+elementSizes+10; + LOG_DEBUG(GwLog::DEBUG,"size for boatData: %d",sz); + DynamicJsonDocument json(sz); for (it=values.begin() ; it != values.end();it++){ it->second->toJsonDoc(&json,minTime); } diff --git a/lib/boatData/GwBoatData.h b/lib/boatData/GwBoatData.h index 1bfdc50..866d086 100644 --- a/lib/boatData/GwBoatData.h +++ b/lib/boatData/GwBoatData.h @@ -67,7 +67,7 @@ class GwBoatItemBase{ lastSet=0; } virtual void toJsonDoc(JsonDocument *doc, unsigned long minTime)=0; - virtual size_t getJsonSize(){return JSON_OBJECT_SIZE(15);} + virtual size_t getJsonSize(){return JSON_OBJECT_SIZE(10);} virtual int getLastSource()=0; virtual void refresh(unsigned long ts=0){uls(ts);} String getName(){return name;}