Merge branch 'master' into boards

This commit is contained in:
free-x 2021-10-29 07:33:09 +02:00 committed by GitHub
commit 62baaa33f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 629 additions and 388 deletions

2
.gitignore vendored
View File

@ -3,4 +3,4 @@
.vscode/c_cpp_properties.json .vscode/c_cpp_properties.json
.vscode/launch.json .vscode/launch.json
.vscode/ipch .vscode/ipch
web/*gz generated/*

View File

@ -2,20 +2,82 @@ print("running extra...")
import gzip import gzip
import shutil import shutil
import os import os
FILES=['web/index.html'] import sys
import inspect
import json
GEN_DIR='generated'
CFG_FILE='web/config.json'
FILES=['web/index.html',CFG_FILE]
CFG_INCLUDE='GwConfigDefinitions.h'
def compressFile(inFile): def basePath():
outfile=inFile+".gz" #see: https://stackoverflow.com/questions/16771894/python-nameerror-global-name-file-is-not-defined
return os.path.dirname(inspect.getfile(lambda: None))
def outPath():
return os.path.join(basePath(),GEN_DIR)
def checkDir():
dn=outPath()
if not os.path.exists(dn):
os.makedirs(dn)
if not os.path.isdir(dn):
print("unable to create %s"%dn)
return False
return True
def isCurrent(infile,outfile):
if os.path.exists(outfile): if os.path.exists(outfile):
otime=os.path.getmtime(outfile) otime=os.path.getmtime(outfile)
itime=os.path.getmtime(inFile) itime=os.path.getmtime(infile)
if (otime >= itime): if (otime >= itime):
print("%s is newer then %s, no need to recreate"%(outfile,inFile)) print("%s is newer then %s, no need to recreate"%(outfile,infile))
return return True
return False
def compressFile(inFile):
outfile=os.path.basename(inFile)+".gz"
inFile=os.path.join(basePath(),inFile)
outfile=os.path.join(outPath(),outfile)
if isCurrent(inFile,outfile):
return
with open(inFile, 'rb') as f_in: with open(inFile, 'rb') as f_in:
with gzip.open(outfile, 'wb') as f_out: with gzip.open(outfile, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out) shutil.copyfileobj(f_in, f_out)
def generateCfg():
outfile=os.path.join(outPath(),CFG_INCLUDE)
infile=os.path.join(basePath(),CFG_FILE)
if isCurrent(infile,outfile):
return
print("creating %s"%CFG_INCLUDE)
with open(CFG_FILE,'rb') as ch:
config=json.load(ch)
with open(outfile,'w') as oh:
oh.write("//generated from %s\n"%CFG_FILE)
oh.write('#include "GwConfigItem.h"\n')
l=len(config)
oh.write('class GwConfigDefinitions{\n')
oh.write(' public:\n')
oh.write(' int getNumConfig() const{return %d;}\n'%(l))
for item in config:
n=item.get('name')
if n is None:
continue
oh.write(' const String %s=F("%s");\n'%(n,n))
oh.write(' protected:\n')
oh.write(' GwConfigItem *configs[%d]={\n'%(l))
first=True
for item in config:
if not first:
oh.write(',\n')
first=False
oh.write(" new GwConfigItem(%s,\"%s\")"%(item.get('name'),item.get('default')))
oh.write('};\n')
oh.write('};\n')
oh.close()
if not checkDir():
sys.exit(1)
for f in FILES: for f in FILES:
print("compressing %s"%f) print("compressing %s"%f)
compressFile(f) compressFile(f)
generateCfg()

View File

@ -54,7 +54,7 @@ GwConfigInterface * GwConfigHandler::getConfigItem(const String name, bool dummy
return &dummyConfig; return &dummyConfig;
} }
#define PREF_NAME "gwprefs" #define PREF_NAME "gwprefs"
GwConfigHandler::GwConfigHandler(GwLog *logger){ GwConfigHandler::GwConfigHandler(GwLog *logger): GwConfigDefinitions(){
this->logger=logger; this->logger=logger;
} }
bool GwConfigHandler::loadConfig(){ bool GwConfigHandler::loadConfig(){

View File

@ -3,74 +3,16 @@
#include <Arduino.h> #include <Arduino.h>
#include <Preferences.h> #include <Preferences.h>
#include "GwLog.h" #include "GwLog.h"
#include "GwConfigItem.h"
class GwConfigInterface{ #include "GwConfigDefinitions.h"
public:
virtual String asString() const=0;
virtual const char * asCString() const =0;
virtual bool asBoolean() const = 0;
virtual int asInt() const = 0;
};
class GwConfigItem: public GwConfigInterface{
private:
String name;
String initialValue;
String value;
public:
GwConfigItem(const String &name, const String initialValue){
this->name=name;
this->initialValue=initialValue;
this->value=initialValue;
}
virtual String asString() const{
return value;
}
virtual const char * asCString() const{
return value.c_str();
};
virtual void fromString(const String v){
value=v;
};
virtual bool asBoolean() const{
return strcasecmp(value.c_str(),"true") == 0;
}
virtual int asInt() const{
return (int)value.toInt();
}
String getName() const{
return name;
}
virtual void reset(){
value=initialValue;
}
bool changed() const{
return value != initialValue;
}
String getDefault() const {
return initialValue;
}
};
class GwConfigHandler{ class GwConfigHandler: public GwConfigDefinitions{
private: private:
Preferences prefs; Preferences prefs;
GwLog *logger; GwLog *logger;
public: public:
public: public:
const String sendUsb=F("sendUsb");
const String receiveUsb=F("receiveUsb");
const String wifiClient=F("wifiClient");
const String wifiPass=F("wifiPass");
const String wifiSSID=F("wifiSSID");
const String serverPort=F("serverPort");
const String maxClients=F("maxClients");
const String sendTCP=F("sendTCP");
const String readTCP=F("receiveTCP");
const String sendSeasmart=F("sendSeasmart");
const String usbBaud=F("usbBaud");
const String systemName=F("systemName");
const String stopApTime=F("stopApTime");
GwConfigHandler(GwLog *logger); GwConfigHandler(GwLog *logger);
bool loadConfig(); bool loadConfig();
bool saveConfig(); bool saveConfig();
@ -85,23 +27,5 @@ class GwConfigHandler{
GwConfigItem * findConfig(const String name, bool dummy=false); GwConfigItem * findConfig(const String name, bool dummy=false);
GwConfigInterface * getConfigItem(const String name, bool dummy=false) const; GwConfigInterface * getConfigItem(const String name, bool dummy=false) const;
private: private:
GwConfigItem* configs[13]={
new GwConfigItem(sendUsb,"true"),
new GwConfigItem (receiveUsb,"false"),
new GwConfigItem (wifiClient,"false"),
new GwConfigItem (wifiSSID,""),
new GwConfigItem (wifiPass,""),
new GwConfigItem (serverPort,"2222"),
new GwConfigItem (maxClients, "10"),
new GwConfigItem (sendTCP,"true"),
new GwConfigItem (readTCP,"true"),
new GwConfigItem (sendSeasmart,"false"),
new GwConfigItem (usbBaud,"115200"),
new GwConfigItem (systemName,"ESP32NMEA2K"),
new GwConfigItem (stopApTime,"0")
};
int getNumConfig() const{
return 13;
}
}; };
#endif #endif

52
lib/config/GwConfigItem.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef _GWCONFIGITEM_H
#define _GWCONFIGITEM_H
#include "WString.h"
class GwConfigInterface{
public:
virtual String asString() const=0;
virtual const char * asCString() const =0;
virtual bool asBoolean() const = 0;
virtual int asInt() const = 0;
};
class GwConfigItem: public GwConfigInterface{
private:
String name;
String initialValue;
String value;
public:
GwConfigItem(const String &name, const String initialValue){
this->name=name;
this->initialValue=initialValue;
this->value=initialValue;
}
virtual String asString() const{
return value;
}
virtual const char * asCString() const{
return value.c_str();
};
virtual void fromString(const String v){
value=v;
};
virtual bool asBoolean() const{
return strcasecmp(value.c_str(),"true") == 0;
}
virtual int asInt() const{
return (int)value.toInt();
}
String getName() const{
return name;
}
virtual void reset(){
value=initialValue;
}
bool changed() const{
return value != initialValue;
}
String getDefault() const {
return initialValue;
}
};
#endif

View File

@ -31,10 +31,11 @@
N2kDataToNMEA0183::N2kDataToNMEA0183(GwLog * logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183) : tNMEA2000::tMsgHandler(0,NMEA2000){ N2kDataToNMEA0183::N2kDataToNMEA0183(GwLog * logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183, int id)
: tNMEA2000::tMsgHandler(0,NMEA2000){
SendNMEA0183MessageCallback=0; SendNMEA0183MessageCallback=0;
pNMEA0183=NMEA0183; pNMEA0183=NMEA0183;
sourceId=id;
} }
@ -46,10 +47,11 @@ void N2kDataToNMEA0183::loop() {
//***************************************************************************** //*****************************************************************************
void N2kDataToNMEA0183::SendMessage(const tNMEA0183Msg &NMEA0183Msg) { void N2kDataToNMEA0183::SendMessage(const tNMEA0183Msg &NMEA0183Msg) {
if ( pNMEA0183 != 0 ) pNMEA0183->SendMessage(NMEA0183Msg); if ( pNMEA0183 != 0 ) pNMEA0183->SendMessage(NMEA0183Msg);
if ( SendNMEA0183MessageCallback != 0 ) SendNMEA0183MessageCallback(NMEA0183Msg); if ( SendNMEA0183MessageCallback != 0 ) SendNMEA0183MessageCallback(NMEA0183Msg, sourceId);
} }
N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183){ N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000,
return new N2kToNMEA0183Functions(logger,boatData,NMEA2000,NMEA0183); tNMEA0183 *NMEA0183, int sourceId){
return new N2kToNMEA0183Functions(logger,boatData,NMEA2000,NMEA0183, sourceId);
} }
//***************************************************************************** //*****************************************************************************

View File

@ -33,22 +33,19 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
class N2kDataToNMEA0183 : public tNMEA2000::tMsgHandler class N2kDataToNMEA0183 : public tNMEA2000::tMsgHandler
{ {
public: public:
using tSendNMEA0183MessageCallback = void (*)(const tNMEA0183Msg &NMEA0183Msg); using tSendNMEA0183MessageCallback = void (*)(const tNMEA0183Msg &NMEA0183Msg, int id);
protected: protected:
GwLog *logger; GwLog *logger;
GwBoatData *boatData; GwBoatData *boatData;
tNMEA0183 *pNMEA0183; tNMEA0183 *pNMEA0183;
int sourceId;
tSendNMEA0183MessageCallback SendNMEA0183MessageCallback; tSendNMEA0183MessageCallback SendNMEA0183MessageCallback;
void SendMessage(const tNMEA0183Msg &NMEA0183Msg); void SendMessage(const tNMEA0183Msg &NMEA0183Msg);
N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183, int sourceId);
N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183);
public: public:
static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183); static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183, int sourceId);
virtual void HandleMsg(const tN2kMsg &N2kMsg) = 0; virtual void HandleMsg(const tN2kMsg &N2kMsg) = 0;
void SetSendNMEA0183MessageCallback(tSendNMEA0183MessageCallback _SendNMEA0183MessageCallback) void SetSendNMEA0183MessageCallback(tSendNMEA0183MessageCallback _SendNMEA0183MessageCallback)
{ {

View File

@ -849,7 +849,8 @@ private:
} }
public: public:
N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183) : N2kDataToNMEA0183(logger, boatData, NMEA2000, NMEA0183) N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183, int sourceId)
: N2kDataToNMEA0183(logger, boatData, NMEA2000, NMEA0183,sourceId)
{ {
LastPosSend = 0; LastPosSend = 0;
lastLoopTime = 0; lastLoopTime = 0;

View File

@ -6,10 +6,9 @@ void GwBuffer::lp(const char *fkt, int p)
fkt, buffer, offset(writePointer), offset(readPointer), usedSpace(), freeSpace(), p); fkt, buffer, offset(writePointer), offset(readPointer), usedSpace(), freeSpace(), p);
} }
GwBuffer::GwBuffer(GwLog *logger,size_t bufferSize, bool rotate) GwBuffer::GwBuffer(GwLog *logger,size_t bufferSize)
{ {
this->logger = logger; this->logger = logger;
this->rotate=rotate;
this->bufferSize=bufferSize; this->bufferSize=bufferSize;
this->buffer=new uint8_t[bufferSize]; this->buffer=new uint8_t[bufferSize];
writePointer = buffer; writePointer = buffer;
@ -26,67 +25,45 @@ void GwBuffer::reset()
} }
size_t GwBuffer::freeSpace() size_t GwBuffer::freeSpace()
{ {
if (! rotate){ if (readPointer <= writePointer){
return bufferSize-offset(writePointer)-1; return readPointer+bufferSize-writePointer-1;
} }
if (readPointer < writePointer)
{
size_t rt = bufferSize - offset(writePointer) - 1 + offset(readPointer);
return rt;
}
if (readPointer == writePointer)
return bufferSize - 1;
return readPointer - writePointer - 1; return readPointer - writePointer - 1;
} }
size_t GwBuffer::usedSpace() size_t GwBuffer::usedSpace()
{ {
if (readPointer == writePointer) if (readPointer <= writePointer)
return 0;
if (readPointer < writePointer)
return writePointer - readPointer; return writePointer - readPointer;
return bufferSize - offset(readPointer) + offset(writePointer); return writePointer+bufferSize-readPointer;
} }
size_t GwBuffer::addData(const uint8_t *data, size_t len) size_t GwBuffer::addData(const uint8_t *data, size_t len, bool addPartial)
{ {
lp("addDataE", len); lp("addDataE", len);
if (len == 0) if (len == 0)
return 0; return 0;
if (freeSpace() < len && rotate) if (freeSpace() < len && !addPartial)
//in rotating mode (send buffer)
//we only fill in a message if it fit's completely
return 0; return 0;
size_t written = 0; size_t written = 0;
if (writePointer >= readPointer) for (int i=0;i<2;i++){
{ size_t currentFree=freeSpace();
written = bufferSize - offset(writePointer) - 1; size_t toWrite=len-written;
bool canRotate=rotate && offset(readPointer) > 0; if (toWrite > currentFree) toWrite=currentFree;
if (canRotate) written++; //we can also fill the last byte if (toWrite > (bufferSize - offset(writePointer))) {
if (written > len) toWrite=bufferSize - offset(writePointer);
written = len;
if (written)
{
memcpy(writePointer, data, written);
len -= written;
data += written;
writePointer += written;
if (offset(writePointer) > (bufferSize - 1))
writePointer = buffer;
} }
lp("addData1", written); if (toWrite != 0){
if (len <= 0) memcpy(writePointer, data, toWrite);
{ written+=toWrite;
return written; data += toWrite;
writePointer += toWrite;
if (offset(writePointer) >= bufferSize){
writePointer -= bufferSize;
}
} }
if (! rotate) return written; lp("addData1", toWrite);
} }
//now we have the write pointer before the read pointer lp("addData2", written);
int maxLen=readPointer-writePointer-1; return written;
if (maxLen <= 0) return written;
if (len < maxLen) maxLen=len;
memcpy(writePointer, data, maxLen);
writePointer += maxLen;
lp("addData2", maxLen);
return maxLen + written;
} }
/** /**
* write some data to the buffer writer * write some data to the buffer writer
@ -94,92 +71,66 @@ size_t GwBuffer::addData(const uint8_t *data, size_t len)
*/ */
GwBuffer::WriteStatus GwBuffer::fetchData(GwBufferWriter *writer, int maxLen,bool errorIf0 ) GwBuffer::WriteStatus GwBuffer::fetchData(GwBufferWriter *writer, int maxLen,bool errorIf0 )
{ {
lp("fetchDataE"); lp("fetchDataE",maxLen);
size_t len = usedSpace(); size_t len = usedSpace();
if (maxLen > 0 && len > maxLen) len=maxLen; if (maxLen > 0 && len > maxLen) len=maxLen;
if (len == 0) if (len == 0){
lp("fetchData0",maxLen);
writer->done();
return OK; return OK;
}
size_t written = 0; size_t written = 0;
size_t plen = len; for (int i=0;i<2;i++){
if (writePointer < readPointer) size_t currentUsed=usedSpace();
{ size_t toWrite=len-written;
//we need to write from readPointer till end and then till writePointer-1 if (toWrite > currentUsed) toWrite=currentUsed;
plen = bufferSize - offset(readPointer) - 1; if (toWrite > (bufferSize - offset(readPointer))) {
int rt = writer->write(readPointer, plen); toWrite=bufferSize - offset(readPointer);
lp("fetchData1", rt);
if (rt < 0)
{
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns error %d", rt);
return ERROR;
} }
if (rt > plen) lp("fetchData1", toWrite);
if (toWrite > 0)
{ {
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write too many bytes(1) %d", rt); int rt = writer->write(readPointer, toWrite);
return ERROR; lp("fetchData2", rt);
if (rt < 0)
{
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns error %d", rt);
writer->done();
return ERROR;
}
if (rt > toWrite)
{
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write too many bytes(1) %d", rt);
writer->done();
return ERROR;
}
readPointer += rt;
if (offset(readPointer) >= bufferSize)
readPointer -= bufferSize;
written += rt;
if (rt == 0) break; //no need to try again
} }
if (rt == 0)
{
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns 0 (1)");
return (errorIf0 ? ERROR : AGAIN);
}
readPointer += rt;
if (offset(readPointer) > (bufferSize - 1))
readPointer = buffer;
if (rt < plen)
return AGAIN;
if (plen >= len)
return OK;
len -= rt;
written += rt;
//next part - readPointer should be at buffer now
} }
plen = writePointer - readPointer; writer->done();
if (plen == 0) if (written == 0){
return OK;
int rt = writer->write(readPointer, plen);
lp("fetchData2", rt);
if (rt < 0)
{
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns error %d", rt);
return ERROR;
}
if (rt == 0)
{
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns 0 (1)");
return (errorIf0 ? ERROR : AGAIN); return (errorIf0 ? ERROR : AGAIN);
} }
if (rt > plen) return (written == len)?OK:AGAIN;
{
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write too many bytes(2)");
return ERROR;
}
readPointer += rt;
if (offset(readPointer) > (bufferSize - 1))
readPointer = buffer;
lp("fetchData3");
written += rt;
if (written < len)
return AGAIN;
return OK;
} }
int GwBuffer::findChar(char x){ int GwBuffer::findChar(char x){
lp("findChar",x); lp("findChar",x);
int offset=0; int of=0;
uint8_t *p; uint8_t *p;
for (p=readPointer; p != writePointer && p < (buffer+bufferSize);p++){ for (p=readPointer; of < usedSpace();p++){
if (*p == x) return offset; if (offset(p) >= bufferSize) p -=bufferSize;
offset++; if (*p == x) {
} lp("findChar1",of);
if (p >= (buffer+bufferSize)){ return of;
//we reached the end of the buffer without "hitting" the write pointer
//so we can start from the beginning if rotating...
if (! rotate) return -1;
for (p=buffer;p < writePointer && p < (buffer+bufferSize);p++){
if (*p == x) return offset;
offset++;
} }
of++;
} }
lp("findChar2");
return -1; return -1;
} }
@ -193,22 +144,5 @@ GwBuffer::WriteStatus GwBuffer::fetchMessage(GwBufferWriter *writer,char delimit
} }
return AGAIN; return AGAIN;
} }
if (! rotate){
//in a non rotating buffer we discard the found message
//and copy the remain to the start
int len=pos+1;
int wr=writer->write(readPointer,len);
//in any case discard the data now
int remain=usedSpace()-len;
if (remain > 0){
memcpy(buffer,readPointer+len,remain);
readPointer=buffer;
writePointer=buffer+remain;
}
else{
reset();
}
return (wr == len)?OK:ERROR;
}
return fetchData(writer,pos+1,true); return fetchData(writer,pos+1,true);
} }

