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:
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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;};
|
||||
};
|
||||
|
||||
|
||||
@@ -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";}
|
||||
};
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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__
|
||||
|
||||
|
||||
3
lib/exampletask/index.css
Normal file
3
lib/exampletask/index.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.examplecss{
|
||||
background-color: coral;
|
||||
}
|
||||
101
lib/exampletask/index.js
Normal file
101
lib/exampletask/index.js
Normal 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);
|
||||
})();
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
15
lib/ledtask/platformio.ini
Normal file
15
lib/ledtask/platformio.ini
Normal 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
|
||||
@@ -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
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
167
lib/socketserver/GwUdpReader.cpp
Normal file
167
lib/socketserver/GwUdpReader.cpp
Normal 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()
|
||||
{
|
||||
}
|
||||
45
lib/socketserver/GwUdpReader.h
Normal file
45
lib/socketserver/GwUdpReader.h
Normal 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
|
||||
203
lib/socketserver/GwUdpWriter.cpp
Normal file
203
lib/socketserver/GwUdpWriter.cpp
Normal 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()
|
||||
{
|
||||
}
|
||||
73
lib/socketserver/GwUdpWriter.h
Normal file
73
lib/socketserver/GwUdpWriter.h
Normal 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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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+=",";
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user