Merge branch 'wellenvogel:master' into master
This commit is contained in:
commit
09e4a03981
14
Readme.md
14
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
|
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).
|
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__<br>
|
__linux users__<br>
|
||||||
You can typically install the esptool (once you have python 3 installed) with
|
You can typically install the esptool (once you have python 3 installed) with
|
||||||
```
|
```
|
||||||
sudo pip install esptool
|
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 tkinter
|
||||||
sudo pip install pyserial
|
sudo pip install pyserial
|
||||||
```
|
```
|
||||||
Afterwards run flashtool.py (potentially making it executable before).
|
Afterwards run flashtool.pyz with
|
||||||
|
```
|
||||||
|
python3 flashtool.pyz
|
||||||
|
```
|
||||||
|
|
||||||
__windows users__<br>
|
__windows users__<br>
|
||||||
You can find a prebuild executable in tools: [esptool.exe](tools/esptool.exe).
|
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.
|
Open a command prompt and change into the directory you downloaded the esptool.exe and the firmware binary.
|
||||||
Flash with the command
|
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.
|
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).
|
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.
|
||||||
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).
|
|
||||||
|
|
||||||
|
|
||||||
Update
|
Update
|
||||||
|
|
|
@ -31,6 +31,52 @@ class GwApi{
|
||||||
return format;
|
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
|
* 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)
|
* just make sure to have the list being of appropriate size (numValues)
|
||||||
*/
|
*/
|
||||||
virtual void getBoatDataValues(int numValues,BoatValue **list)=0;
|
virtual void getBoatDataValues(int numValues,BoatValue **list)=0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fill the status information
|
||||||
|
*/
|
||||||
|
virtual void getStatus(Status &status);
|
||||||
/**
|
/**
|
||||||
* not thread safe methods
|
* not thread safe methods
|
||||||
* accessing boat data must only be executed from within the main thread
|
* accessing boat data must only be executed from within the main thread
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
#include "GwChannel.h"
|
||||||
|
#include <ActisenseReader.h>
|
||||||
|
|
||||||
|
|
||||||
|
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>(String("count")+name+String("in"));
|
||||||
|
this->countOut=new GwCounter<String>(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();
|
||||||
|
}
|
|
@ -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 <N2kMsg.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
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<String> *countIn=NULL;
|
||||||
|
GwCounter<String> *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<void(const char *buffer, int sourceid)> NMEA0183Handler;
|
||||||
|
void readMessages(NMEA0183Handler handler);
|
||||||
|
void sendToClients(const char *buffer, int sourceId);
|
||||||
|
typedef std::function<void(const tN2kMsg &msg, int sourceId)> N2kHandler ;
|
||||||
|
void parseActisense(N2kHandler handler);
|
||||||
|
void sendActisense(const tN2kMsg &msg, int sourceId);
|
||||||
|
unsigned long countRx();
|
||||||
|
unsigned long countTx();
|
||||||
|
};
|
||||||
|
|
|
@ -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;}
|
||||||
|
};
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
#pragma once
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
#include <WString.h>
|
||||||
|
#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<GwChannel *> ChannelList;
|
||||||
|
ChannelList theChannels;
|
||||||
|
|
||||||
|
GwSocketServer *sockets;
|
||||||
|
GwTcpClient *client;
|
||||||
|
String serialMode=F("NONE");
|
||||||
|
public:
|
||||||
|
GwChannelList(GwLog *logger, GwConfigHandler *config);
|
||||||
|
typedef std::function<void(GwChannel *)> 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);
|
||||||
|
|
||||||
|
|
||||||
|
};
|
|
@ -140,18 +140,21 @@ void GwNmeaFilter::parseFilter(){
|
||||||
// "0:1:RMB,RMC"
|
// "0:1:RMB,RMC"
|
||||||
// 0: AIS off, 1:whitelist, list of sentences
|
// 0: AIS off, 1:whitelist, list of sentences
|
||||||
if (isReady) return;
|
if (isReady) return;
|
||||||
|
if (config.isEmpty()){
|
||||||
|
isReady=true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
int found=0;
|
int found=0;
|
||||||
int last=0;
|
int last=0;
|
||||||
int index=0;
|
int index=0;
|
||||||
String data=config->asString();
|
while ((found = config.indexOf(':',last)) >= 0){
|
||||||
while ((found = data.indexOf(':',last)) >= 0){
|
String tok=config.substring(last,found);
|
||||||
String tok=data.substring(last,found);
|
|
||||||
handleToken(tok,index);
|
handleToken(tok,index);
|
||||||
last=found+1;
|
last=found+1;
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
if (last < data.length()){
|
if (last < config.length()){
|
||||||
String tok=data.substring(last);
|
String tok=config.substring(last);
|
||||||
handleToken(tok,index);
|
handleToken(tok,index);
|
||||||
}
|
}
|
||||||
isReady=true;
|
isReady=true;
|
||||||
|
|
|
@ -46,7 +46,7 @@ class GwConfigInterface{
|
||||||
|
|
||||||
class GwNmeaFilter{
|
class GwNmeaFilter{
|
||||||
private:
|
private:
|
||||||
GwConfigInterface *config=NULL;
|
String config;
|
||||||
bool isReady=false;
|
bool isReady=false;
|
||||||
bool ais=true;
|
bool ais=true;
|
||||||
bool blacklist=true;
|
bool blacklist=true;
|
||||||
|
@ -54,7 +54,7 @@ class GwNmeaFilter{
|
||||||
void handleToken(String token, int index);
|
void handleToken(String token, int index);
|
||||||
void parseFilter();
|
void parseFilter();
|
||||||
public:
|
public:
|
||||||
GwNmeaFilter(GwConfigInterface *config){
|
GwNmeaFilter(String config){
|
||||||
this->config=config;
|
this->config=config;
|
||||||
isReady=false;
|
isReady=false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ template<class T> class GwCounter{
|
||||||
globalFail=0;
|
globalFail=0;
|
||||||
globalOk=0;
|
globalOk=0;
|
||||||
}
|
}
|
||||||
|
unsigned long getGlobal(){return globalOk;}
|
||||||
void add(T key){
|
void add(T key){
|
||||||
globalOk++;
|
globalOk++;
|
||||||
auto it=okCounter.find(key);
|
auto it=okCounter.find(key);
|
||||||
|
|
|
@ -88,6 +88,7 @@ void exampleTask(GwApi *api){
|
||||||
GwApi::BoatValue *latitude=new GwApi::BoatValue(F("Latitude"));
|
GwApi::BoatValue *latitude=new GwApi::BoatValue(F("Latitude"));
|
||||||
GwApi::BoatValue *testValue=new GwApi::BoatValue(boatItemName);
|
GwApi::BoatValue *testValue=new GwApi::BoatValue(boatItemName);
|
||||||
GwApi::BoatValue *valueList[]={longitude,latitude,testValue};
|
GwApi::BoatValue *valueList[]={longitude,latitude,testValue};
|
||||||
|
GwApi::Status status;
|
||||||
while(true){
|
while(true){
|
||||||
delay(1000);
|
delay(1000);
|
||||||
/*
|
/*
|
||||||
|
@ -162,6 +163,29 @@ void exampleTask(GwApi *api){
|
||||||
LOG_DEBUG(GwLog::LOG,"%s now invalid",testValue->getName().c_str());
|
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);
|
vTaskDelete(NULL);
|
||||||
|
|
|
@ -29,11 +29,13 @@ uint16_t DaysSince1970 = 0;
|
||||||
|
|
||||||
class MyAisDecoder : public AIS::AisDecoder
|
class MyAisDecoder : public AIS::AisDecoder
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
int sourceId=-1;
|
||||||
private:
|
private:
|
||||||
NMEA0183DataToN2K::N2kSender sender;
|
NMEA0183DataToN2K::N2kSender sender;
|
||||||
GwLog *logger;
|
GwLog *logger;
|
||||||
void send(const tN2kMsg &msg){
|
void send(const tN2kMsg &msg){
|
||||||
(*sender)(msg);
|
(*sender)(msg,sourceId);
|
||||||
}
|
}
|
||||||
AIS::DefaultSentenceParser parser;
|
AIS::DefaultSentenceParser parser;
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -97,26 +97,26 @@ private:
|
||||||
waypointMap[wpName]=newWp;
|
waypointMap[wpName]=newWp;
|
||||||
return newWp.id;
|
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 now=millis();
|
||||||
unsigned long pgn=msg.PGN;
|
unsigned long pgn=msg.PGN;
|
||||||
if (key == "") key=String(msg.PGN);
|
if (key == "") key=String(msg.PGN);
|
||||||
auto it=lastSends.find(key);
|
auto it=lastSends.find(key);
|
||||||
if (it == lastSends.end()){
|
if (it == lastSends.end()){
|
||||||
lastSends[key]=now;
|
lastSends[key]=now;
|
||||||
sender(msg);
|
sender(msg,sourceId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if ((it->second + minDiff) <= now){
|
if ((it->second + minDiff) <= now){
|
||||||
lastSends[key]=now;
|
lastSends[key]=now;
|
||||||
sender(msg);
|
sender(msg,sourceId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
LOG_DEBUG(GwLog::DEBUG+1,"skipped n2k message %d",msg.PGN);
|
LOG_DEBUG(GwLog::DEBUG+1,"skipped n2k message %d",msg.PGN);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool send(tN2kMsg &msg, String key=""){
|
bool send(tN2kMsg &msg, int sourceId,String key=""){
|
||||||
send(msg,key,minSendInterval);
|
return send(msg,key,minSendInterval,sourceId);
|
||||||
}
|
}
|
||||||
bool updateDouble(GwBoatItem<double> *target,double v, int sourceId){
|
bool updateDouble(GwBoatItem<double> *target,double v, int sourceId){
|
||||||
if (v != NMEA0183DoubleNA){
|
if (v != NMEA0183DoubleNA){
|
||||||
|
@ -255,7 +255,7 @@ private:
|
||||||
(tN2kFluidType)(current.selector()),
|
(tN2kFluidType)(current.selector()),
|
||||||
fields[0],
|
fields[0],
|
||||||
fields[1]);
|
fields[1]);
|
||||||
send(n2kMsg, buildN2KKey(n2kMsg, current.mapping));
|
send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case XDRBAT:
|
case XDRBAT:
|
||||||
|
@ -263,7 +263,7 @@ private:
|
||||||
{
|
{
|
||||||
SetN2kPGN127508(n2kMsg, current.mapping.instanceId,
|
SetN2kPGN127508(n2kMsg, current.mapping.instanceId,
|
||||||
fields[0], fields[1], fields[2]);
|
fields[0], fields[1], fields[2]);
|
||||||
send(n2kMsg, buildN2KKey(n2kMsg, current.mapping));
|
send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case XDRTEMP:
|
case XDRTEMP:
|
||||||
|
@ -271,7 +271,7 @@ private:
|
||||||
SetN2kPGN130312(n2kMsg,1,current.mapping.instanceId,
|
SetN2kPGN130312(n2kMsg,1,current.mapping.instanceId,
|
||||||
(tN2kTempSource)(current.selector()),
|
(tN2kTempSource)(current.selector()),
|
||||||
fields[0],fields[1]);
|
fields[0],fields[1]);
|
||||||
send(n2kMsg,buildN2KKey(n2kMsg,current.mapping));
|
send(n2kMsg,msg.sourceId,buildN2KKey(n2kMsg,current.mapping));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case XDRHUMIDITY:
|
case XDRHUMIDITY:
|
||||||
|
@ -281,7 +281,7 @@ private:
|
||||||
fields[0],
|
fields[0],
|
||||||
fields[1]
|
fields[1]
|
||||||
);
|
);
|
||||||
send(n2kMsg,buildN2KKey(n2kMsg,current.mapping));
|
send(n2kMsg,msg.sourceId,buildN2KKey(n2kMsg,current.mapping));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case XDRPRESSURE:
|
case XDRPRESSURE:
|
||||||
|
@ -289,7 +289,7 @@ private:
|
||||||
SetN2kPGN130314(n2kMsg,1,current.mapping.instanceId,
|
SetN2kPGN130314(n2kMsg,1,current.mapping.instanceId,
|
||||||
(tN2kPressureSource)(current.selector()),
|
(tN2kPressureSource)(current.selector()),
|
||||||
fields[0]);
|
fields[0]);
|
||||||
send(n2kMsg,buildN2KKey(n2kMsg,current.mapping));
|
send(n2kMsg,msg.sourceId,buildN2KKey(n2kMsg,current.mapping));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case XDRENGINE:
|
case XDRENGINE:
|
||||||
|
@ -301,14 +301,14 @@ private:
|
||||||
fields[0], fields[1], fields[2], fields[3], fields[4],
|
fields[0], fields[1], fields[2], fields[3], fields[4],
|
||||||
fields[5], fields[6], fields[7], fromDouble(fields[8]), fromDouble(fields[9]),
|
fields[5], fields[6], fields[7], fromDouble(fields[8]), fromDouble(fields[9]),
|
||||||
tN2kEngineDiscreteStatus1(), tN2kEngineDiscreteStatus2());
|
tN2kEngineDiscreteStatus1(), tN2kEngineDiscreteStatus2());
|
||||||
send(n2kMsg, buildN2KKey(n2kMsg, current.mapping));
|
send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (fillFieldList(current, fields, 13,10)){
|
if (fillFieldList(current, fields, 13,10)){
|
||||||
SetN2kPGN127488(n2kMsg,current.mapping.instanceId,
|
SetN2kPGN127488(n2kMsg,current.mapping.instanceId,
|
||||||
fields[10],fields[11],fromDouble(fields[12]));
|
fields[10],fields[11],fromDouble(fields[12]));
|
||||||
send(n2kMsg, buildN2KKey(n2kMsg, current.mapping));
|
send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -334,7 +334,7 @@ private:
|
||||||
mode=xteMode(*modeChar);
|
mode=xteMode(*modeChar);
|
||||||
}
|
}
|
||||||
SetN2kXTE(n2kMsg,1,mode,false,rmb.xte);
|
SetN2kXTE(n2kMsg,1,mode,false,rmb.xte);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
uint8_t destinationId=getWaypointId(rmb.destID);
|
uint8_t destinationId=getWaypointId(rmb.destID);
|
||||||
uint8_t sourceId=getWaypointId(rmb.originID);
|
uint8_t sourceId=getWaypointId(rmb.originID);
|
||||||
|
@ -357,10 +357,10 @@ private:
|
||||||
rmb.longitude,
|
rmb.longitude,
|
||||||
rmb.vmg
|
rmb.vmg
|
||||||
);
|
);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
SetN2kPGN129285(n2kMsg,sourceId,1,1,true,true,"default");
|
SetN2kPGN129285(n2kMsg,sourceId,1,1,true,true,"default");
|
||||||
AppendN2kPGN129285(n2kMsg,destinationId,rmb.destID,rmb.latitude,rmb.longitude);
|
AppendN2kPGN129285(n2kMsg,destinationId,rmb.destID,rmb.latitude,rmb.longitude);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void convertRMC(const SNMEA0183Msg &msg)
|
void convertRMC(const SNMEA0183Msg &msg)
|
||||||
|
@ -382,25 +382,26 @@ private:
|
||||||
{
|
{
|
||||||
|
|
||||||
SetN2kSystemTime(n2kMsg, 1, GpsDate, GpsTime);
|
SetN2kSystemTime(n2kMsg, 1, GpsDate, GpsTime);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
if (UD(Latitude) &&
|
if (UD(Latitude) &&
|
||||||
UD(Longitude)){
|
UD(Longitude)){
|
||||||
SetN2kLatLonRapid(n2kMsg,Latitude,Longitude);
|
SetN2kLatLonRapid(n2kMsg,Latitude,Longitude);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
if (UD(COG) && UD(SOG)){
|
if (UD(COG) && UD(SOG)){
|
||||||
SetN2kCOGSOGRapid(n2kMsg,1,N2khr_true,COG,SOG);
|
SetN2kCOGSOGRapid(n2kMsg,1,N2khr_true,COG,SOG);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
if (UD(Variation)){
|
if (UD(Variation)){
|
||||||
SetN2kMagneticVariation(n2kMsg,1,N2kmagvar_Calc,
|
SetN2kMagneticVariation(n2kMsg,1,N2kmagvar_Calc,
|
||||||
getUint32(boatData->GpsDate), Variation);
|
getUint32(boatData->GpsDate), Variation);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
void convertAIVDX(const SNMEA0183Msg &msg){
|
void convertAIVDX(const SNMEA0183Msg &msg){
|
||||||
|
aisDecoder->sourceId=msg.sourceId;
|
||||||
aisDecoder->handleMessage(msg.line);
|
aisDecoder->handleMessage(msg.line);
|
||||||
}
|
}
|
||||||
void convertMWV(const SNMEA0183Msg &msg){
|
void convertMWV(const SNMEA0183Msg &msg){
|
||||||
|
@ -434,7 +435,7 @@ private:
|
||||||
}
|
}
|
||||||
if (shouldSend){
|
if (shouldSend){
|
||||||
SetN2kWindSpeed(n2kMsg,1,WindSpeed,WindAngle,n2kRef);
|
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)
|
void convertVWR(const SNMEA0183Msg &msg)
|
||||||
|
@ -475,7 +476,7 @@ private:
|
||||||
if (shouldSend)
|
if (shouldSend)
|
||||||
{
|
{
|
||||||
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_Apparent);
|
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)
|
if (shouldSend)
|
||||||
{
|
{
|
||||||
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_True_North);
|
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){
|
if (WindAngleMagnetic != NMEA0183DoubleNA && shouldSend){
|
||||||
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngleMagnetic, N2kWind_Magnetic);
|
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->Variation->getDataWithDefault(N2kDoubleNA),
|
||||||
boatData->Deviation->getDataWithDefault(N2kDoubleNA)
|
boatData->Deviation->getDataWithDefault(N2kDoubleNA)
|
||||||
);
|
);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void convertHDT(const SNMEA0183Msg &msg){
|
void convertHDT(const SNMEA0183Msg &msg){
|
||||||
|
@ -553,7 +554,7 @@ private:
|
||||||
if (! UD(Heading)) return;
|
if (! UD(Heading)) return;
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
SetN2kTrueHeading(n2kMsg,1,Heading);
|
SetN2kTrueHeading(n2kMsg,1,Heading);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
void convertHDG(const SNMEA0183Msg &msg){
|
void convertHDG(const SNMEA0183Msg &msg){
|
||||||
double MagneticHeading=NMEA0183DoubleNA;
|
double MagneticHeading=NMEA0183DoubleNA;
|
||||||
|
@ -584,7 +585,7 @@ private:
|
||||||
UD(Deviation);
|
UD(Deviation);
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
SetN2kMagneticHeading(n2kMsg,1,MagneticHeading,Deviation,Variation);
|
SetN2kMagneticHeading(n2kMsg,1,MagneticHeading,Deviation,Variation);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void convertDPT(const SNMEA0183Msg &msg){
|
void convertDPT(const SNMEA0183Msg &msg){
|
||||||
|
@ -612,7 +613,7 @@ private:
|
||||||
if (! boatData->DepthTransducer->update(DepthBelowTransducer)) return;
|
if (! boatData->DepthTransducer->update(DepthBelowTransducer)) return;
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
SetN2kWaterDepth(n2kMsg,1,DepthBelowTransducer,Offset);
|
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 {
|
typedef enum {
|
||||||
DBS,
|
DBS,
|
||||||
|
@ -647,7 +648,7 @@ private:
|
||||||
if (! boatData->DepthTransducer->update(Depth,msg.sourceId)) return;
|
if (! boatData->DepthTransducer->update(Depth,msg.sourceId)) return;
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
SetN2kWaterDepth(n2kMsg,1,Depth,N2kDoubleNA);
|
SetN2kWaterDepth(n2kMsg,1,Depth,N2kDoubleNA);
|
||||||
send(n2kMsg,String(n2kMsg.PGN)+String(0));
|
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String(0));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//we can only send if we have a valid depth beloww tranducer
|
//we can only send if we have a valid depth beloww tranducer
|
||||||
|
@ -667,7 +668,7 @@ private:
|
||||||
}
|
}
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
SetN2kWaterDepth(n2kMsg,1,Depth,offset);
|
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;
|
tN2kMsg n2kMsg;
|
||||||
if (! UD(RudderPosition)) return;
|
if (! UD(RudderPosition)) return;
|
||||||
SetN2kRudder(n2kMsg,RudderPosition);
|
SetN2kRudder(n2kMsg,RudderPosition);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -711,7 +712,7 @@ private:
|
||||||
if (MagneticHeading == NMEA0183DoubleNA) MagneticHeading=N2kDoubleNA;
|
if (MagneticHeading == NMEA0183DoubleNA) MagneticHeading=N2kDoubleNA;
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
SetN2kBoatSpeed(n2kMsg,1,STW);
|
SetN2kBoatSpeed(n2kMsg,1,STW);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
|
|
||||||
}
|
}
|
||||||
void convertVTG(const SNMEA0183Msg &msg){
|
void convertVTG(const SNMEA0183Msg &msg){
|
||||||
|
@ -727,7 +728,7 @@ private:
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
//TODO: maybe use MCOG if no COG?
|
//TODO: maybe use MCOG if no COG?
|
||||||
SetN2kCOGSOGRapid(n2kMsg,1,N2khr_true,COG,SOG);
|
SetN2kCOGSOGRapid(n2kMsg,1,N2khr_true,COG,SOG);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
void convertZDA(const SNMEA0183Msg &msg){
|
void convertZDA(const SNMEA0183Msg &msg){
|
||||||
time_t DateTime;
|
time_t DateTime;
|
||||||
|
@ -751,10 +752,10 @@ private:
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
if (timezoneValid){
|
if (timezoneValid){
|
||||||
SetN2kLocalOffset(n2kMsg,DaysSince1970,GpsTime,Timezone);
|
SetN2kLocalOffset(n2kMsg,DaysSince1970,GpsTime,Timezone);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
SetN2kSystemTime(n2kMsg,1,DaysSince1970,GpsTime);
|
SetN2kSystemTime(n2kMsg,1,DaysSince1970,GpsTime);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
void convertGGA(const SNMEA0183Msg &msg){
|
void convertGGA(const SNMEA0183Msg &msg){
|
||||||
double GPSTime=NMEA0183DoubleNA;
|
double GPSTime=NMEA0183DoubleNA;
|
||||||
|
@ -788,7 +789,7 @@ private:
|
||||||
SatelliteCount, HDOP, boatData->PDOP->getDataWithDefault(N2kDoubleNA), 0,
|
SatelliteCount, HDOP, boatData->PDOP->getDataWithDefault(N2kDoubleNA), 0,
|
||||||
0, N2kGNSSt_GPS, DGPSReferenceStationID,
|
0, N2kGNSSt_GPS, DGPSReferenceStationID,
|
||||||
DGPSAge);
|
DGPSAge);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
void convertGSA(const SNMEA0183Msg &msg){
|
void convertGSA(const SNMEA0183Msg &msg){
|
||||||
if (msg.FieldCount() < 17)
|
if (msg.FieldCount() < 17)
|
||||||
|
@ -819,7 +820,7 @@ private:
|
||||||
if (!updateDouble(boatData->VDOP,VDOP,msg.sourceId)) return;
|
if (!updateDouble(boatData->VDOP,VDOP,msg.sourceId)) return;
|
||||||
}
|
}
|
||||||
SetN2kGNSSDOPData(n2kMsg,1,rmode,mode,HDOP,VDOP,N2kDoubleNA);
|
SetN2kGNSSDOPData(n2kMsg,1,rmode,mode,HDOP,VDOP,N2kDoubleNA);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
void convertGSV(const SNMEA0183Msg &msg){
|
void convertGSV(const SNMEA0183Msg &msg){
|
||||||
if (msg.FieldCount() < 7){
|
if (msg.FieldCount() < 7){
|
||||||
|
@ -867,7 +868,7 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasInfos){
|
if (hasInfos){
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -885,7 +886,7 @@ private:
|
||||||
if (! updateDouble(boatData->GpsTime,GLL.GPSTime,msg.sourceId)) return;
|
if (! updateDouble(boatData->GpsTime,GLL.GPSTime,msg.sourceId)) return;
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
SetN2kLatLonRapid(n2kMsg,GLL.latitude,GLL.longitude);
|
SetN2kLatLonRapid(n2kMsg,GLL.latitude,GLL.longitude);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void convertROT(const SNMEA0183Msg &msg){
|
void convertROT(const SNMEA0183Msg &msg){
|
||||||
|
@ -897,7 +898,7 @@ private:
|
||||||
if (! updateDouble(boatData->ROT,ROT,msg.sourceId)) return;
|
if (! updateDouble(boatData->ROT,ROT,msg.sourceId)) return;
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
SetN2kRateOfTurn(n2kMsg,1,ROT);
|
SetN2kRateOfTurn(n2kMsg,1,ROT);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
void convertXTE(const SNMEA0183Msg &msg){
|
void convertXTE(const SNMEA0183Msg &msg){
|
||||||
if (msg.FieldCount() < 6){
|
if (msg.FieldCount() < 6){
|
||||||
|
@ -916,7 +917,7 @@ private:
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
tN2kXTEMode mode=xteMode(msg.Field(5)[0]);
|
tN2kXTEMode mode=xteMode(msg.Field(5)[0]);
|
||||||
SetN2kXTE(n2kMsg,1,mode,false,xte);
|
SetN2kXTE(n2kMsg,1,mode,false,xte);
|
||||||
send(n2kMsg);
|
send(n2kMsg,msg.sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//shortcut for lambda converters
|
//shortcut for lambda converters
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
class NMEA0183DataToN2K{
|
class NMEA0183DataToN2K{
|
||||||
public:
|
public:
|
||||||
typedef bool (*N2kSender)(const tN2kMsg &msg);
|
typedef bool (*N2kSender)(const tN2kMsg &msg,int sourceId);
|
||||||
protected:
|
protected:
|
||||||
GwLog * logger;
|
GwLog * logger;
|
||||||
GwBoatData *boatData;
|
GwBoatData *boatData;
|
||||||
|
|
|
@ -34,12 +34,11 @@
|
||||||
|
|
||||||
|
|
||||||
N2kDataToNMEA0183::N2kDataToNMEA0183(GwLog * logger, GwBoatData *boatData,
|
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);
|
strncpy(this->talkerId,talkerId.c_str(),2);
|
||||||
this->talkerId[2]=0;
|
this->talkerId[2]=0;
|
||||||
sourceId=id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,7 +49,7 @@ void N2kDataToNMEA0183::loop() {
|
||||||
|
|
||||||
//*****************************************************************************
|
//*****************************************************************************
|
||||||
void N2kDataToNMEA0183::SendMessage(const tNMEA0183Msg &NMEA0183Msg) {
|
void N2kDataToNMEA0183::SendMessage(const tNMEA0183Msg &NMEA0183Msg) {
|
||||||
if ( SendNMEA0183MessageCallback != 0 ) SendNMEA0183MessageCallback(NMEA0183Msg, sourceId);
|
sendNMEA0183MessageCallback(NMEA0183Msg, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -148,8 +147,9 @@ private:
|
||||||
virtual String handledKeys(){
|
virtual String handledKeys(){
|
||||||
return converters.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);
|
String key=String(N2kMsg.PGN);
|
||||||
bool rt=converters.handleMessage(key,N2kMsg,this);
|
bool rt=converters.handleMessage(key,N2kMsg,this);
|
||||||
if (! rt){
|
if (! rt){
|
||||||
|
@ -1489,9 +1489,9 @@ private:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData,
|
N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData,
|
||||||
tSendNMEA0183MessageCallback callback, int sourceId,
|
SendNMEA0183MessageCallback callback,
|
||||||
String talkerId, GwXDRMappings *xdrMappings, int minXdrInterval)
|
String talkerId, GwXDRMappings *xdrMappings, int minXdrInterval)
|
||||||
: N2kDataToNMEA0183(logger, boatData, callback,sourceId,talkerId)
|
: N2kDataToNMEA0183(logger, boatData, callback,talkerId)
|
||||||
{
|
{
|
||||||
LastPosSend = 0;
|
LastPosSend = 0;
|
||||||
lastLoopTime = 0;
|
lastLoopTime = 0;
|
||||||
|
@ -1516,9 +1516,9 @@ private:
|
||||||
|
|
||||||
|
|
||||||
N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData,
|
N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData,
|
||||||
tSendNMEA0183MessageCallback callback, int sourceId,String talkerId, GwXDRMappings *xdrMappings,
|
SendNMEA0183MessageCallback callback, String talkerId, GwXDRMappings *xdrMappings,
|
||||||
int minXdrInterval){
|
int minXdrInterval){
|
||||||
LOG_DEBUG(GwLog::LOG,"creating N2kToNMEA0183");
|
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);
|
||||||
}
|
}
|
||||||
//*****************************************************************************
|
//*****************************************************************************
|
||||||
|
|
|
@ -22,6 +22,7 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#ifndef _N2KDATATONMEA0183_H
|
#ifndef _N2KDATATONMEA0183_H
|
||||||
#define _N2KDATATONMEA0183_H
|
#define _N2KDATATONMEA0183_H
|
||||||
|
#include <functional>
|
||||||
#include <NMEA0183.h>
|
#include <NMEA0183.h>
|
||||||
#include <NMEA2000.h>
|
#include <NMEA2000.h>
|
||||||
|
|
||||||
|
@ -34,22 +35,22 @@ class GwJsonDocument;
|
||||||
class N2kDataToNMEA0183
|
class N2kDataToNMEA0183
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using tSendNMEA0183MessageCallback = void (*)(const tNMEA0183Msg &NMEA0183Msg, int id);
|
typedef std::function<void(const tNMEA0183Msg &NMEA0183Msg,int id)> SendNMEA0183MessageCallback;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
GwLog *logger;
|
GwLog *logger;
|
||||||
GwBoatData *boatData;
|
GwBoatData *boatData;
|
||||||
int sourceId;
|
int sourceId=0;
|
||||||
char talkerId[3];
|
char talkerId[3];
|
||||||
tSendNMEA0183MessageCallback SendNMEA0183MessageCallback;
|
SendNMEA0183MessageCallback sendNMEA0183MessageCallback;
|
||||||
void SendMessage(const tNMEA0183Msg &NMEA0183Msg);
|
void SendMessage(const tNMEA0183Msg &NMEA0183Msg);
|
||||||
N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData,
|
N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData,
|
||||||
tSendNMEA0183MessageCallback callback, int sourceId,String talkerId);
|
SendNMEA0183MessageCallback callback, String talkerId);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, tSendNMEA0183MessageCallback callback,
|
static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, SendNMEA0183MessageCallback callback,
|
||||||
int sourceId,String talkerId, GwXDRMappings *xdrMappings,int minXdrInterval=100);
|
String talkerId, GwXDRMappings *xdrMappings,int minXdrInterval=100);
|
||||||
virtual void HandleMsg(const tN2kMsg &N2kMsg) = 0;
|
virtual void HandleMsg(const tN2kMsg &N2kMsg, int sourceId) = 0;
|
||||||
virtual void loop();
|
virtual void loop();
|
||||||
virtual ~N2kDataToNMEA0183(){}
|
virtual ~N2kDataToNMEA0183(){}
|
||||||
virtual unsigned long* handledPgns()=0;
|
virtual unsigned long* handledPgns()=0;
|
||||||
|
|
|
@ -22,7 +22,7 @@ typedef size_t (*GwBufferHandleFunction)(uint8_t *buffer, size_t len, void *para
|
||||||
class GwBuffer{
|
class GwBuffer{
|
||||||
public:
|
public:
|
||||||
static const size_t TX_BUFFER_SIZE=1620; // app. 20 NMEA messages
|
static const size_t TX_BUFFER_SIZE=1620; // app. 20 NMEA messages
|
||||||
static const size_t RX_BUFFER_SIZE=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 {
|
typedef enum {
|
||||||
OK,
|
OK,
|
||||||
ERROR,
|
ERROR,
|
||||||
|
|
|
@ -96,7 +96,7 @@ size_t GwSerial::sendToClients(const char *buf,int sourceId,bool partial){
|
||||||
}
|
}
|
||||||
return enqueued;
|
return enqueued;
|
||||||
}
|
}
|
||||||
void GwSerial::loop(bool handleRead){
|
void GwSerial::loop(bool handleRead,bool handleWrite){
|
||||||
write();
|
write();
|
||||||
if (! isInitialized()) return;
|
if (! isInitialized()) return;
|
||||||
if (! handleRead) return;
|
if (! handleRead) return;
|
||||||
|
@ -116,10 +116,10 @@ void GwSerial::loop(bool handleRead){
|
||||||
serial->readBytes(buffer,available);
|
serial->readBytes(buffer,available);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool GwSerial::readMessages(GwMessageFetcher *writer){
|
void GwSerial::readMessages(GwMessageFetcher *writer){
|
||||||
if (! isInitialized()) return false;
|
if (! isInitialized()) return;
|
||||||
if (! allowRead) return false;
|
if (! allowRead) return;
|
||||||
return writer->handleBuffer(readBuffer);
|
writer->handleBuffer(readBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GwSerial::flush(){
|
void GwSerial::flush(){
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
#include "HardwareSerial.h"
|
#include "HardwareSerial.h"
|
||||||
#include "GwLog.h"
|
#include "GwLog.h"
|
||||||
#include "GwBuffer.h"
|
#include "GwBuffer.h"
|
||||||
|
#include "GwChannelInterface.h"
|
||||||
class GwSerialStream;
|
class GwSerialStream;
|
||||||
class GwSerial{
|
class GwSerial : public GwChannelInterface{
|
||||||
private:
|
private:
|
||||||
GwBuffer *buffer;
|
GwBuffer *buffer;
|
||||||
GwBuffer *readBuffer=NULL;
|
GwBuffer *readBuffer=NULL;
|
||||||
|
@ -23,11 +24,11 @@ class GwSerial{
|
||||||
~GwSerial();
|
~GwSerial();
|
||||||
int setup(int baud,int rxpin,int txpin);
|
int setup(int baud,int rxpin,int txpin);
|
||||||
bool isInitialized();
|
bool isInitialized();
|
||||||
size_t sendToClients(const char *buf,int sourceId,bool partial=false);
|
virtual size_t sendToClients(const char *buf,int sourceId,bool partial=false);
|
||||||
void loop(bool handleRead=true);
|
virtual void loop(bool handleRead=true,bool handleWrite=true);
|
||||||
bool readMessages(GwMessageFetcher *writer);
|
virtual void readMessages(GwMessageFetcher *writer);
|
||||||
void flush();
|
void flush();
|
||||||
Stream *getStream(bool partialWrites);
|
virtual Stream *getStream(bool partialWrites);
|
||||||
friend GwSerialStream;
|
friend GwSerialStream;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <lwip/sockets.h>
|
||||||
|
#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);
|
||||||
|
};
|
|
@ -2,188 +2,91 @@
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include <lwip/sockets.h>
|
#include <lwip/sockets.h>
|
||||||
#include "GwBuffer.h"
|
#include "GwBuffer.h"
|
||||||
|
#include "GwSocketConnection.h"
|
||||||
|
|
||||||
class GwClient{
|
GwSocketServer::GwSocketServer(const GwConfigHandler *config, GwLog *logger, int minId)
|
||||||
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->config = config;
|
||||||
this->logger = logger;
|
this->logger = logger;
|
||||||
this->minId = minId;
|
this->minId = minId;
|
||||||
maxClients = 1;
|
maxClients = 1;
|
||||||
allowReceive = false;
|
allowReceive = false;
|
||||||
}
|
}
|
||||||
void GwSocketServer::begin(){
|
bool GwSocketServer::createListener()
|
||||||
|
{
|
||||||
|
struct sockaddr_in server;
|
||||||
|
listener = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (listener < 0)
|
||||||
|
{
|
||||||
|
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);
|
maxClients = config->getInt(config->maxClients);
|
||||||
allowReceive = config->getBool(config->readTCP);
|
allowReceive = config->getBool(config->readTCP);
|
||||||
clients=new gwClientPtr[maxClients];
|
listenerPort=config->getInt(config->serverPort);
|
||||||
for (int i=0;i<maxClients;i++){
|
clients = new GwSocketConnection*[maxClients];
|
||||||
clients[i]=gwClientPtr(new GwClient(wiFiClientPtr(NULL),logger,i,allowReceive));
|
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;
|
||||||
}
|
}
|
||||||
server=new WiFiServer(config->getInt(config->serverPort),maxClients+1);
|
|
||||||
server->begin();
|
|
||||||
LOG_DEBUG(GwLog::LOG, "Socket server created, port=%d",
|
LOG_DEBUG(GwLog::LOG, "Socket server created, port=%d",
|
||||||
config->getInt(config->serverPort));
|
config->getInt(config->serverPort));
|
||||||
MDNS.addService("_nmea-0183", "_tcp", 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)
|
void GwSocketServer::loop(bool handleRead, bool handleWrite)
|
||||||
{
|
{
|
||||||
if (! clients) return;
|
if (!clients)
|
||||||
WiFiClient client = server->available(); // listen for incoming clients
|
return;
|
||||||
|
int client = available(); // listen for incoming clients
|
||||||
if (client)
|
if (client >= 0)
|
||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::LOG, "new client connected from %s",
|
LOG_DEBUG(GwLog::LOG, "new client connected from %s",
|
||||||
client.remoteIP().toString().c_str());
|
GwSocketConnection::remoteIP(client).toString().c_str());
|
||||||
fcntl(client.fd(), F_SETFL, O_NONBLOCK);
|
|
||||||
bool canHandle = false;
|
bool canHandle = false;
|
||||||
for (int i = 0; i < maxClients; i++)
|
for (int i = 0; i < maxClients; i++)
|
||||||
{
|
{
|
||||||
if (!clients[i]->hasClient())
|
if (!clients[i]->hasClient())
|
||||||
{
|
{
|
||||||
clients[i]->setClient(wiFiClientPtr(new WiFiClient(client)));
|
clients[i]->setClient(client);
|
||||||
LOG_DEBUG(GwLog::LOG, "set client as number %d", i);
|
LOG_DEBUG(GwLog::LOG, "set client as number %d", i);
|
||||||
canHandle = true;
|
canHandle = true;
|
||||||
break;
|
break;
|
||||||
|
@ -192,7 +95,7 @@ void GwSocketServer::loop(bool handleRead,bool handleWrite)
|
||||||
if (!canHandle)
|
if (!canHandle)
|
||||||
{
|
{
|
||||||
logger->logDebug(GwLog::ERROR, "no space to store client, disconnect");
|
logger->logDebug(GwLog::ERROR, "no space to store client, disconnect");
|
||||||
client.stop();
|
close(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (handleWrite)
|
if (handleWrite)
|
||||||
|
@ -200,69 +103,83 @@ void GwSocketServer::loop(bool handleRead,bool handleWrite)
|
||||||
//sending
|
//sending
|
||||||
for (int i = 0; i < maxClients; i++)
|
for (int i = 0; i < maxClients; i++)
|
||||||
{
|
{
|
||||||
gwClientPtr client = clients[i];
|
GwSocketConnection *client = clients[i];
|
||||||
if (!client->hasClient())
|
if (!client->hasClient())
|
||||||
continue;
|
continue;
|
||||||
GwBuffer::WriteStatus rt = client->write();
|
GwBuffer::WriteStatus rt = client->write();
|
||||||
if (rt == GwBuffer::ERROR)
|
if (rt == GwBuffer::ERROR)
|
||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::ERROR, "write error on %s, closing", client->remoteIp.c_str());
|
LOG_DEBUG(GwLog::ERROR, "write error on %s, closing", client->remoteIpAddress.c_str());
|
||||||
client->client->stop();
|
client->stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < maxClients; i++)
|
for (int i = 0; i < maxClients; i++)
|
||||||
{
|
{
|
||||||
gwClientPtr client = clients[i];
|
GwSocketConnection *client = clients[i];
|
||||||
if (!client->hasClient())
|
if (!client->hasClient())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!client->client->connected())
|
if (!client->connected())
|
||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::LOG,"client %d disconnect %s", i, client->remoteIp.c_str());
|
LOG_DEBUG(GwLog::LOG, "client %d disconnect %s", i, client->remoteIpAddress.c_str());
|
||||||
client->client->stop();
|
client->stop();
|
||||||
client->setClient(NULL);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (handleRead) client->read();
|
if (handleRead)
|
||||||
|
client->read();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GwSocketServer::readMessages(GwMessageFetcher *writer){
|
void GwSocketServer::readMessages(GwMessageFetcher *writer)
|
||||||
if (! allowReceive || ! clients) return false;
|
{
|
||||||
bool hasMessages=false;
|
if (!allowReceive || !clients)
|
||||||
for (int i = 0; i < maxClients; i++){
|
return;
|
||||||
|
for (int i = 0; i < maxClients; i++)
|
||||||
|
{
|
||||||
writer->id = minId + i;
|
writer->id = minId + i;
|
||||||
if (!clients[i]->hasClient()) continue;
|
if (!clients[i]->hasClient())
|
||||||
if (clients[i]->messagesFromBuffer(writer)) hasMessages=true;
|
continue;
|
||||||
|
clients[i]->messagesFromBuffer(writer);
|
||||||
}
|
}
|
||||||
return hasMessages;
|
return;
|
||||||
}
|
}
|
||||||
void GwSocketServer::sendToClients(const char *buf,int source){
|
size_t GwSocketServer::sendToClients(const char *buf, int source,bool partial)
|
||||||
if (! clients) return;
|
{
|
||||||
|
if (!clients)
|
||||||
|
return 0;
|
||||||
|
bool hasSend=false;
|
||||||
int len = strlen(buf);
|
int len = strlen(buf);
|
||||||
int sourceIndex = source - minId;
|
int sourceIndex = source - minId;
|
||||||
for (int i = 0; i < maxClients; i++)
|
for (int i = 0; i < maxClients; i++)
|
||||||
{
|
{
|
||||||
if (i == sourceIndex)continue; //never send out to the source we received from
|
if (i == sourceIndex)
|
||||||
gwClientPtr client = clients[i];
|
continue; //never send out to the source we received from
|
||||||
if (! client->hasClient()) continue;
|
GwSocketConnection *client = clients[i];
|
||||||
if ( client->client->connected() ) {
|
if (!client->hasClient())
|
||||||
client->enqueue((uint8_t*)buf,len);
|
continue;
|
||||||
|
if (client->connected())
|
||||||
|
{
|
||||||
|
if(client->enqueue((uint8_t *)buf, len)) hasSend=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return hasSend?len:0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GwSocketServer::numClients(){
|
int GwSocketServer::numClients()
|
||||||
if (! clients) return 0;
|
{
|
||||||
|
if (!clients)
|
||||||
|
return 0;
|
||||||
int num = 0;
|
int num = 0;
|
||||||
for (int i = 0; i < maxClients; i++){
|
for (int i = 0; i < maxClients; i++)
|
||||||
if (clients[i]->hasClient()) num++;
|
{
|
||||||
|
if (clients[i]->hasClient())
|
||||||
|
num++;
|
||||||
}
|
}
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
GwSocketServer::~GwSocketServer(){
|
GwSocketServer::~GwSocketServer()
|
||||||
|
{
|
||||||
}
|
}
|
|
@ -3,28 +3,29 @@
|
||||||
#include "GWConfig.h"
|
#include "GWConfig.h"
|
||||||
#include "GwLog.h"
|
#include "GwLog.h"
|
||||||
#include "GwBuffer.h"
|
#include "GwBuffer.h"
|
||||||
|
#include "GwChannelInterface.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <WiFi.h>
|
|
||||||
|
|
||||||
using wiFiClientPtr = std::shared_ptr<WiFiClient>;
|
class GwSocketConnection;
|
||||||
class GwClient;
|
class GwSocketServer: public GwChannelInterface{
|
||||||
using gwClientPtr = std::shared_ptr<GwClient>;
|
|
||||||
class GwSocketServer{
|
|
||||||
private:
|
private:
|
||||||
const GwConfigHandler *config;
|
const GwConfigHandler *config;
|
||||||
GwLog *logger;
|
GwLog *logger;
|
||||||
gwClientPtr *clients=NULL;
|
GwSocketConnection **clients=NULL;
|
||||||
WiFiServer *server=NULL;
|
int listener=-1;
|
||||||
|
int listenerPort=-1;
|
||||||
bool allowReceive;
|
bool allowReceive;
|
||||||
int maxClients;
|
int maxClients;
|
||||||
int minId;
|
int minId;
|
||||||
|
bool createListener();
|
||||||
|
int available();
|
||||||
public:
|
public:
|
||||||
GwSocketServer(const GwConfigHandler *config,GwLog *logger,int minId);
|
GwSocketServer(const GwConfigHandler *config,GwLog *logger,int minId);
|
||||||
~GwSocketServer();
|
~GwSocketServer();
|
||||||
void begin();
|
void begin();
|
||||||
void loop(bool handleRead=true,bool handleWrite=true);
|
virtual void loop(bool handleRead=true,bool handleWrite=true);
|
||||||
void sendToClients(const char *buf,int sourceId);
|
virtual size_t sendToClients(const char *buf,int sourceId, bool partialWrite=false);
|
||||||
int numClients();
|
int numClients();
|
||||||
bool readMessages(GwMessageFetcher *writer);
|
virtual void readMessages(GwMessageFetcher *writer);
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
|
@ -0,0 +1,287 @@
|
||||||
|
#include "GwTcpClient.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
|
@ -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;}
|
||||||
|
};
|
|
@ -99,6 +99,10 @@ public:
|
||||||
GWSYNCHRONIZED(mainLock);
|
GWSYNCHRONIZED(mainLock);
|
||||||
api->getBoatDataValues(num,list);
|
api->getBoatDataValues(num,list);
|
||||||
}
|
}
|
||||||
|
virtual void getStatus(Status &status){
|
||||||
|
GWSYNCHRONIZED(mainLock);
|
||||||
|
api->getStatus(status);
|
||||||
|
}
|
||||||
virtual ~TaskApi(){};
|
virtual ~TaskApi(){};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -23,5 +23,7 @@ class GwWifi{
|
||||||
bool clientConnected();
|
bool clientConnected();
|
||||||
bool connectClient();
|
bool connectClient();
|
||||||
String apIP();
|
String apIP();
|
||||||
|
bool isApActive(){return apActive;}
|
||||||
|
bool isClientActive(){return wifiClient->asBoolean();}
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
418
src/main.cpp
418
src/main.cpp
|
@ -35,6 +35,7 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <Preferences.h>
|
#include <Preferences.h>
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
|
#include <memory>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "esp_heap_caps.h"
|
#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 "GwUserCode.h"
|
||||||
#include "GwStatistics.h"
|
#include "GwStatistics.h"
|
||||||
#include "GwUpdate.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_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/
|
//https://curiouser.cheshireeng.com/2014/08/19/c-compile-time-assert/
|
||||||
#define CASSERT(predicate, text) _impl_CASSERT_LINE(predicate,__LINE__)
|
#define CASSERT(predicate, text) _impl_CASSERT_LINE(predicate,__LINE__)
|
||||||
#define _impl_PASTE(a,b) a##b
|
#define _impl_PASTE(a,b) a##b
|
||||||
|
@ -109,7 +106,7 @@ bool fixedApPass=false;
|
||||||
bool fixedApPass=true;
|
bool fixedApPass=true;
|
||||||
#endif
|
#endif
|
||||||
GwWifi gwWifi(&config,&logger,fixedApPass);
|
GwWifi gwWifi(&config,&logger,fixedApPass);
|
||||||
GwSocketServer socketServer(&config,&logger,MIN_TCP_CHANNEL_ID);
|
GwChannelList channels(&logger,&config);
|
||||||
GwBoatData boatData(&logger);
|
GwBoatData boatData(&logger);
|
||||||
GwXDRMappings xdrMappings(&logger,&config);
|
GwXDRMappings xdrMappings(&logger,&config);
|
||||||
|
|
||||||
|
@ -119,8 +116,6 @@ int NodeAddress; // To store last Node Address
|
||||||
Preferences preferences; // Nonvolatile storage on ESP32 - To store LastDeviceAddress
|
Preferences preferences; // Nonvolatile storage on ESP32 - To store LastDeviceAddress
|
||||||
N2kDataToNMEA0183 *nmea0183Converter=NULL;
|
N2kDataToNMEA0183 *nmea0183Converter=NULL;
|
||||||
NMEA0183DataToN2K *toN2KConverter=NULL;
|
NMEA0183DataToN2K *toN2KConverter=NULL;
|
||||||
tActisenseReader *actisenseReader=NULL;
|
|
||||||
Stream *usbStream=NULL;
|
|
||||||
SemaphoreHandle_t mainLock;
|
SemaphoreHandle_t mainLock;
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,13 +125,6 @@ GwWebServer webserver(&logger,&mainQueue,80);
|
||||||
GwCounter<unsigned long> countNMEA2KIn("count2Kin");
|
GwCounter<unsigned long> countNMEA2KIn("count2Kin");
|
||||||
GwCounter<unsigned long> countNMEA2KOut("count2Kout");
|
GwCounter<unsigned long> countNMEA2KOut("count2Kout");
|
||||||
|
|
||||||
GwCounter<String> countUSBIn("countUSBin");
|
|
||||||
GwCounter<String> countUSBOut("countUSBout");
|
|
||||||
GwCounter<String> countTCPIn("countTCPin");
|
|
||||||
GwCounter<String> countTCPOut("countTCPout");
|
|
||||||
GwCounter<String> countSerialIn("countSerialIn");
|
|
||||||
GwCounter<String> countSerialOut("countSerialOut");
|
|
||||||
|
|
||||||
unsigned long saltBase=esp_random();
|
unsigned long saltBase=esp_random();
|
||||||
|
|
||||||
char hv(uint8_t nibble){
|
char hv(uint8_t nibble){
|
||||||
|
@ -179,136 +167,50 @@ bool checkPass(String hash){
|
||||||
}
|
}
|
||||||
|
|
||||||
GwUpdate updater(&logger,&webserver,&checkPass);
|
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<String> *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 *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;
|
void handleN2kMessage(const tN2kMsg &n2kMsg,int sourceId, bool isConverted=false)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
logger.logDebug(GwLog::DEBUG + 1, "N2K: pgn %d, dir %d",
|
logger.logDebug(GwLog::DEBUG + 1, "N2K: pgn %d, dir %d",
|
||||||
n2kMsg.PGN,(int)direction);
|
n2kMsg.PGN,sourceId);
|
||||||
if (direction == N2KT_MSGIN){
|
if (sourceId == N2K_CHANNEL_ID){
|
||||||
countNMEA2KIn.add(n2kMsg.PGN);
|
countNMEA2KIn.add(n2kMsg.PGN);
|
||||||
}
|
}
|
||||||
if (sendSeasmart->asBoolean())
|
char *buf=new char[MAX_NMEA2000_MESSAGE_SEASMART_SIZE];
|
||||||
{
|
std::unique_ptr<char> bufDel(buf);
|
||||||
char buf[MAX_NMEA2000_MESSAGE_SEASMART_SIZE];
|
bool messageCreated=false;
|
||||||
if (N2kToSeasmart(n2kMsg, millis(), buf, MAX_NMEA2000_MESSAGE_SEASMART_SIZE) == 0)
|
channels.allChannels([&](GwChannel *c){
|
||||||
return;
|
if (c->sendSeaSmart()){
|
||||||
socketServer.sendToClients(buf, N2K_CHANNEL_ID);
|
if (! messageCreated){
|
||||||
|
if (N2kToSeasmart(n2kMsg, millis(), buf, MAX_NMEA2000_MESSAGE_SEASMART_SIZE) != 0) {
|
||||||
|
messageCreated=true;
|
||||||
}
|
}
|
||||||
if (actisenseReader && direction != N2KT_MSGACT && usbStream && usbSendActisens->asBoolean())
|
|
||||||
{
|
|
||||||
countUSBOut.add(String(n2kMsg.PGN));
|
|
||||||
n2kMsg.SendInActisenseFormat(usbStream);
|
|
||||||
}
|
}
|
||||||
if (direction != N2KT_MSGOUT){
|
if (messageCreated){
|
||||||
nmea0183Converter->HandleMsg(n2kMsg);
|
c->sendToClients(buf,sourceId);
|
||||||
}
|
}
|
||||||
if (direction != N2KT_MSGIN){
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
channels.allChannels([&](GwChannel *c){
|
||||||
|
c->sendActisense(n2kMsg,sourceId);
|
||||||
|
});
|
||||||
|
if (! isConverted){
|
||||||
|
nmea0183Converter->HandleMsg(n2kMsg,sourceId);
|
||||||
|
}
|
||||||
|
if (sourceId != N2K_CHANNEL_ID){
|
||||||
countNMEA2KOut.add(n2kMsg.PGN);
|
countNMEA2KOut.add(n2kMsg.PGN);
|
||||||
NMEA2000.SendMsg(n2kMsg);
|
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) {
|
void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg, int sourceId,bool convert=false) {
|
||||||
logger.logDebug(GwLog::DEBUG+2,"SendNMEA0183(1)");
|
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<char> bufDel(buf);
|
||||||
if ( !NMEA0183Msg.GetMessage(buf, MAX_NMEA0183_MESSAGE_SIZE) ) return;
|
if ( !NMEA0183Msg.GetMessage(buf, MAX_NMEA0183_MESSAGE_SIZE) ) return;
|
||||||
logger.logDebug(GwLog::DEBUG+2,"SendNMEA0183: %s",buf);
|
logger.logDebug(GwLog::DEBUG+2,"SendNMEA0183: %s",buf);
|
||||||
if (convert){
|
if (convert){
|
||||||
|
@ -318,42 +220,11 @@ void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg, int sourceId,bool conv
|
||||||
buf[len]=0x0d;
|
buf[len]=0x0d;
|
||||||
buf[len+1]=0x0a;
|
buf[len+1]=0x0a;
|
||||||
buf[len+2]=0;
|
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
|
class ApiImpl : public GwApi
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
@ -370,7 +241,7 @@ public:
|
||||||
}
|
}
|
||||||
virtual void sendN2kMessage(const tN2kMsg &msg,bool convert)
|
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)
|
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(){
|
virtual GwBoatData *getBoatData(){
|
||||||
return &boatData;
|
return &boatData;
|
||||||
}
|
}
|
||||||
|
@ -474,17 +359,11 @@ protected:
|
||||||
GwJsonDocument status(256 +
|
GwJsonDocument status(256 +
|
||||||
countNMEA2KIn.getJsonSize()+
|
countNMEA2KIn.getJsonSize()+
|
||||||
countNMEA2KOut.getJsonSize() +
|
countNMEA2KOut.getJsonSize() +
|
||||||
countUSBIn.getJsonSize()+
|
channels.getJsonSize()
|
||||||
countUSBOut.getJsonSize()+
|
|
||||||
countSerialIn.getJsonSize()+
|
|
||||||
countSerialOut.getJsonSize()+
|
|
||||||
countTCPIn.getJsonSize()+
|
|
||||||
countTCPOut.getJsonSize()
|
|
||||||
);
|
);
|
||||||
status["version"] = VERSION;
|
status["version"] = VERSION;
|
||||||
status["wifiConnected"] = gwWifi.clientConnected();
|
status["wifiConnected"] = gwWifi.clientConnected();
|
||||||
status["clientIP"] = WiFi.localIP().toString();
|
status["clientIP"] = WiFi.localIP().toString();
|
||||||
status["numClients"] = socketServer.numClients();
|
|
||||||
status["apIp"] = gwWifi.apIP();
|
status["apIp"] = gwWifi.apIP();
|
||||||
size_t bsize=2*sizeof(unsigned long)+1;
|
size_t bsize=2*sizeof(unsigned long)+1;
|
||||||
unsigned long base=saltBase + ( millis()/1000UL & ~0x7UL);
|
unsigned long base=saltBase + ( millis()/1000UL & ~0x7UL);
|
||||||
|
@ -495,12 +374,7 @@ protected:
|
||||||
//nmea0183Converter->toJson(status);
|
//nmea0183Converter->toJson(status);
|
||||||
countNMEA2KIn.toJson(status);
|
countNMEA2KIn.toJson(status);
|
||||||
countNMEA2KOut.toJson(status);
|
countNMEA2KOut.toJson(status);
|
||||||
countUSBIn.toJson(status);
|
channels.toJson(status);
|
||||||
countUSBOut.toJson(status);
|
|
||||||
countSerialIn.toJson(status);
|
|
||||||
countSerialOut.toJson(status);
|
|
||||||
countTCPIn.toJson(status);
|
|
||||||
countTCPOut.toJson(status);
|
|
||||||
serializeJson(status, result);
|
serializeJson(status, result);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -683,7 +557,8 @@ protected:
|
||||||
tNMEA0183Msg msg;
|
tNMEA0183Msg msg;
|
||||||
msg.Init("XDR",config.getString(config.talkerId,String("GP")).c_str());
|
msg.Init("XDR",config.getString(config.talkerId,String("GP")).c_str());
|
||||||
msg.AddStrField(val.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<char> bufDel(buf);
|
||||||
msg.GetMessage(buf,MAX_NMEA0183_MSG_BUF_LEN);
|
msg.GetMessage(buf,MAX_NMEA0183_MSG_BUF_LEN);
|
||||||
result=buf;
|
result=buf;
|
||||||
}
|
}
|
||||||
|
@ -707,75 +582,19 @@ void setup() {
|
||||||
uint8_t chipid[6];
|
uint8_t chipid[6];
|
||||||
uint32_t id = 0;
|
uint32_t id = 0;
|
||||||
config.loadConfig();
|
config.loadConfig();
|
||||||
// Init USB serial port
|
bool fallbackSerial=false;
|
||||||
GwConfigInterface *usbBaud=config.getConfigItem(config.usbBaud,false);
|
|
||||||
int baud=115200;
|
|
||||||
if (usbBaud){
|
|
||||||
baud=usbBaud->asInt();
|
|
||||||
}
|
|
||||||
#ifdef FALLBACK_SERIAL
|
#ifdef FALLBACK_SERIAL
|
||||||
int st=-1;
|
fallbackSerial=true;
|
||||||
#else
|
|
||||||
int st=usbSerial->setup(baud,3,1); //TODO: PIN defines
|
|
||||||
#endif
|
|
||||||
if (st < 0){
|
|
||||||
//falling back to old style serial for logging
|
//falling back to old style serial for logging
|
||||||
Serial.begin(baud);
|
Serial.begin(baud);
|
||||||
Serial.printf("fallback serial enabled, error was %d\n",st);
|
Serial.printf("fallback serial enabled, error was %d\n",st);
|
||||||
logger.prefix="FALLBACK:";
|
logger.prefix="FALLBACK:";
|
||||||
}
|
#endif
|
||||||
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());
|
|
||||||
userCodeHandler.startInitTasks(MIN_USER_TASK);
|
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();
|
gwWifi.setup();
|
||||||
|
MDNS.begin(config.getConfigItem(config.systemName)->asCString());
|
||||||
// Start TCP server
|
channels.begin(fallbackSerial);
|
||||||
socketServer.begin();
|
|
||||||
logger.flush();
|
logger.flush();
|
||||||
|
|
||||||
logger.logDebug(GwLog::LOG,"usbRead: %s", usbReadFilter.toString().c_str());
|
|
||||||
logger.flush();
|
|
||||||
|
|
||||||
webserver.registerMainHandler("/api/reset", [](AsyncWebServerRequest *request)->GwRequestMessage *{
|
webserver.registerMainHandler("/api/reset", [](AsyncWebServerRequest *request)->GwRequestMessage *{
|
||||||
return new ResetRequest(request->arg("_hash"));
|
return new ResetRequest(request->arg("_hash"));
|
||||||
});
|
});
|
||||||
|
@ -829,15 +648,15 @@ void setup() {
|
||||||
[](const tNMEA0183Msg &msg, int sourceId){
|
[](const tNMEA0183Msg &msg, int sourceId){
|
||||||
SendNMEA0183Message(msg,sourceId,false);
|
SendNMEA0183Message(msg,sourceId,false);
|
||||||
}
|
}
|
||||||
, N2K_CHANNEL_ID,
|
,
|
||||||
config.getString(config.talkerId,String("GP")),
|
config.getString(config.talkerId,String("GP")),
|
||||||
&xdrMappings,
|
&xdrMappings,
|
||||||
config.getInt(config.minXdrInterval,100)
|
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);
|
logger.logDebug(GwLog::DEBUG+2,"send N2K %ld",msg.PGN);
|
||||||
handleN2kMessage(msg,N2KT_MSGOUT);
|
handleN2kMessage(msg,sourceId,true);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
&xdrMappings,
|
&xdrMappings,
|
||||||
|
@ -892,17 +711,8 @@ void setup() {
|
||||||
}
|
}
|
||||||
NMEA2000.ExtendTransmitMessages(pgns);
|
NMEA2000.ExtendTransmitMessages(pgns);
|
||||||
NMEA2000.ExtendReceiveMessages(nmea0183Converter->handledPgns());
|
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){
|
NMEA2000.SetMsgHandler([](const tN2kMsg &n2kMsg){
|
||||||
handleN2kMessage(n2kMsg,N2KT_MSGIN);
|
handleN2kMessage(n2kMsg,N2K_CHANNEL_ID);
|
||||||
});
|
});
|
||||||
NMEA2000.Open();
|
NMEA2000.Open();
|
||||||
logger.logDebug(GwLog::LOG,"starting addon tasks");
|
logger.logDebug(GwLog::LOG,"starting addon tasks");
|
||||||
|
@ -920,49 +730,12 @@ void setup() {
|
||||||
}
|
}
|
||||||
//*****************************************************************************
|
//*****************************************************************************
|
||||||
void handleSendAndRead(bool handleRead){
|
void handleSendAndRead(bool handleRead){
|
||||||
socketServer.loop(handleRead);
|
channels.allChannels([&](GwChannel *c){
|
||||||
usbSerial->loop(handleRead);
|
c->loop(handleRead,true);
|
||||||
if (serial1) serial1->loop(handleRead);
|
});
|
||||||
}
|
}
|
||||||
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);
|
TimeMonitor monitor(20,0.2);
|
||||||
NMEAMessageReceiver receiver;
|
|
||||||
unsigned long lastHeapReport=0;
|
unsigned long lastHeapReport=0;
|
||||||
void loop() {
|
void loop() {
|
||||||
monitor.reset();
|
monitor.reset();
|
||||||
|
@ -983,20 +756,18 @@ void loop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
monitor.setTime(3);
|
monitor.setTime(3);
|
||||||
//read sockets
|
channels.allChannels([](GwChannel *c){
|
||||||
socketServer.loop(true,false);
|
c->loop(true,false);
|
||||||
|
});
|
||||||
|
//reads
|
||||||
monitor.setTime(4);
|
monitor.setTime(4);
|
||||||
//write sockets
|
channels.allChannels([](GwChannel *c){
|
||||||
socketServer.loop(false,true);
|
c->loop(false,true);
|
||||||
|
});
|
||||||
|
//writes
|
||||||
monitor.setTime(5);
|
monitor.setTime(5);
|
||||||
usbSerial->loop(true);
|
|
||||||
monitor.setTime(6);
|
|
||||||
if (serial1) serial1->loop(true);
|
|
||||||
monitor.setTime(7);
|
|
||||||
handleSendAndRead(true);
|
|
||||||
monitor.setTime(8);
|
|
||||||
NMEA2000.ParseMessages();
|
NMEA2000.ParseMessages();
|
||||||
monitor.setTime(9);
|
monitor.setTime(6);
|
||||||
|
|
||||||
int SourceAddress = NMEA2000.GetN2kSource();
|
int SourceAddress = NMEA2000.GetN2kSource();
|
||||||
if (SourceAddress != NodeAddress) { // Save potentially changed Source Address to NVS memory
|
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);
|
logger.logDebug(GwLog::LOG,"Address Change: New Address=%d\n", SourceAddress);
|
||||||
}
|
}
|
||||||
nmea0183Converter->loop();
|
nmea0183Converter->loop();
|
||||||
monitor.setTime(10);
|
monitor.setTime(7);
|
||||||
|
|
||||||
//read channels
|
//read channels
|
||||||
if (readTCP->asBoolean()) socketServer.readMessages(&receiver);
|
channels.allChannels([](GwChannel *c){
|
||||||
monitor.setTime(11);
|
c->readMessages([&](const char * buffer, int sourceId){
|
||||||
receiver.id=USB_CHANNEL_ID;
|
channels.allChannels([&](GwChannel *oc){
|
||||||
if (! actisenseReader && readUsb->asBoolean()) usbSerial->readMessages(&receiver);
|
oc->sendToClients(buffer,sourceId);
|
||||||
monitor.setTime(12);
|
oc->loop(false,true);
|
||||||
receiver.id=SERIAL1_CHANNEL_ID;
|
});
|
||||||
if (serial1 && serCanRead ) serial1->readMessages(&receiver);
|
if (c->sendToN2K()){
|
||||||
monitor.setTime(13);
|
if (strlen(buffer) > 6 && strncmp(buffer,"$PCDIN",6) == 0){
|
||||||
if (actisenseReader){
|
tN2kMsg n2kMsg;
|
||||||
actisenseReader->ParseMessages();
|
uint32_t timestamp;
|
||||||
|
if (SeasmartToN2k(buffer,timestamp,n2kMsg)){
|
||||||
|
handleN2kMessage(n2kMsg,sourceId);
|
||||||
}
|
}
|
||||||
monitor.setTime(14);
|
}
|
||||||
|
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
|
//handle message requests
|
||||||
GwMessage *msg=mainQueue.fetchMessage(0);
|
GwMessage *msg=mainQueue.fetchMessage(0);
|
||||||
|
@ -1029,5 +815,5 @@ void loop() {
|
||||||
msg->process();
|
msg->process();
|
||||||
msg->unref();
|
msg->unref();
|
||||||
}
|
}
|
||||||
monitor.setTime(15);
|
monitor.setTime(10);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
#! /bin/sh
|
||||||
|
pdir=`dirname $0`
|
||||||
|
cd $pdir || exit 1
|
||||||
|
rm -rf flashtool/.idea
|
||||||
|
python3 -m zipapp flashtool -m flashtool:main
|
||||||
|
|
|
@ -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()
|
|
Binary file not shown.
|
@ -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()
|
|
@ -14,7 +14,7 @@ How to build the bundled flashtool.exe (on windows)
|
||||||
(2) pip install pyinstaller
|
(2) pip install pyinstaller
|
||||||
(3) pip install pyserial
|
(3) pip install pyserial
|
||||||
(4) in this directory:
|
(4) in this directory:
|
||||||
pyinstaller -F flashtool.py
|
pyinstaller --clean -F flashtool.py
|
||||||
will create flashtool.exe in dist
|
will create flashtool.exe in dist
|
||||||
|
|
||||||
|
|
||||||
|
|
101
web/config.json
101
web/config.json
|
@ -376,7 +376,7 @@
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"default": "10110",
|
"default": "10110",
|
||||||
"description": "the TCP port we listen on",
|
"description": "the TCP port we listen on",
|
||||||
"category": "TCP port"
|
"category": "TCP server"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "maxClients",
|
"name": "maxClients",
|
||||||
|
@ -387,55 +387,128 @@
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 6,
|
"max": 6,
|
||||||
"description": "the number of clients we allow to connect to us",
|
"description": "the number of clients we allow to connect to us",
|
||||||
"category": "TCP port"
|
"category": "TCP server"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sendTCP",
|
"name": "sendTCP",
|
||||||
"label": "NMEA to TCP",
|
"label": "NMEA0183 out",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": "true",
|
"default": "true",
|
||||||
"description": "send out NMEA data to connected TCP clients",
|
"description": "send out NMEA data to connected TCP clients",
|
||||||
"category": "TCP port"
|
"category": "TCP server"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "readTCP",
|
"name": "readTCP",
|
||||||
"label": "NMEA from TCP",
|
"label": "NMEA0183 in",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": "true",
|
"default": "true",
|
||||||
"description": "receive NMEA data from connected TCP clients",
|
"description": "receive NMEA data from connected TCP clients",
|
||||||
"category": "TCP port"
|
"category": "TCP server"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "tcpToN2k",
|
"name": "tcpToN2k",
|
||||||
"label": "TCP to NMEA2000",
|
"label": "to NMEA2000",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": "true",
|
"default": "true",
|
||||||
"description": "convert NMEA0183 from TCP clients to NMEA2000",
|
"description": "convert NMEA0183 from TCP clients to NMEA2000",
|
||||||
"category": "TCP port"
|
"category": "TCP server"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "tcpReadFilter",
|
"name": "tcpReadFilter",
|
||||||
"label": "TCP read Filter",
|
"label": "NMEA read Filter",
|
||||||
"type": "filter",
|
"type": "filter",
|
||||||
"default": "",
|
"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",
|
"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",
|
"name": "tcpWriteFilter",
|
||||||
"label": "TCP write Filter",
|
"label": "NMEA write Filter",
|
||||||
"type": "filter",
|
"type": "filter",
|
||||||
"default": "",
|
"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",
|
"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",
|
"name": "sendSeasmart",
|
||||||
"label": "Seasmart to TCP",
|
"label": "Seasmart out",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": "false",
|
"default": "false",
|
||||||
"description": "send NMEA2000 as seasmart to connected TCP clients",
|
"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",
|
"name": "wifiClient",
|
||||||
|
|
|
@ -43,9 +43,17 @@
|
||||||
<span class="value" id="clientIP">---</span>
|
<span class="value" id="clientIP">---</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="label"># TCP clients</span>
|
<span class="label"># clients</span>
|
||||||
<span class="value" id="numClients">---</span>
|
<span class="value" id="numClients">---</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">TCP client connected</span>
|
||||||
|
<span class="value" id="clientCon">---</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">TCP client error</span>
|
||||||
|
<span class="value" id="clientErr">---</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="reset">Reset</button>
|
<button id="reset">Reset</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
22
web/index.js
22
web/index.js
|
@ -162,6 +162,14 @@ function checkAdminPass(v){
|
||||||
return checkApPass(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){
|
function checkXDR(v,allValues){
|
||||||
if (! v) return;
|
if (! v) return;
|
||||||
let parts=v.split(',');
|
let parts=v.split(',');
|
||||||
|
@ -306,12 +314,14 @@ function updateMsgDetails(key, details) {
|
||||||
let counters={
|
let counters={
|
||||||
count2Kin: 'NMEA2000 in',
|
count2Kin: 'NMEA2000 in',
|
||||||
count2Kout: 'NMEA2000 out',
|
count2Kout: 'NMEA2000 out',
|
||||||
countTCPin: 'TCP in',
|
countTCPin: 'TCPserver in',
|
||||||
countTCPout: 'TCP out',
|
countTCPout: 'TCPserver out',
|
||||||
|
countTCPClientin: 'TCPclient in',
|
||||||
|
countTCPClientout: 'TCPclient out',
|
||||||
countUSBin: 'USB in',
|
countUSBin: 'USB in',
|
||||||
countUSBout: 'USB out',
|
countUSBout: 'USB out',
|
||||||
countSerialIn: 'Serial in',
|
countSERIn: 'Serial in',
|
||||||
countSerialOut: 'Serial out'
|
countSEROut: 'Serial out'
|
||||||
}
|
}
|
||||||
function showOverlay(text, isHtml) {
|
function showOverlay(text, isHtml) {
|
||||||
let el = document.getElementById('overlayContent');
|
let el = document.getElementById('overlayContent');
|
||||||
|
@ -1360,7 +1370,9 @@ function sourceName(v){
|
||||||
if (v == 0) return "N2K";
|
if (v == 0) return "N2K";
|
||||||
if (v == 1) return "USB";
|
if (v == 1) return "USB";
|
||||||
if (v == 2) return "SER";
|
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 "---";
|
return "---";
|
||||||
}
|
}
|
||||||
let lastSelectList=[];
|
let lastSelectList=[];
|
||||||
|
|
Loading…
Reference in New Issue