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

Merge branch 'wellenvogel:master' into oldenv

This commit is contained in:
free-x
2024-11-15 08:27:35 +01:00
committed by GitHub
57 changed files with 6111 additions and 3026 deletions

View File

@@ -6,7 +6,9 @@
#include "GWConfig.h"
#include "GwBoatData.h"
#include "GwXDRMappings.h"
#include "GwSynchronized.h"
#include <map>
#include <ESPAsyncWebServer.h>
class GwApi;
typedef void (*GwUserTaskFunction)(GwApi *);
//API to be used for additional tasks
@@ -95,6 +97,8 @@ class GwApi{
unsigned long ser2Tx=0;
unsigned long tcpSerRx=0;
unsigned long tcpSerTx=0;
unsigned long udpwTx=0;
unsigned long udprRx=0;
int tcpClients=0;
unsigned long tcpClRx=0;
unsigned long tcpClTx=0;
@@ -169,6 +173,20 @@ class GwApi{
virtual void remove(int idx){}
virtual TaskInterfaces * taskInterfaces()=0;
/**
* register handler for web URLs
* Please be aware that this handler function will always be called from a separate
* task. So you must ensure proper synchronization!
*/
using HandlerFunction=std::function<void(AsyncWebServerRequest *)>;
/**
* @param url: the url of that will trigger the handler.
* it will be prefixed with /api/user/<taskname>
* taskname is the name that you used in addUserTask
* @param handler: the handler function (see remark above about thread synchronization)
*/
virtual void registerRequestHandler(const String &url,HandlerFunction handler)=0;
/**
* only allowed during init methods
*/

View File

@@ -1,6 +1,7 @@
#include "GwBoatData.h"
#include <GwJsonDocument.h>
#include <ArduinoJson/Json/TextFormatter.hpp>
#include "GWConfig.h"
#define GWTYPE_DOUBLE 1
#define GWTYPE_UINT32 2
#define GWTYPE_UINT16 3
@@ -35,8 +36,48 @@ GwBoatItemBase::GwBoatItemBase(String name, String format, unsigned long invalid
this->format = format;
this->type = 0;
this->lastUpdateSource = -1;
this->toType=TOType::user;
}
GwBoatItemBase::GwBoatItemBase(String name, String format, GwBoatItemBase::TOType toType)
{
lastSet = 0;
this->invalidTime = INVALID_TIME;
this->toType=toType;
this->name = name;
this->format = format;
this->type = 0;
this->lastUpdateSource = -1;
}
void GwBoatItemBase::setInvalidTime(GwConfigHandler *cfg){
if (toType != TOType::user){
unsigned long timeout=GwBoatItemBase::INVALID_TIME;
switch(getToType()){
case GwBoatItemBase::TOType::ais:
timeout=cfg->getInt(GwConfigDefinitions::timoAis);
break;
case GwBoatItemBase::TOType::def:
timeout=cfg->getInt(GwConfigDefinitions::timoDefault);
break;
case GwBoatItemBase::TOType::lng:
timeout=cfg->getInt(GwConfigDefinitions::timoLong);
break;
case GwBoatItemBase::TOType::sensor:
timeout=cfg->getInt(GwConfigDefinitions::timoSensor);
break;
case GwBoatItemBase::TOType::keep:
timeout=0;
break;
}
invalidTime=timeout;
}
}
size_t GwBoatItemBase::getJsonSize() { return JSON_OBJECT_SIZE(10); }
void GwBoatItemBase::GwBoatItemMap::add(const String &name,GwBoatItemBase *item){
boatData->setInvalidTime(item);
(*this)[name]=item;
}
#define STRING_SIZE 40
GwBoatItemBase::StringWriter::StringWriter()
{
@@ -110,7 +151,17 @@ GwBoatItem<T>::GwBoatItem(String name, String formatInfo, unsigned long invalidT
this->type = GwBoatItemTypes::getType(dummy);
if (map)
{
(*map)[name] = this;
map->add(name,this);
}
}
template <class T>
GwBoatItem<T>::GwBoatItem(String name, String formatInfo, GwBoatItemBase::TOType toType, GwBoatItemMap *map) : GwBoatItemBase(name, formatInfo, toType)
{
T dummy;
this->type = GwBoatItemTypes::getType(dummy);
if (map)
{
map->add(name,this);
}
}
@@ -246,14 +297,13 @@ void GwSatInfoList::houseKeeping(unsigned long ts)
sats.end(),
[ts, this](const GwSatInfo &info)
{
return (info.timestamp + lifeTime) < ts;
return info.validTill < ts;
}),
sats.end());
}
void GwSatInfoList::update(GwSatInfo entry)
void GwSatInfoList::update(GwSatInfo entry, unsigned long validTill)
{
unsigned long now = millis();
entry.timestamp = now;
entry.validTill = validTill;
for (auto it = sats.begin(); it != sats.end(); it++)
{
if (it->PRN == entry.PRN)
@@ -267,7 +317,7 @@ void GwSatInfoList::update(GwSatInfo entry)
sats.push_back(entry);
}
GwBoatDataSatList::GwBoatDataSatList(String name, String formatInfo, unsigned long invalidTime, GwBoatItemMap *map) : GwBoatItem<GwSatInfoList>(name, formatInfo, invalidTime, map) {}
GwBoatDataSatList::GwBoatDataSatList(String name, String formatInfo, GwBoatItemBase::TOType toType, GwBoatItemMap *map) : GwBoatItem<GwSatInfoList>(name, formatInfo, toType, map) {}
bool GwBoatDataSatList::update(GwSatInfo info, int source)
{
@@ -284,7 +334,7 @@ bool GwBoatDataSatList::update(GwSatInfo info, int source)
}
lastUpdateSource = source;
uls(now);
data.update(info);
data.update(info,now+invalidTime);
return true;
}
void GwBoatDataSatList::toJsonDoc(GwJsonDocument *doc, unsigned long minTime)
@@ -293,9 +343,15 @@ void GwBoatDataSatList::toJsonDoc(GwJsonDocument *doc, unsigned long minTime)
GwBoatItem<GwSatInfoList>::toJsonDoc(doc, minTime);
}
GwBoatData::GwBoatData(GwLog *logger)
GwBoatData::GwBoatData(GwLog *logger, GwConfigHandler *cfg)
{
this->logger = logger;
this->config = cfg;
}
void GwBoatData::begin(){
for (auto &&it : values){
it.second->setInvalidTime(config);
}
}
GwBoatData::~GwBoatData()
{
@@ -326,7 +382,7 @@ GwBoatItem<T> *GwBoatData::getOrCreate(T initial, GwBoatItemNameProvider *provid
provider->getBoatItemFormat(),
provider->getInvalidTime(),
&values);
rt->update(initial);
rt->update(initial,-1);
LOG_DEBUG(GwLog::LOG, "creating boatItem %s, type %d",
name.c_str(), rt->getCurrentType());
return rt;
@@ -408,6 +464,10 @@ double GwBoatData::getDoubleValue(String name, double defaultv)
return defaultv;
return it->second->getDoubleValue();
}
void GwBoatData::setInvalidTime(GwBoatItemBase *item){
if (config != nullptr) item->setInvalidTime(config);
}
double formatCourse(double cv)
{
double rt = cv * 180.0 / M_PI;

View File

@@ -2,6 +2,7 @@
#define _GWBOATDATA_H
#include "GwLog.h"
#include "GWConfig.h"
#include <Arduino.h>
#include <map>
#include <vector>
@@ -13,8 +14,18 @@
#define ROT_WA_FACTOR 60
class GwJsonDocument;
class GwBoatData;
class GwBoatItemBase{
public:
using TOType=enum{
def=1,
ais=2,
sensor=3,
lng=4,
user=5,
keep=6
};
class StringWriter{
uint8_t *buffer =NULL;
uint8_t *wp=NULL;
@@ -31,7 +42,7 @@ class GwBoatItemBase{
bool baseFilled();
void reset();
};
static const unsigned long INVALID_TIME=60000;
static const long INVALID_TIME=60000;
//the formatter names that must be known in js
GWSC(formatCourse);
GWSC(formatKnots);
@@ -47,14 +58,14 @@ class GwBoatItemBase{
GWSC(formatRot);
GWSC(formatDate);
GWSC(formatTime);
typedef std::map<String,GwBoatItemBase*> GwBoatItemMap;
protected:
int type;
unsigned long lastSet=0;
unsigned long invalidTime=INVALID_TIME;
long invalidTime=INVALID_TIME;
String name;
String format;
StringWriter writer;
TOType toType=TOType::def;
void uls(unsigned long ts=0){
if (ts) lastSet=ts;
else lastSet=millis();
@@ -65,7 +76,8 @@ class GwBoatItemBase{
int getCurrentType(){return type;}
unsigned long getLastSet() const {return lastSet;}
bool isValid(unsigned long now=0) const ;
GwBoatItemBase(String name,String format,unsigned long invalidTime=INVALID_TIME);
GwBoatItemBase(String name,String format,TOType toType);
GwBoatItemBase(String name,String format,unsigned long invalidTime);
virtual ~GwBoatItemBase(){}
void invalidate(){
lastSet=0;
@@ -82,17 +94,25 @@ class GwBoatItemBase{
virtual double getDoubleValue()=0;
String getName(){return name;}
const String & getFormat() const{return format;}
virtual void setInvalidTime(GwConfigHandler *cfg);
TOType getToType(){return toType;}
class GwBoatItemMap : public std::map<String,GwBoatItemBase*>{
GwBoatData *boatData;
public:
GwBoatItemMap(GwBoatData *bd):boatData(bd){}
void add(const String &name,GwBoatItemBase *item);
};
};
class GwBoatData;
template<class T> class GwBoatItem : public GwBoatItemBase{
protected:
T data;
bool lastStringValid=false;
public:
GwBoatItem(String name,String formatInfo,unsigned long invalidTime=INVALID_TIME,GwBoatItemMap *map=NULL);
GwBoatItem(String name,String formatInfo,TOType toType,GwBoatItemMap *map=NULL);
virtual ~GwBoatItem(){}
bool update(T nv, int source=-1);
bool updateMax(T nv,int sourceId=-1);
bool update(T nv, int source);
bool updateMax(T nv,int sourceId);
T getData(){
return data;
}
@@ -118,14 +138,14 @@ class GwSatInfo{
uint32_t Elevation;
uint32_t Azimut;
uint32_t SNR;
unsigned long timestamp;
unsigned long validTill;
};
class GwSatInfoList{
public:
static const unsigned long lifeTime=32000;
static const GwBoatItemBase::TOType toType=GwBoatItemBase::TOType::lng;
std::vector<GwSatInfo> sats;
void houseKeeping(unsigned long ts=0);
void update(GwSatInfo entry);
void update(GwSatInfo entry, unsigned long validTill);
int getNumSats() const{
return sats.size();
}
@@ -139,7 +159,7 @@ class GwSatInfoList{
class GwBoatDataSatList : public GwBoatItem<GwSatInfoList>
{
public:
GwBoatDataSatList(String name, String formatInfo, unsigned long invalidTime = INVALID_TIME, GwBoatItemMap *map = NULL);
GwBoatDataSatList(String name, String formatInfo, GwBoatItemBase::TOType toType, GwBoatItemMap *map = NULL);
bool update(GwSatInfo info, int source);
virtual void toJsonDoc(GwJsonDocument *doc, unsigned long minTime);
GwSatInfo *getAt(int idx){
@@ -164,59 +184,65 @@ public:
virtual unsigned long getInvalidTime(){ return GwBoatItemBase::INVALID_TIME;}
virtual ~GwBoatItemNameProvider() {}
};
#define GWBOATDATA(type,name,time,fmt) \
#define GWBOATDATAT(type,name,toType,fmt) \
static constexpr const char* _##name=#name; \
GwBoatItem<type> *name=new GwBoatItem<type>(#name,GwBoatItemBase::fmt,time,&values) ;
#define GWSPECBOATDATA(clazz,name,time,fmt) \
clazz *name=new clazz(#name,GwBoatItemBase::fmt,time,&values) ;
GwBoatItem<type> *name=new GwBoatItem<type>(#name,GwBoatItemBase::fmt,toType,&values) ;
#define GWBOATDATA(type,name,fmt) GWBOATDATAT(type,name,GwBoatItemBase::TOType::def,fmt)
#define GWSPECBOATDATA(clazz,name,toType,fmt) \
clazz *name=new clazz(#name,GwBoatItemBase::fmt,toType,&values) ;
class GwBoatData{
private:
GwLog *logger;
GwBoatItemBase::GwBoatItemMap values;
GwLog *logger=nullptr;
GwConfigHandler *config=nullptr;
GwBoatItemBase::GwBoatItemMap values{this};
public:
GWBOATDATA(double,COG,4000,formatCourse)
GWBOATDATA(double,TWD,4000,formatCourse)
GWBOATDATA(double,SOG,4000,formatKnots)
GWBOATDATA(double,STW,4000,formatKnots)
GWBOATDATA(double,TWS,4000,formatKnots)
GWBOATDATA(double,AWS,4000,formatKnots)
GWBOATDATA(double,MaxTws,0,formatKnots)
GWBOATDATA(double,MaxAws,0,formatKnots)
GWBOATDATA(double,AWA,4000,formatWind)
GWBOATDATA(double,HDG,4000,formatCourse) //true heading
GWBOATDATA(double,MHDG,4000,formatCourse) //magnetic heading
GWBOATDATA(double,ROT,4000,formatRot)
GWBOATDATA(double,VAR,4000,formatCourse) //Variation
GWBOATDATA(double,DEV,4000,formatCourse) //Deviation
GWBOATDATA(double,HDOP,4000,formatDop)
GWBOATDATA(double,PDOP,4000,formatDop)
GWBOATDATA(double,VDOP,4000,formatDop)
GWBOATDATA(double,RPOS,4000,formatWind) //RudderPosition
GWBOATDATA(double,PRPOS,4000,formatWind) //second rudder pos
GWBOATDATA(double,LAT,4000,formatLatitude)
GWBOATDATA(double,LON,4000,formatLongitude)
GWBOATDATA(double,ALT,4000,formatFixed0) //altitude
GWBOATDATA(double,DBS,4000,formatDepth) //waterDepth (below surface)
GWBOATDATA(double,DBT,4000,formatDepth) //DepthTransducer
GWBOATDATA(double,GPST,4000,formatTime) //GpsTime
GWBOATDATA(double,WTemp,4000,kelvinToC)
GWBOATDATA(double,XTE,4000,formatXte)
GWBOATDATA(double,DTW,4000,mtr2nm) //distance wp
GWBOATDATA(double,BTW,4000,formatCourse) //bearing wp
GWBOATDATA(double,WPLat,4000,formatLatitude)
GWBOATDATA(double,WPLon,4000,formatLongitude)
GWBOATDATA(uint32_t,Log,16000,mtr2nm)
GWBOATDATA(uint32_t,TripLog,16000,mtr2nm)
GWBOATDATA(uint32_t,GPSD,4000,formatDate) //Date
GWBOATDATA(int16_t,TZ,8000,formatFixed0)
GWSPECBOATDATA(GwBoatDataSatList,SatInfo,GwSatInfoList::lifeTime,formatFixed0);
GWBOATDATA(double,COG,formatCourse) // course over ground
GWBOATDATA(double,SOG,formatKnots) // speed over ground
GWBOATDATA(double,HDT,formatCourse) // true heading
GWBOATDATA(double,HDM,formatCourse) // magnetic heading
GWBOATDATA(double,STW,formatKnots) // water speed
GWBOATDATA(double,VAR,formatWind) // variation
GWBOATDATA(double,DEV,formatWind) // deviation
GWBOATDATA(double,AWA,formatWind) // apparent wind ANGLE
GWBOATDATA(double,AWS,formatKnots) // apparent wind speed
GWBOATDATAT(double,MaxAws,GwBoatItemBase::TOType::keep,formatKnots)
GWBOATDATA(double,TWD,formatCourse) // true wind DIRECTION
GWBOATDATA(double,TWA,formatWind) // true wind ANGLE
GWBOATDATA(double,TWS,formatKnots) // true wind speed
GWBOATDATAT(double,MaxTws,GwBoatItemBase::TOType::keep,formatKnots)
GWBOATDATA(double,ROT,formatRot) // rate of turn
GWBOATDATA(double,RPOS,formatWind) // rudder position
GWBOATDATA(double,PRPOS,formatWind) // secondary rudder position
GWBOATDATA(double,LAT,formatLatitude)
GWBOATDATA(double,LON,formatLongitude)
GWBOATDATA(double,ALT,formatFixed0) //altitude
GWBOATDATA(double,HDOP,formatDop)
GWBOATDATA(double,PDOP,formatDop)
GWBOATDATA(double,VDOP,formatDop)
GWBOATDATA(double,DBS,formatDepth) //waterDepth (below surface)
GWBOATDATA(double,DBT,formatDepth) //DepthTransducer
GWBOATDATA(double,GPST,formatTime) // GPS time (seconds of day)
GWBOATDATA(uint32_t,GPSD,formatDate) // GPS date (days since 1979-01-01)
GWBOATDATAT(int16_t,TZ,GwBoatItemBase::TOType::lng,formatFixed0)
GWBOATDATA(double,WTemp,kelvinToC)
GWBOATDATAT(uint32_t,Log,GwBoatItemBase::TOType::lng,mtr2nm)
GWBOATDATAT(uint32_t,TripLog,GwBoatItemBase::TOType::lng,mtr2nm)
GWBOATDATA(double,DTW,mtr2nm) // distance to waypoint
GWBOATDATA(double,BTW,formatCourse) // bearing to waypoint
GWBOATDATA(double,XTE,formatXte) // cross track error
GWBOATDATA(double,WPLat,formatLatitude) // waypoint latitude
GWBOATDATA(double,WPLon,formatLongitude) // waypoint longitude
GWSPECBOATDATA(GwBoatDataSatList,SatInfo,GwSatInfoList::toType,formatFixed0);
public:
GwBoatData(GwLog *logger);
GwBoatData(GwLog *logger, GwConfigHandler *cfg);
~GwBoatData();
void begin();
template<class T> GwBoatItem<T> *getOrCreate(T initial,GwBoatItemNameProvider *provider);
template<class T> bool update(T value,int source,GwBoatItemNameProvider *provider);
template<class T> T getDataWithDefault(T defaultv, GwBoatItemNameProvider *provider);
void setInvalidTime(GwBoatItemBase *item);
bool isValid(String name);
double getDoubleValue(String name,double defaultv);
GwBoatItemBase *getBase(String name);

View File

@@ -58,8 +58,6 @@ GwChannel::GwChannel(GwLog *logger,
this->name=name;
this->sourceId=sourceId;
this->maxSourceId=maxSourceId;
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;
@@ -100,6 +98,12 @@ void GwChannel::begin(
actisenseReader->SetReadStream(channelStream);
}
}
if (nmeaIn || readActisense){
this->countIn=new GwCounter<String>(String("count")+name+String("in"));
}
if (nmeaOut || seaSmartOut || writeActisense){
this->countOut=new GwCounter<String>(String("count")+name+String("out"));
}
}
void GwChannel::setImpl(GwChannelInterface *impl){
this->impl=impl;
@@ -135,10 +139,10 @@ void GwChannel::updateCounter(const char *msg, bool out)
}
if (key[0] == 0) return;
if (out){
countOut->add(key);
if (countOut) countOut->add(key);
}
else{
countIn->add(key);
if (countIn) countIn->add(key);
}
}
@@ -209,7 +213,7 @@ void GwChannel::parseActisense(N2kHandler handler){
tN2kMsg N2kMsg;
while (actisenseReader->GetMessageFromStream(N2kMsg)) {
countIn->add(String(N2kMsg.PGN));
if(countIn) countIn->add(String(N2kMsg.PGN));
handler(N2kMsg,sourceId);
}
}
@@ -218,14 +222,23 @@ 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));
if (maxSourceId < 0 && this->sourceId == sourceId) return;
if (sourceId >= this->sourceId && sourceId <= maxSourceId) return;
if(countOut) 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);
bool GwChannel::overlaps(const GwChannel *other) const{
if (maxSourceId < 0){
if (other->maxSourceId < 0) return sourceId == other->sourceId;
return (other->sourceId <= sourceId && other->maxSourceId >= sourceId);
}
if (other->maxSourceId < 0){
return other->sourceId >= sourceId && other->sourceId <= maxSourceId;
}
if (other->maxSourceId < sourceId) return false;
if (other->sourceId > maxSourceId) return false;
return true;
}
unsigned long GwChannel::countRx(){

View File

@@ -50,7 +50,7 @@ class GwChannel{
);
void setImpl(GwChannelInterface *impl);
bool isOwnSource(int id);
bool overlaps(const GwChannel *) const;
void enable(bool enabled){
this->enabled=enabled;
}
@@ -73,5 +73,11 @@ class GwChannel{
void sendActisense(const tN2kMsg &msg, int sourceId);
unsigned long countRx();
unsigned long countTx();
bool isOwnSource(int source){
if (maxSourceId < 0) return source == sourceId;
return (source >= sourceId && source <= maxSourceId);
}
String getMode(){return impl->getMode();}
int getMinId(){return sourceId;};
};

View File

@@ -6,4 +6,5 @@ class GwChannelInterface{
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;}
virtual String getMode(){return "UNKNOWN";}
};

View File

@@ -6,6 +6,8 @@
#include "GwSocketServer.h"
#include "GwSerial.h"
#include "GwTcpClient.h"
#include "GwUdpWriter.h"
#include "GwUdpReader.h"
class SerInit{
public:
int serial=-1;
@@ -18,108 +20,110 @@ class SerInit{
};
std::vector<SerInit> serialInits;
static int typeFromMode(const char *mode){
if (strcmp(mode,"UNI") == 0) return GWSERIAL_TYPE_UNI;
if (strcmp(mode,"BI") == 0) return GWSERIAL_TYPE_BI;
if (strcmp(mode,"RX") == 0) return GWSERIAL_TYPE_RX;
if (strcmp(mode,"TX") == 0) return GWSERIAL_TYPE_TX;
return GWSERIAL_TYPE_UNK;
}
#define CFG_SERIAL(ser,...) \
__MSG("serial config " #ser); \
static GwInitializer<SerInit> __serial ## ser ## _init \
(serialInits,SerInit(ser,__VA_ARGS__));
#ifdef _GWI_SERIAL1
CFG_SERIAL(1,_GWI_SERIAL1)
CFG_SERIAL(SERIAL1_CHANNEL_ID,_GWI_SERIAL1)
#endif
#ifdef _GWI_SERIAL2
CFG_SERIAL(2,_GWI_SERIAL2)
CFG_SERIAL(SERIAL2_CHANNEL_ID,_GWI_SERIAL2)
#endif
class GwSerialLog : public GwLogWriter
{
static const size_t bufferSize = 4096;
char *logBuffer = NULL;
int wp = 0;
GwSerial *writer;
bool disabled = false;
long flushTimeout=200;
public:
GwSerialLog(GwSerial *writer, bool disabled,long flushTimeout=200)
// handle separate defines
// serial 1
#ifndef GWSERIAL_TX
#define GWSERIAL_TX -1
#endif
#ifndef GWSERIAL_RX
#define GWSERIAL_RX -1
#endif
#ifdef GWSERIAL_TYPE
CFG_SERIAL(SERIAL1_CHANNEL_ID, GWSERIAL_RX, GWSERIAL_TX, GWSERIAL_TYPE)
#else
#ifdef GWSERIAL_MODE
CFG_SERIAL(SERIAL1_CHANNEL_ID, GWSERIAL_RX, GWSERIAL_TX, typeFromMode(GWSERIAL_MODE))
#endif
#endif
// serial 2
#ifndef GWSERIAL2_TX
#define GWSERIAL2_TX -1
#endif
#ifndef GWSERIAL2_RX
#define GWSERIAL2_RX -1
#endif
#ifdef GWSERIAL2_TYPE
CFG_SERIAL(SERIAL2_CHANNEL_ID, GWSERIAL2_RX, GWSERIAL2_TX, GWSERIAL2_TYPE)
#else
#ifdef GWSERIAL2_MODE
CFG_SERIAL(SERIAL2_CHANNEL_ID, GWSERIAL2_RX, GWSERIAL2_TX, typeFromMode(GWSERIAL2_MODE))
#endif
#endif
class GwSerialLog : public GwLogWriter
{
this->writer = writer;
this->disabled = disabled;
this->flushTimeout=flushTimeout;
logBuffer = new char[bufferSize];
wp = 0;
}
virtual ~GwSerialLog() {}
virtual void write(const char *data)
{
if (disabled)
return;
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;
if (!disabled)
static const size_t bufferSize = 4096;
char *logBuffer = NULL;
int wp = 0;
GwSerial *writer;
bool disabled = false;
public:
GwSerialLog(GwSerial *writer, bool disabled)
{
while (handled < wp)
{
if ( !writer->flush(flushTimeout)) break;
size_t rt = writer->sendToClients(logBuffer + handled, -1, true);
handled += rt;
}
if (handled < wp){
if (handled > 0){
memmove(logBuffer,logBuffer+handled,wp-handled);
wp-=handled;
logBuffer[handled]=0;
}
this->writer = writer;
this->disabled = disabled;
logBuffer = new char[bufferSize];
wp = 0;
}
virtual ~GwSerialLog() {}
virtual void write(const char *data)
{
if (disabled)
return;
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;
if (!disabled)
{
while (handled < wp)
{
if (!writer->flush())
break;
size_t rt = writer->sendToClients(logBuffer + handled, -1, true);
handled += rt;
}
if (handled < wp)
{
if (handled > 0)
{
memmove(logBuffer, logBuffer + handled, wp - handled);
wp -= handled;
logBuffer[wp] = 0;
}
return;
}
}
wp = 0;
logBuffer[0] = 0;
}
wp = 0;
logBuffer[0] = 0;
}
};
template<typename T>
class SerialWrapper : public GwChannelList::SerialWrapperBase{
private:
template<class C>
void beginImpl(C *s,unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1){}
void beginImpl(HardwareSerial *s,unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1){
s->begin(baud,config,rxPin,txPin);
}
template<class C>
void setError(C* s, GwLog *logger){}
void setError(HardwareSerial *s,GwLog *logger){
LOG_DEBUG(GwLog::LOG,"enable serial errors for channel %d",id);
s->onReceiveError([logger,this](hardwareSerial_error_t err){
LOG_DEBUG(GwLog::ERROR,"serial error on id %d: %d",this->id,(int)err);
});
}
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
void beginImpl(HWCDC *s,unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1){
s->begin(baud);
}
#endif
T *serial;
int id;
public:
SerialWrapper(T* s,int i):serial(s),id(i){}
virtual void begin(GwLog* logger,unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1) override{
beginImpl(serial,baud,config,rxPin,txPin);
setError(serial,logger);
};
virtual Stream *getStream() override{
return serial;
}
virtual int getId() override{
return id;
}
};
GwChannelList::GwChannelList(GwLog *logger, GwConfigHandler *config){
this->logger=logger;
@@ -139,10 +143,35 @@ typedef struct {
const char *toN2K;
const char *readF;
const char *writeF;
const char *preventLog;
const char *readAct;
const char *writeAct;
const char *sendSeasmart;
const char *name;
} SerialParam;
int maxId;
size_t rxstatus;
size_t txstatus;
} ChannelParam;
static SerialParam serialParameters[]={
static ChannelParam channelParameters[]={
{
.id=USB_CHANNEL_ID,
.baud=GwConfigDefinitions::usbBaud,
.receive=GwConfigDefinitions::receiveUsb,
.send=GwConfigDefinitions::sendUsb,
.direction="",
.toN2K=GwConfigDefinitions::usbToN2k,
.readF=GwConfigDefinitions::usbReadFilter,
.writeF=GwConfigDefinitions::usbWriteFilter,
.preventLog=GwConfigDefinitions::usbActisense,
.readAct=GwConfigDefinitions::usbActisense,
.writeAct=GwConfigDefinitions::usbActSend,
.sendSeasmart="",
.name="USB",
.maxId=-1,
.rxstatus=offsetof(GwApi::Status,GwApi::Status::usbRx),
.txstatus=offsetof(GwApi::Status,GwApi::Status::usbTx)
},
{
.id=SERIAL1_CHANNEL_ID,
.baud=GwConfigDefinitions::serialBaud,
@@ -152,7 +181,14 @@ static SerialParam serialParameters[]={
.toN2K=GwConfigDefinitions::serialToN2k,
.readF=GwConfigDefinitions::serialReadF,
.writeF=GwConfigDefinitions::serialWriteF,
.name="Serial"
.preventLog="",
.readAct="",
.writeAct="",
.sendSeasmart="",
.name="Serial",
.maxId=-1,
.rxstatus=offsetof(GwApi::Status,GwApi::Status::serRx),
.txstatus=offsetof(GwApi::Status,GwApi::Status::serTx)
},
{
.id=SERIAL2_CHANNEL_ID,
@@ -163,81 +199,162 @@ static SerialParam serialParameters[]={
.toN2K=GwConfigDefinitions::serial2ToN2k,
.readF=GwConfigDefinitions::serial2ReadF,
.writeF=GwConfigDefinitions::serial2WriteF,
.name="Serial2"
.preventLog="",
.readAct="",
.writeAct="",
.sendSeasmart="",
.name="Serial2",
.maxId=-1,
.rxstatus=offsetof(GwApi::Status,GwApi::Status::ser2Rx),
.txstatus=offsetof(GwApi::Status,GwApi::Status::ser2Tx)
},
{
.id=MIN_TCP_CHANNEL_ID,
.baud="",
.receive=GwConfigDefinitions::readTCP,
.send=GwConfigDefinitions::sendTCP,
.direction="",
.toN2K=GwConfigDefinitions::tcpToN2k,
.readF=GwConfigDefinitions::tcpReadFilter,
.writeF=GwConfigDefinitions::tcpWriteFilter,
.preventLog="",
.readAct="",
.writeAct="",
.sendSeasmart=GwConfigDefinitions::sendSeasmart,
.name="TCPServer",
.maxId=MIN_TCP_CHANNEL_ID+10,
.rxstatus=offsetof(GwApi::Status,GwApi::Status::tcpSerRx),
.txstatus=offsetof(GwApi::Status,GwApi::Status::tcpSerTx)
},
{
.id=TCP_CLIENT_CHANNEL_ID,
.baud="",
.receive=GwConfigDefinitions::readTCL,
.send=GwConfigDefinitions::sendTCL,
.direction="",
.toN2K=GwConfigDefinitions::tclToN2k,
.readF=GwConfigDefinitions::tclReadFilter,
.writeF=GwConfigDefinitions::tclWriteFilter,
.preventLog="",
.readAct="",
.writeAct="",
.sendSeasmart=GwConfigDefinitions::tclSeasmart,
.name="TCPClient",
.maxId=-1,
.rxstatus=offsetof(GwApi::Status,GwApi::Status::tcpClRx),
.txstatus=offsetof(GwApi::Status,GwApi::Status::tcpClTx)
},
{
.id=UDPW_CHANNEL_ID,
.baud="",
.receive="",
.send=GwConfigDefinitions::udpwEnabled,
.direction="",
.toN2K="",
.readF="",
.writeF=GwConfigDefinitions::udpwWriteFilter,
.preventLog="",
.readAct="",
.writeAct="",
.sendSeasmart=GwConfigDefinitions::udpwSeasmart,
.name="UDPWriter",
.maxId=-1,
.rxstatus=0,
.txstatus=offsetof(GwApi::Status,GwApi::Status::udpwTx)
},
{
.id=UDPR_CHANNEL_ID,
.baud="",
.receive=GwConfigDefinitions::udprEnabled,
.send="",
.direction="",
.toN2K=GwConfigDefinitions::udprToN2k,
.readF=GwConfigDefinitions::udprReadFilter,
.writeF="",
.preventLog="",
.readAct="",
.writeAct="",
.sendSeasmart="",
.name="UDPReader",
.maxId=-1,
.rxstatus=offsetof(GwApi::Status,GwApi::Status::udprRx),
.txstatus=0
}
};
static SerialParam *getSerialParam(int id){
for (size_t idx=0;idx<sizeof(serialParameters)/sizeof(SerialParam*);idx++){
if (serialParameters[idx].id == id) return &serialParameters[idx];
}
return nullptr;
}
void GwChannelList::addSerial(int id, int rx, int tx, int type){
if (id == 1){
addSerial(new SerialWrapper<decltype(Serial1)>(&Serial1,SERIAL1_CHANNEL_ID),type,rx,tx);
return;
}
if (id == 2){
addSerial(new SerialWrapper<decltype(Serial2)>(&Serial2,SERIAL2_CHANNEL_ID),type,rx,tx);
return;
}
LOG_DEBUG(GwLog::ERROR,"invalid serial config with id %d",id);
}
void GwChannelList::addSerial(GwChannelList::SerialWrapperBase *stream,int type,int rx,int tx){
const char *mode=nullptr;
switch (type)
{
case GWSERIAL_TYPE_UNI:
mode="UNI";
break;
case GWSERIAL_TYPE_BI:
mode="BI";
break;
case GWSERIAL_TYPE_RX:
mode="RX";
break;
case GWSERIAL_TYPE_TX:
mode="TX";
break;
}
if (mode == nullptr) {
LOG_DEBUG(GwLog::ERROR,"unknown serial type %d",type);
return;
}
addSerial(stream,mode,rx,tx);
}
void GwChannelList::addSerial(GwChannelList::SerialWrapperBase *serialStream,const String &mode,int rx,int tx){
int id=serialStream->getId();
for (auto &&it:theChannels){
if (it->isOwnSource(id)){
LOG_DEBUG(GwLog::ERROR,"trying to re-add serial id=%d, ignoring",id);
return;
template<typename T>
GwSerial* createSerial(GwLog *logger, T* s,int id, bool canRead=true){
return new GwSerialImpl<T>(logger,s,id,canRead);
}
static ChannelParam * findChannelParam(int id){
ChannelParam *param=nullptr;
for (auto && p: channelParameters){
if (id == p.id){
param=&p;
break;
}
}
SerialParam *param=getSerialParam(id);
return param;
}
static GwSerial * createSerialImpl(GwConfigHandler *config,GwLog *logger, int idx,int rx,int tx, bool setLog=false){
LOG_DEBUG(GwLog::DEBUG,"create serial: channel=%d, rx=%d,tx=%d",
idx,rx,tx);
ChannelParam *param=findChannelParam(idx);
if (param == nullptr){
logger->logDebug(GwLog::ERROR,"trying to set up an unknown serial channel: %d",id);
return;
LOG_DEBUG(GwLog::ERROR,"invalid serial channel id %d",idx);
return nullptr;
}
if (rx < 0 && tx < 0){
logger->logDebug(GwLog::ERROR,"useless config for serial %d: both rx/tx undefined");
return;
GwSerial *serialStream=nullptr;
GwLog *streamLog=setLog?nullptr:logger;
switch(param->id){
case USB_CHANNEL_ID:
serialStream=createSerial(streamLog,&USBSerial,param->id);
break;
case SERIAL1_CHANNEL_ID:
serialStream=createSerial(streamLog,&Serial1,param->id);
break;
case SERIAL2_CHANNEL_ID:
serialStream=createSerial(streamLog,&Serial2,param->id);
break;
}
if (serialStream == nullptr){
LOG_DEBUG(GwLog::ERROR,"invalid serial config with id %d",param->id);
return nullptr;
}
serialStream->begin(config->getInt(param->baud,115200),SERIAL_8N1,rx,tx);
if (setLog){
logger->setWriter(new GwSerialLog(serialStream,config->getBool(param->preventLog,false)));
logger->prefix="GWSERIAL:";
}
return serialStream;
}
static GwChannel * createChannel(GwLog *logger, GwConfigHandler *config, int id,GwChannelInterface *impl, int type=GWSERIAL_TYPE_BI){
ChannelParam *param=findChannelParam(id);
if (param == nullptr){
LOG_DEBUG(GwLog::ERROR,"invalid channel id %d",id);
return nullptr;
}
modes[id]=String(mode);
bool canRead=false;
bool canWrite=false;
if (mode == "BI"){
bool validType=false;
if (type == GWSERIAL_TYPE_BI){
canRead=config->getBool(param->receive);
canWrite=config->getBool(param->send);
validType=true;
}
if (mode == "TX"){
if (type == GWSERIAL_TYPE_TX){
canWrite=true;
validType=true;
}
if (mode == "RX"){
if (type == GWSERIAL_TYPE_RX){
canRead=true;
validType=true;
}
if (mode == "UNI"){
if (type == GWSERIAL_TYPE_UNI ){
String cfgMode=config->getString(param->direction);
if (cfgMode == "receive"){
canRead=true;
@@ -245,138 +362,102 @@ void GwChannelList::addSerial(GwChannelList::SerialWrapperBase *serialStream,con
if (cfgMode == "send"){
canWrite=true;
}
validType=true;
}
if (rx < 0) canRead=false;
if (tx < 0) canWrite=false;
LOG_DEBUG(GwLog::DEBUG,"serial set up: mode=%s,rx=%d,canRead=%d,tx=%d,canWrite=%d",
mode.c_str(),rx,(int)canRead,tx,(int)canWrite);
serialStream->begin(logger,config->getInt(param->baud,115200),SERIAL_8N1,rx,tx);
GwSerial *serial = new GwSerial(logger, serialStream->getStream(), id, canRead);
LOG_DEBUG(GwLog::LOG, "starting serial %d ", id);
GwChannel *channel = new GwChannel(logger, param->name, id);
channel->setImpl(serial);
if (! validType){
LOG_DEBUG(GwLog::ERROR,"invalid type for channel %d: %d",param->id,type);
return nullptr;
}
GwChannel *channel = new GwChannel(logger, param->name,param->id,param->maxId);
bool sendSeaSmart=config->getBool(param->sendSeasmart);
bool readAct=config->getBool(param->readAct);
bool writeAct=config->getBool(param->writeAct);
channel->setImpl(impl);
channel->begin(
canRead || canWrite,
canRead || canWrite || readAct || writeAct|| sendSeaSmart,
canWrite,
canRead,
config->getString(param->readF),
config->getString(param->writeF),
false,
sendSeaSmart,
config->getBool(param->toN2K),
false,
false);
LOG_DEBUG(GwLog::LOG, "%s", channel->toString().c_str());
readAct,
writeAct);
LOG_INFO("created channel %s",channel->toString().c_str());
return channel;
}
void GwChannelList::addChannel(GwChannel * channel){
if (channel == nullptr) return;
for (auto &&it:theChannels){
if (it->overlaps(channel)){
LOG_DEBUG(GwLog::ERROR,"trying to add channel with overlapping ids %s (%s), ignoring",
channel->toString().c_str(),
it->toString().c_str());
return;
}
}
LOG_INFO("adding channel %s", channel->toString().c_str());
theChannels.push_back(channel);
}
void GwChannelList::preinit(){
for (auto &&init:serialInits){
LOG_INFO("serial config found for %d",init.serial);
if (init.fixedBaud >= 0){
switch(init.serial){
case 1:
{
LOG_DEBUG(GwLog::DEBUG,"setting fixed baud %d for serial",init.fixedBaud);
config->setValue(GwConfigDefinitions::serialBaud,String(init.fixedBaud),GwConfigInterface::READONLY);
}
break;
case 2:
{
LOG_DEBUG(GwLog::DEBUG,"setting fixed baud %d for serial2",init.fixedBaud);
config->setValue(GwConfigDefinitions::serial2Baud,String(init.fixedBaud),GwConfigInterface::READONLY);
}
break;
default:
LOG_DEBUG(GwLog::ERROR,"invalid serial definition %d found",init.serial)
ChannelParam *param=findChannelParam(init.serial);
if (! param){
LOG_ERROR("invalid serial definition %d found",init.serial)
return;
}
LOG_DEBUG(GwLog::DEBUG,"setting fixed baud %d for serial %d",init.fixedBaud,init.serial);
config->setValue(param->baud,String(init.fixedBaud),GwConfigInterface::READONLY);
}
}
}
template<typename S>
long getFlushTimeout(S &s){
return 200;
}
template<>
long getFlushTimeout(HardwareSerial &s){
return 2000;
}
#ifndef GWUSB_TX
#define GWUSB_TX -1
#endif
#ifndef GWUSB_RX
#define GWUSB_RX -1
#endif
void GwChannelList::begin(bool fallbackSerial){
LOG_DEBUG(GwLog::DEBUG,"GwChannelList::begin");
GwChannel *channel=NULL;
//usb
if (! fallbackSerial){
GwSerial *usb=new GwSerial(NULL,&USBSerial,USB_CHANNEL_ID);
USBSerial.begin(config->getInt(config->usbBaud));
logger->setWriter(new GwSerialLog(usb,config->getBool(config->usbActisense),getFlushTimeout(USBSerial)));
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());
GwSerial *usbSerial=createSerialImpl(config, logger,USB_CHANNEL_ID,GWUSB_RX,GWUSB_TX,true);
if (usbSerial != nullptr){
GwChannel *usbChannel=createChannel(logger,config,USB_CHANNEL_ID,usbSerial,GWSERIAL_TYPE_BI);
if (usbChannel != nullptr){
addChannel(usbChannel);
}
else{
delete usbSerial;
}
}
}
//TCP server
sockets=new GwSocketServer(config,logger,MIN_TCP_CHANNEL_ID);
sockets->begin();
channel=new GwChannel(logger,"TCPserver",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);
addChannel(createChannel(logger,config,MIN_TCP_CHANNEL_ID,sockets));
//new serial config handling
for (auto &&init:serialInits){
addSerial(init.serial,init.rx,init.tx,init.mode);
LOG_INFO("creating serial channel %d, rx=%d,tx=%d,type=%d",init.serial,init.rx,init.tx,init.mode);
GwSerial *ser=createSerialImpl(config,logger,init.serial,init.rx,init.tx);
if (ser != nullptr){
channel=createChannel(logger,config,init.serial,ser,init.mode);
if (channel != nullptr){
addChannel(channel);
}
else{
delete ser;
}
}
}
//handle separate defines
//serial 1
#ifndef GWSERIAL_TX
#define GWSERIAL_TX -1
#endif
#ifndef GWSERIAL_RX
#define GWSERIAL_RX -1
#endif
#ifdef GWSERIAL_TYPE
addSerial(new SerialWrapper<decltype(Serial1)>(&Serial1,SERIAL1_CHANNEL_ID),GWSERIAL_TYPE,GWSERIAL_RX,GWSERIAL_TX);
#else
#ifdef GWSERIAL_MODE
addSerial(new SerialWrapper<decltype(Serial1)>(&Serial1,SERIAL1_CHANNEL_ID),GWSERIAL_MODE,GWSERIAL_RX,GWSERIAL_TX);
#endif
#endif
//serial 2
#ifndef GWSERIAL2_TX
#define GWSERIAL2_TX -1
#endif
#ifndef GWSERIAL2_RX
#define GWSERIAL2_RX -1
#endif
#ifdef GWSERIAL2_TYPE
addSerial(new SerialWrapper<decltype(Serial2)>(&Serial2,SERIAL2_CHANNEL_ID),GWSERIAL2_TYPE,GWSERIAL2_RX,GWSERIAL2_TX);
#else
#ifdef GWSERIAL2_MODE
addSerial(new SerialWrapper<decltype(Serial2)>(&Serial2,SERIAL2_CHANNEL_ID),GWSERIAL2_MODE,GWSERIAL2_RX,GWSERIAL2_TX);
#endif
#endif
//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,
@@ -384,26 +465,27 @@ void GwChannelList::begin(bool fallbackSerial){
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());
addChannel(createChannel(logger,config,TCP_CLIENT_CHANNEL_ID,client));
//udp writer
if (config->getBool(GwConfigDefinitions::udpwEnabled)){
GwUdpWriter *writer=new GwUdpWriter(config,logger,UDPW_CHANNEL_ID);
writer->begin();
addChannel(createChannel(logger,config,UDPW_CHANNEL_ID,writer));
}
//udp reader
if (config->getBool(GwConfigDefinitions::udprEnabled)){
GwUdpReader *reader=new GwUdpReader(config,logger,UDPR_CHANNEL_ID);
reader->begin();
addChannel(createChannel(logger,config,UDPR_CHANNEL_ID,reader));
}
logger->flush();
}
String GwChannelList::getMode(int id){
auto it=modes.find(id);
if (it != modes.end()) return it->second;
for (auto && c: theChannels){
if (c->isOwnSource(id)) return c->getMode();
}
return "UNKNOWN";
}
int GwChannelList::getJsonSize(){
@@ -428,36 +510,28 @@ void GwChannelList::toJson(GwJsonDocument &doc){
});
}
GwChannel *GwChannelList::getChannelById(int sourceId){
for (auto it=theChannels.begin();it != theChannels.end();it++){
if ((*it)->isOwnSource(sourceId)) return *it;
for (auto && it: theChannels){
if (it->isOwnSource(sourceId)) return it;
}
return NULL;
}
/**
* slightly tricky generic setter for the API status
* we expect all values to be unsigned long
* the offsets are always offsetof(GwApi::Status,GwApi::Status::xxx)
*/
static void setStatus(GwApi::Status *status,size_t offset,unsigned long v){
if (offset == 0) return;
*((unsigned long *)(((unsigned char *)status)+offset))=v;
}
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(SERIAL2_CHANNEL_ID);
if (channel){
status.ser2Rx=channel->countRx();
status.ser2Tx=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();
for (auto && channel: theChannels){
ChannelParam *param=findChannelParam(channel->getMinId());
if (param != nullptr){
setStatus(&status,param->rxstatus,channel->countRx());
setStatus(&status,param->txstatus,channel->countTx());
}
}
}

View File

@@ -8,6 +8,7 @@
#include "GWConfig.h"
#include "GwJsonDocument.h"
#include "GwApi.h"
#include "GwSerial.h"
#include <HardwareSerial.h>
//NMEA message channels
@@ -17,29 +18,22 @@
#define SERIAL2_CHANNEL_ID 3
#define TCP_CLIENT_CHANNEL_ID 4
#define MIN_TCP_CHANNEL_ID 5
#define UDPW_CHANNEL_ID 20
#define UDPR_CHANNEL_ID 21
#define MIN_USER_TASK 200
class GwSocketServer;
class GwTcpClient;
class GwChannelList{
private:
class SerialWrapperBase{
public:
virtual void begin(GwLog* logger,unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1)=0;
virtual Stream *getStream()=0;
virtual int getId()=0;
};
GwLog *logger;
GwConfigHandler *config;
typedef std::vector<GwChannel *> ChannelList;
ChannelList theChannels;
std::map<int,String> modes;
GwSocketServer *sockets;
GwTcpClient *client;
void addSerial(SerialWrapperBase *stream,const String &mode,int rx,int tx);
void addSerial(SerialWrapperBase *stream,int type,int rx,int tx);
public:
void addSerial(int id, int rx, int tx, int type);
void addChannel(GwChannel *);
GwChannelList(GwLog *logger, GwConfigHandler *config);
typedef std::function<void(GwChannel *)> ChannelAction;
void allChannels(ChannelAction action);

View File

@@ -4,6 +4,7 @@
#include <ArduinoJson.h>
#include <string.h>
#include <MD5Builder.h>
#include <esp_partition.h>
using CfgInit=std::function<void(GwConfigHandler *)>;
static std::vector<CfgInit> cfgInits;
#define CFG_INIT(name,value,mode) \
@@ -96,15 +97,36 @@ bool GwConfigHandler::updateValue(String name, String value){
if (i->asString() == value){
return false;
}
LOG_DEBUG(GwLog::LOG,"update config %s=>%s",name.c_str(),i->isSecret()?"***":value.c_str());
prefs->begin(PREF_NAME,false);
prefs->putString(i->getName().c_str(),value);
LOG_DEBUG(GwLog::LOG,"update config %s=>%s, freeEntries=%d",name.c_str(),i->isSecret()?"***":value.c_str(),(int)(prefs->freeEntries()));
prefs->end();
}
return true;
}
bool GwConfigHandler::reset(){
LOG_DEBUG(GwLog::LOG,"reset config");
LOG_DEBUG(GwLog::ERROR,"reset config");
//try to find the nvs partition
//currently we only support the default
bool wiped=false;
const esp_partition_t *nvspart=esp_partition_find_first(ESP_PARTITION_TYPE_DATA,ESP_PARTITION_SUBTYPE_DATA_NVS,"nvs");
if (nvspart != NULL){
LOG_DEBUG(GwLog::ERROR,"wiping nvs partition");
esp_err_t err=esp_partition_erase_range(nvspart,0,nvspart->size);
if (err != ESP_OK){
LOG_DEBUG(GwLog::ERROR,"wiping nvs partition failed: %d",(int)err);
}
else{
wiped=true;
}
}
else{
LOG_DEBUG(GwLog::ERROR,"nvs partition not found");
}
if (wiped){
return true;
}
LOG_DEBUG(GwLog::ERROR,"unable to wipe nvs partition, trying to reset values");
prefs->begin(PREF_NAME,false);
for (int i=0;i<getNumConfig();i++){
prefs->putString(configs[i]->getName().c_str(),configs[i]->getDefault());

View File

@@ -16,16 +16,63 @@
#define _GWCONVERTERCONFIG_H
#include "GWConfig.h"
#include "N2kTypes.h"
#include <map>
//list of configs for the PGN 130306 wind references
static std::map<tN2kWindReference,String> windConfigs={
{N2kWind_True_water,GwConfigDefinitions::windmtra},
{N2kWind_Apparent,GwConfigDefinitions::windmawa},
{N2kWind_True_boat,GwConfigDefinitions::windmgna},
{N2kWind_Magnetic,GwConfigDefinitions::windmmgd},
{N2kWind_True_North,GwConfigDefinitions::windmtng},
};
class GwConverterConfig{
public:
class WindMapping{
public:
using Wind0183Type=enum{
AWA_AWS,
TWA_TWS,
TWD_TWS,
GWA_GWS,
GWD_GWS
};
tN2kWindReference n2kType;
Wind0183Type nmea0183Type;
bool valid=false;
WindMapping(){}
WindMapping(const tN2kWindReference &n2k,const Wind0183Type &n183):
n2kType(n2k),nmea0183Type(n183),valid(true){}
WindMapping(const tN2kWindReference &n2k,const String &n183):
n2kType(n2k){
if (n183 == "twa_tws"){
nmea0183Type=TWA_TWS;
valid=true;
return;
}
if (n183 == "awa_aws"){
nmea0183Type=AWA_AWS;
valid=true;
return;
}
if (n183 == "twd_tws"){
nmea0183Type=TWD_TWS;
valid=true;
return;
}
}
};
int minXdrInterval=100;
int starboardRudderInstance=0;
int portRudderInstance=-1; //ignore
int min2KInterval=50;
int rmcInterval=1000;
int rmcCheckTime=4000;
void init(GwConfigHandler *config){
int winst312=256;
std::vector<WindMapping> windMappings;
void init(GwConfigHandler *config, GwLog*logger){
minXdrInterval=config->getInt(GwConfigDefinitions::minXdrInterval,100);
starboardRudderInstance=config->getInt(GwConfigDefinitions::stbRudderI,0);
portRudderInstance=config->getInt(GwConfigDefinitions::portRudderI,-1);
@@ -36,6 +83,30 @@ class GwConverterConfig{
rmcInterval=config->getInt(GwConfigDefinitions::sendRMCi,1000);
if (rmcInterval < 0) rmcInterval=0;
if (rmcInterval > 0 && rmcInterval <100) rmcInterval=100;
winst312=config->getInt(GwConfigDefinitions::winst312,256);
for (auto && it:windConfigs){
String cfg=config->getString(it.second);
WindMapping mapping(it.first,cfg);
if (mapping.valid){
LOG_DEBUG(GwLog::ERROR,"add wind mapping n2k=%d,nmea0183=%01d(%s)",
(int)(mapping.n2kType),(int)(mapping.nmea0183Type),cfg.c_str());
windMappings.push_back(mapping);
}
}
}
const WindMapping findWindMapping(const tN2kWindReference &n2k) const{
for (const auto & it:windMappings){
if (it.n2kType == n2k) return it;
}
return WindMapping();
}
const WindMapping findWindMapping(const WindMapping::Wind0183Type &n183) const{
for (const auto & it:windMappings){
if (it.nmea0183Type == n183) return it;
}
return WindMapping();
}
};
#endif

View File

@@ -7,6 +7,7 @@
#include <vector>
#include "N2kMessages.h"
#include "GwXdrTypeMappings.h"
/**
* INVALID!!! - the next interface declaration will not work
* as it is not in the correct header file
@@ -144,6 +145,26 @@ String formatValue(GwApi::BoatValue *value){
return String(buffer);
}
class ExampleWebData{
SemaphoreHandle_t lock;
int data=0;
public:
ExampleWebData(){
lock=xSemaphoreCreateMutex();
}
~ExampleWebData(){
vSemaphoreDelete(lock);
}
void set(int v){
GWSYNCHRONIZED(&lock);
data=v;
}
int get(){
GWSYNCHRONIZED(&lock);
return data;
}
};
void exampleTask(GwApi *api){
GwLog *logger=api->getLogger();
//get some configuration data
@@ -172,8 +193,24 @@ void exampleTask(GwApi *api){
LOG_DEBUG(GwLog::LOG,"exampleNotWorking update returned %d",(int)nwrs);
String voltageTransducer=api->getConfig()->getString(GwConfigDefinitions::exTransducer);
int voltageInstance=api->getConfig()->getInt(GwConfigDefinitions::exInstanceId);
ExampleWebData webData;
/**
* an example web request handler
* it uses a synchronized data structure as it gets called from a different thread
* be aware that you must not block for longer times here!
*/
api->registerRequestHandler("data",[&webData](AsyncWebServerRequest *request){
int data=webData.get();
char buffer[30];
snprintf(buffer,29,"%d",data);
buffer[29]=0;
request->send(200,"text/plain",buffer);
});
int loopcounter=0;
while(true){
delay(1000);
loopcounter++;
webData.set(loopcounter);
/*
* getting values from the internal data store (boatData) requires some special handling
* our tasks runs (potentially) at some time on a different core then the main code

View File

@@ -32,6 +32,26 @@ Files
This file allows to add some config definitions that are needed for our task. For the possible options have a look at the global [config.json](../../web/config.json). Be careful not to overwrite config defitions from the global file. A good practice wood be to prefix the names of definitions with parts of the library name. Always put them in a separate category so that they do not interfere with the system ones.
The defined config items can later be accessed in the code (see the example in [GwExampleTask.cpp](GwExampleTask.cpp)).
* [index.js](index.js)<br>
You can add javascript code that will contribute to the UI of the system. The WebUI provides a small API that allows you to "hook" into some functions to include your own parts of the UI. This includes adding new tabs, modifying/replacing the data display items, modifying the status display or accessing the config items.
For the API refer to [../../web/index.js](../../web/index.js#L2001).
To start interacting just register for some events like api.EVENTS.init. You can check the capabilities you have defined to see if your task is active.
By registering an own formatter [api.addUserFormatter](../../web/index.js#L2054) you can influence the way boat data items are shown.
You can even go for an own display by registering for the event *dataItemCreated* and replace the dom element content with your own html. By additionally having added a user formatter you can now fill your own html with the current value.
By using [api.addTabPage](../../web/index.js#L2046) you can add new tabs that you can populate with your own code. Or you can link to an external URL.<br>
Please be aware that your js code is always combined with the code from the core into one js file.<br>
For fast testing there is a small python script that allow you to test the UI without always flushing each change.
Just run it with
```
tools/testServer.py nnn http://x.x.x.x/api
```
with nnn being the local port and x.x.x.x the address of a running system. Open `http://localhost:nnn` in your browser.<br>
After a change just start the compilation and reload the page.
* [index.css](index.css)<br>
You can add own css to influence the styling of the display.
Interfaces
----------
The task init function and the task function interact with the core using an [API](../api/GwApi.h) that they get when started.
@@ -50,7 +70,8 @@ Files
* add capabilities (since 20231105 - as an alternative to a static DECLARE_CAPABILITY )
* add a user task (since 20231105 - as an alternative to a static DECLARE_USERTASK)
* store or read task interface data (see below)
* add a request handler for web requests (since 202411xx) - see registerRequestHandler in the API
__Interfacing between Task__

View File

@@ -0,0 +1,3 @@
.examplecss{
background-color: coral;
}

101
lib/exampletask/index.js Normal file
View File

@@ -0,0 +1,101 @@
(function(){
const api=window.esp32nmea2k;
if (! api) return;
//we only do something if a special capability is set
//on our case this is "testboard"
//so we only start any action when we receive the init event
//and we successfully checked that our requested capability is there
const tabName="example";
const configName="exampleBDSel";
const infoUrl='https://github.com/wellenvogel/esp32-nmea2000/tree/master/lib/exampletask';
let boatItemName;
let boatItemElement;
api.registerListener((id, data) => {
//data is capabilities
//check if our requested capability is there (see GwExampleTask.h)
if (!data.testboard) return; //do nothing if we are not active
//add a simple additional tab page
//you will have to build the content of the page dynamically
//using normal dom manipulation methods
//you can use the helper addEl to create elements
let page = api.addTabPage(tabName, "Example");
api.addEl('div', 'hdg', page, "this is a test tab");
let vrow = api.addEl('div', 'row', page);
api.addEl('span', 'label', vrow, 'loops: ');
let lcount = api.addEl('span', 'value', vrow, '0');
//query the loop count
window.setInterval(() => {
fetch('/api/user/exampleTask/data')
.then((res) => {
if (!res.ok) throw Error("server error: " + res.status);
return res.text();
})
.then((txt) => {
//set the text content of our value element with what we received
lcount.textContent = txt;
})
.catch((e) => console.log("rq:", e));
}, 1000);
api.addEl('button', '', page, 'Info').addEventListener('click', function (ev) {
window.open(infoUrl, 'info');
})
//add a tab for an external URL
api.addTabPage('exhelp', 'Info', infoUrl);
//now as we know we are active - register all the listeners we need
api.registerListener((id, data) => {
console.log("exampletask status listener", data);
}, api.EVENTS.status)
api.registerListener((id, data) => {
if (data === tabName) {
//maybe we need some activity when our page is being activated
console.log("example tab activated");
}
}, api.EVENTS.tab);
api.registerListener((id, data) => {
//we have a configuration that
//gives us the name of a boat data item we would like to
//handle special
//in our case we just use an own formatter and add some
//css to the display field
//as this item can change we need to keep track of the
//last item we handled
let nextboatItemName = data[configName];
console.log("value of " + configName, nextboatItemName);
if (nextboatItemName) {
//register a user formatter that will be called whenever
//there is a new valid value
//we simply add an "X:" in front
api.addUserFormatter(nextboatItemName, "m(x)", function (v, valid) {
if (!valid) return;
return "X:" + v;
})
//after this call the item will be recreated
}
if (boatItemName !== undefined && boatItemName != nextboatItemName) {
//if the boat item that we handle has changed, remove
//the previous user formatter (this will recreate the item)
api.removeUserFormatter(boatItemName);
}
boatItemName = nextboatItemName;
boatItemElement = undefined;
}, api.EVENTS.config);
api.registerListener((id, data) => {
//this event is called whenever a data item has
//been created (or recreated)
//if this is the item we handle, we just add a css class
//we could also completely rebuild the dom below the element
//and use our formatter to directly write/draw the data
//avoid direct manipulation of the element (i.e. changing the classlist)
//as this element remains there all the time
if (boatItemName && boatItemName == data.name) {
boatItemElement = data.element;
//use the helper forEl to find elements within the dashboard item
//the value element has the class "dashValue"
api.forEl(".dashValue", function (el) {
el.classList.add("examplecss");
}, boatItemElement);
}
}, api.EVENTS.dataItemCreated);
}, api.EVENTS.init);
})();

View File

@@ -4,6 +4,7 @@
#include <functional>
#include "GwMessage.h"
#include "GwLog.h"
#include "GwApi.h"
class GwWebServer{
private:
AsyncWebServer *server;
@@ -11,7 +12,7 @@ class GwWebServer{
GwLog *logger;
public:
typedef GwRequestMessage *(RequestCreator)(AsyncWebServerRequest *request);
using HandlerFunction=std::function<void(AsyncWebServerRequest *)>;
using HandlerFunction=GwApi::HandlerFunction;
GwWebServer(GwLog *logger, GwRequestQueue *queue,int port);
~GwWebServer();
void begin();

View File

@@ -1,3 +1,4 @@
#include <esp_wifi.h>
#include "GWWifi.h"
@@ -35,7 +36,29 @@ void GwWifi::setup(){
LOG_DEBUG(GwLog::ERROR,"unable to set access point mask %s, falling back to %s",
apMask.c_str(),AP_subnet.toString().c_str());
}
//try to remove any existing config from nvs
//this will avoid issues when updating from framework 6.3.2 to 6.8.1 - see #78
//we do not need the nvs config any way - so we set persistent to false
//unfortunately this will be to late (config from nvs has already been loaded)
//if we update from an older version that has config in the nvs
//so we need to make a dummy init, erase the flash and deinit
wifi_config_t conf_current;
wifi_init_config_t conf=WIFI_INIT_CONFIG_DEFAULT();
esp_err_t err=esp_wifi_init(&conf);
esp_wifi_get_config((wifi_interface_t)WIFI_IF_AP, &conf_current);
LOG_DEBUG(GwLog::DEBUG,"Wifi AP old config before reset ssid=%s, pass=%s, channel=%d",conf_current.ap.ssid,conf_current.ap.password,conf_current.ap.channel);
if (err){
LOG_DEBUG(GwLog::ERROR,"unable to pre-init wifi: %d",(int)err);
}
err=esp_wifi_restore();
if (err){
LOG_DEBUG(GwLog::ERROR,"unable to reset wifi: %d",(int)err);
}
err=esp_wifi_deinit();
WiFi.persistent(false);
WiFi.mode(WIFI_MODE_APSTA); //enable both AP and client
esp_wifi_get_config((wifi_interface_t)WIFI_IF_AP, &conf_current);
LOG_DEBUG(GwLog::DEBUG,"Wifi AP old config after reset ssid=%s, pass=%s, channel=%d",conf_current.ap.ssid,conf_current.ap.password,conf_current.ap.channel);
const char *ssid=config->getConfigItem(config->systemName)->asCString();
if (fixedApPass){
WiFi.softAP(ssid,AP_password);
@@ -45,7 +68,7 @@ void GwWifi::setup(){
}
delay(100);
WiFi.softAPConfig(AP_local_ip, AP_gateway, AP_subnet);
LOG_DEBUG(GwLog::LOG,"WifiAP created: ssid=%s,adress=%s",
LOG_DEBUG(GwLog::ERROR,"WifiAP created: ssid=%s,adress=%s",
ssid,
WiFi.softAPIP().toString().c_str()
);

View File

@@ -24,6 +24,7 @@
#define GWSERIAL_TYPE_BI 2
#define GWSERIAL_TYPE_RX 3
#define GWSERIAL_TYPE_TX 4
#define GWSERIAL_TYPE_UNK 0
#include <GwConfigItem.h>
#include <HardwareSerial.h>
#include "GwAppInfo.h"

View File

@@ -1,8 +1,19 @@
#include "GwLedTask.h"
#include "GwHardware.h"
#include "GwApi.h"
void handleLeds(GwApi *api);
void initLeds(GwApi *param)
{
#ifdef GWLED_FASTLED
param->addUserTask(handleLeds, "handleLeds");
#endif
}
#ifdef GWLED_FASTLED
#include "FastLED.h"
typedef enum {
typedef enum
{
LED_OFF,
LED_GREEN,
LED_BLUE,
@@ -10,41 +21,38 @@ typedef enum {
LED_WHITE
} GwLedMode;
static CRGB::HTMLColorCode colorFromMode(GwLedMode cmode){
switch(cmode){
case LED_BLUE:
return CRGB::Blue;
case LED_GREEN:
return CRGB::Green;
case LED_RED:
return CRGB::Red;
case LED_WHITE:
return CRGB::White;
default:
return CRGB::Black;
static CRGB::HTMLColorCode colorFromMode(GwLedMode cmode)
{
switch (cmode)
{
case LED_BLUE:
return CRGB::Blue;
case LED_GREEN:
return CRGB::Green;
case LED_RED:
return CRGB::Red;
case LED_WHITE:
return CRGB::White;
default:
return CRGB::Black;
}
}
void handleLeds(GwApi *api){
GwLog *logger=api->getLogger();
#ifndef GWLED_FASTLED
LOG_DEBUG(GwLog::LOG,"currently only fastled handling");
delay(50);
vTaskDelete(NULL);
return;
#else
void handleLeds(GwApi *api)
{
GwLog *logger = api->getLogger();
CRGB leds[1];
#ifdef GWLED_SCHEMA
FastLED.addLeds<GWLED_TYPE,GWLED_PIN,(EOrder)GWLED_SCHEMA>(leds,1);
#else
FastLED.addLeds<GWLED_TYPE,GWLED_PIN>(leds,1);
#endif
uint8_t brightness=api->getConfig()->getInt(GwConfigDefinitions::ledBrightness,128);
GwLedMode currentMode=LED_GREEN;
leds[0]=colorFromMode(currentMode);
#ifdef GWLED_SCHEMA
FastLED.addLeds<GWLED_TYPE, GWLED_PIN, (EOrder)GWLED_SCHEMA>(leds, 1);
#else
FastLED.addLeds<GWLED_TYPE, GWLED_PIN>(leds, 1);
#endif
uint8_t brightness = api->getConfig()->getInt(GwConfigDefinitions::ledBrightness, 128);
GwLedMode currentMode = LED_GREEN;
leds[0] = colorFromMode(currentMode);
FastLED.setBrightness(brightness);
FastLED.show();
LOG_DEBUG(GwLog::LOG,"led task started with mode %d",(int)currentMode);
int apiResult=0;
LOG_DEBUG(GwLog::LOG, "led task started with mode %d, brightness=%d", (int)currentMode, (int)brightness);
int apiResult = 0;
while (true)
{
delay(50);
@@ -77,5 +85,5 @@ void handleLeds(GwApi *api){
}
}
vTaskDelete(NULL);
#endif
}
}
#endif

View File

@@ -1,8 +1,9 @@
#ifndef _GWLEDS_H
#define _GWLEDS_H
#include "GwApi.h"
//task function
void handleLeds(GwApi *param);
//task init function
DECLARE_USERTASK(handleLeds);
void initLeds(GwApi *param);
DECLARE_INITFUNCTION(initLeds);
#endif

View File

@@ -0,0 +1,15 @@
[platformio]
#if you want a pio run to only build
#your special environments you can set this here
#by uncommenting the next line
#default_envs = testboard
[env:nodemculed]
board = nodemcu-32s
lib_deps = ${env.lib_deps}
build_flags =
-D BOARD_HOMBERGER
-D GWLED_CODE=1
-D GWLED_PIN=33
${env.build_flags}
upload_port = /dev/esp32
upload_protocol = esptool

View File

@@ -38,5 +38,7 @@ class GwLog{
long long getRecordCounter(){return recordCounter;}
};
#define LOG_DEBUG(level,...){ if (logger != NULL && logger->isActive(level)) logger->logDebug(level,__VA_ARGS__);}
#define LOG_INFO(...){ if (logger != NULL && logger->isActive(GwLog::LOG)) logger->logDebug(GwLog::LOG,__VA_ARGS__);}
#define LOG_ERROR(...){ if (logger != NULL && logger->isActive(GwLog::ERROR)) logger->logDebug(GwLog::ERROR,__VA_ARGS__);}
#endif

View File

@@ -103,7 +103,7 @@ private:
if (v != NMEA0183UInt32NA){
return target->update(v,sourceId);
}
return v;
return false;
}
uint32_t getUint32(GwBoatItem<uint32_t> *src){
return src->getDataWithDefault(N2kUInt32NA);
@@ -399,28 +399,29 @@ private:
return;
}
tN2kMsg n2kMsg;
tN2kWindReference n2kRef;
bool shouldSend=false;
WindAngle=formatDegToRad(WindAngle);
GwConverterConfig::WindMapping mapping;
switch(Reference){
case NMEA0183Wind_Apparent:
n2kRef=N2kWind_Apparent;
shouldSend=updateDouble(boatData->AWA,WindAngle,msg.sourceId) &&
updateDouble(boatData->AWS,WindSpeed,msg.sourceId);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxAws->updateMax(WindSpeed);
updateDouble(boatData->AWS,WindSpeed,msg.sourceId);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxAws->updateMax(WindSpeed,msg.sourceId);
mapping=config.findWindMapping(GwConverterConfig::WindMapping::AWA_AWS);
break;
case NMEA0183Wind_True:
n2kRef=N2kWind_True_North;
shouldSend=updateDouble(boatData->TWD,WindAngle,msg.sourceId) &&
updateDouble(boatData->TWS,WindSpeed,msg.sourceId);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed);
shouldSend=updateDouble(boatData->TWA,WindAngle,msg.sourceId) &&
updateDouble(boatData->TWS,WindSpeed,msg.sourceId);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed,msg.sourceId);
mapping=config.findWindMapping(GwConverterConfig::WindMapping::TWA_TWS);
break;
default:
LOG_DEBUG(GwLog::DEBUG,"unknown wind reference %d in %s",(int)Reference,msg.line);
}
if (shouldSend){
SetN2kWindSpeed(n2kMsg,1,WindSpeed,WindAngle,n2kRef);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)n2kRef));
//TODO: try to compute TWD and get mapping for this one
if (shouldSend && mapping.valid){
SetN2kWindSpeed(n2kMsg,1,WindSpeed,WindAngle,mapping.n2kType);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)mapping.n2kType));
}
}
void convertVWR(const SNMEA0183Msg &msg)
@@ -457,18 +458,20 @@ private:
bool shouldSend = false;
shouldSend = updateDouble(boatData->AWA, WindAngle, msg.sourceId) &&
updateDouble(boatData->AWS, WindSpeed, msg.sourceId);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxAws->updateMax(WindSpeed);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxAws->updateMax(WindSpeed,msg.sourceId);
if (shouldSend)
{
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, N2kWind_Apparent);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)N2kWind_Apparent));
const GwConverterConfig::WindMapping mapping=config.findWindMapping(GwConverterConfig::WindMapping::AWA_AWS);
if (mapping.valid){
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, mapping.n2kType);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)mapping.n2kType));
}
}
}
void convertMWD(const SNMEA0183Msg &msg)
{
double WindAngle = NMEA0183DoubleNA, WindAngleMagnetic=NMEA0183DoubleNA,
WindSpeed = NMEA0183DoubleNA;
double WindDirection = NMEA0183DoubleNA, WindDirectionMagnetic=NMEA0183DoubleNA, WindSpeed = NMEA0183DoubleNA;
if (msg.FieldCount() < 8 )
{
LOG_DEBUG(GwLog::DEBUG, "failed to parse MWD %s", msg.line);
@@ -476,11 +479,11 @@ private:
}
if (msg.FieldLen(0) > 0 && msg.Field(1)[0] == 'T')
{
WindAngle = formatDegToRad(atof(msg.Field(0)));
WindDirection = formatDegToRad(atof(msg.Field(0)));
}
if (msg.FieldLen(2) > 0 && msg.Field(3)[0] == 'M')
{
WindAngleMagnetic = formatDegToRad(atof(msg.Field(2)));
WindDirectionMagnetic = formatDegToRad(atof(msg.Field(2)));
}
if (msg.FieldLen(4) > 0 && msg.Field(5)[0] == 'N')
{
@@ -497,32 +500,38 @@ private:
}
tN2kMsg n2kMsg;
bool shouldSend = false;
if (WindAngle != NMEA0183DoubleNA){
shouldSend = updateDouble(boatData->TWD, WindAngle, msg.sourceId) &&
if (WindDirection != NMEA0183DoubleNA){
shouldSend = updateDouble(boatData->TWD, WindDirection, msg.sourceId) &&
updateDouble(boatData->TWS, WindSpeed, msg.sourceId);
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed);
}
if (shouldSend)
{
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindAngle, 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,msg.sourceId,String(n2kMsg.PGN)+String((int)N2kWind_Magnetic));
if (WindSpeed != NMEA0183DoubleNA) boatData->MaxTws->updateMax(WindSpeed,msg.sourceId);
if(shouldSend && boatData->HDT->isValid()) {
double twa = WindDirection-boatData->HDT->getData();
if(twa<0) { twa+=2*M_PI; }
updateDouble(boatData->TWA, twa, msg.sourceId);
const GwConverterConfig::WindMapping mapping=config.findWindMapping(GwConverterConfig::WindMapping::TWA_TWS);
if (mapping.valid){
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, twa, mapping.n2kType);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)mapping.n2kType));
}
const GwConverterConfig::WindMapping mapping2=config.findWindMapping(GwConverterConfig::WindMapping::TWD_TWS);
if (mapping2.valid){
SetN2kWindSpeed(n2kMsg, 1, WindSpeed, WindDirection, mapping2.n2kType);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((int)mapping2.n2kType));
}
}
}
}
void convertHDM(const SNMEA0183Msg &msg){
double MHDG=NMEA0183DoubleNA;
if (!NMEA0183ParseHDM_nc(msg, MHDG))
double HDM=NMEA0183DoubleNA;
if (!NMEA0183ParseHDM_nc(msg, HDM))
{
LOG_DEBUG(GwLog::DEBUG, "failed to parse HDM %s", msg.line);
return;
}
if (! UD(MHDG)) return;
if (! UD(HDM)) return;
tN2kMsg n2kMsg;
SetN2kMagneticHeading(n2kMsg,1,MHDG,
SetN2kMagneticHeading(n2kMsg,1,HDM,
boatData->VAR->getDataWithDefault(N2kDoubleNA),
boatData->DEV->getDataWithDefault(N2kDoubleNA)
);
@@ -530,28 +539,29 @@ private:
}
void convertHDT(const SNMEA0183Msg &msg){
double HDG=NMEA0183DoubleNA;
if (!NMEA0183ParseHDT_nc(msg, HDG))
double HDT=NMEA0183DoubleNA;
if (!NMEA0183ParseHDT_nc(msg, HDT))
{
LOG_DEBUG(GwLog::DEBUG, "failed to parse HDT %s", msg.line);
return;
}
if (! UD(HDG)) return;
if (! UD(HDT)) return;
tN2kMsg n2kMsg;
SetN2kTrueHeading(n2kMsg,1,HDG);
SetN2kTrueHeading(n2kMsg,1,HDT);
send(n2kMsg,msg.sourceId);
}
void convertHDG(const SNMEA0183Msg &msg){
double MHDG=NMEA0183DoubleNA;
double VAR=NMEA0183DoubleNA;
double HDM=NMEA0183DoubleNA;
double DEV=NMEA0183DoubleNA;
double VAR=NMEA0183DoubleNA;
if (msg.FieldCount() < 5)
{
LOG_DEBUG(GwLog::DEBUG, "failed to parse HDG %s", msg.line);
return;
}
if (msg.FieldLen(0)>0){
MHDG=formatDegToRad(atof(msg.Field(0)));
HDM=formatDegToRad(atof(msg.Field(0)));
}
else{
return;
@@ -565,11 +575,11 @@ private:
if (msg.Field(4)[0] == 'W') VAR=-VAR;
}
if (! UD(MHDG)) return;
if (! UD(HDM)) return;
UD(VAR);
UD(DEV);
tN2kMsg n2kMsg;
SetN2kMagneticHeading(n2kMsg,1,MHDG,DEV,VAR);
SetN2kMagneticHeading(n2kMsg,1,HDM,DEV,VAR);
send(n2kMsg,msg.sourceId,"127250M");
}
@@ -592,10 +602,10 @@ private:
}
//offset == 0? SK does not allow this
if (Offset != NMEA0183DoubleNA && Offset>=0 ){
if (! boatData->DBS->update(DepthBelowTransducer+Offset)) return;
if (! boatData->DBS->update(DepthBelowTransducer+Offset,msg.sourceId)) return;
}
if (Offset == NMEA0183DoubleNA) Offset=N2kDoubleNA;
if (! boatData->DBT->update(DepthBelowTransducer)) return;
if (! boatData->DBT->update(DepthBelowTransducer,msg.sourceId)) return;
tN2kMsg n2kMsg;
SetN2kWaterDepth(n2kMsg,1,DepthBelowTransducer,Offset);
send(n2kMsg,msg.sourceId,String(n2kMsg.PGN)+String((Offset != N2kDoubleNA)?1:0));
@@ -705,11 +715,11 @@ private:
return;
}
tN2kMsg n2kMsg;
if (updateDouble(boatData->HDG,TrueHeading,msg.sourceId)){
if (updateDouble(boatData->HDT,TrueHeading,msg.sourceId)){
SetN2kTrueHeading(n2kMsg,1,TrueHeading);
send(n2kMsg,msg.sourceId);
}
if(updateDouble(boatData->MHDG,MagneticHeading,msg.sourceId)){
if(updateDouble(boatData->HDM,MagneticHeading,msg.sourceId)){
SetN2kMagneticHeading(n2kMsg,1,MagneticHeading,
boatData->DEV->getDataWithDefault(N2kDoubleNA),
boatData->VAR->getDataWithDefault(N2kDoubleNA)
@@ -850,7 +860,7 @@ private:
LOG_DEBUG(GwLog::DEBUG,"GSV invalid current %u %s",current,msg.line);
return;
}
for (int idx=2;idx < msg.FieldCount();idx+=4){
for (int idx=3;idx < msg.FieldCount();idx+=4){
if (msg.FieldLen(idx) < 1 ||
msg.FieldLen(idx+1) < 1 ||
msg.FieldLen(idx+2) < 1 ||

View File

@@ -185,13 +185,13 @@ private:
if (N2kIsNA(Variation)){
//no variation
if (ref == N2khr_magnetic){
updateDouble(boatData->MHDG,Heading);
updateDouble(boatData->HDM,Heading);
if (NMEA0183SetHDM(NMEA0183Msg,Heading,talkerId)){
SendMessage(NMEA0183Msg);
}
}
if (ref == N2khr_true){
updateDouble(boatData->HDG,Heading);
updateDouble(boatData->HDT,Heading);
if (NMEA0183SetHDT(NMEA0183Msg,Heading,talkerId)){
SendMessage(NMEA0183Msg);
}
@@ -206,8 +206,8 @@ private:
if (ref == N2khr_true){
MagneticHeading=Heading-Variation;
}
updateDouble(boatData->MHDG,MagneticHeading);
updateDouble(boatData->HDG,Heading);
updateDouble(boatData->HDM,MagneticHeading);
updateDouble(boatData->HDT,Heading);
if (!N2kIsNA(MagneticHeading)){
if (NMEA0183SetHDG(NMEA0183Msg, MagneticHeading,_Deviation,
Variation,talkerId))
@@ -252,8 +252,8 @@ private:
tNMEA0183Msg NMEA0183Msg;
updateDouble(boatData->STW, WaterReferenced);
unsigned long now = millis();
double MagneticHeading = (boatData->HDG->isValid(now) && boatData->VAR->isValid(now)) ? boatData->HDG->getData() + boatData->VAR->getData() : NMEA0183DoubleNA;
if (NMEA0183SetVHW(NMEA0183Msg, boatData->HDG->getDataWithDefault(NMEA0183DoubleNA), MagneticHeading, WaterReferenced,talkerId))
double MagneticHeading = (boatData->HDT->isValid(now) && boatData->VAR->isValid(now)) ? boatData->HDT->getData() + boatData->VAR->getData() : NMEA0183DoubleNA;
if (NMEA0183SetVHW(NMEA0183Msg, boatData->HDT->getDataWithDefault(NMEA0183DoubleNA), MagneticHeading, WaterReferenced,talkerId))
{
SendMessage(NMEA0183Msg);
}
@@ -468,37 +468,73 @@ private:
{
unsigned char SID;
tN2kWindReference WindReference;
tNMEA0183WindReference NMEA0183Reference = NMEA0183Wind_True;
double x, y;
double WindAngle=N2kDoubleNA, WindSpeed=N2kDoubleNA;
if (ParseN2kWindSpeed(N2kMsg, SID, WindSpeed, WindAngle, WindReference))
{
tNMEA0183WindReference NMEA0183Reference;
if (ParseN2kWindSpeed(N2kMsg, SID, WindSpeed, WindAngle, WindReference)) {
tNMEA0183Msg NMEA0183Msg;
GwConverterConfig::WindMapping mapping=config.findWindMapping(WindReference);
bool shouldSend = false;
if (WindReference == N2kWind_Apparent)
// MWV sentence contains apparent/true ANGLE and SPEED
// https://gpsd.gitlab.io/gpsd/NMEA.html#_mwv_wind_speed_and_angle
// https://docs.vaisala.com/r/M211109EN-L/en-US/GUID-7402DEF8-5E82-446F-B63E-998F49F3D743/GUID-C77934C7-2A72-466E-BC52-CE6B8CC7ACB6
if (mapping.valid)
{
NMEA0183Reference = NMEA0183Wind_Apparent;
updateDouble(boatData->AWA, WindAngle);
updateDouble(boatData->AWS, WindSpeed);
setMax(boatData->MaxAws, boatData->AWS);
}
if (WindReference == N2kWind_True_North)
{
NMEA0183Reference = NMEA0183Wind_True;
updateDouble(boatData->TWD, WindAngle);
updateDouble(boatData->TWS, WindSpeed);
if (mapping.nmea0183Type == GwConverterConfig::WindMapping::AWA_AWS)
{
NMEA0183Reference = NMEA0183Wind_Apparent;
updateDouble(boatData->AWA, WindAngle);
updateDouble(boatData->AWS, WindSpeed);
setMax(boatData->MaxAws, boatData->AWS);
shouldSend = true;
}
if (mapping.nmea0183Type == GwConverterConfig::WindMapping::TWA_TWS)
{
NMEA0183Reference = NMEA0183Wind_True;
updateDouble(boatData->TWA, WindAngle);
updateDouble(boatData->TWS, WindSpeed);
setMax(boatData->MaxTws, boatData->TWS);
shouldSend = true;
if (boatData->HDT->isValid())
{
double twd = WindAngle + boatData->HDT->getData();
if (twd > 2 * M_PI)
{
twd -= 2 * M_PI;
}
updateDouble(boatData->TWD, twd);
}
}
if (mapping.nmea0183Type == GwConverterConfig::WindMapping::TWD_TWS)
{
NMEA0183Reference = NMEA0183Wind_True;
updateDouble(boatData->TWD, WindAngle);
updateDouble(boatData->TWS, WindSpeed);
setMax(boatData->MaxTws, boatData->TWS);
if (boatData->HDT->isValid())
{
shouldSend = true;
double twa = WindAngle - boatData->HDT->getData();
if (twa > 2 * M_PI)
{
twa -= 2 * M_PI;
}
updateDouble(boatData->TWA, twa);
WindAngle=twa;
}
}
if (shouldSend && NMEA0183SetMWV(NMEA0183Msg, formatCourse(WindAngle), NMEA0183Reference, WindSpeed, talkerId))
{
SendMessage(NMEA0183Msg);
}
}
if (NMEA0183SetMWV(NMEA0183Msg, formatCourse(WindAngle), NMEA0183Reference, WindSpeed,talkerId))
SendMessage(NMEA0183Msg);
if (WindReference == N2kWind_Apparent && boatData->SOG->isValid())
/* if (WindReference == N2kWind_Apparent && boatData->SOG->isValid())
{ // Lets calculate and send TWS/TWA if SOG is available
x = WindSpeed * cos(WindAngle);
y = WindSpeed * sin(WindAngle);
double x = WindSpeed * cos(WindAngle);
double y = WindSpeed * sin(WindAngle);
updateDouble(boatData->TWD, atan2(y, -boatData->SOG->getData() + x));
updateDouble(boatData->TWS, sqrt((y * y) + ((-boatData->SOG->getData() + x) * (-boatData->SOG->getData() + x))));
@@ -534,7 +570,7 @@ private:
return;
SendMessage(NMEA0183Msg);
}
} */
}
}
//*****************************************************************************
@@ -657,12 +693,14 @@ private:
double _Heading=N2kDoubleNA;
double _ROT=N2kDoubleNA;
tN2kAISNavStatus _NavStatus;
tN2kAISTransceiverInformation _AISTransceiverInformation;
uint8_t _SID;
uint8_t _MessageType = 1;
tNMEA0183AISMsg NMEA0183AISMsg;
if (ParseN2kPGN129038(N2kMsg, SID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM, _Seconds,
_COG, _SOG, _Heading, _ROT, _NavStatus))
_COG, _SOG, _Heading, _ROT, _NavStatus,_AISTransceiverInformation,_SID))
{
// Debug
@@ -746,12 +784,13 @@ private:
tN2kGNSStype _GNSStype;
tN2kAISTransceiverInformation _AISinfo;
tN2kAISDTE _DTE;
uint8_t _SID;
tNMEA0183AISMsg NMEA0183AISMsg;
if (ParseN2kPGN129794(N2kMsg, _MessageID, _Repeat, _UserID, _IMONumber, _Callsign, _Name, _VesselType,
_Length, _Beam, _PosRefStbd, _PosRefBow, _ETAdate, _ETAtime, _Draught, _Destination,
_AISversion, _GNSStype, _DTE, _AISinfo))
if (ParseN2kPGN129794(N2kMsg, _MessageID, _Repeat, _UserID, _IMONumber, _Callsign, 8, _Name,21, _VesselType,
_Length, _Beam, _PosRefStbd, _PosRefBow, _ETAdate, _ETAtime, _Draught, _Destination,21,
_AISversion, _GNSStype, _DTE, _AISinfo,_SID))
{
#ifdef SERIAL_PRINT_AIS_FIELDS
@@ -855,9 +894,10 @@ private:
bool _Display, _DSC, _Band, _Msg22, _State;
tN2kAISMode _Mode;
tN2kAISTransceiverInformation _AISTranceiverInformation;
uint8_t _SID;
if (ParseN2kPGN129039(N2kMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM,
_Seconds, _COG, _SOG, _AISTranceiverInformation, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State))
_Seconds, _COG, _SOG, _AISTranceiverInformation, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State,_SID))
{
tNMEA0183AISMsg NMEA0183AISMsg;
@@ -896,8 +936,10 @@ private:
tN2kAISRepeat _Repeat;
uint32_t _UserID; // MMSI
char _Name[21];
tN2kAISTransceiverInformation _AISInfo;
uint8_t _SID;
if (ParseN2kPGN129809(N2kMsg, _MessageID, _Repeat, _UserID, _Name))
if (ParseN2kPGN129809(N2kMsg, _MessageID, _Repeat, _UserID, _Name,21,_AISInfo,_SID))
{
tNMEA0183AISMsg NMEA0183AISMsg;
@@ -923,9 +965,11 @@ private:
double _Beam=N2kDoubleNA;
double _PosRefStbd=N2kDoubleNA;
double _PosRefBow=N2kDoubleNA;
tN2kAISTransceiverInformation _AISInfo;
uint8_t _SID;
if (ParseN2kPGN129810(N2kMsg, _MessageID, _Repeat, _UserID, _VesselType, _Vendor, _Callsign,
_Length, _Beam, _PosRefStbd, _PosRefBow, _MothershipID))
if (ParseN2kPGN129810(N2kMsg, _MessageID, _Repeat, _UserID, _VesselType, _Vendor,4, _Callsign,8,
_Length, _Beam, _PosRefStbd, _PosRefBow, _MothershipID,_AISInfo,_SID))
{
//
@@ -1121,8 +1165,8 @@ private:
int16_t ETADate=0;
double BearingOriginToDestinationWaypoint=N2kDoubleNA;
double BearingPositionToDestinationWaypoint=N2kDoubleNA;
uint8_t OriginWaypointNumber;
uint8_t DestinationWaypointNumber;
uint32_t OriginWaypointNumber;
uint32_t DestinationWaypointNumber;
double DestinationLatitude=N2kDoubleNA;
double DestinationLongitude=N2kDoubleNA;
double WaypointClosingVelocity=N2kDoubleNA;
@@ -1288,6 +1332,20 @@ private:
return;
}
int i=0;
if (TempSource == N2kts_SeaTemperature) {
updateDouble(boatData->WTemp, Temperature);
tNMEA0183Msg NMEA0183Msg;
if (!NMEA0183Msg.Init("MTW", talkerId))
return;
if (!NMEA0183Msg.AddDoubleField(KelvinToC(Temperature)))
return;
if (!NMEA0183Msg.AddStrField("C"))
return;
SendMessage(NMEA0183Msg);
}
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,TempSource,0,0);
if (updateDouble(&mapping,Temperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
@@ -1320,6 +1378,21 @@ private:
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
return;
}
if (TemperatureSource == N2kts_SeaTemperature &&
(config.winst312 == TemperatureInstance || config.winst312 == 256)) {
updateDouble(boatData->WTemp, Temperature);
tNMEA0183Msg NMEA0183Msg;
if (!NMEA0183Msg.Init("MTW", talkerId))
return;
if (!NMEA0183Msg.AddDoubleField(KelvinToC(Temperature)))
return;
if (!NMEA0183Msg.AddStrField("C"))
return;
SendMessage(NMEA0183Msg);
}
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,(int)TemperatureSource,0,TemperatureInstance);
if (updateDouble(&mapping,Temperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());

View File

@@ -219,7 +219,7 @@ bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
bool SetAISClassBMessage24PartA(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat, uint32_t UserID, char *Name) {
bool found = false;
for (int i = 0; i < vships.size(); i++) {
for (size_t i = 0; i < vships.size(); i++) {
if ( vships[i]->_userID == UserID ) {
found = true;
break;

View File

@@ -21,7 +21,7 @@ GwBuffer::~GwBuffer(){
}
void GwBuffer::reset(String reason)
{
LOG_DEBUG(GwLog::LOG,"reseting buffer %s, reason %s",this->name.c_str(),reason.c_str());
if (! reason.isEmpty())LOG_DEBUG(GwLog::LOG,"reseting buffer %s, reason %s",this->name.c_str(),reason.c_str());
writePointer = buffer;
readPointer = buffer;
lp("reset");

View File

@@ -18,9 +18,9 @@ class GwMessageFetcher{
* buffer to safely inserte data if it fits
* and to write out data if possible
*/
typedef size_t (*GwBufferHandleFunction)(uint8_t *buffer, size_t len, void *param);
class GwBuffer{
public:
using GwBufferHandleFunction=std::function<size_t(uint8_t *buffer, size_t len, void *param)>;
static const size_t TX_BUFFER_SIZE=1620; // app. 20 NMEA messages
static const size_t RX_BUFFER_SIZE=600; // enough for 1 NMEA message or actisense message or seasmart message
typedef enum {
@@ -33,7 +33,7 @@ class GwBuffer{
uint8_t *buffer;
uint8_t *writePointer;
uint8_t *readPointer;
size_t offset(uint8_t* ptr){
size_t offset(uint8_t* ptr) const{
return (size_t)(ptr-buffer);
}
GwLog *logger;

View File

@@ -7,10 +7,10 @@ class GwSynchronized{
public:
GwSynchronized(SemaphoreHandle_t *locker){
this->locker=locker;
xSemaphoreTake(*locker, portMAX_DELAY);
if (locker != nullptr) xSemaphoreTake(*locker, portMAX_DELAY);
}
~GwSynchronized(){
xSemaphoreGive(*locker);
if (locker != nullptr) xSemaphoreGive(*locker);
}
};

View File

@@ -1,4 +1,5 @@
#include "GwSerial.h"
#include "GwHardware.h"
class GwSerialStream: public Stream{
private:
@@ -40,11 +41,13 @@ class GwSerialStream: public Stream{
GwSerial::GwSerial(GwLog *logger, Stream *s, int id,bool allowRead):serial(s)
GwSerial::GwSerial(GwLog *logger, Stream * stream,int id,int type,bool allowRead)
{
LOG_DEBUG(GwLog::DEBUG,"creating GwSerial %p id %d",this,id);
this->id=id;
this->logger = logger;
this->id=id;
this->stream=stream;
this->type=type;
String bufName="Ser(";
bufName+=String(id);
bufName+=")";
@@ -62,6 +65,20 @@ GwSerial::~GwSerial()
if (readBuffer) delete readBuffer;
}
String GwSerial::getMode(){
switch (type){
case GWSERIAL_TYPE_UNI:
return "UNI";
case GWSERIAL_TYPE_BI:
return "BI";
case GWSERIAL_TYPE_RX:
return "RX";
case GWSERIAL_TYPE_TX:
return "TX";
}
return "UNKNOWN";
}
bool GwSerial::isInitialized() { return initialized; }
size_t GwSerial::enqueue(const uint8_t *data, size_t len, bool partial)
{
@@ -70,9 +87,9 @@ size_t GwSerial::enqueue(const uint8_t *data, size_t len, bool partial)
}
GwBuffer::WriteStatus GwSerial::write(){
if (! isInitialized()) return GwBuffer::ERROR;
size_t numWrite=serial->availableForWrite();
size_t numWrite=availableForWrite();
size_t rt=buffer->fetchData(numWrite,[](uint8_t *buffer,size_t len, void *p){
return ((GwSerial *)p)->serial->write(buffer,len);
return ((GwSerial *)p)->stream->write(buffer,len);
},this);
if (rt != 0){
LOG_DEBUG(GwLog::DEBUG+1,"Serial %d write %d",id,rt);
@@ -93,11 +110,11 @@ void GwSerial::loop(bool handleRead,bool handleWrite){
write();
if (! isInitialized()) return;
if (! handleRead) return;
size_t available=serial->available();
size_t available=stream->available();
if (! available) return;
if (allowRead){
size_t rd=readBuffer->fillData(available,[](uint8_t *buffer, size_t len, void *p)->size_t{
return ((GwSerial *)p)->serial->readBytes(buffer,len);
return ((GwSerial *)p)->stream->readBytes(buffer,len);
},this);
if (rd != 0){
LOG_DEBUG(GwLog::DEBUG+2,"GwSerial %d read %d bytes",id,rd);
@@ -106,7 +123,7 @@ void GwSerial::loop(bool handleRead,bool handleWrite){
else{
uint8_t buffer[10];
if (available > 10) available=10;
serial->readBytes(buffer,available);
stream->readBytes(buffer,available);
}
}
void GwSerial::readMessages(GwMessageFetcher *writer){
@@ -115,10 +132,11 @@ void GwSerial::readMessages(GwMessageFetcher *writer){
writer->handleBuffer(readBuffer);
}
bool GwSerial::flush(long max){
bool GwSerial::flush(){
if (! isInitialized()) return false;
long max=getFlushTimeout();
if (! availableWrite) {
if ( serial->availableForWrite() < 1){
if ( availableForWrite() < 1){
return false;
}
availableWrite=true;
@@ -128,7 +146,7 @@ bool GwSerial::flush(long max){
if (write() != GwBuffer::AGAIN) return true;
vTaskDelay(1);
}
availableWrite=(serial->availableForWrite() > 0);
availableWrite=(availableForWrite() > 0);
return false;
}
Stream * GwSerial::getStream(bool partialWrite){

View File

@@ -4,31 +4,119 @@
#include "GwLog.h"
#include "GwBuffer.h"
#include "GwChannelInterface.h"
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
#include "hal/usb_serial_jtag_ll.h"
#endif
#define USBCDC_RESTART_TIME 100
class GwSerialStream;
class GwSerial : public GwChannelInterface{
private:
protected:
GwBuffer *buffer;
GwBuffer *readBuffer=NULL;
GwLog *logger;
Stream *stream;
bool initialized=false;
bool allowRead=true;
GwBuffer::WriteStatus write();
int id=-1;
int overflows=0;
size_t enqueue(const uint8_t *data, size_t len,bool partial=false);
Stream *serial;
bool availableWrite=false; //if this is false we will wait for availabkleWrite until we flush again
virtual long getFlushTimeout(){return 2000;}
virtual int availableForWrite()=0;
int type=0;
public:
static const int bufferSize=200;
GwSerial(GwLog *logger,Stream *stream,int id,bool allowRead=true);
~GwSerial();
GwSerial(GwLog *logger,Stream *stream,int id,int type,bool allowRead=true);
virtual ~GwSerial();
bool isInitialized();
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);
bool flush(long millis=200);
bool flush();
virtual Stream *getStream(bool partialWrites);
bool getAvailableWrite(){return availableWrite;}
virtual void begin(unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1)=0;
virtual String getMode() override;
friend GwSerialStream;
};
template<typename T>
class GwSerialImpl : public GwSerial{
private:
unsigned long lastWritable=0;
template<class C>
void beginImpl(C *s,unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1){}
void beginImpl(HardwareSerial *s,unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1){
s->begin(baud,config,rxPin,txPin);
}
template<class C>
void setError(C* s, GwLog *logger){}
void setError(HardwareSerial *s,GwLog *logger){
LOG_DEBUG(GwLog::LOG,"enable serial errors for channel %d",id);
s->onReceiveError([logger,this](hardwareSerial_error_t err){
LOG_DEBUG(GwLog::ERROR,"serial error on id %d: %d",this->id,(int)err);
});
}
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
void beginImpl(HWCDC *s,unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1){
s->begin(baud);
}
#endif
template<class C>
long getFlushTimeoutImpl(const C*){return 2000;}
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
long getFlushTimeoutImpl(HWCDC *){return 200;}
#endif
template<class C>
int availableForWrite(C* c){
return c->availableForWrite();
}
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
/**
* issue #81
* workaround for the HWCDC beeing stuck at some point in time
* with availableForWrite == 0 but the ISR being disabled
* we simply give a small delay of 100ms for availableForWrite being 0
* and afterwards retrigger the ISR
*/
int availableForWrite(HWCDC* c){
int rt=c->availableForWrite();
if (rt > 0) {
lastWritable=millis();
return rt;
}
unsigned long now=millis();
if (now > (lastWritable+USBCDC_RESTART_TIME)){
lastWritable=now;
if (c->isConnected()){
//this retriggers the ISR
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
}
}
return rt;
}
#endif
T *serial;
protected:
virtual long getFlushTimeout() override{
return getFlushTimeoutImpl(serial);
}
virtual int availableForWrite(){
return availableForWrite(serial);
}
public:
GwSerialImpl(GwLog* logger,T* s,int i,int type,bool allowRead=true): GwSerial(logger,s,i,type,allowRead),serial(s){}
virtual ~GwSerialImpl(){}
virtual void begin(unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1) override{
beginImpl(serial,baud,config,rxPin,txPin);
setError(serial,logger);
};
};
#endif

View File

@@ -17,4 +17,12 @@ class GwSocketHelper{
if (setsockopt(socket, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) != ESP_OK) return false;
return true;
}
static bool isMulticast(const String &addr){
in_addr iaddr;
if (inet_pton(AF_INET,addr.c_str(),&iaddr) != 1) return false;
return IN_MULTICAST(ntohl(iaddr.s_addr));
}
static bool equals(const in_addr &left, const in_addr &right){
return left.s_addr == right.s_addr;
}
};

View File

@@ -0,0 +1,167 @@
#include "GwUdpReader.h"
#include <ESPmDNS.h>
#include <errno.h>
#include "GwBuffer.h"
#include "GwSocketConnection.h"
#include "GwSocketHelper.h"
#include "GWWifi.h"
GwUdpReader::GwUdpReader(const GwConfigHandler *config, GwLog *logger, int minId)
{
this->config = config;
this->logger = logger;
this->minId = minId;
port=config->getInt(GwConfigDefinitions::udprPort);
buffer= new GwBuffer(logger,GwBuffer::RX_BUFFER_SIZE,"udprd");
}
void GwUdpReader::createAndBind(){
if (fd >= 0){
::close(fd);
}
if (currentStationIp.isEmpty() && (type == T_STA || type == T_MCSTA)) return;
fd=socket(AF_INET,SOCK_DGRAM,IPPROTO_IP);
if (fd < 0){
LOG_ERROR("UDPR: unable to create udp socket: %d",errno);
return;
}
int enable = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
if (type == T_STA)
{
if (inet_pton(AF_INET, currentStationIp.c_str(), &listenA.sin_addr) != 1)
{
LOG_ERROR("UDPR: invalid station ip address %s", currentStationIp.c_str());
close(fd);
fd = -1;
return;
}
}
if (bind(fd,(struct sockaddr *)&listenA,sizeof(listenA)) < 0){
LOG_ERROR("UDPR: unable to bind: %d",errno);
close(fd);
fd=-1;
return;
}
LOG_INFO("UDPR: socket created and bound");
if (type != T_MCALL && type != T_MCAP && type != T_MCSTA) {
return;
}
struct ip_mreq mc;
mc.imr_multiaddr=listenA.sin_addr;
if (type == T_MCALL || type == T_MCAP){
mc.imr_interface=apAddr;
int res=setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mc,sizeof(mc));
if (res != 0){
LOG_ERROR("UDPR: unable to add MC membership for AP:%d",errno);
}
else{
LOG_INFO("UDPR: membership for for AP");
}
}
if (!currentStationIp.isEmpty() && (type == T_MCALL || type == T_MCSTA))
{
mc.imr_interface = staAddr;
int res = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mc, sizeof(mc));
if (res != 0)
{
LOG_ERROR("UDPR: unable to add MC membership for STA:%d", errno);
}
else{
LOG_INFO("UDPR: membership for STA %s",currentStationIp.c_str());
}
}
}
void GwUdpReader::begin()
{
if (type != T_UNKNOWN) return; //already started
type=(UType)(config->getInt(GwConfigDefinitions::udprType));
LOG_INFO("UDPR begin, mode=%d",(int)type);
port=config->getInt(GwConfigDefinitions::udprPort);
listenA.sin_family=AF_INET;
listenA.sin_port=htons(port);
listenA.sin_addr.s_addr=htonl(INADDR_ANY); //default
String ap=WiFi.softAPIP().toString();
if (inet_pton(AF_INET, ap.c_str(), &apAddr) != 1)
{
LOG_ERROR("UDPR: invalid ap ip address %s", ap.c_str());
return;
}
if (type == T_MCALL || type == T_MCAP || type == T_MCSTA){
String mcAddr=config->getString(GwConfigDefinitions::udprMC);
if (inet_pton(AF_INET, mcAddr.c_str(), &listenA.sin_addr) != 1)
{
LOG_ERROR("UDPR: invalid mc address %s", mcAddr.c_str());
close(fd);
fd = -1;
return;
}
LOG_INFO("UDPR: using multicast address %s",mcAddr.c_str());
}
if (type == T_AP){
listenA.sin_addr=apAddr;
}
String sta;
if (WiFi.isConnected()) sta=WiFi.localIP().toString();
setStationAdd(sta);
createAndBind();
}
bool GwUdpReader::setStationAdd(const String &sta){
if (sta == currentStationIp) return false;
currentStationIp=sta;
if (inet_pton(AF_INET, currentStationIp.c_str(), &staAddr) != 1){
LOG_ERROR("UDPR: invalid station ip address %s", currentStationIp.c_str());
return false;
}
LOG_INFO("UDPR: new station IP %s",currentStationIp.c_str());
return true;
}
void GwUdpReader::loop(bool handleRead, bool handleWrite)
{
if (handleRead){
if (type == T_STA || type == T_MCALL || type == T_MCSTA){
//only change anything if we considered the station IP
String nextStationIp;
if (WiFi.isConnected()){
nextStationIp=WiFi.localIP().toString();
}
if (setStationAdd(nextStationIp)){
LOG_INFO("UDPR: wifi client IP changed, restart");
createAndBind();
}
}
}
}
void GwUdpReader::readMessages(GwMessageFetcher *writer)
{
if (fd < 0) return;
//we expect one NMEA message in one UDP packet
buffer->reset();
size_t rd=buffer->fillData(buffer->freeSpace(),
[this](uint8_t *rcvb,size_t rcvlen,void *param)->size_t{
struct sockaddr_in from;
socklen_t fromLen=sizeof(from);
ssize_t res=recvfrom(fd,rcvb,rcvlen,MSG_DONTWAIT,
(struct sockaddr*)&from,&fromLen);
if (res <= 0) return 0;
if (GwSocketHelper::equals(from.sin_addr,apAddr)) return 0;
if (!currentStationIp.isEmpty() && (GwSocketHelper::equals(from.sin_addr,staAddr))) return 0;
return res;
},this);
if (buffer->usedSpace() > 0)(GwLog::DEBUG,"UDPR: received %d bytes",buffer->usedSpace());
writer->handleBuffer(buffer);
}
size_t GwUdpReader::sendToClients(const char *buf, int source,bool partial)
{
return 0;
}
GwUdpReader::~GwUdpReader()
{
}

View File

@@ -0,0 +1,45 @@
#ifndef _GWUDPREADER_H
#define _GWUDPREADER_H
#include "GWConfig.h"
#include "GwLog.h"
#include "GwBuffer.h"
#include "GwChannelInterface.h"
#include <memory>
#include <sys/socket.h>
#include <arpa/inet.h>
class GwUdpReader: public GwChannelInterface{
public:
using UType=enum{
T_ALL=0,
T_AP=1,
T_STA=2,
T_MCALL=4,
T_MCAP=5,
T_MCSTA=6,
T_UNKNOWN=-1
};
private:
const GwConfigHandler *config;
GwLog *logger;
int minId;
int port;
int fd=-1;
struct sockaddr_in listenA;
String listenIp;
String currentStationIp;
struct in_addr apAddr;
struct in_addr staAddr;
UType type=T_UNKNOWN;
void createAndBind();
bool setStationAdd(const String &sta);
GwBuffer *buffer=nullptr;
public:
GwUdpReader(const GwConfigHandler *config,GwLog *logger,int minId);
~GwUdpReader();
void begin();
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);
};
#endif

View File

@@ -0,0 +1,203 @@
#include "GwUdpWriter.h"
#include <ESPmDNS.h>
#include <errno.h>
#include "GwBuffer.h"
#include "GwSocketConnection.h"
#include "GwSocketHelper.h"
#include "GWWifi.h"
GwUdpWriter::WriterSocket::WriterSocket(GwLog *l,int p,const String &src,const String &dst, SourceMode sm) :
sourceMode(sm), source(src), destination(dst), port(p),logger(l)
{
if (inet_pton(AF_INET, dst.c_str(), &dstA.sin_addr) != 1)
{
LOG_ERROR("UDPW: invalid destination ip address %s", dst.c_str());
return;
}
if (sourceMode != SourceMode::S_UNBOUND)
{
if (inet_pton(AF_INET, src.c_str(), &srcA) != 1)
{
LOG_ERROR("UDPW: invalid source ip address %s", src.c_str());
return;
}
}
dstA.sin_family=AF_INET;
dstA.sin_port=htons(port);
fd=socket(AF_INET,SOCK_DGRAM,IPPROTO_IP);
if (fd < 0){
LOG_ERROR("UDPW: unable to create udp socket: %d",errno);
return;
}
int enable = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int));
switch (sourceMode)
{
case SourceMode::S_SRC:
{
sockaddr_in bindA;
bindA.sin_family = AF_INET;
bindA.sin_port = htons(0); // let system select
bindA.sin_addr = srcA;
if (bind(fd, (struct sockaddr *)&bindA, sizeof(bindA)) != 0)
{
LOG_ERROR("UDPW: bind failed for address %s: %d", source.c_str(), errno);
::close(fd);
fd = -1;
return;
}
}
break;
case SourceMode::S_MC:
{
if (setsockopt(fd,IPPROTO_IP,IP_MULTICAST_IF,&srcA,sizeof(srcA)) != 0){
LOG_ERROR("UDPW: unable to set MC source %s: %d",source.c_str(),errno);
::close(fd);
fd=-1;
return;
}
int loop=0;
setsockopt(fd,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop));
}
break;
default:
//not bound
break;
}
}
bool GwUdpWriter::WriterSocket::changed(const String &newSrc, const String &newDst){
if (newDst != destination) return true;
if (sourceMode == SourceMode::S_UNBOUND) return false;
return newSrc != source;
}
size_t GwUdpWriter::WriterSocket::send(const char *buf,size_t len){
if (fd < 0) return 0;
ssize_t err = sendto(fd,buf,len,0,(struct sockaddr *)&dstA, sizeof(dstA));
if (err < 0){
LOG_DEBUG(GwLog::DEBUG,"UDPW %s error sending: %d",destination.c_str(), errno);
return 0;
}
return err;
}
GwUdpWriter::GwUdpWriter(const GwConfigHandler *config, GwLog *logger, int minId)
{
this->config = config;
this->logger = logger;
this->minId = minId;
port=config->getInt(GwConfigDefinitions::udpwPort);
}
void GwUdpWriter::checkStaSocket(){
String src;
String bc;
if (type == T_BCAP || type == T_MCAP || type == T_NORM || type == T_UNKNOWN ) return;
bool connected=false;
if (WiFi.isConnected()){
src=WiFi.localIP().toString();
bc=WiFi.broadcastIP().toString();
connected=true;
}
else{
if (staSocket == nullptr) return;
}
String dst;
WriterSocket::SourceMode sm=WriterSocket::SourceMode::S_SRC;
switch (type){
case T_BCALL:
case T_BCSTA:
sm=WriterSocket::SourceMode::S_SRC;
dst=bc;
break;
case T_MCALL:
case T_MCSTA:
dst=config->getString(GwConfigDefinitions::udpwMC);
sm=WriterSocket::SourceMode::S_MC;
break;
}
if (staSocket != nullptr)
{
if (!connected || staSocket->changed(src, dst))
{
staSocket->close();
delete staSocket;
staSocket = nullptr;
LOG_INFO("changing/stopping UDPW(sta) socket");
}
}
if (staSocket == nullptr && connected)
{
LOG_INFO("creating new UDP(sta) socket src=%s, dst=%s", src.c_str(), dst.c_str());
staSocket = new WriterSocket(logger, port, src, dst, WriterSocket::SourceMode::S_SRC);
}
}
void GwUdpWriter::begin()
{
if (type != T_UNKNOWN) return; //already started
type=(UType)(config->getInt(GwConfigDefinitions::udpwType));
LOG_INFO("UDPW begin, mode=%d",(int)type);
String src=WiFi.softAPIP().toString();
String dst;
WriterSocket::SourceMode sm=WriterSocket::SourceMode::S_UNBOUND;
bool createApSocket=false;
switch(type){
case T_BCALL:
case T_BCAP:
createApSocket=true;
dst=WiFi.softAPBroadcastIP().toString();
sm=WriterSocket::SourceMode::S_SRC;
break;
case T_MCALL:
case T_MCAP:
createApSocket=true;
dst=config->getString(GwConfigDefinitions::udpwMC);
sm=WriterSocket::SourceMode::S_SRC;
break;
case T_NORM:
createApSocket=true;
dst=config->getString(GwConfigDefinitions::udpwAddress);
sm=WriterSocket::SourceMode::S_UNBOUND;
}
if (createApSocket){
LOG_INFO("creating new UDPW(ap) socket src=%s, dst=%s", src.c_str(), dst.c_str());
apSocket=new WriterSocket(logger,port,src,dst,sm);
}
checkStaSocket();
}
void GwUdpWriter::loop(bool handleRead, bool handleWrite)
{
if (handleWrite){
checkStaSocket();
}
}
void GwUdpWriter::readMessages(GwMessageFetcher *writer)
{
}
size_t GwUdpWriter::sendToClients(const char *buf, int source,bool partial)
{
if (source == minId) return 0;
size_t len=strlen(buf);
bool hasSent=false;
size_t res=0;
if (apSocket != nullptr){
res=apSocket->send(buf,len);
if (res > 0) hasSent=true;
}
if (staSocket != nullptr){
res=staSocket->send(buf,len);
if (res > 0) hasSent=true;
}
return hasSent?len:0;
}
GwUdpWriter::~GwUdpWriter()
{
}

View File

@@ -0,0 +1,73 @@
#ifndef _GWUDPWRITER_H
#define _GWUDPWRITER_H
#include "GWConfig.h"
#include "GwLog.h"
#include "GwBuffer.h"
#include "GwChannelInterface.h"
#include <memory>
#include <sys/socket.h>
#include <arpa/inet.h>
class GwUdpWriter: public GwChannelInterface{
public:
using UType=enum{
T_BCALL=0,
T_BCAP=1,
T_BCSTA=2,
T_NORM=3,
T_MCALL=4,
T_MCAP=5,
T_MCSTA=6,
T_UNKNOWN=-1
};
private:
class WriterSocket{
public:
int fd=-1;
struct in_addr srcA;
struct sockaddr_in dstA;
String source;
String destination;
int port;
GwLog *logger;
using SourceMode=enum {
S_UNBOUND=0,
S_MC,
S_SRC
};
SourceMode sourceMode;
WriterSocket(GwLog *logger,int p,const String &src,const String &dst, SourceMode sm);
void close(){
if (fd > 0){
::close(fd);
}
fd=-1;
}
~WriterSocket(){
close();
}
bool changed(const String &newSrc, const String &newDst);
size_t send(const char *buf,size_t len);
};
const GwConfigHandler *config;
GwLog *logger;
/**
* we use fd/address to send to the AP network
* and fd2,address2 to send to the station network
* for type "normal" we only use fd
*/
WriterSocket *apSocket=nullptr; //also for T_NORM
WriterSocket *staSocket=nullptr;
int minId;
int port;
UType type=T_UNKNOWN;
void checkStaSocket();
public:
GwUdpWriter(const GwConfigHandler *config,GwLog *logger,int minId);
~GwUdpWriter();
void begin();
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);
};
#endif

View File

@@ -191,6 +191,7 @@ class TaskApi : public GwApiInternal
SemaphoreHandle_t *mainLock;
SemaphoreHandle_t localLock;
std::map<int,GwCounter<String>> counter;
std::map<String,GwApi::HandlerFunction> webHandlers;
String name;
bool counterUsed=false;
int counterIdx=0;
@@ -315,6 +316,10 @@ public:
virtual bool addXdrMapping(const GwXDRMappingDef &def){
return api->addXdrMapping(def);
}
virtual void registerRequestHandler(const String &url,HandlerFunction handler){
GWSYNCHRONIZED(&localLock);
webHandlers[url]=handler;
}
virtual void addCapability(const String &name, const String &value){
if (! isInit) return;
userCapabilities[name]=value;
@@ -335,6 +340,16 @@ public:
virtual void setCalibrationValue(const String &name, double value){
api->setCalibrationValue(name,value);
}
virtual bool handleWebRequest(const String &url,AsyncWebServerRequest *req){
GWSYNCHRONIZED(&localLock);
auto it=webHandlers.find(url);
if (it == webHandlers.end()){
api->getLogger()->logDebug(GwLog::LOG,"no web handler task=%s url=%s",name.c_str(),url.c_str());
return false;
}
it->second(req);
return true;
}
};
@@ -404,4 +419,19 @@ int GwUserCode::getJsonSize(){
}
}
return rt;
}
void GwUserCode::handleWebRequest(const String &url,AsyncWebServerRequest *req){
int sep1=url.indexOf('/');
String tname;
if (sep1 > 0){
tname=url.substring(0,sep1);
for (auto &&it:userTasks){
if (it.api && it.name == tname){
if (it.api->handleWebRequest(url.substring(sep1+1),req)) return;
break;
}
}
}
LOG_DEBUG(GwLog::DEBUG,"no task found for web request %s[%s]",url.c_str(),tname.c_str());
req->send(404, "text/plain", "not found");
}

View File

@@ -11,6 +11,7 @@ class GwApiInternal : public GwApi{
~GwApiInternal(){}
virtual void fillStatus(GwJsonDocument &status){};
virtual int getJsonSize(){return 0;};
virtual bool handleWebRequest(const String &url,AsyncWebServerRequest *req){return false;}
};
class GwUserTask{
public:
@@ -50,5 +51,6 @@ class GwUserCode{
Capabilities *getCapabilities();
void fillStatus(GwJsonDocument &status);
int getJsonSize();
void handleWebRequest(const String &url,AsyncWebServerRequest *);
};
#endif

View File

@@ -355,6 +355,7 @@ void GwXDRMappings::begin()
GwXDRFoundMapping GwXDRMappings::selectMapping(GwXDRMapping::MappingList *list, int instance, const char *key)
{
GwXDRMapping *candidate = NULL;
unsigned long invalidTime=config->getInt(GwConfigDefinitions::timoSensor);
for (auto mit = list->begin(); mit != list->end(); mit++)
{
GwXDRMappingDef *def = (*mit)->definition;
@@ -369,7 +370,7 @@ GwXDRFoundMapping GwXDRMappings::selectMapping(GwXDRMapping::MappingList *list,
{
LOG_DEBUG(GwLog::DEBUG + 1, "selected mapping %s for %s, i=%d",
def->toString().c_str(), key, instance);
return GwXDRFoundMapping(*mit, instance);
return GwXDRFoundMapping(*mit,invalidTime, instance);
}
if (instance < 0)
{
@@ -393,7 +394,7 @@ GwXDRFoundMapping GwXDRMappings::selectMapping(GwXDRMapping::MappingList *list,
{
LOG_DEBUG(GwLog::DEBUG + 1, "selected mapping %s for %s, i=%d",
candidate->definition->toString().c_str(), key, instance);
return GwXDRFoundMapping(candidate, instance>=0?instance:candidate->definition->instanceId);
return GwXDRFoundMapping(candidate, invalidTime,instance>=0?instance:candidate->definition->instanceId);
}
LOG_DEBUG(GwLog::DEBUG + 1, "no instance mapping found for key=%s, i=%d", key, instance);
return GwXDRFoundMapping();
@@ -472,8 +473,9 @@ String GwXDRMappings::getXdrEntry(String mapping, double value,int instance){
}
GwXDRType *type = findType(code, &typeIndex);
bool first=true;
unsigned long invalidTime=config->getInt(GwConfigDefinitions::timoSensor);
while (type){
GwXDRFoundMapping found(def,type);
GwXDRFoundMapping found(def,type,invalidTime);
found.instanceId=instance;
if (first) first=false;
else rt+=",";

View File

@@ -167,15 +167,18 @@ class GwXDRFoundMapping : public GwBoatItemNameProvider{
GwXDRType *type=NULL;
int instanceId=-1;
bool empty=true;
GwXDRFoundMapping(GwXDRMappingDef *definition,GwXDRType *type){
unsigned long timeout=0;
GwXDRFoundMapping(GwXDRMappingDef *definition,GwXDRType *type, unsigned long timeout){
this->definition=definition;
this->type=type;
this->timeout=timeout;
empty=false;
}
GwXDRFoundMapping(GwXDRMapping* mapping,int instance=0){
GwXDRFoundMapping(GwXDRMapping* mapping,unsigned long timeout,int instance){
this->definition=mapping->definition;
this->type=mapping->type;
this->instanceId=instance;
this->timeout=timeout;
empty=false;
}
GwXDRFoundMapping(){}
@@ -195,6 +198,9 @@ class GwXDRFoundMapping : public GwBoatItemNameProvider{
return "formatXdr:"+type->xdrtype+":"+type->boatDataUnit;
};
virtual ~GwXDRFoundMapping(){}
virtual unsigned long getInvalidTime() override{
return timeout;
}
};
//the class GwXDRMappings is not intended to be deleted