mirror of
https://github.com/thooge/esp32-nmea2000-obp60.git
synced 2026-03-28 18:06:37 +01:00
Compare commits
39 Commits
744cf6469b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 86cd1ed97c | |||
| e33f908187 | |||
|
|
065a9807d2 | ||
|
|
98c318f055 | ||
| 97fcebdcb7 | |||
| a6fd3ef599 | |||
|
|
66e71acac3 | ||
|
|
b85504bf50 | ||
|
|
3043be8e1d | ||
|
|
02b2c888ee | ||
|
|
00b06f458b | ||
|
|
43b0a780d5 | ||
| 0363ba4379 | |||
|
|
4b6e2abe33 | ||
|
|
0401d82b62 | ||
|
|
2fecbee492 | ||
|
|
fc5daaba37 | ||
|
|
04dc09e44a | ||
|
|
6c7997e369 | ||
| 1d2ba2f71d | |||
|
|
7f747e9b35 | ||
|
|
71512e7262 | ||
|
|
4468c0555b | ||
|
|
99404991a3 | ||
|
|
ee5077e0a5 | ||
|
|
bbecf5e55f | ||
|
|
ded1b2b22e | ||
|
|
a0a88fa2c9 | ||
|
|
e9bf54e99f | ||
|
|
64950c3974 | ||
|
|
fb2fbc85a4 | ||
|
|
6b92a5e69c | ||
|
|
ef4546a2e6 | ||
|
|
6870c9b8a4 | ||
|
|
a70d976a6e | ||
|
|
fbba6ffff2 | ||
|
|
d516c82041 | ||
|
|
ccca784ac2 | ||
|
|
337214d650 |
@@ -1,542 +0,0 @@
|
|||||||
print("running extra...")
|
|
||||||
import gzip
|
|
||||||
import shutil
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import inspect
|
|
||||||
import json
|
|
||||||
import glob
|
|
||||||
from datetime import datetime
|
|
||||||
import re
|
|
||||||
import pprint
|
|
||||||
from platformio.project.config import ProjectConfig
|
|
||||||
from platformio.project.exception import InvalidProjectConfError
|
|
||||||
|
|
||||||
Import("env")
|
|
||||||
#print(env.Dump())
|
|
||||||
OWN_FILE="extra_script.py"
|
|
||||||
GEN_DIR='lib/generated'
|
|
||||||
CFG_FILE='web/config.json'
|
|
||||||
XDR_FILE='web/xdrconfig.json'
|
|
||||||
INDEXJS="index.js"
|
|
||||||
INDEXCSS="index.css"
|
|
||||||
CFG_INCLUDE='GwConfigDefinitions.h'
|
|
||||||
CFG_INCLUDE_IMPL='GwConfigDefImpl.h'
|
|
||||||
XDR_INCLUDE='GwXdrTypeMappings.h'
|
|
||||||
TASK_INCLUDE='GwUserTasks.h'
|
|
||||||
GROVE_CONFIG="GwM5GroveGen.h"
|
|
||||||
GROVE_CONFIG_IN="lib/hardware/GwM5Grove.in"
|
|
||||||
EMBEDDED_INCLUDE="GwEmbeddedFiles.h"
|
|
||||||
|
|
||||||
def getEmbeddedFiles(env):
|
|
||||||
rt=[]
|
|
||||||
efiles=env.GetProjectOption("board_build.embed_files")
|
|
||||||
for f in efiles.split("\n"):
|
|
||||||
if f == '':
|
|
||||||
continue
|
|
||||||
rt.append(f)
|
|
||||||
return rt
|
|
||||||
|
|
||||||
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)
|
|
||||||
if (otime >= itime):
|
|
||||||
own=os.path.join(basePath(),OWN_FILE)
|
|
||||||
if os.path.exists(own):
|
|
||||||
owntime=os.path.getmtime(own)
|
|
||||||
if owntime > otime:
|
|
||||||
return False
|
|
||||||
print("%s is newer then %s, no need to recreate"%(outfile,infile))
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
def compressFile(inFile,outfile):
|
|
||||||
if isCurrent(inFile,outfile):
|
|
||||||
return
|
|
||||||
print("compressing %s"%inFile)
|
|
||||||
with open(inFile, 'rb') as f_in:
|
|
||||||
with gzip.open(outfile, 'wb') as f_out:
|
|
||||||
shutil.copyfileobj(f_in, f_out)
|
|
||||||
|
|
||||||
def generateFile(infile,outfile,callback,inMode='rb',outMode='w'):
|
|
||||||
if isCurrent(infile,outfile):
|
|
||||||
return
|
|
||||||
print("creating %s"%outfile)
|
|
||||||
oh=None
|
|
||||||
with open(infile,inMode) as ch:
|
|
||||||
with open(outfile,outMode) as oh:
|
|
||||||
try:
|
|
||||||
callback(ch,oh,inFile=infile)
|
|
||||||
oh.close()
|
|
||||||
except Exception as e:
|
|
||||||
try:
|
|
||||||
oh.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
os.unlink(outfile)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def writeFileIfChanged(fileName,data):
|
|
||||||
if os.path.exists(fileName):
|
|
||||||
with open(fileName,"r") as ih:
|
|
||||||
old=ih.read()
|
|
||||||
ih.close()
|
|
||||||
if old == data:
|
|
||||||
return False
|
|
||||||
print("#generating %s"%fileName)
|
|
||||||
with open(fileName,"w") as oh:
|
|
||||||
oh.write(data)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def mergeConfig(base,other):
|
|
||||||
try:
|
|
||||||
customconfig = env.GetProjectOption("custom_config")
|
|
||||||
except InvalidProjectConfError:
|
|
||||||
customconfig = None
|
|
||||||
for bdir in other:
|
|
||||||
if customconfig and os.path.exists(os.path.join(bdir,customconfig)):
|
|
||||||
cname=os.path.join(bdir,customconfig)
|
|
||||||
print("merge custom config {}".format(cname))
|
|
||||||
with open(cname,'rb') as ah:
|
|
||||||
base += json.load(ah)
|
|
||||||
continue
|
|
||||||
cname=os.path.join(bdir,"config.json")
|
|
||||||
if os.path.exists(cname):
|
|
||||||
print("merge config %s"%cname)
|
|
||||||
with open(cname,'rb') as ah:
|
|
||||||
merge=json.load(ah)
|
|
||||||
base=base+merge
|
|
||||||
return base
|
|
||||||
|
|
||||||
def replaceTexts(data,replacements):
|
|
||||||
if replacements is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data,str):
|
|
||||||
for k,v in replacements.items():
|
|
||||||
data=data.replace("$"+k,str(v))
|
|
||||||
return data
|
|
||||||
if isinstance(data,list):
|
|
||||||
rt=[]
|
|
||||||
for e in data:
|
|
||||||
rt.append(replaceTexts(e,replacements))
|
|
||||||
return rt
|
|
||||||
if isinstance(data,dict):
|
|
||||||
rt={}
|
|
||||||
for k,v in data.items():
|
|
||||||
rt[replaceTexts(k,replacements)]=replaceTexts(v,replacements)
|
|
||||||
return rt
|
|
||||||
return data
|
|
||||||
def expandConfig(config):
|
|
||||||
rt=[]
|
|
||||||
for item in config:
|
|
||||||
type=item.get('type')
|
|
||||||
if type != 'array':
|
|
||||||
rt.append(item)
|
|
||||||
continue
|
|
||||||
replacements=item.get('replace')
|
|
||||||
children=item.get('children')
|
|
||||||
name=item.get('name')
|
|
||||||
if name is None:
|
|
||||||
name="#unknown#"
|
|
||||||
if not isinstance(replacements,list):
|
|
||||||
raise Exception("missing replacements at array %s"%name)
|
|
||||||
for replace in replacements:
|
|
||||||
if children is not None:
|
|
||||||
for c in children:
|
|
||||||
rt.append(replaceTexts(c,replace))
|
|
||||||
return rt
|
|
||||||
|
|
||||||
def generateMergedConfig(inFile,outFile,addDirs=[]):
|
|
||||||
if not os.path.exists(inFile):
|
|
||||||
raise Exception("unable to read cfg file %s"%inFile)
|
|
||||||
data=""
|
|
||||||
with open(inFile,'rb') as ch:
|
|
||||||
config=json.load(ch)
|
|
||||||
config=mergeConfig(config,addDirs)
|
|
||||||
config=expandConfig(config)
|
|
||||||
data=json.dumps(config,indent=2)
|
|
||||||
writeFileIfChanged(outFile,data)
|
|
||||||
|
|
||||||
def generateCfg(inFile,outFile,impl):
|
|
||||||
if not os.path.exists(inFile):
|
|
||||||
raise Exception("unable to read cfg file %s"%inFile)
|
|
||||||
data=""
|
|
||||||
with open(inFile,'rb') as ch:
|
|
||||||
config=json.load(ch)
|
|
||||||
data+="//generated from %s\n"%inFile
|
|
||||||
l=len(config)
|
|
||||||
idx=0
|
|
||||||
if not impl:
|
|
||||||
data+='#include "GwConfigItem.h"\n'
|
|
||||||
data+='class GwConfigDefinitions{\n'
|
|
||||||
data+=' public:\n'
|
|
||||||
data+=' int getNumConfig() const{return %d;}\n'%(l)
|
|
||||||
for item in config:
|
|
||||||
n=item.get('name')
|
|
||||||
if n is None:
|
|
||||||
continue
|
|
||||||
if len(n) > 15:
|
|
||||||
raise Exception("%s: config names must be max 15 caracters"%n)
|
|
||||||
data+=' static constexpr const char* %s="%s";\n'%(n,n)
|
|
||||||
data+="};\n"
|
|
||||||
else:
|
|
||||||
data+='void GwConfigHandler::populateConfigs(GwConfigInterface **config){\n'
|
|
||||||
for item in config:
|
|
||||||
name=item.get('name')
|
|
||||||
if name is None:
|
|
||||||
continue
|
|
||||||
data+=' configs[%d]='%(idx)
|
|
||||||
idx+=1
|
|
||||||
secret="false";
|
|
||||||
if item.get('type') == 'password':
|
|
||||||
secret="true"
|
|
||||||
data+=" new GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret)
|
|
||||||
data+='}\n'
|
|
||||||
writeFileIfChanged(outFile,data)
|
|
||||||
|
|
||||||
def labelFilter(label):
|
|
||||||
return re.sub("[^a-zA-Z0-9]","",re.sub("\([0-9]*\)","",label))
|
|
||||||
def generateXdrMappings(fp,oh,inFile=''):
|
|
||||||
jdoc=json.load(fp)
|
|
||||||
oh.write("static GwXDRTypeMapping* typeMappings[]={\n")
|
|
||||||
first=True
|
|
||||||
for cat in jdoc:
|
|
||||||
item=jdoc[cat]
|
|
||||||
cid=item.get('id')
|
|
||||||
if cid is None:
|
|
||||||
continue
|
|
||||||
tc=item.get('type')
|
|
||||||
if tc is not None:
|
|
||||||
if first:
|
|
||||||
first=False
|
|
||||||
else:
|
|
||||||
oh.write(",\n")
|
|
||||||
oh.write(" new GwXDRTypeMapping(%d,0,%d) /*%s*/"%(cid,tc,cat))
|
|
||||||
fields=item.get('fields')
|
|
||||||
if fields is None:
|
|
||||||
continue
|
|
||||||
idx=0
|
|
||||||
for fe in fields:
|
|
||||||
if not isinstance(fe,dict):
|
|
||||||
continue
|
|
||||||
tc=fe.get('t')
|
|
||||||
id=fe.get('v')
|
|
||||||
if id is None:
|
|
||||||
id=idx
|
|
||||||
idx+=1
|
|
||||||
l=fe.get('l') or ''
|
|
||||||
if tc is None or id is None:
|
|
||||||
continue
|
|
||||||
if first:
|
|
||||||
first=False
|
|
||||||
else:
|
|
||||||
oh.write(",\n")
|
|
||||||
oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,id,tc,cat,l))
|
|
||||||
oh.write("\n")
|
|
||||||
oh.write("};\n")
|
|
||||||
for cat in jdoc:
|
|
||||||
item=jdoc[cat]
|
|
||||||
cid=item.get('id')
|
|
||||||
if cid is None:
|
|
||||||
continue
|
|
||||||
selectors=item.get('selector')
|
|
||||||
if selectors is not None:
|
|
||||||
for selector in selectors:
|
|
||||||
label=selector.get('l')
|
|
||||||
value=selector.get('v')
|
|
||||||
if label is not None and value is not None:
|
|
||||||
label=labelFilter(label)
|
|
||||||
define=("GWXDRSEL_%s_%s"%(cat,label)).upper()
|
|
||||||
oh.write(" #define %s %s\n"%(define,value))
|
|
||||||
fields=item.get('fields')
|
|
||||||
if fields is not None:
|
|
||||||
idx=0
|
|
||||||
for field in fields:
|
|
||||||
v=field.get('v')
|
|
||||||
if v is None:
|
|
||||||
v=idx
|
|
||||||
else:
|
|
||||||
v=int(v)
|
|
||||||
label=field.get('l')
|
|
||||||
if v is not None and label is not None:
|
|
||||||
define=("GWXDRFIELD_%s_%s"%(cat,labelFilter(label))).upper();
|
|
||||||
oh.write(" #define %s %s\n"%(define,str(v)))
|
|
||||||
idx+=1
|
|
||||||
|
|
||||||
class Grove:
|
|
||||||
def __init__(self,name) -> None:
|
|
||||||
self.name=name
|
|
||||||
def _ss(self,z=False):
|
|
||||||
if z:
|
|
||||||
return self.name
|
|
||||||
return self.name if self.name != 'Z' else ''
|
|
||||||
def _suffix(self):
|
|
||||||
return '_'+self.name if self.name != 'Z' else ''
|
|
||||||
def replace(self,line):
|
|
||||||
if line is None:
|
|
||||||
return line
|
|
||||||
return line.replace('$G$',self._ss()).replace('$Z$',self._ss(True)).replace('$GS$',self._suffix())
|
|
||||||
def generateGroveDefs(inh,outh,inFile=''):
|
|
||||||
GROVES=[Grove('Z'),Grove('A'),Grove('B'),Grove('C')]
|
|
||||||
definition=[]
|
|
||||||
started=False
|
|
||||||
def writeConfig():
|
|
||||||
for grove in GROVES:
|
|
||||||
for cl in definition:
|
|
||||||
outh.write(grove.replace(cl))
|
|
||||||
|
|
||||||
for line in inh:
|
|
||||||
if re.match(" *#GROVE",line):
|
|
||||||
started=True
|
|
||||||
if len(definition) > 0:
|
|
||||||
writeConfig()
|
|
||||||
definition=[]
|
|
||||||
continue
|
|
||||||
if started:
|
|
||||||
definition.append(line)
|
|
||||||
if len(definition) > 0:
|
|
||||||
writeConfig()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
userTaskDirs=[]
|
|
||||||
|
|
||||||
def getUserTaskDirs():
|
|
||||||
rt=[]
|
|
||||||
taskdirs=glob.glob(os.path.join( basePath(),'lib','*task*'))
|
|
||||||
for task in taskdirs:
|
|
||||||
rt.append(task)
|
|
||||||
return rt
|
|
||||||
|
|
||||||
def checkAndAdd(file,names,ilist):
|
|
||||||
if not file.endswith('.h'):
|
|
||||||
return
|
|
||||||
match=False
|
|
||||||
for cmp in names:
|
|
||||||
#print("##check %s<->%s"%(f.lower(),cmp))
|
|
||||||
if file.lower() == cmp:
|
|
||||||
match=True
|
|
||||||
if not match:
|
|
||||||
return
|
|
||||||
ilist.append(file)
|
|
||||||
def genereateUserTasks(outfile):
|
|
||||||
includes=[]
|
|
||||||
for task in userTaskDirs:
|
|
||||||
#print("##taskdir=%s"%task)
|
|
||||||
base=os.path.basename(task)
|
|
||||||
includeNames=[base.lower()+".h",'gw'+base.lower()+'.h']
|
|
||||||
for f in os.listdir(task):
|
|
||||||
checkAndAdd(f,includeNames,includes)
|
|
||||||
includeData=""
|
|
||||||
for i in includes:
|
|
||||||
print("#task include %s"%i)
|
|
||||||
includeData+="#include <%s>\n"%i
|
|
||||||
writeFileIfChanged(outfile,includeData)
|
|
||||||
|
|
||||||
def generateEmbedded(elist,outFile):
|
|
||||||
content=""
|
|
||||||
for entry in elist:
|
|
||||||
content+="EMBED_GZ_FILE(\"%s\",%s,\"%s\");\n"%entry
|
|
||||||
writeFileIfChanged(outFile,content)
|
|
||||||
|
|
||||||
def getContentType(fn):
|
|
||||||
if (fn.endswith('.gz')):
|
|
||||||
fn=fn[0:-3]
|
|
||||||
if (fn.endswith('html')):
|
|
||||||
return "text/html"
|
|
||||||
if (fn.endswith('json')):
|
|
||||||
return "application/json"
|
|
||||||
if (fn.endswith('js')):
|
|
||||||
return "text/javascript"
|
|
||||||
if (fn.endswith('css')):
|
|
||||||
return "text/css"
|
|
||||||
return "application/octet-stream"
|
|
||||||
|
|
||||||
|
|
||||||
def getLibs():
|
|
||||||
base=os.path.join(basePath(),"lib")
|
|
||||||
rt=[]
|
|
||||||
for sd in os.listdir(base):
|
|
||||||
if sd == '..':
|
|
||||||
continue
|
|
||||||
if sd == '.':
|
|
||||||
continue
|
|
||||||
fn=os.path.join(base,sd)
|
|
||||||
if os.path.isdir(fn):
|
|
||||||
rt.append(sd)
|
|
||||||
EXTRAS=['generated']
|
|
||||||
for e in EXTRAS:
|
|
||||||
if not e in rt:
|
|
||||||
rt.append(e)
|
|
||||||
return rt
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def joinFiles(target,pattern,dirlist):
|
|
||||||
flist=[]
|
|
||||||
for dir in dirlist:
|
|
||||||
fn=os.path.join(dir,pattern)
|
|
||||||
if os.path.exists(fn):
|
|
||||||
flist.append(fn)
|
|
||||||
current=False
|
|
||||||
if os.path.exists(target):
|
|
||||||
current=True
|
|
||||||
for f in flist:
|
|
||||||
if not isCurrent(f,target):
|
|
||||||
current=False
|
|
||||||
break
|
|
||||||
if current:
|
|
||||||
print("%s is up to date"%target)
|
|
||||||
return
|
|
||||||
print("creating %s"%target)
|
|
||||||
with gzip.open(target,"wb") as oh:
|
|
||||||
for fn in flist:
|
|
||||||
print("adding %s to %s"%(fn,target))
|
|
||||||
with open(fn,"rb") as rh:
|
|
||||||
shutil.copyfileobj(rh,oh)
|
|
||||||
|
|
||||||
|
|
||||||
OWNLIBS=getLibs()+["FS","WiFi"]
|
|
||||||
GLOBAL_INCLUDES=[]
|
|
||||||
|
|
||||||
def handleDeps(env):
|
|
||||||
#overwrite the GetProjectConfig
|
|
||||||
#to inject all our libs
|
|
||||||
oldGetProjectConfig=env.GetProjectConfig
|
|
||||||
def GetProjectConfigX(env):
|
|
||||||
rt=oldGetProjectConfig()
|
|
||||||
cenv="env:"+env['PIOENV']
|
|
||||||
libs=[]
|
|
||||||
for section,options in rt.as_tuple():
|
|
||||||
if section == cenv:
|
|
||||||
for key,values in options:
|
|
||||||
if key == 'lib_deps':
|
|
||||||
libs=values
|
|
||||||
|
|
||||||
mustUpdate=False
|
|
||||||
for lib in OWNLIBS:
|
|
||||||
if not lib in libs:
|
|
||||||
libs.append(lib)
|
|
||||||
mustUpdate=True
|
|
||||||
if mustUpdate:
|
|
||||||
update=[(cenv,[('lib_deps',libs)])]
|
|
||||||
rt.update(update)
|
|
||||||
return rt
|
|
||||||
env.AddMethod(GetProjectConfigX,"GetProjectConfig")
|
|
||||||
#store the list of all includes after we resolved
|
|
||||||
#the dependencies for our main project
|
|
||||||
#we will use them for all compilations afterwards
|
|
||||||
oldLibBuilder=env.ConfigureProjectLibBuilder
|
|
||||||
def ConfigureProjectLibBuilderX(env):
|
|
||||||
global GLOBAL_INCLUDES
|
|
||||||
project=oldLibBuilder()
|
|
||||||
#print("##ConfigureProjectLibBuilderX")
|
|
||||||
#pprint.pprint(project)
|
|
||||||
if project.depbuilders:
|
|
||||||
#print("##depbuilders %s"%",".join(map(lambda x: x.path,project.depbuilders)))
|
|
||||||
for db in project.depbuilders:
|
|
||||||
idirs=db.get_include_dirs()
|
|
||||||
for id in idirs:
|
|
||||||
if not id in GLOBAL_INCLUDES:
|
|
||||||
GLOBAL_INCLUDES.append(id)
|
|
||||||
return project
|
|
||||||
env.AddMethod(ConfigureProjectLibBuilderX,"ConfigureProjectLibBuilder")
|
|
||||||
def injectIncludes(env,node):
|
|
||||||
return env.Object(
|
|
||||||
node,
|
|
||||||
CPPPATH=env["CPPPATH"]+GLOBAL_INCLUDES
|
|
||||||
)
|
|
||||||
env.AddBuildMiddleware(injectIncludes)
|
|
||||||
|
|
||||||
|
|
||||||
def prebuild(env):
|
|
||||||
global userTaskDirs
|
|
||||||
print("#prebuild running")
|
|
||||||
if not checkDir():
|
|
||||||
sys.exit(1)
|
|
||||||
ldf_mode=env.GetProjectOption("lib_ldf_mode")
|
|
||||||
if ldf_mode == 'off':
|
|
||||||
print("##ldf off - own dependency handling")
|
|
||||||
handleDeps(env)
|
|
||||||
userTaskDirs=getUserTaskDirs()
|
|
||||||
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
|
|
||||||
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
|
|
||||||
compressFile(mergedConfig,mergedConfig+".gz")
|
|
||||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False)
|
|
||||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True)
|
|
||||||
joinFiles(os.path.join(outPath(),INDEXJS+".gz"),INDEXJS,["web"]+userTaskDirs)
|
|
||||||
joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),INDEXCSS,["web"]+userTaskDirs)
|
|
||||||
embedded=getEmbeddedFiles(env)
|
|
||||||
filedefs=[]
|
|
||||||
for ef in embedded:
|
|
||||||
print("#checking embedded file %s"%ef)
|
|
||||||
(dn,fn)=os.path.split(ef)
|
|
||||||
pureName=fn
|
|
||||||
if pureName.endswith('.gz'):
|
|
||||||
pureName=pureName[0:-3]
|
|
||||||
ct=getContentType(pureName)
|
|
||||||
usname=ef.replace('/','_').replace('.','_')
|
|
||||||
filedefs.append((pureName,usname,ct))
|
|
||||||
inFile=os.path.join(basePath(),"web",pureName)
|
|
||||||
if os.path.exists(inFile):
|
|
||||||
compressFile(inFile,ef)
|
|
||||||
else:
|
|
||||||
print("#WARNING: infile %s for %s not found"%(inFile,ef))
|
|
||||||
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
|
|
||||||
genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
|
|
||||||
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
|
|
||||||
generateFile(os.path.join(basePath(),GROVE_CONFIG_IN),os.path.join(outPath(),GROVE_CONFIG),generateGroveDefs,inMode='r')
|
|
||||||
version="dev"+datetime.now().strftime("%Y%m%d")
|
|
||||||
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
|
|
||||||
|
|
||||||
def cleangenerated(source, target, env):
|
|
||||||
od=outPath()
|
|
||||||
if os.path.isdir(od):
|
|
||||||
print("#cleaning up %s"%od)
|
|
||||||
for f in os.listdir(od):
|
|
||||||
if f == "." or f == "..":
|
|
||||||
continue
|
|
||||||
fn=os.path.join(od,f)
|
|
||||||
os.unlink(f)
|
|
||||||
|
|
||||||
|
|
||||||
print("#prescript...")
|
|
||||||
prebuild(env)
|
|
||||||
board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper()
|
|
||||||
print("Board=#%s#"%board)
|
|
||||||
print("BuildFlags=%s"%(" ".join(env["BUILD_FLAGS"])))
|
|
||||||
env.Append(
|
|
||||||
LINKFLAGS=[ "-u", "custom_app_desc" ],
|
|
||||||
CPPDEFINES=[(board,"1")]
|
|
||||||
)
|
|
||||||
#script does not run on clean yet - maybe in the future
|
|
||||||
env.AddPostAction("clean",cleangenerated)
|
|
||||||
|
|
||||||
#look for extra task scripts and include them here
|
|
||||||
for taskdir in userTaskDirs:
|
|
||||||
script = os.path.join(taskdir, "extra_task.py")
|
|
||||||
if os.path.isfile(script):
|
|
||||||
taskname = os.path.basename(os.path.normpath(taskdir))
|
|
||||||
print("#extra task script for '{}'".format(taskname))
|
|
||||||
with open(script) as fh:
|
|
||||||
try:
|
|
||||||
code = compile(fh.read(), taskname, 'exec')
|
|
||||||
except SyntaxError:
|
|
||||||
print("#ERROR: script does not compile")
|
|
||||||
continue
|
|
||||||
exec(code)
|
|
||||||
@@ -1,518 +0,0 @@
|
|||||||
print("running extra...")
|
|
||||||
import gzip
|
|
||||||
import shutil
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import inspect
|
|
||||||
import json
|
|
||||||
import glob
|
|
||||||
from datetime import datetime
|
|
||||||
import re
|
|
||||||
import pprint
|
|
||||||
from platformio.project.config import ProjectConfig
|
|
||||||
|
|
||||||
|
|
||||||
Import("env")
|
|
||||||
#print(env.Dump())
|
|
||||||
OWN_FILE="extra_script.py"
|
|
||||||
GEN_DIR='lib/generated'
|
|
||||||
CFG_FILE='web/config.json'
|
|
||||||
XDR_FILE='web/xdrconfig.json'
|
|
||||||
INDEXJS="index.js"
|
|
||||||
INDEXCSS="index.css"
|
|
||||||
CFG_INCLUDE='GwConfigDefinitions.h'
|
|
||||||
CFG_INCLUDE_IMPL='GwConfigDefImpl.h'
|
|
||||||
XDR_INCLUDE='GwXdrTypeMappings.h'
|
|
||||||
TASK_INCLUDE='GwUserTasks.h'
|
|
||||||
GROVE_CONFIG="GwM5GroveGen.h"
|
|
||||||
GROVE_CONFIG_IN="lib/hardware/GwM5Grove.in"
|
|
||||||
EMBEDDED_INCLUDE="GwEmbeddedFiles.h"
|
|
||||||
|
|
||||||
def getEmbeddedFiles(env):
|
|
||||||
rt=[]
|
|
||||||
efiles=env.GetProjectOption("board_build.embed_files")
|
|
||||||
for f in efiles.split("\n"):
|
|
||||||
if f == '':
|
|
||||||
continue
|
|
||||||
rt.append(f)
|
|
||||||
return rt
|
|
||||||
|
|
||||||
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)
|
|
||||||
if (otime >= itime):
|
|
||||||
own=os.path.join(basePath(),OWN_FILE)
|
|
||||||
if os.path.exists(own):
|
|
||||||
owntime=os.path.getmtime(own)
|
|
||||||
if owntime > otime:
|
|
||||||
return False
|
|
||||||
print("%s is newer then %s, no need to recreate"%(outfile,infile))
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
def compressFile(inFile,outfile):
|
|
||||||
if isCurrent(inFile,outfile):
|
|
||||||
return
|
|
||||||
print("compressing %s"%inFile)
|
|
||||||
with open(inFile, 'rb') as f_in:
|
|
||||||
with gzip.open(outfile, 'wb') as f_out:
|
|
||||||
shutil.copyfileobj(f_in, f_out)
|
|
||||||
|
|
||||||
def generateFile(infile,outfile,callback,inMode='rb',outMode='w'):
|
|
||||||
if isCurrent(infile,outfile):
|
|
||||||
return
|
|
||||||
print("creating %s"%outfile)
|
|
||||||
oh=None
|
|
||||||
with open(infile,inMode) as ch:
|
|
||||||
with open(outfile,outMode) as oh:
|
|
||||||
try:
|
|
||||||
callback(ch,oh,inFile=infile)
|
|
||||||
oh.close()
|
|
||||||
except Exception as e:
|
|
||||||
try:
|
|
||||||
oh.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
os.unlink(outfile)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def writeFileIfChanged(fileName,data):
|
|
||||||
if os.path.exists(fileName):
|
|
||||||
with open(fileName,"r") as ih:
|
|
||||||
old=ih.read()
|
|
||||||
ih.close()
|
|
||||||
if old == data:
|
|
||||||
return False
|
|
||||||
print("#generating %s"%fileName)
|
|
||||||
with open(fileName,"w") as oh:
|
|
||||||
oh.write(data)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def mergeConfig(base,other):
|
|
||||||
for bdir in other:
|
|
||||||
cname=os.path.join(bdir,"config.json")
|
|
||||||
if os.path.exists(cname):
|
|
||||||
print("merge config %s"%cname)
|
|
||||||
with open(cname,'rb') as ah:
|
|
||||||
merge=json.load(ah)
|
|
||||||
base=base+merge
|
|
||||||
return base
|
|
||||||
|
|
||||||
def replaceTexts(data,replacements):
|
|
||||||
if replacements is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data,str):
|
|
||||||
for k,v in replacements.items():
|
|
||||||
data=data.replace("$"+k,str(v))
|
|
||||||
return data
|
|
||||||
if isinstance(data,list):
|
|
||||||
rt=[]
|
|
||||||
for e in data:
|
|
||||||
rt.append(replaceTexts(e,replacements))
|
|
||||||
return rt
|
|
||||||
if isinstance(data,dict):
|
|
||||||
rt={}
|
|
||||||
for k,v in data.items():
|
|
||||||
rt[replaceTexts(k,replacements)]=replaceTexts(v,replacements)
|
|
||||||
return rt
|
|
||||||
return data
|
|
||||||
def expandConfig(config):
|
|
||||||
rt=[]
|
|
||||||
for item in config:
|
|
||||||
type=item.get('type')
|
|
||||||
if type != 'array':
|
|
||||||
rt.append(item)
|
|
||||||
continue
|
|
||||||
replacements=item.get('replace')
|
|
||||||
children=item.get('children')
|
|
||||||
name=item.get('name')
|
|
||||||
if name is None:
|
|
||||||
name="#unknown#"
|
|
||||||
if not isinstance(replacements,list):
|
|
||||||
raise Exception("missing replacements at array %s"%name)
|
|
||||||
for replace in replacements:
|
|
||||||
if children is not None:
|
|
||||||
for c in children:
|
|
||||||
rt.append(replaceTexts(c,replace))
|
|
||||||
return rt
|
|
||||||
|
|
||||||
def generateMergedConfig(inFile,outFile,addDirs=[]):
|
|
||||||
if not os.path.exists(inFile):
|
|
||||||
raise Exception("unable to read cfg file %s"%inFile)
|
|
||||||
data=""
|
|
||||||
with open(inFile,'rb') as ch:
|
|
||||||
config=json.load(ch)
|
|
||||||
config=mergeConfig(config,addDirs)
|
|
||||||
config=expandConfig(config)
|
|
||||||
data=json.dumps(config,indent=2)
|
|
||||||
writeFileIfChanged(outFile,data)
|
|
||||||
|
|
||||||
def generateCfg(inFile,outFile,impl):
|
|
||||||
if not os.path.exists(inFile):
|
|
||||||
raise Exception("unable to read cfg file %s"%inFile)
|
|
||||||
data=""
|
|
||||||
with open(inFile,'rb') as ch:
|
|
||||||
config=json.load(ch)
|
|
||||||
data+="//generated from %s\n"%inFile
|
|
||||||
l=len(config)
|
|
||||||
idx=0
|
|
||||||
if not impl:
|
|
||||||
data+='#include "GwConfigItem.h"\n'
|
|
||||||
data+='class GwConfigDefinitions{\n'
|
|
||||||
data+=' public:\n'
|
|
||||||
data+=' int getNumConfig() const{return %d;}\n'%(l)
|
|
||||||
for item in config:
|
|
||||||
n=item.get('name')
|
|
||||||
if n is None:
|
|
||||||
continue
|
|
||||||
if len(n) > 15:
|
|
||||||
raise Exception("%s: config names must be max 15 caracters"%n)
|
|
||||||
data+=' static constexpr const char* %s="%s";\n'%(n,n)
|
|
||||||
data+="};\n"
|
|
||||||
else:
|
|
||||||
data+='void GwConfigHandler::populateConfigs(GwConfigInterface **config){\n'
|
|
||||||
for item in config:
|
|
||||||
name=item.get('name')
|
|
||||||
if name is None:
|
|
||||||
continue
|
|
||||||
data+=' configs[%d]='%(idx)
|
|
||||||
idx+=1
|
|
||||||
secret="false";
|
|
||||||
if item.get('type') == 'password':
|
|
||||||
secret="true"
|
|
||||||
data+=" new GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret)
|
|
||||||
data+='}\n'
|
|
||||||
writeFileIfChanged(outFile,data)
|
|
||||||
|
|
||||||
def labelFilter(label):
|
|
||||||
return re.sub("[^a-zA-Z0-9]","",re.sub("\([0-9]*\)","",label))
|
|
||||||
def generateXdrMappings(fp,oh,inFile=''):
|
|
||||||
jdoc=json.load(fp)
|
|
||||||
oh.write("static GwXDRTypeMapping* typeMappings[]={\n")
|
|
||||||
first=True
|
|
||||||
for cat in jdoc:
|
|
||||||
item=jdoc[cat]
|
|
||||||
cid=item.get('id')
|
|
||||||
if cid is None:
|
|
||||||
continue
|
|
||||||
tc=item.get('type')
|
|
||||||
if tc is not None:
|
|
||||||
if first:
|
|
||||||
first=False
|
|
||||||
else:
|
|
||||||
oh.write(",\n")
|
|
||||||
oh.write(" new GwXDRTypeMapping(%d,0,%d) /*%s*/"%(cid,tc,cat))
|
|
||||||
fields=item.get('fields')
|
|
||||||
if fields is None:
|
|
||||||
continue
|
|
||||||
idx=0
|
|
||||||
for fe in fields:
|
|
||||||
if not isinstance(fe,dict):
|
|
||||||
continue
|
|
||||||
tc=fe.get('t')
|
|
||||||
id=fe.get('v')
|
|
||||||
if id is None:
|
|
||||||
id=idx
|
|
||||||
idx+=1
|
|
||||||
l=fe.get('l') or ''
|
|
||||||
if tc is None or id is None:
|
|
||||||
continue
|
|
||||||
if first:
|
|
||||||
first=False
|
|
||||||
else:
|
|
||||||
oh.write(",\n")
|
|
||||||
oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,id,tc,cat,l))
|
|
||||||
oh.write("\n")
|
|
||||||
oh.write("};\n")
|
|
||||||
for cat in jdoc:
|
|
||||||
item=jdoc[cat]
|
|
||||||
cid=item.get('id')
|
|
||||||
if cid is None:
|
|
||||||
continue
|
|
||||||
selectors=item.get('selector')
|
|
||||||
if selectors is not None:
|
|
||||||
for selector in selectors:
|
|
||||||
label=selector.get('l')
|
|
||||||
value=selector.get('v')
|
|
||||||
if label is not None and value is not None:
|
|
||||||
label=labelFilter(label)
|
|
||||||
define=("GWXDRSEL_%s_%s"%(cat,label)).upper()
|
|
||||||
oh.write(" #define %s %s\n"%(define,value))
|
|
||||||
fields=item.get('fields')
|
|
||||||
if fields is not None:
|
|
||||||
idx=0
|
|
||||||
for field in fields:
|
|
||||||
v=field.get('v')
|
|
||||||
if v is None:
|
|
||||||
v=idx
|
|
||||||
else:
|
|
||||||
v=int(v)
|
|
||||||
label=field.get('l')
|
|
||||||
if v is not None and label is not None:
|
|
||||||
define=("GWXDRFIELD_%s_%s"%(cat,labelFilter(label))).upper();
|
|
||||||
oh.write(" #define %s %s\n"%(define,str(v)))
|
|
||||||
idx+=1
|
|
||||||
|
|
||||||
class Grove:
|
|
||||||
def __init__(self,name) -> None:
|
|
||||||
self.name=name
|
|
||||||
def _ss(self,z=False):
|
|
||||||
if z:
|
|
||||||
return self.name
|
|
||||||
return self.name if self.name is not 'Z' else ''
|
|
||||||
def _suffix(self):
|
|
||||||
return '_'+self.name if self.name is not 'Z' else ''
|
|
||||||
def replace(self,line):
|
|
||||||
if line is None:
|
|
||||||
return line
|
|
||||||
return line.replace('$G$',self._ss()).replace('$Z$',self._ss(True)).replace('$GS$',self._suffix())
|
|
||||||
def generateGroveDefs(inh,outh,inFile=''):
|
|
||||||
GROVES=[Grove('Z'),Grove('A'),Grove('B'),Grove('C')]
|
|
||||||
definition=[]
|
|
||||||
started=False
|
|
||||||
def writeConfig():
|
|
||||||
for grove in GROVES:
|
|
||||||
for cl in definition:
|
|
||||||
outh.write(grove.replace(cl))
|
|
||||||
|
|
||||||
for line in inh:
|
|
||||||
if re.match(" *#GROVE",line):
|
|
||||||
started=True
|
|
||||||
if len(definition) > 0:
|
|
||||||
writeConfig()
|
|
||||||
definition=[]
|
|
||||||
continue
|
|
||||||
if started:
|
|
||||||
definition.append(line)
|
|
||||||
if len(definition) > 0:
|
|
||||||
writeConfig()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
userTaskDirs=[]
|
|
||||||
|
|
||||||
def getUserTaskDirs():
|
|
||||||
rt=[]
|
|
||||||
taskdirs=glob.glob(os.path.join( basePath(),'lib','*task*'))
|
|
||||||
for task in taskdirs:
|
|
||||||
rt.append(task)
|
|
||||||
return rt
|
|
||||||
|
|
||||||
def checkAndAdd(file,names,ilist):
|
|
||||||
if not file.endswith('.h'):
|
|
||||||
return
|
|
||||||
match=False
|
|
||||||
for cmp in names:
|
|
||||||
#print("##check %s<->%s"%(f.lower(),cmp))
|
|
||||||
if file.lower() == cmp:
|
|
||||||
match=True
|
|
||||||
if not match:
|
|
||||||
return
|
|
||||||
ilist.append(file)
|
|
||||||
def genereateUserTasks(outfile):
|
|
||||||
includes=[]
|
|
||||||
for task in userTaskDirs:
|
|
||||||
#print("##taskdir=%s"%task)
|
|
||||||
base=os.path.basename(task)
|
|
||||||
includeNames=[base.lower()+".h",'gw'+base.lower()+'.h']
|
|
||||||
for f in os.listdir(task):
|
|
||||||
checkAndAdd(f,includeNames,includes)
|
|
||||||
includeData=""
|
|
||||||
for i in includes:
|
|
||||||
print("#task include %s"%i)
|
|
||||||
includeData+="#include <%s>\n"%i
|
|
||||||
writeFileIfChanged(outfile,includeData)
|
|
||||||
|
|
||||||
def generateEmbedded(elist,outFile):
|
|
||||||
content=""
|
|
||||||
for entry in elist:
|
|
||||||
content+="EMBED_GZ_FILE(\"%s\",%s,\"%s\");\n"%entry
|
|
||||||
writeFileIfChanged(outFile,content)
|
|
||||||
|
|
||||||
def getContentType(fn):
|
|
||||||
if (fn.endswith('.gz')):
|
|
||||||
fn=fn[0:-3]
|
|
||||||
if (fn.endswith('html')):
|
|
||||||
return "text/html"
|
|
||||||
if (fn.endswith('json')):
|
|
||||||
return "application/json"
|
|
||||||
if (fn.endswith('js')):
|
|
||||||
return "text/javascript"
|
|
||||||
if (fn.endswith('css')):
|
|
||||||
return "text/css"
|
|
||||||
return "application/octet-stream"
|
|
||||||
|
|
||||||
|
|
||||||
def getLibs():
|
|
||||||
base=os.path.join(basePath(),"lib")
|
|
||||||
rt=[]
|
|
||||||
for sd in os.listdir(base):
|
|
||||||
if sd == '..':
|
|
||||||
continue
|
|
||||||
if sd == '.':
|
|
||||||
continue
|
|
||||||
fn=os.path.join(base,sd)
|
|
||||||
if os.path.isdir(fn):
|
|
||||||
rt.append(sd)
|
|
||||||
EXTRAS=['generated']
|
|
||||||
for e in EXTRAS:
|
|
||||||
if not e in rt:
|
|
||||||
rt.append(e)
|
|
||||||
return rt
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def joinFiles(target,pattern,dirlist):
|
|
||||||
flist=[]
|
|
||||||
for dir in dirlist:
|
|
||||||
fn=os.path.join(dir,pattern)
|
|
||||||
if os.path.exists(fn):
|
|
||||||
flist.append(fn)
|
|
||||||
current=False
|
|
||||||
if os.path.exists(target):
|
|
||||||
current=True
|
|
||||||
for f in flist:
|
|
||||||
if not isCurrent(f,target):
|
|
||||||
current=False
|
|
||||||
break
|
|
||||||
if current:
|
|
||||||
print("%s is up to date"%target)
|
|
||||||
return
|
|
||||||
print("creating %s"%target)
|
|
||||||
with gzip.open(target,"wb") as oh:
|
|
||||||
for fn in flist:
|
|
||||||
print("adding %s to %s"%(fn,target))
|
|
||||||
with open(fn,"rb") as rh:
|
|
||||||
shutil.copyfileobj(rh,oh)
|
|
||||||
|
|
||||||
|
|
||||||
OWNLIBS=getLibs()+["FS","WiFi"]
|
|
||||||
GLOBAL_INCLUDES=[]
|
|
||||||
|
|
||||||
def handleDeps(env):
|
|
||||||
#overwrite the GetProjectConfig
|
|
||||||
#to inject all our libs
|
|
||||||
oldGetProjectConfig=env.GetProjectConfig
|
|
||||||
def GetProjectConfigX(env):
|
|
||||||
rt=oldGetProjectConfig()
|
|
||||||
cenv="env:"+env['PIOENV']
|
|
||||||
libs=[]
|
|
||||||
for section,options in rt.as_tuple():
|
|
||||||
if section == cenv:
|
|
||||||
for key,values in options:
|
|
||||||
if key == 'lib_deps':
|
|
||||||
libs=values
|
|
||||||
|
|
||||||
mustUpdate=False
|
|
||||||
for lib in OWNLIBS:
|
|
||||||
if not lib in libs:
|
|
||||||
libs.append(lib)
|
|
||||||
mustUpdate=True
|
|
||||||
if mustUpdate:
|
|
||||||
update=[(cenv,[('lib_deps',libs)])]
|
|
||||||
rt.update(update)
|
|
||||||
return rt
|
|
||||||
env.AddMethod(GetProjectConfigX,"GetProjectConfig")
|
|
||||||
#store the list of all includes after we resolved
|
|
||||||
#the dependencies for our main project
|
|
||||||
#we will use them for all compilations afterwards
|
|
||||||
oldLibBuilder=env.ConfigureProjectLibBuilder
|
|
||||||
def ConfigureProjectLibBuilderX(env):
|
|
||||||
global GLOBAL_INCLUDES
|
|
||||||
project=oldLibBuilder()
|
|
||||||
#print("##ConfigureProjectLibBuilderX")
|
|
||||||
#pprint.pprint(project)
|
|
||||||
if project.depbuilders:
|
|
||||||
#print("##depbuilders %s"%",".join(map(lambda x: x.path,project.depbuilders)))
|
|
||||||
for db in project.depbuilders:
|
|
||||||
idirs=db.get_include_dirs()
|
|
||||||
for id in idirs:
|
|
||||||
if not id in GLOBAL_INCLUDES:
|
|
||||||
GLOBAL_INCLUDES.append(id)
|
|
||||||
return project
|
|
||||||
env.AddMethod(ConfigureProjectLibBuilderX,"ConfigureProjectLibBuilder")
|
|
||||||
def injectIncludes(env,node):
|
|
||||||
return env.Object(
|
|
||||||
node,
|
|
||||||
CPPPATH=env["CPPPATH"]+GLOBAL_INCLUDES
|
|
||||||
)
|
|
||||||
env.AddBuildMiddleware(injectIncludes)
|
|
||||||
|
|
||||||
|
|
||||||
def prebuild(env):
|
|
||||||
global userTaskDirs
|
|
||||||
print("#prebuild running")
|
|
||||||
if not checkDir():
|
|
||||||
sys.exit(1)
|
|
||||||
ldf_mode=env.GetProjectOption("lib_ldf_mode")
|
|
||||||
if ldf_mode == 'off':
|
|
||||||
print("##ldf off - own dependency handling")
|
|
||||||
handleDeps(env)
|
|
||||||
userTaskDirs=getUserTaskDirs()
|
|
||||||
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
|
|
||||||
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
|
|
||||||
compressFile(mergedConfig,mergedConfig+".gz")
|
|
||||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False)
|
|
||||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True)
|
|
||||||
joinFiles(os.path.join(outPath(),INDEXJS+".gz"),INDEXJS,["web"]+userTaskDirs)
|
|
||||||
joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),INDEXCSS,["web"]+userTaskDirs)
|
|
||||||
embedded=getEmbeddedFiles(env)
|
|
||||||
filedefs=[]
|
|
||||||
for ef in embedded:
|
|
||||||
print("#checking embedded file %s"%ef)
|
|
||||||
(dn,fn)=os.path.split(ef)
|
|
||||||
pureName=fn
|
|
||||||
if pureName.endswith('.gz'):
|
|
||||||
pureName=pureName[0:-3]
|
|
||||||
ct=getContentType(pureName)
|
|
||||||
usname=ef.replace('/','_').replace('.','_')
|
|
||||||
filedefs.append((pureName,usname,ct))
|
|
||||||
inFile=os.path.join(basePath(),"web",pureName)
|
|
||||||
if os.path.exists(inFile):
|
|
||||||
compressFile(inFile,ef)
|
|
||||||
else:
|
|
||||||
print("#WARNING: infile %s for %s not found"%(inFile,ef))
|
|
||||||
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
|
|
||||||
genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
|
|
||||||
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
|
|
||||||
generateFile(os.path.join(basePath(),GROVE_CONFIG_IN),os.path.join(outPath(),GROVE_CONFIG),generateGroveDefs,inMode='r')
|
|
||||||
version="dev"+datetime.now().strftime("%Y%m%d")
|
|
||||||
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
|
|
||||||
|
|
||||||
def cleangenerated(source, target, env):
|
|
||||||
od=outPath()
|
|
||||||
if os.path.isdir(od):
|
|
||||||
print("#cleaning up %s"%od)
|
|
||||||
for f in os.listdir(od):
|
|
||||||
if f == "." or f == "..":
|
|
||||||
continue
|
|
||||||
fn=os.path.join(od,f)
|
|
||||||
os.unlink(f)
|
|
||||||
|
|
||||||
|
|
||||||
print("#prescript...")
|
|
||||||
prebuild(env)
|
|
||||||
board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper()
|
|
||||||
print("Board=#%s#"%board)
|
|
||||||
print("BuildFlags=%s"%(" ".join(env["BUILD_FLAGS"])))
|
|
||||||
env.Append(
|
|
||||||
LINKFLAGS=[ "-u", "custom_app_desc" ],
|
|
||||||
CPPDEFINES=[(board,"1")]
|
|
||||||
)
|
|
||||||
#script does not run on clean yet - maybe in the future
|
|
||||||
env.AddPostAction("clean",cleangenerated)
|
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
#define _GWWIFI_H
|
#define _GWWIFI_H
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <GWConfig.h>
|
#include <GWConfig.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
|
||||||
class GwWifi{
|
class GwWifi{
|
||||||
private:
|
private:
|
||||||
const GwConfigHandler *config;
|
const GwConfigHandler *config;
|
||||||
@@ -16,13 +19,19 @@ class GwWifi{
|
|||||||
bool apActive=false;
|
bool apActive=false;
|
||||||
bool fixedApPass=true;
|
bool fixedApPass=true;
|
||||||
bool clientIsConnected=false;
|
bool clientIsConnected=false;
|
||||||
|
SemaphoreHandle_t wifiMutex=nullptr;
|
||||||
|
static const TickType_t WIFI_MUTEX_TIMEOUT=pdMS_TO_TICKS(1000);
|
||||||
|
bool acquireMutex();
|
||||||
|
void releaseMutex();
|
||||||
public:
|
public:
|
||||||
const char *AP_password = "esp32nmea2k";
|
const char *AP_password = "esp32nmea2k";
|
||||||
GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass=true);
|
GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass=true);
|
||||||
|
~GwWifi();
|
||||||
void setup();
|
void setup();
|
||||||
void loop();
|
void loop();
|
||||||
bool clientConnected();
|
bool clientConnected();
|
||||||
bool connectClient();
|
bool connectClient(); // Blocking version
|
||||||
|
bool connectClientAsync(); // Non-blocking version for other tasks
|
||||||
String apIP();
|
String apIP();
|
||||||
bool isApActive(){return apActive;}
|
bool isApActive(){return apActive;}
|
||||||
bool isClientActive(){return wifiClient->asBoolean();}
|
bool isClientActive(){return wifiClient->asBoolean();}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include <esp_wifi.h>
|
#include <esp_wifi.h>
|
||||||
#include "GWWifi.h"
|
#include "GWWifi.h"
|
||||||
|
|
||||||
|
|
||||||
GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
|
GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
|
||||||
this->config=config;
|
this->config=config;
|
||||||
this->logger=log;
|
this->logger=log;
|
||||||
@@ -9,6 +8,28 @@ GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
|
|||||||
wifiSSID=config->getConfigItem(config->wifiSSID,true);
|
wifiSSID=config->getConfigItem(config->wifiSSID,true);
|
||||||
wifiPass=config->getConfigItem(config->wifiPass,true);
|
wifiPass=config->getConfigItem(config->wifiPass,true);
|
||||||
this->fixedApPass=fixedApPass;
|
this->fixedApPass=fixedApPass;
|
||||||
|
wifiMutex=xSemaphoreCreateMutex();
|
||||||
|
if (wifiMutex==nullptr){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: unable to create mutex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GwWifi::~GwWifi(){
|
||||||
|
if (wifiMutex!=nullptr){
|
||||||
|
vSemaphoreDelete(wifiMutex);
|
||||||
|
wifiMutex=nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GwWifi::acquireMutex(){
|
||||||
|
if (wifiMutex==nullptr) return false;
|
||||||
|
return xSemaphoreTake(wifiMutex,WIFI_MUTEX_TIMEOUT)==pdTRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GwWifi::releaseMutex(){
|
||||||
|
if (wifiMutex!=nullptr){
|
||||||
|
xSemaphoreGive(wifiMutex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void GwWifi::setup(){
|
void GwWifi::setup(){
|
||||||
LOG_DEBUG(GwLog::LOG,"Wifi setup");
|
LOG_DEBUG(GwLog::LOG,"Wifi setup");
|
||||||
@@ -85,8 +106,14 @@ bool GwWifi::connectInternal(){
|
|||||||
if (wifiClient->asBoolean()){
|
if (wifiClient->asBoolean()){
|
||||||
clientIsConnected=false;
|
clientIsConnected=false;
|
||||||
LOG_DEBUG(GwLog::LOG,"creating wifiClient ssid=%s",wifiSSID->asString().c_str());
|
LOG_DEBUG(GwLog::LOG,"creating wifiClient ssid=%s",wifiSSID->asString().c_str());
|
||||||
|
// CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden
|
||||||
|
if (!acquireMutex()){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in connectInternal");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
WiFi.setAutoReconnect(false); //#102
|
WiFi.setAutoReconnect(false); //#102
|
||||||
wl_status_t rt=WiFi.begin(wifiSSID->asCString(),wifiPass->asCString());
|
wl_status_t rt=WiFi.begin(wifiSSID->asCString(),wifiPass->asCString());
|
||||||
|
releaseMutex();
|
||||||
LOG_DEBUG(GwLog::LOG,"wifiClient connect returns %d",(int)rt);
|
LOG_DEBUG(GwLog::LOG,"wifiClient connect returns %d",(int)rt);
|
||||||
lastConnectStart=millis();
|
lastConnectStart=millis();
|
||||||
return true;
|
return true;
|
||||||
@@ -104,8 +131,20 @@ void GwWifi::loop(){
|
|||||||
if (lastConnectStart > now || (lastConnectStart + RETRY_MILLIS) < now)
|
if (lastConnectStart > now || (lastConnectStart + RETRY_MILLIS) < now)
|
||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString());
|
LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString());
|
||||||
WiFi.disconnect();
|
|
||||||
connectInternal();
|
// CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden
|
||||||
|
if (acquireMutex()){
|
||||||
|
WiFi.disconnect(true);
|
||||||
|
delay(300);
|
||||||
|
esp_wifi_stop();
|
||||||
|
delay(100);
|
||||||
|
esp_wifi_start();
|
||||||
|
releaseMutex();
|
||||||
|
connectInternal();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
@@ -126,11 +165,42 @@ void GwWifi::loop(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GwWifi::clientConnected(){
|
bool GwWifi::clientConnected(){
|
||||||
return WiFi.status() == WL_CONNECTED;
|
// CRITICAL SECTION: WiFi.status() muss geschützt werden
|
||||||
|
if (!acquireMutex()){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in clientConnected");
|
||||||
|
return false; // Conservative: nehme an, nicht verbunden
|
||||||
|
}
|
||||||
|
bool result = WiFi.status() == WL_CONNECTED;
|
||||||
|
releaseMutex();
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool GwWifi::connectClient(){
|
bool GwWifi::connectClient(){
|
||||||
|
// CRITICAL SECTION: Disconnect und Connect müssen atomar sein
|
||||||
|
if (!acquireMutex()){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in connectClient");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
WiFi.disconnect();
|
WiFi.disconnect();
|
||||||
|
releaseMutex();
|
||||||
|
return connectInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GwWifi::connectClientAsync(){
|
||||||
|
// Non-blocking version: Versuche Mutex zu nehmen, gib aber sofort auf
|
||||||
|
// Ideal für Tasks, die nicht blockieren dürfen
|
||||||
|
if (wifiMutex==nullptr){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex not initialized in connectClientAsync");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (xSemaphoreTake(wifiMutex, 0)!=pdTRUE){
|
||||||
|
LOG_DEBUG(GwLog::LOG,"GwWifi: connectClientAsync skipped - WiFi busy");
|
||||||
|
return false; // WiFi ist aktuell busy, versuche es später nochmal
|
||||||
|
}
|
||||||
|
WiFi.disconnect();
|
||||||
|
xSemaphoreGive(wifiMutex);
|
||||||
return connectInternal();
|
return connectInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,30 @@ https://controllerstech.com/ws2812-leds-using-spi/
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
String Color::toHex() {
|
||||||
|
char hexColor[8];
|
||||||
|
sprintf(hexColor, "#%02X%02X%02X", r, g, b);
|
||||||
|
return String(hexColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
String Color::toName() {
|
||||||
|
static std::map<int, String> const names = {
|
||||||
|
{0xff0000, "Red"},
|
||||||
|
{0x00ff00, "Green"},
|
||||||
|
{0x0000ff, "Blue",},
|
||||||
|
{0xff9900, "Orange"},
|
||||||
|
{0xffff00, "Yellow"},
|
||||||
|
{0x3366ff, "Aqua"},
|
||||||
|
{0xff0066, "Violet"},
|
||||||
|
{0xffffff, "White"}
|
||||||
|
};
|
||||||
|
int color = (r << 16) + (g << 8) + b;
|
||||||
|
auto it = names.find(color);
|
||||||
|
if (it == names.end()) {
|
||||||
|
return toHex();
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
static uint8_t mulcolor(uint8_t f1, uint8_t f2){
|
static uint8_t mulcolor(uint8_t f1, uint8_t f2){
|
||||||
uint16_t rt=f1;
|
uint16_t rt=f1;
|
||||||
@@ -231,4 +255,4 @@ void handleSpiLeds(void *param){
|
|||||||
|
|
||||||
void createSpiLedTask(LedTaskData *param){
|
void createSpiLedTask(LedTaskData *param){
|
||||||
xTaskCreate(handleSpiLeds,"handleLeds",4000,param,3,NULL);
|
xTaskCreate(handleSpiLeds,"handleLeds",4000,param,3,NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class Color{
|
|||||||
uint8_t g;
|
uint8_t g;
|
||||||
uint8_t b;
|
uint8_t b;
|
||||||
Color():r(0),g(0),b(0){}
|
Color():r(0),g(0),b(0){}
|
||||||
Color(uint8_t cr, uint8_t cg,uint8_t cb):
|
Color(uint8_t cr, uint8_t cg, uint8_t cb):
|
||||||
b(cb),g(cg),r(cr){}
|
b(cb),g(cg),r(cr){}
|
||||||
Color(const Color &o):b(o.b),g(o.g),r(o.r){}
|
Color(const Color &o):b(o.b),g(o.g),r(o.r){}
|
||||||
bool equal(const Color &o) const{
|
bool equal(const Color &o) const{
|
||||||
@@ -22,6 +22,8 @@ class Color{
|
|||||||
bool operator != (const Color &other) const{
|
bool operator != (const Color &other) const{
|
||||||
return ! equal(other);
|
return ! equal(other);
|
||||||
}
|
}
|
||||||
|
String toHex();
|
||||||
|
String toName();
|
||||||
};
|
};
|
||||||
|
|
||||||
static Color COLOR_GREEN=Color(0,255,0);
|
static Color COLOR_GREEN=Color(0,255,0);
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
#include "NetworkClient.h"
|
#include "NetworkClient.h"
|
||||||
|
#include "GWWifi.h" // WiFi management (thread-safe)
|
||||||
|
|
||||||
|
extern GwWifi gwWifi; // Extern declaration of global WiFi instance
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "puff.h"
|
#include "puff.h"
|
||||||
@@ -51,8 +54,13 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
|
|||||||
const size_t capacity = READLIMIT; // Read limit for data (can be adjusted in NetworkClient.h)
|
const size_t capacity = READLIMIT; // Read limit for data (can be adjusted in NetworkClient.h)
|
||||||
uint8_t* buffer = (uint8_t*)malloc(capacity);
|
uint8_t* buffer = (uint8_t*)malloc(capacity);
|
||||||
|
|
||||||
|
if (!gwWifi.clientConnected()) {
|
||||||
|
if (DEBUGING) {Serial.println("No WiFi connection");}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
if (DEBUG) {Serial.println("Malloc failed (buffer");}
|
if (DEBUGING) {Serial.println("Malloc failed buffer");}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +114,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
|
|||||||
len += read;
|
len += read;
|
||||||
lastData = millis();
|
lastData = millis();
|
||||||
|
|
||||||
if (DEBUG) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
|
if (DEBUGING) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
|
||||||
|
|
||||||
if (len < 20) continue; // Not enough data for header
|
if (len < 20) continue; // Not enough data for header
|
||||||
|
|
||||||
@@ -122,7 +130,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
|
|||||||
|
|
||||||
int res = puff(test, &testLen, buffer + headerOffset, &srcLen);
|
int res = puff(test, &testLen, buffer + headerOffset, &srcLen);
|
||||||
if (res == 0) {
|
if (res == 0) {
|
||||||
if (DEBUG) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
|
if (DEBUGING) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
|
||||||
outData = test;
|
outData = test;
|
||||||
outLen = testLen;
|
outLen = testLen;
|
||||||
complete = true;
|
complete = true;
|
||||||
@@ -167,7 +175,7 @@ bool NetworkClient::fetchAndDecompressJson(const String& url) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DEBUG) {Serial.println("JSON OK!");}
|
if (DEBUGING) {Serial.println("JSON OK!");}
|
||||||
_valid = true;
|
_valid = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <HTTPClient.h>
|
#include <HTTPClient.h>
|
||||||
|
|
||||||
#define DEBUG false // Debug flag for NetworkClient for more live information
|
#define DEBUGING false // Debug flag for NetworkClient for more live information
|
||||||
#define READLIMIT 200000 // HTTP read limit in byte for gzip content (can be adjusted)
|
#define READLIMIT 200000 // HTTP read limit in byte for gzip content (can be adjusted)
|
||||||
#define CONNECTIONTIMEOUT 3000 // Timeout in ms for HTTP connection
|
#define CONNECTIONTIMEOUT 3000 // Timeout in ms for HTTP connection
|
||||||
#define TCPREADTIMEOUT 2000 // Timeout in ms for read HTTP client stack
|
#define TCPREADTIMEOUT 2000 // Timeout in ms for read HTTP client stack
|
||||||
|
|||||||
@@ -923,7 +923,7 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor){
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generator graphic with fill level
|
// Generator graphic
|
||||||
void generatorGraphic(uint x, uint y, int pcolor, int bcolor){
|
void generatorGraphic(uint x, uint y, int pcolor, int bcolor){
|
||||||
// Show battery
|
// Show battery
|
||||||
int xb = x; // X position
|
int xb = x; // X position
|
||||||
@@ -940,6 +940,74 @@ void generatorGraphic(uint x, uint y, int pcolor, int bcolor){
|
|||||||
getdisplay().print("G");
|
getdisplay().print("G");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display rudder position as horizontal bargraph with configurable +/- range (degrees)
|
||||||
|
void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg){
|
||||||
|
const int w = 360;
|
||||||
|
const int h = 20;
|
||||||
|
const int t = 3; // Line thickness
|
||||||
|
const int halfw = w/2;
|
||||||
|
const int halfh = h/2;
|
||||||
|
// Calculate top-left of bar (cx,cy are center of 0°)
|
||||||
|
int left = int(cx) - halfw;
|
||||||
|
int top = int(cy) - halfh;
|
||||||
|
|
||||||
|
// clamp provided range to allowed bounds [10,45]
|
||||||
|
if (rangeDeg < 10) rangeDeg = 10;
|
||||||
|
if (rangeDeg > 45) rangeDeg = 45;
|
||||||
|
|
||||||
|
// Pixels per degree for +/-rangeDeg -> total span = 2*rangeDeg
|
||||||
|
const float pxPerDeg = float(w) / (2.0f * float(rangeDeg));
|
||||||
|
|
||||||
|
// Draw outer border (thickness t)
|
||||||
|
for (int i = 0; i < t; i++) {
|
||||||
|
getdisplay().drawRect(left + i, top + i, w - 2 * i, h - 2 * i, fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill inner area with background
|
||||||
|
getdisplay().fillRect(left + t, top + t, w - 2 * t, h - 2 * t, bg);
|
||||||
|
|
||||||
|
// Draw center line
|
||||||
|
getdisplay().drawRect(cx - 1, top + 1, 3 , h - 2, fg);
|
||||||
|
|
||||||
|
// Clamp rudder position to -rangeDeg..rangeDeg
|
||||||
|
if (rudderPosition > (int)rangeDeg) rudderPosition = (int)rangeDeg;
|
||||||
|
if (rudderPosition < -((int)rangeDeg)) rudderPosition = -((int)rangeDeg);
|
||||||
|
|
||||||
|
// Compute fill width in pixels
|
||||||
|
int fillPx = int(round(rudderPosition * pxPerDeg)); // positive -> right
|
||||||
|
|
||||||
|
// Fill area from center to position (if non-zero)
|
||||||
|
int centerx = cx;
|
||||||
|
int innerTop = top + t;
|
||||||
|
int innerH = h - 2 * t;
|
||||||
|
if (fillPx > 0) {
|
||||||
|
// Right side
|
||||||
|
getdisplay().fillRect(centerx, innerTop, fillPx, innerH, fg);
|
||||||
|
} else if (fillPx < 0) {
|
||||||
|
// Left side
|
||||||
|
getdisplay().fillRect(centerx + fillPx, innerTop, -fillPx, innerH, fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Draw tick marks every 5° and labels outside the bar
|
||||||
|
getdisplay().setTextColor(fg);
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
for (int angle = -((int)rangeDeg); angle <= (int)rangeDeg; angle += 5) {
|
||||||
|
int xpos = int(round(centerx + angle * pxPerDeg));
|
||||||
|
// Vertical tick inside bar
|
||||||
|
getdisplay().drawLine(xpos, top, xpos, top + h + 2, fg);
|
||||||
|
// Label outside: below the bar
|
||||||
|
String lbl = String(angle);
|
||||||
|
int16_t bx, by;
|
||||||
|
uint16_t bw, bh;
|
||||||
|
getdisplay().getTextBounds(lbl, 0, 0, &bx, &by, &bw, &bh);
|
||||||
|
int16_t tx = xpos - bw/2;
|
||||||
|
int16_t ty = top + h + bh + 5; // A little spacing
|
||||||
|
getdisplay().setCursor(tx, ty);
|
||||||
|
getdisplay().print(lbl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Function to handle HTTP image request
|
// Function to handle HTTP image request
|
||||||
// http://192.168.15.1/api/user/OBP60Task/screenshot
|
// http://192.168.15.1/api/user/OBP60Task/screenshot
|
||||||
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request) {
|
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request) {
|
||||||
|
|||||||
@@ -128,6 +128,10 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor); // S
|
|||||||
void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic
|
void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic
|
||||||
void startLedTask(GwApi *api);
|
void startLedTask(GwApi *api);
|
||||||
|
|
||||||
|
// Display rudder position as horizontal bargraph with configurable +/- range (degrees)
|
||||||
|
// 'rangeDeg' is unsigned and will be clamped to [10,45]
|
||||||
|
void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg);
|
||||||
|
|
||||||
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request);
|
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request);
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
|
|||||||
@@ -835,7 +835,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
|||||||
result.cvalue = dplace;
|
result.cvalue = dplace;
|
||||||
}
|
}
|
||||||
//########################################################
|
//########################################################
|
||||||
else if (value->getFormat() == "formatXdr:A:D"){
|
else if ((value->getFormat() == "formatXdr:A:D") || ((value->getFormat() == "formatXdr:A:rd"))){
|
||||||
double angle = 0;
|
double angle = 0;
|
||||||
if (usesimudata == false) {
|
if (usesimudata == false) {
|
||||||
angle = value->value;
|
angle = value->value;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// General hardware definitions
|
// General hardware definitions
|
||||||
// CAN and RS485 bus pin definitions see obp60task.h
|
// CAN and RS485 bus pin definitions see obp60task.h
|
||||||
|
|
||||||
#ifdef HARDWARE_V21
|
#if defined HARDWARE_V20 || HARDWARE_V21
|
||||||
// Direction pin for RS485 NMEA0183
|
// Direction pin for RS485 NMEA0183
|
||||||
#define OBP_DIRECTION_PIN 18
|
#define OBP_DIRECTION_PIN 18
|
||||||
// I2C
|
// I2C
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ void initKeys(CommonData &commonData) {
|
|||||||
commonData.keydata[5].h = height;
|
commonData.keydata[5].h = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HARDWARE_V21
|
#if defined HARDWARE_V20 || HARDWARE_V21
|
||||||
// Keypad functions for original OBP60 hardware
|
// Keypad functions for original OBP60 hardware
|
||||||
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {
|
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
|||||||
double dataValue = 0;
|
double dataValue = 0;
|
||||||
std::string format = "";
|
std::string format = "";
|
||||||
|
|
||||||
// we test this earlier, but for safety reason ...
|
// we test this earlier, but for safety reasons ...
|
||||||
if (calibrationMap.find(instance) == calibrationMap.end()) {
|
if (calibrationMap.find(instance) == calibrationMap.end()) {
|
||||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
||||||
return false;
|
return false;
|
||||||
@@ -151,7 +151,7 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
|||||||
slope = calibrationMap[instance].slope;
|
slope = calibrationMap[instance].slope;
|
||||||
dataValue = boatDataValue->value;
|
dataValue = boatDataValue->value;
|
||||||
format = boatDataValue->getFormat().c_str();
|
format = boatDataValue->getFormat().c_str();
|
||||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: value: %f, format: %s", instance.c_str(), dataValue, format.c_str());
|
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: value: %f, format: %s", instance.c_str(), dataValue, format.c_str());
|
||||||
|
|
||||||
if (format == "formatWind") { // instance is of type angle
|
if (format == "formatWind") { // instance is of type angle
|
||||||
dataValue = (dataValue * slope) + offset;
|
dataValue = (dataValue * slope) + offset;
|
||||||
@@ -174,7 +174,7 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
|||||||
calibrationMap[instance].value = dataValue; // store the calibrated value in the list
|
calibrationMap[instance].value = dataValue; // store the calibrated value in the list
|
||||||
calibrationMap[instance].isCalibrated = true;
|
calibrationMap[instance].isCalibrated = true;
|
||||||
|
|
||||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: Offset: %f, Slope: %f, Result: %f", instance.c_str(), offset, slope, calibrationMap[instance].value);
|
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: Offset: %f, Slope: %f, Result: %f", instance.c_str(), offset, slope, calibrationMap[instance].value);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ bool CalibrationData::smoothInstance(GwApi::BoatValue* boatDataValue)
|
|||||||
|
|
||||||
// we test this earlier, but for safety reason ...
|
// we test this earlier, but for safety reason ...
|
||||||
if (calibrationMap.find(instance) == calibrationMap.end()) {
|
if (calibrationMap.find(instance) == calibrationMap.end()) {
|
||||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ bool CalibrationData::smoothInstance(GwApi::BoatValue* boatDataValue)
|
|||||||
calibrationMap[instance].value = dataValue; // store the smoothed value in the list
|
calibrationMap[instance].value = dataValue; // store the smoothed value in the list
|
||||||
calibrationMap[instance].isCalibrated = true;
|
calibrationMap[instance].isCalibrated = true;
|
||||||
|
|
||||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: smooth: %f, oldValue: %f, result: %f", instance.c_str(), smoothFactor, oldValue, calibrationMap[instance].value);
|
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: smooth: %f, oldValue: %f, result: %f", instance.c_str(), smoothFactor, oldValue, calibrationMap[instance].value);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -241,7 +241,7 @@ void HstryBuf::add(double value)
|
|||||||
{
|
{
|
||||||
if (value >= hstryMin && value <= hstryMax) {
|
if (value >= hstryMin && value <= hstryMax) {
|
||||||
hstryBuf.add(value);
|
hstryBuf.add(value);
|
||||||
LOG_DEBUG(GwLog::DEBUG, "HstryBuf::add: name: %s, value: %.3f", hstryBuf.getName(), value);
|
// LOG_DEBUG(GwLog::DEBUG, "HstryBuf::add: name: %s, value: %.3f", hstryBuf.getName(), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,7 +405,7 @@ void WindUtils::calcTwdSA(const double* AWA, const double* AWS,
|
|||||||
double stw = -*STW;
|
double stw = -*STW;
|
||||||
addPolar(AWD, AWS, CTW, &stw, TWD, TWS);
|
addPolar(AWD, AWS, CTW, &stw, TWD, TWS);
|
||||||
|
|
||||||
// Normalize TWD and TWA to 0-360°/2PI
|
// Normalize TWD to [0..360°] (2PI) and TWA to [-180..180] (PI)
|
||||||
*TWD = to2PI(*TWD);
|
*TWD = to2PI(*TWD);
|
||||||
*TWA = toPI(*TWD - *HDT);
|
*TWA = toPI(*TWD - *HDT);
|
||||||
}
|
}
|
||||||
@@ -487,8 +487,8 @@ bool WindUtils::addWinds()
|
|||||||
double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
|
double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
|
||||||
double hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX;
|
double hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX;
|
||||||
double varVal = varBVal->valid ? varBVal->value : DBL_MAX;
|
double varVal = varBVal->valid ? varBVal->value : DBL_MAX;
|
||||||
LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852,
|
//LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852,
|
||||||
cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG);
|
// cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG);
|
||||||
|
|
||||||
// Check if TWD can be calculated from TWA and HDT/HDM
|
// Check if TWD can be calculated from TWA and HDT/HDM
|
||||||
if (twaBVal->valid) {
|
if (twaBVal->valid) {
|
||||||
@@ -528,8 +528,8 @@ bool WindUtils::addWinds()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG,
|
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG,
|
||||||
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG);
|
// twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG);
|
||||||
|
|
||||||
return twCalculated;
|
return twCalculated;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,10 @@ void sensorTask(void *param){
|
|||||||
|
|
||||||
// Init sensor stuff
|
// Init sensor stuff
|
||||||
bool oneWire_ready = false; // 1Wire initialized and ready to use
|
bool oneWire_ready = false; // 1Wire initialized and ready to use
|
||||||
|
bool iRTC_ready = false; // Software RTC initialized and ready to use
|
||||||
bool RTC_ready = false; // DS1388 initialized and ready to use
|
bool RTC_ready = false; // DS1388 initialized and ready to use
|
||||||
bool GPS_ready = false; // GPS initialized and ready to use
|
bool GPS_ready = false; // GPS initialized and ready to use
|
||||||
|
bool N2K_GPS_ready = false; // GPS time on N2K bus
|
||||||
bool BME280_ready = false; // BME280 initialized and ready to use
|
bool BME280_ready = false; // BME280 initialized and ready to use
|
||||||
bool BMP280_ready = false; // BMP280 initialized and ready to use
|
bool BMP280_ready = false; // BMP280 initialized and ready to use
|
||||||
bool BMP180_ready = false; // BMP180 initialized and ready to use
|
bool BMP180_ready = false; // BMP180 initialized and ready to use
|
||||||
@@ -369,7 +371,7 @@ void sensorTask(void *param){
|
|||||||
GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP);
|
GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP);
|
||||||
GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop};
|
GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop};
|
||||||
|
|
||||||
// Internal RTC with NTP init
|
// Internal iRTC with NTP init
|
||||||
ESP32Time rtc(0);
|
ESP32Time rtc(0);
|
||||||
if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") {
|
if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") {
|
||||||
GwApi::Status status;
|
GwApi::Status status;
|
||||||
@@ -382,6 +384,7 @@ void sensorTask(void *param){
|
|||||||
if (getLocalTime(&timeinfo)) {
|
if (getLocalTime(&timeinfo)) {
|
||||||
api->getLogger()->logDebug(GwLog::LOG,"NTP time: %04d-%02d-%02d %02d:%02d:%02d UTC", timeinfo.tm_year+1900, timeinfo.tm_mon+1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
|
api->getLogger()->logDebug(GwLog::LOG,"NTP time: %04d-%02d-%02d %02d:%02d:%02d UTC", timeinfo.tm_year+1900, timeinfo.tm_mon+1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
|
||||||
rtc.setTimeStruct(timeinfo);
|
rtc.setTimeStruct(timeinfo);
|
||||||
|
iRTC_ready = true;
|
||||||
sensors.rtcValid = true;
|
sensors.rtcValid = true;
|
||||||
} else {
|
} else {
|
||||||
api->getLogger()->logDebug(GwLog::LOG,"NTP time fetch failed!");
|
api->getLogger()->logDebug(GwLog::LOG,"NTP time fetch failed!");
|
||||||
@@ -400,7 +403,7 @@ void sensorTask(void *param){
|
|||||||
if (millis() > starttime0 + 100)
|
if (millis() > starttime0 + 100)
|
||||||
{
|
{
|
||||||
starttime0 = millis();
|
starttime0 = millis();
|
||||||
// Send NMEA0183 GPS data on several bus systems all 100ms
|
// Send NMEA0183 GPS data on several bus systems (N2K an 0183) all 100ms
|
||||||
if (GPS_ready == true && hdop->value <= hdopAccuracy)
|
if (GPS_ready == true && hdop->value <= hdopAccuracy)
|
||||||
{
|
{
|
||||||
SNMEA0183Msg NMEA0183Msg;
|
SNMEA0183Msg NMEA0183Msg;
|
||||||
@@ -412,9 +415,55 @@ void sensorTask(void *param){
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If RTC DS1388 ready, then copy GPS data to RTC all 5min
|
/*
|
||||||
if(millis() > starttime11 + 5*60*1000){
|
Time set logic for RTC and N2K
|
||||||
|
###############################
|
||||||
|
|
||||||
|
iRTC = Software RTC updatetd with NTP via internet
|
||||||
|
RTC = RTC chip on PCB
|
||||||
|
GPS = GPS Receiver on PCB
|
||||||
|
N2K = GPS time on N2K od 183 bus
|
||||||
|
0 = device not ready
|
||||||
|
1 = device ready
|
||||||
|
X = independend
|
||||||
|
() = source for set time N2K
|
||||||
|
-> = set RTC via iRTC
|
||||||
|
<- = set RTC via GPS
|
||||||
|
|
||||||
|
iRTC RTC GPS N2K
|
||||||
|
0 0 0 (1)
|
||||||
|
0 0 (1) X
|
||||||
|
0 (1) 0 X
|
||||||
|
0 1 <-(1) X
|
||||||
|
(1) 0 0 X
|
||||||
|
1 0 (1) X
|
||||||
|
1 ->(1) 0 X
|
||||||
|
1 1 <-(1) X
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// If RTC DS1388 ready, then copy iRTC and GPS data to RTC all 1 min
|
||||||
|
if(millis() > starttime11 + 1*60*1000){
|
||||||
starttime11 = millis();
|
starttime11 = millis();
|
||||||
|
// Set RTC chip via iRTC (NTP)
|
||||||
|
if(iRTC_ready == true && RTC_ready == true && GPS_ready == false){
|
||||||
|
GwApi::Status status;
|
||||||
|
api->getStatus(status);
|
||||||
|
// Check WiFi connection
|
||||||
|
if (status.wifiClientConnected) {
|
||||||
|
sensors.rtcTime = rtc.getTimeStruct(); // Get time from software RTC (iRTC)
|
||||||
|
DateTime now = DateTime(
|
||||||
|
sensors.rtcTime.tm_year + 1900,
|
||||||
|
sensors.rtcTime.tm_mon + 1,
|
||||||
|
sensors.rtcTime.tm_mday,
|
||||||
|
sensors.rtcTime.tm_hour,
|
||||||
|
sensors.rtcTime.tm_min,
|
||||||
|
sensors.rtcTime.tm_sec
|
||||||
|
);
|
||||||
|
ds1388.adjust(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Set RTC chip via internal GPS
|
||||||
if(rtcOn == "DS1388" && RTC_ready == true && GPS_ready == true){
|
if(rtcOn == "DS1388" && RTC_ready == true && GPS_ready == true){
|
||||||
api->getBoatDataValues(3,valueList);
|
api->getBoatDataValues(3,valueList);
|
||||||
if(gpsdays->valid && gpsseconds->valid && hdop->valid){
|
if(gpsdays->valid && gpsseconds->valid && hdop->valid){
|
||||||
@@ -422,40 +471,33 @@ void sensorTask(void *param){
|
|||||||
// sample input: date = "Dec 26 2009", time = "12:34:56"
|
// sample input: date = "Dec 26 2009", time = "12:34:56"
|
||||||
// ds1388.adjust(DateTime("Dec 26 2009", "12:34:56"));
|
// ds1388.adjust(DateTime("Dec 26 2009", "12:34:56"));
|
||||||
DateTime adjusttime(ts);
|
DateTime adjusttime(ts);
|
||||||
api->getLogger()->logDebug(GwLog::LOG,"Adjust RTC time: %04d/%02d/%02d %02d:%02d:%02d",adjusttime.year(), adjusttime.month(), adjusttime.day(), adjusttime.hour(), adjusttime.minute(), adjusttime.second());
|
api->getLogger()->logDebug(GwLog::LOG,"Adjust RTC time via internal GPS: %04d/%02d/%02d %02d:%02d:%02d",adjusttime.year(), adjusttime.month(), adjusttime.day(), adjusttime.hour(), adjusttime.minute(), adjusttime.second());
|
||||||
// Adjust RTC time as unix time value
|
// Adjust RTC time as unix time value
|
||||||
ds1388.adjust(adjusttime);
|
ds1388.adjust(adjusttime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send 1Wire data for all temperature sensors all 2s
|
// Set RTC chip via N2K or 183 in case the internal GPS is off (only one time)
|
||||||
if(millis() > starttime13 + 2000 && String(oneWireOn) == "DS18B20" && oneWire_ready == true){
|
if(N2K_GPS_ready == false && RTC_ready == true && GPS_ready == false){
|
||||||
starttime13 = millis();
|
api->getBoatDataValues(3,valueList);
|
||||||
float tempC;
|
if(gpsdays->valid && gpsseconds->valid && hdop->valid){
|
||||||
ds18b20.requestTemperatures(); // Collect all temperature values (max.8)
|
long ts = tNMEA0183Msg::daysToTime_t(gpsdays->value - (30*365+7))+floor(gpsseconds->value); // Adjusted to reference year 2000 (-30 years and 7 days for switch years)
|
||||||
for(int i=0;i<numberOfDevices; i++){
|
// sample input: date = "Dec 26 2009", time = "12:34:56"
|
||||||
// Send only one 1Wire data per loop step (time reduction)
|
// ds1388.adjust(DateTime("Dec 26 2009", "12:34:56"));
|
||||||
if(i == loopCounter % numberOfDevices){
|
DateTime adjusttime(ts);
|
||||||
if(ds18b20.getAddress(tempDeviceAddress, i)){
|
api->getLogger()->logDebug(GwLog::LOG,"Adjust RTC time via N2K/183: %04d/%02d/%02d %02d:%02d:%02d",adjusttime.year(), adjusttime.month(), adjusttime.day(), adjusttime.hour(), adjusttime.minute(), adjusttime.second());
|
||||||
// Read temperature value in Celsius
|
// Adjust RTC time as unix time value
|
||||||
tempC = ds18b20.getTempC(tempDeviceAddress);
|
ds1388.adjust(adjusttime);
|
||||||
}
|
// N2K GPS time ready
|
||||||
// Send to NMEA200 bus for each sensor with instance number
|
N2K_GPS_ready = true;
|
||||||
if(!isnan(tempC)){
|
}
|
||||||
sensors.onewireTemp[i] = tempC; // Save values in SensorData
|
|
||||||
api->getLogger()->logDebug(GwLog::DEBUG,"DS18B20-%1d Temp: %.1f",i,tempC);
|
|
||||||
SetN2kPGN130316(N2kMsg, 0, i, N2kts_OutsideTemperature, CToKelvin(tempC), N2kDoubleNA);
|
|
||||||
api->sendN2kMessage(N2kMsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loopCounter++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current RTC date and time all 500ms
|
// Send RTC date and time to N2K all 500ms
|
||||||
if (millis() > starttime12 + 500) {
|
if (millis() > starttime12 + 500) {
|
||||||
starttime12 = millis();
|
starttime12 = millis();
|
||||||
|
// Send date and time from RTC chip if GPS not ready
|
||||||
if (rtcOn == "DS1388" && RTC_ready) {
|
if (rtcOn == "DS1388" && RTC_ready) {
|
||||||
DateTime dt = ds1388.now();
|
DateTime dt = ds1388.now();
|
||||||
sensors.rtcTime.tm_year = dt.year() - 1900; // Save values in SensorData
|
sensors.rtcTime.tm_year = dt.year() - 1900; // Save values in SensorData
|
||||||
@@ -481,21 +523,63 @@ void sensorTask(void *param){
|
|||||||
}
|
}
|
||||||
// N2K sysTime is double in n2klib
|
// N2K sysTime is double in n2klib
|
||||||
double sysTime = (dt.hour() * 3600) + (dt.minute() * 60) + dt.second();
|
double sysTime = (dt.hour() * 3600) + (dt.minute() * 60) + dt.second();
|
||||||
// WHY? isnan should always fail here
|
if(!isnan(daysAt1970) && !isnan(sysTime)){
|
||||||
//if(!isnan(daysAt1970) && !isnan(sysTime)){
|
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon+1, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
|
||||||
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
|
|
||||||
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
|
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
|
||||||
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
|
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
|
||||||
api->sendN2kMessage(N2kMsg);
|
api->sendN2kMessage(N2kMsg);
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
} else if (sensors.rtcValid) {
|
|
||||||
// use internal rtc feature
|
|
||||||
sensors.rtcTime = rtc.getTimeStruct();
|
|
||||||
}
|
}
|
||||||
|
// Send date and time from software RTC (iRTC)
|
||||||
|
if (iRTC_ready == true && RTC_ready == false && GPS_ready == false) {
|
||||||
|
sensors.rtcTime = rtc.getTimeStruct();
|
||||||
|
|
||||||
|
const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334};
|
||||||
|
int year = sensors.rtcTime.tm_year + 1900;
|
||||||
|
int month = sensors.rtcTime.tm_mon;
|
||||||
|
int day = sensors.rtcTime.tm_mday;
|
||||||
|
uint16_t switchYear = ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 + ((year - 1) - 1600) / 400;
|
||||||
|
long daysAt1970 = (year - 1970) * 365L + switchYear + daysOfYear[month] + day - 1;
|
||||||
|
|
||||||
|
// Leap day add if date is after Feb (i.e. month >= March)
|
||||||
|
if (month >= 2 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) {
|
||||||
|
daysAt1970 += 1;
|
||||||
|
}
|
||||||
|
double sysTime = sensors.rtcTime.tm_hour * 3600.0 + sensors.rtcTime.tm_min * 60.0 + sensors.rtcTime.tm_sec;
|
||||||
|
//api->getLogger()->logDebug(GwLog::LOG, "iRTC time: %04d/%02d/%02d %02d:%02d:%02d", year, month + 1, day, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
|
||||||
|
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
|
||||||
|
SetN2kPGN126992(N2kMsg, 0, daysAt1970, sysTime, N2ktimes_LocalCrystalClock);
|
||||||
|
api->sendN2kMessage(N2kMsg);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send supply voltage value all 1s
|
// Send 1Wire data for all temperature sensors to N2K all 2s
|
||||||
|
if(millis() > starttime13 + 2000 && String(oneWireOn) == "DS18B20" && oneWire_ready == true){
|
||||||
|
starttime13 = millis();
|
||||||
|
float tempC;
|
||||||
|
ds18b20.requestTemperatures(); // Collect all temperature values (max.8)
|
||||||
|
for(int i=0;i<numberOfDevices; i++){
|
||||||
|
// Send only one 1Wire data per loop step (time reduction)
|
||||||
|
if(i == loopCounter % numberOfDevices){
|
||||||
|
if(ds18b20.getAddress(tempDeviceAddress, i)){
|
||||||
|
// Read temperature value in Celsius
|
||||||
|
tempC = ds18b20.getTempC(tempDeviceAddress);
|
||||||
|
}
|
||||||
|
// Send to NMEA200 bus for each sensor with instance number
|
||||||
|
if(!isnan(tempC)){
|
||||||
|
sensors.onewireTemp[i] = tempC; // Save values in SensorData
|
||||||
|
api->getLogger()->logDebug(GwLog::DEBUG,"DS18B20-%1d Temp: %.1f",i,tempC);
|
||||||
|
SetN2kPGN130316(N2kMsg, 0, i, N2kts_OutsideTemperature, CToKelvin(tempC), N2kDoubleNA);
|
||||||
|
api->sendN2kMessage(N2kMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loopCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send supply voltage value to N2K all 1s
|
||||||
if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){
|
if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){
|
||||||
starttime5 = millis();
|
starttime5 = millis();
|
||||||
float rawVoltage = 0; // Default value
|
float rawVoltage = 0; // Default value
|
||||||
@@ -565,7 +649,7 @@ void sensorTask(void *param){
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send data from environment sensor all 2s
|
// Send data from environment sensor to N2K all 2s
|
||||||
if(millis() > starttime6 + 2000){
|
if(millis() > starttime6 + 2000){
|
||||||
starttime6 = millis();
|
starttime6 = millis();
|
||||||
unsigned char TempSource = 2; // Inside temperature
|
unsigned char TempSource = 2; // Inside temperature
|
||||||
@@ -630,7 +714,7 @@ void sensorTask(void *param){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send rotation angle all 500ms
|
// Send rotation angle to N2K all 500ms
|
||||||
if(millis() > starttime7 + 500){
|
if(millis() > starttime7 + 500){
|
||||||
starttime7 = millis();
|
starttime7 = millis();
|
||||||
double rotationAngle=0;
|
double rotationAngle=0;
|
||||||
@@ -678,7 +762,7 @@ void sensorTask(void *param){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send battery power value all 1s
|
// Send battery power value to N2K all 1s
|
||||||
if(millis() > starttime8 + 1000 && (String(powsensor1) == "INA219" || String(powsensor1) == "INA226")){
|
if(millis() > starttime8 + 1000 && (String(powsensor1) == "INA219" || String(powsensor1) == "INA226")){
|
||||||
starttime8 = millis();
|
starttime8 = millis();
|
||||||
if(String(powsensor1) == "INA226" && INA226_1_ready == true){
|
if(String(powsensor1) == "INA226" && INA226_1_ready == true){
|
||||||
@@ -720,7 +804,7 @@ void sensorTask(void *param){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send solar power value all 1s
|
// Send solar power value to N2K all 1s
|
||||||
if(millis() > starttime9 + 1000 && (String(powsensor2) == "INA219" || String(powsensor2) == "INA226")){
|
if(millis() > starttime9 + 1000 && (String(powsensor2) == "INA219" || String(powsensor2) == "INA226")){
|
||||||
starttime9 = millis();
|
starttime9 = millis();
|
||||||
if(String(powsensor2) == "INA226" && INA226_2_ready == true){
|
if(String(powsensor2) == "INA226" && INA226_2_ready == true){
|
||||||
@@ -750,7 +834,7 @@ void sensorTask(void *param){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send generator power value all 1s
|
// Send generator power value to N2K all 1s
|
||||||
if(millis() > starttime10 + 1000 && (String(powsensor3) == "INA219" || String(powsensor3) == "INA226")){
|
if(millis() > starttime10 + 1000 && (String(powsensor3) == "INA219" || String(powsensor3) == "INA226")){
|
||||||
starttime10 = millis();
|
starttime10 = millis();
|
||||||
if(String(powsensor3) == "INA226" && INA226_3_ready == true){
|
if(String(powsensor3) == "INA226" && INA226_3_ready == true){
|
||||||
|
|||||||
@@ -161,8 +161,8 @@ bool Chart::setChartDimensions(const char direction, const int8_t size)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG_DEBUG(GwLog::ERROR, "obp60:setChartDimensions %s: direction: %c, size: %d, dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot{%d, %d}, top: %d, bottom: %d, hGap: %d, vGap: %d",
|
//LOG_DEBUG(GwLog::DEBUG, "obp60:setChartDimensions %s: direction: %c, size: %d, dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot{%d, %d}, top: %d, bottom: %d, hGap: %d, vGap: %d",
|
||||||
dataBuf.getName(), direction, size, dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, top, bottom, hGap, vGap);
|
// dataBuf.getName(), direction, size, dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, top, bottom, hGap, vGap);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ void Chart::drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue
|
|||||||
// LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
|
// LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
|
||||||
calcChrtBorders(chrtMin, chrtMid, chrtMax, chrtRng);
|
calcChrtBorders(chrtMin, chrtMid, chrtMax, chrtRng);
|
||||||
chrtScale = double(valAxis) / chrtRng; // Chart scale: pixels per value step
|
chrtScale = double(valAxis) / chrtRng; // Chart scale: pixels per value step
|
||||||
LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
|
// LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
|
||||||
|
|
||||||
// Do we have valid buffer data?
|
// Do we have valid buffer data?
|
||||||
if (dataBuf.getMax() == dbMAX_VAL) { // only <MAX_VAL> values in buffer -> no valid wind data available
|
if (dataBuf.getMax() == dbMAX_VAL) { // only <MAX_VAL> values in buffer -> no valid wind data available
|
||||||
@@ -261,8 +261,8 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
|
|||||||
}
|
}
|
||||||
recalcRngMid = false; // Reset flag for <rngMid> determination
|
recalcRngMid = false; // Reset flag for <rngMid> determination
|
||||||
|
|
||||||
LOG_DEBUG(GwLog::DEBUG, "calcChrtRange: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
// LOG_DEBUG(GwLog::DEBUG, "calcChrtRange: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||||
rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
// rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,8 +287,8 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
|
|||||||
|
|
||||||
rng = halfRng * 2.0;
|
rng = halfRng * 2.0;
|
||||||
|
|
||||||
LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, tmpRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
// LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, tmpRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||||
tmpRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
// tmpRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||||
|
|
||||||
} else { // chart data is of any other type
|
} else { // chart data is of any other type
|
||||||
|
|
||||||
@@ -320,8 +320,8 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
|
|||||||
rngMid = (rngMin + rngMax) / 2.0;
|
rngMid = (rngMin + rngMax) / 2.0;
|
||||||
rng = rngMax - rngMin;
|
rng = rngMax - rngMin;
|
||||||
|
|
||||||
LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f",
|
// LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f",
|
||||||
currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL);
|
// currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,6 +397,8 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
taskYIELD(); // we run for 50-150ms; be polite to other tasks with same priority
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,7 +658,7 @@ void Chart::prntHorizChartThreeValueAxisLabel(const GFXfont* font)
|
|||||||
|
|
||||||
if (font == &Ubuntu_Bold10pt8b) {
|
if (font == &Ubuntu_Bold10pt8b) {
|
||||||
xOffset = 39;
|
xOffset = 39;
|
||||||
yOffset = 15;
|
yOffset = 16;
|
||||||
} else if (font == &Ubuntu_Bold12pt8b) {
|
} else if (font == &Ubuntu_Bold12pt8b) {
|
||||||
xOffset = 51;
|
xOffset = 51;
|
||||||
yOffset = 18;
|
yOffset = 18;
|
||||||
@@ -718,7 +720,7 @@ void Chart::prntHorizChartMultiValueAxisLabel(const GFXfont* font)
|
|||||||
axSlots = valAxis / static_cast<double>(VALAXIS_STEP); // number of axis labels (and we want to have a double calculation, no integer)
|
axSlots = valAxis / static_cast<double>(VALAXIS_STEP); // number of axis labels (and we want to have a double calculation, no integer)
|
||||||
axIntv = chrtRng / axSlots;
|
axIntv = chrtRng / axSlots;
|
||||||
axLabel = chrtMin + axIntv;
|
axLabel = chrtMin + axIntv;
|
||||||
LOG_DEBUG(GwLog::DEBUG, "Chart::printHorizMultiValueAxisLabel: chrtRng: %.2f, th-chrtRng: %.2f, axSlots: %.2f, axIntv: %.2f, axLabel: %.2f, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, this->chrtRng, axSlots, axIntv, axLabel, this->chrtMin, chrtMid, chrtMax);
|
// LOG_DEBUG(GwLog::DEBUG, "Chart::printHorizMultiValueAxisLabel: chrtRng: %.2f, th-chrtRng: %.2f, axSlots: %.2f, axIntv: %.2f, axLabel: %.2f, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, this->chrtRng, axSlots, axIntv, axLabel, this->chrtMin, chrtMid, chrtMax);
|
||||||
|
|
||||||
int loopStrt, loopEnd, loopStp;
|
int loopStrt, loopEnd, loopStp;
|
||||||
if (chrtDataFmt == SPEED || chrtDataFmt == TEMPERATURE || chrtDataFmt == OTHER) {
|
if (chrtDataFmt == SPEED || chrtDataFmt == TEMPERATURE || chrtDataFmt == OTHER) {
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
|
|
||||||
// These constants have to match the declaration below in :
|
// These constants have to match the declaration below in :
|
||||||
// PageDescription registerPageAutopilot(
|
// PageDescription registerPageAutopilot(
|
||||||
// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page
|
// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page
|
||||||
const int HowManyValues = 9;
|
|
||||||
|
const int HowManyValues = 11;
|
||||||
|
|
||||||
const int AverageValues = 4;
|
const int AverageValues = 4;
|
||||||
|
|
||||||
@@ -19,10 +20,13 @@ const int ShowDBT = 5;
|
|||||||
const int ShowXTE = 6;
|
const int ShowXTE = 6;
|
||||||
const int ShowDTW = 7;
|
const int ShowDTW = 7;
|
||||||
const int ShowBTW = 8;
|
const int ShowBTW = 8;
|
||||||
|
const int ShowRPOS = 9;
|
||||||
|
const int ShowROT = 10;
|
||||||
|
|
||||||
const int Compass_X0 = 200; // X center point of compass band
|
const int Compass_X0 = 200; // X center point of compass band
|
||||||
const int Compass_Y0 = 220; // Y position of compass lines
|
const int Compass_Y0 = 90; // Y position of compass lines
|
||||||
const int Compass_LineLength = 22; // Length of compass lines
|
//const int Compass_LineLength = 22; // Length of compass lines
|
||||||
|
const int Compass_LineLength = 15; // Length of compass lines
|
||||||
const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
|
const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
|
||||||
|
|
||||||
class PageAutopilot : public Page
|
class PageAutopilot : public Page
|
||||||
@@ -38,8 +42,11 @@ class PageAutopilot : public Page
|
|||||||
|
|
||||||
virtual void setupKeys(){
|
virtual void setupKeys(){
|
||||||
Page::setupKeys();
|
Page::setupKeys();
|
||||||
commonData->keydata[0].label = "CMP";
|
commonData->keydata[0].label = "-10";
|
||||||
commonData->keydata[1].label = "SRC";
|
commonData->keydata[1].label = "-1";
|
||||||
|
commonData->keydata[2].label = "Auto";
|
||||||
|
commonData->keydata[3].label = "+1";
|
||||||
|
commonData->keydata[4].label = "+10";
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual int handleKey(int key){
|
virtual int handleKey(int key){
|
||||||
@@ -69,8 +76,8 @@ class PageAutopilot : public Page
|
|||||||
GwLog *logger = commonData->logger;
|
GwLog *logger = commonData->logger;
|
||||||
|
|
||||||
// Old values for hold function
|
// Old values for hold function
|
||||||
static String OldDataText[HowManyValues] = {"", "", "","", "", "","", "", ""};
|
static String OldDataText[HowManyValues] = {"", "", "", "", "", "","", "", "", "", ""};
|
||||||
static String OldDataUnits[HowManyValues] = {"", "", "","", "", "","", "", ""};
|
static String OldDataUnits[HowManyValues] = {"", "", "", "", "", "","", "", "", "", ""};
|
||||||
|
|
||||||
// Get config data
|
// Get config data
|
||||||
String lengthformat = config->getString(config->lengthFormat);
|
String lengthformat = config->getString(config->lengthFormat);
|
||||||
@@ -106,15 +113,13 @@ class PageAutopilot : public Page
|
|||||||
setBlinkingLED(false);
|
setBlinkingLED(false);
|
||||||
setFlashLED(false);
|
setFlashLED(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bvalue == NULL) return PAGE_OK; // WTF why this statement?
|
|
||||||
|
|
||||||
//***********************************************************
|
//***********************************************************
|
||||||
|
|
||||||
// Set display in partial refresh mode
|
// Set display in partial refresh mode
|
||||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
getdisplay().setTextColor(commonData->fgcolor);
|
getdisplay().setTextColor(commonData->fgcolor);
|
||||||
|
/*
|
||||||
// Horizontal line 2 pix top & bottom
|
// Horizontal line 2 pix top & bottom
|
||||||
// Print data on top half
|
// Print data on top half
|
||||||
getdisplay().fillRect(0, 130, 400, 2, commonData->fgcolor);
|
getdisplay().fillRect(0, 130, 400, 2, commonData->fgcolor);
|
||||||
@@ -138,7 +143,7 @@ class PageAutopilot : public Page
|
|||||||
OldDataText[WhichDataDisplay] = DataText[WhichDataDisplay]; // Save the old value
|
OldDataText[WhichDataDisplay] = DataText[WhichDataDisplay]; // Save the old value
|
||||||
OldDataUnits[WhichDataDisplay] = DataUnits[WhichDataDisplay]; // Save the old unit
|
OldDataUnits[WhichDataDisplay] = DataUnits[WhichDataDisplay]; // Save the old unit
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
// Now draw compass band
|
// Now draw compass band
|
||||||
// Get the data
|
// Get the data
|
||||||
double TheAngle = DataValue[WhichDataCompass];
|
double TheAngle = DataValue[WhichDataCompass];
|
||||||
@@ -152,13 +157,13 @@ class PageAutopilot : public Page
|
|||||||
buffer[0]=0;
|
buffer[0]=0;
|
||||||
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
||||||
getdisplay().setCursor(10, Compass_Y0-60);
|
getdisplay().setCursor(10, Compass_Y0-40);
|
||||||
getdisplay().print(DataName[WhichDataCompass]); // Page name
|
getdisplay().print(DataName[WhichDataCompass]); // Page name
|
||||||
|
|
||||||
|
|
||||||
// Draw compass base line and pointer
|
// Draw compass base line and pointer
|
||||||
getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor);
|
getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor);
|
||||||
getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor);
|
//getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor);
|
||||||
|
getdisplay().fillTriangle(Compass_X0,Compass_Y0-30,Compass_X0-10,Compass_Y0-60,Compass_X0+10,Compass_Y0-60,commonData->fgcolor);
|
||||||
// Draw trendlines
|
// Draw trendlines
|
||||||
for ( int i = 1; i < abs(TheTrend) / 2; i++){
|
for ( int i = 1; i < abs(TheTrend) / 2; i++){
|
||||||
int x1;
|
int x1;
|
||||||
@@ -238,6 +243,8 @@ class PageAutopilot : public Page
|
|||||||
// if ( x_test > 390)
|
// if ( x_test > 390)
|
||||||
// x_test = 320;
|
// x_test = 320;
|
||||||
|
|
||||||
|
displayRudderPosition(DataValue[ShowSOG], 20, 200, 160, commonData->fgcolor, commonData->bgcolor);
|
||||||
|
|
||||||
return PAGE_UPDATE;
|
return PAGE_UPDATE;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -256,7 +263,7 @@ PageDescription registerPageAutopilot(
|
|||||||
"Autopilot", // Page name
|
"Autopilot", // Page name
|
||||||
createPage, // Action
|
createPage, // Action
|
||||||
0, // Number of bus values depends on selection in Web configuration
|
0, // Number of bus values depends on selection in Web configuration
|
||||||
{"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page
|
{"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page
|
||||||
true // Show display header on/off
|
true // Show display header on/off
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -162,9 +162,13 @@ public:
|
|||||||
constexpr int ZOOM_KEY = 1;
|
constexpr int ZOOM_KEY = 1;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (dataHstryBuf) { // show "Mode" key only if chart supported boat data type is available
|
if (dataHstryBuf) { // show "Mode" key only if chart-supported boat data type is available
|
||||||
commonData->keydata[0].label = "MODE";
|
commonData->keydata[0].label = "MODE";
|
||||||
commonData->keydata[ZOOM_KEY].label = "ZOOM";
|
if (pageMode != VALUE) { // show "ZOOM" key only if chart is visible
|
||||||
|
commonData->keydata[ZOOM_KEY].label = "ZOOM";
|
||||||
|
} else {
|
||||||
|
commonData->keydata[ZOOM_KEY].label = "";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
commonData->keydata[0].label = "";
|
commonData->keydata[0].label = "";
|
||||||
commonData->keydata[ZOOM_KEY].label = "";
|
commonData->keydata[ZOOM_KEY].label = "";
|
||||||
@@ -189,14 +193,15 @@ public:
|
|||||||
pageMode = VALUE;
|
pageMode = VALUE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||||
return 0; // Commit the key
|
return 0; // Commit the key
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set time frame to show for history chart
|
// Set time frame to show for chart
|
||||||
#if defined BOARD_OBP60S3
|
#if defined BOARD_OBP60S3
|
||||||
if (key == 5) {
|
if (key == 5 && pageMode != VALUE) {
|
||||||
#elif defined BOARD_OBP40S3
|
#elif defined BOARD_OBP40S3
|
||||||
if (key == 2) {
|
if (key == 2 && pageMode != VALUE) {
|
||||||
#endif
|
#endif
|
||||||
if (dataIntv == 1) {
|
if (dataIntv == 1) {
|
||||||
dataIntv = 2;
|
dataIntv = 2;
|
||||||
@@ -247,7 +252,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupKeys(); // adjust <mode> key depending on chart supported boat data type
|
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||||
}
|
}
|
||||||
|
|
||||||
int displayPage(PageData& pageData)
|
int displayPage(PageData& pageData)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "images/logo64.xbm"
|
#include "images/logo64.xbm"
|
||||||
#include <esp32/clk.h>
|
#include <esp32/clk.h>
|
||||||
#include "qrcode.h"
|
#include "qrcode.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#ifdef BOARD_OBP40S3
|
#ifdef BOARD_OBP40S3
|
||||||
#include "dirent.h"
|
#include "dirent.h"
|
||||||
@@ -37,9 +38,11 @@ private:
|
|||||||
String buzzer_mode;
|
String buzzer_mode;
|
||||||
uint8_t buzzer_power;
|
uint8_t buzzer_power;
|
||||||
String cpuspeed;
|
String cpuspeed;
|
||||||
|
String powermode;
|
||||||
String rtc_module;
|
String rtc_module;
|
||||||
String gps_module;
|
String gps_module;
|
||||||
String env_module;
|
String env_module;
|
||||||
|
String flashLED;
|
||||||
|
|
||||||
String batt_sensor;
|
String batt_sensor;
|
||||||
String solar_sensor;
|
String solar_sensor;
|
||||||
@@ -48,15 +51,445 @@ private:
|
|||||||
double homelat;
|
double homelat;
|
||||||
double homelon;
|
double homelon;
|
||||||
|
|
||||||
char mode = 'N'; // (N)ormal, (S)ettings, (D)evice list, (C)ard
|
char mode = 'N'; // (N)ormal, (S)ettings, (C)onfiguration, (D)evice list, c(A)rd
|
||||||
|
|
||||||
|
#ifdef PATCH_N2K
|
||||||
|
struct device {
|
||||||
|
uint64_t NAME;
|
||||||
|
uint8_t id;
|
||||||
|
char hex_name[17];
|
||||||
|
uint16_t manuf_code;
|
||||||
|
const char *model;
|
||||||
|
};
|
||||||
|
std::vector<device> devicelist;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void incMode() {
|
||||||
|
if (mode == 'N') { // Normal
|
||||||
|
mode = 'S';
|
||||||
|
} else if (mode == 'S') { // Settings
|
||||||
|
mode = 'C';
|
||||||
|
} else if (mode == 'C') { // Config
|
||||||
|
mode = 'D';
|
||||||
|
} else if (mode == 'D') { // Device list
|
||||||
|
if (use_sdcard) {
|
||||||
|
mode = 'A'; // SD-Card
|
||||||
|
} else {
|
||||||
|
mode = 'N';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mode = 'N';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void decMode() {
|
||||||
|
if (mode == 'N') {
|
||||||
|
if (use_sdcard) {
|
||||||
|
mode = 'A'; // SD-Card
|
||||||
|
} else {
|
||||||
|
mode = 'D'; // Device list
|
||||||
|
}
|
||||||
|
} else if (mode == 'S') { // Settings
|
||||||
|
mode = 'N';
|
||||||
|
} else if (mode == 'C') { // Config
|
||||||
|
mode = 'S';
|
||||||
|
} else if (mode == 'D') { // Device list
|
||||||
|
mode = 'C';
|
||||||
|
} else {
|
||||||
|
mode = 'D';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayModeNormal() {
|
||||||
|
// Default system page view
|
||||||
|
|
||||||
|
uint16_t y0 = 155;
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(8, 48);
|
||||||
|
getdisplay().print("System Information");
|
||||||
|
|
||||||
|
getdisplay().drawXBitmap(320, 25, logo64_bits, logo64_width, logo64_height, commonData->fgcolor);
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
|
||||||
|
char ssid[13];
|
||||||
|
snprintf(ssid, 13, "%04X%08X", (uint16_t)(chipid >> 32), (uint32_t)chipid);
|
||||||
|
displayBarcode(String(ssid), 320, 200, 2);
|
||||||
|
getdisplay().setCursor(8, 70);
|
||||||
|
getdisplay().print(String("MCUDEVICE-") + String(ssid));
|
||||||
|
|
||||||
|
getdisplay().setCursor(8, 95);
|
||||||
|
getdisplay().print("Firmware version: ");
|
||||||
|
getdisplay().setCursor(150, 95);
|
||||||
|
getdisplay().print(VERSINFO);
|
||||||
|
|
||||||
|
getdisplay().setCursor(8, 113);
|
||||||
|
getdisplay().print("Board version: ");
|
||||||
|
getdisplay().setCursor(150, 113);
|
||||||
|
getdisplay().print(BOARDINFO);
|
||||||
|
getdisplay().print(String(" HW ") + String(PCBINFO));
|
||||||
|
|
||||||
|
getdisplay().setCursor(8, 131);
|
||||||
|
getdisplay().print("Display version: ");
|
||||||
|
getdisplay().setCursor(150, 131);
|
||||||
|
getdisplay().print(DISPLAYINFO);
|
||||||
|
getdisplay().print("; GxEPD2 v");
|
||||||
|
getdisplay().print(GXEPD2INFO);
|
||||||
|
|
||||||
|
getdisplay().setCursor(8, 265);
|
||||||
|
#ifdef BOARD_OBP60S3
|
||||||
|
getdisplay().print("Press STBY to enter deep sleep mode");
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
getdisplay().print("Press wheel to enter deep sleep mode");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Flash memory size
|
||||||
|
uint32_t flash_size = ESP.getFlashChipSize();
|
||||||
|
getdisplay().setCursor(8, y0);
|
||||||
|
getdisplay().print("FLASH:");
|
||||||
|
getdisplay().setCursor(90, y0);
|
||||||
|
getdisplay().print(String(flash_size / 1024) + String(" kB"));
|
||||||
|
|
||||||
|
// PSRAM memory size
|
||||||
|
uint32_t psram_size = ESP.getPsramSize();
|
||||||
|
getdisplay().setCursor(8, y0 + 16);
|
||||||
|
getdisplay().print("PSRAM:");
|
||||||
|
getdisplay().setCursor(90, y0 + 16);
|
||||||
|
getdisplay().print(String(psram_size / 1024) + String(" kB"));
|
||||||
|
|
||||||
|
// FRAM available / status
|
||||||
|
getdisplay().setCursor(8, y0 + 32);
|
||||||
|
getdisplay().print("FRAM:");
|
||||||
|
getdisplay().setCursor(90, y0 + 32);
|
||||||
|
getdisplay().print(hasFRAM ? "available" : "not found");
|
||||||
|
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
// SD-Card
|
||||||
|
getdisplay().setCursor(8, y0 + 48);
|
||||||
|
getdisplay().print("SD-Card:");
|
||||||
|
getdisplay().setCursor(90, y0 + 48);
|
||||||
|
if (hasSDCard) {
|
||||||
|
uint64_t cardsize = ((uint64_t) sdcard->csd.capacity) * sdcard->csd.sector_size / (1024 * 1024);
|
||||||
|
getdisplay().printf("%llu MB", cardsize);
|
||||||
|
} else {
|
||||||
|
getdisplay().print("off");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Uptime
|
||||||
|
int64_t uptime = esp_timer_get_time() / 1000000;
|
||||||
|
String uptime_unit;
|
||||||
|
if (uptime < 120) {
|
||||||
|
uptime_unit = " seconds";
|
||||||
|
} else {
|
||||||
|
if (uptime < 2 * 3600) {
|
||||||
|
uptime /= 60;
|
||||||
|
uptime_unit = " minutes";
|
||||||
|
} else if (uptime < 2 * 3600 * 24) {
|
||||||
|
uptime /= 3600;
|
||||||
|
uptime_unit = " hours";
|
||||||
|
} else {
|
||||||
|
uptime /= 86400;
|
||||||
|
uptime_unit = " days";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getdisplay().setCursor(8, y0 + 80);
|
||||||
|
getdisplay().print("Uptime:");
|
||||||
|
getdisplay().setCursor(90, y0 + 80);
|
||||||
|
getdisplay().print(uptime);
|
||||||
|
getdisplay().print(uptime_unit);
|
||||||
|
|
||||||
|
// CPU speed config / active
|
||||||
|
getdisplay().setCursor(202, y0);
|
||||||
|
getdisplay().print("CPU speed:");
|
||||||
|
getdisplay().setCursor(300, y0);
|
||||||
|
getdisplay().print(cpuspeed);
|
||||||
|
getdisplay().print(" / ");
|
||||||
|
int cpu_freq = esp_clk_cpu_freq() / 1000000;
|
||||||
|
getdisplay().print(String(cpu_freq));
|
||||||
|
|
||||||
|
// total RAM free
|
||||||
|
int Heap_free = esp_get_free_heap_size();
|
||||||
|
getdisplay().setCursor(202, y0 + 16);
|
||||||
|
getdisplay().print("Total free:");
|
||||||
|
getdisplay().setCursor(300, y0 + 16);
|
||||||
|
getdisplay().print(String(Heap_free));
|
||||||
|
|
||||||
|
// RAM free for task
|
||||||
|
int RAM_free = uxTaskGetStackHighWaterMark(NULL);
|
||||||
|
getdisplay().setCursor(202, y0 + 32);
|
||||||
|
getdisplay().print("Task free:");
|
||||||
|
getdisplay().setCursor(300, y0 + 32);
|
||||||
|
getdisplay().print(String(RAM_free));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayModeConfig() {
|
||||||
|
// Configuration interface
|
||||||
|
|
||||||
|
uint16_t x0 = 16;
|
||||||
|
uint16_t y0 = 80;
|
||||||
|
uint16_t dy = 20;
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(8, 48);
|
||||||
|
getdisplay().print("System configuration");
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
|
||||||
|
getdisplay().setCursor(x0, y0);
|
||||||
|
getdisplay().print("CPU speed: 80 | 160 | 240");
|
||||||
|
getdisplay().setCursor(x0, y0 + 1 * dy);
|
||||||
|
getdisplay().print("Power mode: Max | 5V | Min");
|
||||||
|
getdisplay().setCursor(x0, y0 + 2 * dy);
|
||||||
|
getdisplay().print("Accesspoint: On | Off");
|
||||||
|
|
||||||
|
// TODO Change NVRAM-preferences settings here
|
||||||
|
getdisplay().setCursor(x0, y0 + 4 * dy);
|
||||||
|
getdisplay().print("Simulation: On | Off");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayModeSettings() {
|
||||||
|
// View some of the current settings
|
||||||
|
|
||||||
|
const uint16_t x0 = 8;
|
||||||
|
const uint16_t y0 = 72;
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(x0, 48);
|
||||||
|
getdisplay().print("System settings");
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
|
||||||
|
// left column
|
||||||
|
getdisplay().setCursor(x0, y0);
|
||||||
|
getdisplay().print("Simulation:");
|
||||||
|
getdisplay().setCursor(120, y0);
|
||||||
|
getdisplay().print(simulation ? "on" : "off");
|
||||||
|
|
||||||
|
getdisplay().setCursor(x0, y0 + 16);
|
||||||
|
getdisplay().print("Environment:");
|
||||||
|
getdisplay().setCursor(120, y0 + 16);
|
||||||
|
getdisplay().print(env_module);
|
||||||
|
|
||||||
|
getdisplay().setCursor(x0, y0 + 32);
|
||||||
|
getdisplay().print("Buzzer:");
|
||||||
|
getdisplay().setCursor(120, y0 + 32);
|
||||||
|
getdisplay().print(buzzer_mode);
|
||||||
|
|
||||||
|
getdisplay().setCursor(x0, y0 + 64);
|
||||||
|
getdisplay().print("GPS:");
|
||||||
|
getdisplay().setCursor(120, y0 + 64);
|
||||||
|
getdisplay().print(gps_module);
|
||||||
|
|
||||||
|
getdisplay().setCursor(x0, y0 + 80);
|
||||||
|
getdisplay().print("RTC:");
|
||||||
|
getdisplay().setCursor(120, y0 + 80);
|
||||||
|
getdisplay().print(rtc_module);
|
||||||
|
|
||||||
|
getdisplay().setCursor(x0, y0 + 96);
|
||||||
|
getdisplay().print("Wifi:");
|
||||||
|
getdisplay().setCursor(120, y0 + 96);
|
||||||
|
getdisplay().print(commonData->status.wifiApOn ? "on" : "off");
|
||||||
|
|
||||||
|
// Home location
|
||||||
|
getdisplay().setCursor(x0, y0 + 128);
|
||||||
|
getdisplay().print("Home Lat.:");
|
||||||
|
getdisplay().setCursor(120, y0 + 128);
|
||||||
|
getdisplay().print(formatLatitude(homelat));
|
||||||
|
getdisplay().setCursor(x0, y0 + 144);
|
||||||
|
getdisplay().print("Home Lon.:");
|
||||||
|
getdisplay().setCursor(120, y0 + 144);
|
||||||
|
getdisplay().print(formatLongitude(homelon));
|
||||||
|
|
||||||
|
// Power
|
||||||
|
getdisplay().setCursor(x0, y0 + 176);
|
||||||
|
getdisplay().print("Power mode:");
|
||||||
|
getdisplay().setCursor(120, y0 + 176);
|
||||||
|
getdisplay().print(powermode);
|
||||||
|
|
||||||
|
// right column
|
||||||
|
getdisplay().setCursor(202, y0);
|
||||||
|
getdisplay().print("Batt. sensor:");
|
||||||
|
getdisplay().setCursor(320, y0);
|
||||||
|
getdisplay().print(batt_sensor);
|
||||||
|
|
||||||
|
// Solar sensor
|
||||||
|
getdisplay().setCursor(202, y0 + 16);
|
||||||
|
getdisplay().print("Solar sensor:");
|
||||||
|
getdisplay().setCursor(320, y0 + 16);
|
||||||
|
getdisplay().print(solar_sensor);
|
||||||
|
|
||||||
|
// Generator sensor
|
||||||
|
getdisplay().setCursor(202, y0 + 32);
|
||||||
|
getdisplay().print("Gen. sensor:");
|
||||||
|
getdisplay().setCursor(320, y0 + 32);
|
||||||
|
getdisplay().print(gen_sensor);
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// Gyro sensor (rotation)
|
||||||
|
getdisplay().setCursor(202, y0 + 48);
|
||||||
|
getdisplay().print("Rot. sensor:");
|
||||||
|
getdisplay().setCursor(320, y0 + 48);
|
||||||
|
getdisplay().print(rot_sensor);
|
||||||
|
|
||||||
|
// Temp.-sensor
|
||||||
|
// Power Mode
|
||||||
|
|
||||||
|
#ifdef BOARD_OBP60S3
|
||||||
|
// Backlight infos
|
||||||
|
getdisplay().setCursor(202, y0 + 64);
|
||||||
|
getdisplay().print("Backlight:");
|
||||||
|
getdisplay().setCursor(320, y0 + 64);
|
||||||
|
getdisplay().printf("%d%%", commonData->backlight.brightness);
|
||||||
|
// TODO test function with OBP60 device
|
||||||
|
getdisplay().setCursor(202, y0 + 80);
|
||||||
|
getdisplay().print("Bl color:");
|
||||||
|
getdisplay().setCursor(320, y0 + 80);
|
||||||
|
getdisplay().print(commonData->backlight.color.toName());
|
||||||
|
getdisplay().setCursor(202, y0 + 96);
|
||||||
|
getdisplay().print("Bl mode:");
|
||||||
|
getdisplay().setCursor(320, y0 + 96);
|
||||||
|
getdisplay().print(commonData->backlight.mode);
|
||||||
|
// TODO Buzzer mode and power
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayModeSDCard() {
|
||||||
|
|
||||||
|
// SD Card info
|
||||||
|
uint16_t x0 = 20;
|
||||||
|
uint16_t y0 = 72;
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(8, 48);
|
||||||
|
getdisplay().print("SD Card info");
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(x0, y0);
|
||||||
|
#ifdef BOARD_OBP60S3
|
||||||
|
// This mode should not be callable by devices without card hardware
|
||||||
|
// In case of accidential reaching this, display a friendly message
|
||||||
|
getdisplay().print("This mode is not indended to be reached!\n");
|
||||||
|
getdisplay().print("There's nothing to see here. Move on.");
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_OBP40S3
|
||||||
|
getdisplay().print("Work in progress...");
|
||||||
|
|
||||||
|
/* TODO
|
||||||
|
this code should go somewhere else. only for testing purposes here
|
||||||
|
identify card as OBP-Card:
|
||||||
|
magic.dat
|
||||||
|
version.dat
|
||||||
|
readme.txt
|
||||||
|
IMAGES/
|
||||||
|
CHARTS/
|
||||||
|
LOGS/
|
||||||
|
DATA/
|
||||||
|
hint: file access with fopen, fgets, fread, fclose
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Simple test for magic file in root
|
||||||
|
getdisplay().setCursor(x0, y0 + 32);
|
||||||
|
String file_magic = MOUNT_POINT "/magic.dat";
|
||||||
|
commonData->logger->logDebug(GwLog::LOG, "Test magicfile: %s", file_magic.c_str());
|
||||||
|
struct stat st;
|
||||||
|
if (stat(file_magic.c_str(), &st) == 0) {
|
||||||
|
getdisplay().printf("File %s exists", file_magic.c_str());
|
||||||
|
} else {
|
||||||
|
getdisplay().printf("File %s not found", file_magic.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root directory check
|
||||||
|
DIR* dir = opendir(MOUNT_POINT);
|
||||||
|
int dy = 0;
|
||||||
|
if (dir != NULL) {
|
||||||
|
commonData->logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT);
|
||||||
|
struct dirent* entry;
|
||||||
|
while (((entry = readdir(dir)) != NULL) and (dy < 140)) {
|
||||||
|
getdisplay().setCursor(x0, y0 + 64 + dy);
|
||||||
|
getdisplay().print(entry->d_name);
|
||||||
|
// type 1 is file, type 2 is dir
|
||||||
|
if (entry->d_type == 2) {
|
||||||
|
getdisplay().print("/");
|
||||||
|
}
|
||||||
|
dy += 20;
|
||||||
|
commonData->logger->logDebug(GwLog::DEBUG, " %s type %d", entry->d_name, entry->d_type);
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
} else {
|
||||||
|
commonData->logger->logDebug(GwLog::LOG, "Failed to open root directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayModeDevicelist() {
|
||||||
|
// NMEA2000 device list
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||||
|
getdisplay().setCursor(8, 48);
|
||||||
|
getdisplay().print("NMEA2000 device list");
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
getdisplay().setCursor(20, 70);
|
||||||
|
getdisplay().print("RxD: ");
|
||||||
|
getdisplay().print(String(commonData->status.n2kRx));
|
||||||
|
getdisplay().setCursor(120, 70);
|
||||||
|
getdisplay().print("TxD: ");
|
||||||
|
getdisplay().print(String(commonData->status.n2kTx));
|
||||||
|
|
||||||
|
#ifdef PATCH_N2K
|
||||||
|
uint16_t x0 = 20;
|
||||||
|
uint16_t y0 = 100;
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold10pt8b);
|
||||||
|
getdisplay().setCursor(x0, y0);
|
||||||
|
getdisplay().print("ID");
|
||||||
|
getdisplay().setCursor(x0 + 50, y0);
|
||||||
|
getdisplay().print("Model");
|
||||||
|
getdisplay().setCursor(x0 + 250, y0);
|
||||||
|
getdisplay().print("Manuf.");
|
||||||
|
getdisplay().drawLine(18, y0 + 4, 360 , y0 + 4 , commonData->fgcolor);
|
||||||
|
|
||||||
|
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||||
|
y0 = 120;
|
||||||
|
uint8_t n_dev = 0;
|
||||||
|
for (const device& item : devicelist) {
|
||||||
|
if (n_dev > 8) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
getdisplay().setCursor(x0, y0 + n_dev * 20);
|
||||||
|
getdisplay().print(item.id);
|
||||||
|
getdisplay().setCursor(x0 + 50, y0 + n_dev * 20);
|
||||||
|
getdisplay().print(item.model);
|
||||||
|
getdisplay().setCursor(x0 + 250, y0 + n_dev * 20);
|
||||||
|
getdisplay().print(item.manuf_code);
|
||||||
|
n_dev++;
|
||||||
|
}
|
||||||
|
getdisplay().setCursor(x0, y0 + (n_dev + 1) * 20);
|
||||||
|
if (n_dev == 0) {
|
||||||
|
getdisplay().printf("no devices found on bus");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
getdisplay().drawLine(18, y0 + n_dev * 20, 360 , y0 + n_dev * 20, commonData->fgcolor);
|
||||||
|
getdisplay().printf("%d devices of %d in total", n_dev, devicelist.size());
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
getdisplay().setCursor(20, 100);
|
||||||
|
getdisplay().print("NMEA2000 not exposed to obp60 task");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PageSystem(CommonData &common){
|
PageSystem(CommonData &common){
|
||||||
commonData = &common;
|
commonData = &common;
|
||||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageSystem");
|
commonData->logger->logDebug(GwLog::LOG,"Instantiate PageSystem");
|
||||||
if (hasFRAM) {
|
if (hasFRAM) {
|
||||||
mode = fram.read(FRAM_SYSTEM_MODE);
|
mode = fram.read(FRAM_SYSTEM_MODE);
|
||||||
common.logger->logDebug(GwLog::DEBUG, "Loaded mode '%c' from FRAM", mode);
|
commonData->logger->logDebug(GwLog::DEBUG, "Loaded mode '%c' from FRAM", mode);
|
||||||
}
|
}
|
||||||
chipid = ESP.getEfuseMac();
|
chipid = ESP.getEfuseMac();
|
||||||
simulation = common.config->getBool(common.config->useSimuData);
|
simulation = common.config->getBool(common.config->useSimuData);
|
||||||
@@ -67,6 +500,7 @@ public:
|
|||||||
buzzer_mode.toLowerCase();
|
buzzer_mode.toLowerCase();
|
||||||
buzzer_power = common.config->getInt(common.config->buzzerPower);
|
buzzer_power = common.config->getInt(common.config->buzzerPower);
|
||||||
cpuspeed = common.config->getString(common.config->cpuSpeed);
|
cpuspeed = common.config->getString(common.config->cpuSpeed);
|
||||||
|
powermode = common.config->getString(common.config->powerMode);
|
||||||
env_module = common.config->getString(common.config->useEnvSensor);
|
env_module = common.config->getString(common.config->useEnvSensor);
|
||||||
rtc_module = common.config->getString(common.config->useRTC);
|
rtc_module = common.config->getString(common.config->useRTC);
|
||||||
gps_module = common.config->getString(common.config->useGPS);
|
gps_module = common.config->getString(common.config->useGPS);
|
||||||
@@ -76,6 +510,7 @@ public:
|
|||||||
rot_sensor = common.config->getString(common.config->useRotSensor);
|
rot_sensor = common.config->getString(common.config->useRotSensor);
|
||||||
homelat = common.config->getString(common.config->homeLAT).toDouble();
|
homelat = common.config->getString(common.config->homeLAT).toDouble();
|
||||||
homelon = common.config->getString(common.config->homeLON).toDouble();
|
homelon = common.config->getString(common.config->homeLON).toDouble();
|
||||||
|
flashLED = common.config->getString(common.config->flashLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupKeys() {
|
void setupKeys() {
|
||||||
@@ -92,19 +527,7 @@ public:
|
|||||||
// Switch display mode
|
// Switch display mode
|
||||||
commonData->logger->logDebug(GwLog::LOG, "System keyboard handler");
|
commonData->logger->logDebug(GwLog::LOG, "System keyboard handler");
|
||||||
if (key == 2) {
|
if (key == 2) {
|
||||||
if (mode == 'N') {
|
incMode();
|
||||||
mode = 'S';
|
|
||||||
} else if (mode == 'S') {
|
|
||||||
mode = 'D';
|
|
||||||
} else if (mode == 'D') {
|
|
||||||
if (hasSDCard) {
|
|
||||||
mode = 'C';
|
|
||||||
} else {
|
|
||||||
mode = 'N';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mode = 'N';
|
|
||||||
}
|
|
||||||
if (hasFRAM) fram.write(FRAM_SYSTEM_MODE, mode);
|
if (hasFRAM) fram.write(FRAM_SYSTEM_MODE, mode);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -129,8 +552,13 @@ public:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef BOARD_OBP40S3
|
#ifdef BOARD_OBP40S3
|
||||||
// grab cursor keys to disable page navigation
|
// use cursor keys for local mode navigation
|
||||||
if (key == 9 or key == 10) {
|
if (key == 9) {
|
||||||
|
incMode();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (key == 10) {
|
||||||
|
decMode();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// standby / deep sleep
|
// standby / deep sleep
|
||||||
@@ -168,305 +596,64 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int displayPage(PageData &pageData){
|
void displayNew(PageData &pageData) {
|
||||||
GwConfigHandler *config = commonData->config;
|
#ifdef BOARD_OBP60S3
|
||||||
GwLog *logger = commonData->logger;
|
// Clear optical warning
|
||||||
|
if (flashLED == "Limit Violation") {
|
||||||
// Get config data
|
|
||||||
String flashLED = config->getString(config->flashLED);
|
|
||||||
|
|
||||||
// Optical warning by limit violation (unused)
|
|
||||||
if(String(flashLED) == "Limit Violation"){
|
|
||||||
setBlinkingLED(false);
|
setBlinkingLED(false);
|
||||||
setFlashLED(false);
|
setFlashLED(false);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Logging boat values
|
#ifdef PATCH_N2K
|
||||||
logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
|
// load current device list
|
||||||
|
tN2kDeviceList *pDevList = pageData.api->getN2kDeviceList();
|
||||||
|
// TODO check if changed
|
||||||
|
if (pDevList->ReadResetIsListUpdated()) {
|
||||||
|
// only reload if changed
|
||||||
|
devicelist.clear();
|
||||||
|
for (uint8_t i = 0; i <= 252; i++) {
|
||||||
|
const tNMEA2000::tDevice *d = pDevList->FindDeviceBySource(i);
|
||||||
|
if (d == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
device dev;
|
||||||
|
dev.id = i;
|
||||||
|
dev.NAME = d->GetName();
|
||||||
|
snprintf(dev.hex_name, sizeof(dev.hex_name), "%08X%08X", (uint32_t)(dev.NAME >> 32), (uint32_t)(dev.NAME & 0xFFFFFFFF));
|
||||||
|
dev.manuf_code = d->GetManufacturerCode();
|
||||||
|
dev.model = d->GetModelID();
|
||||||
|
devicelist.push_back(dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
// Draw page
|
int displayPage(PageData &pageData){
|
||||||
//***********************************************************
|
|
||||||
|
|
||||||
uint16_t x0 = 8; // left column
|
// Logging page information
|
||||||
uint16_t y0 = 48; // data table starts here
|
commonData->logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
|
||||||
|
|
||||||
// Set display in partial refresh mode
|
// Set display in partial refresh mode
|
||||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||||
|
|
||||||
if (mode == 'N') {
|
// call current system page
|
||||||
|
switch (mode) {
|
||||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
case 'N':
|
||||||
getdisplay().setCursor(8, 48);
|
displayModeNormal();
|
||||||
getdisplay().print("System Information");
|
break;
|
||||||
|
case 'S':
|
||||||
getdisplay().drawXBitmap(320, 25, logo64_bits, logo64_width, logo64_height, commonData->fgcolor);
|
displayModeSettings();
|
||||||
|
break;
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
case 'C':
|
||||||
y0 = 155;
|
displayModeConfig();
|
||||||
|
break;
|
||||||
char ssid[13];
|
case 'A':
|
||||||
snprintf(ssid, 13, "%04X%08X", (uint16_t)(chipid >> 32), (uint32_t)chipid);
|
displayModeSDCard();
|
||||||
displayBarcode(String(ssid), 320, 200, 2);
|
break;
|
||||||
getdisplay().setCursor(8, 70);
|
case 'D':
|
||||||
getdisplay().print(String("MCUDEVICE-") + String(ssid));
|
displayModeDevicelist();
|
||||||
|
break;
|
||||||
getdisplay().setCursor(8, 95);
|
|
||||||
getdisplay().print("Firmware version: ");
|
|
||||||
getdisplay().setCursor(150, 95);
|
|
||||||
getdisplay().print(VERSINFO);
|
|
||||||
|
|
||||||
getdisplay().setCursor(8, 113);
|
|
||||||
getdisplay().print("Board version: ");
|
|
||||||
getdisplay().setCursor(150, 113);
|
|
||||||
getdisplay().print(BOARDINFO);
|
|
||||||
getdisplay().print(String(" HW ") + String(PCBINFO));
|
|
||||||
|
|
||||||
getdisplay().setCursor(8, 131);
|
|
||||||
getdisplay().print("Display version: ");
|
|
||||||
getdisplay().setCursor(150, 131);
|
|
||||||
getdisplay().print(DISPLAYINFO);
|
|
||||||
getdisplay().print("; GxEPD2 v");
|
|
||||||
getdisplay().print(GXEPD2INFO);
|
|
||||||
|
|
||||||
getdisplay().setCursor(8, 265);
|
|
||||||
#ifdef BOARD_OBP60S3
|
|
||||||
getdisplay().print("Press STBY to enter deep sleep mode");
|
|
||||||
#endif
|
|
||||||
#ifdef BOARD_OBP40S3
|
|
||||||
getdisplay().print("Press wheel to enter deep sleep mode");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Flash memory size
|
|
||||||
uint32_t flash_size = ESP.getFlashChipSize();
|
|
||||||
getdisplay().setCursor(8, y0);
|
|
||||||
getdisplay().print("FLASH:");
|
|
||||||
getdisplay().setCursor(90, y0);
|
|
||||||
getdisplay().print(String(flash_size / 1024) + String(" kB"));
|
|
||||||
|
|
||||||
// PSRAM memory size
|
|
||||||
uint32_t psram_size = ESP.getPsramSize();
|
|
||||||
getdisplay().setCursor(8, y0 + 16);
|
|
||||||
getdisplay().print("PSRAM:");
|
|
||||||
getdisplay().setCursor(90, y0 + 16);
|
|
||||||
getdisplay().print(String(psram_size / 1024) + String(" kB"));
|
|
||||||
|
|
||||||
// FRAM available / status
|
|
||||||
getdisplay().setCursor(8, y0 + 32);
|
|
||||||
getdisplay().print("FRAM:");
|
|
||||||
getdisplay().setCursor(90, y0 + 32);
|
|
||||||
getdisplay().print(hasFRAM ? "available" : "not found");
|
|
||||||
|
|
||||||
#ifdef BOARD_OBP40S3
|
|
||||||
// SD-Card
|
|
||||||
getdisplay().setCursor(8, y0 + 48);
|
|
||||||
getdisplay().print("SD-Card:");
|
|
||||||
getdisplay().setCursor(90, y0 + 48);
|
|
||||||
if (hasSDCard) {
|
|
||||||
uint64_t cardsize = ((uint64_t) sdcard->csd.capacity) * sdcard->csd.sector_size / (1024 * 1024);
|
|
||||||
getdisplay().printf("%llu MB", cardsize);
|
|
||||||
} else {
|
|
||||||
getdisplay().print("off");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Uptime
|
|
||||||
int64_t uptime = esp_timer_get_time() / 1000000;
|
|
||||||
String uptime_unit;
|
|
||||||
if (uptime < 120) {
|
|
||||||
uptime_unit = " seconds";
|
|
||||||
} else {
|
|
||||||
if (uptime < 2 * 3600) {
|
|
||||||
uptime /= 60;
|
|
||||||
uptime_unit = " minutes";
|
|
||||||
} else if (uptime < 2 * 3600 * 24) {
|
|
||||||
uptime /= 3600;
|
|
||||||
uptime_unit = " hours";
|
|
||||||
} else {
|
|
||||||
uptime /= 86400;
|
|
||||||
uptime_unit = " days";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getdisplay().setCursor(8, y0 + 80);
|
|
||||||
getdisplay().print("Uptime:");
|
|
||||||
getdisplay().setCursor(90, y0 + 80);
|
|
||||||
getdisplay().print(uptime);
|
|
||||||
getdisplay().print(uptime_unit);
|
|
||||||
|
|
||||||
// CPU speed config / active
|
|
||||||
getdisplay().setCursor(202, y0);
|
|
||||||
getdisplay().print("CPU speed:");
|
|
||||||
getdisplay().setCursor(300, y0);
|
|
||||||
getdisplay().print(cpuspeed);
|
|
||||||
getdisplay().print(" / ");
|
|
||||||
int cpu_freq = esp_clk_cpu_freq() / 1000000;
|
|
||||||
getdisplay().print(String(cpu_freq));
|
|
||||||
|
|
||||||
// total RAM free
|
|
||||||
int Heap_free = esp_get_free_heap_size();
|
|
||||||
getdisplay().setCursor(202, y0 + 16);
|
|
||||||
getdisplay().print("Total free:");
|
|
||||||
getdisplay().setCursor(300, y0 + 16);
|
|
||||||
getdisplay().print(String(Heap_free));
|
|
||||||
|
|
||||||
// RAM free for task
|
|
||||||
int RAM_free = uxTaskGetStackHighWaterMark(NULL);
|
|
||||||
getdisplay().setCursor(202, y0 + 32);
|
|
||||||
getdisplay().print("Task free:");
|
|
||||||
getdisplay().setCursor(300, y0 + 32);
|
|
||||||
getdisplay().print(String(RAM_free));
|
|
||||||
|
|
||||||
} else if (mode == 'S') {
|
|
||||||
// Settings
|
|
||||||
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
|
||||||
getdisplay().setCursor(x0, 48);
|
|
||||||
getdisplay().print("System settings");
|
|
||||||
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
|
||||||
x0 = 8;
|
|
||||||
y0 = 72;
|
|
||||||
|
|
||||||
// left column
|
|
||||||
getdisplay().setCursor(x0, y0);
|
|
||||||
getdisplay().print("Simulation:");
|
|
||||||
getdisplay().setCursor(120, y0);
|
|
||||||
getdisplay().print(simulation ? "on" : "off");
|
|
||||||
|
|
||||||
getdisplay().setCursor(x0, y0 + 16);
|
|
||||||
getdisplay().print("Environment:");
|
|
||||||
getdisplay().setCursor(120, y0 + 16);
|
|
||||||
getdisplay().print(env_module);
|
|
||||||
|
|
||||||
getdisplay().setCursor(x0, y0 + 32);
|
|
||||||
getdisplay().print("Buzzer:");
|
|
||||||
getdisplay().setCursor(120, y0 + 32);
|
|
||||||
getdisplay().print(buzzer_mode);
|
|
||||||
|
|
||||||
getdisplay().setCursor(x0, y0 + 64);
|
|
||||||
getdisplay().print("GPS:");
|
|
||||||
getdisplay().setCursor(120, y0 + 64);
|
|
||||||
getdisplay().print(gps_module);
|
|
||||||
|
|
||||||
getdisplay().setCursor(x0, y0 + 80);
|
|
||||||
getdisplay().print("RTC:");
|
|
||||||
getdisplay().setCursor(120, y0 + 80);
|
|
||||||
getdisplay().print(rtc_module);
|
|
||||||
|
|
||||||
getdisplay().setCursor(x0, y0 + 96);
|
|
||||||
getdisplay().print("Wifi:");
|
|
||||||
getdisplay().setCursor(120, y0 + 96);
|
|
||||||
getdisplay().print(commonData->status.wifiApOn ? "on" : "off");
|
|
||||||
|
|
||||||
// Home location
|
|
||||||
getdisplay().setCursor(x0, y0 + 128);
|
|
||||||
getdisplay().print("Home Lat.:");
|
|
||||||
getdisplay().setCursor(120, y0 + 128);
|
|
||||||
getdisplay().print(formatLatitude(homelat));
|
|
||||||
getdisplay().setCursor(x0, y0 + 144);
|
|
||||||
getdisplay().print("Home Lon.:");
|
|
||||||
getdisplay().setCursor(120, y0 + 144);
|
|
||||||
getdisplay().print(formatLongitude(homelon));
|
|
||||||
|
|
||||||
// right column
|
|
||||||
getdisplay().setCursor(202, y0);
|
|
||||||
getdisplay().print("Batt. sensor:");
|
|
||||||
getdisplay().setCursor(320, y0);
|
|
||||||
getdisplay().print(batt_sensor);
|
|
||||||
|
|
||||||
// Solar sensor
|
|
||||||
getdisplay().setCursor(202, y0 + 16);
|
|
||||||
getdisplay().print("Solar sensor:");
|
|
||||||
getdisplay().setCursor(320, y0 + 16);
|
|
||||||
getdisplay().print(solar_sensor);
|
|
||||||
|
|
||||||
// Generator sensor
|
|
||||||
getdisplay().setCursor(202, y0 + 32);
|
|
||||||
getdisplay().print("Gen. sensor:");
|
|
||||||
getdisplay().setCursor(320, y0 + 32);
|
|
||||||
getdisplay().print(gen_sensor);
|
|
||||||
|
|
||||||
// Gyro sensor
|
|
||||||
|
|
||||||
} else if (mode == 'C') {
|
|
||||||
// Card info
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
|
||||||
getdisplay().setCursor(8, 48);
|
|
||||||
getdisplay().print("SD Card info");
|
|
||||||
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
|
||||||
|
|
||||||
x0 = 20;
|
|
||||||
y0 = 72;
|
|
||||||
getdisplay().setCursor(x0, y0);
|
|
||||||
#ifdef BOARD_OBP60S3
|
|
||||||
// This mode should not be callable by devices without card hardware
|
|
||||||
// In case of accidential reaching this, display a friendly message
|
|
||||||
getdisplay().print("This mode is not indended to be reached!\n");
|
|
||||||
getdisplay().print("There's nothing to see here. Move on.");
|
|
||||||
#endif
|
|
||||||
#ifdef BOARD_OBP40S3
|
|
||||||
getdisplay().print("Work in progress...");
|
|
||||||
|
|
||||||
/* TODO
|
|
||||||
this code should go somewhere else. only for testing purposes here
|
|
||||||
identify card as OBP-Card:
|
|
||||||
magic.dat
|
|
||||||
version.dat
|
|
||||||
readme.txt
|
|
||||||
IMAGES/
|
|
||||||
CHARTS/
|
|
||||||
LOGS/
|
|
||||||
DATA/
|
|
||||||
hint: file access with fopen, fgets, fread, fclose
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Simple test for magic file in root
|
|
||||||
getdisplay().setCursor(x0, y0 + 32);
|
|
||||||
String file_magic = MOUNT_POINT "/magic.dat";
|
|
||||||
logger->logDebug(GwLog::LOG, "Test magicfile: %s", file_magic.c_str());
|
|
||||||
struct stat st;
|
|
||||||
if (stat(file_magic.c_str(), &st) == 0) {
|
|
||||||
getdisplay().printf("File %s exists", file_magic.c_str());
|
|
||||||
} else {
|
|
||||||
getdisplay().printf("File %s not found", file_magic.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root directory check
|
|
||||||
DIR* dir = opendir(MOUNT_POINT);
|
|
||||||
int dy = 0;
|
|
||||||
if (dir != NULL) {
|
|
||||||
logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT);
|
|
||||||
struct dirent* entry;
|
|
||||||
while (((entry = readdir(dir)) != NULL) and (dy < 140)) {
|
|
||||||
getdisplay().setCursor(x0, y0 + 64 + dy);
|
|
||||||
getdisplay().print(entry->d_name);
|
|
||||||
// type 1 is file, type 2 is dir
|
|
||||||
if (entry->d_type == 2) {
|
|
||||||
getdisplay().print("/");
|
|
||||||
}
|
|
||||||
dy += 20;
|
|
||||||
logger->logDebug(GwLog::DEBUG, " %s type %d", entry->d_name, entry->d_type);
|
|
||||||
}
|
|
||||||
closedir(dir);
|
|
||||||
} else {
|
|
||||||
logger->logDebug(GwLog::LOG, "Failed to open root directory");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// NMEA2000 device list
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
|
||||||
getdisplay().setCursor(8, 48);
|
|
||||||
getdisplay().print("NMEA2000 device list");
|
|
||||||
|
|
||||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
|
||||||
getdisplay().setCursor(20, 80);
|
|
||||||
getdisplay().print("RxD: ");
|
|
||||||
getdisplay().print(String(commonData->status.n2kRx));
|
|
||||||
getdisplay().setCursor(20, 100);
|
|
||||||
getdisplay().print("TxD: ");
|
|
||||||
getdisplay().print(String(commonData->status.n2kTx));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display
|
// Update display
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (numValues == 2 && mode == FULL) { // print line only, if we want to show 2 data values
|
if (numValues == 2 && mode == FULL) { // print line only, if we want to show 2 data values
|
||||||
getdisplay().fillRect(0, 145, width, 3, commonData->fgcolor); // Horizontal line 3 pix
|
getdisplay().fillRect(0, 145, width, 3, commonData->fgcolor); // Horizontal line 3 pix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +149,11 @@ public:
|
|||||||
|
|
||||||
if (dataHstryBuf[0] || dataHstryBuf[1]) { // show "Mode" key only if at least 1 chart supported boat data type is available
|
if (dataHstryBuf[0] || dataHstryBuf[1]) { // show "Mode" key only if at least 1 chart supported boat data type is available
|
||||||
commonData->keydata[0].label = "MODE";
|
commonData->keydata[0].label = "MODE";
|
||||||
commonData->keydata[ZOOM_KEY].label = "ZOOM";
|
if (pageMode != VALUES) { // show "ZOOM" key only if chart is visible
|
||||||
|
commonData->keydata[ZOOM_KEY].label = "ZOOM";
|
||||||
|
} else {
|
||||||
|
commonData->keydata[ZOOM_KEY].label = "";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
commonData->keydata[0].label = "";
|
commonData->keydata[0].label = "";
|
||||||
commonData->keydata[ZOOM_KEY].label = "";
|
commonData->keydata[ZOOM_KEY].label = "";
|
||||||
@@ -191,14 +195,15 @@ public:
|
|||||||
pageMode = VALUES;
|
pageMode = VALUES;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||||
return 0; // Commit the key
|
return 0; // Commit the key
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set time frame to show for history chart
|
// Set time frame to show for chart
|
||||||
#if defined BOARD_OBP60S3
|
#if defined BOARD_OBP60S3
|
||||||
if (key == 5) {
|
if (key == 5 && pageMode != VALUES) {
|
||||||
#elif defined BOARD_OBP40S3
|
#elif defined BOARD_OBP40S3
|
||||||
if (key == 2) {
|
if (key == 2 && pageMode != VALUES) {
|
||||||
#endif
|
#endif
|
||||||
if (dataIntv == 1) {
|
if (dataIntv == 1) {
|
||||||
dataIntv = 2;
|
dataIntv = 2;
|
||||||
@@ -251,7 +256,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupKeys(); // adjust <mode> key depending on chart supported boat data type
|
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||||
}
|
}
|
||||||
|
|
||||||
int displayPage(PageData& pageData)
|
int displayPage(PageData& pageData)
|
||||||
@@ -285,13 +290,13 @@ public:
|
|||||||
showData(bValue, FULL);
|
showData(bValue, FULL);
|
||||||
|
|
||||||
} else if (pageMode == VAL1_CHART) { // show data value 1 and chart
|
} else if (pageMode == VAL1_CHART) { // show data value 1 and chart
|
||||||
showData({bValue[0]}, HALF);
|
showData({ bValue[0] }, HALF);
|
||||||
if (dataChart[0]) {
|
if (dataChart[0]) {
|
||||||
dataChart[0]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[0]);
|
dataChart[0]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (pageMode == VAL2_CHART) { // show data value 2 and chart
|
} else if (pageMode == VAL2_CHART) { // show data value 2 and chart
|
||||||
showData({bValue[1]}, HALF);
|
showData({ bValue[1] }, HALF);
|
||||||
if (dataChart[1]) {
|
if (dataChart[1]) {
|
||||||
dataChart[1]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[1]);
|
dataChart[1]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[1]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ public:
|
|||||||
int displayPage(PageData& pageData)
|
int displayPage(PageData& pageData)
|
||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::LOG, "Display PageWindPlot");
|
LOG_DEBUG(GwLog::LOG, "Display PageWindPlot");
|
||||||
ulong pageTime = millis();
|
// ulong pageTime = millis();
|
||||||
|
|
||||||
if (showTruW != oldShowTruW) {
|
if (showTruW != oldShowTruW) {
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: page time %ldms", millis() - pageTime);
|
// LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: page time %ldms", millis() - pageTime);
|
||||||
return PAGE_UPDATE;
|
return PAGE_UPDATE;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ typedef struct{
|
|||||||
double rotationAngle = 0; // Rotation angle in radiant
|
double rotationAngle = 0; // Rotation angle in radiant
|
||||||
bool validRotAngle = false; // Valid flag magnet present for rotation sensor
|
bool validRotAngle = false; // Valid flag magnet present for rotation sensor
|
||||||
struct tm rtcTime; // UTC time from internal RTC
|
struct tm rtcTime; // UTC time from internal RTC
|
||||||
bool rtcValid = false;
|
bool rtcValid = false; // Internal RTC chip
|
||||||
int sunsetHour = 0;
|
int sunsetHour = 0;
|
||||||
int sunsetMinute = 0;
|
int sunsetMinute = 0;
|
||||||
int sunriseHour = 0;
|
int sunriseHour = 0;
|
||||||
|
|||||||
@@ -1245,8 +1245,8 @@
|
|||||||
"name": "timeSource",
|
"name": "timeSource",
|
||||||
"label": "Status Time Source",
|
"label": "Status Time Source",
|
||||||
"type": "list",
|
"type": "list",
|
||||||
"default": "GPS",
|
"default": "iRTC",
|
||||||
"description": "Data source for date and time display in status line [RTC|iRTC|GPS]",
|
"description": "Data source for date and time display in status line [iRTC|RTC|GPS]",
|
||||||
"list": [
|
"list": [
|
||||||
{"l":"Internal real time clock (iRTC)","v":"iRTC"},
|
{"l":"Internal real time clock (iRTC)","v":"iRTC"},
|
||||||
{"l":"External real time clock (RTC)","v":"RTC"},
|
{"l":"External real time clock (RTC)","v":"RTC"},
|
||||||
@@ -1517,6 +1517,7 @@
|
|||||||
"default": "Voltage",
|
"default": "Voltage",
|
||||||
"description": "Type of page for page 1",
|
"description": "Type of page for page 1",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -1818,7 +1819,7 @@
|
|||||||
"description": "Wind source for page 1: [true|apparent]",
|
"description": "Wind source for page 1: [true|apparent]",
|
||||||
"list": [
|
"list": [
|
||||||
"True wind",
|
"True wind",
|
||||||
"apparent wind"
|
"Apparent wind"
|
||||||
],
|
],
|
||||||
"category": "OBP40 Page 1",
|
"category": "OBP40 Page 1",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
@@ -1847,6 +1848,7 @@
|
|||||||
"default": "WindRose",
|
"default": "WindRose",
|
||||||
"description": "Type of page for page 2",
|
"description": "Type of page for page 2",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2140,7 +2142,7 @@
|
|||||||
"description": "Wind source for page 2: [true|apparent]",
|
"description": "Wind source for page 2: [true|apparent]",
|
||||||
"list": [
|
"list": [
|
||||||
"True wind",
|
"True wind",
|
||||||
"apparent wind"
|
"Apparent wind"
|
||||||
],
|
],
|
||||||
"category": "OBP40 Page 2",
|
"category": "OBP40 Page 2",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
@@ -2168,6 +2170,7 @@
|
|||||||
"default": "OneValue",
|
"default": "OneValue",
|
||||||
"description": "Type of page for page 3",
|
"description": "Type of page for page 3",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2453,7 +2456,7 @@
|
|||||||
"description": "Wind source for page 3: [true|apparent]",
|
"description": "Wind source for page 3: [true|apparent]",
|
||||||
"list": [
|
"list": [
|
||||||
"True wind",
|
"True wind",
|
||||||
"apparent wind"
|
"Apparent wind"
|
||||||
],
|
],
|
||||||
"category": "OBP40 Page 3",
|
"category": "OBP40 Page 3",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
@@ -2480,6 +2483,7 @@
|
|||||||
"default": "TwoValues",
|
"default": "TwoValues",
|
||||||
"description": "Type of page for page 4",
|
"description": "Type of page for page 4",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2757,7 +2761,7 @@
|
|||||||
"description": "Wind source for page 4: [true|apparent]",
|
"description": "Wind source for page 4: [true|apparent]",
|
||||||
"list": [
|
"list": [
|
||||||
"True wind",
|
"True wind",
|
||||||
"apparent wind"
|
"Apparent wind"
|
||||||
],
|
],
|
||||||
"category": "OBP40 Page 4",
|
"category": "OBP40 Page 4",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
@@ -2783,6 +2787,7 @@
|
|||||||
"default": "ThreeValues",
|
"default": "ThreeValues",
|
||||||
"description": "Type of page for page 5",
|
"description": "Type of page for page 5",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3052,7 +3057,7 @@
|
|||||||
"description": "Wind source for page 5: [true|apparent]",
|
"description": "Wind source for page 5: [true|apparent]",
|
||||||
"list": [
|
"list": [
|
||||||
"True wind",
|
"True wind",
|
||||||
"apparent wind"
|
"Apparent wind"
|
||||||
],
|
],
|
||||||
"category": "OBP40 Page 5",
|
"category": "OBP40 Page 5",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
@@ -3077,6 +3082,7 @@
|
|||||||
"default": "FourValues",
|
"default": "FourValues",
|
||||||
"description": "Type of page for page 6",
|
"description": "Type of page for page 6",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3338,7 +3344,7 @@
|
|||||||
"description": "Wind source for page 6: [true|apparent]",
|
"description": "Wind source for page 6: [true|apparent]",
|
||||||
"list": [
|
"list": [
|
||||||
"True wind",
|
"True wind",
|
||||||
"apparent wind"
|
"Apparent wind"
|
||||||
],
|
],
|
||||||
"category": "OBP40 Page 6",
|
"category": "OBP40 Page 6",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
@@ -3362,6 +3368,7 @@
|
|||||||
"default": "FourValues2",
|
"default": "FourValues2",
|
||||||
"description": "Type of page for page 7",
|
"description": "Type of page for page 7",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3615,7 +3622,7 @@
|
|||||||
"description": "Wind source for page 7: [true|apparent]",
|
"description": "Wind source for page 7: [true|apparent]",
|
||||||
"list": [
|
"list": [
|
||||||
"True wind",
|
"True wind",
|
||||||
"apparent wind"
|
"Apparent wind"
|
||||||
],
|
],
|
||||||
"category": "OBP40 Page 7",
|
"category": "OBP40 Page 7",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
@@ -3638,6 +3645,7 @@
|
|||||||
"default": "Clock",
|
"default": "Clock",
|
||||||
"description": "Type of page for page 8",
|
"description": "Type of page for page 8",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3883,7 +3891,7 @@
|
|||||||
"description": "Wind source for page 8: [true|apparent]",
|
"description": "Wind source for page 8: [true|apparent]",
|
||||||
"list": [
|
"list": [
|
||||||
"True wind",
|
"True wind",
|
||||||
"apparent wind"
|
"Apparent wind"
|
||||||
],
|
],
|
||||||
"category": "OBP40 Page 8",
|
"category": "OBP40 Page 8",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
@@ -3905,6 +3913,7 @@
|
|||||||
"default": "RollPitch",
|
"default": "RollPitch",
|
||||||
"description": "Type of page for page 9",
|
"description": "Type of page for page 9",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -4142,7 +4151,7 @@
|
|||||||
"description": "Wind source for page 9: [true|apparent]",
|
"description": "Wind source for page 9: [true|apparent]",
|
||||||
"list": [
|
"list": [
|
||||||
"True wind",
|
"True wind",
|
||||||
"apparent wind"
|
"Apparent wind"
|
||||||
],
|
],
|
||||||
"category": "OBP40 Page 9",
|
"category": "OBP40 Page 9",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
@@ -4163,6 +4172,7 @@
|
|||||||
"default": "Battery2",
|
"default": "Battery2",
|
||||||
"description": "Type of page for page 10",
|
"description": "Type of page for page 10",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -4392,7 +4402,7 @@
|
|||||||
"description": "Wind source for page 10: [true|apparent]",
|
"description": "Wind source for page 10: [true|apparent]",
|
||||||
"list": [
|
"list": [
|
||||||
"True wind",
|
"True wind",
|
||||||
"apparent wind"
|
"Apparent wind"
|
||||||
],
|
],
|
||||||
"category": "OBP40 Page 10",
|
"category": "OBP40 Page 10",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
"name": "homeLAT",
|
"name": "homeLAT",
|
||||||
"label": "Home latitude",
|
"label": "Home latitude",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"default": "",
|
"default": "0.00000",
|
||||||
"check": "checkMinMax",
|
"check": "checkMinMax",
|
||||||
"min": -90.0,
|
"min": -90.0,
|
||||||
"max": 90.0,
|
"max": 90.0,
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"name": "homeLON",
|
"name": "homeLON",
|
||||||
"label": "Home longitude",
|
"label": "Home longitude",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"default": "",
|
"default": "0.00000",
|
||||||
"check": "checkMinMax",
|
"check": "checkMinMax",
|
||||||
"min": -180.0,
|
"min": -180.0,
|
||||||
"max": 180.0,
|
"max": 180.0,
|
||||||
@@ -1235,8 +1235,9 @@
|
|||||||
"label": "Status Time Source",
|
"label": "Status Time Source",
|
||||||
"type": "list",
|
"type": "list",
|
||||||
"default": "GPS",
|
"default": "GPS",
|
||||||
"description": "Data source for date and time display in status line [RTC|GPS]",
|
"description": "Data source for date and time display in status line [iRTC|RTC|GPS]",
|
||||||
"list": [
|
"list": [
|
||||||
|
{"l":"Internal real time clock (iRTC)","v":"iRTC"},
|
||||||
{"l":"Real time clock (RTC)","v":"RTC"},
|
{"l":"Real time clock (RTC)","v":"RTC"},
|
||||||
{"l":"Time via bus (GPS)","v":"GPS"}
|
{"l":"Time via bus (GPS)","v":"GPS"}
|
||||||
],
|
],
|
||||||
@@ -1494,6 +1495,7 @@
|
|||||||
"default": "Voltage",
|
"default": "Voltage",
|
||||||
"description": "Type of page for page 1",
|
"description": "Type of page for page 1",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -1794,6 +1796,7 @@
|
|||||||
"default": "WindRose",
|
"default": "WindRose",
|
||||||
"description": "Type of page for page 2",
|
"description": "Type of page for page 2",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2086,6 +2089,7 @@
|
|||||||
"default": "OneValue",
|
"default": "OneValue",
|
||||||
"description": "Type of page for page 3",
|
"description": "Type of page for page 3",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2370,6 +2374,7 @@
|
|||||||
"default": "TwoValues",
|
"default": "TwoValues",
|
||||||
"description": "Type of page for page 4",
|
"description": "Type of page for page 4",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2646,6 +2651,7 @@
|
|||||||
"default": "ThreeValues",
|
"default": "ThreeValues",
|
||||||
"description": "Type of page for page 5",
|
"description": "Type of page for page 5",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -2914,6 +2920,7 @@
|
|||||||
"default": "FourValues",
|
"default": "FourValues",
|
||||||
"description": "Type of page for page 6",
|
"description": "Type of page for page 6",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3174,6 +3181,7 @@
|
|||||||
"default": "FourValues2",
|
"default": "FourValues2",
|
||||||
"description": "Type of page for page 7",
|
"description": "Type of page for page 7",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3426,6 +3434,7 @@
|
|||||||
"default": "Clock",
|
"default": "Clock",
|
||||||
"description": "Type of page for page 8",
|
"description": "Type of page for page 8",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3670,6 +3679,7 @@
|
|||||||
"default": "RollPitch",
|
"default": "RollPitch",
|
||||||
"description": "Type of page for page 9",
|
"description": "Type of page for page 9",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
@@ -3906,6 +3916,7 @@
|
|||||||
"default": "Battery2",
|
"default": "Battery2",
|
||||||
"description": "Type of page for page 10",
|
"description": "Type of page for page 10",
|
||||||
"list": [
|
"list": [
|
||||||
|
"Autopilot",
|
||||||
"BME280",
|
"BME280",
|
||||||
"Battery",
|
"Battery",
|
||||||
"Battery2",
|
"Battery2",
|
||||||
|
|||||||
@@ -1,12 +1,29 @@
|
|||||||
# PlatformIO extra script for obp60task
|
# PlatformIO extra script for obp60task
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def cleanup_patches(source, target, env):
|
||||||
|
for p in patchfiles:
|
||||||
|
patch = os.path.join(patchdir, p)
|
||||||
|
print(f"removing {patch}")
|
||||||
|
res = subprocess.run(["git", "apply", "-R", patch], capture_output=True, text=True)
|
||||||
|
if res.returncode != 0:
|
||||||
|
print(res.stderr)
|
||||||
|
|
||||||
|
patching = False
|
||||||
|
|
||||||
epdtype = "unknown"
|
epdtype = "unknown"
|
||||||
pcbvers = "unknown"
|
pcbvers = "unknown"
|
||||||
for x in env["BUILD_FLAGS"]:
|
for x in env["BUILD_FLAGS"]:
|
||||||
if x.startswith("-D HARDWARE_"):
|
if not x.startswith('-D'):
|
||||||
|
continue
|
||||||
|
opt = x[2:].strip()
|
||||||
|
if opt.startswith("HARDWARE_"):
|
||||||
pcbvers = x.split('_')[1]
|
pcbvers = x.split('_')[1]
|
||||||
if x.startswith("-D DISPLAY_"):
|
elif opt.startswith("DISPLAY_"):
|
||||||
epdtype = x.split('_')[1]
|
epdtype = x.split('_')[1]
|
||||||
|
elif opt == 'ENABLE_PATCHES':
|
||||||
|
patching = True
|
||||||
|
|
||||||
propfilename = os.path.join(env["PROJECT_LIBDEPS_DIR"], env["PIOENV"], "GxEPD2/library.properties")
|
propfilename = os.path.join(env["PROJECT_LIBDEPS_DIR"], env["PIOENV"], "GxEPD2/library.properties")
|
||||||
properties = {}
|
properties = {}
|
||||||
@@ -28,3 +45,22 @@ except:
|
|||||||
env["CPPDEFINES"].extend([("BOARD", env["BOARD"]), ("EPDTYPE", epdtype), ("PCBVERS", pcbvers), ("GXEPD2VERS", gxepd2vers)])
|
env["CPPDEFINES"].extend([("BOARD", env["BOARD"]), ("EPDTYPE", epdtype), ("PCBVERS", pcbvers), ("GXEPD2VERS", gxepd2vers)])
|
||||||
|
|
||||||
print("added hardware info to CPPDEFINES")
|
print("added hardware info to CPPDEFINES")
|
||||||
|
|
||||||
|
if patching:
|
||||||
|
# apply patches to gateway code
|
||||||
|
print("applying gateway patches")
|
||||||
|
patchdir = os.path.join(os.path.dirname(script), "patches")
|
||||||
|
if not os.path.isdir(patchdir):
|
||||||
|
print("patchdir not found, no patches applied")
|
||||||
|
else:
|
||||||
|
patchfiles = [f for f in os.listdir(patchdir)]
|
||||||
|
if len(patchfiles) > 0:
|
||||||
|
for p in patchfiles:
|
||||||
|
patch = os.path.join(patchdir, p)
|
||||||
|
print(f"applying {patch}")
|
||||||
|
res = subprocess.run(["git", "apply", patch], capture_output=True, text=True)
|
||||||
|
if res.returncode != 0:
|
||||||
|
print(res.stderr)
|
||||||
|
env.AddPostAction("$PROGPATH", cleanup_patches)
|
||||||
|
else:
|
||||||
|
print("no patches found")
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ void OBP60Task(GwApi *api){
|
|||||||
// return;
|
// return;
|
||||||
GwLog *logger=api->getLogger();
|
GwLog *logger=api->getLogger();
|
||||||
GwConfigHandler *config=api->getConfig();
|
GwConfigHandler *config=api->getConfig();
|
||||||
#ifdef HARDWARE_V21
|
#if defined HARDWARE_V20 || HARDWARE_V21
|
||||||
startLedTask(api);
|
startLedTask(api);
|
||||||
#endif
|
#endif
|
||||||
PageList allPages;
|
PageList allPages;
|
||||||
@@ -341,7 +341,7 @@ void OBP60Task(GwApi *api){
|
|||||||
commonData.logger=logger;
|
commonData.logger=logger;
|
||||||
commonData.config=config;
|
commonData.config=config;
|
||||||
|
|
||||||
#ifdef HARDWARE_V21
|
#if defined HARDWARE_V20 || HARDWARE_V21
|
||||||
// Keyboard coordinates for page footer
|
// Keyboard coordinates for page footer
|
||||||
initKeys(commonData);
|
initKeys(commonData);
|
||||||
#endif
|
#endif
|
||||||
@@ -432,7 +432,7 @@ void OBP60Task(GwApi *api){
|
|||||||
#endif
|
#endif
|
||||||
LOG_DEBUG(GwLog::LOG,"...done");
|
LOG_DEBUG(GwLog::LOG,"...done");
|
||||||
|
|
||||||
int lastPage=-1; // initialize with an impiossible value, so we can detect wether we are during startup and no page has been displayed yet
|
int lastPage=-1; // initialize with an impossible value, so we can detect wether we are during startup and no page has been displayed yet
|
||||||
|
|
||||||
BoatValueList boatValues; //all the boat values for the api query
|
BoatValueList boatValues; //all the boat values for the api query
|
||||||
HstryBuffers hstryBufferList(1920, &boatValues, logger); // Create empty list of boat data history buffers (1.920 values = seconds = 32 min.)
|
HstryBuffers hstryBufferList(1920, &boatValues, logger); // Create empty list of boat data history buffers (1.920 values = seconds = 32 min.)
|
||||||
@@ -729,7 +729,7 @@ void OBP60Task(GwApi *api){
|
|||||||
else{
|
else{
|
||||||
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
||||||
#ifdef DISPLAY_GDEY042T81
|
#ifdef DISPLAY_GDEY042T81
|
||||||
getdisplay().hibernate(); // Set display in hybenate mode
|
getdisplay().hibernate(); // Set display in hibenate mode
|
||||||
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
|
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
|
||||||
#else
|
#else
|
||||||
getdisplay().init(115200); // Init for normal displays
|
getdisplay().init(115200); // Init for normal displays
|
||||||
@@ -757,7 +757,7 @@ void OBP60Task(GwApi *api){
|
|||||||
else{
|
else{
|
||||||
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
||||||
#ifdef DISPLAY_GDEY042T81
|
#ifdef DISPLAY_GDEY042T81
|
||||||
getdisplay().hibernate(); // Set display in hybenate mode
|
getdisplay().hibernate(); // Set display in hibernate mode
|
||||||
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
|
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
|
||||||
#else
|
#else
|
||||||
getdisplay().init(115200); // Init for normal displays
|
getdisplay().init(115200); // Init for normal displays
|
||||||
@@ -782,7 +782,7 @@ void OBP60Task(GwApi *api){
|
|||||||
else{
|
else{
|
||||||
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
||||||
#ifdef DISPLAY_GDEY042T81
|
#ifdef DISPLAY_GDEY042T81
|
||||||
getdisplay().hibernate(); // Set display in hybenate mode
|
getdisplay().hibernate(); // Set display in hibernate mode
|
||||||
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
|
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
|
||||||
#else
|
#else
|
||||||
getdisplay().init(115200); // Init for normal displays
|
getdisplay().init(115200); // Init for normal displays
|
||||||
|
|||||||
103
lib/obp60task/patches/01-nmea2000.patch
Normal file
103
lib/obp60task/patches/01-nmea2000.patch
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
diff --git a/lib/api/GwApi.h b/lib/api/GwApi.h
|
||||||
|
index 88f9690..9663a65 100644
|
||||||
|
--- a/lib/api/GwApi.h
|
||||||
|
+++ b/lib/api/GwApi.h
|
||||||
|
@@ -2,6 +2,8 @@
|
||||||
|
#define _GWAPI_H
|
||||||
|
#include "GwMessage.h"
|
||||||
|
#include "N2kMsg.h"
|
||||||
|
+#include "Nmea2kTwai.h"
|
||||||
|
+#include "N2kDeviceList.h"
|
||||||
|
#include "NMEA0183Msg.h"
|
||||||
|
#include "GWConfig.h"
|
||||||
|
#include "GwBoatData.h"
|
||||||
|
@@ -222,6 +224,8 @@ class GwApi{
|
||||||
|
* accessing boat data must only be executed from within the main thread
|
||||||
|
* you need to use the request pattern as shown in GwExampleTask.cpp
|
||||||
|
*/
|
||||||
|
+ virtual Nmea2kTwai *getNMEA2000()=0;
|
||||||
|
+ virtual tN2kDeviceList *getN2kDeviceList()=0;
|
||||||
|
virtual GwBoatData *getBoatData()=0;
|
||||||
|
virtual ~GwApi(){}
|
||||||
|
};
|
||||||
|
diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h
|
||||||
|
index 604c356..2fe4496 100644
|
||||||
|
--- a/lib/obp60task/OBP60Extensions.h
|
||||||
|
+++ b/lib/obp60task/OBP60Extensions.h
|
||||||
|
@@ -15,6 +15,9 @@
|
||||||
|
#define MOUNT_POINT "/sdcard"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
+// Patches to apply to gateway code
|
||||||
|
+#define PATCH_N2K
|
||||||
|
+
|
||||||
|
// FRAM address reservations 32kB: 0x0000 - 0x7FFF
|
||||||
|
// 0x0000 - 0x03ff: single variables
|
||||||
|
#define FRAM_PAGE_NO 0x0002
|
||||||
|
diff --git a/lib/usercode/GwUserCode.cpp b/lib/usercode/GwUserCode.cpp
|
||||||
|
index 1b007f8..90087d4 100644
|
||||||
|
--- a/lib/usercode/GwUserCode.cpp
|
||||||
|
+++ b/lib/usercode/GwUserCode.cpp
|
||||||
|
@@ -216,6 +216,14 @@ public:
|
||||||
|
{
|
||||||
|
return api->getLogger();
|
||||||
|
}
|
||||||
|
+ virtual Nmea2kTwai *getNMEA2000()
|
||||||
|
+ {
|
||||||
|
+ return api->getNMEA2000();
|
||||||
|
+ }
|
||||||
|
+ virtual tN2kDeviceList *getN2kDeviceList()
|
||||||
|
+ {
|
||||||
|
+ return api->getN2kDeviceList();
|
||||||
|
+ }
|
||||||
|
virtual GwBoatData *getBoatData()
|
||||||
|
{
|
||||||
|
return api->getBoatData();
|
||||||
|
@@ -428,4 +436,4 @@ void GwUserCode::handleWebRequest(const String &url,AsyncWebServerRequest *req){
|
||||||
|
}
|
||||||
|
LOG_DEBUG(GwLog::DEBUG,"no task found for web request %s[%s]",url.c_str(),tname.c_str());
|
||||||
|
req->send(404, "text/plain", "not found");
|
||||||
|
-}
|
||||||
|
\ No newline at end of file
|
||||||
|
+}
|
||||||
|
diff --git a/src/main.cpp b/src/main.cpp
|
||||||
|
index 44c715f..fdb0366 100644
|
||||||
|
--- a/src/main.cpp
|
||||||
|
+++ b/src/main.cpp
|
||||||
|
@@ -100,6 +100,7 @@ GwLog logger(LOGLEVEL,NULL);
|
||||||
|
GwConfigHandler config(&logger);
|
||||||
|
|
||||||
|
#include "Nmea2kTwai.h"
|
||||||
|
+#include <N2kDeviceList.h>
|
||||||
|
static const unsigned long CAN_RECOVERY_PERIOD=3000; //ms
|
||||||
|
static const unsigned long NMEA2000_HEARTBEAT_INTERVAL=5000;
|
||||||
|
class Nmea2kTwaiLog : public Nmea2kTwai{
|
||||||
|
@@ -126,6 +127,7 @@ class Nmea2kTwaiLog : public Nmea2kTwai{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Nmea2kTwai &NMEA2000=*(new Nmea2kTwaiLog((gpio_num_t)ESP32_CAN_TX_PIN,(gpio_num_t)ESP32_CAN_RX_PIN,CAN_RECOVERY_PERIOD,&logger));
|
||||||
|
+tN2kDeviceList *pN2kDeviceList;
|
||||||
|
|
||||||
|
#ifdef GWBUTTON_PIN
|
||||||
|
bool fixedApPass=false;
|
||||||
|
@@ -333,6 +335,12 @@ public:
|
||||||
|
status.n2kTx=countNMEA2KOut.getGlobal();
|
||||||
|
channels.fillStatus(status);
|
||||||
|
}
|
||||||
|
+ virtual Nmea2kTwai *getNMEA2000(){
|
||||||
|
+ return &NMEA2000;
|
||||||
|
+ }
|
||||||
|
+ virtual tN2kDeviceList *getN2kDeviceList(){
|
||||||
|
+ return pN2kDeviceList;
|
||||||
|
+ }
|
||||||
|
virtual GwBoatData *getBoatData(){
|
||||||
|
return &boatData;
|
||||||
|
}
|
||||||
|
@@ -935,6 +943,7 @@ void setup() {
|
||||||
|
NMEA2000.SetMsgHandler([](const tN2kMsg &n2kMsg){
|
||||||
|
handleN2kMessage(n2kMsg,N2K_CHANNEL_ID);
|
||||||
|
});
|
||||||
|
+ pN2kDeviceList = new tN2kDeviceList(&NMEA2000);
|
||||||
|
NMEA2000.Open();
|
||||||
|
logger.logDebug(GwLog::LOG,"starting addon tasks");
|
||||||
|
logger.flush();
|
||||||
@@ -58,6 +58,7 @@ build_flags=
|
|||||||
# -D DISPLAY_GYE042A87 #alternativ E-Ink display from Genyo Optical, R10 2.2 ohm - medium
|
# -D DISPLAY_GYE042A87 #alternativ E-Ink display from Genyo Optical, R10 2.2 ohm - medium
|
||||||
# -D DISPLAY_SE0420NQ04 #alternativ E-Ink display from SID Technology, R10 2.2 ohm - bad (burn in effects)
|
# -D DISPLAY_SE0420NQ04 #alternativ E-Ink display from SID Technology, R10 2.2 ohm - bad (burn in effects)
|
||||||
# -D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
|
# -D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
|
||||||
|
# -D ENABLE_PATCHES #enable patching of gateway code
|
||||||
${env.build_flags}
|
${env.build_flags}
|
||||||
#CONFIG_ESP_TASK_WDT_TIMEOUT_S = 10 #Task Watchdog timeout period (seconds) [1...60] 5 default
|
#CONFIG_ESP_TASK_WDT_TIMEOUT_S = 10 #Task Watchdog timeout period (seconds) [1...60] 5 default
|
||||||
upload_port = /dev/ttyACM0 #OBP60 download via USB-C direct
|
upload_port = /dev/ttyACM0 #OBP60 download via USB-C direct
|
||||||
@@ -108,6 +109,7 @@ build_flags=
|
|||||||
#-D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
|
#-D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
|
||||||
-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
|
-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
|
||||||
-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
|
-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
|
||||||
|
#-D ENABLE_PATCHES #enable patching of gateway code
|
||||||
${env.build_flags}
|
${env.build_flags}
|
||||||
upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter
|
upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter
|
||||||
upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27
|
upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27
|
||||||
|
|||||||
@@ -8,6 +8,6 @@
|
|||||||
|
|
||||||
# Install tools
|
# Install tools
|
||||||
echo "Installing tools"
|
echo "Installing tools"
|
||||||
cd /workspace/esp32-nmea2000
|
cd /workspaces/esp32-nmea2000
|
||||||
pip3 install -U esptool
|
pip3 install -U esptool
|
||||||
pip3 install platformio
|
pip3 install platformio
|
||||||
|
|||||||
Reference in New Issue
Block a user