Merge branch 'wellenvogel:master' into master
This commit is contained in:
commit
3035314825
11
Readme.md
11
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/).
|
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
|
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.
|
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
|
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)
|
[20220109](../../releases/tag/20220109)
|
||||||
********
|
********
|
||||||
* allow to set the log level in config
|
* allow to set the log level in config
|
||||||
|
|
|
@ -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 (! 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;
|
if (writeFilter && ! writeFilter->canPass(buffer)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -177,9 +179,9 @@ void GwChannel::readMessages(GwChannel::NMEA0183Handler handler){
|
||||||
receiver->setHandler(handler);
|
receiver->setHandler(handler);
|
||||||
impl->readMessages(receiver);
|
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 (! impl) return;
|
||||||
if (canSendOut(buffer)){
|
if (canSendOut(buffer,isSeasmart)){
|
||||||
if(impl->sendToClients(buffer,sourceId)){
|
if(impl->sendToClients(buffer,sourceId)){
|
||||||
updateCounter(buffer,true);
|
updateCounter(buffer,true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ class GwChannel{
|
||||||
}
|
}
|
||||||
bool isEnabled(){return enabled;}
|
bool isEnabled(){return enabled;}
|
||||||
bool shouldRead(){return enabled && NMEAin;}
|
bool shouldRead(){return enabled && NMEAin;}
|
||||||
bool canSendOut(const char *buffer);
|
bool canSendOut(const char *buffer, bool isSeasmart);
|
||||||
bool canReceive(const char *buffer);
|
bool canReceive(const char *buffer);
|
||||||
bool sendSeaSmart(){ return seaSmartOut;}
|
bool sendSeaSmart(){ return seaSmartOut;}
|
||||||
bool sendToN2K(){return toN2k;}
|
bool sendToN2K(){return toN2k;}
|
||||||
|
@ -67,7 +67,7 @@ class GwChannel{
|
||||||
void loop(bool handleRead, bool handleWrite);
|
void loop(bool handleRead, bool handleWrite);
|
||||||
typedef std::function<void(const char *buffer, int sourceid)> NMEA0183Handler;
|
typedef std::function<void(const char *buffer, int sourceid)> NMEA0183Handler;
|
||||||
void readMessages(NMEA0183Handler handler);
|
void readMessages(NMEA0183Handler handler);
|
||||||
void sendToClients(const char *buffer, int sourceId);
|
void sendToClients(const char *buffer, int sourceId, bool isSeasmart=false);
|
||||||
typedef std::function<void(const tN2kMsg &msg, int sourceId)> N2kHandler ;
|
typedef std::function<void(const tN2kMsg &msg, int sourceId)> N2kHandler ;
|
||||||
void parseActisense(N2kHandler handler);
|
void parseActisense(N2kHandler handler);
|
||||||
void sendActisense(const tN2kMsg &msg, int sourceId);
|
void sendActisense(const tN2kMsg &msg, int sourceId);
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
#include <lwip/sockets.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
|
@ -3,6 +3,7 @@
|
||||||
#include <lwip/sockets.h>
|
#include <lwip/sockets.h>
|
||||||
#include "GwBuffer.h"
|
#include "GwBuffer.h"
|
||||||
#include "GwSocketConnection.h"
|
#include "GwSocketConnection.h"
|
||||||
|
#include "GwSocketHelper.h"
|
||||||
|
|
||||||
GwSocketServer::GwSocketServer(const GwConfigHandler *config, GwLog *logger, int minId)
|
GwSocketServer::GwSocketServer(const GwConfigHandler *config, GwLog *logger, int minId)
|
||||||
{
|
{
|
||||||
|
@ -62,11 +63,13 @@ int GwSocketServer::available()
|
||||||
if (client_sock >= 0)
|
if (client_sock >= 0)
|
||||||
{
|
{
|
||||||
int val = 1;
|
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);
|
||||||
fcntl(client_sock, F_SETFL, O_NONBLOCK);
|
return client_sock;
|
||||||
return client_sock;
|
|
||||||
}
|
}
|
||||||
close(client_sock);
|
close(client_sock);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "GwTcpClient.h"
|
#include "GwTcpClient.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
|
#include "GwSocketHelper.h"
|
||||||
|
|
||||||
class ResolveArgs{
|
class ResolveArgs{
|
||||||
public:
|
public:
|
||||||
|
@ -72,6 +73,12 @@ void GwTcpClient::startConnection()
|
||||||
LOG_DEBUG(GwLog::ERROR,"unable to create socket: %d", errno);
|
LOG_DEBUG(GwLog::ERROR,"unable to create socket: %d", errno);
|
||||||
return;
|
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 );
|
fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK );
|
||||||
int res = lwip_connect_r(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
|
int res = lwip_connect_r(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
|
||||||
if (res < 0 ) {
|
if (res < 0 ) {
|
||||||
|
|
22
src/main.cpp
22
src/main.cpp
|
@ -177,18 +177,24 @@ void handleN2kMessage(const tN2kMsg &n2kMsg,int sourceId, bool isConverted=false
|
||||||
if (sourceId == N2K_CHANNEL_ID){
|
if (sourceId == N2K_CHANNEL_ID){
|
||||||
countNMEA2KIn.add(n2kMsg.PGN);
|
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<char> bufDel(buf);
|
std::unique_ptr<char> bufDel(buf);
|
||||||
bool messageCreated=false;
|
bool messageCreated=false;
|
||||||
channels.allChannels([&](GwChannel *c){
|
channels.allChannels([&](GwChannel *c){
|
||||||
if (c->sendSeaSmart()){
|
if (c->sendSeaSmart()){
|
||||||
if (! messageCreated){
|
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;
|
messageCreated=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (messageCreated){
|
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+1]=0x0a;
|
||||||
buf[len+2]=0;
|
buf[len+2]=0;
|
||||||
channels.allChannels([&](GwChannel *c){
|
channels.allChannels([&](GwChannel *c){
|
||||||
c->sendToClients(buf,sourceId);
|
c->sendToClients(buf,sourceId,false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -785,12 +791,16 @@ void loop() {
|
||||||
//read channels
|
//read channels
|
||||||
channels.allChannels([](GwChannel *c){
|
channels.allChannels([](GwChannel *c){
|
||||||
c->readMessages([&](const char * buffer, int sourceId){
|
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){
|
channels.allChannels([&](GwChannel *oc){
|
||||||
oc->sendToClients(buffer,sourceId);
|
oc->sendToClients(buffer,sourceId,isSeasmart);
|
||||||
oc->loop(false,true);
|
oc->loop(false,true);
|
||||||
});
|
});
|
||||||
if (c->sendToN2K()){
|
if (c->sendToN2K()){
|
||||||
if (strlen(buffer) > 6 && strncmp(buffer,"$PCDIN",6) == 0){
|
if (isSeasmart){
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
uint32_t timestamp;
|
uint32_t timestamp;
|
||||||
if (SeasmartToN2k(buffer,timestamp,n2kMsg)){
|
if (SeasmartToN2k(buffer,timestamp,n2kMsg)){
|
||||||
|
|
Loading…
Reference in New Issue