diff --git a/.gitignore b/.gitignore index 7977336..f85bd69 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch -web/*gz \ No newline at end of file +generated/* \ No newline at end of file diff --git a/extra_script.py b/extra_script.py index ffe4d6b..7778c53 100644 --- a/extra_script.py +++ b/extra_script.py @@ -2,20 +2,82 @@ print("running extra...") import gzip import shutil import os -FILES=['web/index.html'] +import sys +import inspect +import json +GEN_DIR='generated' +CFG_FILE='web/config.json' +FILES=['web/index.html',CFG_FILE] +CFG_INCLUDE='GwConfigDefinitions.h' -def compressFile(inFile): - outfile=inFile+".gz" +def basePath(): + #see: https://stackoverflow.com/questions/16771894/python-nameerror-global-name-file-is-not-defined + return os.path.dirname(inspect.getfile(lambda: None)) + +def outPath(): + return os.path.join(basePath(),GEN_DIR) +def checkDir(): + dn=outPath() + if not os.path.exists(dn): + os.makedirs(dn) + if not os.path.isdir(dn): + print("unable to create %s"%dn) + return False + return True + +def isCurrent(infile,outfile): if os.path.exists(outfile): otime=os.path.getmtime(outfile) - itime=os.path.getmtime(inFile) + itime=os.path.getmtime(infile) if (otime >= itime): - print("%s is newer then %s, no need to recreate"%(outfile,inFile)) - return + print("%s is newer then %s, no need to recreate"%(outfile,infile)) + return True + return False +def compressFile(inFile): + outfile=os.path.basename(inFile)+".gz" + inFile=os.path.join(basePath(),inFile) + outfile=os.path.join(outPath(),outfile) + if isCurrent(inFile,outfile): + return with open(inFile, 'rb') as f_in: with gzip.open(outfile, 'wb') as f_out: shutil.copyfileobj(f_in, f_out) + +def generateCfg(): + outfile=os.path.join(outPath(),CFG_INCLUDE) + infile=os.path.join(basePath(),CFG_FILE) + if isCurrent(infile,outfile): + return + print("creating %s"%CFG_INCLUDE) + with open(CFG_FILE,'rb') as ch: + config=json.load(ch) + with open(outfile,'w') as oh: + oh.write("//generated from %s\n"%CFG_FILE) + oh.write('#include "GwConfigItem.h"\n') + l=len(config) + oh.write('class GwConfigDefinitions{\n') + oh.write(' public:\n') + oh.write(' int getNumConfig() const{return %d;}\n'%(l)) + for item in config: + n=item.get('name') + if n is None: + continue + oh.write(' const String %s=F("%s");\n'%(n,n)) + oh.write(' protected:\n') + oh.write(' GwConfigItem *configs[%d]={\n'%(l)) + first=True + for item in config: + if not first: + oh.write(',\n') + first=False + oh.write(" new GwConfigItem(%s,\"%s\")"%(item.get('name'),item.get('default'))) + oh.write('};\n') + oh.write('};\n') + oh.close() +if not checkDir(): + sys.exit(1) for f in FILES: print("compressing %s"%f) - compressFile(f) \ No newline at end of file + compressFile(f) +generateCfg() \ No newline at end of file diff --git a/lib/config/GWConfig.cpp b/lib/config/GWConfig.cpp index c2910f4..44078d6 100644 --- a/lib/config/GWConfig.cpp +++ b/lib/config/GWConfig.cpp @@ -54,7 +54,7 @@ GwConfigInterface * GwConfigHandler::getConfigItem(const String name, bool dummy return &dummyConfig; } #define PREF_NAME "gwprefs" -GwConfigHandler::GwConfigHandler(GwLog *logger){ +GwConfigHandler::GwConfigHandler(GwLog *logger): GwConfigDefinitions(){ this->logger=logger; } bool GwConfigHandler::loadConfig(){ diff --git a/lib/config/GWConfig.h b/lib/config/GWConfig.h index 9ea7d1b..8f8137f 100644 --- a/lib/config/GWConfig.h +++ b/lib/config/GWConfig.h @@ -3,74 +3,16 @@ #include #include #include "GwLog.h" - -class GwConfigInterface{ - public: - virtual String asString() const=0; - virtual const char * asCString() const =0; - virtual bool asBoolean() const = 0; - virtual int asInt() const = 0; -}; -class GwConfigItem: public GwConfigInterface{ - private: - String name; - String initialValue; - String value; - public: - GwConfigItem(const String &name, const String initialValue){ - this->name=name; - this->initialValue=initialValue; - this->value=initialValue; - } - virtual String asString() const{ - return value; - } - virtual const char * asCString() const{ - return value.c_str(); - }; - virtual void fromString(const String v){ - value=v; - }; - virtual bool asBoolean() const{ - return strcasecmp(value.c_str(),"true") == 0; - } - virtual int asInt() const{ - return (int)value.toInt(); - } - String getName() const{ - return name; - } - virtual void reset(){ - value=initialValue; - } - bool changed() const{ - return value != initialValue; - } - String getDefault() const { - return initialValue; - } -}; +#include "GwConfigItem.h" +#include "GwConfigDefinitions.h" -class GwConfigHandler{ +class GwConfigHandler: public GwConfigDefinitions{ private: Preferences prefs; GwLog *logger; public: public: - const String sendUsb=F("sendUsb"); - const String receiveUsb=F("receiveUsb"); - const String wifiClient=F("wifiClient"); - const String wifiPass=F("wifiPass"); - const String wifiSSID=F("wifiSSID"); - const String serverPort=F("serverPort"); - const String maxClients=F("maxClients"); - const String sendTCP=F("sendTCP"); - const String readTCP=F("receiveTCP"); - const String sendSeasmart=F("sendSeasmart"); - const String usbBaud=F("usbBaud"); - const String systemName=F("systemName"); - const String stopApTime=F("stopApTime"); GwConfigHandler(GwLog *logger); bool loadConfig(); bool saveConfig(); @@ -85,23 +27,5 @@ class GwConfigHandler{ GwConfigItem * findConfig(const String name, bool dummy=false); GwConfigInterface * getConfigItem(const String name, bool dummy=false) const; private: - GwConfigItem* configs[13]={ - new GwConfigItem(sendUsb,"true"), - new GwConfigItem (receiveUsb,"false"), - new GwConfigItem (wifiClient,"false"), - new GwConfigItem (wifiSSID,""), - new GwConfigItem (wifiPass,""), - new GwConfigItem (serverPort,"2222"), - new GwConfigItem (maxClients, "10"), - new GwConfigItem (sendTCP,"true"), - new GwConfigItem (readTCP,"true"), - new GwConfigItem (sendSeasmart,"false"), - new GwConfigItem (usbBaud,"115200"), - new GwConfigItem (systemName,"ESP32NMEA2K"), - new GwConfigItem (stopApTime,"0") - }; - int getNumConfig() const{ - return 13; - } }; #endif \ No newline at end of file diff --git a/lib/config/GwConfigItem.h b/lib/config/GwConfigItem.h new file mode 100644 index 0000000..528a1ce --- /dev/null +++ b/lib/config/GwConfigItem.h @@ -0,0 +1,52 @@ +#ifndef _GWCONFIGITEM_H +#define _GWCONFIGITEM_H +#include "WString.h" +class GwConfigInterface{ + public: + virtual String asString() const=0; + virtual const char * asCString() const =0; + virtual bool asBoolean() const = 0; + virtual int asInt() const = 0; +}; +class GwConfigItem: public GwConfigInterface{ + private: + String name; + String initialValue; + String value; + public: + GwConfigItem(const String &name, const String initialValue){ + this->name=name; + this->initialValue=initialValue; + this->value=initialValue; + } + virtual String asString() const{ + return value; + } + virtual const char * asCString() const{ + return value.c_str(); + }; + virtual void fromString(const String v){ + value=v; + }; + virtual bool asBoolean() const{ + return strcasecmp(value.c_str(),"true") == 0; + } + virtual int asInt() const{ + return (int)value.toInt(); + } + String getName() const{ + return name; + } + virtual void reset(){ + value=initialValue; + } + bool changed() const{ + return value != initialValue; + } + String getDefault() const { + return initialValue; + } +}; + + +#endif \ No newline at end of file diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp index 7f497b7..039184e 100644 --- a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp +++ b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp @@ -31,10 +31,11 @@ -N2kDataToNMEA0183::N2kDataToNMEA0183(GwLog * logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183) : tNMEA2000::tMsgHandler(0,NMEA2000){ +N2kDataToNMEA0183::N2kDataToNMEA0183(GwLog * logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183, int id) +: tNMEA2000::tMsgHandler(0,NMEA2000){ SendNMEA0183MessageCallback=0; pNMEA0183=NMEA0183; - + sourceId=id; } @@ -46,10 +47,11 @@ void N2kDataToNMEA0183::loop() { //***************************************************************************** void N2kDataToNMEA0183::SendMessage(const tNMEA0183Msg &NMEA0183Msg) { if ( pNMEA0183 != 0 ) pNMEA0183->SendMessage(NMEA0183Msg); - if ( SendNMEA0183MessageCallback != 0 ) SendNMEA0183MessageCallback(NMEA0183Msg); + if ( SendNMEA0183MessageCallback != 0 ) SendNMEA0183MessageCallback(NMEA0183Msg, sourceId); } -N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183){ - return new N2kToNMEA0183Functions(logger,boatData,NMEA2000,NMEA0183); +N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, + tNMEA0183 *NMEA0183, int sourceId){ + return new N2kToNMEA0183Functions(logger,boatData,NMEA2000,NMEA0183, sourceId); } //***************************************************************************** diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.h b/lib/nmea2kto0183/N2kDataToNMEA0183.h index 3b11e4b..8207ae5 100644 --- a/lib/nmea2kto0183/N2kDataToNMEA0183.h +++ b/lib/nmea2kto0183/N2kDataToNMEA0183.h @@ -33,22 +33,19 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. class N2kDataToNMEA0183 : public tNMEA2000::tMsgHandler { public: - using tSendNMEA0183MessageCallback = void (*)(const tNMEA0183Msg &NMEA0183Msg); + using tSendNMEA0183MessageCallback = void (*)(const tNMEA0183Msg &NMEA0183Msg, int id); protected: GwLog *logger; GwBoatData *boatData; - tNMEA0183 *pNMEA0183; + int sourceId; tSendNMEA0183MessageCallback SendNMEA0183MessageCallback; - - void SendMessage(const tNMEA0183Msg &NMEA0183Msg); - - N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183); + N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183, int sourceId); public: - static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183); + static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183, int sourceId); virtual void HandleMsg(const tN2kMsg &N2kMsg) = 0; void SetSendNMEA0183MessageCallback(tSendNMEA0183MessageCallback _SendNMEA0183MessageCallback) { diff --git a/lib/nmea2kto0183/N2kToNMEA0183Functions.h b/lib/nmea2kto0183/N2kToNMEA0183Functions.h index 891db43..3e36991 100644 --- a/lib/nmea2kto0183/N2kToNMEA0183Functions.h +++ b/lib/nmea2kto0183/N2kToNMEA0183Functions.h @@ -849,7 +849,8 @@ private: } public: - N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183) : N2kDataToNMEA0183(logger, boatData, NMEA2000, NMEA0183) + N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183, int sourceId) + : N2kDataToNMEA0183(logger, boatData, NMEA2000, NMEA0183,sourceId) { LastPosSend = 0; lastLoopTime = 0; diff --git a/lib/queue/GwBuffer.cpp b/lib/queue/GwBuffer.cpp index 8ead682..fc2e83e 100644 --- a/lib/queue/GwBuffer.cpp +++ b/lib/queue/GwBuffer.cpp @@ -6,10 +6,9 @@ void GwBuffer::lp(const char *fkt, int p) fkt, buffer, offset(writePointer), offset(readPointer), usedSpace(), freeSpace(), p); } -GwBuffer::GwBuffer(GwLog *logger,size_t bufferSize, bool rotate) +GwBuffer::GwBuffer(GwLog *logger,size_t bufferSize) { this->logger = logger; - this->rotate=rotate; this->bufferSize=bufferSize; this->buffer=new uint8_t[bufferSize]; writePointer = buffer; @@ -26,67 +25,45 @@ void GwBuffer::reset() } size_t GwBuffer::freeSpace() { - if (! rotate){ - return bufferSize-offset(writePointer)-1; + if (readPointer <= writePointer){ + return readPointer+bufferSize-writePointer-1; } - if (readPointer < writePointer) - { - size_t rt = bufferSize - offset(writePointer) - 1 + offset(readPointer); - return rt; - } - if (readPointer == writePointer) - return bufferSize - 1; return readPointer - writePointer - 1; } size_t GwBuffer::usedSpace() { - if (readPointer == writePointer) - return 0; - if (readPointer < writePointer) + if (readPointer <= writePointer) return writePointer - readPointer; - return bufferSize - offset(readPointer) + offset(writePointer); + return writePointer+bufferSize-readPointer; } -size_t GwBuffer::addData(const uint8_t *data, size_t len) +size_t GwBuffer::addData(const uint8_t *data, size_t len, bool addPartial) { lp("addDataE", len); if (len == 0) return 0; - if (freeSpace() < len && rotate) - //in rotating mode (send buffer) - //we only fill in a message if it fit's completely + if (freeSpace() < len && !addPartial) return 0; - size_t written = 0; - if (writePointer >= readPointer) - { - written = bufferSize - offset(writePointer) - 1; - bool canRotate=rotate && offset(readPointer) > 0; - if (canRotate) written++; //we can also fill the last byte - if (written > len) - written = len; - if (written) - { - memcpy(writePointer, data, written); - len -= written; - data += written; - writePointer += written; - if (offset(writePointer) > (bufferSize - 1)) - writePointer = buffer; + size_t written = 0; + for (int i=0;i<2;i++){ + size_t currentFree=freeSpace(); + size_t toWrite=len-written; + if (toWrite > currentFree) toWrite=currentFree; + if (toWrite > (bufferSize - offset(writePointer))) { + toWrite=bufferSize - offset(writePointer); } - lp("addData1", written); - if (len <= 0) - { - return written; + if (toWrite != 0){ + memcpy(writePointer, data, toWrite); + written+=toWrite; + data += toWrite; + writePointer += toWrite; + if (offset(writePointer) >= bufferSize){ + writePointer -= bufferSize; + } } - if (! rotate) return written; + lp("addData1", toWrite); } - //now we have the write pointer before the read pointer - int maxLen=readPointer-writePointer-1; - if (maxLen <= 0) return written; - if (len < maxLen) maxLen=len; - memcpy(writePointer, data, maxLen); - writePointer += maxLen; - lp("addData2", maxLen); - return maxLen + written; + lp("addData2", written); + return written; } /** * write some data to the buffer writer @@ -94,92 +71,66 @@ size_t GwBuffer::addData(const uint8_t *data, size_t len) */ GwBuffer::WriteStatus GwBuffer::fetchData(GwBufferWriter *writer, int maxLen,bool errorIf0 ) { - lp("fetchDataE"); + lp("fetchDataE",maxLen); size_t len = usedSpace(); if (maxLen > 0 && len > maxLen) len=maxLen; - if (len == 0) + if (len == 0){ + lp("fetchData0",maxLen); + writer->done(); return OK; + } size_t written = 0; - size_t plen = len; - if (writePointer < readPointer) - { - //we need to write from readPointer till end and then till writePointer-1 - plen = bufferSize - offset(readPointer) - 1; - int rt = writer->write(readPointer, plen); - lp("fetchData1", rt); - if (rt < 0) - { - LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns error %d", rt); - return ERROR; + for (int i=0;i<2;i++){ + size_t currentUsed=usedSpace(); + size_t toWrite=len-written; + if (toWrite > currentUsed) toWrite=currentUsed; + if (toWrite > (bufferSize - offset(readPointer))) { + toWrite=bufferSize - offset(readPointer); } - if (rt > plen) + lp("fetchData1", toWrite); + if (toWrite > 0) { - LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write too many bytes(1) %d", rt); - return ERROR; + int rt = writer->write(readPointer, toWrite); + lp("fetchData2", rt); + if (rt < 0) + { + LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns error %d", rt); + writer->done(); + return ERROR; + } + if (rt > toWrite) + { + LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write too many bytes(1) %d", rt); + writer->done(); + return ERROR; + } + readPointer += rt; + if (offset(readPointer) >= bufferSize) + readPointer -= bufferSize; + written += rt; + if (rt == 0) break; //no need to try again } - if (rt == 0) - { - LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns 0 (1)"); - return (errorIf0 ? ERROR : AGAIN); - } - readPointer += rt; - if (offset(readPointer) > (bufferSize - 1)) - readPointer = buffer; - if (rt < plen) - return AGAIN; - if (plen >= len) - return OK; - len -= rt; - written += rt; - //next part - readPointer should be at buffer now } - plen = writePointer - readPointer; - if (plen == 0) - return OK; - int rt = writer->write(readPointer, plen); - lp("fetchData2", rt); - if (rt < 0) - { - LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns error %d", rt); - return ERROR; - } - if (rt == 0) - { - LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns 0 (1)"); + writer->done(); + if (written == 0){ return (errorIf0 ? ERROR : AGAIN); } - if (rt > plen) - { - LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write too many bytes(2)"); - return ERROR; - } - readPointer += rt; - if (offset(readPointer) > (bufferSize - 1)) - readPointer = buffer; - lp("fetchData3"); - written += rt; - if (written < len) - return AGAIN; - return OK; + return (written == len)?OK:AGAIN; } int GwBuffer::findChar(char x){ lp("findChar",x); - int offset=0; + int of=0; uint8_t *p; - for (p=readPointer; p != writePointer && p < (buffer+bufferSize);p++){ - if (*p == x) return offset; - offset++; - } - if (p >= (buffer+bufferSize)){ - //we reached the end of the buffer without "hitting" the write pointer - //so we can start from the beginning if rotating... - if (! rotate) return -1; - for (p=buffer;p < writePointer && p < (buffer+bufferSize);p++){ - if (*p == x) return offset; - offset++; + for (p=readPointer; of < usedSpace();p++){ + if (offset(p) >= bufferSize) p -=bufferSize; + if (*p == x) { + lp("findChar1",of); + return of; } + of++; } + lp("findChar2"); return -1; } @@ -193,22 +144,5 @@ GwBuffer::WriteStatus GwBuffer::fetchMessage(GwBufferWriter *writer,char delimit } return AGAIN; } - if (! rotate){ - //in a non rotating buffer we discard the found message - //and copy the remain to the start - int len=pos+1; - int wr=writer->write(readPointer,len); - //in any case discard the data now - int remain=usedSpace()-len; - if (remain > 0){ - memcpy(buffer,readPointer+len,remain); - readPointer=buffer; - writePointer=buffer+remain; - } - else{ - reset(); - } - return (wr == len)?OK:ERROR; - } return fetchData(writer,pos+1,true); } \ No newline at end of file diff --git a/lib/queue/GwBuffer.h b/lib/queue/GwBuffer.h index 6c0e47b..4218543 100644 --- a/lib/queue/GwBuffer.h +++ b/lib/queue/GwBuffer.h @@ -8,6 +8,7 @@ class GwBufferWriter{ public: int id=0; //can be set be users virtual int write(const uint8_t *buffer,size_t len)=0; + virtual void done(){} virtual ~GwBufferWriter(){}; }; @@ -18,7 +19,8 @@ class GwBufferWriter{ */ class GwBuffer{ public: - static const size_t BUFFER_SIZE=1620; // app. 20 NMEA messages + static const size_t TX_BUFFER_SIZE=1620; // app. 20 NMEA messages + static const size_t RX_BUFFER_SIZE=200; // enough for 1 NMEA message... typedef enum { OK, ERROR, @@ -26,7 +28,6 @@ class GwBuffer{ } WriteStatus; private: size_t bufferSize; - bool rotate; uint8_t *buffer; uint8_t *writePointer; uint8_t *readPointer; @@ -40,12 +41,12 @@ class GwBuffer{ */ int findChar(char x); public: - GwBuffer(GwLog *logger,size_t bufferSize,bool rotate=true); + GwBuffer(GwLog *logger,size_t bufferSize); ~GwBuffer(); void reset(); size_t freeSpace(); size_t usedSpace(); - size_t addData(const uint8_t *data,size_t len); + size_t addData(const uint8_t *data,size_t len,bool addPartial=false); /** * write some data to the buffer writer * return an error if the buffer writer returned < 0 diff --git a/lib/serial/GwSerial.cpp b/lib/serial/GwSerial.cpp index c8d368c..e6c5c1f 100644 --- a/lib/serial/GwSerial.cpp +++ b/lib/serial/GwSerial.cpp @@ -13,17 +13,24 @@ class SerialWriter : public GwBufferWriter{ }; -GwSerial::GwSerial(GwLog *logger, uart_port_t num) +GwSerial::GwSerial(GwLog *logger, uart_port_t num, int id,bool allowRead) { + this->id=id; this->logger = logger; this->num = num; - this->buffer = new GwBuffer(logger,1600); + this->buffer = new GwBuffer(logger,GwBuffer::TX_BUFFER_SIZE); this->writer = new SerialWriter(num); + this->allowRead=allowRead; + if (allowRead){ + this->readBuffer=new GwBuffer(logger, GwBuffer::RX_BUFFER_SIZE); + } + } GwSerial::~GwSerial() { delete buffer; delete writer; + if (readBuffer) delete readBuffer; } int GwSerial::setup(int baud, int rxpin, int txpin) { @@ -54,13 +61,32 @@ int GwSerial::setup(int baud, int rxpin, int txpin) bool GwSerial::isInitialized() { return initialized; } size_t GwSerial::enqueue(const uint8_t *data, size_t len) { + if (! isInitialized()) return 0; return buffer->addData(data, len); } GwBuffer::WriteStatus GwSerial::write(){ + if (! isInitialized()) return GwBuffer::ERROR; return buffer->fetchData(writer,false); } -const char *GwSerial::read(){ +void GwSerial::sendToClients(const char *buf,int sourceId){ + if ( sourceId == id) return; + size_t len=strlen(buf); + size_t enqueued=enqueue((const uint8_t*)buf,len); + if (enqueued != len){ + LOG_DEBUG(GwLog::DEBUG,"GwSerial overflow on channel %d",id); + overflows++; + } +} +void GwSerial::loop(bool handleRead){ + write(); + if (! handleRead) return; char buffer[10]; - uart_read_bytes(num,(uint8_t *)(&buffer),10,0); - return NULL; + int rt=uart_read_bytes(num,(uint8_t *)(&buffer),10,0); + if (allowRead & rt > 0){ + readBuffer->addData((uint8_t *)(&buffer),rt,true); + } +} +bool GwSerial::readMessages(GwBufferWriter *writer){ + if (! allowRead) return false; + return readBuffer->fetchMessage(writer,'\n',true) == GwBuffer::OK; } \ No newline at end of file diff --git a/lib/serial/GwSerial.h b/lib/serial/GwSerial.h index cb9903f..6fbab5f 100644 --- a/lib/serial/GwSerial.h +++ b/lib/serial/GwSerial.h @@ -7,19 +7,24 @@ class SerialWriter; class GwSerial{ private: GwBuffer *buffer; - GwBuffer *readBuffer; + GwBuffer *readBuffer=NULL; GwLog *logger; SerialWriter *writer; uart_port_t num; bool initialized=false; + bool allowRead=true; + GwBuffer::WriteStatus write(); + int id=-1; + int overflows=0; + size_t enqueue(const uint8_t *data, size_t len); public: static const int bufferSize=200; - GwSerial(GwLog *logger,uart_port_t num); + GwSerial(GwLog *logger,uart_port_t num,int id,bool allowRead=true); ~GwSerial(); int setup(int baud,int rxpin,int txpin); bool isInitialized(); - size_t enqueue(const uint8_t *data, size_t len); - GwBuffer::WriteStatus write(); - const char *read(); + void sendToClients(const char *buf,int sourceId); + void loop(bool handleRead=true); + bool readMessages(GwBufferWriter *writer); }; #endif \ No newline at end of file diff --git a/lib/socketserver/GwSocketServer.cpp b/lib/socketserver/GwSocketServer.cpp index 0c12bf9..024d041 100644 --- a/lib/socketserver/GwSocketServer.cpp +++ b/lib/socketserver/GwSocketServer.cpp @@ -3,8 +3,6 @@ #include #include "GwBuffer.h" -#define WRITE_BUFFER_SIZE 1600 -#define READ_BUFFER_SIZE 200 class Writer : public GwBufferWriter{ public: wiFiClientPtr client; @@ -63,9 +61,9 @@ class GwClient{ this->client=client; this->logger=logger; this->allowRead=allowRead; - buffer=new GwBuffer(logger,WRITE_BUFFER_SIZE); + buffer=new GwBuffer(logger,GwBuffer::TX_BUFFER_SIZE); if (allowRead){ - readBuffer=new GwBuffer(logger,READ_BUFFER_SIZE,false); + readBuffer=new GwBuffer(logger,GwBuffer::RX_BUFFER_SIZE); } overflows=0; if (client != NULL){ @@ -177,7 +175,7 @@ void GwSocketServer::begin(){ MDNS.addService("_nmea-0183","_tcp",config->getInt(config->serverPort)); } -void GwSocketServer::loop() +void GwSocketServer::loop(bool handleRead) { WiFiClient client = server->available(); // listen for incoming clients @@ -230,7 +228,7 @@ void GwSocketServer::loop() } else { - client->read(); + if (handleRead) client->read(); } } } @@ -247,12 +245,6 @@ bool GwSocketServer::readMessages(GwBufferWriter *writer){ } void GwSocketServer::sendToClients(const char *buf,int source){ int len=strlen(buf); - char buffer[len+2]; - memcpy(buffer,buf,len); - buffer[len]=0x0d; - len++; - buffer[len]=0x0a; - len++; int sourceIndex=source-minId; for (int i = 0; i < maxClients; i++) { @@ -260,7 +252,7 @@ void GwSocketServer::sendToClients(const char *buf,int source){ gwClientPtr client = clients[i]; if (! client->hasClient()) continue; if ( client->client->connected() ) { - bool rt=client->enqueue((uint8_t*)buffer,len); + bool rt=client->enqueue((uint8_t*)buf,len); if (!rt){ LOG_DEBUG(GwLog::DEBUG,"overflow in send to %s",client->remoteIp.c_str()); } diff --git a/lib/socketserver/GwSocketServer.h b/lib/socketserver/GwSocketServer.h index 0b10c6d..38bc001 100644 --- a/lib/socketserver/GwSocketServer.h +++ b/lib/socketserver/GwSocketServer.h @@ -22,7 +22,7 @@ class GwSocketServer{ GwSocketServer(const GwConfigHandler *config,GwLog *logger,int minId); ~GwSocketServer(); void begin(); - void loop(); + void loop(bool handleRead=true); void sendToClients(const char *buf,int sourceId); int numClients(); bool readMessages(GwBufferWriter *writer); diff --git a/lib/wifi/GwWifi.cpp b/lib/wifi/GwWifi.cpp index 9c1945e..f610b22 100644 --- a/lib/wifi/GwWifi.cpp +++ b/lib/wifi/GwWifi.cpp @@ -41,7 +41,7 @@ bool GwWifi::connectInternal(){ } return false; } -#define RETRY_MILLIS 5000 +#define RETRY_MILLIS 10000 void GwWifi::loop(){ if (wifiClient->asBoolean() && ! clientConnected()){ long now=millis(); diff --git a/platformio.ini b/platformio.ini index 1a75656..692389f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,26 +18,35 @@ lib_deps = bblanchon/ArduinoJson@^6.18.5 ottowinter/ESPAsyncWebServer-esphome@^2.0.1 board_build.embed_files = - web/index.html.gz + generated/index.html.gz + generated/config.json.gz extra_scripts = extra_script.py +build_flags= + -Igenerated [env:m5stack-atom] board = m5stack-atom lib_deps = ${env.lib_deps} -build_flags = -D BOARD_M5ATOM +build_flags = + -D BOARD_M5ATOM + ${env.build_flags} upload_port = /dev/esp32 [env:m5stack-atom-canunit] board = m5stack-atom lib_deps = ${env.lib_deps} -build_flags = -D BOARD_M5ATOM_CANUNIT +build_flags = + -D BOARD_M5ATOM_CANUNIT + ${env.build_flags} upload_port = /dev/esp32 [env:m5stickc-atom-canunit] board = m5stack-atom lib_deps = ${env.lib_deps} -build_flags = -D BOARD_M5STICK_CANUNIT -D HAS_RTC -D HAS_M5LCD +build_flags = + -D BOARD_M5STICK_CANUNIT -D HAS_RTC -D HAS_M5LCD + ${env.build_flags} upload_port = /dev/esp32 diff --git a/src/main.cpp b/src/main.cpp index 82d6a28..5861af5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,7 +12,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#define VERSION "0.1.0" +#define VERSION "0.1.2" #include "GwHardware.h" #include @@ -65,7 +65,7 @@ Preferences preferences; // Nonvolatile storage on ESP32 - To store bool SendNMEA0183Conversion = true; // Do we send NMEA2000 -> NMEA0183 conversion bool SendSeaSmart = false; // Do we send NMEA2000 messages in SeaSmart format -N2kDataToNMEA0183 *nmea0183Converter=N2kDataToNMEA0183::create(&logger, &boatData,&NMEA2000, 0); +N2kDataToNMEA0183 *nmea0183Converter=N2kDataToNMEA0183::create(&logger, &boatData,&NMEA2000, 0, N2K_CHANNEL_ID); // Set the information for other bus devices, which messages we support const unsigned long TransmitMessages[] PROGMEM = {127489L, // Engine dynamic @@ -73,7 +73,7 @@ const unsigned long TransmitMessages[] PROGMEM = {127489L, // Engine dynamic }; // Forward declarations void HandleNMEA2000Msg(const tN2kMsg &N2kMsg); -void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg); +void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg,int id); AsyncWebServer webserver(80); @@ -135,11 +135,39 @@ void handleAsyncWebRequest(AsyncWebServerRequest *request, RequestMessage *msg, } #define JSON_OK "{\"status\":\"OK\"}" -//embedded files -extern const uint8_t indexFile[] asm("_binary_web_index_html_gz_start"); -extern const uint8_t indexFileEnd[] asm("_binary_web_index_html_gz_end"); -extern const uint8_t indexFileLen[] asm("_binary_web_index_html_gz_size"); +class EmbeddedFile; +static std::map embeddedFiles; +class EmbeddedFile { + public: + const uint8_t *start; + int len; + EmbeddedFile(String name,const uint8_t *start,int len){ + this->start=start; + this->len=len; + embeddedFiles[name]=this; + } +} ; +#define EMBED_GZ_FILE(fileName, fileExt) \ + extern const uint8_t fileName##_##fileExt##_File[] asm("_binary_generated_" #fileName "_" #fileExt "_gz_start"); \ + extern const uint8_t fileName##_##fileExt##_FileLen[] asm("_binary_generated_" #fileName "_" #fileExt "_gz_size"); \ + const EmbeddedFile fileName##_##fileExt##_Config(#fileName "." #fileExt,(const uint8_t*)fileName##_##fileExt##_File,(int)fileName##_##fileExt##_FileLen); + +EMBED_GZ_FILE(index,html) +EMBED_GZ_FILE(config,json) + +void sendEmbeddedFile(String name,String contentType,AsyncWebServerRequest *request){ + std::map::iterator it=embeddedFiles.find(name); + if (it != embeddedFiles.end()){ + EmbeddedFile* found=it->second; + AsyncWebServerResponse *response=request->beginResponse_P(200,contentType,found->start,found->len); + response->addHeader(F("Content-Encoding"), F("gzip")); + request->send(response); + } + else{ + request->send(404, "text/plain", "Not found"); + } + } String js_status(){ int numPgns=nmea0183Converter->numPgns(); @@ -165,15 +193,24 @@ GwConfigInterface *sendTCP=NULL; GwConfigInterface *sendSeasmart=NULL; GwConfigInterface *systemName=NULL; -GwSerial usbSerial(&logger, UART_NUM_0); +GwSerial usbSerial(&logger, UART_NUM_0, USB_CHANNEL_ID); class GwSerialLog : public GwLogWriter{ public: virtual ~GwSerialLog(){} virtual void write(const char *data){ - usbSerial.enqueue((const uint8_t*)data,strlen(data)); //ignore any errors + usbSerial.sendToClients(data,-1); //ignore any errors } }; + +void delayedRestart(){ + xTaskCreate([](void *p){ + delay(500); + ESP.restart(); + vTaskDelete(NULL); + },"reset",1000,NULL,0,NULL); +} + void setup() { uint8_t chipid[6]; @@ -212,13 +249,14 @@ void setup() { // Start Web Server webserver.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ - AsyncWebServerResponse *response=request->beginResponse_P(200,"text/html",(const uint8_t *)indexFile,(int)indexFileLen); - response->addHeader(F("Content-Encoding"), F("gzip")); - request->send(response); + sendEmbeddedFile("index.html","text/html",request); + }); + webserver.on("/config.json", HTTP_GET, [](AsyncWebServerRequest *request){ + sendEmbeddedFile("config.json","application/json",request); }); webserver.on("/api/reset", HTTP_GET,[](AsyncWebServerRequest *request){ logger.logDebug(GwLog::LOG,"Reset Button"); - ESP.restart(); + delayedRestart(); }); class StatusRequest : public RequestMessage{ public: @@ -268,8 +306,7 @@ void setup() { result=JSON_OK; logger.logString("update config and restart"); config.saveConfig(); - delay(100); - ESP.restart(); + delayedRestart(); } else{ DynamicJsonDocument rt(100); @@ -295,8 +332,7 @@ void setup() { config.reset(true); logger.logString("reset config, restart"); result=JSON_OK; - delay(100); - ESP.restart(); + delayedRestart(); } }; webserver.on("/api/resetConfig",HTTP_GET,[](AsyncWebServerRequest *request){ @@ -385,45 +421,82 @@ void HandleNMEA2000Msg(const tN2kMsg &N2kMsg) { socketServer.sendToClients(buf,N2K_CHANNEL_ID); } - -//***************************************************************************** -void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg) { - if ( ! sendTCP->asBoolean() && ! sendUsb->asBoolean() ) return; - - char buf[MAX_NMEA0183_MESSAGE_SIZE]; - if ( !NMEA0183Msg.GetMessage(buf, MAX_NMEA0183_MESSAGE_SIZE) ) return; +void sendBufferToChannels(const char * buffer, int sourceId){ if (sendTCP->asBoolean()){ - socketServer.sendToClients(buf,N2K_CHANNEL_ID); + socketServer.sendToClients(buffer,sourceId); } if (sendUsb->asBoolean()){ - int len=strlen(buf); - if (len >= (MAX_NMEA0183_MESSAGE_SIZE -2)) return; - buf[len]=0x0d; - len++; - buf[len]=0x0a; - len++; - buf[len]=0; - usbSerial.enqueue((const uint8_t*)buf,len); + usbSerial.sendToClients(buffer,sourceId); } } +//***************************************************************************** +void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg, int sourceId) { + if ( ! sendTCP->asBoolean() && ! sendUsb->asBoolean() ) return; + + char buf[MAX_NMEA0183_MESSAGE_SIZE+3]; + if ( !NMEA0183Msg.GetMessage(buf, MAX_NMEA0183_MESSAGE_SIZE) ) return; + size_t len=strlen(buf); + buf[len]=0x0d; + buf[len+1]=0x0a; + buf[len+2]=0; + sendBufferToChannels(buf,sourceId); +} + +void handleReceivedNmeaMessage(const char *buf, int sourceId){ + //TODO - for now only send out again + //add the conversion to N2K here + sendBufferToChannels(buf,sourceId); +} + +void handleSendAndRead(bool handleRead){ + socketServer.loop(handleRead); + usbSerial.loop(handleRead); +} class NMEAMessageReceiver : public GwBufferWriter{ + uint8_t buffer[GwBuffer::RX_BUFFER_SIZE+4]; + uint8_t *writePointer=buffer; public: virtual int write(const uint8_t *buffer,size_t len){ - char nbuf[len+1]; - memcpy(nbuf,buffer,len); - nbuf[len]=0; - logger.logDebug(GwLog::DEBUG,"NMEA[%d]: %s",id,nbuf); - return len; + size_t toWrite=GwBuffer::RX_BUFFER_SIZE-(writePointer-buffer); + if (toWrite > len) toWrite=len; + memcpy(writePointer,buffer,toWrite); + writePointer+=toWrite; + *writePointer=0; + return toWrite; + } + virtual void done(){ + if (writePointer == buffer) return; + uint8_t *p; + for (p=writePointer-1;p>=buffer && *p <= 0x20;p--){ + *p=0; + } + if (p > buffer){ + p++; + *p=0x0d; + p++; + *p=0x0a; + p++; + *p=0; + } + for (p=buffer; *p != 0 && p < writePointer && *p <= 0x20;p++){} + //very simple NMEA check + if (*p != '!' && *p != '$'){ + logger.logDebug(GwLog::DEBUG,"unknown line [%d] - ignore: %s",id,(const char *)p); + } + else{ + logger.logDebug(GwLog::DEBUG,"NMEA[%d]: %s",id,(const char *)p); + handleReceivedNmeaMessage((const char *)p,id); + //trigger sending to empty buffers + handleSendAndRead(false); + } + writePointer=buffer; } }; void loop() { gwWifi.loop(); - socketServer.loop(); - if (usbSerial.write() == GwBuffer::ERROR){ - //logger.logDebug(GwLog::DEBUG,"overflow in USB serial"); - } + handleSendAndRead(true); NMEA2000.ParseMessages(); int SourceAddress = NMEA2000.GetN2kSource(); @@ -447,6 +520,6 @@ void loop() { socketServer.readMessages(&receiver); //read channels - usbSerial.read(); + usbSerial.readMessages(&receiver); } diff --git a/tools/testServer.py b/tools/testServer.py index e5d4108..6866c1a 100755 --- a/tools/testServer.py +++ b/tools/testServer.py @@ -16,15 +16,19 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): apiurl=self.server.proxyUrl url=apiurl+p.replace("/api","") print("proxy to %s"%url) - with urllib.request.urlopen(url) as response: - self.send_response(http.HTTPStatus.OK) - self.send_header("Content-type", response.getheader("Content-type")) + try: + with urllib.request.urlopen(url,timeout=10) as response: + self.send_response(http.HTTPStatus.OK) + self.send_header("Content-type", response.getheader("Content-type")) - self.end_headers() - shutil.copyfileobj(response,self.wfile) + self.end_headers() + shutil.copyfileobj(response,self.wfile) + return None + self.send_error(http.HTTPStatus.NOT_FOUND, "api not found") + return None + except Exception as e: + self.send_error(http.HTTPStatus.INTERNAL_SERVER_ERROR, "api error %s"%str(e)) return None - self.send_error(http.HTTPStatus.NOT_FOUND, "api not found") - return None super().do_GET() def translate_path(self, path): """Translate a /-separated PATH to the local filename syntax. diff --git a/web/config.json b/web/config.json new file mode 100644 index 0000000..bd852ac --- /dev/null +++ b/web/config.json @@ -0,0 +1,99 @@ +[ + { + "name": "systemName", + "label": "system name", + "type": "string", + "default": "ESP32NMEA2K", + "check": "checkSystemName", + "description": "system name, used for the access point and for services" + }, + { + "name": "usbBaud", + "label": "USB baud rate", + "type": "list", + "default": "115200", + "description": "baud rate for the USB port", + "list": [1200,2400,4800,9600,14400,19200,28800,38400,57600,115200,230400,460800] + }, + { + "name": "sendUsb", + "label": "NMEA to USB", + "type": "boolean", + "default": "true", + "description": "send out NMEA data on the USB port" + }, + { + "name": "receiveUsb", + "label": "NMEA from USB", + "type": "boolean", + "default": "true", + "description": "receive NMEA data on the USB port" + }, + { + "name": "serverPort", + "label": "TCP port", + "type": "number", + "default": "2222", + "description": "the TCP port we listen on" + }, + { + "name": "maxClients", + "label": "max. TCP clients", + "type": "number", + "default": "10", + "check":"checkMaxClients", + "description": "the number of clients we allow to connect to us" + }, + { + "name": "sendTCP", + "label": "NMEA to TCP", + "type": "boolean", + "default": "true", + "description": "send out NMEA data to connected TCP clients" + }, + { + "name": "readTCP", + "label": "NMEA from TCP", + "type": "boolean", + "default": "true", + "description": "receive NMEA data from connected TCP clients" + }, + { + "name": "sendSeasmart", + "label": "Seasmart to TCP", + "type": "boolean", + "default": "false", + "description": "send NMEA2000 as seasmart to connected TCP clients" + }, + { + "name": "wifiClient", + "label": "wifi client", + "type": "boolean", + "default": "false", + "description": "connect to an external WIFI network" + }, + { + "name": "wifiPass", + "label": "wifi client password", + "type": "password", + "default": "", + "description": "the password for an external WIFI network" + }, + { + "name": "wifiSSID", + "label": "wifi client SSID", + "type": "string", + "default": "", + "check": "checkSSID", + "description": "the SSID for an external WIFI network" + }, + { + "name": "stopApTime", + "type": "number", + "default": "0", + "check": "checkStopApTime", + "description": "stop the access point after that many minutes if not used" + } + + +] \ No newline at end of file diff --git a/web/index.html b/web/index.html index 2cf0eb5..e8fd795 100644 --- a/web/index.html +++ b/web/index.html @@ -5,6 +5,7 @@ NMEA 2000 Gateway