diff --git a/lib/boatData/GwBoatData.cpp b/lib/boatData/GwBoatData.cpp index 585b6d7..ee7e003 100644 --- a/lib/boatData/GwBoatData.cpp +++ b/lib/boatData/GwBoatData.cpp @@ -1,4 +1,5 @@ #include "GwBoatData.h" +#include #define GWTYPE_DOUBLE 1 #define GWTYPE_UINT32 2 #define GWTYPE_UINT16 3 @@ -30,8 +31,55 @@ GwBoatItemBase::GwBoatItemBase(String name, String format, unsigned long invalid this->name = name; this->format = format; this->type = 0; + this->lastUpdateSource=-1; +} +#define STRING_SIZE 40 +GwBoatItemBase::StringWriter::StringWriter(){ + buffer=new uint8_t[STRING_SIZE]; + wp=buffer; + bufferSize=STRING_SIZE; + buffer [0]=0; +}; +const char *GwBoatItemBase::StringWriter::c_str() const{ + return (const char *)buffer; +} +int GwBoatItemBase::StringWriter::getSize() const{ + return wp-buffer; +} +void GwBoatItemBase::StringWriter::reset(){ + wp=buffer; + *wp=0; +} +void GwBoatItemBase::StringWriter::ensure(size_t size){ + size_t fill=wp-buffer; + size_t newSize=bufferSize; + while ((fill+size) >= (newSize-1) ){ + newSize+=STRING_SIZE; + } + if (newSize != bufferSize){ + uint8_t *newBuffer=new uint8_t[newSize]; + memcpy(newBuffer,buffer,fill); + newBuffer[fill]=0; + delete buffer; + buffer=newBuffer; + wp=newBuffer+fill; + bufferSize=newSize; + } +} +size_t GwBoatItemBase::StringWriter::write(uint8_t c){ + ensure(1); + *wp=c; + wp++; + *wp=0; + return 1; +} +size_t GwBoatItemBase::StringWriter::write(const uint8_t* s, size_t n){ + ensure(n); + memcpy(wp,s,n); + wp+=n; + *wp=0; + return n; } - template GwBoatItem::GwBoatItem(String name,String formatInfo,unsigned long invalidTime,GwBoatItemMap *map): GwBoatItemBase(name,formatInfo,invalidTime){ T dummy; @@ -39,7 +87,6 @@ template GwBoatItem::GwBoatItem(String name,String formatInfo,unsign if (map){ (*map)[name]=this; } - lastUpdateSource=-1; } template @@ -88,6 +135,59 @@ void GwBoatItem::toJsonDoc(JsonDocument *doc, unsigned long minTime) o[F("valid")] = isValid(minTime); o[F("format")] = format; } + + +class WriterWrapper{ + GwBoatItemBase::StringWriter *writer=NULL; + public: + WriterWrapper(GwBoatItemBase::StringWriter *w){ + writer=w; + } + size_t write(uint8_t c){ + if (! writer) return 0; + return writer->write(c); + } + size_t write(const uint8_t* s, size_t n){ + if (! writer) return 0; + return writer->write(s,n); + } +}; +typedef ARDUINOJSON_NAMESPACE::TextFormatter GwTextWriter; + +static void writeToString(GwTextWriter *writer,const double &value){ + writer->writeFloat(value); +} +static void writeToString(GwTextWriter *writer,const uint16_t &value){ + writer->writeInteger(value); +} +static void writeToString(GwTextWriter *writer,const uint32_t &value){ + writer->writeInteger(value); +} +static void writeToString(GwTextWriter *writer,const int16_t &value){ + writer->writeInteger(value); +} +static void writeToString(GwTextWriter *writer,GwSatInfoList &value){ + writer->writeInteger(value.getNumSats()); +} + +template +void GwBoatItem::fillString(){ + writer.reset(); + WriterWrapper wrapper(&writer); + GwTextWriter stringWriter(wrapper); + stringWriter.writeRaw(name.c_str()); + stringWriter.writeChar(','); + stringWriter.writeInteger(isValid()?1:0); + stringWriter.writeChar(','); + stringWriter.writeInteger(lastSet); + stringWriter.writeChar(','); + stringWriter.writeInteger(lastUpdateSource); + stringWriter.writeChar(','); + stringWriter.writeRaw(format.c_str()); + stringWriter.writeChar(','); + writeToString(&stringWriter,data); +} + template class GwBoatItem; template class GwBoatItem; template class GwBoatItem; @@ -214,7 +314,14 @@ String GwBoatData::toJson() const { serializeJson(json,buf); return buf; } - +String GwBoatData::toString(){ + String rt; + for (auto it=values.begin() ; it != values.end();it++){ + rt+=it->second->getDataString(); + rt+="\n"; + } + return rt; +} double formatCourse(double cv) { double rt = cv * 180.0 / M_PI; diff --git a/lib/boatData/GwBoatData.h b/lib/boatData/GwBoatData.h index 4ddf7a9..ba0ec7c 100644 --- a/lib/boatData/GwBoatData.h +++ b/lib/boatData/GwBoatData.h @@ -10,6 +10,19 @@ class GwBoatItemBase{ public: + class StringWriter{ + uint8_t *buffer =NULL; + uint8_t *wp=NULL; + size_t bufferSize=0; + void ensure(size_t size); + public: + StringWriter(); + size_t write(uint8_t c); + size_t write(const uint8_t* s, size_t n); + const char * c_str() const; + int getSize() const; + void reset(); + }; static const unsigned long INVALID_TIME=60000; //the formatter names that must be known in js GWSC(formatCourse); @@ -31,10 +44,12 @@ class GwBoatItemBase{ unsigned long invalidTime=INVALID_TIME; String name; String format; + StringWriter writer; void uls(unsigned long ts=0){ if (ts) lastSet=ts; else lastSet=millis(); } + int lastUpdateSource; public: int getCurrentType(){return type;} unsigned long getLastSet() const {return lastSet;} @@ -44,9 +59,14 @@ class GwBoatItemBase{ void invalidate(){ lastSet=0; } + const char *getDataString(){ + fillString(); + return writer.c_str(); + } + virtual void fillString()=0; virtual void toJsonDoc(JsonDocument *doc, unsigned long minTime)=0; virtual size_t getJsonSize(){return JSON_OBJECT_SIZE(10);} - virtual int getLastSource()=0; + virtual int getLastSource(){return lastUpdateSource;} virtual void refresh(unsigned long ts=0){uls(ts);} String getName(){return name;} }; @@ -54,7 +74,6 @@ class GwBoatData; template class GwBoatItem : public GwBoatItemBase{ protected: T data; - int lastUpdateSource; public: GwBoatItem(String name,String formatInfo,unsigned long invalidTime=INVALID_TIME,GwBoatItemMap *map=NULL); virtual ~GwBoatItem(){} @@ -67,6 +86,7 @@ template class GwBoatItem : public GwBoatItemBase{ if (! isValid(millis())) return defaultv; return data; } + virtual void fillString(); virtual void toJsonDoc(JsonDocument *doc, unsigned long minTime); virtual int getLastSource(){return lastUpdateSource;} }; @@ -178,6 +198,7 @@ class GwBoatData{ template bool update(T value,int source,GwBoatItemNameProvider *provider); template T getDataWithDefault(T defaultv, GwBoatItemNameProvider *provider); String toJson() const; + String toString(); }; diff --git a/src/main.cpp b/src/main.cpp index a2fa2e0..dc73a54 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -420,6 +420,17 @@ protected: result = boatData.toJson(); } }; +class BoatDataStringRequest : public GwRequestMessage +{ +public: + BoatDataStringRequest() : GwRequestMessage(F("text/plain"),F("boatDataString")){}; + +protected: + virtual void processRequest() + { + result = boatData.toString(); + } +}; class XdrExampleRequest : public GwRequestMessage { @@ -562,6 +573,8 @@ void setup() { { return new ResetConfigRequest(); }); webserver.registerMainHandler("/api/boatData", [](AsyncWebServerRequest *request)->GwRequestMessage * { return new BoatDataRequest(); }); + webserver.registerMainHandler("/api/boatDataString", [](AsyncWebServerRequest *request)->GwRequestMessage * + { return new BoatDataStringRequest(); }); webserver.registerMainHandler("/api/xdrExample", [](AsyncWebServerRequest *request)->GwRequestMessage * { String mapping=request->arg("mapping"); diff --git a/web/index.js b/web/index.js index 2b76e50..231acab 100644 --- a/web/index.js +++ b/web/index.js @@ -1035,6 +1035,7 @@ function resizeFont(el,reset,maxIt){ } } function createDashboardItem(name, def, parent) { + if (! def.name) return; let frame = addEl('div', 'dash', parent); let title = addEl('span', 'dashTitle', frame, name); let value = addEl('span', 'dashValue', frame); @@ -1044,22 +1045,35 @@ function createDashboardItem(name, def, parent) { let footer = addEl('div','footer',frame); let src= addEl('span','source',footer); src.setAttribute('id','source_'+name); - let u=fmt?fmt.u:''; - if (! fmt && def.format.match(/formatXdr/)){ + let u=fmt?fmt.u:' '; + if (! fmt && def.format && def.format.match(/formatXdr/)){ u=def.format.replace(/formatXdr/,''); } addEl('span','unit',footer,u); return value; } +function parseBoatDataLine(line){ + let rt={}; + let parts=line.split(','); + rt.name=parts[0]; + rt.valid=parts[1] === '1'; + rt.update=parseInt(parts[2]); + rt.source=parseInt(parts[3]); + rt.format=parts[4]; + rt.value=parts[5]; + return rt; +} function createDashboard() { let frame = document.getElementById('dashboardPage'); if (!frame) return; - getJson("api/boatData").then(function (json) { + getText("api/boatDataString").then(function (txt) { frame.innerHTML = ''; - for (let n in json) { - createDashboardItem(n, json[n], frame); + let values=txt.split('\n'); + for (let n in values) { + let def=parseBoatDataLine(values[n]); + createDashboardItem(def.name, def, frame); } - updateDashboard(json); + updateDashboard(values); }); } function sourceName(v){ @@ -1071,30 +1085,34 @@ function sourceName(v){ } function updateDashboard(data) { let frame = document.getElementById('dashboardPage'); + let names={}; for (let n in data) { - let de = document.getElementById('data_' + n); + let current=parseBoatDataLine(data[n]); + if (! current.name) return; + names[current.name]=true; + let de = document.getElementById('data_' + current.name); if (! de && frame){ - de=createDashboardItem(n,data[n],frame); + de=createDashboardItem(current.name,current,frame); } if (de) { let newContent='----'; - if (data[n].valid) { + if (current.valid) { let formatter; - if (data[n].format && data[n].format != "NULL") { - let key = data[n].format.replace(/^\&/, ''); + if (current.format && current.format != "NULL") { + let key = current.format.replace(/^\&/, ''); formatter = valueFormatters[key]; } if (formatter) { - newContent = formatter.f(data[n].value); + newContent = formatter.f(current.value); } else { - let v = parseFloat(data[n].value); + let v = parseFloat(current.value); if (!isNaN(v)) { v = v.toFixed(3) newContent = v; } else { - newContent = data[n].value; + newContent = current.value; } } } @@ -1104,15 +1122,15 @@ function updateDashboard(data) { resizeFont(de,true); } } - let src=document.getElementById('source_'+n); + let src=document.getElementById('source_'+current.name); if (src){ - src.textContent=sourceName(data[n].source); + src.textContent=sourceName(current.source); } } forEl('.dashValue',function(el){ let id=el.getAttribute('id'); if (id){ - if (! data[id.replace(/^data_/,'')]){ + if (! names[id.replace(/^data_/,'')]){ el.parentElement.remove(); } } @@ -1123,8 +1141,8 @@ window.setInterval(update, 1000); window.setInterval(function () { let dp = document.getElementById('dashboardPage'); if (dp.classList.contains('hidden')) return; - getJson('api/boatData').then(function (data) { - updateDashboard(data); + getText('api/boatDataString').then(function (data) { + updateDashboard(data.split('\n')); }); }, 1000); window.addEventListener('load', function () {