Merge branch 'master' into boards
This commit is contained in:
commit
62baaa33f6
|
@ -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/*
|
|
@ -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 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
|
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()
|
|
@ -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(){
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||||
}
|
}
|
||||||
//*****************************************************************************
|
//*****************************************************************************
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,160 +25,112 @@ 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;
|
||||||
|
data += toWrite;
|
||||||
|
writePointer += toWrite;
|
||||||
|
if (offset(writePointer) >= bufferSize){
|
||||||
|
writePointer -= bufferSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lp("addData1", toWrite);
|
||||||
|
}
|
||||||
|
lp("addData2", written);
|
||||||
return written;
|
return written;
|
||||||
}
|
}
|
||||||
if (! rotate) return written;
|
|
||||||
}
|
|
||||||
//now we have the write pointer before the read pointer
|
|
||||||
int maxLen=readPointer-writePointer-1;
|
|
||||||
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
|
||||||
* return an error if the buffer writer returned < 0
|
* return an error if the buffer writer returned < 0
|
||||||
*/
|
*/
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
if (plen == 0)
|
|
||||||
return OK;
|
|
||||||
int rt = writer->write(readPointer, plen);
|
|
||||||
lp("fetchData2", rt);
|
lp("fetchData2", rt);
|
||||||
if (rt < 0)
|
if (rt < 0)
|
||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns error %d", rt);
|
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns error %d", rt);
|
||||||
|
writer->done();
|
||||||
return ERROR;
|
return ERROR;
|
||||||
}
|
}
|
||||||
if (rt == 0)
|
if (rt > toWrite)
|
||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns 0 (1)");
|
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write too many bytes(1) %d", rt);
|
||||||
return (errorIf0 ? ERROR : AGAIN);
|
writer->done();
|
||||||
}
|
|
||||||
if (rt > plen)
|
|
||||||
{
|
|
||||||
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write too many bytes(2)");
|
|
||||||
return ERROR;
|
return ERROR;
|
||||||
}
|
}
|
||||||
readPointer += rt;
|
readPointer += rt;
|
||||||
if (offset(readPointer) > (bufferSize - 1))
|
if (offset(readPointer) >= bufferSize)
|
||||||
readPointer = buffer;
|
readPointer -= bufferSize;
|
||||||
lp("fetchData3");
|
|
||||||
written += rt;
|
written += rt;
|
||||||
if (written < len)
|
if (rt == 0) break; //no need to try again
|
||||||
return AGAIN;
|
}
|
||||||
return OK;
|
}
|
||||||
|
writer->done();
|
||||||
|
if (written == 0){
|
||||||
|
return (errorIf0 ? ERROR : AGAIN);
|
||||||
|
}
|
||||||
|
return (written == len)?OK:AGAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
159
src/main.cpp
159
src/main.cpp
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,8 @@ 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:
|
||||||
|
with urllib.request.urlopen(url,timeout=10) as response:
|
||||||
self.send_response(http.HTTPStatus.OK)
|
self.send_response(http.HTTPStatus.OK)
|
||||||
self.send_header("Content-type", response.getheader("Content-type"))
|
self.send_header("Content-type", response.getheader("Content-type"))
|
||||||
|
|
||||||
|
@ -25,6 +26,9 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
return None
|
return None
|
||||||
self.send_error(http.HTTPStatus.NOT_FOUND, "api not found")
|
self.send_error(http.HTTPStatus.NOT_FOUND, "api not found")
|
||||||
return None
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
self.send_error(http.HTTPStatus.INTERNAL_SERVER_ERROR, "api error %s"%str(e))
|
||||||
|
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.
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
]
|
200
web/index.html
200
web/index.html
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue