mirror of
https://github.com/thooge/esp32-nmea2000-obp60.git
synced 2025-12-13 22:13:07 +01:00
Merge branch 'wellenvogel:master' into master
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
|
||||
*/
|
||||
|
||||
@@ -48,12 +48,36 @@ GwBoatItemBase::GwBoatItemBase(String name, String format, GwBoatItemBase::TOTyp
|
||||
this->type = 0;
|
||||
this->lastUpdateSource = -1;
|
||||
}
|
||||
void GwBoatItemBase::setInvalidTime(unsigned long it, bool force){
|
||||
if (toType != TOType::user || force ){
|
||||
invalidTime=it;
|
||||
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()
|
||||
{
|
||||
@@ -127,7 +151,7 @@ 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>
|
||||
@@ -137,7 +161,7 @@ GwBoatItem<T>::GwBoatItem(String name, String formatInfo, GwBoatItemBase::TOType
|
||||
this->type = GwBoatItemTypes::getType(dummy);
|
||||
if (map)
|
||||
{
|
||||
(*map)[name] = this;
|
||||
map->add(name,this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,28 +346,12 @@ void GwBoatDataSatList::toJsonDoc(GwJsonDocument *doc, unsigned long minTime)
|
||||
GwBoatData::GwBoatData(GwLog *logger, GwConfigHandler *cfg)
|
||||
{
|
||||
this->logger = logger;
|
||||
this->config = cfg;
|
||||
}
|
||||
void GwBoatData::begin(){
|
||||
for (auto &&it : values){
|
||||
unsigned long timeout=GwBoatItemBase::INVALID_TIME;
|
||||
switch(it.second->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;
|
||||
}
|
||||
it.second->setInvalidTime(timeout);
|
||||
it.second->setInvalidTime(config);
|
||||
}
|
||||
|
||||
}
|
||||
GwBoatData::~GwBoatData()
|
||||
{
|
||||
@@ -456,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;
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#define ROT_WA_FACTOR 60
|
||||
|
||||
class GwJsonDocument;
|
||||
class GwBoatData;
|
||||
|
||||
class GwBoatItemBase{
|
||||
public:
|
||||
using TOType=enum{
|
||||
@@ -56,7 +58,6 @@ class GwBoatItemBase{
|
||||
GWSC(formatRot);
|
||||
GWSC(formatDate);
|
||||
GWSC(formatTime);
|
||||
typedef std::map<String,GwBoatItemBase*> GwBoatItemMap;
|
||||
protected:
|
||||
int type;
|
||||
unsigned long lastSet=0;
|
||||
@@ -93,10 +94,15 @@ class GwBoatItemBase{
|
||||
virtual double getDoubleValue()=0;
|
||||
String getName(){return name;}
|
||||
const String & getFormat() const{return format;}
|
||||
virtual void setInvalidTime(unsigned long it, bool force=true);
|
||||
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;
|
||||
@@ -186,8 +192,9 @@ public:
|
||||
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,formatCourse) // course over ground
|
||||
@@ -231,9 +238,11 @@ class GwBoatData{
|
||||
public:
|
||||
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[wp]=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);
|
||||
|
||||
@@ -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__
|
||||
|
||||
|
||||
@@ -5,84 +5,97 @@
|
||||
//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
|
||||
let isActive=false;
|
||||
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)=>{
|
||||
if (id === api.EVENTS.init){
|
||||
//data is capabilities
|
||||
//check if our requested capability is there (see GwExampleTask.h)
|
||||
if (data.testboard) isActive=true;
|
||||
if (isActive){
|
||||
//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");
|
||||
api.addEl('button','',page,'Info').addEventListener('click',function(ev){
|
||||
window.open(infoUrl,'info');
|
||||
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();
|
||||
})
|
||||
//add a tab for an external URL
|
||||
api.addTabPage('exhelp','Info',infoUrl);
|
||||
.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");
|
||||
}
|
||||
}
|
||||
if (isActive){
|
||||
//console.log("exampletask listener",id,data);
|
||||
if (id === api.EVENTS.tab){
|
||||
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 (id == api.EVENTS.config){
|
||||
//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;
|
||||
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);
|
||||
}
|
||||
if (id == api.EVENTS.dataItemCreated){
|
||||
//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);
|
||||
}
|
||||
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();
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -63,6 +63,17 @@
|
||||
#define _GWQMP6988
|
||||
#endif
|
||||
|
||||
#GROVE
|
||||
#ifdef M5_ENV4$GS$
|
||||
#ifndef M5_GROOVEIIC$GS$
|
||||
#define M5_GROOVEIIC$GS$
|
||||
#endif
|
||||
GROOVE_IIC(SHT3X,$Z$,1)
|
||||
GROOVE_IIC(BMP280,$Z$,1)
|
||||
#define _GWSHT3X
|
||||
#define _GWBMP280
|
||||
#endif
|
||||
|
||||
#GROVE
|
||||
//example: -DSHT3XG1_A : defines STH3Xn1 on grove A - x depends on the other devices
|
||||
#ifdef GWSHT3XG1$GS$
|
||||
@@ -118,6 +129,24 @@
|
||||
#define _GWBME280
|
||||
#endif
|
||||
|
||||
#GROVE
|
||||
#ifdef GWBMP280G1$GS$
|
||||
#ifndef M5_GROOVEIIC$GS$
|
||||
#define M5_GROOVEIIC$GS$
|
||||
#endif
|
||||
GROOVE_IIC(BMP280,$Z$,1)
|
||||
#define _GWBMP280
|
||||
#endif
|
||||
|
||||
#GROVE
|
||||
#ifdef GWBMP280G2$GS$
|
||||
#ifndef M5_GROOVEIIC$GS$
|
||||
#define M5_GROOVEIIC$GS$
|
||||
#endif
|
||||
GROOVE_IIC(BMP280,$Z$,2)
|
||||
#define _GWBMP280
|
||||
#endif
|
||||
|
||||
#GROVE
|
||||
//select up to 2 IIC devices for grove usage
|
||||
#ifdef M5_GROOVEIIC$GS$
|
||||
@@ -143,3 +172,5 @@
|
||||
#error "both serial devices already in use"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -68,16 +68,20 @@ class BME280Config : public IICSensorBase{
|
||||
if (!device)
|
||||
return;
|
||||
GwLog *logger = api->getLogger();
|
||||
float pressure = N2kDoubleNA;
|
||||
float temperature = N2kDoubleNA;
|
||||
float humidity = N2kDoubleNA;
|
||||
float computed = N2kDoubleNA;
|
||||
if (prAct)
|
||||
{
|
||||
float pressure = device->readPressure();
|
||||
float computed = pressure + prOff;
|
||||
pressure = device->readPressure();
|
||||
computed = pressure + prOff;
|
||||
LOG_DEBUG(GwLog::DEBUG, "%s measure %2.0fPa, computed %2.0fPa", prefix.c_str(), pressure, computed);
|
||||
sendN2kPressure(api, *this, computed, counterId);
|
||||
}
|
||||
if (tmAct)
|
||||
{
|
||||
float temperature = device->readTemperature(); // offset is handled internally
|
||||
temperature = device->readTemperature(); // offset is handled internally
|
||||
temperature = CToKelvin(temperature);
|
||||
LOG_DEBUG(GwLog::DEBUG, "%s measure temp=%2.1f", prefix.c_str(), temperature);
|
||||
sendN2kTemperature(api, *this, temperature, counterId);
|
||||
@@ -88,6 +92,10 @@ class BME280Config : public IICSensorBase{
|
||||
LOG_DEBUG(GwLog::DEBUG, "%s read humid=%02.0f", prefix.c_str(), humidity);
|
||||
sendN2kHumidity(api, *this, humidity, counterId);
|
||||
}
|
||||
if (tmAct || prAct || (huAct && sensorId == 0x60))
|
||||
{
|
||||
sendN2kEnvironmentalParameters(api, *this, temperature, humidity, computed,counterId);
|
||||
}
|
||||
}
|
||||
#define CFG280(prefix) \
|
||||
CFG_GET(prAct,prefix); \
|
||||
@@ -132,6 +140,7 @@ class BME280Config : public IICSensorBase{
|
||||
busId = 2;
|
||||
addr = 0x77;
|
||||
CFG280(BME28022);
|
||||
ok=true;
|
||||
}
|
||||
intv *= 1000;
|
||||
}
|
||||
|
||||
182
lib/iictask/GwBMP280.cpp
Normal file
182
lib/iictask/GwBMP280.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
#include "GwBMP280.h"
|
||||
#ifdef _GWIIC
|
||||
#if defined(GWBMP280) || defined(GWBMP28011) || defined(GWBMP28012)|| defined(GWBMP28021)|| defined(GWBMP28022)
|
||||
#define _GWBMP280
|
||||
#endif
|
||||
#else
|
||||
#undef _GWBMP280
|
||||
#undef GWBMP280
|
||||
#undef GWBMP28011
|
||||
#undef GWBMP28012
|
||||
#undef GWBMP28021
|
||||
#undef GWBMP28022
|
||||
#endif
|
||||
#ifdef _GWBMP280
|
||||
#include <Adafruit_BMP280.h>
|
||||
#endif
|
||||
#ifdef _GWBMP280
|
||||
#define TYPE "BMP280"
|
||||
#define PRFX1 TYPE "11"
|
||||
#define PRFX2 TYPE "12"
|
||||
#define PRFX3 TYPE "21"
|
||||
#define PRFX4 TYPE "22"
|
||||
class BMP280Config : public IICSensorBase{
|
||||
public:
|
||||
bool prAct=true;
|
||||
bool tmAct=true;
|
||||
tN2kTempSource tmSrc=tN2kTempSource::N2kts_InsideTemperature;
|
||||
tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric;
|
||||
tN2kHumiditySource huSrc=tN2kHumiditySource::N2khs_Undef;
|
||||
String tmNam="Temperature";
|
||||
String prNam="Pressure";
|
||||
float tmOff=0;
|
||||
float prOff=0;
|
||||
Adafruit_BMP280 *device=nullptr;
|
||||
uint32_t sensorId=-1;
|
||||
BMP280Config(GwApi * api, const String &prfx):SensorBase(TYPE,api,prfx){
|
||||
}
|
||||
virtual bool isActive(){return prAct||tmAct;}
|
||||
virtual bool initDevice(GwApi *api,TwoWire *wire){
|
||||
GwLog *logger=api->getLogger();
|
||||
device= new Adafruit_BMP280(wire);
|
||||
if (! device->begin(addr)){
|
||||
LOG_DEBUG(GwLog::ERROR,"unable to initialize %s at %d",prefix.c_str(),addr);
|
||||
delete device;
|
||||
device=nullptr;
|
||||
return false;
|
||||
}
|
||||
sensorId=device->sensorID();
|
||||
LOG_DEBUG(GwLog::LOG, "initialized %s at %d, sensorId 0x%x", prefix.c_str(), addr, sensorId);
|
||||
return (sensorId == 0x56 || sensorId == 0x57 || sensorId == 0x58)?true:false;
|
||||
}
|
||||
virtual bool preinit(GwApi * api){
|
||||
GwLog *logger=api->getLogger();
|
||||
LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str());
|
||||
api->addCapability(prefix,"true");
|
||||
addPressureXdr(api,*this);
|
||||
addTempXdr(api,*this);
|
||||
return isActive();
|
||||
}
|
||||
virtual void measure(GwApi *api, TwoWire *wire, int counterId)
|
||||
{
|
||||
if (!device)
|
||||
return;
|
||||
GwLog *logger = api->getLogger();
|
||||
float pressure = N2kDoubleNA;
|
||||
float temperature = N2kDoubleNA;
|
||||
float humidity = N2kDoubleNA;
|
||||
float computed = N2kDoubleNA;
|
||||
if (prAct)
|
||||
{
|
||||
pressure = device->readPressure();
|
||||
computed = pressure + prOff;
|
||||
LOG_DEBUG(GwLog::DEBUG, "%s measure %2.0fPa, computed %2.0fPa", prefix.c_str(), pressure, computed);
|
||||
sendN2kPressure(api, *this, computed, counterId);
|
||||
}
|
||||
if (tmAct)
|
||||
{
|
||||
temperature = device->readTemperature(); // offset is handled internally
|
||||
temperature = CToKelvin(temperature);
|
||||
LOG_DEBUG(GwLog::DEBUG, "%s measure temp=%2.1f", prefix.c_str(), temperature);
|
||||
sendN2kTemperature(api, *this, temperature, counterId);
|
||||
}
|
||||
if (tmAct || prAct )
|
||||
{
|
||||
sendN2kEnvironmentalParameters(api, *this, temperature, humidity, computed,counterId);
|
||||
}
|
||||
}
|
||||
#define CFGBMP280(prefix) \
|
||||
CFG_GET(prAct,prefix); \
|
||||
CFG_GET(tmAct,prefix);\
|
||||
CFG_GET(tmSrc,prefix);\
|
||||
CFG_GET(iid,prefix);\
|
||||
CFG_GET(intv,prefix);\
|
||||
CFG_GET(tmNam,prefix);\
|
||||
CFG_GET(prNam,prefix);\
|
||||
CFG_GET(tmOff,prefix);\
|
||||
CFG_GET(prOff,prefix);
|
||||
|
||||
virtual void readConfig(GwConfigHandler *cfg) override
|
||||
{
|
||||
if (prefix == PRFX1)
|
||||
{
|
||||
busId = 1;
|
||||
addr = 0x76;
|
||||
CFGBMP280(BMP28011);
|
||||
ok=true;
|
||||
}
|
||||
if (prefix == PRFX2)
|
||||
{
|
||||
busId = 1;
|
||||
addr = 0x77;
|
||||
CFGBMP280(BMP28012);
|
||||
ok=true;
|
||||
}
|
||||
if (prefix == PRFX3)
|
||||
{
|
||||
busId = 2;
|
||||
addr = 0x76;
|
||||
CFGBMP280(BMP28021);
|
||||
ok=true;
|
||||
}
|
||||
if (prefix == PRFX4)
|
||||
{
|
||||
busId = 2;
|
||||
addr = 0x77;
|
||||
CFGBMP280(BMP28022);
|
||||
ok=true;
|
||||
}
|
||||
intv *= 1000;
|
||||
}
|
||||
};
|
||||
|
||||
static IICSensorBase::Creator creator([](GwApi *api, const String &prfx){
|
||||
return new BMP280Config(api,prfx);
|
||||
});
|
||||
IICSensorBase::Creator registerBMP280(GwApi *api,IICSensorList &sensors){
|
||||
#if defined(GWBMP280) || defined(GWBMP28011)
|
||||
{
|
||||
auto *cfg=creator(api,PRFX1);
|
||||
//BMP280Config *cfg=new BMP280Config(api,PRFX1);
|
||||
sensors.add(api,cfg);
|
||||
CHECK_IIC1();
|
||||
#pragma message "GWBMP28011 defined"
|
||||
}
|
||||
#endif
|
||||
#if defined(GWBMP28012)
|
||||
{
|
||||
auto *cfg=creator(api,PRFX2);
|
||||
//BMP280Config *cfg=new BMP280Config(api,PRFX2);
|
||||
sensors.add(api,cfg);
|
||||
CHECK_IIC1();
|
||||
#pragma message "GWBMP28012 defined"
|
||||
}
|
||||
#endif
|
||||
#if defined(GWBMP28021)
|
||||
{
|
||||
auto *cfg=creator(api,PRFX3);
|
||||
//BMP280Config *cfg=new BMP280Config(api,PRFX3);
|
||||
sensors.add(api,cfg);
|
||||
CHECK_IIC2();
|
||||
#pragma message "GWBMP28021 defined"
|
||||
}
|
||||
#endif
|
||||
#if defined(GWBMP28022)
|
||||
{
|
||||
auto *cfg=creator(api,PRFX4);
|
||||
//BMP280Config *cfg=new BMP280Config(api,PRFX4);
|
||||
sensors.add(api,cfg);
|
||||
CHECK_IIC1();
|
||||
#pragma message "GWBMP28022 defined"
|
||||
}
|
||||
#endif
|
||||
return creator;
|
||||
}
|
||||
#else
|
||||
IICSensorBase::Creator registerBMP280(GwApi *api,IICSensorList &sensors){
|
||||
return IICSensorBase::Creator();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
6
lib/iictask/GwBMP280.h
Normal file
6
lib/iictask/GwBMP280.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef _GWBMP280_H
|
||||
#define _GWBMP280_H
|
||||
#include "GwIicSensors.h"
|
||||
IICSensorBase::Creator registerBMP280(GwApi *api,IICSensorList &sensors);
|
||||
#endif
|
||||
|
||||
@@ -102,6 +102,15 @@ void sendN2kTemperature(GwApi *api,CFG &cfg,double value, int counterId){
|
||||
api->increment(counterId,cfg.prefix+String("temp"));
|
||||
}
|
||||
|
||||
template <class CFG>
|
||||
void sendN2kEnvironmentalParameters(GwApi *api,CFG &cfg,double tmValue, double huValue, double prValue, int counterId){
|
||||
tN2kMsg msg;
|
||||
SetN2kEnvironmentalParameters(msg,1,cfg.tmSrc,tmValue,cfg.huSrc,huValue,prValue);
|
||||
api->sendN2kMessage(msg);
|
||||
api->increment(counterId,cfg.prefix+String("hum"));
|
||||
api->increment(counterId,cfg.prefix+String("press"));
|
||||
api->increment(counterId,cfg.prefix+String("temp"));
|
||||
}
|
||||
|
||||
#ifndef _GWI_IIC1
|
||||
#define CHECK_IIC1() checkDef(GWIIC_SCL,GWIIC_SDA)
|
||||
@@ -114,4 +123,4 @@ void sendN2kTemperature(GwApi *api,CFG &cfg,double value, int counterId){
|
||||
#define CHECK_IIC2()
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -21,6 +21,7 @@ static std::vector<IICGrove> iicGroveList;
|
||||
#include "GwIicSensors.h"
|
||||
#include "GwHardware.h"
|
||||
#include "GwBME280.h"
|
||||
#include "GwBMP280.h"
|
||||
#include "GwQMP6988.h"
|
||||
#include "GwSHT3X.h"
|
||||
#include <map>
|
||||
@@ -92,6 +93,7 @@ void initIicTask(GwApi *api){
|
||||
creators.push_back(registerSHT3X(api,sensors));
|
||||
creators.push_back(registerQMP6988(api,sensors));
|
||||
creators.push_back(registerBME280(api,sensors));
|
||||
creators.push_back(registerBMP280(api,sensors));
|
||||
#ifdef _GWI_IIC1
|
||||
addGroveItems(creators,api,sensors,"1",_GWI_IIC1);
|
||||
#endif
|
||||
|
||||
@@ -530,5 +530,204 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"name": "BMP280",
|
||||
"replace": [
|
||||
{
|
||||
"b": "1",
|
||||
"i": "11",
|
||||
"n": "93"
|
||||
},
|
||||
{
|
||||
"b": "1",
|
||||
"i": "12",
|
||||
"n": "92"
|
||||
},
|
||||
{
|
||||
"b": "2",
|
||||
"i": "21",
|
||||
"n": "103"
|
||||
},
|
||||
{
|
||||
"b": "2",
|
||||
"i": "22",
|
||||
"n": "102"
|
||||
}
|
||||
],
|
||||
"children": [
|
||||
{
|
||||
"name": "BMP280$itmAct",
|
||||
"label": "BMP280-$i Temp",
|
||||
"type": "boolean",
|
||||
"default": "true",
|
||||
"description": "Enable the $i. I2C BMP280 temp sensor (bus $b)",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"BMP280$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BMP280$itmSrc",
|
||||
"label": "BMP280-$i Temp Type",
|
||||
"type": "list",
|
||||
"default": "2",
|
||||
"description": "the NMEA2000 source type for the temperature",
|
||||
"list": [
|
||||
{
|
||||
"l": "SeaTemperature",
|
||||
"v": "0"
|
||||
},
|
||||
{
|
||||
"l": "OutsideTemperature",
|
||||
"v": "1"
|
||||
},
|
||||
{
|
||||
"l": "InsideTemperature",
|
||||
"v": "2"
|
||||
},
|
||||
{
|
||||
"l": "EngineRoomTemperature",
|
||||
"v": "3"
|
||||
},
|
||||
{
|
||||
"l": "MainCabinTemperature",
|
||||
"v": "4"
|
||||
},
|
||||
{
|
||||
"l": "LiveWellTemperature",
|
||||
"v": "5"
|
||||
},
|
||||
{
|
||||
"l": "BaitWellTemperature",
|
||||
"v": "6"
|
||||
},
|
||||
{
|
||||
"l": "RefridgerationTemperature",
|
||||
"v": "7"
|
||||
},
|
||||
{
|
||||
"l": "HeatingSystemTemperature",
|
||||
"v": "8"
|
||||
},
|
||||
{
|
||||
"l": "DewPointTemperature",
|
||||
"v": "9"
|
||||
},
|
||||
{
|
||||
"l": "ApparentWindChillTemperature",
|
||||
"v": "10"
|
||||
},
|
||||
{
|
||||
"l": "TheoreticalWindChillTemperature",
|
||||
"v": "11"
|
||||
},
|
||||
{
|
||||
"l": "HeatIndexTemperature",
|
||||
"v": "12"
|
||||
},
|
||||
{
|
||||
"l": "FreezerTemperature",
|
||||
"v": "13"
|
||||
},
|
||||
{
|
||||
"l": "ExhaustGasTemperature",
|
||||
"v": "14"
|
||||
},
|
||||
{
|
||||
"l": "ShaftSealTemperature",
|
||||
"v": "15"
|
||||
}
|
||||
],
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"BMP280$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BMP280$itmOff",
|
||||
"label": "BMP280-$i Temperature Offset",
|
||||
"type": "number",
|
||||
"description": "offset (in °) to be added to the BMP280 temperature measurements",
|
||||
"default": "0",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"BMP280$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BMP280$iprAct",
|
||||
"label": "BMP280-$i Pressure",
|
||||
"type": "boolean",
|
||||
"default": "true",
|
||||
"description": "Enable the $i. I2C BMP280 pressure sensor (bus $b)",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"BMP280$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BMP280$iprOff",
|
||||
"label": "BMP280 Pressure Offset",
|
||||
"type": "number",
|
||||
"description": "offset (in pa) to be added to the BMP280 pressure measurements",
|
||||
"default": "0",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"BMP280$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BMP280$iiid",
|
||||
"label": "BMP280-$i N2K iid",
|
||||
"type": "number",
|
||||
"default": "$n",
|
||||
"description": "the N2K instance id for the BMP280 Temperature",
|
||||
"category": "iicsensors$b",
|
||||
"min": 0,
|
||||
"max": 253,
|
||||
"check": "checkMinMax",
|
||||
"capabilities": {
|
||||
"BMP280$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BMP280$iintv",
|
||||
"label": "BMP280-$i Interval",
|
||||
"type": "number",
|
||||
"default": 2,
|
||||
"description": "Interval(s) to query BME280 Temperature (1...300)",
|
||||
"category": "iicsensors$b",
|
||||
"min": 1,
|
||||
"max": 300,
|
||||
"check": "checkMinMax",
|
||||
"capabilities": {
|
||||
"BMP280$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BMP280$itmNam",
|
||||
"label": "BMP280-$i Temp XDR",
|
||||
"type": "String",
|
||||
"default": "BTemp$i",
|
||||
"description": "set the XDR transducer name for the BMP280 Temperature, leave empty to disable NMEA0183 XDR ",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"BMP280$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BMP280$iprNam",
|
||||
"label": "BMP280-$i Pressure XDR",
|
||||
"type": "String",
|
||||
"default": "BPressure$i",
|
||||
"description": "set the XDR transducer name for the BMP280 Pressure, leave empty to disable NMEA0183 XDR",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"BMP280$i": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -11,6 +11,17 @@ build_flags=
|
||||
-D M5_CAN_KIT
|
||||
${env.build_flags}
|
||||
|
||||
[env:m5stack-atom-env4]
|
||||
extends = sensors
|
||||
board = m5stack-atom
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
${sensors.lib_deps}
|
||||
build_flags=
|
||||
-D M5_ENV4
|
||||
-D M5_CAN_KIT
|
||||
${env.build_flags}
|
||||
|
||||
[env:m5stack-atom-bme280]
|
||||
extends = sensors
|
||||
board = m5stack-atom
|
||||
@@ -35,3 +46,15 @@ build_flags=
|
||||
-D M5_GROOVEIIC
|
||||
-D M5_CAN_KIT
|
||||
${env.build_flags}
|
||||
|
||||
[env:m5stack-atom-envbps]
|
||||
extends = sensors
|
||||
board = m5stack-atom
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
${sensors.lib_deps}
|
||||
build_flags=
|
||||
-D GWBMP280G1
|
||||
-D M5_GROOVEIIC
|
||||
-D M5_CAN_KIT
|
||||
${env.build_flags}
|
||||
|
||||
@@ -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
|
||||
@@ -860,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 ||
|
||||
|
||||
@@ -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,7 +340,23 @@ public:
|
||||
virtual void setCalibrationValue(const String &name, double value){
|
||||
api->setCalibrationValue(name,value);
|
||||
}
|
||||
|
||||
virtual bool handleWebRequest(const String &url, AsyncWebServerRequest *req)
|
||||
{
|
||||
GwApi::HandlerFunction handler;
|
||||
{
|
||||
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;
|
||||
}
|
||||
handler = it->second;
|
||||
}
|
||||
if (handler)
|
||||
handler(req);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
GwUserCode::GwUserCode(GwApiInternal *api,SemaphoreHandle_t *mainLock){
|
||||
@@ -404,4 +425,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
|
||||
Reference in New Issue
Block a user