Merge branch 'master' into boards
This commit is contained in:
commit
62baaa33f6
|
@ -3,4 +3,4 @@
|
|||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
web/*gz
|
||||
generated/*
|
|
@ -2,20 +2,82 @@ print("running extra...")
|
|||
import gzip
|
||||
import shutil
|
||||
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):
|
||||
outfile=inFile+".gz"
|
||||
def basePath():
|
||||
#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):
|
||||
otime=os.path.getmtime(outfile)
|
||||
itime=os.path.getmtime(inFile)
|
||||
itime=os.path.getmtime(infile)
|
||||
if (otime >= itime):
|
||||
print("%s is newer then %s, no need to recreate"%(outfile,inFile))
|
||||
return
|
||||
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
|
||||
with open(inFile, 'rb') as f_in:
|
||||
with gzip.open(outfile, 'wb') as 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:
|
||||
print("compressing %s"%f)
|
||||
compressFile(f)
|
||||
compressFile(f)
|
||||
generateCfg()
|
|
@ -54,7 +54,7 @@ GwConfigInterface * GwConfigHandler::getConfigItem(const String name, bool dummy
|
|||
return &dummyConfig;
|
||||
}
|
||||
#define PREF_NAME "gwprefs"
|
||||
GwConfigHandler::GwConfigHandler(GwLog *logger){
|
||||
GwConfigHandler::GwConfigHandler(GwLog *logger): GwConfigDefinitions(){
|
||||
this->logger=logger;
|
||||
}
|
||||
bool GwConfigHandler::loadConfig(){
|
||||
|
|
|
@ -3,74 +3,16 @@
|
|||
#include <Arduino.h>
|
||||
#include <Preferences.h>
|
||||
#include "GwLog.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;
|
||||
}
|
||||
};
|
||||
#include "GwConfigItem.h"
|
||||
#include "GwConfigDefinitions.h"
|
||||
|
||||
|
||||
class GwConfigHandler{
|
||||
class GwConfigHandler: public GwConfigDefinitions{
|
||||
private:
|
||||
Preferences prefs;
|
||||
GwLog *logger;
|
||||
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);
|
||||
bool loadConfig();
|
||||
bool saveConfig();
|
||||
|
@ -85,23 +27,5 @@ class GwConfigHandler{
|
|||
GwConfigItem * findConfig(const String name, bool dummy=false);
|
||||
GwConfigInterface * getConfigItem(const String name, bool dummy=false) const;
|
||||
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
|
|
@ -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;
|
||||
pNMEA0183=NMEA0183;
|
||||
|
||||
sourceId=id;
|
||||
}
|
||||
|
||||
|
||||
|
@ -46,10 +47,11 @@ void N2kDataToNMEA0183::loop() {
|
|||
//*****************************************************************************
|
||||
void N2kDataToNMEA0183::SendMessage(const tNMEA0183Msg &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){
|
||||
return new N2kToNMEA0183Functions(logger,boatData,NMEA2000,NMEA0183);
|
||||
N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000,
|
||||
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
|
||||
{
|
||||
public:
|
||||
using tSendNMEA0183MessageCallback = void (*)(const tNMEA0183Msg &NMEA0183Msg);
|
||||
using tSendNMEA0183MessageCallback = void (*)(const tNMEA0183Msg &NMEA0183Msg, int id);
|
||||
|
||||
protected:
|
||||
GwLog *logger;
|
||||
GwBoatData *boatData;
|
||||
|
||||
tNMEA0183 *pNMEA0183;
|
||||
int sourceId;
|
||||
tSendNMEA0183MessageCallback SendNMEA0183MessageCallback;
|
||||
|
||||
|
||||
void SendMessage(const tNMEA0183Msg &NMEA0183Msg);
|
||||
|
||||
N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183);
|
||||
N2kDataToNMEA0183(GwLog *logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183, int sourceId);
|
||||
|
||||
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;
|
||||
void SetSendNMEA0183MessageCallback(tSendNMEA0183MessageCallback _SendNMEA0183MessageCallback)
|
||||
{
|
||||
|
|
|
@ -849,7 +849,8 @@ private:
|
|||
}
|
||||
|
||||
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;
|
||||
lastLoopTime = 0;
|
||||
|
|
|
@ -6,10 +6,9 @@ void GwBuffer::lp(const char *fkt, int 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->rotate=rotate;
|
||||
this->bufferSize=bufferSize;
|
||||
this->buffer=new uint8_t[bufferSize];
|
||||
writePointer = buffer;
|
||||
|
@ -26,67 +25,45 @@ void GwBuffer::reset()
|
|||
}
|
||||
size_t GwBuffer::freeSpace()
|
||||
{
|
||||
if (! rotate){
|
||||
return bufferSize-offset(writePointer)-1;
|
||||
if (readPointer <= writePointer){
|
||||
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;
|
||||
}
|
||||
size_t GwBuffer::usedSpace()
|
||||
{
|
||||
if (readPointer == writePointer)
|
||||
return 0;
|
||||
if (readPointer < writePointer)
|
||||
if (readPointer <= writePointer)
|
||||
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);
|
||||
if (len == 0)
|
||||
return 0;
|
||||
if (freeSpace() < len && rotate)
|
||||
//in rotating mode (send buffer)
|
||||
//we only fill in a message if it fit's completely
|
||||
if (freeSpace() < len && !addPartial)
|
||||
return 0;
|
||||
size_t written = 0;
|
||||
if (writePointer >= readPointer)
|
||||
{
|
||||
written = bufferSize - offset(writePointer) - 1;
|
||||
bool canRotate=rotate && offset(readPointer) > 0;
|
||||
if (canRotate) written++; //we can also fill the last byte
|
||||
if (written > len)
|
||||
written = len;
|
||||
if (written)
|
||||
{
|
||||
memcpy(writePointer, data, written);
|
||||
len -= written;
|
||||
data += written;
|
||||
writePointer += written;
|
||||
if (offset(writePointer) > (bufferSize - 1))
|
||||
writePointer = buffer;
|
||||
size_t written = 0;
|
||||
for (int i=0;i<2;i++){
|
||||
size_t currentFree=freeSpace();
|
||||
size_t toWrite=len-written;
|
||||
if (toWrite > currentFree) toWrite=currentFree;
|
||||
if (toWrite > (bufferSize - offset(writePointer))) {
|
||||
toWrite=bufferSize - offset(writePointer);
|
||||
}
|
||||
lp("addData1", written);
|
||||
if (len <= 0)
|
||||
{
|
||||
return written;
|
||||
if (toWrite != 0){
|
||||
memcpy(writePointer, data, toWrite);
|
||||
written+=toWrite;
|
||||
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
|
||||
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;
|
||||
lp("addData2", written);
|
||||
return written;
|
||||
}
|
||||
/**
|
||||
* 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 )
|
||||
{
|
||||
lp("fetchDataE");
|
||||
lp("fetchDataE",maxLen);
|
||||
size_t len = usedSpace();
|
||||
if (maxLen > 0 && len > maxLen) len=maxLen;
|
||||
if (len == 0)
|
||||
if (len == 0){
|
||||
lp("fetchData0",maxLen);
|
||||
writer->done();
|
||||
return OK;
|
||||
}
|
||||
size_t written = 0;
|
||||
size_t plen = len;
|
||||
if (writePointer < readPointer)
|
||||
{
|
||||
//we need to write from readPointer till end and then till writePointer-1
|
||||
plen = bufferSize - offset(readPointer) - 1;
|
||||
int rt = writer->write(readPointer, plen);
|
||||
lp("fetchData1", rt);
|
||||
if (rt < 0)
|
||||
{
|
||||
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write returns error %d", rt);
|
||||
return ERROR;
|
||||
for (int i=0;i<2;i++){
|
||||
size_t currentUsed=usedSpace();
|
||||
size_t toWrite=len-written;
|
||||
if (toWrite > currentUsed) toWrite=currentUsed;
|
||||
if (toWrite > (bufferSize - offset(readPointer))) {
|
||||
toWrite=bufferSize - offset(readPointer);
|
||||
}
|
||||
if (rt > plen)
|
||||
lp("fetchData1", toWrite);
|
||||
if (toWrite > 0)
|
||||
{
|
||||
LOG_DEBUG(GwLog::DEBUG + 1, "buffer: write too many bytes(1) %d", rt);
|
||||
return ERROR;
|
||||
int rt = writer->write(readPointer, toWrite);
|
||||
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;
|
||||
if (plen == 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)");
|
||||
writer->done();
|
||||
if (written == 0){
|
||||
return (errorIf0 ? ERROR : AGAIN);
|
||||
}
|
||||
if (rt > plen)
|
||||
{
|
||||
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;
|
||||
return (written == len)?OK:AGAIN;
|
||||
}
|
||||
|
||||
int GwBuffer::findChar(char x){
|
||||
lp("findChar",x);
|
||||
int offset=0;
|
||||
int of=0;
|
||||
uint8_t *p;
|
||||
for (p=readPointer; p != writePointer && p < (buffer+bufferSize);p++){
|
||||
if (*p == x) return offset;
|
||||
offset++;
|
||||
}
|
||||
if (p >= (buffer+bufferSize)){
|
||||
//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++;
|
||||
for (p=readPointer; of < usedSpace();p++){
|
||||
if (offset(p) >= bufferSize) p -=bufferSize;
|
||||
if (*p == x) {
|
||||
lp("findChar1",of);
|
||||
return of;
|
||||
}
|
||||
of++;
|
||||
}
|
||||
lp("findChar2");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -193,22 +144,5 @@ GwBuffer::WriteStatus GwBuffer::fetchMessage(GwBufferWriter *writer,char delimit
|
|||
}
|
||||
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);
|
||||
}
|
|
@ -8,6 +8,7 @@ class GwBufferWriter{
|
|||
public:
|
||||
int id=0; //can be set be users
|
||||
virtual int write(const uint8_t *buffer,size_t len)=0;
|
||||
virtual void done(){}
|
||||
virtual ~GwBufferWriter(){};
|
||||
};
|
||||
|
||||
|
@ -18,7 +19,8 @@ class GwBufferWriter{
|
|||
*/
|
||||
class GwBuffer{
|
||||
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 {
|
||||
OK,
|
||||
ERROR,
|
||||
|
@ -26,7 +28,6 @@ class GwBuffer{
|
|||
} WriteStatus;
|
||||
private:
|
||||
size_t bufferSize;
|
||||
bool rotate;
|
||||
uint8_t *buffer;
|
||||
uint8_t *writePointer;
|
||||
uint8_t *readPointer;
|
||||
|
@ -40,12 +41,12 @@ class GwBuffer{
|
|||
*/
|
||||
int findChar(char x);
|
||||
public:
|
||||
GwBuffer(GwLog *logger,size_t bufferSize,bool rotate=true);
|
||||
GwBuffer(GwLog *logger,size_t bufferSize);
|
||||
~GwBuffer();
|
||||
void reset();
|
||||
size_t freeSpace();
|
||||
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
|
||||
* 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->num = num;
|
||||
this->buffer = new GwBuffer(logger,1600);
|
||||
this->buffer = new GwBuffer(logger,GwBuffer::TX_BUFFER_SIZE);
|
||||
this->writer = new SerialWriter(num);
|
||||
this->allowRead=allowRead;
|
||||
if (allowRead){
|
||||
this->readBuffer=new GwBuffer(logger, GwBuffer::RX_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
}
|
||||
GwSerial::~GwSerial()
|
||||
{
|
||||
delete buffer;
|
||||
delete writer;
|
||||
if (readBuffer) delete readBuffer;
|
||||
}
|
||||
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; }
|
||||
size_t GwSerial::enqueue(const uint8_t *data, size_t len)
|
||||
{
|
||||
if (! isInitialized()) return 0;
|
||||
return buffer->addData(data, len);
|
||||
}
|
||||
GwBuffer::WriteStatus GwSerial::write(){
|
||||
if (! isInitialized()) return GwBuffer::ERROR;
|
||||
return buffer->fetchData(writer,false);
|
||||
}
|
||||
const char *GwSerial::read(){
|
||||
void GwSerial::sendToClients(const char *buf,int sourceId){
|
||||
if ( sourceId == id) return;
|
||||
size_t len=strlen(buf);
|
||||
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];
|
||||
uart_read_bytes(num,(uint8_t *)(&buffer),10,0);
|
||||
return NULL;
|
||||
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{
|
||||
private:
|
||||
GwBuffer *buffer;
|
||||
GwBuffer *readBuffer;
|
||||
GwBuffer *readBuffer=NULL;
|
||||
GwLog *logger;
|
||||
SerialWriter *writer;
|
||||
uart_port_t num;
|
||||
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:
|
||||
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();
|
||||
int setup(int baud,int rxpin,int txpin);
|
||||
bool isInitialized();
|
||||
size_t enqueue(const uint8_t *data, size_t len);
|
||||
GwBuffer::WriteStatus write();
|
||||
const char *read();
|
||||
void sendToClients(const char *buf,int sourceId);
|
||||
void loop(bool handleRead=true);
|
||||
bool readMessages(GwBufferWriter *writer);
|
||||
};
|
||||
#endif
|
|
@ -3,8 +3,6 @@
|
|||
#include <lwip/sockets.h>
|
||||
#include "GwBuffer.h"
|
||||
|
||||
#define WRITE_BUFFER_SIZE 1600
|
||||
#define READ_BUFFER_SIZE 200
|
||||
class Writer : public GwBufferWriter{
|
||||
public:
|
||||
wiFiClientPtr client;
|
||||
|
@ -63,9 +61,9 @@ class GwClient{
|
|||
this->client=client;
|
||||
this->logger=logger;
|
||||
this->allowRead=allowRead;
|
||||
buffer=new GwBuffer(logger,WRITE_BUFFER_SIZE);
|
||||
buffer=new GwBuffer(logger,GwBuffer::TX_BUFFER_SIZE);
|
||||
if (allowRead){
|
||||
readBuffer=new GwBuffer(logger,READ_BUFFER_SIZE,false);
|
||||
readBuffer=new GwBuffer(logger,GwBuffer::RX_BUFFER_SIZE);
|
||||
}
|
||||
overflows=0;
|
||||
if (client != NULL){
|
||||
|
@ -177,7 +175,7 @@ void GwSocketServer::begin(){
|
|||
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
|
||||
|
||||
|
@ -230,7 +228,7 @@ void GwSocketServer::loop()
|
|||
}
|
||||
else
|
||||
{
|
||||
client->read();
|
||||
if (handleRead) client->read();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,12 +245,6 @@ bool GwSocketServer::readMessages(GwBufferWriter *writer){
|
|||
}
|
||||
void GwSocketServer::sendToClients(const char *buf,int source){
|
||||
int len=strlen(buf);
|
||||
char buffer[len+2];
|
||||
memcpy(buffer,buf,len);
|
||||
buffer[len]=0x0d;
|
||||
len++;
|
||||
buffer[len]=0x0a;
|
||||
len++;
|
||||
int sourceIndex=source-minId;
|
||||
for (int i = 0; i < maxClients; i++)
|
||||
{
|
||||
|
@ -260,7 +252,7 @@ void GwSocketServer::sendToClients(const char *buf,int source){
|
|||
gwClientPtr client = clients[i];
|
||||
if (! client->hasClient()) continue;
|
||||
if ( client->client->connected() ) {
|
||||
bool rt=client->enqueue((uint8_t*)buffer,len);
|
||||
bool rt=client->enqueue((uint8_t*)buf,len);
|
||||
if (!rt){
|
||||
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();
|
||||
void begin();
|
||||
void loop();
|
||||
void loop(bool handleRead=true);
|
||||
void sendToClients(const char *buf,int sourceId);
|
||||
int numClients();
|
||||
bool readMessages(GwBufferWriter *writer);
|
||||
|
|
|
@ -41,7 +41,7 @@ bool GwWifi::connectInternal(){
|
|||
}
|
||||
return false;
|
||||
}
|
||||
#define RETRY_MILLIS 5000
|
||||
#define RETRY_MILLIS 10000
|
||||
void GwWifi::loop(){
|
||||
if (wifiClient->asBoolean() && ! clientConnected()){
|
||||
long now=millis();
|
||||
|
|
|
@ -18,26 +18,35 @@ lib_deps =
|
|||
bblanchon/ArduinoJson@^6.18.5
|
||||
ottowinter/ESPAsyncWebServer-esphome@^2.0.1
|
||||
board_build.embed_files =
|
||||
web/index.html.gz
|
||||
generated/index.html.gz
|
||||
generated/config.json.gz
|
||||
extra_scripts = extra_script.py
|
||||
build_flags=
|
||||
-Igenerated
|
||||
|
||||
[env:m5stack-atom]
|
||||
board = m5stack-atom
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
build_flags = -D BOARD_M5ATOM
|
||||
build_flags =
|
||||
-D BOARD_M5ATOM
|
||||
${env.build_flags}
|
||||
upload_port = /dev/esp32
|
||||
|
||||
[env:m5stack-atom-canunit]
|
||||
board = m5stack-atom
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
build_flags = -D BOARD_M5ATOM_CANUNIT
|
||||
build_flags =
|
||||
-D BOARD_M5ATOM_CANUNIT
|
||||
${env.build_flags}
|
||||
upload_port = /dev/esp32
|
||||
|
||||
[env:m5stickc-atom-canunit]
|
||||
board = m5stack-atom
|
||||
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
|
||||
|
|
159
src/main.cpp
159
src/main.cpp
|
@ -12,7 +12,7 @@
|
|||
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 <Arduino.h>
|
||||
|
@ -65,7 +65,7 @@ Preferences preferences; // Nonvolatile storage on ESP32 - To store
|
|||
bool SendNMEA0183Conversion = true; // Do we send NMEA2000 -> NMEA0183 conversion
|
||||
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
|
||||
const unsigned long TransmitMessages[] PROGMEM = {127489L, // Engine dynamic
|
||||
|
@ -73,7 +73,7 @@ const unsigned long TransmitMessages[] PROGMEM = {127489L, // Engine dynamic
|
|||
};
|
||||
// Forward declarations
|
||||
void HandleNMEA2000Msg(const tN2kMsg &N2kMsg);
|
||||
void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg);
|
||||
void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg,int id);
|
||||
|
||||
AsyncWebServer webserver(80);
|
||||
|
||||
|
@ -135,11 +135,39 @@ void handleAsyncWebRequest(AsyncWebServerRequest *request, RequestMessage *msg,
|
|||
}
|
||||
|
||||
#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(){
|
||||
int numPgns=nmea0183Converter->numPgns();
|
||||
|
@ -165,15 +193,24 @@ GwConfigInterface *sendTCP=NULL;
|
|||
GwConfigInterface *sendSeasmart=NULL;
|
||||
GwConfigInterface *systemName=NULL;
|
||||
|
||||
GwSerial usbSerial(&logger, UART_NUM_0);
|
||||
GwSerial usbSerial(&logger, UART_NUM_0, USB_CHANNEL_ID);
|
||||
class GwSerialLog : public GwLogWriter{
|
||||
public:
|
||||
virtual ~GwSerialLog(){}
|
||||
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() {
|
||||
|
||||
uint8_t chipid[6];
|
||||
|
@ -212,13 +249,14 @@ void setup() {
|
|||
|
||||
// Start Web Server
|
||||
webserver.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
AsyncWebServerResponse *response=request->beginResponse_P(200,"text/html",(const uint8_t *)indexFile,(int)indexFileLen);
|
||||
response->addHeader(F("Content-Encoding"), F("gzip"));
|
||||
request->send(response);
|
||||
sendEmbeddedFile("index.html","text/html",request);
|
||||
});
|
||||
webserver.on("/config.json", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
sendEmbeddedFile("config.json","application/json",request);
|
||||
});
|
||||
webserver.on("/api/reset", HTTP_GET,[](AsyncWebServerRequest *request){
|
||||
logger.logDebug(GwLog::LOG,"Reset Button");
|
||||
ESP.restart();
|
||||
delayedRestart();
|
||||
});
|
||||
class StatusRequest : public RequestMessage{
|
||||
public:
|
||||
|
@ -268,8 +306,7 @@ void setup() {
|
|||
result=JSON_OK;
|
||||
logger.logString("update config and restart");
|
||||
config.saveConfig();
|
||||
delay(100);
|
||||
ESP.restart();
|
||||
delayedRestart();
|
||||
}
|
||||
else{
|
||||
DynamicJsonDocument rt(100);
|
||||
|
@ -295,8 +332,7 @@ void setup() {
|
|||
config.reset(true);
|
||||
logger.logString("reset config, restart");
|
||||
result=JSON_OK;
|
||||
delay(100);
|
||||
ESP.restart();
|
||||
delayedRestart();
|
||||
}
|
||||
};
|
||||
webserver.on("/api/resetConfig",HTTP_GET,[](AsyncWebServerRequest *request){
|
||||
|
@ -385,45 +421,82 @@ void HandleNMEA2000Msg(const tN2kMsg &N2kMsg) {
|
|||
socketServer.sendToClients(buf,N2K_CHANNEL_ID);
|
||||
}
|
||||
|
||||
|
||||
//*****************************************************************************
|
||||
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;
|
||||
void sendBufferToChannels(const char * buffer, int sourceId){
|
||||
if (sendTCP->asBoolean()){
|
||||
socketServer.sendToClients(buf,N2K_CHANNEL_ID);
|
||||
socketServer.sendToClients(buffer,sourceId);
|
||||
}
|
||||
if (sendUsb->asBoolean()){
|
||||
int len=strlen(buf);
|
||||
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);
|
||||
usbSerial.sendToClients(buffer,sourceId);
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
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{
|
||||
uint8_t buffer[GwBuffer::RX_BUFFER_SIZE+4];
|
||||
uint8_t *writePointer=buffer;
|
||||
public:
|
||||
virtual int write(const uint8_t *buffer,size_t len){
|
||||
char nbuf[len+1];
|
||||
memcpy(nbuf,buffer,len);
|
||||
nbuf[len]=0;
|
||||
logger.logDebug(GwLog::DEBUG,"NMEA[%d]: %s",id,nbuf);
|
||||
return len;
|
||||
size_t toWrite=GwBuffer::RX_BUFFER_SIZE-(writePointer-buffer);
|
||||
if (toWrite > len) toWrite=len;
|
||||
memcpy(writePointer,buffer,toWrite);
|
||||
writePointer+=toWrite;
|
||||
*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() {
|
||||
gwWifi.loop();
|
||||
|
||||
socketServer.loop();
|
||||
if (usbSerial.write() == GwBuffer::ERROR){
|
||||
//logger.logDebug(GwLog::DEBUG,"overflow in USB serial");
|
||||
}
|
||||
handleSendAndRead(true);
|
||||
NMEA2000.ParseMessages();
|
||||
|
||||
int SourceAddress = NMEA2000.GetN2kSource();
|
||||
|
@ -447,6 +520,6 @@ void loop() {
|
|||
socketServer.readMessages(&receiver);
|
||||
//read channels
|
||||
|
||||
usbSerial.read();
|
||||
usbSerial.readMessages(&receiver);
|
||||
|
||||
}
|
||||
|
|
|
@ -16,15 +16,19 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler):
|
|||
apiurl=self.server.proxyUrl
|
||||
url=apiurl+p.replace("/api","")
|
||||
print("proxy to %s"%url)
|
||||
with urllib.request.urlopen(url) as response:
|
||||
self.send_response(http.HTTPStatus.OK)
|
||||
self.send_header("Content-type", response.getheader("Content-type"))
|
||||
try:
|
||||
with urllib.request.urlopen(url,timeout=10) as response:
|
||||
self.send_response(http.HTTPStatus.OK)
|
||||
self.send_header("Content-type", response.getheader("Content-type"))
|
||||
|
||||
self.end_headers()
|
||||
shutil.copyfileobj(response,self.wfile)
|
||||
self.end_headers()
|
||||
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
|
||||
self.send_error(http.HTTPStatus.NOT_FOUND, "api not found")
|
||||
return None
|
||||
super().do_GET()
|
||||
def translate_path(self, path):
|
||||
"""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>
|
||||
|
||||
<script type="text/javascript">
|
||||
let self=this;
|
||||
let lastUpdate=(new Date()).getTime();
|
||||
function alertRestart(){
|
||||
alert("Board reset triggered, reconnect WLAN if necessary");
|
||||
|
@ -50,15 +51,39 @@
|
|||
if (el){
|
||||
let v=jsonData[k];
|
||||
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(){
|
||||
let url="/api/setConfig?";
|
||||
let values=document.querySelectorAll('.configForm select , .configForm input');
|
||||
for (let i=0;i<values.length;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)+"&";
|
||||
}
|
||||
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.addEventListener('load',function(){
|
||||
let buttons=document.querySelectorAll('button');
|
||||
|
@ -118,7 +238,7 @@
|
|||
cd.addEventListener('change',function(ev){
|
||||
showCanDetails(ev.target.checked);
|
||||
});
|
||||
resetForm();
|
||||
loadConfigDefinitions();
|
||||
});
|
||||
</script>
|
||||
<style type="text/css">
|
||||
|
@ -130,6 +250,9 @@
|
|||
border: 1px solid grey;
|
||||
max-width: 40em;
|
||||
}
|
||||
.changed {
|
||||
color: green
|
||||
}
|
||||
span.label {
|
||||
width: 10em;
|
||||
display: inline-block;
|
||||
|
@ -151,6 +274,10 @@ span#connected.ok{
|
|||
.buttons {
|
||||
padding-left: 1em;
|
||||
}
|
||||
button.infoButton {
|
||||
margin-left: 1em;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
#canDetails{
|
||||
display:none;
|
||||
}
|
||||
|
@ -201,75 +328,8 @@ span#connected.ok{
|
|||
</div>
|
||||
<button id="reset" >Reset</button>
|
||||
<div class="configForm">
|
||||
<div class="row">
|
||||
<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 class="configFormRows">
|
||||
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button id="resetForm">ReloadConfig</button>
|
||||
|
|
Loading…
Reference in New Issue