View File

@ -8,6 +8,7 @@ class GwBufferWriter{
public: public:
int id=0; //can be set be users int id=0; //can be set be users
virtual int write(const uint8_t *buffer,size_t len)=0; virtual int write(const uint8_t *buffer,size_t len)=0;
virtual void done(){}
virtual ~GwBufferWriter(){}; virtual ~GwBufferWriter(){};
}; };
@ -18,7 +19,8 @@ class GwBufferWriter{
*/ */
class GwBuffer{ class GwBuffer{
public: public:
static const size_t BUFFER_SIZE=1620; // app. 20 NMEA messages static const size_t TX_BUFFER_SIZE=1620; // app. 20 NMEA messages
static const size_t RX_BUFFER_SIZE=200; // enough for 1 NMEA message...
typedef enum { typedef enum {
OK, OK,
ERROR, ERROR,
@ -26,7 +28,6 @@ class GwBuffer{
} WriteStatus; } WriteStatus;
private: private:
size_t bufferSize; size_t bufferSize;
bool rotate;
uint8_t *buffer; uint8_t *buffer;
uint8_t *writePointer; uint8_t *writePointer;
uint8_t *readPointer; uint8_t *readPointer;
@ -40,12 +41,12 @@ class GwBuffer{
*/ */
int findChar(char x); int findChar(char x);
public: public:
GwBuffer(GwLog *logger,size_t bufferSize,bool rotate=true); GwBuffer(GwLog *logger,size_t bufferSize);
~GwBuffer(); ~GwBuffer();
void reset(); void reset();
size_t freeSpace(); size_t freeSpace();
size_t usedSpace(); size_t usedSpace();
size_t addData(const uint8_t *data,size_t len); size_t addData(const uint8_t *data,size_t len,bool addPartial=false);
/** /**
* write some data to the buffer writer * write some data to the buffer writer
* return an error if the buffer writer returned < 0 * return an error if the buffer writer returned < 0

View File

@ -13,17 +13,24 @@ class SerialWriter : public GwBufferWriter{
}; };
GwSerial::GwSerial(GwLog *logger, uart_port_t num) GwSerial::GwSerial(GwLog *logger, uart_port_t num, int id,bool allowRead)
{ {
this->id=id;
this->logger = logger; this->logger = logger;
this->num = num; this->num = num;
this->buffer = new GwBuffer(logger,1600); this->buffer = new GwBuffer(logger,GwBuffer::TX_BUFFER_SIZE);
this->writer = new SerialWriter(num); this->writer = new SerialWriter(num);
this->allowRead=allowRead;
if (allowRead){
this->readBuffer=new GwBuffer(logger, GwBuffer::RX_BUFFER_SIZE);
}
} }
GwSerial::~GwSerial() GwSerial::~GwSerial()
{ {
delete buffer; delete buffer;
delete writer; delete writer;
if (readBuffer) delete readBuffer;
} }
int GwSerial::setup(int baud, int rxpin, int txpin) int GwSerial::setup(int baud, int rxpin, int txpin)
{ {
@ -54,13 +61,32 @@ int GwSerial::setup(int baud, int rxpin, int txpin)
bool GwSerial::isInitialized() { return initialized; } bool GwSerial::isInitialized() { return initialized; }
size_t GwSerial::enqueue(const uint8_t *data, size_t len) size_t GwSerial::enqueue(const uint8_t *data, size_t len)
{ {
if (! isInitialized()) return 0;
return buffer->addData(data, len); return buffer->addData(data, len);
} }
GwBuffer::WriteStatus GwSerial::write(){ GwBuffer::WriteStatus GwSerial::write(){
if (! isInitialized()) return GwBuffer::ERROR;
return buffer->fetchData(writer,false); return buffer->fetchData(writer,false);
} }
const char *GwSerial::read(){ void GwSerial::sendToClients(const char *buf,int sourceId){
char buffer[10]; if ( sourceId == id) return;
uart_read_bytes(num,(uint8_t *)(&buffer),10,0); size_t len=strlen(buf);
return NULL; size_t enqueued=enqueue((const uint8_t*)buf,len);
if (enqueued != len){
LOG_DEBUG(GwLog::DEBUG,"GwSerial overflow on channel %d",id);
overflows++;
}
}
void GwSerial::loop(bool handleRead){
write();
if (! handleRead) return;
char buffer[10];
int rt=uart_read_bytes(num,(uint8_t *)(&buffer),10,0);
if (allowRead & rt > 0){
readBuffer->addData((uint8_t *)(&buffer),rt,true);
}
}
bool GwSerial::readMessages(GwBufferWriter *writer){
if (! allowRead) return false;
return readBuffer->fetchMessage(writer,'\n',true) == GwBuffer::OK;
} }

View File

@ -7,19 +7,24 @@ class SerialWriter;
class GwSerial{ class GwSerial{
private: private:
GwBuffer *buffer; GwBuffer *buffer;
GwBuffer *readBuffer; GwBuffer *readBuffer=NULL;
GwLog *logger; GwLog *logger;
SerialWriter *writer; SerialWriter *writer;
uart_port_t num; uart_port_t num;
bool initialized=false; 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);
public: public:
static const int bufferSize=200; static const int bufferSize=200;
GwSerial(GwLog *logger,uart_port_t num); GwSerial(GwLog *logger,uart_port_t num,int id,bool allowRead=true);
~GwSerial(); ~GwSerial();
int setup(int baud,int rxpin,int txpin); int setup(int baud,int rxpin,int txpin);
bool isInitialized(); bool isInitialized();
size_t enqueue(const uint8_t *data, size_t len); void sendToClients(const char *buf,int sourceId);
GwBuffer::WriteStatus write(); void loop(bool handleRead=true);
const char *read(); bool readMessages(GwBufferWriter *writer);
}; };
#endif #endif

View File

@ -3,8 +3,6 @@
#include <lwip/sockets.h> #include <lwip/sockets.h>
#include "GwBuffer.h" #include "GwBuffer.h"
#define WRITE_BUFFER_SIZE 1600
#define READ_BUFFER_SIZE 200
class Writer : public GwBufferWriter{ class Writer : public GwBufferWriter{
public: public:
wiFiClientPtr client; wiFiClientPtr client;
@ -63,9 +61,9 @@ class GwClient{
this->client=client; this->client=client;
this->logger=logger; this->logger=logger;
this->allowRead=allowRead; this->allowRead=allowRead;
buffer=new GwBuffer(logger,WRITE_BUFFER_SIZE); buffer=new GwBuffer(logger,GwBuffer::TX_BUFFER_SIZE);
if (allowRead){ if (allowRead){
readBuffer=new GwBuffer(logger,READ_BUFFER_SIZE,false); readBuffer=new GwBuffer(logger,GwBuffer::RX_BUFFER_SIZE);
} }
overflows=0; overflows=0;
if (client != NULL){ if (client != NULL){
@ -177,7 +175,7 @@ void GwSocketServer::begin(){
MDNS.addService("_nmea-0183","_tcp",config->getInt(config->serverPort)); MDNS.addService("_nmea-0183","_tcp",config->getInt(config->serverPort));
} }
void GwSocketServer::loop() void GwSocketServer::loop(bool handleRead)
{ {
WiFiClient client = server->available(); // listen for incoming clients WiFiClient client = server->available(); // listen for incoming clients
@ -230,7 +228,7 @@ void GwSocketServer::loop()
} }
else else
{ {
client->read(); if (handleRead) client->read();
} }
} }
} }
@ -247,12 +245,6 @@ bool GwSocketServer::readMessages(GwBufferWriter *writer){
} }
void GwSocketServer::sendToClients(const char *buf,int source){ void GwSocketServer::sendToClients(const char *buf,int source){
int len=strlen(buf); int len=strlen(buf);
char buffer[len+2];
memcpy(buffer,buf,len);
buffer[len]=0x0d;
len++;
buffer[len]=0x0a;
len++;
int sourceIndex=source-minId; int sourceIndex=source-minId;
for (int i = 0; i < maxClients; i++) for (int i = 0; i < maxClients; i++)
{ {
@ -260,7 +252,7 @@ void GwSocketServer::sendToClients(const char *buf,int source){
gwClientPtr client = clients[i]; gwClientPtr client = clients[i];
if (! client->hasClient()) continue; if (! client->hasClient()) continue;
if ( client->client->connected() ) { if ( client->client->connected() ) {
bool rt=client->enqueue((uint8_t*)buffer,len); bool rt=client->enqueue((uint8_t*)buf,len);
if (!rt){ if (!rt){
LOG_DEBUG(GwLog::DEBUG,"overflow in send to %s",client->remoteIp.c_str()); LOG_DEBUG(GwLog::DEBUG,"overflow in send to %s",client->remoteIp.c_str());
} }

View File

@ -22,7 +22,7 @@ class GwSocketServer{
GwSocketServer(const GwConfigHandler *config,GwLog *logger,int minId); GwSocketServer(const GwConfigHandler *config,GwLog *logger,int minId);
~GwSocketServer(); ~GwSocketServer();
void begin(); void begin();
void loop(); void loop(bool handleRead=true);
void sendToClients(const char *buf,int sourceId); void sendToClients(const char *buf,int sourceId);
int numClients(); int numClients();
bool readMessages(GwBufferWriter *writer); bool readMessages(GwBufferWriter *writer);

View File

@ -41,7 +41,7 @@ bool GwWifi::connectInternal(){
} }
return false; return false;
} }
#define RETRY_MILLIS 5000 #define RETRY_MILLIS 10000
void GwWifi::loop(){ void GwWifi::loop(){
if (wifiClient->asBoolean() && ! clientConnected()){ if (wifiClient->asBoolean() && ! clientConnected()){
long now=millis(); long now=millis();

View File

@ -18,26 +18,35 @@ lib_deps =
bblanchon/ArduinoJson@^6.18.5 bblanchon/ArduinoJson@^6.18.5
ottowinter/ESPAsyncWebServer-esphome@^2.0.1 ottowinter/ESPAsyncWebServer-esphome@^2.0.1
board_build.embed_files = board_build.embed_files =
web/index.html.gz generated/index.html.gz
generated/config.json.gz
extra_scripts = extra_script.py extra_scripts = extra_script.py
build_flags=
-Igenerated
[env:m5stack-atom] [env:m5stack-atom]
board = m5stack-atom board = m5stack-atom
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
build_flags = -D BOARD_M5ATOM build_flags =
-D BOARD_M5ATOM
${env.build_flags}
upload_port = /dev/esp32 upload_port = /dev/esp32
[env:m5stack-atom-canunit] [env:m5stack-atom-canunit]
board = m5stack-atom board = m5stack-atom
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
build_flags = -D BOARD_M5ATOM_CANUNIT build_flags =
-D BOARD_M5ATOM_CANUNIT
${env.build_flags}
upload_port = /dev/esp32 upload_port = /dev/esp32
[env:m5stickc-atom-canunit] [env:m5stickc-atom-canunit]
board = m5stack-atom board = m5stack-atom
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
build_flags = -D BOARD_M5STICK_CANUNIT -D HAS_RTC -D HAS_M5LCD build_flags =
-D BOARD_M5STICK_CANUNIT -D HAS_RTC -D HAS_M5LCD
${env.build_flags}
upload_port = /dev/esp32 upload_port = /dev/esp32

View File

@ -12,7 +12,7 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#define VERSION "0.1.0" #define VERSION "0.1.2"
#include "GwHardware.h" #include "GwHardware.h"
#include <Arduino.h> #include <Arduino.h>
@ -65,7 +65,7 @@ Preferences preferences; // Nonvolatile storage on ESP32 - To store
bool SendNMEA0183Conversion = true; // Do we send NMEA2000 -> NMEA0183 conversion bool SendNMEA0183Conversion = true; // Do we send NMEA2000 -> NMEA0183 conversion
bool SendSeaSmart = false; // Do we send NMEA2000 messages in SeaSmart format bool SendSeaSmart = false; // Do we send NMEA2000 messages in SeaSmart format
N2kDataToNMEA0183 *nmea0183Converter=N2kDataToNMEA0183::create(&logger, &boatData,&NMEA2000, 0); N2kDataToNMEA0183 *nmea0183Converter=N2kDataToNMEA0183::create(&logger, &boatData,&NMEA2000, 0, N2K_CHANNEL_ID);
// Set the information for other bus devices, which messages we support // Set the information for other bus devices, which messages we support
const unsigned long TransmitMessages[] PROGMEM = {127489L, // Engine dynamic const unsigned long TransmitMessages[] PROGMEM = {127489L, // Engine dynamic
@ -73,7 +73,7 @@ const unsigned long TransmitMessages[] PROGMEM = {127489L, // Engine dynamic
}; };
// Forward declarations // Forward declarations
void HandleNMEA2000Msg(const tN2kMsg &N2kMsg); void HandleNMEA2000Msg(const tN2kMsg &N2kMsg);
void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg); void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg,int id);
AsyncWebServer webserver(80); AsyncWebServer webserver(80);
@ -135,11 +135,39 @@ void handleAsyncWebRequest(AsyncWebServerRequest *request, RequestMessage *msg,
} }
#define JSON_OK "{\"status\":\"OK\"}" #define JSON_OK "{\"status\":\"OK\"}"
//embedded files
extern const uint8_t indexFile[] asm("_binary_web_index_html_gz_start");
extern const uint8_t indexFileEnd[] asm("_binary_web_index_html_gz_end");
extern const uint8_t indexFileLen[] asm("_binary_web_index_html_gz_size");
class EmbeddedFile;
static std::map<String,EmbeddedFile*> embeddedFiles;
class EmbeddedFile {
public:
const uint8_t *start;
int len;
EmbeddedFile(String name,const uint8_t *start,int len){
this->start=start;
this->len=len;
embeddedFiles[name]=this;
}
} ;
#define EMBED_GZ_FILE(fileName, fileExt) \
extern const uint8_t fileName##_##fileExt##_File[] asm("_binary_generated_" #fileName "_" #fileExt "_gz_start"); \
extern const uint8_t fileName##_##fileExt##_FileLen[] asm("_binary_generated_" #fileName "_" #fileExt "_gz_size"); \
const EmbeddedFile fileName##_##fileExt##_Config(#fileName "." #fileExt,(const uint8_t*)fileName##_##fileExt##_File,(int)fileName##_##fileExt##_FileLen);
EMBED_GZ_FILE(index,html)
EMBED_GZ_FILE(config,json)
void sendEmbeddedFile(String name,String contentType,AsyncWebServerRequest *request){
std::map<String,EmbeddedFile*>::iterator it=embeddedFiles.find(name);
if (it != embeddedFiles.end()){
EmbeddedFile* found=it->second;
AsyncWebServerResponse *response=request->beginResponse_P(200,contentType,found->start,found->len);
response->addHeader(F("Content-Encoding"), F("gzip"));
request->send(response);
}
else{
request->send(404, "text/plain", "Not found");
}
}
String js_status(){ String js_status(){
int numPgns=nmea0183Converter->numPgns(); int numPgns=nmea0183Converter->numPgns();
@ -165,15 +193,24 @@ GwConfigInterface *sendTCP=NULL;
GwConfigInterface *sendSeasmart=NULL; GwConfigInterface *sendSeasmart=NULL;
GwConfigInterface *systemName=NULL; GwConfigInterface *systemName=NULL;
GwSerial usbSerial(&logger, UART_NUM_0); GwSerial usbSerial(&logger, UART_NUM_0, USB_CHANNEL_ID);
class GwSerialLog : public GwLogWriter{ class GwSerialLog : public GwLogWriter{
public: public:
virtual ~GwSerialLog(){} virtual ~GwSerialLog(){}
virtual void write(const char *data){ virtual void write(const char *data){
usbSerial.enqueue((const uint8_t*)data,strlen(data)); //ignore any errors usbSerial.sendToClients(data,-1); //ignore any errors
} }
}; };
void delayedRestart(){
xTaskCreate([](void *p){
delay(500);
ESP.restart();
vTaskDelete(NULL);
},"reset",1000,NULL,0,NULL);
}
void setup() { void setup() {
uint8_t chipid[6]; uint8_t chipid[6];
@ -212,13 +249,14 @@ void setup() {
// Start Web Server // Start Web Server
webserver.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ webserver.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response=request->beginResponse_P(200,"text/html",(const uint8_t *)indexFile,(int)indexFileLen); sendEmbeddedFile("index.html","text/html",request);
response->addHeader(F("Content-Encoding"), F("gzip")); });
request->send(response); webserver.on("/config.json", HTTP_GET, [](AsyncWebServerRequest *request){
sendEmbeddedFile("config.json","application/json",request);
}); });
webserver.on("/api/reset", HTTP_GET,[](AsyncWebServerRequest *request){ webserver.on("/api/reset", HTTP_GET,[](AsyncWebServerRequest *request){
logger.logDebug(GwLog::LOG,"Reset Button"); logger.logDebug(GwLog::LOG,"Reset Button");
ESP.restart(); delayedRestart();
}); });
class StatusRequest : public RequestMessage{ class StatusRequest : public RequestMessage{
public: public:
@ -268,8 +306,7 @@ void setup() {
result=JSON_OK; result=JSON_OK;
logger.logString("update config and restart"); logger.logString("update config and restart");
config.saveConfig(); config.saveConfig();
delay(100); delayedRestart();
ESP.restart();
} }
else{ else{
DynamicJsonDocument rt(100); DynamicJsonDocument rt(100);
@ -295,8 +332,7 @@ void setup() {
config.reset(true); config.reset(true);
logger.logString("reset config, restart"); logger.logString("reset config, restart");
result=JSON_OK; result=JSON_OK;
delay(100); delayedRestart();
ESP.restart();
} }
}; };
webserver.on("/api/resetConfig",HTTP_GET,[](AsyncWebServerRequest *request){ webserver.on("/api/resetConfig",HTTP_GET,[](AsyncWebServerRequest *request){
@ -385,45 +421,82 @@ void HandleNMEA2000Msg(const tN2kMsg &N2kMsg) {
socketServer.sendToClients(buf,N2K_CHANNEL_ID); socketServer.sendToClients(buf,N2K_CHANNEL_ID);
} }
void sendBufferToChannels(const char * buffer, int sourceId){
//*****************************************************************************
void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg) {
if ( ! sendTCP->asBoolean() && ! sendUsb->asBoolean() ) return;
char buf[MAX_NMEA0183_MESSAGE_SIZE];
if ( !NMEA0183Msg.GetMessage(buf, MAX_NMEA0183_MESSAGE_SIZE) ) return;
if (sendTCP->asBoolean()){ if (sendTCP->asBoolean()){
socketServer.sendToClients(buf,N2K_CHANNEL_ID); socketServer.sendToClients(buffer,sourceId);
} }
if (sendUsb->asBoolean()){ if (sendUsb->asBoolean()){
int len=strlen(buf); usbSerial.sendToClients(buffer,sourceId);
if (len >= (MAX_NMEA0183_MESSAGE_SIZE -2)) return;
buf[len]=0x0d;
len++;
buf[len]=0x0a;
len++;
buf[len]=0;
usbSerial.enqueue((const uint8_t*)buf,len);
} }
} }
//*****************************************************************************
void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg, int sourceId) {
if ( ! sendTCP->asBoolean() && ! sendUsb->asBoolean() ) return;
char buf[MAX_NMEA0183_MESSAGE_SIZE+3];
if ( !NMEA0183Msg.GetMessage(buf, MAX_NMEA0183_MESSAGE_SIZE) ) return;
size_t len=strlen(buf);
buf[len]=0x0d;
buf[len+1]=0x0a;
buf[len+2]=0;
sendBufferToChannels(buf,sourceId);
}
void handleReceivedNmeaMessage(const char *buf, int sourceId){
//TODO - for now only send out again
//add the conversion to N2K here
sendBufferToChannels(buf,sourceId);
}
void handleSendAndRead(bool handleRead){
socketServer.loop(handleRead);
usbSerial.loop(handleRead);
}
class NMEAMessageReceiver : public GwBufferWriter{ class NMEAMessageReceiver : public GwBufferWriter{
uint8_t buffer[GwBuffer::RX_BUFFER_SIZE+4];
uint8_t *writePointer=buffer;
public: public:
virtual int write(const uint8_t *buffer,size_t len){ virtual int write(const uint8_t *buffer,size_t len){
char nbuf[len+1]; size_t toWrite=GwBuffer::RX_BUFFER_SIZE-(writePointer-buffer);
memcpy(nbuf,buffer,len); if (toWrite > len) toWrite=len;
nbuf[len]=0; memcpy(writePointer,buffer,toWrite);
logger.logDebug(GwLog::DEBUG,"NMEA[%d]: %s",id,nbuf); writePointer+=toWrite;
return len; *writePointer=0;
return toWrite;
}
virtual void done(){
if (writePointer == buffer) return;
uint8_t *p;
for (p=writePointer-1;p>=buffer && *p <= 0x20;p--){
*p=0;
}
if (p > buffer){
p++;
*p=0x0d;
p++;
*p=0x0a;
p++;
*p=0;
}
for (p=buffer; *p != 0 && p < writePointer && *p <= 0x20;p++){}
//very simple NMEA check
if (*p != '!' && *p != '$'){
logger.logDebug(GwLog::DEBUG,"unknown line [%d] - ignore: %s",id,(const char *)p);
}
else{
logger.logDebug(GwLog::DEBUG,"NMEA[%d]: %s",id,(const char *)p);
handleReceivedNmeaMessage((const char *)p,id);
//trigger sending to empty buffers
handleSendAndRead(false);
}
writePointer=buffer;
} }
}; };
void loop() { void loop() {
gwWifi.loop(); gwWifi.loop();
socketServer.loop(); handleSendAndRead(true);
if (usbSerial.write() == GwBuffer::ERROR){
//logger.logDebug(GwLog::DEBUG,"overflow in USB serial");
}
NMEA2000.ParseMessages(); NMEA2000.ParseMessages();
int SourceAddress = NMEA2000.GetN2kSource(); int SourceAddress = NMEA2000.GetN2kSource();
@ -447,6 +520,6 @@ void loop() {
socketServer.readMessages(&receiver); socketServer.readMessages(&receiver);
//read channels //read channels
usbSerial.read(); usbSerial.readMessages(&receiver);
} }

View File

@ -16,15 +16,19 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler):
apiurl=self.server.proxyUrl apiurl=self.server.proxyUrl
url=apiurl+p.replace("/api","") url=apiurl+p.replace("/api","")
print("proxy to %s"%url) print("proxy to %s"%url)
with urllib.request.urlopen(url) as response: try:
self.send_response(http.HTTPStatus.OK) with urllib.request.urlopen(url,timeout=10) as response:
self.send_header("Content-type", response.getheader("Content-type")) self.send_response(http.HTTPStatus.OK)
self.send_header("Content-type", response.getheader("Content-type"))
self.end_headers() self.end_headers()
shutil.copyfileobj(response,self.wfile) shutil.copyfileobj(response,self.wfile)
return None
self.send_error(http.HTTPStatus.NOT_FOUND, "api not found")
return None
except Exception as e:
self.send_error(http.HTTPStatus.INTERNAL_SERVER_ERROR, "api error %s"%str(e))
return None return None
self.send_error(http.HTTPStatus.NOT_FOUND, "api not found")
return None
super().do_GET() super().do_GET()
def translate_path(self, path): def translate_path(self, path):
"""Translate a /-separated PATH to the local filename syntax. """Translate a /-separated PATH to the local filename syntax.

99
web/config.json Normal file
View File

@ -0,0 +1,99 @@
[
{
"name": "systemName",
"label": "system name",
"type": "string",
"default": "ESP32NMEA2K",
"check": "checkSystemName",
"description": "system name, used for the access point and for services"
},
{
"name": "usbBaud",
"label": "USB baud rate",
"type": "list",
"default": "115200",
"description": "baud rate for the USB port",
"list": [1200,2400,4800,9600,14400,19200,28800,38400,57600,115200,230400,460800]
},
{
"name": "sendUsb",
"label": "NMEA to USB",
"type": "boolean",
"default": "true",
"description": "send out NMEA data on the USB port"
},
{
"name": "receiveUsb",
"label": "NMEA from USB",
"type": "boolean",
"default": "true",
"description": "receive NMEA data on the USB port"
},
{
"name": "serverPort",
"label": "TCP port",
"type": "number",
"default": "2222",
"description": "the TCP port we listen on"
},
{
"name": "maxClients",
"label": "max. TCP clients",
"type": "number",
"default": "10",
"check":"checkMaxClients",
"description": "the number of clients we allow to connect to us"
},
{
"name": "sendTCP",
"label": "NMEA to TCP",
"type": "boolean",
"default": "true",
"description": "send out NMEA data to connected TCP clients"
},
{
"name": "readTCP",
"label": "NMEA from TCP",
"type": "boolean",
"default": "true",
"description": "receive NMEA data from connected TCP clients"
},
{
"name": "sendSeasmart",
"label": "Seasmart to TCP",
"type": "boolean",
"default": "false",
"description": "send NMEA2000 as seasmart to connected TCP clients"
},
{
"name": "wifiClient",
"label": "wifi client",
"type": "boolean",
"default": "false",
"description": "connect to an external WIFI network"
},
{
"name": "wifiPass",
"label": "wifi client password",
"type": "password",
"default": "",
"description": "the password for an external WIFI network"
},
{
"name": "wifiSSID",
"label": "wifi client SSID",
"type": "string",
"default": "",
"check": "checkSSID",
"description": "the SSID for an external WIFI network"
},
{
"name": "stopApTime",
"type": "number",
"default": "0",
"check": "checkStopApTime",
"description": "stop the access point after that many minutes if not used"
}
]

View File

@ -5,6 +5,7 @@
<title>NMEA 2000 Gateway</title> <title>NMEA 2000 Gateway</title>
<script type="text/javascript"> <script type="text/javascript">
let self=this;
let lastUpdate=(new Date()).getTime(); let lastUpdate=(new Date()).getTime();
function alertRestart(){ function alertRestart(){
alert("Board reset triggered, reconnect WLAN if necessary"); alert("Board reset triggered, reconnect WLAN if necessary");
@ -50,15 +51,39 @@
if (el){ if (el){
let v=jsonData[k]; let v=jsonData[k];
el.value=v; el.value=v;
el.setAttribute('data-loaded',v);
checkChange(el);
} }
} }
}); });
} }
function checkMaxClients(v){
let parsed=parseInt(v);
if (isNaN(parsed)) return "not a valid number";
if (parsed < 0) return "must be >= 0";
if (parsed > 10) return "max is 10";
}
function checkSystemName(v){
//2...32 characters for ssid
let allowed=v.replace(/[^a-zA-Z0-9]*/g,'');
if (allowed != v) return "contains invalid characters, only a-z, A-Z, 0-9";
if (v.length < 2 || v.length > 32) return "invalid length (2...32)";
}
function changeConfig(){ function changeConfig(){
let url="/api/setConfig?"; let url="/api/setConfig?";
let values=document.querySelectorAll('.configForm select , .configForm input'); let values=document.querySelectorAll('.configForm select , .configForm input');
for (let i=0;i<values.length;i++){ for (let i=0;i<values.length;i++){
let v=values[i]; let v=values[i];
let check=v.getAttribute('data-check');
if (check){
if (typeof(self[check]) === 'function'){
let res=self[check](v.value);
if (res){
alert("invalid config for "+v.getAttribute('name')+"("+v.value+"):\n"+res);
return;
}
}
}
url+=v.getAttribute('name')+"="+encodeURIComponent(v.value)+"&"; url+=v.getAttribute('name')+"="+encodeURIComponent(v.value)+"&";
} }
getJson(url) getJson(url)
@ -107,6 +132,101 @@
} }
} }
} }
function checkChange(el) {
let loaded = el.getAttribute('data-loaded');
if (loaded !== undefined) {
if (loaded != el.value) {
el.classList.add('changed');
}
else {
el.classList.remove("changed");
}
}
}
function createInput(configItem){
let el;
if (configItem.type === 'boolean' || configItem.type === 'list'){
el=document.createElement('select')
el.setAttribute('name',configItem.name)
let slist=[];
if (configItem.list){
configItem.list.forEach(function(v){
slist.push({l:v,v:v});
})
}
else{
slist.push({l:'on',v:'true'})
slist.push({l:'off',v:'false'})
}
slist.forEach(function(sitem){
let sitemEl=document.createElement('option');
sitemEl.setAttribute('value',sitem.v);
sitemEl.textContent=sitem.l;
el.appendChild(sitemEl);
})
return el;
}
el=document.createElement('input');
el.setAttribute('name',configItem.name)
if (configItem.type === 'password'){
el.setAttribute('type','password');
}
else if (configItem.type === 'number'){
el.setAttribute('type','number');
}
else{
el.setAttribute('type','text');
}
return el;
}
let configDefinitions;
function loadConfigDefinitions(){
getJson("config.json")
.then(function(defs){
let frame=document.querySelector('.configFormRows');
if (! frame) throw Error("no config form");
frame.innerHTML='';
configDefinitions=defs;
defs.forEach(function(item){
if (! item.type) return;
let row=document.createElement('div');
row.classList.add('row');
let label=item.label || item.name;
let labelEl=document.createElement('span');
labelEl.classList.add('label');
labelEl.textContent=label;
row.appendChild(labelEl);
let valueEl=createInput(item);
if (!valueEl) return;
valueEl.setAttribute('data-default',item.default);
valueEl.addEventListener('change',function(ev){
let el=ev.target;
checkChange(el);
})
if (item.check) valueEl.setAttribute('data-check',item.check);
row.appendChild(valueEl);
let bt=document.createElement('button');
bt.classList.add('defaultButton');
bt.setAttribute('data-default',item.default);
bt.addEventListener('click',function(ev){
valueEl.value=valueEl.getAttribute('data-default');
checkChange(valueEl);
})
bt.textContent="X";
row.appendChild(bt);
bt=document.createElement('button');
bt.classList.add('infoButton');
bt.addEventListener('click',function(ev){
alert(item.description);
});
bt.textContent="?";
row.appendChild(bt);
frame.appendChild(row);
})
resetForm();
})
.catch(function(err){alert("unable to load config: "+err)})
}
window.setInterval(update,1000); window.setInterval(update,1000);
window.addEventListener('load',function(){ window.addEventListener('load',function(){
let buttons=document.querySelectorAll('button'); let buttons=document.querySelectorAll('button');
@ -118,7 +238,7 @@
cd.addEventListener('change',function(ev){ cd.addEventListener('change',function(ev){
showCanDetails(ev.target.checked); showCanDetails(ev.target.checked);
}); });
resetForm(); loadConfigDefinitions();
}); });
</script> </script>
<style type="text/css"> <style type="text/css">
@ -130,6 +250,9 @@
border: 1px solid grey; border: 1px solid grey;
max-width: 40em; max-width: 40em;
} }
.changed {
color: green
}
span.label { span.label {
width: 10em; width: 10em;
display: inline-block; display: inline-block;
@ -151,6 +274,10 @@ span#connected.ok{
.buttons { .buttons {
padding-left: 1em; padding-left: 1em;
} }
button.infoButton {
margin-left: 1em;
vertical-align: bottom;
}
#canDetails{ #canDetails{
display:none; display:none;
} }
@ -201,75 +328,8 @@ span#connected.ok{
</div> </div>
<button id="reset" >Reset</button> <button id="reset" >Reset</button>
<div class="configForm"> <div class="configForm">
<div class="row"> <div class="configFormRows">
<span class="label">system name</span>
<input name="systemName" type="text">
</div>
<div class="row">
<span class="label">NMEAtoUSB</span>
<select name="sendUsb">
<option value="true">On</option>
<option value="false" selected="selected">Off</option>
</select>
</div>
<!--"1200","2400","4800","9600","14400","19200","28800","38400","57600","115200","230400","460800"-->
<div class="row">
<span class="label">USB baud rate</span>
<select name="usbBaud">
<option value="1200">1200</option>
<option value="2400">2400</option>
<option value="4800">4800</option>
<option value="9600">9600</option>
<option value="14400">14400</option>
<option value="19200">19200</option>
<option value="28800">28800</option>
<option value="38400">38400</option>
<option value="57600">57600</option>
<option value="115200">115200</option>
<option value="230400">230400</option>
<option value="460800">460800</option>
</select>
</div>
<div class="row">
<span class="label">TCP Port</span>
<input name="serverPort" type="number">
</div>
<div class="row">
<span class="label">maxTCPClients</span>
<input name="maxClients" type="number">
</div>
<div class="row">
<span class="label">NMEAtoTCP</span>
<select name="sendTCP">
<option value="true">On</option>
<option value="false" selected="selected">Off</option>
</select>
</div>
<div class="row">
<span class="label">SeasmartToTCP</span>
<select name="sendSeasmart">
<option value="true">On</option>
<option value="false" selected="selected">Off</option>
</select>
</div>
<div class="row">
<span class="label">shutdown AP after (min)</span>
<input name="stopApTime" type="number">
</div>
<div class="row">
<span class="label">wifiClient</span>
<select name="wifiClient">
<option value="true">On</option>
<option value="false" selected="selected">Off</option>
</select>
</div>
<div class="row">
<span class="label">wifiClientPass</span>
<input name="wifiPass" type="text">
</div>
<div class="row">
<span class="label">wifiClientSSID</span>
<input name="wifiSSID" type="text">
</div> </div>
<div class="buttons"> <div class="buttons">
<button id="resetForm">ReloadConfig</button> <button id="resetForm">ReloadConfig</button>