diff --git a/Readme.md b/Readme.md index e1032e4..d9f8e90 100644 --- a/Readme.md +++ b/Readme.md @@ -54,19 +54,22 @@ The flash command must be (example for m5stack-atom): esptool.py --port XXXX --chip esp32 write_flash 0x1000 m5stack-atom-20211217-all.bin ``` For the meaning of the board names have a look at [Hardware](doc/Hardware.md). For details refer to the code in [platformio.ini](platformio.ini) and look for the hardware definitions in [GwHardware.h](lib/hardware/GwHardware.h). -Additionally there is a small GUI for the esptool included here at [tools/flashtool.py](tools/flashtool.py) +Additionally there is a small GUI for the esptool included here at [tools/flashtool/flashtool.py](tools/flashtool/flashtool.py) __linux users__
You can typically install the esptool (once you have python 3 installed) with ``` sudo pip install esptool ``` -To use the flashtool just copy flashtool.py and esptool.py from [tools](tools) to an empty directory. +To use the flashtool just download [flashtool.pyz](../../raw/master/tools/flashtool.pyz). ``` sudo pip install tkinter sudo pip install pyserial ``` -Afterwards run flashtool.py (potentially making it executable before). +Afterwards run flashtool.pyz with +``` +python3 flashtool.pyz +``` __windows users__
You can find a prebuild executable in tools: [esptool.exe](tools/esptool.exe). @@ -76,11 +79,10 @@ After installing the driver check with your device manager for the com port that Open a command prompt and change into the directory you downloaded the esptool.exe and the firmware binary. Flash with the command ``` -esptool.exe --port COM3 0x1000 xxxxx-xxxx-all.bin +esptool.exe --port COM3 write_flash 0x1000 xxxxx-xxxx-all.bin ``` Replace COM3 with the port shown in the device manager and the xxx with the name of the downloaded binary. -If you do not want to use the command line you can download the precompiled [flashtool.exe](../../raw/master/tools/flashtool.exe). -Just start the downloaded exe. Unfortunately some virus scanners seem to consider the exe a virus or trojan. There is not much I can do against this - the exe is simply build from flashtool.py - see [tools readme](tools/readme-esptool-win.txt). +If you do not want to use the command line you first need to install python3 from the [download page](https://www.python.org/downloads/windows/) - use the Windows 64 Bit installer. Install using the default settings. Afterwards download [flashtool.pyz](../../raw/master/tools/flashtool.pyz) and run it with a double click. Update diff --git a/lib/api/GwApi.h b/lib/api/GwApi.h index 8d83a63..afef372 100644 --- a/lib/api/GwApi.h +++ b/lib/api/GwApi.h @@ -30,6 +30,52 @@ class GwApi{ const String & getFormat() const{ return format; } + }; + + class Status{ + public: + bool wifiApOn=false; + bool wifiClientOn=false; + bool wifiClientConnected=false; + String wifiApIp; + String systemName; //is also AP SSID + String wifiApPass; + String wifiClientIp; + String wifiClientSSID; + unsigned long usbRx=0; + unsigned long usbTx=0; + unsigned long serRx=0; + unsigned long serTx=0; + unsigned long tcpSerRx=0; + unsigned long tcpSerTx=0; + int tcpClients=0; + unsigned long tcpClRx=0; + unsigned long tcpClTx=0; + bool tcpClientConnected=false; + unsigned long n2kRx=0; + unsigned long n2kTx=0; + void empty(){ + wifiApOn=false; + wifiClientOn=false; + wifiClientConnected=false; + wifiApIp=String(); + systemName=String(); //is also AP SSID + wifiApPass=String(); + wifiClientIp=String(); + wifiClientSSID=String(); + usbRx=0; + usbTx=0; + serRx=0; + serTx=0; + tcpSerRx=0; + tcpSerTx=0; + tcpClients=0; + tcpClRx=0; + tcpClTx=0; + tcpClientConnected=false; + n2kRx=0; + n2kTx=0; + } }; /** * thread safe methods - can directly be called from a user task @@ -58,6 +104,11 @@ class GwApi{ * just make sure to have the list being of appropriate size (numValues) */ virtual void getBoatDataValues(int numValues,BoatValue **list)=0; + + /** + * fill the status information + */ + virtual void getStatus(Status &status); /** * not thread safe methods * accessing boat data must only be executed from within the main thread diff --git a/lib/channel/GwChannel.cpp b/lib/channel/GwChannel.cpp new file mode 100644 index 0000000..1b703cd --- /dev/null +++ b/lib/channel/GwChannel.cpp @@ -0,0 +1,219 @@ +#include "GwChannel.h" +#include + + +class GwChannelMessageReceiver : public GwMessageFetcher{ + static const int bufferSize=GwBuffer::RX_BUFFER_SIZE+4; + uint8_t buffer[bufferSize]; + uint8_t *writePointer=buffer; + GwLog *logger; + GwChannel *channel; + GwChannel::NMEA0183Handler handler; + public: + GwChannelMessageReceiver(GwLog *logger,GwChannel *channel){ + this->logger=logger; + this->channel=channel; + } + void setHandler(GwChannel::NMEA0183Handler handler){ + this->handler=handler; + } + virtual bool handleBuffer(GwBuffer *gwbuffer){ + size_t len=fetchMessageToBuffer(gwbuffer,buffer,bufferSize-4,'\n'); + writePointer=buffer+len; + if (writePointer == buffer) return false; + 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 != '$'){ + LOG_DEBUG(GwLog::DEBUG,"unknown line [%d] - ignore: %s",id,(const char *)p); + } + else{ + LOG_DEBUG(GwLog::DEBUG,"NMEA[%d]: %s",id,(const char *)p); + if (channel->canReceive((const char *)p)){ + handler((const char *)p,id); + } + } + writePointer=buffer; + return true; + } +}; + + +GwChannel::GwChannel(GwLog *logger, + String name, + int sourceId, + int maxSourceId){ + this->logger = logger; + this->name=name; + this->sourceId=sourceId; + this->maxSourceId=sourceId; + this->countIn=new GwCounter(String("count")+name+String("in")); + this->countOut=new GwCounter(String("count")+name+String("out")); + this->impl=NULL; + this->receiver=new GwChannelMessageReceiver(logger,this); + this->actisenseReader=NULL; +} +void GwChannel::begin( + bool enabled, + bool nmeaOut, + bool nmeaIn, + String readFilter, + String writeFilter, + bool seaSmartOut, + bool toN2k, + bool readActisense, + bool writeActisense) +{ + this->enabled = enabled; + this->NMEAout = nmeaOut; + this->NMEAin = nmeaIn; + this->readFilter=readFilter.isEmpty()? + NULL: + new GwNmeaFilter(readFilter); + this->writeFilter=writeFilter.isEmpty()? + NULL: + new GwNmeaFilter(writeFilter); + this->seaSmartOut=seaSmartOut; + this->toN2k=toN2k; + this->readActisense=readActisense; + this->writeActisense=writeActisense; + if (impl && readActisense){ + channelStream=impl->getStream(false); + if (! channelStream) { + this->readActisense=false; + this->writeActisense=false; + LOG_DEBUG(GwLog::ERROR,"unable to read actisnse on %s",name.c_str()); + } + else{ + this->actisenseReader= new tActisenseReader(); + actisenseReader->SetReadStream(channelStream); + } + } +} +void GwChannel::setImpl(GwChannelInterface *impl){ + this->impl=impl; +} +void GwChannel::updateCounter(const char *msg, bool out) +{ + char key[6]; + if (msg[0] == '$') + { + strncpy(key, &msg[3], 3); + key[3] = 0; + } + else if (msg[0] == '!') + { + strncpy(key, &msg[1], 5); + key[5] = 0; + } + else{ + return; + } + if (out){ + countOut->add(key); + } + else{ + countIn->add(key); + } +} + +bool GwChannel::canSendOut(const char *buffer){ + if (! enabled || ! impl) return false; + if (! NMEAout || readActisense) return false; + if (writeFilter && ! writeFilter->canPass(buffer)) return false; + return true; +} + +bool GwChannel::canReceive(const char *buffer){ + if (! enabled) return false; + if (! NMEAin) return false; + if (readFilter && ! readFilter->canPass(buffer)) return false; + updateCounter(buffer,false); + return true; +} + +int GwChannel::getJsonSize(){ + int rt=2; + if (countIn) rt+=countIn->getJsonSize(); + if (countOut) rt+=countOut->getJsonSize(); + return rt; +} +void GwChannel::toJson(GwJsonDocument &doc){ + if (countOut) countOut->toJson(doc); + if (countIn) countIn->toJson(doc); +} +String GwChannel::toString(){ + String rt="CH:"+name; + rt+=enabled?"[ena]":"[dis]"; + rt+=NMEAin?"in,":""; + rt+=NMEAout?"out,":""; + rt+=String("RF:") + (readFilter?readFilter->toString():"[]"); + rt+=String("WF:") + (writeFilter?writeFilter->toString():"[]"); + rt+=String(",")+ (toN2k?"n2k":""); + rt+=String(",")+ (seaSmartOut?"SM":""); + rt+=String(",")+(readActisense?"AR":""); + rt+=String(",")+(writeActisense?"AW":""); + return rt; +} +void GwChannel::loop(bool handleRead, bool handleWrite){ + if (! enabled || ! impl) return; + impl->loop(handleRead,handleWrite); +} +void GwChannel::readMessages(GwChannel::NMEA0183Handler handler){ + if (! enabled || ! impl) return; + if (readActisense || ! NMEAin) return; + receiver->id=sourceId; + receiver->setHandler(handler); + impl->readMessages(receiver); +} +void GwChannel::sendToClients(const char *buffer, int sourceId){ + if (! impl) return; + if (canSendOut(buffer)){ + if(impl->sendToClients(buffer,sourceId)){ + updateCounter(buffer,true); + } + } +} +void GwChannel::parseActisense(N2kHandler handler){ + if (!enabled || ! impl || ! readActisense || ! actisenseReader) return; + tN2kMsg N2kMsg; + + while (actisenseReader->GetMessageFromStream(N2kMsg)) { + countIn->add(String(N2kMsg.PGN)); + handler(N2kMsg,sourceId); + } +} + +void GwChannel::sendActisense(const tN2kMsg &msg, int sourceId){ + if (!enabled || ! impl || ! writeActisense || ! channelStream) return; + //currently actisense only for channels with a single source id + //so we can check it here + if (isOwnSource(sourceId)) return; + countOut->add(String(msg.PGN)); + msg.SendInActisenseFormat(channelStream); +} + +bool GwChannel::isOwnSource(int id){ + if (maxSourceId < 0) return id == sourceId; + else return (id >= sourceId && id <= maxSourceId); +} + +unsigned long GwChannel::countRx(){ + if (! countIn) return 0UL; + return countIn->getGlobal(); +} +unsigned long GwChannel::countTx(){ + if (! countOut) return 0UL; + return countOut->getGlobal(); +} diff --git a/lib/channel/GwChannel.h b/lib/channel/GwChannel.h new file mode 100644 index 0000000..c187ba6 --- /dev/null +++ b/lib/channel/GwChannel.h @@ -0,0 +1,77 @@ +#pragma once +#include "GwChannelInterface.h" +#include "GwConfigItem.h" +#include "GwLog.h" +#include "GWConfig.h" +#include "GwCounter.h" +#include "GwJsonDocument.h" +#include +#include + +class GwChannelMessageReceiver; +class tActisenseReader; +class GwChannel{ + bool enabled=false; + bool NMEAout=false; + bool NMEAin=false; + GwNmeaFilter* readFilter=NULL; + GwNmeaFilter* writeFilter=NULL; + bool seaSmartOut=false; + bool toN2k=false; + bool readActisense=false; + bool writeActisense=false; + GwLog *logger; + String name; + GwCounter *countIn=NULL; + GwCounter *countOut=NULL; + GwChannelInterface *impl; + int sourceId=0; + int maxSourceId=-1; + GwChannelMessageReceiver *receiver=NULL; + tActisenseReader *actisenseReader=NULL; + Stream *channelStream=NULL; + void updateCounter(const char *msg, bool out); + public: + GwChannel( + GwLog *logger, + String name, + int sourceId, + int maxSourceId=-1); + void begin( + bool enabled, + bool nmeaOut, + bool nmeaIn, + String readFilter, + String writeFilter, + bool seaSmartOut, + bool toN2k, + bool readActisense=false, + bool writeActisense=false + ); + + void setImpl(GwChannelInterface *impl); + bool isOwnSource(int id); + void enable(bool enabled){ + this->enabled=enabled; + } + bool isEnabled(){return enabled;} + bool shouldRead(){return enabled && NMEAin;} + bool canSendOut(const char *buffer); + bool canReceive(const char *buffer); + bool sendSeaSmart(){ return seaSmartOut;} + bool sendToN2K(){return toN2k;} + int getJsonSize(); + void toJson(GwJsonDocument &doc); + String toString(); + + void loop(bool handleRead, bool handleWrite); + typedef std::function NMEA0183Handler; + void readMessages(NMEA0183Handler handler); + void sendToClients(const char *buffer, int sourceId); + typedef std::function N2kHandler ; + void parseActisense(N2kHandler handler); + void sendActisense(const tN2kMsg &msg, int sourceId); + unsigned long countRx(); + unsigned long countTx(); +}; + diff --git a/lib/channel/GwChannelInterface.h b/lib/channel/GwChannelInterface.h new file mode 100644 index 0000000..f9b076c --- /dev/null +++ b/lib/channel/GwChannelInterface.h @@ -0,0 +1,9 @@ +#pragma once +#include "GwBuffer.h" +class GwChannelInterface{ + public: + virtual void loop(bool handleRead,bool handleWrite)=0; + virtual void readMessages(GwMessageFetcher *writer)=0; + virtual size_t sendToClients(const char *buffer, int sourceId, bool partial=false)=0; + virtual Stream * getStream(bool partialWrites){ return NULL;} +}; \ No newline at end of file diff --git a/lib/channel/GwChannelList.cpp b/lib/channel/GwChannelList.cpp new file mode 100644 index 0000000..a49ad0f --- /dev/null +++ b/lib/channel/GwChannelList.cpp @@ -0,0 +1,220 @@ +#include "GwChannelList.h" +#include "GwApi.h" +#include "GwHardware.h" +#include "GwSocketServer.h" +#include "GwSerial.h" +#include "GwTcpClient.h" +class GwSerialLog : public GwLogWriter{ + static const size_t bufferSize=4096; + char *logBuffer=NULL; + int wp=0; + GwSerial *writer; + public: + GwSerialLog(GwSerial *writer){ + this->writer=writer; + logBuffer=new char[bufferSize]; + wp=0; + } + virtual ~GwSerialLog(){} + virtual void write(const char *data){ + int len=strlen(data); + if ((wp+len) >= (bufferSize-1)) return; + strncpy(logBuffer+wp,data,len); + wp+=len; + logBuffer[wp]=0; + } + virtual void flush(){ + size_t handled=0; + while (handled < wp){ + writer->flush(); + size_t rt=writer->sendToClients(logBuffer+handled,-1,true); + handled+=rt; + } + wp=0; + logBuffer[0]=0; + } + +}; + + +GwChannelList::GwChannelList(GwLog *logger, GwConfigHandler *config){ + this->logger=logger; + this->config=config; +} +void GwChannelList::allChannels(ChannelAction action){ + for (auto it=theChannels.begin();it != theChannels.end();it++){ + action(*it); + } +} +void GwChannelList::begin(bool fallbackSerial){ + LOG_DEBUG(GwLog::DEBUG,"GwChannelList::begin"); + GwChannel *channel=NULL; + //usb + if (! fallbackSerial){ + GwSerial *usb=new GwSerial(NULL,0,USB_CHANNEL_ID); + usb->setup(config->getInt(config->usbBaud),3,1); + logger->setWriter(new GwSerialLog(usb)); + logger->prefix="GWSERIAL:"; + channel=new GwChannel(logger,"USB",USB_CHANNEL_ID); + channel->setImpl(usb); + channel->begin(true, + config->getBool(config->sendUsb), + config->getBool(config->receiveUsb), + config->getString(config->usbReadFilter), + config->getString(config->usbWriteFilter), + false, + config->getBool(config->usbToN2k), + config->getBool(config->usbActisense), + config->getBool(config->usbActSend) + ); + theChannels.push_back(channel); + LOG_DEBUG(GwLog::LOG,"%s",channel->toString().c_str()); + } + //TCP server + sockets=new GwSocketServer(config,logger,MIN_TCP_CHANNEL_ID); + sockets->begin(); + channel=new GwChannel(logger,"TCP",MIN_TCP_CHANNEL_ID,MIN_TCP_CHANNEL_ID+10); + channel->setImpl(sockets); + channel->begin( + true, + config->getBool(config->sendTCP), + config->getBool(config->readTCP), + config->getString(config->tcpReadFilter), + config->getString(config->tcpWriteFilter), + config->getBool(config->sendSeasmart), + config->getBool(config->tcpToN2k), + false, + false + ); + LOG_DEBUG(GwLog::LOG,"%s",channel->toString().c_str()); + theChannels.push_back(channel); + + //serial 1 + bool serCanRead=false; + bool serCanWrite=false; + int serialrx=-1; + int serialtx=-1; + #ifdef GWSERIAL_MODE + #ifdef GWSERIAL_TX + serialtx=GWSERIAL_TX; + #endif + #ifdef GWSERIAL_RX + serialrx=GWSERIAL_RX; + #endif + if (serialrx != -1 && serialtx != -1){ + serialMode=GWSERIAL_MODE; + } + #endif + //the serial direction is from the config (only valid for mode UNI) + String serialDirection=config->getString(config->serialDirection); + //we only consider the direction if mode is UNI + if (serialMode != String("UNI")){ + serialDirection=String(""); + //if mode is UNI it depends on the selection + serCanRead=config->getBool(config->receiveSerial); + serCanWrite=config->getBool(config->sendSerial); + } + if (serialDirection == "receive" || serialDirection == "off" || serialMode == "RX") serCanWrite=false; + if (serialDirection == "send" || serialDirection == "off" || serialMode == "TX") serCanRead=false; + LOG_DEBUG(GwLog::DEBUG,"serial set up: mode=%s,direction=%s,rx=%d,tx=%d", + serialMode.c_str(),serialDirection.c_str(),serialrx,serialtx + ); + if (serialtx != -1 || serialrx != -1 ){ + LOG_DEBUG(GwLog::LOG,"creating serial interface rx=%d, tx=%d",serialrx,serialtx); + GwSerial *serial=new GwSerial(logger,1,SERIAL1_CHANNEL_ID,serCanRead); + int rt=serial->setup(config->getInt(config->serialBaud,115200),serialrx,serialtx); + LOG_DEBUG(GwLog::LOG,"starting serial returns %d",rt); + channel=new GwChannel(logger,"SER",SERIAL1_CHANNEL_ID); + channel->setImpl(serial); + channel->begin( + serCanRead || serCanWrite, + serCanWrite, + serCanRead, + config->getString(config->serialReadF), + config->getString(config->serialWriteF), + false, + config->getBool(config->serialToN2k), + false, + false + ); + LOG_DEBUG(GwLog::LOG,"%s",channel->toString().c_str()); + theChannels.push_back(channel); + } + + //tcp client + bool tclEnabled=config->getBool(config->tclEnabled); + channel=new GwChannel(logger,"TCPClient",TCP_CLIENT_CHANNEL_ID); + if (tclEnabled){ + client=new GwTcpClient(logger); + client->begin(TCP_CLIENT_CHANNEL_ID, + config->getString(config->remoteAddress), + config->getInt(config->remotePort), + config->getBool(config->readTCL) + ); + channel->setImpl(client); + } + channel->begin( + tclEnabled, + config->getBool(config->sendTCL), + config->getBool(config->readTCL), + config->getString(config->tclReadFilter), + config->getString(config->tclReadFilter), + config->getBool(config->tclSeasmart), + config->getBool(config->tclToN2k), + false, + false + ); + theChannels.push_back(channel); + LOG_DEBUG(GwLog::LOG,"%s",channel->toString().c_str()); + logger->flush(); +} +int GwChannelList::getJsonSize(){ + int rt=0; + allChannels([&](GwChannel *c){ + rt+=c->getJsonSize(); + }); + return rt+20; +} +void GwChannelList::toJson(GwJsonDocument &doc){ + if (sockets) doc["numClients"]=sockets->numClients(); + if (client){ + doc["clientCon"]=client->isConnected(); + doc["clientErr"]=client->getError(); + } + else{ + doc["clientCon"]=false; + doc["clientErr"]="disabled"; + } + allChannels([&](GwChannel *c){ + c->toJson(doc); + }); +} +GwChannel *GwChannelList::getChannelById(int sourceId){ + for (auto it=theChannels.begin();it != theChannels.end();it++){ + if ((*it)->isOwnSource(sourceId)) return *it; + } + return NULL; +} + +void GwChannelList::fillStatus(GwApi::Status &status){ + GwChannel *channel=getChannelById(USB_CHANNEL_ID); + if (channel){ + status.usbRx=channel->countRx(); + status.usbTx=channel->countTx(); + } + channel=getChannelById(SERIAL1_CHANNEL_ID); + if (channel){ + status.serRx=channel->countRx(); + status.serTx=channel->countTx(); + } + channel=getChannelById(MIN_TCP_CHANNEL_ID); + if (channel){ + status.tcpSerRx=channel->countRx(); + status.tcpSerTx=channel->countTx(); + } + channel=getChannelById(TCP_CLIENT_CHANNEL_ID); + if (channel){ + status.tcpClRx=channel->countRx(); + status.tcpClTx=channel->countTx(); + } +} \ No newline at end of file diff --git a/lib/channel/GwChannelList.h b/lib/channel/GwChannelList.h new file mode 100644 index 0000000..cbef1d4 --- /dev/null +++ b/lib/channel/GwChannelList.h @@ -0,0 +1,45 @@ +#pragma once +#include +#include +#include +#include "GwChannel.h" +#include "GwLog.h" +#include "GWConfig.h" +#include "GwJsonDocument.h" +#include "GwApi.h" + +//NMEA message channels +#define N2K_CHANNEL_ID 0 +#define USB_CHANNEL_ID 1 +#define SERIAL1_CHANNEL_ID 2 +#define TCP_CLIENT_CHANNEL_ID 3 +#define MIN_TCP_CHANNEL_ID 4 + +#define MIN_USER_TASK 200 +class GwSocketServer; +class GwTcpClient; +class GwChannelList{ + private: + GwLog *logger; + GwConfigHandler *config; + typedef std::vector ChannelList; + ChannelList theChannels; + + GwSocketServer *sockets; + GwTcpClient *client; + String serialMode=F("NONE"); + public: + GwChannelList(GwLog *logger, GwConfigHandler *config); + typedef std::function ChannelAction; + void allChannels(ChannelAction action); + //initialize + void begin(bool fallbackSerial=false); + //status + int getJsonSize(); + void toJson(GwJsonDocument &doc); + //single channel + GwChannel *getChannelById(int sourceId); + void fillStatus(GwApi::Status &status); + + +}; diff --git a/lib/config/GWConfig.cpp b/lib/config/GWConfig.cpp index 7aef68b..c6c8532 100644 --- a/lib/config/GWConfig.cpp +++ b/lib/config/GWConfig.cpp @@ -140,18 +140,21 @@ void GwNmeaFilter::parseFilter(){ // "0:1:RMB,RMC" // 0: AIS off, 1:whitelist, list of sentences if (isReady) return; + if (config.isEmpty()){ + isReady=true; + return; + } int found=0; int last=0; int index=0; - String data=config->asString(); - while ((found = data.indexOf(':',last)) >= 0){ - String tok=data.substring(last,found); + while ((found = config.indexOf(':',last)) >= 0){ + String tok=config.substring(last,found); handleToken(tok,index); last=found+1; index++; } - if (last < data.length()){ - String tok=data.substring(last); + if (last < config.length()){ + String tok=config.substring(last); handleToken(tok,index); } isReady=true; diff --git a/lib/config/GwConfigItem.h b/lib/config/GwConfigItem.h index cef920a..f97bbd5 100644 --- a/lib/config/GwConfigItem.h +++ b/lib/config/GwConfigItem.h @@ -46,7 +46,7 @@ class GwConfigInterface{ class GwNmeaFilter{ private: - GwConfigInterface *config=NULL; + String config; bool isReady=false; bool ais=true; bool blacklist=true; @@ -54,7 +54,7 @@ class GwNmeaFilter{ void handleToken(String token, int index); void parseFilter(); public: - GwNmeaFilter(GwConfigInterface *config){ + GwNmeaFilter(String config){ this->config=config; isReady=false; } diff --git a/lib/counter/GwCounter.h b/lib/counter/GwCounter.h index 6d4be4f..2280328 100644 --- a/lib/counter/GwCounter.h +++ b/lib/counter/GwCounter.h @@ -20,6 +20,7 @@ template class GwCounter{ globalFail=0; globalOk=0; } + unsigned long getGlobal(){return globalOk;} void add(T key){ globalOk++; auto it=okCounter.find(key); diff --git a/lib/exampletask/GwExampleTask.cpp b/lib/exampletask/GwExampleTask.cpp index 57fb613..18c4a77 100644 --- a/lib/exampletask/GwExampleTask.cpp +++ b/lib/exampletask/GwExampleTask.cpp @@ -88,6 +88,7 @@ void exampleTask(GwApi *api){ GwApi::BoatValue *latitude=new GwApi::BoatValue(F("Latitude")); GwApi::BoatValue *testValue=new GwApi::BoatValue(boatItemName); GwApi::BoatValue *valueList[]={longitude,latitude,testValue}; + GwApi::Status status; while(true){ delay(1000); /* @@ -162,6 +163,29 @@ void exampleTask(GwApi *api){ LOG_DEBUG(GwLog::LOG,"%s now invalid",testValue->getName().c_str()); } } + api->getStatus(status); + #define B(v) (v?"true":"false") + LOG_DEBUG(GwLog::LOG,"ST1:ap=%s,wc=%s,cc=%s", + B(status.wifiApOn), + B(status.wifiClientOn), + B(status.wifiClientConnected)); + LOG_DEBUG(GwLog::LOG,"ST2:sn=%s,ai=%s,ap=%s,cs=%s,ci=%s", + status.systemName.c_str(), + status.wifiApIp.c_str(), + status.wifiApPass.c_str(), + status.wifiClientSSID.c_str(), + status.wifiClientIp.c_str()); + LOG_DEBUG(GwLog::LOG,"ST3:ur=%ld,ut=%ld,sr=%ld,st=%ld,tr=%ld,tt=%ld,cr=%ld,ct=%ld,2r=%ld,2t=%ld", + status.usbRx, + status.usbTx, + status.serRx, + status.serTx, + status.tcpSerRx, + status.tcpSerTx, + status.tcpClRx, + status.tcpClTx, + status.n2kRx, + status.n2kTx); } vTaskDelete(NULL); diff --git a/lib/nmea0183ton2k/NMEA0183AIStoNMEA2000.h b/lib/nmea0183ton2k/NMEA0183AIStoNMEA2000.h index 4c4b440..a45ad2d 100644 --- a/lib/nmea0183ton2k/NMEA0183AIStoNMEA2000.h +++ b/lib/nmea0183ton2k/NMEA0183AIStoNMEA2000.h @@ -29,11 +29,13 @@ uint16_t DaysSince1970 = 0; class MyAisDecoder : public AIS::AisDecoder { + public: + int sourceId=-1; private: NMEA0183DataToN2K::N2kSender sender; GwLog *logger; void send(const tN2kMsg &msg){ - (*sender)(msg); + (*sender)(msg,sourceId); } AIS::DefaultSentenceParser parser; public: diff --git a/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp b/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp index e3c6585..bc0986f 100644 --- a/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp +++ b/lib/nmea0183ton2k/NMEA0183DataToN2K.cpp @@ -97,26 +97,26 @@ private: waypointMap[wpName]=newWp; return newWp.id; } - bool send(tN2kMsg &msg,String key,unsigned long minDiff){ + bool send(tN2kMsg &msg,String key,unsigned long minDiff,int sourceId){ unsigned long now=millis(); unsigned long pgn=msg.PGN; if (key == "") key=String(msg.PGN); auto it=lastSends.find(key); if (it == lastSends.end()){ lastSends[key]=now; - sender(msg); + sender(msg,sourceId); return true; } if ((it->second + minDiff) <= now){ lastSends[key]=now; - sender(msg); + sender(msg,sourceId); return true; } LOG_DEBUG(GwLog::DEBUG+1,"skipped n2k message %d",msg.PGN); return false; } - bool send(tN2kMsg &msg, String key=""){ - send(msg,key,minSendInterval); + bool send(tN2kMsg &msg, int sourceId,String key=""){ + return send(msg,key,minSendInterval,sourceId); } bool updateDouble(GwBoatItem *target,double v, int sourceId){ if (v != NMEA0183DoubleNA){ @@ -255,7 +255,7 @@ private: (tN2kFluidType)(current.selector()), fields[0], fields[1]); - send(n2kMsg, buildN2KKey(n2kMsg, current.mapping)); + send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping)); } break; case XDRBAT: @@ -263,7 +263,7 @@ private: { SetN2kPGN127508(n2kMsg, current.mapping.instanceId, fields[0], fields[1], fields[2]); - send(n2kMsg, buildN2KKey(n2kMsg, current.mapping)); + send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping)); } break; case XDRTEMP: @@ -271,7 +271,7 @@ private: SetN2kPGN130312(n2kMsg,1,current.mapping.instanceId, (tN2kTempSource)(current.selector()), fields[0],fields[1]); - send(n2kMsg,buildN2KKey(n2kMsg,current.mapping)); + send(n2kMsg,msg.sourceId,buildN2KKey(n2kMsg,current.mapping)); } break; case XDRHUMIDITY: @@ -281,7 +281,7 @@ private: fields[0], fields[1] ); - send(n2kMsg,buildN2KKey(n2kMsg,current.mapping)); + send(n2kMsg,msg.sourceId,buildN2KKey(n2kMsg,current.mapping)); } break; case XDRPRESSURE: @@ -289,7 +289,7 @@ private: SetN2kPGN130314(n2kMsg,1,current.mapping.instanceId, (tN2kPressureSource)(current.selector()), fields[0]); - send(n2kMsg,buildN2KKey(n2kMsg,current.mapping)); + send(n2kMsg,msg.sourceId,buildN2KKey(n2kMsg,current.mapping)); } break; case XDRENGINE: @@ -301,14 +301,14 @@ private: fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6], fields[7], fromDouble(fields[8]), fromDouble(fields[9]), tN2kEngineDiscreteStatus1(), tN2kEngineDiscreteStatus2()); - send(n2kMsg, buildN2KKey(n2kMsg, current.mapping)); + send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping)); } } else{ if (fillFieldList(current, fields, 13,10)){ SetN2kPGN127488(n2kMsg,current.mapping.instanceId, fields[10],fields[11],fromDouble(fields[12])); - send(n2kMsg, buildN2KKey(n2kMsg, current.mapping)); + send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping)); } } break; @@ -334,7 +334,7 @@ private: mode=xteMode(*modeChar); } SetN2kXTE(n2kMsg,1,mode,false,rmb.xte); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } uint8_t destinationId=getWaypointId(rmb.destID); uint8_t sourceId=getWaypointId(rmb.originID); @@ -357,10 +357,10 @@ private: rmb.longitude, rmb.vmg ); - send(n2kMsg); + send(n2kMsg,msg.sourceId); SetN2kPGN129285(n2kMsg,sourceId,1,1,true,true,"default"); AppendN2kPGN129285(n2kMsg,destinationId,rmb.destID,rmb.latitude,rmb.longitude); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } } void convertRMC(const SNMEA0183Msg &msg) @@ -382,25 +382,26 @@ private: { SetN2kSystemTime(n2kMsg, 1, GpsDate, GpsTime); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } if (UD(Latitude) && UD(Longitude)){ SetN2kLatLonRapid(n2kMsg,Latitude,Longitude); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } if (UD(COG) && UD(SOG)){ SetN2kCOGSOGRapid(n2kMsg,1,N2khr_true,COG,SOG); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } if (UD(Variation)){ SetN2kMagneticVariation(n2kMsg,1,N2kmagvar_Calc, getUint32(boatData->GpsDate), Variation); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } } void convertAIVDX(const SNMEA0183Msg &msg){ + aisDecoder->sourceId=msg.sourceId; aisDecoder->handleMessage(msg.line); } void convertMWV(const SNMEA0183Msg &msg){ @@ -434,7 +435,7 @@ private: } if (shouldSend){ SetN2kWindSpeed(n2kMsg,1,WindSpeed,WindAngle,n2kRef); - send(n2kMsg,String(n2kMsg.PGN)+String((int)n2kRef)); + send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)n2kRef)); } } void convertVWR(const SNMEA0183Msg &msg) @@ -475,7 +476,7 @@ private: if (shouldSend) { SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_Apparent); - send(n2kMsg,String(n2kMsg.PGN)+String((int)N2kWind_Apparent)); + send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)N2kWind_Apparent)); } } @@ -519,11 +520,11 @@ private: if (shouldSend) { SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_True_North); - send(n2kMsg,String(n2kMsg.PGN)+String((int)N2kWind_True_North)); + send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)N2kWind_True_North)); } if (WindAngleMagnetic != NMEA0183DoubleNA && shouldSend){ SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngleMagnetic, N2kWind_Magnetic); - send(n2kMsg,String(n2kMsg.PGN)+String((int)N2kWind_Magnetic)); + send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)N2kWind_Magnetic)); } } @@ -540,7 +541,7 @@ private: boatData->Variation->getDataWithDefault(N2kDoubleNA), boatData->Deviation->getDataWithDefault(N2kDoubleNA) ); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } void convertHDT(const SNMEA0183Msg &msg){ @@ -553,7 +554,7 @@ private: if (! UD(Heading)) return; tN2kMsg n2kMsg; SetN2kTrueHeading(n2kMsg,1,Heading); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } void convertHDG(const SNMEA0183Msg &msg){ double MagneticHeading=NMEA0183DoubleNA; @@ -584,7 +585,7 @@ private: UD(Deviation); tN2kMsg n2kMsg; SetN2kMagneticHeading(n2kMsg,1,MagneticHeading,Deviation,Variation); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } void convertDPT(const SNMEA0183Msg &msg){ @@ -612,7 +613,7 @@ private: if (! boatData->DepthTransducer->update(DepthBelowTransducer)) return; tN2kMsg n2kMsg; SetN2kWaterDepth(n2kMsg,1,DepthBelowTransducer,Offset); - send(n2kMsg,String(n2kMsg.PGN)+String((Offset != N2kDoubleNA)?1:0)); + send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((Offset != N2kDoubleNA)?1:0)); } typedef enum { DBS, @@ -647,7 +648,7 @@ private: if (! boatData->DepthTransducer->update(Depth,msg.sourceId)) return; tN2kMsg n2kMsg; SetN2kWaterDepth(n2kMsg,1,Depth,N2kDoubleNA); - send(n2kMsg,String(n2kMsg.PGN)+String(0)); + send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String(0)); return; } //we can only send if we have a valid depth beloww tranducer @@ -667,7 +668,7 @@ private: } tN2kMsg n2kMsg; SetN2kWaterDepth(n2kMsg,1,Depth,offset); - send(n2kMsg,String(n2kMsg.PGN)+String((offset != N2kDoubleNA)?1:0)); + send(n2kMsg,msg.sourceId,(n2kMsg.PGN)+String((offset != N2kDoubleNA)?1:0)); } } } @@ -694,7 +695,7 @@ private: tN2kMsg n2kMsg; if (! UD(RudderPosition)) return; SetN2kRudder(n2kMsg,RudderPosition); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } } @@ -711,7 +712,7 @@ private: if (MagneticHeading == NMEA0183DoubleNA) MagneticHeading=N2kDoubleNA; tN2kMsg n2kMsg; SetN2kBoatSpeed(n2kMsg,1,STW); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } void convertVTG(const SNMEA0183Msg &msg){ @@ -727,7 +728,7 @@ private: tN2kMsg n2kMsg; //TODO: maybe use MCOG if no COG? SetN2kCOGSOGRapid(n2kMsg,1,N2khr_true,COG,SOG); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } void convertZDA(const SNMEA0183Msg &msg){ time_t DateTime; @@ -751,10 +752,10 @@ private: tN2kMsg n2kMsg; if (timezoneValid){ SetN2kLocalOffset(n2kMsg,DaysSince1970,GpsTime,Timezone); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } SetN2kSystemTime(n2kMsg,1,DaysSince1970,GpsTime); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } void convertGGA(const SNMEA0183Msg &msg){ double GPSTime=NMEA0183DoubleNA; @@ -788,7 +789,7 @@ private: SatelliteCount, HDOP, boatData->PDOP->getDataWithDefault(N2kDoubleNA), 0, 0, N2kGNSSt_GPS, DGPSReferenceStationID, DGPSAge); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } void convertGSA(const SNMEA0183Msg &msg){ if (msg.FieldCount() < 17) @@ -819,7 +820,7 @@ private: if (!updateDouble(boatData->VDOP,VDOP,msg.sourceId)) return; } SetN2kGNSSDOPData(n2kMsg,1,rmode,mode,HDOP,VDOP,N2kDoubleNA); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } void convertGSV(const SNMEA0183Msg &msg){ if (msg.FieldCount() < 7){ @@ -867,7 +868,7 @@ private: } } if (hasInfos){ - send(n2kMsg); + send(n2kMsg,msg.sourceId); } } @@ -885,7 +886,7 @@ private: if (! updateDouble(boatData->GpsTime,GLL.GPSTime,msg.sourceId)) return; tN2kMsg n2kMsg; SetN2kLatLonRapid(n2kMsg,GLL.latitude,GLL.longitude); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } void convertROT(const SNMEA0183Msg &msg){ @@ -897,7 +898,7 @@ private: if (! updateDouble(boatData->ROT,ROT,msg.sourceId)) return; tN2kMsg n2kMsg; SetN2kRateOfTurn(n2kMsg,1,ROT); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } void convertXTE(const SNMEA0183Msg &msg){ if (msg.FieldCount() < 6){ @@ -916,7 +917,7 @@ private: tN2kMsg n2kMsg; tN2kXTEMode mode=xteMode(msg.Field(5)[0]); SetN2kXTE(n2kMsg,1,mode,false,xte); - send(n2kMsg); + send(n2kMsg,msg.sourceId); } //shortcut for lambda converters diff --git a/lib/nmea0183ton2k/NMEA0183DataToN2K.h b/lib/nmea0183ton2k/NMEA0183DataToN2K.h index 59ace4f..d56b80a 100644 --- a/lib/nmea0183ton2k/NMEA0183DataToN2K.h +++ b/lib/nmea0183ton2k/NMEA0183DataToN2K.h @@ -7,7 +7,7 @@ class NMEA0183DataToN2K{ public: - typedef bool (*N2kSender)(const tN2kMsg &msg); + typedef bool (*N2kSender)(const tN2kMsg &msg,int sourceId); protected: GwLog * logger; GwBoatData *boatData; diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp index 240823c..53a8019 100644 --- a/lib/nmea2kto0183/N2kDataToNMEA0183.cpp +++ b/lib/nmea2kto0183/N2kDataToNMEA0183.cpp @@ -34,12 +34,11 @@ N2kDataToNMEA0183::N2kDataToNMEA0183(GwLog * logger, GwBoatData *boatData, - tSendNMEA0183MessageCallback callback, int id,String talkerId) + SendNMEA0183MessageCallback callback, String talkerId) { - this->SendNMEA0183MessageCallback=callback; + this->sendNMEA0183MessageCallback=callback; strncpy(this->talkerId,talkerId.c_str(),2); - this->talkerId[2]=0; - sourceId=id; + this->talkerId[2]=0; } @@ -50,7 +49,7 @@ void N2kDataToNMEA0183::loop() { //***************************************************************************** void N2kDataToNMEA0183::SendMessage(const tNMEA0183Msg &NMEA0183Msg) { - if ( SendNMEA0183MessageCallback != 0 ) SendNMEA0183MessageCallback(NMEA0183Msg, sourceId); + sendNMEA0183MessageCallback(NMEA0183Msg, sourceId); } /** @@ -148,8 +147,9 @@ private: virtual String handledKeys(){ return converters.handledKeys(); } - virtual void HandleMsg(const tN2kMsg &N2kMsg) + virtual void HandleMsg(const tN2kMsg &N2kMsg, int sourceId) { + this->sourceId=sourceId; String key=String(N2kMsg.PGN); bool rt=converters.handleMessage(key,N2kMsg,this); if (! rt){ @@ -1489,9 +1489,9 @@ private: public: N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData, - tSendNMEA0183MessageCallback callback, int sourceId, + SendNMEA0183MessageCallback callback, String talkerId, GwXDRMappings *xdrMappings, int minXdrInterval) - : N2kDataToNMEA0183(logger, boatData, callback,sourceId,talkerId) + : N2kDataToNMEA0183(logger, boatData, callback,talkerId) { LastPosSend = 0; lastLoopTime = 0; @@ -1516,9 +1516,9 @@ private: N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData, - tSendNMEA0183MessageCallback callback, int sourceId,String talkerId, GwXDRMappings *xdrMappings, + SendNMEA0183MessageCallback callback, String talkerId, GwXDRMappings *xdrMappings, int minXdrInterval){ LOG_DEBUG(GwLog::LOG,"creating N2kToNMEA0183"); - return new N2kToNMEA0183Functions(logger,boatData,callback, sourceId,talkerId,xdrMappings,minXdrInterval); + return new N2kToNMEA0183Functions(logger,boatData,callback, talkerId,xdrMappings,minXdrInterval); } //***************************************************************************** diff --git a/lib/nmea2kto0183/N2kDataToNMEA0183.h b/lib/nmea2kto0183/N2kDataToNMEA0183.h index 0fe40f3..a317cdd 100644 --- a/lib/nmea2kto0183/N2kDataToNMEA0183.h +++ b/lib/nmea2kto0183/N2kDataToNMEA0183.h @@ -22,6 +22,7 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef _N2KDATATONMEA0183_H #define _N2KDATATONMEA0183_H +#include #include #include @@ -34,22 +35,22 @@ class GwJsonDocument; class N2kDataToNMEA0183 { public: - using tSendNMEA0183MessageCallback = void (*)(const tNMEA0183Msg &NMEA0183Msg, int id); + typedef std::function SendNMEA0183MessageCallback; protected: GwLog *logger; GwBoatData *boatData; - int sourceId; + int sourceId=0; char talkerId[3]; - tSendNMEA0183MessageCallback SendNMEA0183MessageCallback; + SendNMEA0183MessageCallback sendNMEA0183MessageCallback; void SendMessage(const tNMEA0183Msg &NMEA0183Msg); N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData, - tSendNMEA0183MessageCallback callback, int sourceId,String talkerId); + SendNMEA0183MessageCallback callback, String talkerId); public: - static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, tSendNMEA0183MessageCallback callback, - int sourceId,String talkerId, GwXDRMappings *xdrMappings,int minXdrInterval=100); - virtual void HandleMsg(const tN2kMsg &N2kMsg) = 0; + static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, SendNMEA0183MessageCallback callback, + String talkerId, GwXDRMappings *xdrMappings,int minXdrInterval=100); + virtual void HandleMsg(const tN2kMsg &N2kMsg, int sourceId) = 0; virtual void loop(); virtual ~N2kDataToNMEA0183(){} virtual unsigned long* handledPgns()=0; diff --git a/lib/queue/GwBuffer.h b/lib/queue/GwBuffer.h index 22022c9..17490ec 100644 --- a/lib/queue/GwBuffer.h +++ b/lib/queue/GwBuffer.h @@ -22,7 +22,7 @@ typedef size_t (*GwBufferHandleFunction)(uint8_t *buffer, size_t len, void *para class GwBuffer{ public: static const size_t TX_BUFFER_SIZE=1620; // app. 20 NMEA messages - static const size_t RX_BUFFER_SIZE=400; // enough for 1 NMEA message or actisense message + static const size_t RX_BUFFER_SIZE=600; // enough for 1 NMEA message or actisense message or seasmart message typedef enum { OK, ERROR, diff --git a/lib/serial/GwSerial.cpp b/lib/serial/GwSerial.cpp index 1539bb3..6f35856 100644 --- a/lib/serial/GwSerial.cpp +++ b/lib/serial/GwSerial.cpp @@ -96,7 +96,7 @@ size_t GwSerial::sendToClients(const char *buf,int sourceId,bool partial){ } return enqueued; } -void GwSerial::loop(bool handleRead){ +void GwSerial::loop(bool handleRead,bool handleWrite){ write(); if (! isInitialized()) return; if (! handleRead) return; @@ -116,10 +116,10 @@ void GwSerial::loop(bool handleRead){ serial->readBytes(buffer,available); } } -bool GwSerial::readMessages(GwMessageFetcher *writer){ - if (! isInitialized()) return false; - if (! allowRead) return false; - return writer->handleBuffer(readBuffer); +void GwSerial::readMessages(GwMessageFetcher *writer){ + if (! isInitialized()) return; + if (! allowRead) return; + writer->handleBuffer(readBuffer); } void GwSerial::flush(){ diff --git a/lib/serial/GwSerial.h b/lib/serial/GwSerial.h index 1fd23d6..b3880fc 100644 --- a/lib/serial/GwSerial.h +++ b/lib/serial/GwSerial.h @@ -3,8 +3,9 @@ #include "HardwareSerial.h" #include "GwLog.h" #include "GwBuffer.h" +#include "GwChannelInterface.h" class GwSerialStream; -class GwSerial{ +class GwSerial : public GwChannelInterface{ private: GwBuffer *buffer; GwBuffer *readBuffer=NULL; @@ -23,11 +24,11 @@ class GwSerial{ ~GwSerial(); int setup(int baud,int rxpin,int txpin); bool isInitialized(); - size_t sendToClients(const char *buf,int sourceId,bool partial=false); - void loop(bool handleRead=true); - bool readMessages(GwMessageFetcher *writer); + virtual size_t sendToClients(const char *buf,int sourceId,bool partial=false); + virtual void loop(bool handleRead=true,bool handleWrite=true); + virtual void readMessages(GwMessageFetcher *writer); void flush(); - Stream *getStream(bool partialWrites); + virtual Stream *getStream(bool partialWrites); friend GwSerialStream; }; #endif \ No newline at end of file diff --git a/lib/socketserver/GwSocketConnection.cpp b/lib/socketserver/GwSocketConnection.cpp new file mode 100644 index 0000000..b518b4c --- /dev/null +++ b/lib/socketserver/GwSocketConnection.cpp @@ -0,0 +1,214 @@ +#include "GwSocketConnection.h" +IPAddress GwSocketConnection::remoteIP(int fd) +{ + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getpeername(fd, (struct sockaddr *)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return IPAddress((uint32_t)(s->sin_addr.s_addr)); +} +GwSocketConnection::GwSocketConnection(GwLog *logger, int id, bool allowRead) +{ + this->logger = logger; + this->allowRead = allowRead; + String bufName = "Sock("; + bufName += String(id); + bufName += ")"; + buffer = new GwBuffer(logger, GwBuffer::TX_BUFFER_SIZE, bufName + "wr"); + if (allowRead) + { + readBuffer = new GwBuffer(logger, GwBuffer::RX_BUFFER_SIZE, bufName + "rd"); + } + overflows = 0; +} +void GwSocketConnection::setClient(int fd) +{ + this->fd = fd; + buffer->reset("new client"); + if (readBuffer) + readBuffer->reset("new client"); + overflows = 0; + pendingWrite = false; + writeError = false; + lastWrite = 0; + if (fd >= 0) + { + remoteIpAddress = remoteIP(fd).toString(); + } + else + { + remoteIpAddress = String("---"); + } +} +bool GwSocketConnection::hasClient() +{ + return fd >= 0; +} +void GwSocketConnection::stop() +{ + if (fd >= 0) + { + close(fd); + fd = -1; + } +} +GwSocketConnection::~GwSocketConnection() +{ + delete buffer; + if (readBuffer) + delete readBuffer; +} +bool GwSocketConnection::connected() +{ + if (fd >= 0) + { + uint8_t dummy; + int res = recv(fd, &dummy, 0, MSG_DONTWAIT); + // avoid unused var warning by gcc + (void)res; + // recv only sets errno if res is <= 0 + if (res <= 0) + { + switch (errno) + { + case EWOULDBLOCK: + case ENOENT: //caused by vfs + return true; + break; + case ENOTCONN: + case EPIPE: + case ECONNRESET: + case ECONNREFUSED: + case ECONNABORTED: + return false; + break; + default: + return true; + } + } + else + { + return true; + } + } + return false; +} + +bool GwSocketConnection::enqueue(uint8_t *data, size_t len) +{ + if (len == 0) + return true; + size_t rt = buffer->addData(data, len); + if (rt < len) + { + LOG_DEBUG(GwLog::LOG, "overflow on %s", remoteIpAddress.c_str()); + overflows++; + return false; + } + return true; +} +bool GwSocketConnection::hasData() +{ + return buffer->usedSpace() > 0; +} +bool GwSocketConnection::handleError(int res, bool errorIf0) +{ + if (res == 0 && errorIf0) + { + LOG_DEBUG(GwLog::LOG, "client shutdown (recv 0) on %s", remoteIpAddress.c_str()); + stop(); + return false; + } + if (res < 0) + { + if (errno != EAGAIN) + { + LOG_DEBUG(GwLog::LOG, "client read error %d on %s", errno, remoteIpAddress.c_str()); + stop(); + return false; + } + return false; + } + return true; +} +GwBuffer::WriteStatus GwSocketConnection::write() +{ + if (!hasClient()) + { + LOG_DEBUG(GwLog::LOG, "write called on empty client"); + return GwBuffer::ERROR; + } + if (!buffer->usedSpace()) + { + pendingWrite = false; + return GwBuffer::OK; + } + buffer->fetchData( + -1, [](uint8_t *buffer, size_t len, void *param) -> size_t + { + GwSocketConnection *c = (GwSocketConnection *)param; + int res = send(c->fd, (void *)buffer, len, MSG_DONTWAIT); + if (!c->handleError(res, false)) + return 0; + if (res >= len) + { + c->pendingWrite = false; + } + else + { + if (!c->pendingWrite) + { + c->lastWrite = millis(); + c->pendingWrite = true; + } + else + { + //we need to check if we have still not been able + //to write until timeout + if (millis() >= (c->lastWrite + c->writeTimeout)) + { + c->logger->logDebug(GwLog::ERROR, "Write timeout on channel %s", c->remoteIpAddress.c_str()); + c->writeError = true; + } + } + } + return res; + }, + this); + if (writeError) + { + LOG_DEBUG(GwLog::DEBUG + 1, "write error on %s", remoteIpAddress.c_str()); + return GwBuffer::ERROR; + } + + return GwBuffer::OK; +} + +bool GwSocketConnection::read() +{ + if (!allowRead) + { + size_t maxLen = 100; + char buffer[maxLen]; + int res = recv(fd, (void *)buffer, maxLen, MSG_DONTWAIT); + return handleError(res); + } + readBuffer->fillData( + -1, [](uint8_t *buffer, size_t len, void *param) -> size_t + { + GwSocketConnection *c = (GwSocketConnection *)param; + int res = recv(c->fd, (void *)buffer, len, MSG_DONTWAIT); + if (!c->handleError(res)) + return 0; + return res; + }, + this); + return true; +} +bool GwSocketConnection::messagesFromBuffer(GwMessageFetcher *writer) +{ + if (!allowRead) + return false; + return writer->handleBuffer(readBuffer); +} + diff --git a/lib/socketserver/GwSocketConnection.h b/lib/socketserver/GwSocketConnection.h new file mode 100644 index 0000000..8b693ee --- /dev/null +++ b/lib/socketserver/GwSocketConnection.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include +#include "GwBuffer.h" +class GwSocketConnection +{ +public: + int fd=-1; + int overflows; + String remoteIpAddress; + +private: + unsigned long lastWrite = 0; + unsigned long writeTimeout = 10000; + bool pendingWrite = false; + bool writeError = false; + bool allowRead; + GwBuffer *buffer = NULL; + GwBuffer *readBuffer = NULL; + GwLog *logger; + +public: + static IPAddress remoteIP(int fd); + GwSocketConnection(GwLog *logger, int id, bool allowRead = false); + void setClient(int fd); + bool hasClient(); + void stop(); + ~GwSocketConnection(); + bool connected(); + bool enqueue(uint8_t *data, size_t len); + bool hasData(); + bool handleError(int res, bool errorIf0 = true); + GwBuffer::WriteStatus write(); + bool read(); + bool messagesFromBuffer(GwMessageFetcher *writer); +}; diff --git a/lib/socketserver/GwSocketServer.cpp b/lib/socketserver/GwSocketServer.cpp index f9c7f6d..1bee3a6 100644 --- a/lib/socketserver/GwSocketServer.cpp +++ b/lib/socketserver/GwSocketServer.cpp @@ -2,189 +2,92 @@ #include #include #include "GwBuffer.h" +#include "GwSocketConnection.h" -class GwClient{ - public: - wiFiClientPtr client; - int overflows; - String remoteIp; - private: - unsigned long lastWrite=0; - unsigned long writeTimeout=10000; - bool pendingWrite=false; - bool writeError=false; - bool allowRead; - GwBuffer *buffer=NULL; - GwBuffer *readBuffer=NULL; - GwLog *logger; - public: - GwClient(wiFiClientPtr client,GwLog *logger,int id, bool allowRead=false){ - this->client=client; - this->logger=logger; - this->allowRead=allowRead; - String bufName="Sock("; - bufName+=String(id); - bufName+=")"; - buffer=new GwBuffer(logger,GwBuffer::TX_BUFFER_SIZE,bufName+"wr"); - if (allowRead){ - readBuffer=new GwBuffer(logger,GwBuffer::RX_BUFFER_SIZE,bufName+"rd"); - } - overflows=0; - if (client != NULL){ - remoteIp=client->remoteIP().toString(); - } - } - void setClient(wiFiClientPtr client){ - this->client=client; - buffer->reset("new client"); - if (readBuffer) readBuffer->reset("new client"); - overflows=0; - pendingWrite=false; - writeError=false; - lastWrite=0; - if (client){ - remoteIp=client->remoteIP().toString(); - } - else{ - remoteIp=String("---"); - } - } - bool hasClient(){ - return client != NULL; - } - ~GwClient(){ - delete buffer; - if (readBuffer) delete readBuffer; - } - bool enqueue(uint8_t *data, size_t len){ - if (len == 0) return true; - size_t rt=buffer->addData(data,len); - if (rt < len){ - LOG_DEBUG(GwLog::LOG,"overflow on %s",remoteIp.c_str()); - overflows++; - return false; - } - return true; - } - bool hasData(){ - return buffer->usedSpace() > 0; - } - bool handleError(int res,bool errorIf0=true){ - if (res == 0 && errorIf0){ - LOG_DEBUG(GwLog::LOG,"client shutdown (recv 0) on %s",remoteIp.c_str()); - client->stop(); - return false; - } - if (res < 0){ - if (errno != EAGAIN){ - LOG_DEBUG(GwLog::LOG,"client read error %d on %s",errno,remoteIp.c_str()); - client->stop(); - return false; - } - return false; - } - return true; - } - GwBuffer::WriteStatus write(){ - if (! hasClient()) { - LOG_DEBUG(GwLog::LOG,"write called on empty client"); - return GwBuffer::ERROR; - } - if (! buffer->usedSpace()){ - pendingWrite=false; - return GwBuffer::OK; - } - buffer->fetchData(-1,[](uint8_t *buffer, size_t len, void *param)->size_t{ - GwClient *c=(GwClient*)param; - int res = send(c->client->fd(), (void*) buffer, len, MSG_DONTWAIT); - if (! c->handleError(res,false)) return 0; - if (res >= len){ - c->pendingWrite=false; - } - else{ - if (!c->pendingWrite){ - c->lastWrite=millis(); - c->pendingWrite=true; - } - else{ - //we need to check if we have still not been able - //to write until timeout - if (millis() >= (c->lastWrite+c->writeTimeout)){ - c->logger->logDebug(GwLog::ERROR,"Write timeout on channel %s",c->remoteIp.c_str()); - c->writeError=true; - } - } - } - return res; - },this); - if (writeError){ - LOG_DEBUG(GwLog::DEBUG+1,"write error on %s",remoteIp.c_str()); - return GwBuffer::ERROR; - } - - return GwBuffer::OK; - } - - bool read(){ - if (! allowRead){ - size_t maxLen=100; - char buffer[maxLen]; - int res = recv(client->fd(), (void*) buffer, maxLen, MSG_DONTWAIT); - return handleError(res); - } - readBuffer->fillData(-1,[](uint8_t *buffer, size_t len, void *param)->size_t{ - GwClient *c=(GwClient*)param; - int res = recv(c->client->fd(), (void*) buffer, len, MSG_DONTWAIT); - if (! c->handleError(res)) return 0; - return res; - },this); - return true; - } - bool messagesFromBuffer(GwMessageFetcher *writer){ - if (! allowRead) return false; - return writer->handleBuffer(readBuffer); - } -}; - - -GwSocketServer::GwSocketServer(const GwConfigHandler *config,GwLog *logger,int minId){ - this->config=config; - this->logger=logger; - this->minId=minId; - maxClients=1; - allowReceive=false; -} -void GwSocketServer::begin(){ - maxClients=config->getInt(config->maxClients); - allowReceive=config->getBool(config->readTCP); - clients=new gwClientPtr[maxClients]; - for (int i=0;igetInt(config->serverPort),maxClients+1); - server->begin(); - LOG_DEBUG(GwLog::LOG,"Socket server created, port=%d", - config->getInt(config->serverPort)); - MDNS.addService("_nmea-0183","_tcp",config->getInt(config->serverPort)); - -} -void GwSocketServer::loop(bool handleRead,bool handleWrite) +GwSocketServer::GwSocketServer(const GwConfigHandler *config, GwLog *logger, int minId) { - if (! clients) return; - WiFiClient client = server->available(); // listen for incoming clients - - if (client) + this->config = config; + this->logger = logger; + this->minId = minId; + maxClients = 1; + allowReceive = false; +} +bool GwSocketServer::createListener() +{ + struct sockaddr_in server; + listener = socket(AF_INET, SOCK_STREAM, 0); + if (listener < 0) { - LOG_DEBUG(GwLog::LOG,"new client connected from %s", - client.remoteIP().toString().c_str()); - fcntl(client.fd(), F_SETFL, O_NONBLOCK); + return false; + } + int enable = 1; + setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + server.sin_port = htons(listenerPort); + if (bind(listener, (struct sockaddr *)&server, sizeof(server)) < 0) + return false; + if (listen(listener, maxClients) < 0) + return false; + fcntl(listener, F_SETFL, O_NONBLOCK); + return true; +} +void GwSocketServer::begin() +{ + maxClients = config->getInt(config->maxClients); + allowReceive = config->getBool(config->readTCP); + listenerPort=config->getInt(config->serverPort); + clients = new GwSocketConnection*[maxClients]; + for (int i = 0; i < maxClients; i++) + { + clients[i] = new GwSocketConnection(logger, i, allowReceive); + } + if (! createListener()){ + listener=-1; + LOG_DEBUG(GwLog::ERROR,"Unable to create listener"); + return; + } + LOG_DEBUG(GwLog::LOG, "Socket server created, port=%d", + config->getInt(config->serverPort)); + MDNS.addService("_nmea-0183", "_tcp", config->getInt(config->serverPort)); +} +int GwSocketServer::available() +{ + if (listener < 0) + return -1; + int client_sock; + struct sockaddr_in _client; + int cs = sizeof(struct sockaddr_in); + client_sock = lwip_accept_r(listener, (struct sockaddr *)&_client, (socklen_t *)&cs); + if (client_sock >= 0) + { + int val = 1; + if (setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&val, sizeof(int)) == ESP_OK) + { + if (setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(int)) == ESP_OK) + fcntl(client_sock, F_SETFL, O_NONBLOCK); + return client_sock; + } + close(client_sock); + } + return -1; +} +void GwSocketServer::loop(bool handleRead, bool handleWrite) +{ + if (!clients) + return; + int client = available(); // listen for incoming clients + if (client >= 0) + { + LOG_DEBUG(GwLog::LOG, "new client connected from %s", + GwSocketConnection::remoteIP(client).toString().c_str()); bool canHandle = false; for (int i = 0; i < maxClients; i++) { if (!clients[i]->hasClient()) { - clients[i]->setClient(wiFiClientPtr(new WiFiClient(client))); - LOG_DEBUG(GwLog::LOG,"set client as number %d", i); + clients[i]->setClient(client); + LOG_DEBUG(GwLog::LOG, "set client as number %d", i); canHandle = true; break; } @@ -192,7 +95,7 @@ void GwSocketServer::loop(bool handleRead,bool handleWrite) if (!canHandle) { logger->logDebug(GwLog::ERROR, "no space to store client, disconnect"); - client.stop(); + close(client); } } if (handleWrite) @@ -200,69 +103,83 @@ void GwSocketServer::loop(bool handleRead,bool handleWrite) //sending for (int i = 0; i < maxClients; i++) { - gwClientPtr client = clients[i]; + GwSocketConnection *client = clients[i]; if (!client->hasClient()) continue; GwBuffer::WriteStatus rt = client->write(); if (rt == GwBuffer::ERROR) { - LOG_DEBUG(GwLog::ERROR, "write error on %s, closing", client->remoteIp.c_str()); - client->client->stop(); + LOG_DEBUG(GwLog::ERROR, "write error on %s, closing", client->remoteIpAddress.c_str()); + client->stop(); } } } for (int i = 0; i < maxClients; i++) { - gwClientPtr client = clients[i]; + GwSocketConnection *client = clients[i]; if (!client->hasClient()) continue; - if (!client->client->connected()) + if (!client->connected()) { - LOG_DEBUG(GwLog::LOG,"client %d disconnect %s", i, client->remoteIp.c_str()); - client->client->stop(); - client->setClient(NULL); + LOG_DEBUG(GwLog::LOG, "client %d disconnect %s", i, client->remoteIpAddress.c_str()); + client->stop(); } else { - if (handleRead) client->read(); + if (handleRead) + client->read(); } } } -bool GwSocketServer::readMessages(GwMessageFetcher *writer){ - if (! allowReceive || ! clients) return false; - bool hasMessages=false; - for (int i = 0; i < maxClients; i++){ - writer->id=minId+i; - if (!clients[i]->hasClient()) continue; - if (clients[i]->messagesFromBuffer(writer)) hasMessages=true; - } - return hasMessages; -} -void GwSocketServer::sendToClients(const char *buf,int source){ - if (! clients) return; - int len=strlen(buf); - int sourceIndex=source-minId; +void GwSocketServer::readMessages(GwMessageFetcher *writer) +{ + if (!allowReceive || !clients) + return; for (int i = 0; i < maxClients; i++) { - if (i == sourceIndex)continue; //never send out to the source we received from - gwClientPtr client = clients[i]; - if (! client->hasClient()) continue; - if ( client->client->connected() ) { - client->enqueue((uint8_t*)buf,len); + writer->id = minId + i; + if (!clients[i]->hasClient()) + continue; + clients[i]->messagesFromBuffer(writer); + } + return; +} +size_t GwSocketServer::sendToClients(const char *buf, int source,bool partial) +{ + if (!clients) + return 0; + bool hasSend=false; + int len = strlen(buf); + int sourceIndex = source - minId; + for (int i = 0; i < maxClients; i++) + { + if (i == sourceIndex) + continue; //never send out to the source we received from + GwSocketConnection *client = clients[i]; + if (!client->hasClient()) + continue; + if (client->connected()) + { + if(client->enqueue((uint8_t *)buf, len)) hasSend=true; } } + return hasSend?len:0; } -int GwSocketServer::numClients(){ - if (! clients) return 0; - int num=0; - for (int i = 0; i < maxClients; i++){ - if (clients[i]->hasClient()) num++; +int GwSocketServer::numClients() +{ + if (!clients) + return 0; + int num = 0; + for (int i = 0; i < maxClients; i++) + { + if (clients[i]->hasClient()) + num++; } return num; } -GwSocketServer::~GwSocketServer(){ - +GwSocketServer::~GwSocketServer() +{ } \ No newline at end of file diff --git a/lib/socketserver/GwSocketServer.h b/lib/socketserver/GwSocketServer.h index 33761d3..248fc95 100644 --- a/lib/socketserver/GwSocketServer.h +++ b/lib/socketserver/GwSocketServer.h @@ -3,28 +3,29 @@ #include "GWConfig.h" #include "GwLog.h" #include "GwBuffer.h" +#include "GwChannelInterface.h" #include -#include -using wiFiClientPtr = std::shared_ptr; -class GwClient; -using gwClientPtr = std::shared_ptr; -class GwSocketServer{ +class GwSocketConnection; +class GwSocketServer: public GwChannelInterface{ private: const GwConfigHandler *config; GwLog *logger; - gwClientPtr *clients=NULL; - WiFiServer *server=NULL; + GwSocketConnection **clients=NULL; + int listener=-1; + int listenerPort=-1; bool allowReceive; int maxClients; int minId; + bool createListener(); + int available(); public: GwSocketServer(const GwConfigHandler *config,GwLog *logger,int minId); ~GwSocketServer(); void begin(); - void loop(bool handleRead=true,bool handleWrite=true); - void sendToClients(const char *buf,int sourceId); + virtual void loop(bool handleRead=true,bool handleWrite=true); + virtual size_t sendToClients(const char *buf,int sourceId, bool partialWrite=false); int numClients(); - bool readMessages(GwMessageFetcher *writer); + virtual void readMessages(GwMessageFetcher *writer); }; #endif \ No newline at end of file diff --git a/lib/socketserver/GwTcpClient.cpp b/lib/socketserver/GwTcpClient.cpp new file mode 100644 index 0000000..5a81470 --- /dev/null +++ b/lib/socketserver/GwTcpClient.cpp @@ -0,0 +1,287 @@ +#include "GwTcpClient.h" +#include +#include + +class ResolveArgs{ + public: + String host; + uint32_t timeout; + GwTcpClient *client; +}; + +bool GwTcpClient::hasConfig(){ + return configured; +} +bool GwTcpClient::isConnected(){ + return state == C_CONNECTED; +} +void GwTcpClient::stop() +{ + if (connection && connection->hasClient()) + { + LOG_DEBUG(GwLog::DEBUG, "stopping tcp client"); + connection->stop(); + } + state = C_DISABLED; +} +void GwTcpClient::startResolving(){ + LOG_DEBUG(GwLog::DEBUG,"TcpClient::resolveHost to %s:%d", + remoteAddress.c_str(),port); + state = C_INITIALIZED; + IPAddress addr; + if (! addr.fromString(remoteAddress)){ + if (remoteAddress.endsWith(".local")){ + //try to resolve + resolveHost(remoteAddress.substring(0,remoteAddress.length()-6)); + } + else{ + error="invalid ip "+remoteAddress; + LOG_DEBUG(GwLog::ERROR,"%s",error.c_str()); + return; + } + } + else{ + setResolved(addr,true); + startConnection(); + } +} +void GwTcpClient::startConnection() +{ + LOG_DEBUG(GwLog::DEBUG,"TcpClient::startConnection to %s:%d", + remoteAddress.c_str(),port); + ResolvedAddress addr=getResolved(); + state = C_INITIALIZED; + connectStart=millis(); + if (! addr.resolved){ + error="unable to resolve "+remoteAddress; + LOG_DEBUG(GwLog::ERROR,"%s",error.c_str()); + return; + } + else{ + if (error.isEmpty()) error="connecting..."; + } + uint32_t ip_addr = addr.address; + struct sockaddr_in serveraddr; + memset((char *) &serveraddr, 0, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + memcpy((void *)&serveraddr.sin_addr.s_addr, (const void *)(&ip_addr), 4); + serveraddr.sin_port = htons(port); + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + error="unable to create socket"; + LOG_DEBUG(GwLog::ERROR,"unable to create socket: %d", errno); + return; + } + fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK ); + int res = lwip_connect_r(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)); + if (res < 0 ) { + if (errno != EINPROGRESS){ + error=String("connect error ")+String(strerror(errno)); + LOG_DEBUG(GwLog::ERROR,"connect on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno)); + close(sockfd); + return; + } + state=C_CONNECTING; + connection->setClient(sockfd); + LOG_DEBUG(GwLog::DEBUG,"TcpClient connecting..."); + } + else{ + state=C_CONNECTED; + connection->setClient(sockfd); + LOG_DEBUG(GwLog::DEBUG,"TcpClient connected"); + } +} +void GwTcpClient::checkConnection() +{ + unsigned long now=millis(); + LOG_DEBUG(GwLog::DEBUG+3,"TcpClient::checkConnection state=%d, start=%ul, now=%ul", + (int)state,connectStart,now); + if (state == C_RESOLVING){ + //TODO: timeout??? + return; + } + if (state == C_RESOLVED){ + startConnection(); + return; + } + if (! connection->hasClient()){ + state = hasConfig()?C_INITIALIZED:C_DISABLED; + } + if (state == C_INITIALIZED){ + if ((now - connectStart) > CON_TIMEOUT){ + LOG_DEBUG(GwLog::LOG,"retry connect to %s",remoteAddress.c_str()); + startResolving(); + } + return; + } + if (state != C_CONNECTING){ + return; + } + fd_set fdset; + struct timeval tv; + FD_ZERO(&fdset); + int sockfd=connection->fd; + FD_SET(connection->fd, &fdset); + tv.tv_sec = 0; + tv.tv_usec = 0; + int res = select(sockfd + 1, nullptr, &fdset, nullptr, &tv); + if (res < 0) { + error=String("select error ")+String(strerror(errno)); + LOG_DEBUG(GwLog::ERROR,"select on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno)); + connection->stop(); + return; + } else if (res == 0) { + //still connecting + if ((now - connectStart) >= CON_TIMEOUT){ + error="connect timeout"; + LOG_DEBUG(GwLog::ERROR,"connect timeout to %s, retry",remoteAddress.c_str()); + connection->stop(); + return; + } + return; + } else { + int sockerr; + socklen_t len = (socklen_t)sizeof(int); + res = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &sockerr, &len); + + if (res < 0) { + error="getsockopt failed"; + LOG_DEBUG(GwLog::ERROR,"getsockopt on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno)); + connection->stop(); + return; + } + if (sockerr != 0) { + error=String("socket error ")+String(strerror(sockerr)); + LOG_DEBUG(GwLog::ERROR,"socket error on fd %d, errno: %d, \"%s\"", sockfd, sockerr, strerror(sockerr)); + connection->stop(); + return; + } + } + if (connection->connected()){ + error=""; + LOG_DEBUG(GwLog::LOG,"connected to %s",remoteAddress.c_str()); + state=C_CONNECTED; + } + else{ + error=String("connect error ")+String(strerror(errno)); + LOG_DEBUG(GwLog::ERROR,"%s",error.c_str()); + state=C_INITIALIZED; + } +} + +GwTcpClient::GwTcpClient(GwLog *logger) +{ + this->logger = logger; + this->connection=NULL; + locker=xSemaphoreCreateMutex(); +} +GwTcpClient::~GwTcpClient(){ + if (connection) + delete connection; + vSemaphoreDelete(locker); +} +void GwTcpClient::begin(int sourceId,String address, uint16_t port,bool allowRead) +{ + stop(); + this->sourceId=sourceId; + this->remoteAddress = address; + this->port = port; + configured=true; + state = C_INITIALIZED; + this->connection = new GwSocketConnection(logger,0,allowRead); + startResolving(); +} +void GwTcpClient::loop(bool handleRead,bool handleWrite) +{ + checkConnection(); + if (state != C_CONNECTED){ + return; + } + if (handleRead){ + if (connection->hasClient()){ + if (! connection->connected()){ + LOG_DEBUG(GwLog::ERROR,"tcp client connection closed on %s",connection->remoteIpAddress.c_str()); + connection->stop(); + } + else{ + connection->read(); + } + } + } + if (handleWrite){ + if (connection->hasClient()){ + GwBuffer::WriteStatus rt = connection->write(); + if (rt == GwBuffer::ERROR) + { + LOG_DEBUG(GwLog::ERROR, "write error on %s, closing", connection->remoteIpAddress.c_str()); + connection->stop(); + } + } + } +} + +size_t GwTcpClient::sendToClients(const char *buf,int sourceId, bool partialWrite){ + if (sourceId == this->sourceId) return 0; + if (state != C_CONNECTED) return 0; + if (! connection->hasClient()) return 0; + size_t len=strlen(buf); + if (connection->enqueue((uint8_t*)buf,len)){ + return len; + } + return 0; +} +void GwTcpClient::readMessages(GwMessageFetcher *writer){ + if (state != C_CONNECTED) return; + if (! connection->hasClient()) return; + connection->messagesFromBuffer(writer); +} +void GwTcpClient::resolveHost(String host) +{ + LOG_DEBUG(GwLog::LOG,"start resolving %s",host.c_str()); + { + GWSYNCHRONIZED(&locker); + resolvedAddress.resolved = false; + } + state = C_RESOLVING; + error=String("resolving ")+host; + ResolveArgs *args=new ResolveArgs(); + args->host = host; + args->timeout = 10000; + args->client = this; + if (xTaskCreate([](void *p) + { + ResolveArgs *args = (ResolveArgs *)p; + struct ip4_addr addr; + addr.addr = 0; + esp_err_t err = mdns_query_a(args->host.c_str(), args->timeout, &addr); + if (err) + { + args->client->setResolved(IPAddress(), false); + } + else{ + args->client->setResolved(IPAddress(addr.addr), true); + } + args->client->logger->logDebug(GwLog::DEBUG,"resolve task end"); + delete args; + vTaskDelete(NULL); + }, + "resolve", 4000, args, 0, NULL) != pdPASS) + { + LOG_DEBUG(GwLog::ERROR,"unable to start resolve task"); + error = "unable to start resolve task"; + delete args; + setResolved(IPAddress(), false); + } +} +void GwTcpClient::setResolved(IPAddress addr, bool valid){ + LOG_DEBUG(GwLog::LOG,"setResolved %s, valid=%s", + addr.toString().c_str(),(valid?"true":"false")); + GWSYNCHRONIZED(&locker); + resolvedAddress.address=addr; + resolvedAddress.resolved=valid; + state=C_RESOLVED; +} +GwTcpClient::ResolvedAddress GwTcpClient::getResolved(){ + GWSYNCHRONIZED(&locker); + return resolvedAddress; +} \ No newline at end of file diff --git a/lib/socketserver/GwTcpClient.h b/lib/socketserver/GwTcpClient.h new file mode 100644 index 0000000..25cc654 --- /dev/null +++ b/lib/socketserver/GwTcpClient.h @@ -0,0 +1,56 @@ +#pragma once +#include "GwSocketConnection.h" +#include "GwChannelInterface.h" +#include "GwSynchronized.h" +class GwTcpClient : public GwChannelInterface +{ + class ResolvedAddress{ + public: + IPAddress address; + bool resolved=false; + }; + static const unsigned long CON_TIMEOUT=10000; + GwSocketConnection *connection = NULL; + String remoteAddress; + ResolvedAddress resolvedAddress; + uint16_t port = 0; + unsigned long connectStart=0; + GwLog *logger; + int sourceId; + bool configured=false; + String error; + SemaphoreHandle_t locker; + +public: + typedef enum + { + C_DISABLED = 0, + C_INITIALIZED = 1, + C_RESOLVING = 2, + C_RESOLVED = 3, + C_CONNECTING = 4, + C_CONNECTED = 5 + } State; + +private: + + State state = C_DISABLED; + void stop(); + void startResolving(); + void startConnection(); + void checkConnection(); + bool hasConfig(); + void resolveHost(String host); + void setResolved(IPAddress addr, bool valid); + ResolvedAddress getResolved(); + +public: + GwTcpClient(GwLog *logger); + ~GwTcpClient(); + void begin(int sourceId,String address, uint16_t port,bool allowRead); + virtual void loop(bool handleRead=true,bool handleWrite=true); + virtual size_t sendToClients(const char *buf,int sourceId, bool partialWrite=false); + virtual void readMessages(GwMessageFetcher *writer); + bool isConnected(); + String getError(){return error;} +}; \ No newline at end of file diff --git a/lib/usercode/GwUserCode.cpp b/lib/usercode/GwUserCode.cpp index 6be082d..df5a633 100644 --- a/lib/usercode/GwUserCode.cpp +++ b/lib/usercode/GwUserCode.cpp @@ -99,6 +99,10 @@ public: GWSYNCHRONIZED(mainLock); api->getBoatDataValues(num,list); } + virtual void getStatus(Status &status){ + GWSYNCHRONIZED(mainLock); + api->getStatus(status); + } virtual ~TaskApi(){}; }; diff --git a/lib/wifi/GWWifi.h b/lib/wifi/GWWifi.h index bc75013..4ae3969 100644 --- a/lib/wifi/GWWifi.h +++ b/lib/wifi/GWWifi.h @@ -23,5 +23,7 @@ class GwWifi{ bool clientConnected(); bool connectClient(); String apIP(); + bool isApActive(){return apActive;} + bool isClientActive(){return wifiClient->asBoolean();} }; #endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index be18d13..141eb63 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,6 +35,7 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting #include #include #include +#include #include #include #include "esp_heap_caps.h" @@ -61,18 +62,14 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting #include "GwUserCode.h" #include "GwStatistics.h" #include "GwUpdate.h" +#include "GwTcpClient.h" +#include "GwChannel.h" +#include "GwChannelList.h" -//NMEA message channels -#define N2K_CHANNEL_ID 0 -#define USB_CHANNEL_ID 1 -#define SERIAL1_CHANNEL_ID 2 -#define MIN_TCP_CHANNEL_ID 3 - -#define MIN_USER_TASK 200 #define MAX_NMEA2000_MESSAGE_SEASMART_SIZE 500 -#define MAX_NMEA0183_MESSAGE_SIZE 150 // For AIS +#define MAX_NMEA0183_MESSAGE_SIZE MAX_NMEA2000_MESSAGE_SEASMART_SIZE //https://curiouser.cheshireeng.com/2014/08/19/c-compile-time-assert/ #define CASSERT(predicate, text) _impl_CASSERT_LINE(predicate,__LINE__) #define _impl_PASTE(a,b) a##b @@ -109,7 +106,7 @@ bool fixedApPass=false; bool fixedApPass=true; #endif GwWifi gwWifi(&config,&logger,fixedApPass); -GwSocketServer socketServer(&config,&logger,MIN_TCP_CHANNEL_ID); +GwChannelList channels(&logger,&config); GwBoatData boatData(&logger); GwXDRMappings xdrMappings(&logger,&config); @@ -119,8 +116,6 @@ int NodeAddress; // To store last Node Address Preferences preferences; // Nonvolatile storage on ESP32 - To store LastDeviceAddress N2kDataToNMEA0183 *nmea0183Converter=NULL; NMEA0183DataToN2K *toN2KConverter=NULL; -tActisenseReader *actisenseReader=NULL; -Stream *usbStream=NULL; SemaphoreHandle_t mainLock; @@ -130,13 +125,6 @@ GwWebServer webserver(&logger,&mainQueue,80); GwCounter countNMEA2KIn("count2Kin"); GwCounter countNMEA2KOut("count2Kout"); -GwCounter countUSBIn("countUSBin"); -GwCounter countUSBOut("countUSBout"); -GwCounter countTCPIn("countTCPin"); -GwCounter countTCPOut("countTCPout"); -GwCounter countSerialIn("countSerialIn"); -GwCounter countSerialOut("countSerialOut"); - unsigned long saltBase=esp_random(); char hv(uint8_t nibble){ @@ -179,136 +167,50 @@ bool checkPass(String hash){ } GwUpdate updater(&logger,&webserver,&checkPass); - -void updateNMEACounter(int id,const char *msg,bool incoming,bool fail=false){ - //we rely on the msg being long enough - char key[6]; - if (msg[0] == '$') { - strncpy(key,&msg[3],3); - key[3]=0; - } - else if(msg[0] == '!'){ - strncpy(key,&msg[1],5); - key[5]=0; - } - else return; - GwCounter *counter=NULL; - if (id == USB_CHANNEL_ID) counter=incoming?&countUSBIn:&countUSBOut; - if (id == SERIAL1_CHANNEL_ID) counter=incoming?&countSerialIn:&countSerialOut; - if (id >= MIN_TCP_CHANNEL_ID) counter=incoming?&countTCPIn:&countTCPOut; - if (! counter) return; - if (fail){ - counter->addFail(key); - } - else{ - counter->add(key); - } -} - -//configs that we need in main - - -GwConfigInterface *sendUsb=config.getConfigItem(config.sendUsb,true); -GwConfigInterface *readUsb=config.getConfigItem(config.receiveUsb,true); -GwConfigInterface *usbActisense=config.getConfigItem(config.usbActisense,true); -GwConfigInterface *usbSendActisens=config.getConfigItem(config.usbActSend,true); -GwConfigInterface *sendTCP=config.getConfigItem(config.sendTCP,true); -GwConfigInterface *readTCP=config.getConfigItem(config.readTCP,true); -GwConfigInterface *sendSeasmart=config.getConfigItem(config.sendSeasmart,true); GwConfigInterface *systemName=config.getConfigItem(config.systemName,true); -GwConfigInterface *n2kFromTCP=config.getConfigItem(config.tcpToN2k,true); -GwConfigInterface *n2kFromUSB=config.getConfigItem(config.usbToN2k,true); -GwConfigInterface *receiveSerial=config.getConfigItem(config.receiveSerial,true); -GwConfigInterface *sendSerial=config.getConfigItem(config.sendSerial,true); -GwConfigInterface *n2kFromSerial=config.getConfigItem(config.serialToN2k,true); -GwNmeaFilter usbReadFilter(config.getConfigItem(config.usbReadFilter,true)); -GwNmeaFilter usbWriteFilter(config.getConfigItem(config.usbWriteFilter,true)); -GwNmeaFilter serialReadFilter(config.getConfigItem(config.serialReadF,true)); -GwNmeaFilter serialWriteFilter(config.getConfigItem(config.serialWriteF,true)); -GwNmeaFilter tcpReadFilter(config.getConfigItem(config.tcpReadFilter,true)); -GwNmeaFilter tcpWriteFilter(config.getConfigItem(config.tcpWriteFilter,true)); -bool checkFilter(const char *buffer,int channelId,bool read){ - GwNmeaFilter *filter=NULL; - if (channelId == USB_CHANNEL_ID) filter=read?&usbReadFilter:&usbWriteFilter; - else if (channelId == SERIAL1_CHANNEL_ID) filter=read?&serialReadFilter:&serialWriteFilter; - else if (channelId >= MIN_TCP_CHANNEL_ID) filter=read?&tcpReadFilter:&tcpWriteFilter; - if (!filter) return true; - if (filter->canPass(buffer)) return true; - logger.logDebug(GwLog::DEBUG,"%s filter for channel %d dropped %s",(read?"read":"write"),channelId,buffer); - return false; -} -bool serCanWrite=true; -bool serCanRead=true; - -GwSerial *usbSerial = new GwSerial(NULL, 0, USB_CHANNEL_ID); -GwSerial *serial1=NULL; - -void sendBufferToChannels(const char * buffer, int sourceId){ - if (sendTCP->asBoolean() && checkFilter(buffer,MIN_TCP_CHANNEL_ID,false)){ - socketServer.sendToClients(buffer,sourceId); - updateNMEACounter(MIN_TCP_CHANNEL_ID,buffer,false); - } - if (! actisenseReader && sendUsb->asBoolean() && checkFilter(buffer,USB_CHANNEL_ID,false)){ - usbSerial->sendToClients(buffer,sourceId); - updateNMEACounter(USB_CHANNEL_ID,buffer,false); - } - if (serial1 && serCanWrite && checkFilter(buffer,SERIAL1_CHANNEL_ID,false)){ - serial1->sendToClients(buffer,sourceId); - updateNMEACounter(SERIAL1_CHANNEL_ID,buffer,false); - } -} -typedef enum { - N2KT_MSGIN, //from CAN - N2KT_MSGINT, //from internal source - N2KT_MSGOUT, //from converter - N2KT_MSGACT //from actisense -} N2K_MsgDirection; -void handleN2kMessage(const tN2kMsg &n2kMsg,N2K_MsgDirection direction) +void handleN2kMessage(const tN2kMsg &n2kMsg,int sourceId, bool isConverted=false) { logger.logDebug(GwLog::DEBUG + 1, "N2K: pgn %d, dir %d", - n2kMsg.PGN,(int)direction); - if (direction == N2KT_MSGIN){ + n2kMsg.PGN,sourceId); + if (sourceId == N2K_CHANNEL_ID){ countNMEA2KIn.add(n2kMsg.PGN); } - if (sendSeasmart->asBoolean()) - { - char buf[MAX_NMEA2000_MESSAGE_SEASMART_SIZE]; - if (N2kToSeasmart(n2kMsg, millis(), buf, MAX_NMEA2000_MESSAGE_SEASMART_SIZE) == 0) - return; - socketServer.sendToClients(buf, N2K_CHANNEL_ID); + char *buf=new char[MAX_NMEA2000_MESSAGE_SEASMART_SIZE]; + std::unique_ptr bufDel(buf); + bool messageCreated=false; + channels.allChannels([&](GwChannel *c){ + if (c->sendSeaSmart()){ + if (! messageCreated){ + if (N2kToSeasmart(n2kMsg, millis(), buf, MAX_NMEA2000_MESSAGE_SEASMART_SIZE) != 0) { + messageCreated=true; + } + } + if (messageCreated){ + c->sendToClients(buf,sourceId); + } + } + }); + + channels.allChannels([&](GwChannel *c){ + c->sendActisense(n2kMsg,sourceId); + }); + if (! isConverted){ + nmea0183Converter->HandleMsg(n2kMsg,sourceId); } - if (actisenseReader && direction != N2KT_MSGACT && usbStream && usbSendActisens->asBoolean()) - { - countUSBOut.add(String(n2kMsg.PGN)); - n2kMsg.SendInActisenseFormat(usbStream); - } - if (direction != N2KT_MSGOUT){ - nmea0183Converter->HandleMsg(n2kMsg); - } - if (direction != N2KT_MSGIN){ + if (sourceId != N2K_CHANNEL_ID){ countNMEA2KOut.add(n2kMsg.PGN); NMEA2000.SendMsg(n2kMsg); } }; -void handleReceivedNmeaMessage(const char *buf, int sourceId){ - if (! checkFilter(buf,sourceId,true)) return; - updateNMEACounter(sourceId,buf,true); - if ( (sourceId >= MIN_USER_TASK) || - (sourceId == USB_CHANNEL_ID && n2kFromUSB->asBoolean())|| - (sourceId >= MIN_TCP_CHANNEL_ID && n2kFromTCP->asBoolean())|| - (sourceId == SERIAL1_CHANNEL_ID && n2kFromSerial->asBoolean()) - ) - toN2KConverter->parseAndSend(buf,sourceId); - sendBufferToChannels(buf,sourceId); -} //***************************************************************************** void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg, int sourceId,bool convert=false) { logger.logDebug(GwLog::DEBUG+2,"SendNMEA0183(1)"); - char buf[MAX_NMEA0183_MESSAGE_SIZE+3]; + char *buf=new char[MAX_NMEA0183_MESSAGE_SIZE+3]; + std::unique_ptr bufDel(buf); if ( !NMEA0183Msg.GetMessage(buf, MAX_NMEA0183_MESSAGE_SIZE) ) return; logger.logDebug(GwLog::DEBUG+2,"SendNMEA0183: %s",buf); if (convert){ @@ -318,42 +220,11 @@ void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg, int sourceId,bool conv buf[len]=0x0d; buf[len+1]=0x0a; buf[len+2]=0; - sendBufferToChannels(buf,sourceId); + channels.allChannels([&](GwChannel *c){ + c->sendToClients(buf,sourceId); + }); } -class GwSerialLog : public GwLogWriter{ - static const size_t bufferSize=4096; - char *logBuffer=NULL; - int wp=0; - public: - GwSerialLog(){ - logBuffer=new char[bufferSize]; - wp=0; - } - virtual ~GwSerialLog(){} - virtual void write(const char *data){ - int len=strlen(data); - if ((wp+len) >= (bufferSize-1)) return; - strncpy(logBuffer+wp,data,len); - wp+=len; - logBuffer[wp]=0; - } - virtual void flush(){ - size_t handled=0; - while (handled < wp){ - usbSerial->flush(); - size_t rt=usbSerial->sendToClients(logBuffer+handled,-1,true); - handled+=rt; - } - wp=0; - logBuffer[0]=0; - } - -}; - -GwSerialLog logWriter; - - class ApiImpl : public GwApi { private: @@ -370,7 +241,7 @@ public: } virtual void sendN2kMessage(const tN2kMsg &msg,bool convert) { - handleN2kMessage(msg,convert?N2KT_MSGINT:N2KT_MSGOUT); + handleN2kMessage(msg,sourceId,!convert); } virtual void sendNMEA0183Message(const tNMEA0183Msg &msg, int sourceId,bool convert) @@ -413,6 +284,20 @@ public: } } } + virtual void getStatus(Status &status){ + status.empty(); + status.wifiApOn=gwWifi.isApActive(); + status.wifiClientOn=gwWifi.isClientActive(); + status.wifiClientConnected=gwWifi.clientConnected(); + status.wifiApIp=gwWifi.apIP(); + status.systemName=systemName->asString(); + status.wifiApPass=config.getString(config.apPassword); + status.wifiClientIp=WiFi.localIP().toString(); + status.wifiClientSSID=config.getString(config.wifiSSID); + status.n2kRx=countNMEA2KIn.getGlobal(); + status.n2kTx=countNMEA2KOut.getGlobal(); + channels.fillStatus(status); + } virtual GwBoatData *getBoatData(){ return &boatData; } @@ -474,17 +359,11 @@ protected: GwJsonDocument status(256 + countNMEA2KIn.getJsonSize()+ countNMEA2KOut.getJsonSize() + - countUSBIn.getJsonSize()+ - countUSBOut.getJsonSize()+ - countSerialIn.getJsonSize()+ - countSerialOut.getJsonSize()+ - countTCPIn.getJsonSize()+ - countTCPOut.getJsonSize() + channels.getJsonSize() ); status["version"] = VERSION; status["wifiConnected"] = gwWifi.clientConnected(); status["clientIP"] = WiFi.localIP().toString(); - status["numClients"] = socketServer.numClients(); status["apIp"] = gwWifi.apIP(); size_t bsize=2*sizeof(unsigned long)+1; unsigned long base=saltBase + ( millis()/1000UL & ~0x7UL); @@ -495,12 +374,7 @@ protected: //nmea0183Converter->toJson(status); countNMEA2KIn.toJson(status); countNMEA2KOut.toJson(status); - countUSBIn.toJson(status); - countUSBOut.toJson(status); - countSerialIn.toJson(status); - countSerialOut.toJson(status); - countTCPIn.toJson(status); - countTCPOut.toJson(status); + channels.toJson(status); serializeJson(status, result); } }; @@ -683,7 +557,8 @@ protected: tNMEA0183Msg msg; msg.Init("XDR",config.getString(config.talkerId,String("GP")).c_str()); msg.AddStrField(val.c_str()); - char buf[MAX_NMEA0183_MSG_BUF_LEN]; + char *buf=new char[MAX_NMEA0183_MSG_BUF_LEN+2]; + std::unique_ptr bufDel(buf); msg.GetMessage(buf,MAX_NMEA0183_MSG_BUF_LEN); result=buf; } @@ -707,75 +582,19 @@ void setup() { uint8_t chipid[6]; uint32_t id = 0; config.loadConfig(); - // Init USB serial port - GwConfigInterface *usbBaud=config.getConfigItem(config.usbBaud,false); - int baud=115200; - if (usbBaud){ - baud=usbBaud->asInt(); - } + bool fallbackSerial=false; #ifdef FALLBACK_SERIAL - int st=-1; -#else - int st=usbSerial->setup(baud,3,1); //TODO: PIN defines -#endif - if (st < 0){ + fallbackSerial=true; //falling back to old style serial for logging Serial.begin(baud); Serial.printf("fallback serial enabled, error was %d\n",st); logger.prefix="FALLBACK:"; - } - else{ - logger.prefix="GWSERIAL:"; - logger.setWriter(&logWriter); - logger.logDebug(GwLog::LOG,"created GwSerial for USB port"); - } - logger.logDebug(GwLog::LOG,"config: %s", config.toString().c_str()); +#endif userCodeHandler.startInitTasks(MIN_USER_TASK); - #ifdef GWSERIAL_MODE - int serialrx=-1; - int serialtx=-1; - #ifdef GWSERIAL_TX - serialtx=GWSERIAL_TX; - #endif - #ifdef GWSERIAL_RX - serialrx=GWSERIAL_RX; - #endif - //the mode is a compile time preselection from hardware.h - String serialMode(F(GWSERIAL_MODE)); - //the serial direction is from the config (only valid for mode UNI) - String serialDirection=config.getString(config.serialDirection); - //we only consider the direction if mode is UNI - if (serialMode != String("UNI")){ - serialDirection=String(""); - //if mode is UNI it depends on the selection - serCanRead=receiveSerial->asBoolean(); - serCanWrite=sendSerial->asBoolean(); - } - if (serialDirection == "receive" || serialDirection == "off" || serialMode == "RX") serCanWrite=false; - if (serialDirection == "send" || serialDirection == "off" || serialMode == "TX") serCanRead=false; - logger.logDebug(GwLog::DEBUG,"serial set up: mode=%s,direction=%s,rx=%d,tx=%d", - serialMode.c_str(),serialDirection.c_str(),serialrx,serialtx - ); - if (serialtx != -1 || serialrx != -1){ - logger.logDebug(GwLog::LOG,"creating serial interface rx=%d, tx=%d",serialrx,serialtx); - serial1=new GwSerial(&logger,1,SERIAL1_CHANNEL_ID,serCanRead); - } - if (serial1){ - int rt=serial1->setup(config.getInt(config.serialBaud,115200),serialrx,serialtx); - logger.logDebug(GwLog::LOG,"starting serial returns %d",rt); - } - #endif - - MDNS.begin(config.getConfigItem(config.systemName)->asCString()); gwWifi.setup(); - - // Start TCP server - socketServer.begin(); + MDNS.begin(config.getConfigItem(config.systemName)->asCString()); + channels.begin(fallbackSerial); logger.flush(); - - logger.logDebug(GwLog::LOG,"usbRead: %s", usbReadFilter.toString().c_str()); - logger.flush(); - webserver.registerMainHandler("/api/reset", [](AsyncWebServerRequest *request)->GwRequestMessage *{ return new ResetRequest(request->arg("_hash")); }); @@ -829,15 +648,15 @@ void setup() { [](const tNMEA0183Msg &msg, int sourceId){ SendNMEA0183Message(msg,sourceId,false); } - , N2K_CHANNEL_ID, + , config.getString(config.talkerId,String("GP")), &xdrMappings, config.getInt(config.minXdrInterval,100) ); - toN2KConverter= NMEA0183DataToN2K::create(&logger,&boatData,[](const tN2kMsg &msg)->bool{ + toN2KConverter= NMEA0183DataToN2K::create(&logger,&boatData,[](const tN2kMsg &msg, int sourceId)->bool{ logger.logDebug(GwLog::DEBUG+2,"send N2K %ld",msg.PGN); - handleN2kMessage(msg,N2KT_MSGOUT); + handleN2kMessage(msg,sourceId,true); return true; }, &xdrMappings, @@ -892,17 +711,8 @@ void setup() { } NMEA2000.ExtendTransmitMessages(pgns); NMEA2000.ExtendReceiveMessages(nmea0183Converter->handledPgns()); - if (usbActisense->asBoolean()){ - actisenseReader=new tActisenseReader(); - usbStream=usbSerial->getStream(false); - actisenseReader->SetReadStream(usbStream); - actisenseReader->SetMsgHandler([](const tN2kMsg &msg){ - countUSBIn.add(String(msg.PGN)); - handleN2kMessage(msg,N2KT_MSGACT); - }); - } NMEA2000.SetMsgHandler([](const tN2kMsg &n2kMsg){ - handleN2kMessage(n2kMsg,N2KT_MSGIN); + handleN2kMessage(n2kMsg,N2K_CHANNEL_ID); }); NMEA2000.Open(); logger.logDebug(GwLog::LOG,"starting addon tasks"); @@ -920,49 +730,12 @@ void setup() { } //***************************************************************************** void handleSendAndRead(bool handleRead){ - socketServer.loop(handleRead); - usbSerial->loop(handleRead); - if (serial1) serial1->loop(handleRead); + channels.allChannels([&](GwChannel *c){ + c->loop(handleRead,true); + }); } -class NMEAMessageReceiver : public GwMessageFetcher{ - static const int bufferSize=GwBuffer::RX_BUFFER_SIZE+4; - uint8_t buffer[bufferSize]; - uint8_t *writePointer=buffer; - public: - virtual bool handleBuffer(GwBuffer *gwbuffer){ - size_t len=fetchMessageToBuffer(gwbuffer,buffer,bufferSize-4,'\n'); - writePointer=buffer+len; - if (writePointer == buffer) return false; - 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; - return true; - } -}; TimeMonitor monitor(20,0.2); -NMEAMessageReceiver receiver; unsigned long lastHeapReport=0; void loop() { monitor.reset(); @@ -983,20 +756,18 @@ void loop() { } } monitor.setTime(3); - //read sockets - socketServer.loop(true,false); + channels.allChannels([](GwChannel *c){ + c->loop(true,false); + }); + //reads monitor.setTime(4); - //write sockets - socketServer.loop(false,true); + channels.allChannels([](GwChannel *c){ + c->loop(false,true); + }); + //writes monitor.setTime(5); - usbSerial->loop(true); - monitor.setTime(6); - if (serial1) serial1->loop(true); - monitor.setTime(7); - handleSendAndRead(true); - monitor.setTime(8); NMEA2000.ParseMessages(); - monitor.setTime(9); + monitor.setTime(6); int SourceAddress = NMEA2000.GetN2kSource(); if (SourceAddress != NodeAddress) { // Save potentially changed Source Address to NVS memory @@ -1007,21 +778,36 @@ void loop() { logger.logDebug(GwLog::LOG,"Address Change: New Address=%d\n", SourceAddress); } nmea0183Converter->loop(); - monitor.setTime(10); + monitor.setTime(7); //read channels - if (readTCP->asBoolean()) socketServer.readMessages(&receiver); - monitor.setTime(11); - receiver.id=USB_CHANNEL_ID; - if (! actisenseReader && readUsb->asBoolean()) usbSerial->readMessages(&receiver); - monitor.setTime(12); - receiver.id=SERIAL1_CHANNEL_ID; - if (serial1 && serCanRead ) serial1->readMessages(&receiver); - monitor.setTime(13); - if (actisenseReader){ - actisenseReader->ParseMessages(); - } - monitor.setTime(14); + channels.allChannels([](GwChannel *c){ + c->readMessages([&](const char * buffer, int sourceId){ + channels.allChannels([&](GwChannel *oc){ + oc->sendToClients(buffer,sourceId); + oc->loop(false,true); + }); + if (c->sendToN2K()){ + if (strlen(buffer) > 6 && strncmp(buffer,"$PCDIN",6) == 0){ + tN2kMsg n2kMsg; + uint32_t timestamp; + if (SeasmartToN2k(buffer,timestamp,n2kMsg)){ + handleN2kMessage(n2kMsg,sourceId); + } + } + else{ + toN2KConverter->parseAndSend(buffer, sourceId); + } + } + }); + }); + monitor.setTime(8); + channels.allChannels([](GwChannel *c){ + c->parseActisense([](const tN2kMsg &msg,int source){ + handleN2kMessage(msg,source); + }); + }); + monitor.setTime(9); //handle message requests GwMessage *msg=mainQueue.fetchMessage(0); @@ -1029,5 +815,5 @@ void loop() { msg->process(); msg->unref(); } - monitor.setTime(15); + monitor.setTime(10); } diff --git a/tools/buildFlashtool.sh b/tools/buildFlashtool.sh new file mode 100755 index 0000000..a7a43e0 --- /dev/null +++ b/tools/buildFlashtool.sh @@ -0,0 +1,6 @@ +#! /bin/sh +pdir=`dirname $0` +cd $pdir || exit 1 +rm -rf flashtool/.idea +python3 -m zipapp flashtool -m flashtool:main + diff --git a/tools/flashtool.py b/tools/flashtool.py deleted file mode 100755 index 9e1d882..0000000 --- a/tools/flashtool.py +++ /dev/null @@ -1,237 +0,0 @@ -#! /usr/bin/env python3 -import tkinter as tk -from tkinter import ttk -import tkinter.font as tkFont - -import os -import serial.tools.list_ports -from tkinter import filedialog as FileDialog - -import builtins - -VERSION="1.0, esptool 3.2" - -oldprint=builtins.print -def print(*args, **kwargs): - app.addText(*args,**kwargs) - - -builtins.print=print -import esptool - -class App: - def __init__(self, root): - root.title("ESP32 NMEA2000 Flash Tool") - root.geometry("800x600") - root.resizable(width=True, height=True) - root.configure(background='lightgrey') - root.columnconfigure(0, weight=1) - root.rowconfigure(0, weight=1) - frame=tk.Frame(root) - row=0 - frame.grid(row=0,column=0,sticky='news') - #frame.configure(background='lightblue') - frame.columnconfigure(0,weight=1) - frame.columnconfigure(1, weight=3) - tk.Label(frame,text="ESP32 NMEA2000 Flash Tool").grid(row=row,column=0,columnspan=2,sticky='ew') - row+=1 - tk.Label(frame, text=VERSION).grid(row=row,column=0,columnspan=2,sticky="ew",pady=10) - row+=1 - self.mode=tk.IntVar() - self.mode.set(1) - rdFrame=tk.Frame(frame) - rdFrame.grid(row=row,column=1,sticky="ew",pady=20) - tk.Radiobutton(rdFrame,text="Initial Flash",value=1,variable=self.mode,command=self.changeMode).grid(row=0,column=0) - tk.Radiobutton(rdFrame, text="Update Flash", value=2, variable=self.mode,command=self.changeMode).grid(row=0,column=1) - row+=1 - tk.Label(frame, text="Com Port").grid(row=row,column=0,sticky='ew') - self.port=ttk.Combobox(frame) - self.port.grid(row=row,column=1,sticky="ew") - row+=1 - tk.Label(frame,text="Select Firmware").grid(row=row,column=0,sticky='ew') - self.filename=tk.StringVar() - fn=tk.Entry(frame,textvariable=self.filename) - fn.grid(row=row,column=1,sticky='ew') - fn.bind("<1>",self.fileDialog) - row+=1 - self.fileInfo=tk.StringVar() - tk.Label(frame,textvariable=self.fileInfo).grid(row=row,column=0,columnspan=2,sticky="ew") - row+=1 - self.flashInfo=tk.StringVar() - self.flashInfo.set("Address 0x1000") - tk.Label(frame,textvariable=self.flashInfo).grid(row=row,column=0,columnspan=2,sticky='ew',pady=10) - row+=1 - btFrame=tk.Frame(frame) - btFrame.grid(row=row,column=0,columnspan=2,pady=15) - self.actionButtons=[] - bt=tk.Button(btFrame,text="Check",command=self.buttonCheck) - bt.grid(row=0,column=0) - self.actionButtons.append(bt) - bt=tk.Button(btFrame, text="Flash", command=self.buttonFlash) - bt.grid(row=0, column=1) - self.actionButtons.append(bt) - self.cancelButton=tk.Button(btFrame,text="Cancel",state=tk.DISABLED,command=self.buttonCancel) - self.cancelButton.grid(row=0,column=2) - row+=1 - self.text_widget = tk.Text(frame) - frame.rowconfigure(row,weight=1) - self.text_widget.grid(row=row,column=0,columnspan=2,sticky='news') - self.readDevices() - self.interrupt=False - - def updateFlashInfo(self): - if self.mode.get() == 1: - #full - self.flashInfo.set("Address 0x1000") - else: - self.flashInfo.set("Erase(otadata): 0xe000...0xffff, Address 0x10000") - def changeMode(self): - m=self.mode.get() - self.updateFlashInfo() - self.filename.set('') - self.fileInfo.set('') - def fileDialog(self,ev): - fn=FileDialog.askopenfilename() - if fn: - self.filename.set(fn) - info=self.checkImageFile(fn,self.mode.get() == 1) - if info['error']: - self.fileInfo.set("***ERROR: %s"%info['info']) - else: - self.fileInfo.set(info['info']) - def readDevices(self): - self.serialDevices=[] - names=[] - for dev in serial.tools.list_ports.comports(False): - self.serialDevices.append(dev.device) - if dev.description != 'n/a': - label=dev.description+"("+dev.device+")" - else: - label=dev.name+"("+dev.device+")" - names.append(label) - self.port.configure(values=names) - def addText(self,*args,**kwargs): - first=True - for k in args: - self.text_widget.insert(tk.END,k) - if not first: - self.text_widget.insert(tk.END, ',') - first=False - if kwargs.get('end') is None: - self.text_widget.insert(tk.END,"\n") - else: - self.text_widget.insert(tk.END,kwargs.get('end')) - self.text_widget.see('end') - root.update() - if self.interrupt: - self.interrupt=False - raise Exception("User cancel") - - FULLOFFSET=61440 - HDROFFSET = 288 - VERSIONOFFSET = 16 - NAMEOFFSET = 48 - MINSIZE = HDROFFSET + NAMEOFFSET + 32 - CHECKBYTES = { - 0: 0xe9, # image magic - 288: 0x32, # app header magic - 289: 0x54, - 290: 0xcd, - 291: 0xab - } - - def getString(self,buffer, offset, len): - return buffer[offset:offset + len].rstrip(b'\0').decode('utf-8') - - def getFirmwareInfo(self,ih,imageFile,offset): - buffer = ih.read(self.MINSIZE) - if len(buffer) != self.MINSIZE: - return self.setErr("invalid image file %s, to short"%imageFile) - for k, v in self.CHECKBYTES.items(): - if buffer[k] != v: - return self.setErr("invalid magic at %d, expected %d got %d" - % (k+offset, v, buffer[k])) - name = self.getString(buffer, self.HDROFFSET + self.NAMEOFFSET, 32) - version = self.getString(buffer, self.HDROFFSET + self.VERSIONOFFSET, 32) - return {'error':False,'info':"%s:%s"%(name,version)} - - def setErr(self,err): - return {'error':True,'info':err} - def checkImageFile(self,filename,isFull): - if not os.path.exists(filename): - return self.setErr("file %s not found"%filename) - with open(filename,"rb") as fh: - offset=0 - if isFull: - b=fh.read(1) - if len(b) != 1: - return self.setErr("unable to read header") - if b[0] != 0xe9: - return self.setErr("invalid magic in file, expected 0xe9 got 0x%02x"%b[0]) - st=fh.seek(self.FULLOFFSET) - offset=self.FULLOFFSET - return self.getFirmwareInfo(fh,filename,offset) - - def runCheck(self): - self.text_widget.delete("1.0", "end") - idx = self.port.current() - isFull = self.mode.get() == 1 - if idx < 0: - self.addText("ERROR: no com port selected") - return - port = self.serialDevices[idx] - fn = self.filename.get() - if fn is None or fn == '': - self.addText("ERROR: no filename selected") - return - info = self.checkImageFile(fn, isFull) - if info['error']: - print("ERROR: %s" % info['info']) - return - return {'port':port,'isFull':isFull} - - def runEspTool(self,command): - for b in self.actionButtons: - b.configure(state=tk.DISABLED) - self.cancelButton.configure(state=tk.NORMAL) - print("run esptool: %s" % " ".join(command)) - root.update() - root.update_idletasks() - try: - esptool.main(command) - print("esptool done") - except Exception as e: - print("Exception in esptool %s" % e) - for b in self.actionButtons: - b.configure(state=tk.NORMAL) - self.cancelButton.configure(state=tk.DISABLED) - def buttonCheck(self): - param = self.runCheck() - if not param: - return - print("Settings OK") - command = ['--chip', 'ESP32', '--port', param['port'], 'chip_id'] - self.runEspTool(command) - - def buttonFlash(self): - param=self.runCheck() - if not param: - return - if param['isFull']: - command=['--chip','ESP32','--port',param['port'],'write_flash','0x1000',self.filename.get()] - self.runEspTool(command) - else: - command=['--chip','ESP32','--port',param['port'],'erase_region','0xe000','0x2000'] - self.runEspTool(command) - command = ['--chip', 'ESP32', '--port', param['port'], 'write_flash', '0x10000', self.filename.get()] - self.runEspTool(command) - - - def buttonCancel(self): - self.interrupt=True - - -if __name__ == "__main__": - root = tk.Tk() - app = App(root) - root.mainloop() diff --git a/tools/flashtool.pyz b/tools/flashtool.pyz new file mode 100644 index 0000000..2f50de5 Binary files /dev/null and b/tools/flashtool.pyz differ diff --git a/tools/esptool.py b/tools/flashtool/esptool.py similarity index 100% rename from tools/esptool.py rename to tools/flashtool/esptool.py diff --git a/tools/flashtool/flashtool.py b/tools/flashtool/flashtool.py new file mode 100755 index 0000000..3a30162 --- /dev/null +++ b/tools/flashtool/flashtool.py @@ -0,0 +1,253 @@ +#! /usr/bin/env python3 +import subprocess +import sys + +try: + import serial +except ImportError: + subprocess.check_call([sys.executable, "-m", "pip", "install", 'pyserial']) +finally: + import serial + +import tkinter as tk +from tkinter import ttk +import tkinter.font as tkFont + +import os +import serial.tools.list_ports +from tkinter import filedialog as FileDialog + +import builtins + +def main(): + VERSION="Version 1.1, esptool 3.2" + + oldprint=builtins.print + def print(*args, **kwargs): + app.addText(*args,**kwargs) + + + builtins.print=print + import esptool + + class App: + def __init__(self, root): + root.title("ESP32 NMEA2000 Flash Tool") + root.geometry("800x600") + root.resizable(width=True, height=True) + root.configure(background='lightgrey') + root.columnconfigure(0, weight=1) + root.rowconfigure(0, weight=1) + frame=tk.Frame(root) + row=0 + frame.grid(row=0,column=0,sticky='news',padx=10,pady=5) + DUMMY = "prevent to handled as virus" + #frame.configure(background='lightblue') + frame.columnconfigure(0,weight=1) + frame.columnconfigure(1, weight=3) + tk.Label(frame,text="ESP32 NMEA2000 Flash Tool").grid(row=row,column=0,columnspan=2,sticky='ew') + row+=1 + tk.Label(frame, text=VERSION).grid(row=row,column=0,columnspan=2,sticky="ew",pady=10) + row+=1 + self.mode=tk.IntVar() + self.mode.set(1) + rdFrame=tk.Frame(frame) + rdFrame.grid(row=row,column=1,sticky="ew",pady=20) + tk.Radiobutton(rdFrame,text="Initial Flash",value=1,variable=self.mode,command=self.changeMode).grid(row=0,column=0) + tk.Radiobutton(rdFrame, text="Update Flash", value=2, variable=self.mode,command=self.changeMode).grid(row=0,column=1) + row+=1 + tk.Label(frame, text="Com Port").grid(row=row,column=0,sticky='ew') + ttk.Style().configure("TCombobox",padding=8,arrowsize=28) + ttk.Style().configure("TEntry", padding=8) + self.port=ttk.Combobox(frame) + self.port.grid(row=row,column=1,sticky="ew",pady=5) + row+=1 + tk.Label(frame,text="Select Firmware").grid(row=row,column=0,sticky='ew') + self.filename=tk.StringVar() + fn=ttk.Entry(frame,textvariable=self.filename) + fn.grid(row=row,column=1,sticky='ew',pady=5) + fn.bind("<1>",self.fileDialog) + row+=1 + self.fileInfo=tk.StringVar() + tk.Label(frame,textvariable=self.fileInfo).grid(row=row,column=0,columnspan=2,sticky="ew") + row+=1 + self.flashInfo=tk.StringVar() + self.flashInfo.set("Address 0x1000") + tk.Label(frame,textvariable=self.flashInfo).grid(row=row,column=0,columnspan=2,sticky='ew',pady=10) + row+=1 + btFrame=tk.Frame(frame) + btFrame.grid(row=row,column=0,columnspan=2,pady=15) + self.actionButtons=[] + bt=tk.Button(btFrame,text="Check",command=self.buttonCheck) + bt.grid(row=0,column=0) + self.actionButtons.append(bt) + bt=tk.Button(btFrame, text="Flash", command=self.buttonFlash) + bt.grid(row=0, column=1) + self.actionButtons.append(bt) + self.cancelButton=tk.Button(btFrame,text="Cancel",state=tk.DISABLED,command=self.buttonCancel) + self.cancelButton.grid(row=0,column=2) + row+=1 + self.text_widget = tk.Text(frame) + frame.rowconfigure(row,weight=1) + self.text_widget.grid(row=row,column=0,columnspan=2,sticky='news') + self.readDevices() + self.interrupt=False + + def updateFlashInfo(self): + if self.mode.get() == 1: + #full + self.flashInfo.set("Address 0x1000") + else: + self.flashInfo.set("Erase(otadata): 0xe000...0xffff, Address 0x10000") + def changeMode(self): + m=self.mode.get() + self.updateFlashInfo() + self.filename.set('') + self.fileInfo.set('') + def fileDialog(self,ev): + fn=FileDialog.askopenfilename() + if fn: + self.filename.set(fn) + info=self.checkImageFile(fn,self.mode.get() == 1) + if info['error']: + self.fileInfo.set("***ERROR: %s"%info['info']) + else: + self.fileInfo.set(info['info']) + def readDevices(self): + self.serialDevices=[] + names=[] + for dev in serial.tools.list_ports.comports(False): + self.serialDevices.append(dev.device) + if dev.description != 'n/a': + label=dev.description+"("+dev.device+")" + else: + label=dev.name+"("+dev.device+")" + names.append(label) + self.port.configure(values=names) + def addText(self,*args,**kwargs): + first=True + for k in args: + self.text_widget.insert(tk.END,k) + if not first: + self.text_widget.insert(tk.END, ',') + first=False + if kwargs.get('end') is None: + self.text_widget.insert(tk.END,"\n") + else: + self.text_widget.insert(tk.END,kwargs.get('end')) + self.text_widget.see('end') + root.update() + if self.interrupt: + self.interrupt=False + raise Exception("User cancel") + + FULLOFFSET=61440 + HDROFFSET = 288 + VERSIONOFFSET = 16 + NAMEOFFSET = 48 + MINSIZE = HDROFFSET + NAMEOFFSET + 32 + CHECKBYTES = { + 0: 0xe9, # image magic + 288: 0x32, # app header magic + 289: 0x54, + 290: 0xcd, + 291: 0xab + } + + def getString(self,buffer, offset, len): + return buffer[offset:offset + len].rstrip(b'\0').decode('utf-8') + + def getFirmwareInfo(self,ih,imageFile,offset): + buffer = ih.read(self.MINSIZE) + if len(buffer) != self.MINSIZE: + return self.setErr("invalid image file %s, to short"%imageFile) + for k, v in self.CHECKBYTES.items(): + if buffer[k] != v: + return self.setErr("invalid magic at %d, expected %d got %d" + % (k+offset, v, buffer[k])) + name = self.getString(buffer, self.HDROFFSET + self.NAMEOFFSET, 32) + version = self.getString(buffer, self.HDROFFSET + self.VERSIONOFFSET, 32) + return {'error':False,'info':"%s:%s"%(name,version)} + + def setErr(self,err): + return {'error':True,'info':err} + def checkImageFile(self,filename,isFull): + if not os.path.exists(filename): + return self.setErr("file %s not found"%filename) + with open(filename,"rb") as fh: + offset=0 + if isFull: + b=fh.read(1) + if len(b) != 1: + return self.setErr("unable to read header") + if b[0] != 0xe9: + return self.setErr("invalid magic in file, expected 0xe9 got 0x%02x"%b[0]) + st=fh.seek(self.FULLOFFSET) + offset=self.FULLOFFSET + return self.getFirmwareInfo(fh,filename,offset) + + def runCheck(self): + self.text_widget.delete("1.0", "end") + idx = self.port.current() + isFull = self.mode.get() == 1 + if idx < 0: + self.addText("ERROR: no com port selected") + return + port = self.serialDevices[idx] + fn = self.filename.get() + if fn is None or fn == '': + self.addText("ERROR: no filename selected") + return + info = self.checkImageFile(fn, isFull) + if info['error']: + print("ERROR: %s" % info['info']) + return + return {'port':port,'isFull':isFull} + + def runEspTool(self,command): + for b in self.actionButtons: + b.configure(state=tk.DISABLED) + self.cancelButton.configure(state=tk.NORMAL) + print("run esptool: %s" % " ".join(command)) + root.update() + root.update_idletasks() + try: + esptool.main(command) + print("esptool done") + except Exception as e: + print("Exception in esptool %s" % e) + for b in self.actionButtons: + b.configure(state=tk.NORMAL) + self.cancelButton.configure(state=tk.DISABLED) + def buttonCheck(self): + param = self.runCheck() + if not param: + return + print("Settings OK") + command = ['--chip', 'ESP32', '--port', param['port'], 'chip_id'] + self.runEspTool(command) + + def buttonFlash(self): + param=self.runCheck() + if not param: + return + if param['isFull']: + command=['--chip','ESP32','--port',param['port'],'write_flash','0x1000',self.filename.get()] + self.runEspTool(command) + else: + command=['--chip','ESP32','--port',param['port'],'erase_region','0xe000','0x2000'] + self.runEspTool(command) + command = ['--chip', 'ESP32', '--port', param['port'], 'write_flash', '0x10000', self.filename.get()] + self.runEspTool(command) + + + def buttonCancel(self): + self.interrupt=True + + root = tk.Tk() + app = App(root) + root.mainloop() + + +if __name__ == "__main__": + main() diff --git a/tools/readme-esptool-win.txt b/tools/readme-esptool-win.txt index 06bdfe0..4ff31ba 100644 --- a/tools/readme-esptool-win.txt +++ b/tools/readme-esptool-win.txt @@ -14,7 +14,7 @@ How to build the bundled flashtool.exe (on windows) (2) pip install pyinstaller (3) pip install pyserial (4) in this directory: - pyinstaller -F flashtool.py + pyinstaller --clean -F flashtool.py will create flashtool.exe in dist diff --git a/web/config.json b/web/config.json index 3c3cb17..e2e45e8 100644 --- a/web/config.json +++ b/web/config.json @@ -376,7 +376,7 @@ "type": "number", "default": "10110", "description": "the TCP port we listen on", - "category": "TCP port" + "category": "TCP server" }, { "name": "maxClients", @@ -387,55 +387,128 @@ "min": 0, "max": 6, "description": "the number of clients we allow to connect to us", - "category": "TCP port" + "category": "TCP server" }, { "name": "sendTCP", - "label": "NMEA to TCP", + "label": "NMEA0183 out", "type": "boolean", "default": "true", "description": "send out NMEA data to connected TCP clients", - "category": "TCP port" + "category": "TCP server" }, { "name": "readTCP", - "label": "NMEA from TCP", + "label": "NMEA0183 in", "type": "boolean", "default": "true", "description": "receive NMEA data from connected TCP clients", - "category": "TCP port" + "category": "TCP server" }, { "name": "tcpToN2k", - "label": "TCP to NMEA2000", + "label": "to NMEA2000", "type": "boolean", "default": "true", "description": "convert NMEA0183 from TCP clients to NMEA2000", - "category": "TCP port" + "category": "TCP server" }, { "name": "tcpReadFilter", - "label": "TCP read Filter", + "label": "NMEA read Filter", "type": "filter", "default": "", "description": "filter for NMEA0183 data when reading from TCP\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB", - "category": "TCP port" + "category": "TCP server" }, { "name": "tcpWriteFilter", - "label": "TCP write Filter", + "label": "NMEA write Filter", "type": "filter", "default": "", "description": "filter for NMEA0183 data when writing to TCP\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB", - "category": "TCP port" + "category": "TCP server" }, { "name": "sendSeasmart", - "label": "Seasmart to TCP", + "label": "Seasmart out", "type": "boolean", "default": "false", "description": "send NMEA2000 as seasmart to connected TCP clients", - "category": "TCP port" + "category": "TCP server" + }, + { + "name": "tclEnabled", + "label": "enable", + "type": "boolean", + "default": "false", + "description":"enable the TCP client", + "category":"TCP client" + }, + { + "name": "remotePort", + "label": "remote port", + "type": "number", + "default": "10110", + "description": "the TCP port we connect to", + "category": "TCP client" + }, + { + "name": "remoteAddress", + "label": "remote address", + "type": "string", + "default": "", + "check": "checkIpAddress", + "description": "the IP address we connect to in the form 192.168.1.2\nor an MDNS name like ESP32NMEA2K.local", + "category": "TCP client" + }, + { + "name": "sendTCL", + "label": "NMEA0183 out", + "type": "boolean", + "default": "true", + "description": "send out NMEA data to remote TCP server", + "category": "TCP client" + }, + { + "name": "readTCL", + "label": "NMEA0183 in", + "type": "boolean", + "default": "true", + "description": "receive NMEA data from remote TCP server", + "category": "TCP client" + }, + { + "name": "tclToN2k", + "label": "to NMEA2000", + "type": "boolean", + "default": "true", + "description": "convert NMEA0183 from remote TCP server to NMEA2000", + "category": "TCP client" + }, + { + "name": "tclReadFilter", + "label": "NMEA read Filter", + "type": "filter", + "default": "", + "description": "filter for NMEA0183 data when reading from remote TCP server\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB", + "category": "TCP client" + }, + { + "name": "tclWriteFilter", + "label": "NMEA write Filter", + "type": "filter", + "default": "", + "description": "filter for NMEA0183 data when writing to remote TCP server\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB", + "category": "TCP client" + }, + { + "name": "tclSeasmart", + "label": "Seasmart out", + "type": "boolean", + "default": "false", + "description": "send NMEA2000 as seasmart to remote TCP server", + "category": "TCP client" }, { "name": "wifiClient", diff --git a/web/index.html b/web/index.html index 8ae9a09..d6cc011 100644 --- a/web/index.html +++ b/web/index.html @@ -43,8 +43,16 @@ ---
- # TCP clients + # clients --- +
+
+ TCP client connected + --- +
+
+ TCP client error + ---
diff --git a/web/index.js b/web/index.js index 2634c74..eafb494 100644 --- a/web/index.js +++ b/web/index.js @@ -162,6 +162,14 @@ function checkAdminPass(v){ return checkApPass(v); } +function checkIpAddress(v,allValues,def){ + if (allValues.tclEnabled != "true") return; + if (! v) return "cannot be empty"; + if (! v.match(/[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*/) + && ! v.match(/.*\.local/)) + return "must be either in the form 192.168.1.1 or xxx.local"; +} + function checkXDR(v,allValues){ if (! v) return; let parts=v.split(','); @@ -306,12 +314,14 @@ function updateMsgDetails(key, details) { let counters={ count2Kin: 'NMEA2000 in', count2Kout: 'NMEA2000 out', - countTCPin: 'TCP in', - countTCPout: 'TCP out', + countTCPin: 'TCPserver in', + countTCPout: 'TCPserver out', + countTCPClientin: 'TCPclient in', + countTCPClientout: 'TCPclient out', countUSBin: 'USB in', countUSBout: 'USB out', - countSerialIn: 'Serial in', - countSerialOut: 'Serial out' + countSERIn: 'Serial in', + countSEROut: 'Serial out' } function showOverlay(text, isHtml) { let el = document.getElementById('overlayContent'); @@ -1360,7 +1370,9 @@ function sourceName(v){ if (v == 0) return "N2K"; if (v == 1) return "USB"; if (v == 2) return "SER"; - if (v >= 3) return "TCP"; + if (v == 3) return "TCPcl" + if (v >= 4 && v <= 20) return "TCPser"; + if (v >= 200) return "USER"; return "---"; } let lastSelectList=[];