1
0
mirror of https://github.com/thooge/esp32-nmea2000-obp60.git synced 2025-12-12 13:33:06 +01:00

Merge branch 'wellenvogel:master' into master

This commit is contained in:
norbert-walter
2022-01-06 12:01:22 +01:00
committed by GitHub
37 changed files with 1955 additions and 881 deletions

View File

@@ -30,6 +30,52 @@ class GwApi{
const String & getFormat() const{
return format;
}
};
class Status{
public:
bool wifiApOn=false;
bool wifiClientOn=false;
bool wifiClientConnected=false;
String wifiApIp;
String systemName; //is also AP SSID
String wifiApPass;
String wifiClientIp;
String wifiClientSSID;
unsigned long usbRx=0;
unsigned long usbTx=0;
unsigned long serRx=0;
unsigned long serTx=0;
unsigned long tcpSerRx=0;
unsigned long tcpSerTx=0;
int tcpClients=0;
unsigned long tcpClRx=0;
unsigned long tcpClTx=0;
bool tcpClientConnected=false;
unsigned long n2kRx=0;
unsigned long n2kTx=0;
void empty(){
wifiApOn=false;
wifiClientOn=false;
wifiClientConnected=false;
wifiApIp=String();
systemName=String(); //is also AP SSID
wifiApPass=String();
wifiClientIp=String();
wifiClientSSID=String();
usbRx=0;
usbTx=0;
serRx=0;
serTx=0;
tcpSerRx=0;
tcpSerTx=0;
tcpClients=0;
tcpClRx=0;
tcpClTx=0;
tcpClientConnected=false;
n2kRx=0;
n2kTx=0;
}
};
/**
* thread safe methods - can directly be called from a user task
@@ -58,6 +104,11 @@ class GwApi{
* just make sure to have the list being of appropriate size (numValues)
*/
virtual void getBoatDataValues(int numValues,BoatValue **list)=0;
/**
* fill the status information
*/
virtual void getStatus(Status &status);
/**
* not thread safe methods
* accessing boat data must only be executed from within the main thread

219
lib/channel/GwChannel.cpp Normal file
View File

@@ -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();
}

77
lib/channel/GwChannel.h Normal file
View File

@@ -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();
};

View File

@@ -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;}
};

View File

@@ -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();
}
}

View File

@@ -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);
};

View File

@@ -140,18 +140,21 @@ void GwNmeaFilter::parseFilter(){
// "0:1:RMB,RMC"
// 0: AIS off, 1:whitelist, list of sentences
if (isReady) return;
if (config.isEmpty()){
isReady=true;
return;
}
int found=0;
int last=0;
int index=0;
String data=config->asString();
while ((found = data.indexOf(':',last)) >= 0){
String tok=data.substring(last,found);
while ((found = config.indexOf(':',last)) >= 0){
String tok=config.substring(last,found);
handleToken(tok,index);
last=found+1;
index++;
}
if (last < data.length()){
String tok=data.substring(last);
if (last < config.length()){
String tok=config.substring(last);
handleToken(tok,index);
}
isReady=true;

View File

@@ -46,7 +46,7 @@ class GwConfigInterface{
class GwNmeaFilter{
private:
GwConfigInterface *config=NULL;
String config;
bool isReady=false;
bool ais=true;
bool blacklist=true;
@@ -54,7 +54,7 @@ class GwNmeaFilter{
void handleToken(String token, int index);
void parseFilter();
public:
GwNmeaFilter(GwConfigInterface *config){
GwNmeaFilter(String config){
this->config=config;
isReady=false;
}

View File

@@ -20,6 +20,7 @@ template<class T> class GwCounter{
globalFail=0;
globalOk=0;
}
unsigned long getGlobal(){return globalOk;}
void add(T key){
globalOk++;
auto it=okCounter.find(key);

View File

@@ -88,6 +88,7 @@ void exampleTask(GwApi *api){
GwApi::BoatValue *latitude=new GwApi::BoatValue(F("Latitude"));
GwApi::BoatValue *testValue=new GwApi::BoatValue(boatItemName);
GwApi::BoatValue *valueList[]={longitude,latitude,testValue};
GwApi::Status status;
while(true){
delay(1000);
/*
@@ -162,6 +163,29 @@ void exampleTask(GwApi *api){
LOG_DEBUG(GwLog::LOG,"%s now invalid",testValue->getName().c_str());
}
}
api->getStatus(status);
#define B(v) (v?"true":"false")
LOG_DEBUG(GwLog::LOG,"ST1:ap=%s,wc=%s,cc=%s",
B(status.wifiApOn),
B(status.wifiClientOn),
B(status.wifiClientConnected));
LOG_DEBUG(GwLog::LOG,"ST2:sn=%s,ai=%s,ap=%s,cs=%s,ci=%s",
status.systemName.c_str(),
status.wifiApIp.c_str(),
status.wifiApPass.c_str(),
status.wifiClientSSID.c_str(),
status.wifiClientIp.c_str());
LOG_DEBUG(GwLog::LOG,"ST3:ur=%ld,ut=%ld,sr=%ld,st=%ld,tr=%ld,tt=%ld,cr=%ld,ct=%ld,2r=%ld,2t=%ld",
status.usbRx,
status.usbTx,
status.serRx,
status.serTx,
status.tcpSerRx,
status.tcpSerTx,
status.tcpClRx,
status.tcpClTx,
status.n2kRx,
status.n2kTx);
}
vTaskDelete(NULL);

View File

@@ -29,11 +29,13 @@ uint16_t DaysSince1970 = 0;
class MyAisDecoder : public AIS::AisDecoder
{
public:
int sourceId=-1;
private:
NMEA0183DataToN2K::N2kSender sender;
GwLog *logger;
void send(const tN2kMsg &msg){
(*sender)(msg);
(*sender)(msg,sourceId);
}
AIS::DefaultSentenceParser parser;
public:

View File

@@ -97,26 +97,26 @@ private:
waypointMap[wpName]=newWp;
return newWp.id;
}
bool send(tN2kMsg &msg,String key,unsigned long minDiff){
bool send(tN2kMsg &msg,String key,unsigned long minDiff,int sourceId){
unsigned long now=millis();
unsigned long pgn=msg.PGN;
if (key == "") key=String(msg.PGN);
auto it=lastSends.find(key);
if (it == lastSends.end()){
lastSends[key]=now;
sender(msg);
sender(msg,sourceId);
return true;
}
if ((it->second + minDiff) <= now){
lastSends[key]=now;
sender(msg);
sender(msg,sourceId);
return true;
}
LOG_DEBUG(GwLog::DEBUG+1,"skipped n2k message %d",msg.PGN);
return false;
}
bool send(tN2kMsg &msg, String key=""){
send(msg,key,minSendInterval);
bool send(tN2kMsg &msg, int sourceId,String key=""){
return send(msg,key,minSendInterval,sourceId);
}
bool updateDouble(GwBoatItem<double> *target,double v, int sourceId){
if (v != NMEA0183DoubleNA){
@@ -255,7 +255,7 @@ private:
(tN2kFluidType)(current.selector()),
fields[0],
fields[1]);
send(n2kMsg, buildN2KKey(n2kMsg, current.mapping));
send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping));
}
break;
case XDRBAT:
@@ -263,7 +263,7 @@ private:
{
SetN2kPGN127508(n2kMsg, current.mapping.instanceId,
fields[0], fields[1], fields[2]);
send(n2kMsg, buildN2KKey(n2kMsg, current.mapping));
send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping));
}
break;
case XDRTEMP:
@@ -271,7 +271,7 @@ private:
SetN2kPGN130312(n2kMsg,1,current.mapping.instanceId,
(tN2kTempSource)(current.selector()),
fields[0],fields[1]);
send(n2kMsg,buildN2KKey(n2kMsg,current.mapping));
send(n2kMsg,msg.sourceId,buildN2KKey(n2kMsg,current.mapping));
}
break;
case XDRHUMIDITY:
@@ -281,7 +281,7 @@ private:
fields[0],
fields[1]
);
send(n2kMsg,buildN2KKey(n2kMsg,current.mapping));
send(n2kMsg,msg.sourceId,buildN2KKey(n2kMsg,current.mapping));
}
break;
case XDRPRESSURE:
@@ -289,7 +289,7 @@ private:
SetN2kPGN130314(n2kMsg,1,current.mapping.instanceId,
(tN2kPressureSource)(current.selector()),
fields[0]);
send(n2kMsg,buildN2KKey(n2kMsg,current.mapping));
send(n2kMsg,msg.sourceId,buildN2KKey(n2kMsg,current.mapping));
}
break;
case XDRENGINE:
@@ -301,14 +301,14 @@ private:
fields[0], fields[1], fields[2], fields[3], fields[4],
fields[5], fields[6], fields[7], fromDouble(fields[8]), fromDouble(fields[9]),
tN2kEngineDiscreteStatus1(), tN2kEngineDiscreteStatus2());
send(n2kMsg, buildN2KKey(n2kMsg, current.mapping));
send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping));
}
}
else{
if (fillFieldList(current, fields, 13,10)){
SetN2kPGN127488(n2kMsg,current.mapping.instanceId,
fields[10],fields[11],fromDouble(fields[12]));
send(n2kMsg, buildN2KKey(n2kMsg, current.mapping));
send(n2kMsg,msg.sourceId, buildN2KKey(n2kMsg, current.mapping));
}
}
break;
@@ -334,7 +334,7 @@ private:
mode=xteMode(*modeChar);
}
SetN2kXTE(n2kMsg,1,mode,false,rmb.xte);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
uint8_t destinationId=getWaypointId(rmb.destID);
uint8_t sourceId=getWaypointId(rmb.originID);
@@ -357,10 +357,10 @@ private:
rmb.longitude,
rmb.vmg
);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
SetN2kPGN129285(n2kMsg,sourceId,1,1,true,true,"default");
AppendN2kPGN129285(n2kMsg,destinationId,rmb.destID,rmb.latitude,rmb.longitude);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
}
void convertRMC(const SNMEA0183Msg &msg)
@@ -382,25 +382,26 @@ private:
{
SetN2kSystemTime(n2kMsg, 1, GpsDate, GpsTime);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
if (UD(Latitude) &&
UD(Longitude)){
SetN2kLatLonRapid(n2kMsg,Latitude,Longitude);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
if (UD(COG) && UD(SOG)){
SetN2kCOGSOGRapid(n2kMsg,1,N2khr_true,COG,SOG);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
if (UD(Variation)){
SetN2kMagneticVariation(n2kMsg,1,N2kmagvar_Calc,
getUint32(boatData->GpsDate), Variation);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
}
void convertAIVDX(const SNMEA0183Msg &msg){
aisDecoder->sourceId=msg.sourceId;
aisDecoder->handleMessage(msg.line);
}
void convertMWV(const SNMEA0183Msg &msg){
@@ -434,7 +435,7 @@ private:
}
if (shouldSend){
SetN2kWindSpeed(n2kMsg,1,WindSpeed,WindAngle,n2kRef);
send(n2kMsg,String(n2kMsg.PGN)+String((int)n2kRef));
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)n2kRef));
}
}
void convertVWR(const SNMEA0183Msg &msg)
@@ -475,7 +476,7 @@ private:
if (shouldSend)
{
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_Apparent);
send(n2kMsg,String(n2kMsg.PGN)+String((int)N2kWind_Apparent));
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)N2kWind_Apparent));
}
}
@@ -519,11 +520,11 @@ private:
if (shouldSend)
{
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_True_North);
send(n2kMsg,String(n2kMsg.PGN)+String((int)N2kWind_True_North));
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)N2kWind_True_North));
}
if (WindAngleMagnetic != NMEA0183DoubleNA && shouldSend){
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngleMagnetic, N2kWind_Magnetic);
send(n2kMsg,String(n2kMsg.PGN)+String((int)N2kWind_Magnetic));
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)N2kWind_Magnetic));
}
}
@@ -540,7 +541,7 @@ private:
boatData->Variation->getDataWithDefault(N2kDoubleNA),
boatData->Deviation->getDataWithDefault(N2kDoubleNA)
);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
void convertHDT(const SNMEA0183Msg &msg){
@@ -553,7 +554,7 @@ private:
if (! UD(Heading)) return;
tN2kMsg n2kMsg;
SetN2kTrueHeading(n2kMsg,1,Heading);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
void convertHDG(const SNMEA0183Msg &msg){
double MagneticHeading=NMEA0183DoubleNA;
@@ -584,7 +585,7 @@ private:
UD(Deviation);
tN2kMsg n2kMsg;
SetN2kMagneticHeading(n2kMsg,1,MagneticHeading,Deviation,Variation);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
void convertDPT(const SNMEA0183Msg &msg){
@@ -612,7 +613,7 @@ private:
if (! boatData->DepthTransducer->update(DepthBelowTransducer)) return;
tN2kMsg n2kMsg;
SetN2kWaterDepth(n2kMsg,1,DepthBelowTransducer,Offset);
send(n2kMsg,String(n2kMsg.PGN)+String((Offset != N2kDoubleNA)?1:0));
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((Offset != N2kDoubleNA)?1:0));
}
typedef enum {
DBS,
@@ -647,7 +648,7 @@ private:
if (! boatData->DepthTransducer->update(Depth,msg.sourceId)) return;
tN2kMsg n2kMsg;
SetN2kWaterDepth(n2kMsg,1,Depth,N2kDoubleNA);
send(n2kMsg,String(n2kMsg.PGN)+String(0));
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String(0));
return;
}
//we can only send if we have a valid depth beloww tranducer
@@ -667,7 +668,7 @@ private:
}
tN2kMsg n2kMsg;
SetN2kWaterDepth(n2kMsg,1,Depth,offset);
send(n2kMsg,String(n2kMsg.PGN)+String((offset != N2kDoubleNA)?1:0));
send(n2kMsg,msg.sourceId,(n2kMsg.PGN)+String((offset != N2kDoubleNA)?1:0));
}
}
}
@@ -694,7 +695,7 @@ private:
tN2kMsg n2kMsg;
if (! UD(RudderPosition)) return;
SetN2kRudder(n2kMsg,RudderPosition);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
}
@@ -711,7 +712,7 @@ private:
if (MagneticHeading == NMEA0183DoubleNA) MagneticHeading=N2kDoubleNA;
tN2kMsg n2kMsg;
SetN2kBoatSpeed(n2kMsg,1,STW);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
void convertVTG(const SNMEA0183Msg &msg){
@@ -727,7 +728,7 @@ private:
tN2kMsg n2kMsg;
//TODO: maybe use MCOG if no COG?
SetN2kCOGSOGRapid(n2kMsg,1,N2khr_true,COG,SOG);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
void convertZDA(const SNMEA0183Msg &msg){
time_t DateTime;
@@ -751,10 +752,10 @@ private:
tN2kMsg n2kMsg;
if (timezoneValid){
SetN2kLocalOffset(n2kMsg,DaysSince1970,GpsTime,Timezone);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
SetN2kSystemTime(n2kMsg,1,DaysSince1970,GpsTime);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
void convertGGA(const SNMEA0183Msg &msg){
double GPSTime=NMEA0183DoubleNA;
@@ -788,7 +789,7 @@ private:
SatelliteCount, HDOP, boatData->PDOP->getDataWithDefault(N2kDoubleNA), 0,
0, N2kGNSSt_GPS, DGPSReferenceStationID,
DGPSAge);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
void convertGSA(const SNMEA0183Msg &msg){
if (msg.FieldCount() < 17)
@@ -819,7 +820,7 @@ private:
if (!updateDouble(boatData->VDOP,VDOP,msg.sourceId)) return;
}
SetN2kGNSSDOPData(n2kMsg,1,rmode,mode,HDOP,VDOP,N2kDoubleNA);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
void convertGSV(const SNMEA0183Msg &msg){
if (msg.FieldCount() < 7){
@@ -867,7 +868,7 @@ private:
}
}
if (hasInfos){
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
}
@@ -885,7 +886,7 @@ private:
if (! updateDouble(boatData->GpsTime,GLL.GPSTime,msg.sourceId)) return;
tN2kMsg n2kMsg;
SetN2kLatLonRapid(n2kMsg,GLL.latitude,GLL.longitude);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
void convertROT(const SNMEA0183Msg &msg){
@@ -897,7 +898,7 @@ private:
if (! updateDouble(boatData->ROT,ROT,msg.sourceId)) return;
tN2kMsg n2kMsg;
SetN2kRateOfTurn(n2kMsg,1,ROT);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
void convertXTE(const SNMEA0183Msg &msg){
if (msg.FieldCount() < 6){
@@ -916,7 +917,7 @@ private:
tN2kMsg n2kMsg;
tN2kXTEMode mode=xteMode(msg.Field(5)[0]);
SetN2kXTE(n2kMsg,1,mode,false,xte);
send(n2kMsg);
send(n2kMsg,msg.sourceId);
}
//shortcut for lambda converters

View File

@@ -7,7 +7,7 @@
class NMEA0183DataToN2K{
public:
typedef bool (*N2kSender)(const tN2kMsg &msg);
typedef bool (*N2kSender)(const tN2kMsg &msg,int sourceId);
protected:
GwLog * logger;
GwBoatData *boatData;

View File

@@ -34,12 +34,11 @@
N2kDataToNMEA0183::N2kDataToNMEA0183(GwLog * logger, GwBoatData *boatData,
tSendNMEA0183MessageCallback callback, int id,String talkerId)
SendNMEA0183MessageCallback callback, String talkerId)
{
this->SendNMEA0183MessageCallback=callback;
this->sendNMEA0183MessageCallback=callback;
strncpy(this->talkerId,talkerId.c_str(),2);
this->talkerId[2]=0;
sourceId=id;
this->talkerId[2]=0;
}
@@ -50,7 +49,7 @@ void N2kDataToNMEA0183::loop() {
//*****************************************************************************
void N2kDataToNMEA0183::SendMessage(const tNMEA0183Msg &NMEA0183Msg) {
if ( SendNMEA0183MessageCallback != 0 ) SendNMEA0183MessageCallback(NMEA0183Msg, sourceId);
sendNMEA0183MessageCallback(NMEA0183Msg, sourceId);
}
/**
@@ -148,8 +147,9 @@ private:
virtual String handledKeys(){
return converters.handledKeys();
}
virtual void HandleMsg(const tN2kMsg &N2kMsg)
virtual void HandleMsg(const tN2kMsg &N2kMsg, int sourceId)
{
this->sourceId=sourceId;
String key=String(N2kMsg.PGN);
bool rt=converters.handleMessage(key,N2kMsg,this);
if (! rt){
@@ -1489,9 +1489,9 @@ private:
public:
N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData,
tSendNMEA0183MessageCallback callback, int sourceId,
SendNMEA0183MessageCallback callback,
String talkerId, GwXDRMappings *xdrMappings, int minXdrInterval)
: N2kDataToNMEA0183(logger, boatData, callback,sourceId,talkerId)
: N2kDataToNMEA0183(logger, boatData, callback,talkerId)
{
LastPosSend = 0;
lastLoopTime = 0;
@@ -1516,9 +1516,9 @@ private:
N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData,
tSendNMEA0183MessageCallback callback, int sourceId,String talkerId, GwXDRMappings *xdrMappings,
SendNMEA0183MessageCallback callback, String talkerId, GwXDRMappings *xdrMappings,
int minXdrInterval){
LOG_DEBUG(GwLog::LOG,"creating N2kToNMEA0183");
return new N2kToNMEA0183Functions(logger,boatData,callback, sourceId,talkerId,xdrMappings,minXdrInterval);
return new N2kToNMEA0183Functions(logger,boatData,callback, talkerId,xdrMappings,minXdrInterval);
}
//*****************************************************************************

View File

@@ -22,6 +22,7 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef _N2KDATATONMEA0183_H
#define _N2KDATATONMEA0183_H
#include <functional>
#include <NMEA0183.h>
#include <NMEA2000.h>
@@ -34,22 +35,22 @@ class GwJsonDocument;
class N2kDataToNMEA0183
{
public:
using tSendNMEA0183MessageCallback = void (*)(const tNMEA0183Msg &NMEA0183Msg, int id);
typedef std::function<void(const tNMEA0183Msg &NMEA0183Msg,int id)> SendNMEA0183MessageCallback;
protected:
GwLog *logger;
GwBoatData *boatData;
int sourceId;
int sourceId=0;
char talkerId[3];
tSendNMEA0183MessageCallback SendNMEA0183MessageCallback;
SendNMEA0183MessageCallback sendNMEA0183MessageCallback;
void SendMessage(const tNMEA0183Msg &NMEA0183Msg);
N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData,
tSendNMEA0183MessageCallback callback, int sourceId,String talkerId);
SendNMEA0183MessageCallback callback, String talkerId);
public:
static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, tSendNMEA0183MessageCallback callback,
int sourceId,String talkerId, GwXDRMappings *xdrMappings,int minXdrInterval=100);
virtual void HandleMsg(const tN2kMsg &N2kMsg) = 0;
static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, SendNMEA0183MessageCallback callback,
String talkerId, GwXDRMappings *xdrMappings,int minXdrInterval=100);
virtual void HandleMsg(const tN2kMsg &N2kMsg, int sourceId) = 0;
virtual void loop();
virtual ~N2kDataToNMEA0183(){}
virtual unsigned long* handledPgns()=0;

View File

@@ -22,7 +22,7 @@ typedef size_t (*GwBufferHandleFunction)(uint8_t *buffer, size_t len, void *para
class GwBuffer{
public:
static const size_t TX_BUFFER_SIZE=1620; // app. 20 NMEA messages
static const size_t RX_BUFFER_SIZE=400; // enough for 1 NMEA message or actisense message
static const size_t RX_BUFFER_SIZE=600; // enough for 1 NMEA message or actisense message or seasmart message
typedef enum {
OK,
ERROR,

View File

@@ -96,7 +96,7 @@ size_t GwSerial::sendToClients(const char *buf,int sourceId,bool partial){
}
return enqueued;
}
void GwSerial::loop(bool handleRead){
void GwSerial::loop(bool handleRead,bool handleWrite){
write();
if (! isInitialized()) return;
if (! handleRead) return;
@@ -116,10 +116,10 @@ void GwSerial::loop(bool handleRead){
serial->readBytes(buffer,available);
}
}
bool GwSerial::readMessages(GwMessageFetcher *writer){
if (! isInitialized()) return false;
if (! allowRead) return false;
return writer->handleBuffer(readBuffer);
void GwSerial::readMessages(GwMessageFetcher *writer){
if (! isInitialized()) return;
if (! allowRead) return;
writer->handleBuffer(readBuffer);
}
void GwSerial::flush(){

View File

@@ -3,8 +3,9 @@
#include "HardwareSerial.h"
#include "GwLog.h"
#include "GwBuffer.h"
#include "GwChannelInterface.h"
class GwSerialStream;
class GwSerial{
class GwSerial : public GwChannelInterface{
private:
GwBuffer *buffer;
GwBuffer *readBuffer=NULL;
@@ -23,11 +24,11 @@ class GwSerial{
~GwSerial();
int setup(int baud,int rxpin,int txpin);
bool isInitialized();
size_t sendToClients(const char *buf,int sourceId,bool partial=false);
void loop(bool handleRead=true);
bool readMessages(GwMessageFetcher *writer);
virtual size_t sendToClients(const char *buf,int sourceId,bool partial=false);
virtual void loop(bool handleRead=true,bool handleWrite=true);
virtual void readMessages(GwMessageFetcher *writer);
void flush();
Stream *getStream(bool partialWrites);
virtual Stream *getStream(bool partialWrites);
friend GwSerialStream;
};
#endif

View File

@@ -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);
}

View File

@@ -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);
};

View File

@@ -2,189 +2,92 @@
#include <ESPmDNS.h>
#include <lwip/sockets.h>
#include "GwBuffer.h"
#include "GwSocketConnection.h"
class GwClient{
public:
wiFiClientPtr client;
int overflows;
String remoteIp;
private:
unsigned long lastWrite=0;
unsigned long writeTimeout=10000;
bool pendingWrite=false;
bool writeError=false;
bool allowRead;
GwBuffer *buffer=NULL;
GwBuffer *readBuffer=NULL;
GwLog *logger;
public:
GwClient(wiFiClientPtr client,GwLog *logger,int id, bool allowRead=false){
this->client=client;
this->logger=logger;
this->allowRead=allowRead;
String bufName="Sock(";
bufName+=String(id);
bufName+=")";
buffer=new GwBuffer(logger,GwBuffer::TX_BUFFER_SIZE,bufName+"wr");
if (allowRead){
readBuffer=new GwBuffer(logger,GwBuffer::RX_BUFFER_SIZE,bufName+"rd");
}
overflows=0;
if (client != NULL){
remoteIp=client->remoteIP().toString();
}
}
void setClient(wiFiClientPtr client){
this->client=client;
buffer->reset("new client");
if (readBuffer) readBuffer->reset("new client");
overflows=0;
pendingWrite=false;
writeError=false;
lastWrite=0;
if (client){
remoteIp=client->remoteIP().toString();
}
else{
remoteIp=String("---");
}
}
bool hasClient(){
return client != NULL;
}
~GwClient(){
delete buffer;
if (readBuffer) delete readBuffer;
}
bool enqueue(uint8_t *data, size_t len){
if (len == 0) return true;
size_t rt=buffer->addData(data,len);
if (rt < len){
LOG_DEBUG(GwLog::LOG,"overflow on %s",remoteIp.c_str());
overflows++;
return false;
}
return true;
}
bool hasData(){
return buffer->usedSpace() > 0;
}
bool handleError(int res,bool errorIf0=true){
if (res == 0 && errorIf0){
LOG_DEBUG(GwLog::LOG,"client shutdown (recv 0) on %s",remoteIp.c_str());
client->stop();
return false;
}
if (res < 0){
if (errno != EAGAIN){
LOG_DEBUG(GwLog::LOG,"client read error %d on %s",errno,remoteIp.c_str());
client->stop();
return false;
}
return false;
}
return true;
}
GwBuffer::WriteStatus write(){
if (! hasClient()) {
LOG_DEBUG(GwLog::LOG,"write called on empty client");
return GwBuffer::ERROR;
}
if (! buffer->usedSpace()){
pendingWrite=false;
return GwBuffer::OK;
}
buffer->fetchData(-1,[](uint8_t *buffer, size_t len, void *param)->size_t{
GwClient *c=(GwClient*)param;
int res = send(c->client->fd(), (void*) buffer, len, MSG_DONTWAIT);
if (! c->handleError(res,false)) return 0;
if (res >= len){
c->pendingWrite=false;
}
else{
if (!c->pendingWrite){
c->lastWrite=millis();
c->pendingWrite=true;
}
else{
//we need to check if we have still not been able
//to write until timeout
if (millis() >= (c->lastWrite+c->writeTimeout)){
c->logger->logDebug(GwLog::ERROR,"Write timeout on channel %s",c->remoteIp.c_str());
c->writeError=true;
}
}
}
return res;
},this);
if (writeError){
LOG_DEBUG(GwLog::DEBUG+1,"write error on %s",remoteIp.c_str());
return GwBuffer::ERROR;
}
return GwBuffer::OK;
}
bool read(){
if (! allowRead){
size_t maxLen=100;
char buffer[maxLen];
int res = recv(client->fd(), (void*) buffer, maxLen, MSG_DONTWAIT);
return handleError(res);
}
readBuffer->fillData(-1,[](uint8_t *buffer, size_t len, void *param)->size_t{
GwClient *c=(GwClient*)param;
int res = recv(c->client->fd(), (void*) buffer, len, MSG_DONTWAIT);
if (! c->handleError(res)) return 0;
return res;
},this);
return true;
}
bool messagesFromBuffer(GwMessageFetcher *writer){
if (! allowRead) return false;
return writer->handleBuffer(readBuffer);
}
};
GwSocketServer::GwSocketServer(const GwConfigHandler *config,GwLog *logger,int minId){
this->config=config;
this->logger=logger;
this->minId=minId;
maxClients=1;
allowReceive=false;
}
void GwSocketServer::begin(){
maxClients=config->getInt(config->maxClients);
allowReceive=config->getBool(config->readTCP);
clients=new gwClientPtr[maxClients];
for (int i=0;i<maxClients;i++){
clients[i]=gwClientPtr(new GwClient(wiFiClientPtr(NULL),logger,i,allowReceive));
}
server=new WiFiServer(config->getInt(config->serverPort),maxClients+1);
server->begin();
LOG_DEBUG(GwLog::LOG,"Socket server created, port=%d",
config->getInt(config->serverPort));
MDNS.addService("_nmea-0183","_tcp",config->getInt(config->serverPort));
}
void GwSocketServer::loop(bool handleRead,bool handleWrite)
GwSocketServer::GwSocketServer(const GwConfigHandler *config, GwLog *logger, int minId)
{
if (! clients) return;
WiFiClient client = server->available(); // listen for incoming clients
if (client)
this->config = config;
this->logger = logger;
this->minId = minId;
maxClients = 1;
allowReceive = false;
}
bool GwSocketServer::createListener()
{
struct sockaddr_in server;
listener = socket(AF_INET, SOCK_STREAM, 0);
if (listener < 0)
{
LOG_DEBUG(GwLog::LOG,"new client connected from %s",
client.remoteIP().toString().c_str());
fcntl(client.fd(), F_SETFL, O_NONBLOCK);
return false;
}
int enable = 1;
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(listenerPort);
if (bind(listener, (struct sockaddr *)&server, sizeof(server)) < 0)
return false;
if (listen(listener, maxClients) < 0)
return false;
fcntl(listener, F_SETFL, O_NONBLOCK);
return true;
}
void GwSocketServer::begin()
{
maxClients = config->getInt(config->maxClients);
allowReceive = config->getBool(config->readTCP);
listenerPort=config->getInt(config->serverPort);
clients = new GwSocketConnection*[maxClients];
for (int i = 0; i < maxClients; i++)
{
clients[i] = new GwSocketConnection(logger, i, allowReceive);
}
if (! createListener()){
listener=-1;
LOG_DEBUG(GwLog::ERROR,"Unable to create listener");
return;
}
LOG_DEBUG(GwLog::LOG, "Socket server created, port=%d",
config->getInt(config->serverPort));
MDNS.addService("_nmea-0183", "_tcp", config->getInt(config->serverPort));
}
int GwSocketServer::available()
{
if (listener < 0)
return -1;
int client_sock;
struct sockaddr_in _client;
int cs = sizeof(struct sockaddr_in);
client_sock = lwip_accept_r(listener, (struct sockaddr *)&_client, (socklen_t *)&cs);
if (client_sock >= 0)
{
int val = 1;
if (setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&val, sizeof(int)) == ESP_OK)
{
if (setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(int)) == ESP_OK)
fcntl(client_sock, F_SETFL, O_NONBLOCK);
return client_sock;
}
close(client_sock);
}
return -1;
}
void GwSocketServer::loop(bool handleRead, bool handleWrite)
{
if (!clients)
return;
int client = available(); // listen for incoming clients
if (client >= 0)
{
LOG_DEBUG(GwLog::LOG, "new client connected from %s",
GwSocketConnection::remoteIP(client).toString().c_str());
bool canHandle = false;
for (int i = 0; i < maxClients; i++)
{
if (!clients[i]->hasClient())
{
clients[i]->setClient(wiFiClientPtr(new WiFiClient(client)));
LOG_DEBUG(GwLog::LOG,"set client as number %d", i);
clients[i]->setClient(client);
LOG_DEBUG(GwLog::LOG, "set client as number %d", i);
canHandle = true;
break;
}
@@ -192,7 +95,7 @@ void GwSocketServer::loop(bool handleRead,bool handleWrite)
if (!canHandle)
{
logger->logDebug(GwLog::ERROR, "no space to store client, disconnect");
client.stop();
close(client);
}
}
if (handleWrite)
@@ -200,69 +103,83 @@ void GwSocketServer::loop(bool handleRead,bool handleWrite)
//sending
for (int i = 0; i < maxClients; i++)
{
gwClientPtr client = clients[i];
GwSocketConnection *client = clients[i];
if (!client->hasClient())
continue;
GwBuffer::WriteStatus rt = client->write();
if (rt == GwBuffer::ERROR)
{
LOG_DEBUG(GwLog::ERROR, "write error on %s, closing", client->remoteIp.c_str());
client->client->stop();
LOG_DEBUG(GwLog::ERROR, "write error on %s, closing", client->remoteIpAddress.c_str());
client->stop();
}
}
}
for (int i = 0; i < maxClients; i++)
{
gwClientPtr client = clients[i];
GwSocketConnection *client = clients[i];
if (!client->hasClient())
continue;
if (!client->client->connected())
if (!client->connected())
{
LOG_DEBUG(GwLog::LOG,"client %d disconnect %s", i, client->remoteIp.c_str());
client->client->stop();
client->setClient(NULL);
LOG_DEBUG(GwLog::LOG, "client %d disconnect %s", i, client->remoteIpAddress.c_str());
client->stop();
}
else
{
if (handleRead) client->read();
if (handleRead)
client->read();
}
}
}
bool GwSocketServer::readMessages(GwMessageFetcher *writer){
if (! allowReceive || ! clients) return false;
bool hasMessages=false;
for (int i = 0; i < maxClients; i++){
writer->id=minId+i;
if (!clients[i]->hasClient()) continue;
if (clients[i]->messagesFromBuffer(writer)) hasMessages=true;
}
return hasMessages;
}
void GwSocketServer::sendToClients(const char *buf,int source){
if (! clients) return;
int len=strlen(buf);
int sourceIndex=source-minId;
void GwSocketServer::readMessages(GwMessageFetcher *writer)
{
if (!allowReceive || !clients)
return;
for (int i = 0; i < maxClients; i++)
{
if (i == sourceIndex)continue; //never send out to the source we received from
gwClientPtr client = clients[i];
if (! client->hasClient()) continue;
if ( client->client->connected() ) {
client->enqueue((uint8_t*)buf,len);
writer->id = minId + i;
if (!clients[i]->hasClient())
continue;
clients[i]->messagesFromBuffer(writer);
}
return;
}
size_t GwSocketServer::sendToClients(const char *buf, int source,bool partial)
{
if (!clients)
return 0;
bool hasSend=false;
int len = strlen(buf);
int sourceIndex = source - minId;
for (int i = 0; i < maxClients; i++)
{
if (i == sourceIndex)
continue; //never send out to the source we received from
GwSocketConnection *client = clients[i];
if (!client->hasClient())
continue;
if (client->connected())
{
if(client->enqueue((uint8_t *)buf, len)) hasSend=true;
}
}
return hasSend?len:0;
}
int GwSocketServer::numClients(){
if (! clients) return 0;
int num=0;
for (int i = 0; i < maxClients; i++){
if (clients[i]->hasClient()) num++;
int GwSocketServer::numClients()
{
if (!clients)
return 0;
int num = 0;
for (int i = 0; i < maxClients; i++)
{
if (clients[i]->hasClient())
num++;
}
return num;
}
GwSocketServer::~GwSocketServer(){
GwSocketServer::~GwSocketServer()
{
}

View File

@@ -3,28 +3,29 @@
#include "GWConfig.h"
#include "GwLog.h"
#include "GwBuffer.h"
#include "GwChannelInterface.h"
#include <memory>
#include <WiFi.h>
using wiFiClientPtr = std::shared_ptr<WiFiClient>;
class GwClient;
using gwClientPtr = std::shared_ptr<GwClient>;
class GwSocketServer{
class GwSocketConnection;
class GwSocketServer: public GwChannelInterface{
private:
const GwConfigHandler *config;
GwLog *logger;
gwClientPtr *clients=NULL;
WiFiServer *server=NULL;
GwSocketConnection **clients=NULL;
int listener=-1;
int listenerPort=-1;
bool allowReceive;
int maxClients;
int minId;
bool createListener();
int available();
public:
GwSocketServer(const GwConfigHandler *config,GwLog *logger,int minId);
~GwSocketServer();
void begin();
void loop(bool handleRead=true,bool handleWrite=true);
void sendToClients(const char *buf,int sourceId);
virtual void loop(bool handleRead=true,bool handleWrite=true);
virtual size_t sendToClients(const char *buf,int sourceId, bool partialWrite=false);
int numClients();
bool readMessages(GwMessageFetcher *writer);
virtual void readMessages(GwMessageFetcher *writer);
};
#endif

View File

@@ -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;
}

View File

@@ -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;}
};

View File

@@ -99,6 +99,10 @@ public:
GWSYNCHRONIZED(mainLock);
api->getBoatDataValues(num,list);
}
virtual void getStatus(Status &status){
GWSYNCHRONIZED(mainLock);
api->getStatus(status);
}
virtual ~TaskApi(){};
};

View File

@@ -23,5 +23,7 @@ class GwWifi{
bool clientConnected();
bool connectClient();
String apIP();
bool isApActive(){return apActive;}
bool isClientActive(){return wifiClient->asBoolean();}
};
#endif