diff --git a/Readme.md b/Readme.md index 4f474f1..787e500 100644 --- a/Readme.md +++ b/Readme.md @@ -10,6 +10,13 @@ Many thanks for all the great work. This project is part of [OpenBoatProjects](https://open-boat-projects.org/de/nmea2000-gateway-mit-m5stack-atom/). +Important Hint +-------------- + +Although the term NMEA2000 is used here, the software or device is __not a certified NMEA2000 device__ as it did not pass any approval process. +There are chances that the software / device does not follow the NMEA2000 specification at several points. +If you connect the device to your NMEA2000 network you do this on your own risk. + Goal ---- Have a simple ready-to-go ESP32 binary that can be flashed onto a [M5 Atom CAN](https://docs.m5stack.com/en/atom/atom_can), potentially extended by an [Atom Tail485](https://shop.m5stack.com/collections/atom-series/products/atom-tail485?variant=32169041559642) for NMEA0183 connection and power supply. @@ -138,6 +145,10 @@ For details refer to the [example description](lib/exampletask/Readme.md). Changelog --------- +[20220112](../../releases/tag/20220112) +********* +* correctly send out seasmart if NMEA out is not configured +* enable TCP keepalive on connections to reconnect on failures [20220109](../../releases/tag/20220109) ******** * allow to set the log level in config diff --git a/lib/channel/GwChannel.cpp b/lib/channel/GwChannel.cpp index 1b703cd..d25841d 100644 --- a/lib/channel/GwChannel.cpp +++ b/lib/channel/GwChannel.cpp @@ -128,9 +128,11 @@ void GwChannel::updateCounter(const char *msg, bool out) } } -bool GwChannel::canSendOut(const char *buffer){ +bool GwChannel::canSendOut(const char *buffer, bool isSeasmart){ if (! enabled || ! impl) return false; - if (! NMEAout || readActisense) return false; + if (readActisense) return false; + if (! isSeasmart && ! NMEAout) return false; + if (isSeasmart && ! seaSmartOut) return false; if (writeFilter && ! writeFilter->canPass(buffer)) return false; return true; } @@ -177,9 +179,9 @@ void GwChannel::readMessages(GwChannel::NMEA0183Handler handler){ receiver->setHandler(handler); impl->readMessages(receiver); } -void GwChannel::sendToClients(const char *buffer, int sourceId){ +void GwChannel::sendToClients(const char *buffer, int sourceId, bool isSeasmart){ if (! impl) return; - if (canSendOut(buffer)){ + if (canSendOut(buffer,isSeasmart)){ if(impl->sendToClients(buffer,sourceId)){ updateCounter(buffer,true); } diff --git a/lib/channel/GwChannel.h b/lib/channel/GwChannel.h index c187ba6..77af597 100644 --- a/lib/channel/GwChannel.h +++ b/lib/channel/GwChannel.h @@ -56,7 +56,7 @@ class GwChannel{ } bool isEnabled(){return enabled;} bool shouldRead(){return enabled && NMEAin;} - bool canSendOut(const char *buffer); + bool canSendOut(const char *buffer, bool isSeasmart); bool canReceive(const char *buffer); bool sendSeaSmart(){ return seaSmartOut;} bool sendToN2K(){return toN2k;} @@ -67,7 +67,7 @@ class GwChannel{ void loop(bool handleRead, bool handleWrite); typedef std::function NMEA0183Handler; void readMessages(NMEA0183Handler handler); - void sendToClients(const char *buffer, int sourceId); + void sendToClients(const char *buffer, int sourceId, bool isSeasmart=false); typedef std::function N2kHandler ; void parseActisense(N2kHandler handler); void sendActisense(const tN2kMsg &msg, int sourceId); diff --git a/lib/socketserver/GwSocketHelper.h b/lib/socketserver/GwSocketHelper.h new file mode 100644 index 0000000..8dea507 --- /dev/null +++ b/lib/socketserver/GwSocketHelper.h @@ -0,0 +1,20 @@ +#pragma once +#include + +class GwSocketHelper{ + public: + static bool setKeepAlive(int socket, bool noDelay=true){ + int val=1; + if (noDelay){ + if (setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(int)) != ESP_OK) return false; + } + if (setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&val, sizeof(int)) != ESP_OK) return false; + val = 10; // seconds of idleness before first keepalive probe + if (setsockopt(socket, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) != ESP_OK) return false; + val = 5; // interval between first and subsequent keepalives + if (setsockopt(socket, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) != ESP_OK) return false; + val = 4; // number of lost keepalives before we close the socket + if (setsockopt(socket, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) != ESP_OK) return false; + return true; + } +}; \ No newline at end of file diff --git a/lib/socketserver/GwSocketServer.cpp b/lib/socketserver/GwSocketServer.cpp index 1bee3a6..c225726 100644 --- a/lib/socketserver/GwSocketServer.cpp +++ b/lib/socketserver/GwSocketServer.cpp @@ -3,6 +3,7 @@ #include #include "GwBuffer.h" #include "GwSocketConnection.h" +#include "GwSocketHelper.h" GwSocketServer::GwSocketServer(const GwConfigHandler *config, GwLog *logger, int minId) { @@ -62,11 +63,13 @@ int GwSocketServer::available() if (client_sock >= 0) { int val = 1; - if (setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&val, sizeof(int)) == ESP_OK) + if (! GwSocketHelper::setKeepAlive(client_sock,true)){ + LOG_DEBUG(GwLog::ERROR,"unable to set keepalive, nodelay on socket"); + } + else { - if (setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(int)) == ESP_OK) - fcntl(client_sock, F_SETFL, O_NONBLOCK); - return client_sock; + fcntl(client_sock, F_SETFL, O_NONBLOCK); + return client_sock; } close(client_sock); } diff --git a/lib/socketserver/GwTcpClient.cpp b/lib/socketserver/GwTcpClient.cpp index 5a81470..c8fba27 100644 --- a/lib/socketserver/GwTcpClient.cpp +++ b/lib/socketserver/GwTcpClient.cpp @@ -1,6 +1,7 @@ #include "GwTcpClient.h" #include #include +#include "GwSocketHelper.h" class ResolveArgs{ public: @@ -72,6 +73,12 @@ void GwTcpClient::startConnection() LOG_DEBUG(GwLog::ERROR,"unable to create socket: %d", errno); return; } + if (! GwSocketHelper::setKeepAlive(sockfd,true)){ + error="unable to set keepalive, nodelay on socket"; + LOG_DEBUG(GwLog::ERROR,"%s",error.c_str()); + close(sockfd); + 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 ) { diff --git a/src/main.cpp b/src/main.cpp index 96cbb48..0bbe859 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -177,18 +177,24 @@ void handleN2kMessage(const tN2kMsg &n2kMsg,int sourceId, bool isConverted=false if (sourceId == N2K_CHANNEL_ID){ countNMEA2KIn.add(n2kMsg.PGN); } - char *buf=new char[MAX_NMEA2000_MESSAGE_SEASMART_SIZE]; + char *buf=new char[MAX_NMEA2000_MESSAGE_SEASMART_SIZE+3]; std::unique_ptr bufDel(buf); bool messageCreated=false; channels.allChannels([&](GwChannel *c){ if (c->sendSeaSmart()){ if (! messageCreated){ - if (N2kToSeasmart(n2kMsg, millis(), buf, MAX_NMEA2000_MESSAGE_SEASMART_SIZE) != 0) { + size_t len; + if ((len=N2kToSeasmart(n2kMsg, millis(), buf, MAX_NMEA2000_MESSAGE_SEASMART_SIZE)) != 0) { + buf[len]=0x0d; + len++; + buf[len]=0x0a; + len++; + buf[len]=0; messageCreated=true; } } if (messageCreated){ - c->sendToClients(buf,sourceId); + c->sendToClients(buf,sourceId,true); } } }); @@ -221,7 +227,7 @@ void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg, int sourceId,bool conv buf[len+1]=0x0a; buf[len+2]=0; channels.allChannels([&](GwChannel *c){ - c->sendToClients(buf,sourceId); + c->sendToClients(buf,sourceId,false); }); } @@ -785,12 +791,16 @@ void loop() { //read channels channels.allChannels([](GwChannel *c){ c->readMessages([&](const char * buffer, int sourceId){ + bool isSeasmart=false; + if (strlen(buffer) > 6 && strncmp(buffer,"$PCDIN",6) == 0){ + isSeasmart=true; + } channels.allChannels([&](GwChannel *oc){ - oc->sendToClients(buffer,sourceId); + oc->sendToClients(buffer,sourceId,isSeasmart); oc->loop(false,true); }); if (c->sendToN2K()){ - if (strlen(buffer) > 6 && strncmp(buffer,"$PCDIN",6) == 0){ + if (isSeasmart){ tN2kMsg n2kMsg; uint32_t timestamp; if (SeasmartToN2k(buffer,timestamp,n2kMsg)){