mirror of
https://github.com/thooge/esp32-nmea2000-obp60.git
synced 2025-12-28 05:03:06 +01:00
Compare commits
15 Commits
cba21574cb
...
extended
| Author | SHA1 | Date | |
|---|---|---|---|
| a8cf34343f | |||
| c00a0ecbed | |||
| a16ee74b32 | |||
| 601d56ee49 | |||
| 9a04029cb4 | |||
| 22e3ca3875 | |||
| 8e72537286 | |||
| 494acbf0d0 | |||
| d7251eeb8a | |||
| c4406fd009 | |||
| 0097a1eb1e | |||
| c932724473 | |||
| 672a3843d1 | |||
| 1e0acf2a8f | |||
| 63ae42588f |
@@ -205,6 +205,11 @@ def generateCfg(inFile,outFile,impl):
|
||||
secret="false";
|
||||
if item.get('type') == 'password':
|
||||
secret="true"
|
||||
"""
|
||||
PSRAM Allocator TODO Tests
|
||||
new (heap_caps_malloc(sizeof(GwConfigInterface), MALLOC_CAP_SPIRAM))
|
||||
"""
|
||||
#data+=" new (heap_caps_malloc(sizeof(GwConfigInterface), MALLOC_CAP_SPIRAM)) GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret)
|
||||
data+=" new GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret)
|
||||
data+='}\n'
|
||||
writeFileIfChanged(outFile,data)
|
||||
@@ -505,6 +510,8 @@ def prebuild(env):
|
||||
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
|
||||
|
||||
def cleangenerated(source, target, env):
|
||||
# TODO source / target order?
|
||||
print("CLEAN: {} - {}".format(source, target))
|
||||
od=outPath()
|
||||
if os.path.isdir(od):
|
||||
print("#cleaning up %s"%od)
|
||||
@@ -514,7 +521,6 @@ def cleangenerated(source, target, env):
|
||||
fn=os.path.join(od,f)
|
||||
os.unlink(f)
|
||||
|
||||
|
||||
print("#prescript...")
|
||||
prebuild(env)
|
||||
board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper()
|
||||
|
||||
@@ -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)
|
||||
@@ -12,6 +12,7 @@ public:
|
||||
static int getType(const double &x) { return GWTYPE_DOUBLE; }
|
||||
static int getType(const String &x) { return GWTYPE_STRING; }
|
||||
static int getType(const GwSatInfoList &x) { return GWTYPE_USER + 1; }
|
||||
static int getType(const GwAisTargetList &x) { return GWTYPE_USER + 1; }
|
||||
};
|
||||
|
||||
bool GwBoatItemBase::isValid(unsigned long now) const
|
||||
@@ -289,6 +290,7 @@ template class GwBoatItem<uint32_t>;
|
||||
template class GwBoatItem<uint16_t>;
|
||||
template class GwBoatItem<int16_t>;
|
||||
template class GwBoatItem<String>;
|
||||
|
||||
void GwSatInfoList::houseKeeping(unsigned long ts)
|
||||
{
|
||||
if (ts == 0)
|
||||
@@ -302,6 +304,7 @@ void GwSatInfoList::houseKeeping(unsigned long ts)
|
||||
}),
|
||||
sats.end());
|
||||
}
|
||||
|
||||
void GwSatInfoList::update(GwSatInfo entry, unsigned long validTill)
|
||||
{
|
||||
entry.validTill = validTill;
|
||||
@@ -344,6 +347,63 @@ void GwBoatDataSatList::toJsonDoc(GwJsonDocument *doc, unsigned long minTime)
|
||||
GwBoatItem<GwSatInfoList>::toJsonDoc(doc, minTime);
|
||||
}
|
||||
|
||||
void GwAisTargetList::houseKeeping(unsigned long ts)
|
||||
{
|
||||
if (ts == 0) {
|
||||
ts = millis();
|
||||
}
|
||||
targets.erase(
|
||||
std::remove_if(
|
||||
targets.begin(),
|
||||
targets.end(),
|
||||
[ts, this](const GwAisTarget &target) {
|
||||
return target.validTill < ts;
|
||||
}
|
||||
),
|
||||
targets.end()
|
||||
);
|
||||
}
|
||||
|
||||
void GwAisTargetList::update(GwAisTarget target, unsigned long validTill)
|
||||
{
|
||||
target.validTill = validTill;
|
||||
for (auto it = targets.begin(); it != targets.end(); it++) {
|
||||
if (it->mmsi == target.mmsi) {
|
||||
*it = target;
|
||||
houseKeeping();
|
||||
return;
|
||||
}
|
||||
}
|
||||
houseKeeping();
|
||||
targets.push_back(target);
|
||||
}
|
||||
|
||||
GwBoatDataAisList::GwBoatDataAisList(String name, String formatInfo, GwBoatItemBase::TOType toType, GwBoatItemMap *map) : GwBoatItem<GwAisTargetList>(name, formatInfo, toType, map) {}
|
||||
|
||||
bool GwBoatDataAisList::update(GwAisTarget target, int source)
|
||||
{
|
||||
unsigned long now = millis();
|
||||
if (isValid(now))
|
||||
{
|
||||
//priority handling
|
||||
//sources with lower ids will win
|
||||
//and we will not overwrite their value
|
||||
if (lastUpdateSource < source)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
lastUpdateSource = source;
|
||||
uls(now);
|
||||
data.update(target, now+invalidTime);
|
||||
return true;
|
||||
}
|
||||
void GwBoatDataAisList::toJsonDoc(GwJsonDocument *doc, unsigned long minTime)
|
||||
{
|
||||
data.houseKeeping();
|
||||
GwBoatItem<GwAisTargetList>::toJsonDoc(doc, minTime);
|
||||
}
|
||||
|
||||
GwBoatData::GwBoatData(GwLog *logger, GwConfigHandler *cfg)
|
||||
{
|
||||
this->logger = logger;
|
||||
@@ -513,6 +573,11 @@ bool convertToJson(const GwSatInfoList &si, JsonVariant &variant)
|
||||
return variant.set(si.getNumSats());
|
||||
}
|
||||
|
||||
bool convertToJson(const GwAisTargetList &si, JsonVariant &variant)
|
||||
{
|
||||
return variant.set(si.getNumTargets());
|
||||
}
|
||||
|
||||
#ifdef _UNDEF
|
||||
#include <ArduinoJson/Json/TextFormatter.hpp>
|
||||
|
||||
|
||||
@@ -196,6 +196,55 @@ public:
|
||||
|
||||
};
|
||||
|
||||
class GwAisTarget {
|
||||
public:
|
||||
uint32_t mmsi;
|
||||
char callsign[8];
|
||||
char name[21];
|
||||
uint8_t vesseltype;
|
||||
double lat;
|
||||
double lon;
|
||||
float length;
|
||||
float beam;
|
||||
float sog;
|
||||
float cog;
|
||||
unsigned long validTill;
|
||||
};
|
||||
|
||||
class GwAisTargetList {
|
||||
public:
|
||||
static const GwBoatItemBase::TOType toType=GwBoatItemBase::TOType::ais;
|
||||
std::vector<GwAisTarget> targets;
|
||||
void houseKeeping(unsigned long ts=0);
|
||||
void update(GwAisTarget target, unsigned long validTill);
|
||||
int getNumTargets() const {
|
||||
return targets.size();
|
||||
}
|
||||
GwAisTarget *getAt(int idx){
|
||||
if (idx >= 0 && idx < targets.size()) return &targets.at(idx);
|
||||
return NULL;
|
||||
}
|
||||
operator double(){ return getNumTargets();}
|
||||
};
|
||||
|
||||
class GwBoatDataAisList : public GwBoatItem<GwAisTargetList> {
|
||||
public:
|
||||
GwBoatDataAisList(String name, String formatInfo, GwBoatItemBase::TOType toType, GwBoatItemMap *map = NULL);
|
||||
bool update(GwAisTarget target, int source);
|
||||
virtual void toJsonDoc(GwJsonDocument *doc, unsigned long minTime);
|
||||
GwAisTarget *getAt(int idx) {
|
||||
if (! isValid()) return NULL;
|
||||
return data.getAt(idx);
|
||||
}
|
||||
int getNumTargets(){
|
||||
if (! isValid()) return 0;
|
||||
return data.getNumTargets();
|
||||
}
|
||||
virtual double getDoubleValue(){
|
||||
return (double)(data.getNumTargets());
|
||||
}
|
||||
};
|
||||
|
||||
class GwBoatItemNameProvider
|
||||
{
|
||||
public:
|
||||
@@ -256,6 +305,7 @@ class GwBoatData{
|
||||
GWBOATDATA(double,WPLon,formatLongitude) // waypoint longitude
|
||||
GWBOATDATA(String,WPName,formatName) // waypoint name
|
||||
GWSPECBOATDATA(GwBoatDataSatList,SatInfo,GwSatInfoList::toType,formatFixed0);
|
||||
GWSPECBOATDATA(GwBoatDataAisList,AisTarget,GwAisTargetList::toType,formatFixed0);
|
||||
public:
|
||||
GwBoatData(GwLog *logger, GwConfigHandler *cfg);
|
||||
~GwBoatData();
|
||||
|
||||
@@ -79,7 +79,7 @@ GwUpdate::GwUpdate(GwLog *log, GwWebServer *webserver, PasswordChecker ckr)
|
||||
}
|
||||
if (!param->hasError())
|
||||
{
|
||||
AsyncWebParameter *hash=request->getParam("_hash");
|
||||
const AsyncWebParameter *hash=request->getParam("_hash");
|
||||
if (! hash){
|
||||
hash=request->getParam("_hash",true);
|
||||
}
|
||||
@@ -141,4 +141,4 @@ GwUpdate::GwUpdate(GwLog *log, GwWebServer *webserver, PasswordChecker ckr)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ void sendEmbeddedFile(String name,String contentType,AsyncWebServerRequest *requ
|
||||
std::map<String,EmbeddedFile*>::iterator it=embeddedFiles.find(name);
|
||||
if (it != embeddedFiles.end()){
|
||||
EmbeddedFile* found=it->second;
|
||||
AsyncWebServerResponse *response=request->beginResponse_P(200,contentType,found->start,found->len);
|
||||
AsyncWebServerResponse *response=request->beginResponse(200, contentType, found->start, found->len);
|
||||
response->addHeader(F("Content-Encoding"), F("gzip"));
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ void CalibrationDataList::readConfig(GwConfigHandler* config, GwLog* logger)
|
||||
calibMap[instance].slope = slope;
|
||||
calibMap[instance].smooth = smooth;
|
||||
calibMap[instance].isCalibrated = false;
|
||||
logger->logDebug(GwLog::LOG, "stored calibration data: %s, offset: %f, slope: %f, smoothing: %f", instance.c_str(),
|
||||
logger->logDebug(GwLog::LOG, "calibration data: %s, offset: %f, slope: %f, smoothing: %f", instance.c_str(),
|
||||
calibMap[instance].offset, calibMap[instance].slope, calibMap[instance].smooth);
|
||||
}
|
||||
logger->logDebug(GwLog::LOG, "all calibration data read");
|
||||
@@ -117,7 +117,7 @@ void CalibrationDataList::calibrateInstance(GwApi::BoatValue* boatDataValue, GwL
|
||||
std::string format = "";
|
||||
|
||||
if (calibMap.find(instance) == calibMap.end()) {
|
||||
logger->logDebug(GwLog::DEBUG, "BoatDataCalibration: %s not found in calibration data list", instance.c_str());
|
||||
logger->logDebug(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
||||
return;
|
||||
} else if (!boatDataValue->valid) { // no valid boat data value, so we don't want to apply calibration data
|
||||
calibMap[instance].isCalibrated = false;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#ifndef _BOATDATACALIBRATION_H
|
||||
#define _BOATDATACALIBRATION_H
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "GwApi.h"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -30,4 +30,4 @@ private:
|
||||
|
||||
extern CalibrationDataList calibrationData; // this list holds all calibration data
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include <FreeRTOS.h>
|
||||
#include "LedSpiTask.h"
|
||||
#include "GwHardware.h"
|
||||
@@ -251,6 +252,11 @@ void handleSpiLeds(void *param){
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void createSpiLedTask(LedTaskData *param){
|
||||
xTaskCreate(handleSpiLeds,"handleLeds",4000,param,3,NULL);
|
||||
void createSpiLedTask(LedTaskData *param) {
|
||||
TaskHandle_t xHandle = NULL;
|
||||
GwLog *logger = param->api->getLogger();
|
||||
esp_err_t err = xTaskCreate(handleSpiLeds, "handleLeds", configMINIMAL_STACK_SIZE + 2048, param, 3, &xHandle);
|
||||
if (err != pdPASS) {
|
||||
logger->logDebug(GwLog::ERROR, "Failed to create spiled task! (err=%d)", err);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "fonts/Ubuntu_Bold20pt8b.h"
|
||||
#include "fonts/Ubuntu_Bold32pt8b.h"
|
||||
#include "fonts/Atari16px8b.h" // Key label font
|
||||
#include "fonts/Atari6px8b.h" // Very small (6x6) font
|
||||
|
||||
// E-Ink Display
|
||||
#define GxEPD_WIDTH 400 // Display width
|
||||
@@ -81,7 +82,7 @@ void hardwareInit(GwApi *api)
|
||||
|
||||
Wire.begin();
|
||||
// Init PCF8574 digital outputs
|
||||
Wire.setClock(I2C_SPEED); // Set I2C clock on 10 kHz
|
||||
Wire.setClock(I2C_SPEED); // Set I2C clock as defined
|
||||
if(pcf8574_Out.begin()){ // Initialize PCF8574
|
||||
pcf8574_Out.write8(255); // Clear all outputs
|
||||
}
|
||||
@@ -212,8 +213,8 @@ void deepSleep(CommonData &common){
|
||||
setFlashLED(false); // Flash LED Off
|
||||
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
|
||||
// Shutdown EInk display
|
||||
epd->setFullWindow(); // Set full Refresh
|
||||
epd->fillScreen(common.bgcolor); // Clear screen
|
||||
epd->setFullWindow(); // Set full Refresh
|
||||
epd->fillScreen(common.bgcolor); // Clear screen
|
||||
epd->setTextColor(common.fgcolor);
|
||||
epd->setFont(&Ubuntu_Bold20pt8b);
|
||||
epd->setCursor(85, 150);
|
||||
@@ -221,8 +222,8 @@ void deepSleep(CommonData &common){
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setCursor(65, 175);
|
||||
epd->print("To wake up press key and wait 5s");
|
||||
epd->nextPage(); // Update display contents
|
||||
epd->powerOff(); // Display power off
|
||||
epd->nextPage(); // Update display contents
|
||||
epd->powerOff(); // Display power off
|
||||
setPortPin(OBP_POWER_50, false); // Power off ePaper display
|
||||
// Stop system
|
||||
esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin
|
||||
@@ -346,11 +347,14 @@ void setBuzzerPower(uint power){
|
||||
buzzerpower = power;
|
||||
}
|
||||
|
||||
// Delete xdr prefix from string
|
||||
String xdrDelete(String input){
|
||||
if(input.substring(0,3) == "xdr"){
|
||||
// Delete xdr prefix from string and optional limit length
|
||||
String xdrDelete(String input, uint8_t maxlen) {
|
||||
if (input.substring(0, 3) == "xdr") {
|
||||
input = input.substring(3, input.length());
|
||||
}
|
||||
if (maxlen > 0) {
|
||||
return input.substring(0, maxlen);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -474,6 +478,7 @@ void displayHeader(CommonData &commonData, bool symbolmode, GwApi::BoatValue *da
|
||||
uint16_t symbol_x = 2;
|
||||
static const uint16_t symbol_offset = 20;
|
||||
|
||||
// TODO invert and get rid of the if
|
||||
if(commonData.config->getBool(commonData.config->statusLine) == true){
|
||||
|
||||
// Show status info
|
||||
@@ -583,7 +588,7 @@ void displayHeader(CommonData &commonData, bool symbolmode, GwApi::BoatValue *da
|
||||
heartbeat = !heartbeat;
|
||||
|
||||
// Date and time
|
||||
String fmttype = commonData.config->getString(commonData.config->dateFormat);
|
||||
fmtDate fmttype = commonData.fmt->getDateFormat(commonData.config->getString(commonData.config->dateFormat));
|
||||
String timesource = commonData.config->getString(commonData.config->timeSource);
|
||||
double tz = commonData.config->getString(commonData.config->timeZone).toDouble();
|
||||
epd->setTextColor(commonData.fgcolor);
|
||||
@@ -594,7 +599,7 @@ void displayHeader(CommonData &commonData, bool symbolmode, GwApi::BoatValue *da
|
||||
if (commonData.data.rtcValid) {
|
||||
time_t tv = mktime(&commonData.data.rtcTime) + (int)(tz * 3600);
|
||||
struct tm *local_tm = localtime(&tv);
|
||||
epd->print(formatTime('m', local_tm->tm_hour, local_tm->tm_min, 0));
|
||||
epd->print(formatTime(fmtTime::MMHH, local_tm->tm_hour, local_tm->tm_min, 0));
|
||||
epd->print(" ");
|
||||
epd->print(formatDate(fmttype, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday));
|
||||
epd->print(" ");
|
||||
@@ -921,7 +926,7 @@ void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUM
|
||||
createPBM(fb, &imageBuffer, GxEPD_WIDTH, GxEPD_HEIGHT);
|
||||
}
|
||||
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, mimetype, (const uint8_t*)imageBuffer.data(), imageBuffer.size());
|
||||
AsyncWebServerResponse *response = request->beginResponse(200, mimetype, (const uint8_t*)imageBuffer.data(), imageBuffer.size());
|
||||
response->addHeader("Content-Disposition", "inline; filename=" + filename);
|
||||
request->send(response);
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ extern const GFXfont Ubuntu_Bold16pt8b;
|
||||
extern const GFXfont Ubuntu_Bold20pt8b;
|
||||
extern const GFXfont Ubuntu_Bold32pt8b;
|
||||
extern const GFXfont Atari16px;
|
||||
extern const GFXfont Atari6px;
|
||||
|
||||
// Global functions
|
||||
#ifdef DISPLAY_GDEW042T2
|
||||
@@ -102,7 +103,7 @@ void setBlinkingLED(bool on); // Set blinking flash LED active
|
||||
void buzzer(uint frequency, uint duration); // Buzzer function
|
||||
void setBuzzerPower(uint power); // Set buzzer power
|
||||
|
||||
String xdrDelete(String input); // Delete xdr prefix from string
|
||||
String xdrDelete(String input, uint8_t maxlen = 0); // Delete xdr prefix from string and optional limit length
|
||||
|
||||
void drawTextCenter(int16_t cx, int16_t cy, String text);
|
||||
void drawTextRalign(int16_t x, int16_t y, String text);
|
||||
|
||||
@@ -20,6 +20,7 @@ Formatter::Formatter(GwConfigHandler *config) {
|
||||
windspeedFormat = config->getString(config->windspeedFormat);
|
||||
tempFormat = config->getString(config->tempFormat);
|
||||
dateFormat = config->getString(config->dateFormat);
|
||||
dateFmt = getDateFormat(dateFormat);
|
||||
usesimudata = config->getBool(config->useSimuData);
|
||||
precision = config->getString(config->valueprecision);
|
||||
|
||||
@@ -35,6 +36,37 @@ Formatter::Formatter(GwConfigHandler *config) {
|
||||
|
||||
}
|
||||
|
||||
fmtType Formatter::stringToFormat(const char* formatStr) {
|
||||
auto it = formatMap.find(formatStr);
|
||||
if (it != formatMap.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return fmtType::XDR_G; // generic as default
|
||||
}
|
||||
|
||||
fmtDate Formatter::getDateFormat(String sformat) {
|
||||
if (sformat == "DE") {
|
||||
return fmtDate::DE;
|
||||
}
|
||||
if (sformat == "GB") {
|
||||
return fmtDate::GB;
|
||||
}
|
||||
if (sformat == "US") {
|
||||
return fmtDate::US;
|
||||
}
|
||||
return fmtDate::ISO; // default
|
||||
}
|
||||
|
||||
fmtTime Formatter::getTimeFormat(String sformat) {
|
||||
if (sformat == "MMHH") {
|
||||
return fmtTime::MMHH;
|
||||
}
|
||||
if (sformat == "MMHHSS") {
|
||||
return fmtTime::MMHHSS;
|
||||
}
|
||||
return fmtTime::MMHH; // default
|
||||
}
|
||||
|
||||
FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &commondata){
|
||||
GwLog *logger = commondata.logger;
|
||||
FormattedData result;
|
||||
@@ -43,7 +75,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
|
||||
|
||||
// If boat value not valid
|
||||
if (! value->valid && !usesimudata){
|
||||
result.svalue = "---";
|
||||
result.svalue = placeholder;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -86,12 +118,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
|
||||
else{
|
||||
snprintf(buffer, bsize, "01.01.2022");
|
||||
}
|
||||
if(timeZone == 0){
|
||||
result.unit = "UTC";
|
||||
}
|
||||
else{
|
||||
result.unit = "LOT";
|
||||
}
|
||||
result.unit = ((timeZone == 0) ? "UTC" : "LOT");
|
||||
}
|
||||
//########################################################
|
||||
else if(value->getFormat() == "formatTime"){
|
||||
@@ -121,12 +148,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
|
||||
snprintf(buffer, bsize, "11:36:%02i", int(sec));
|
||||
lasttime = millis();
|
||||
}
|
||||
if(timeZone == 0){
|
||||
result.unit = "UTC";
|
||||
}
|
||||
else{
|
||||
result.unit = "LOT";
|
||||
}
|
||||
result.unit = ((timeZone == 0) ? "UTC" : "LOT");
|
||||
}
|
||||
//########################################################
|
||||
else if (value->getFormat() == "formatFixed0"){
|
||||
@@ -286,13 +308,13 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
|
||||
if (rotation < -100){
|
||||
rotation = -99;
|
||||
}
|
||||
if (rotation > 100){
|
||||
else if (rotation > 100){
|
||||
rotation = 99;
|
||||
}
|
||||
if (rotation > -10 && rotation < 10){
|
||||
snprintf(buffer, bsize, "%3.2f", rotation);
|
||||
}
|
||||
if (rotation <= -10 || rotation >= 10){
|
||||
else {
|
||||
snprintf(buffer, bsize, "%3.0f", rotation);
|
||||
}
|
||||
}
|
||||
@@ -330,12 +352,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
|
||||
String latdir = "";
|
||||
float degree = abs(int(lat));
|
||||
float minute = abs((lat - int(lat)) * 60);
|
||||
if (lat > 0){
|
||||
latdir = "N";
|
||||
}
|
||||
else {
|
||||
latdir = "S";
|
||||
}
|
||||
latdir = (lat > 0) ? "N" : "S";
|
||||
latitude = String(degree,0) + "\x90 " + String(minute,4) + "' " + latdir;
|
||||
result.unit = "";
|
||||
strcpy(buffer, latitude.c_str());
|
||||
@@ -354,12 +371,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
|
||||
String londir = "";
|
||||
float degree = abs(int(lon));
|
||||
float minute = abs((lon - int(lon)) * 60);
|
||||
if (lon > 0){
|
||||
londir = "E";
|
||||
}
|
||||
else {
|
||||
londir = "W";
|
||||
}
|
||||
londir = (lon > 0) ? "E" : "W";
|
||||
longitude = String(degree,0) + "\x90 " + String(minute,4) + "' " + londir;
|
||||
result.unit = "";
|
||||
strcpy(buffer, longitude.c_str());
|
||||
@@ -381,7 +393,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
|
||||
depth = rawvalue;
|
||||
}
|
||||
if(String(lengthFormat) == "ft"){
|
||||
depth = depth * 3.28084;
|
||||
depth = depth * 3.28084; // TODO use global defined factor
|
||||
result.unit = "ft";
|
||||
}
|
||||
else{
|
||||
@@ -411,7 +423,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
|
||||
xte = xte * 0.001;
|
||||
result.unit = "km";
|
||||
} else if (distanceFormat == "nm") {
|
||||
xte = xte * 0.000539957;
|
||||
xte = xte * 0.000539957; // TODO use global defined factor
|
||||
result.unit = "nm";
|
||||
} else {
|
||||
result.unit = "m";
|
||||
@@ -447,7 +459,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
|
||||
else{
|
||||
result.unit = "K";
|
||||
}
|
||||
if(temp < 10) {
|
||||
if (temp < 10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, temp);
|
||||
}
|
||||
else if (temp < 100) {
|
||||
@@ -473,7 +485,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
|
||||
result.unit = "km";
|
||||
}
|
||||
else if (String(distanceFormat) == "nm") {
|
||||
distance = distance * 0.000539957;
|
||||
distance = distance * 0.000539957; // TODO use global defined factor
|
||||
result.unit = "nm";
|
||||
}
|
||||
else {
|
||||
@@ -802,32 +814,36 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
|
||||
return result;
|
||||
}
|
||||
|
||||
String formatDate(String fmttype, uint16_t year, uint8_t month, uint8_t day) {
|
||||
String formatDate(fmtDate fmttype, uint16_t year, uint8_t month, uint8_t day) {
|
||||
char buffer[12];
|
||||
if (fmttype == "GB") {
|
||||
if (fmttype == fmtDate::GB) {
|
||||
snprintf(buffer, 12, "%02d/%02d/%04d", day , month, year);
|
||||
}
|
||||
else if (fmttype == "US") {
|
||||
else if (fmttype == fmtDate::US) {
|
||||
snprintf(buffer, 12, "%02d/%02d/%04d", month, day, year);
|
||||
}
|
||||
else if (fmttype == "ISO") {
|
||||
else if (fmttype == fmtDate::ISO) {
|
||||
snprintf(buffer, 12, "%04d-%02d-%02d", year, month, day);
|
||||
}
|
||||
else {
|
||||
else if (fmttype == fmtDate::DE) {
|
||||
snprintf(buffer, 12, "%02d.%02d.%04d", day, month, year);
|
||||
} else {
|
||||
snprintf(buffer, 12, "%04d-%02d-%02d", year, month, day);
|
||||
}
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
String formatTime(char fmttype, uint8_t hour, uint8_t minute, uint8_t second) {
|
||||
// fmttype: s: with seconds, m: only minutes
|
||||
String formatTime(fmtTime fmttype, uint8_t hour, uint8_t minute, uint8_t second) {
|
||||
char buffer[10];
|
||||
if (fmttype == 'm') {
|
||||
if (fmttype == fmtTime::MMHH) {
|
||||
snprintf(buffer, 10, "%02d:%02d", hour , minute);
|
||||
}
|
||||
else {
|
||||
else if (fmttype == fmtTime::MMHHSS) {
|
||||
snprintf(buffer, 10, "%02d:%02d:%02d", hour, minute, second);
|
||||
}
|
||||
else {
|
||||
snprintf(buffer, 10, "%02d%02d%02d", hour, minute, second);
|
||||
}
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,41 +2,12 @@
|
||||
#ifndef _OBP60FORMATTER_H
|
||||
#define _OBP60FORMATTER_H
|
||||
|
||||
#include "GwApi.h"
|
||||
#include "Pagedata.h"
|
||||
#include <unordered_map>
|
||||
|
||||
/*
|
||||
|
||||
Formatter names as defined in BoatItemBase
|
||||
formatCourse
|
||||
formatKnots
|
||||
formatWind
|
||||
formatLatitude
|
||||
formatLongitude
|
||||
formatXte
|
||||
formatFixed0
|
||||
formatDepth
|
||||
kelvinToC TODO not a format but conversion
|
||||
mtr2nm TODO not a format but conversion
|
||||
formatDop dilution of precision
|
||||
formatRot
|
||||
formatDate
|
||||
formatTime
|
||||
formatName
|
||||
|
||||
XDR Formatter names
|
||||
formatXdr:P:P // pressure percent
|
||||
formatXdr:P:B // pressure bar
|
||||
formatXdr:U:V // voltage volt
|
||||
formatXdr:I:A // current ampere
|
||||
formatXdr:C:K // temperature kelvin
|
||||
formatXdr:C:C // temperature celsius
|
||||
formatXdr:H:P // humidity percent
|
||||
formatXdr:V:P // volume percent
|
||||
formatXdr:V:M // volume cubic meters
|
||||
formatXdr:R:I // flow liter per second?
|
||||
formatXdr:G: // generic
|
||||
formatXdr:A:P // angle percent
|
||||
formatXdr:A:D // angle degrees
|
||||
formatXdr:T:R // tachometer rpm
|
||||
|
||||
XDR types
|
||||
A Angular displacement
|
||||
C Temperature
|
||||
@@ -69,8 +40,77 @@ XDR units
|
||||
|
||||
*/
|
||||
|
||||
enum class fmtType {
|
||||
// Formatter names as defined in BoatItemBase
|
||||
COURSE,
|
||||
KNOTS,
|
||||
WIND,
|
||||
LATITUDE,
|
||||
LONGITUDE,
|
||||
XTE,
|
||||
FIXED0,
|
||||
DEPTH,
|
||||
DOP, // dilution of precision
|
||||
ROT,
|
||||
DATE,
|
||||
TIME,
|
||||
NAME,
|
||||
|
||||
kelvinToC, // TODO not a format but conversion
|
||||
mtr2nm, // TODO not a format but conversion
|
||||
|
||||
// XDR Formatter names
|
||||
XDR_PP, // pressure percent
|
||||
XDR_PB, // pressure bar
|
||||
XDR_UV, // voltage volt
|
||||
XDR_IA, // current ampere
|
||||
XDR_CK, // temperature kelvin
|
||||
XDR_CC, // temperature celsius
|
||||
XDR_HP, // humidity percent
|
||||
XDR_VP, // volume percent
|
||||
XDR_VM, // volume cubic meters
|
||||
XDR_RI, // flow liter per second?
|
||||
XDR_G, // generic
|
||||
XDR_AP, // angle percent
|
||||
XDR_AD, // angle degrees
|
||||
XDR_TR // tachometer rpm
|
||||
};
|
||||
|
||||
// Hint: String is not supported
|
||||
static std::unordered_map<const char*, fmtType> formatMap PROGMEM = {
|
||||
{"formatCourse", fmtType::COURSE},
|
||||
{"formatKnots", fmtType::KNOTS},
|
||||
{"formatWind", fmtType::WIND},
|
||||
{"formatLatitude", fmtType::LATITUDE},
|
||||
{"formatLongitude", fmtType::LONGITUDE},
|
||||
{"formatXte", fmtType::XTE},
|
||||
{"formatFixed0", fmtType::FIXED0},
|
||||
{"formatDepth", fmtType::DEPTH},
|
||||
{"formatDop", fmtType::DOP},
|
||||
{"formatRot", fmtType::ROT},
|
||||
{"formatDate", fmtType::DATE},
|
||||
{"formatTime", fmtType::TIME},
|
||||
{"formatName", fmtType::NAME},
|
||||
{"kelvinToC", fmtType::kelvinToC},
|
||||
{"mtr2nm", fmtType::mtr2nm},
|
||||
{"formatXdr:P:P", fmtType::XDR_PP},
|
||||
{"formatXdr:P:B", fmtType::XDR_PB},
|
||||
{"formatXdr:U:V", fmtType::XDR_UV},
|
||||
{"formatXdr:I:A", fmtType::XDR_IA},
|
||||
{"formatXdr:C:K", fmtType::XDR_CK},
|
||||
{"formatXdr:C:C", fmtType::XDR_CC},
|
||||
{"formatXdr:H:P", fmtType::XDR_HP},
|
||||
{"formatXdr:V:P", fmtType::XDR_VP},
|
||||
{"formatXdr:V:M", fmtType::XDR_VM},
|
||||
{"formatXdr:R:I", fmtType::XDR_RI},
|
||||
{"formatXdr:G:", fmtType::XDR_G},
|
||||
{"formatXdr:A:P", fmtType::XDR_AP},
|
||||
{"formatXdr:A:D", fmtType::XDR_AD},
|
||||
{"formatXdr:T:R", fmtType::XDR_TR}
|
||||
};
|
||||
|
||||
// Possible formats as scoped enums
|
||||
enum class fmtDate {DE, EN, GB, ISO};
|
||||
enum class fmtDate {DE, GB, US, ISO};
|
||||
enum class fmtTime {MMHH, MMHHSS};
|
||||
enum class fmtLength {METER, FEET, FATHOM, CABLE};
|
||||
enum class fmtDepth {METER, FEET, FATHOM};
|
||||
@@ -106,6 +146,7 @@ private:
|
||||
String windspeedFormat = "kn"; // [m/s|km/h|kn|bft]
|
||||
String tempFormat = "C"; // [K|°C|°F]
|
||||
String dateFormat = "ISO"; // [DE|GB|US|ISO]
|
||||
fmtDate dateFmt;
|
||||
bool usesimudata = false; // [on|off]
|
||||
|
||||
String precision = "2"; // [1|2]
|
||||
@@ -115,12 +156,16 @@ private:
|
||||
|
||||
public:
|
||||
Formatter(GwConfigHandler *config);
|
||||
fmtType stringToFormat(const char* formatStr);
|
||||
fmtDate getDateFormat(String sformat);
|
||||
fmtTime getTimeFormat(String sformat);
|
||||
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata);
|
||||
String placeholder = "---";
|
||||
};
|
||||
|
||||
// Standard format functions without overhead
|
||||
String formatDate(String fmttype, uint16_t year, uint8_t month, uint8_t day);
|
||||
String formatTime(char fmttype, uint8_t hour, uint8_t minute, uint8_t second);
|
||||
// Standard format functions without class and overhead
|
||||
String formatDate(fmtDate fmttype, uint16_t year, uint8_t month, uint8_t day);
|
||||
String formatTime(fmtTime fmttype, uint8_t hour, uint8_t minute, uint8_t second);
|
||||
String formatLatitude(double lat);
|
||||
String formatLongitude(double lon);
|
||||
|
||||
|
||||
@@ -1,5 +1,147 @@
|
||||
#include "OBPDataOperations.h"
|
||||
|
||||
// --- Class HstryBuf ---------------
|
||||
// Init history buffers for selected boat data
|
||||
void HstryBuf::init(BoatValueList* boatValues, GwLog *log) {
|
||||
|
||||
logger = log;
|
||||
|
||||
int hstryUpdFreq = 1000; // Update frequency for history buffers in ms
|
||||
int hstryMinVal = 0; // Minimum value for these history buffers
|
||||
twdHstryMax = 6283; // Max value for wind direction (TWD, AWD) in rad [0...2*PI], shifted by 1000 for 3 decimals
|
||||
twsHstryMax = 65000; // Max value for wind speed (TWS, AWS) in m/s [0..65], shifted by 1000 for 3 decimals
|
||||
awdHstryMax = twdHstryMax;
|
||||
awsHstryMax = twsHstryMax;
|
||||
twdHstryMin = hstryMinVal;
|
||||
twsHstryMin = hstryMinVal;
|
||||
awdHstryMin = hstryMinVal;
|
||||
awsHstryMin = hstryMinVal;
|
||||
const double DBL_MAX = std::numeric_limits<double>::max();
|
||||
|
||||
// Initialize history buffers with meta data
|
||||
hstryBufList.twdHstry->setMetaData("TWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax);
|
||||
hstryBufList.twsHstry->setMetaData("TWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax);
|
||||
hstryBufList.awdHstry->setMetaData("AWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax);
|
||||
hstryBufList.awsHstry->setMetaData("AWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax);
|
||||
|
||||
// create boat values for history data types, if they don't exist yet
|
||||
twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName());
|
||||
twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName());
|
||||
twaBVal = boatValues->findValueOrCreate("TWA");
|
||||
awdBVal = boatValues->findValueOrCreate(hstryBufList.awdHstry->getName());
|
||||
awsBVal = boatValues->findValueOrCreate(hstryBufList.awsHstry->getName());
|
||||
|
||||
if (!awdBVal->valid) { // AWD usually does not exist
|
||||
awdBVal->setFormat(hstryBufList.awdHstry->getFormat());
|
||||
awdBVal->value = DBL_MAX;
|
||||
}
|
||||
|
||||
// collect boat values for true wind calculation
|
||||
awaBVal = boatValues->findValueOrCreate("AWA");
|
||||
hdtBVal = boatValues->findValueOrCreate("HDT");
|
||||
hdmBVal = boatValues->findValueOrCreate("HDM");
|
||||
varBVal = boatValues->findValueOrCreate("VAR");
|
||||
cogBVal = boatValues->findValueOrCreate("COG");
|
||||
sogBVal = boatValues->findValueOrCreate("SOG");
|
||||
}
|
||||
|
||||
// Handle history buffers for TWD, TWS, AWD, AWS
|
||||
//void HstryBuf::handleHstryBuf(GwApi* api, BoatValueList* boatValues, bool useSimuData) {
|
||||
void HstryBuf::handleHstryBuf(bool useSimuData) {
|
||||
|
||||
static int16_t twd = 20; //initial value only relevant if we use simulation data
|
||||
static uint16_t tws = 20; //initial value only relevant if we use simulation data
|
||||
static double awd, aws, hdt = 20; //initial value only relevant if we use simulation data
|
||||
GwApi::BoatValue *calBVal; // temp variable just for data calibration -> we don't want to calibrate the original data here
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf: TWD_isValid? %d, twdBVal: %.1f, twaBVal: %.1f, twsBVal: %.1f", twdBVal->valid, twdBVal->value * RAD_TO_DEG,
|
||||
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852);
|
||||
|
||||
if (twdBVal->valid) {
|
||||
calBVal = new GwApi::BoatValue("TWD"); // temporary solution for calibration of history buffer values
|
||||
calBVal->setFormat(twdBVal->getFormat());
|
||||
calBVal->value = twdBVal->value;
|
||||
calBVal->valid = twdBVal->valid;
|
||||
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
|
||||
twd = static_cast<int16_t>(std::round(calBVal->value * 1000.0));
|
||||
if (twd >= twdHstryMin && twd <= twdHstryMax) {
|
||||
hstryBufList.twdHstry->add(twd);
|
||||
}
|
||||
delete calBVal;
|
||||
calBVal = nullptr;
|
||||
} else if (useSimuData) {
|
||||
twd += random(-20, 20);
|
||||
twd = WindUtils::to360(twd);
|
||||
hstryBufList.twdHstry->add(static_cast<int16_t>(DegToRad(twd) * 1000.0));
|
||||
}
|
||||
|
||||
if (twsBVal->valid) {
|
||||
calBVal = new GwApi::BoatValue("TWS"); // temporary solution for calibration of history buffer values
|
||||
calBVal->setFormat(twsBVal->getFormat());
|
||||
calBVal->value = twsBVal->value;
|
||||
calBVal->valid = twsBVal->valid;
|
||||
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
|
||||
tws = static_cast<uint16_t>(std::round(calBVal->value * 1000));
|
||||
if (tws >= twsHstryMin && tws <= twsHstryMax) {
|
||||
hstryBufList.twsHstry->add(tws);
|
||||
}
|
||||
delete calBVal;
|
||||
calBVal = nullptr;
|
||||
} else if (useSimuData) {
|
||||
tws += random(-5000, 5000); // TWS value in m/s; expands to 3 decimals
|
||||
tws = constrain(tws, 0, 25000); // Limit TWS to [0..25] m/s
|
||||
hstryBufList.twsHstry->add(tws);
|
||||
}
|
||||
|
||||
if (awaBVal->valid) {
|
||||
if (hdtBVal->valid) {
|
||||
hdt = hdtBVal->value; // Use HDT if available
|
||||
} else {
|
||||
hdt = WindUtils::calcHDT(&hdmBVal->value, &varBVal->value, &cogBVal->value, &sogBVal->value);
|
||||
}
|
||||
|
||||
awd = awaBVal->value + hdt;
|
||||
awd = WindUtils::to2PI(awd);
|
||||
calBVal = new GwApi::BoatValue("AWD"); // temporary solution for calibration of history buffer values
|
||||
calBVal->value = awd;
|
||||
calBVal->setFormat(awdBVal->getFormat());
|
||||
calBVal->valid = true;
|
||||
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
|
||||
awdBVal->value = calBVal->value;
|
||||
awdBVal->valid = true;
|
||||
awd = std::round(calBVal->value * 1000.0);
|
||||
if (awd >= awdHstryMin && awd <= awdHstryMax) {
|
||||
hstryBufList.awdHstry->add(static_cast<int16_t>(awd));
|
||||
}
|
||||
delete calBVal;
|
||||
calBVal = nullptr;
|
||||
} else if (useSimuData) {
|
||||
awd += random(-20, 20);
|
||||
awd = WindUtils::to360(awd);
|
||||
hstryBufList.awdHstry->add(static_cast<int16_t>(DegToRad(awd) * 1000.0));
|
||||
}
|
||||
|
||||
if (awsBVal->valid) {
|
||||
calBVal = new GwApi::BoatValue("AWS"); // temporary solution for calibration of history buffer values
|
||||
calBVal->setFormat(awsBVal->getFormat());
|
||||
calBVal->value = awsBVal->value;
|
||||
calBVal->valid = awsBVal->valid;
|
||||
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
|
||||
aws = std::round(calBVal->value * 1000);
|
||||
if (aws >= awsHstryMin && aws <= awsHstryMax) {
|
||||
hstryBufList.awsHstry->add(static_cast<uint16_t>(aws));
|
||||
}
|
||||
delete calBVal;
|
||||
calBVal = nullptr;
|
||||
} else if (useSimuData) {
|
||||
aws += random(-5000, 5000); // TWS value in m/s; expands to 1 decimal
|
||||
aws = constrain(aws, 0, 25000); // Limit TWS to [0..25] m/s
|
||||
hstryBufList.awsHstry->add(aws);
|
||||
}
|
||||
}
|
||||
// --- Class HstryBuf ---------------
|
||||
|
||||
// --- Class WindUtils --------------
|
||||
double WindUtils::to2PI(double a)
|
||||
{
|
||||
a = fmod(a, 2 * M_PI);
|
||||
@@ -68,29 +210,25 @@ void WindUtils::calcTwdSA(const double* AWA, const double* AWS,
|
||||
double awd = *AWA + *HDT;
|
||||
awd = to2PI(awd);
|
||||
double stw = -*STW;
|
||||
// Serial.println("\ncalcTwdSA: AWA: " + String(*AWA) + ", AWS: " + String(*AWS) + ", CTW: " + String(*CTW) + ", STW: " + String(*STW) + ", HDT: " + String(*HDT));
|
||||
addPolar(&awd, AWS, CTW, &stw, TWD, TWS);
|
||||
|
||||
// Normalize TWD and TWA to 0-360°
|
||||
*TWD = to2PI(*TWD);
|
||||
*TWA = toPI(*TWD - *HDT);
|
||||
// Serial.println("calcTwdSA: TWD: " + String(*TWD) + ", TWS: " + String(*TWS));
|
||||
}
|
||||
|
||||
double WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal)
|
||||
{
|
||||
double hdt;
|
||||
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
|
||||
static const double DBL_MIN = std::numeric_limits<double>::lowest();
|
||||
|
||||
// Serial.println("\ncalcTrueWind: HDT: " + String(*hdtVal) + ", HDM: " + String(*hdmVal) + ", VAR: " + String(*varVal) + ", SOG: " + String(*sogVal) + ", COG: " + String(*cogVal));
|
||||
if (*hdmVal != DBL_MIN) {
|
||||
hdt = *hdmVal + (*varVal != DBL_MIN ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available)
|
||||
if (*hdmVal != DBL_MAX) {
|
||||
hdt = *hdmVal + (*varVal != DBL_MAX ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available)
|
||||
hdt = to2PI(hdt);
|
||||
} else if (*cogVal != DBL_MIN && *sogVal >= minSogVal) {
|
||||
} else if (*cogVal != DBL_MAX && *sogVal >= minSogVal) {
|
||||
hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise
|
||||
} else {
|
||||
hdt = DBL_MIN; // Cannot calculate HDT without valid HDM or HDM+VAR or COG
|
||||
hdt = DBL_MAX; // Cannot calculate HDT without valid HDM or HDM+VAR or COG
|
||||
}
|
||||
|
||||
return hdt;
|
||||
@@ -103,37 +241,23 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal,
|
||||
double stw, hdt, ctw;
|
||||
double twd, tws, twa;
|
||||
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
|
||||
static const double DBL_MIN = std::numeric_limits<double>::lowest();
|
||||
|
||||
// Serial.println("\ncalcTrueWind: HDT: " + String(*hdtVal) + ", HDM: " + String(*hdmVal) + ", VAR: " + String(*varVal) + ", SOG: " + String(*sogVal) + ", COG: " + String(*cogVal));
|
||||
/* if (*hdtVal != DBL_MIN) {
|
||||
hdt = *hdtVal; // Use HDT if available
|
||||
} else {
|
||||
if (*hdmVal != DBL_MIN) {
|
||||
hdt = *hdmVal + (*varVal != DBL_MIN ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available)
|
||||
hdt = to2PI(hdt);
|
||||
} else if (*cogVal != DBL_MIN && *sogVal >= minSogVal) {
|
||||
hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise
|
||||
} else {
|
||||
return false; // Cannot calculate without valid HDT or HDM+VAR or COG
|
||||
}
|
||||
} */
|
||||
if (*hdtVal != DBL_MIN) {
|
||||
if (*hdtVal != DBL_MAX) {
|
||||
hdt = *hdtVal; // Use HDT if available
|
||||
} else {
|
||||
hdt = calcHDT(hdmVal, varVal, cogVal, sogVal);
|
||||
}
|
||||
|
||||
if (*cogVal != DBL_MIN && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG
|
||||
if (*cogVal != DBL_MAX && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG
|
||||
|
||||
ctw = *cogVal; // Use COG for CTW if available
|
||||
} else {
|
||||
ctw = hdt; // 2nd approximation for CTW; hdt must exist if we reach this part of the code
|
||||
}
|
||||
|
||||
if (*stwVal != DBL_MIN) {
|
||||
if (*stwVal != DBL_MAX) {
|
||||
stw = *stwVal; // Use STW if available
|
||||
} else if (*sogVal != DBL_MIN) {
|
||||
} else if (*sogVal != DBL_MAX) {
|
||||
stw = *sogVal;
|
||||
} else {
|
||||
// If STW and SOG are not available, we cannot calculate true wind
|
||||
@@ -141,7 +265,7 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal,
|
||||
}
|
||||
// Serial.println("\ncalcTrueWind: HDT: " + String(hdt) + ", CTW: " + String(ctw) + ", STW: " + String(stw));
|
||||
|
||||
if ((*awaVal == DBL_MIN) || (*awsVal == DBL_MIN)) {
|
||||
if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) {
|
||||
// Cannot calculate true wind without valid AWA, AWS; other checks are done earlier
|
||||
return false;
|
||||
} else {
|
||||
@@ -153,3 +277,46 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal,
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate true wind data and add to obp60task boat data list
|
||||
bool WindUtils::addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog* log) {
|
||||
|
||||
GwLog* logger = log;
|
||||
|
||||
double awaVal, awsVal, cogVal, stwVal, sogVal, hdtVal, hdmVal, varVal;
|
||||
double twd, tws, twa;
|
||||
bool isCalculated = false;
|
||||
|
||||
awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX;
|
||||
awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX;
|
||||
cogVal = cogBVal->valid ? cogBVal->value : DBL_MAX;
|
||||
stwVal = stwBVal->valid ? stwBVal->value : DBL_MAX;
|
||||
sogVal = sogBVal->valid ? sogBVal->value : DBL_MAX;
|
||||
hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
|
||||
hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX;
|
||||
varVal = varBVal->valid ? varBVal->value : DBL_MAX;
|
||||
LOG_DEBUG(GwLog::DEBUG,"obp60task addTrueWind: 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);
|
||||
|
||||
isCalculated = calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa);
|
||||
|
||||
if (isCalculated) { // Replace values only, if successfully calculated and not already available
|
||||
if (!twdBVal->valid) {
|
||||
twdBVal->value = twd;
|
||||
twdBVal->valid = true;
|
||||
}
|
||||
if (!twsBVal->valid) {
|
||||
twsBVal->value = tws;
|
||||
twsBVal->valid = true;
|
||||
}
|
||||
if (!twaBVal->valid) {
|
||||
twaBVal->value = twa;
|
||||
twaBVal->valid = true;
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(GwLog::DEBUG,"obp60task addTrueWind: isCalculated %d, TWD %.1f, TWA %.1f, TWS %.1f", isCalculated, twdBVal->value * RAD_TO_DEG,
|
||||
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852);
|
||||
|
||||
return isCalculated;
|
||||
}
|
||||
// --- Class WindUtils --------------
|
||||
|
||||
@@ -1,39 +1,90 @@
|
||||
#pragma once
|
||||
#include "GwApi.h"
|
||||
#include <N2kMessages.h>
|
||||
#include "OBPRingBuffer.h"
|
||||
// #include <Arduino.h>
|
||||
#include "BoatDataCalibration.h" // Functions lib for data instance calibration
|
||||
#include "obp60task.h"
|
||||
#include <math.h>
|
||||
|
||||
typedef struct {
|
||||
RingBuffer<int16_t>* twdHstry;
|
||||
RingBuffer<int16_t>* twsHstry;
|
||||
RingBuffer<uint16_t>* twsHstry;
|
||||
RingBuffer<int16_t>* awdHstry;
|
||||
RingBuffer<int16_t>* awsHstry;
|
||||
RingBuffer<uint16_t>* awsHstry;
|
||||
} tBoatHstryData; // Holds pointers to all history buffers for boat data
|
||||
|
||||
class HstryBuf {
|
||||
private:
|
||||
GwLog *logger;
|
||||
|
||||
RingBuffer<int16_t> twdHstry; // Circular buffer to store true wind direction values
|
||||
RingBuffer<uint16_t> twsHstry; // Circular buffer to store true wind speed values (TWS)
|
||||
RingBuffer<int16_t> awdHstry; // Circular buffer to store apparant wind direction values
|
||||
RingBuffer<uint16_t> awsHstry; // Circular buffer to store apparant xwind speed values (AWS)
|
||||
int16_t twdHstryMin; // Min value for wind direction (TWD) in history buffer
|
||||
int16_t twdHstryMax; // Max value for wind direction (TWD) in history buffer
|
||||
uint16_t twsHstryMin;
|
||||
uint16_t twsHstryMax;
|
||||
int16_t awdHstryMin;
|
||||
int16_t awdHstryMax;
|
||||
uint16_t awsHstryMin;
|
||||
uint16_t awsHstryMax;
|
||||
|
||||
// boat values for buffers and for true wind calculation
|
||||
GwApi::BoatValue *twdBVal, *twsBVal, *twaBVal, *awdBVal, *awsBVal;
|
||||
GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal;
|
||||
|
||||
public:
|
||||
tBoatHstryData hstryBufList;
|
||||
|
||||
HstryBuf(){
|
||||
hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry}; // Generate history buffers of zero size
|
||||
};
|
||||
HstryBuf(int size) {
|
||||
hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry};
|
||||
hstryBufList.twdHstry->resize(960); // store 960 TWD values for 16 minutes history
|
||||
hstryBufList.twsHstry->resize(960);
|
||||
hstryBufList.awdHstry->resize(960);
|
||||
hstryBufList.awsHstry->resize(960);
|
||||
};
|
||||
void init(BoatValueList* boatValues, GwLog *log);
|
||||
void handleHstryBuf(bool useSimuData);
|
||||
};
|
||||
|
||||
class WindUtils {
|
||||
private:
|
||||
GwApi::BoatValue *twdBVal, *twsBVal, *twaBVal;
|
||||
GwApi::BoatValue *awaBVal, *awsBVal, *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal;
|
||||
static constexpr double DBL_MAX = std::numeric_limits<double>::max();
|
||||
|
||||
public:
|
||||
WindUtils(BoatValueList* boatValues){
|
||||
twdBVal = boatValues->findValueOrCreate("TWD");
|
||||
twsBVal = boatValues->findValueOrCreate("TWS");
|
||||
twaBVal = boatValues->findValueOrCreate("TWA");
|
||||
awaBVal = boatValues->findValueOrCreate("AWA");
|
||||
awsBVal = boatValues->findValueOrCreate("AWS");
|
||||
cogBVal = boatValues->findValueOrCreate("COG");
|
||||
stwBVal = boatValues->findValueOrCreate("STW");
|
||||
sogBVal = boatValues->findValueOrCreate("SOG");
|
||||
hdtBVal = boatValues->findValueOrCreate("HDT");
|
||||
hdmBVal = boatValues->findValueOrCreate("HDM");
|
||||
varBVal = boatValues->findValueOrCreate("VAR");
|
||||
};
|
||||
static double to2PI(double a);
|
||||
static double toPI(double a);
|
||||
static double to360(double a);
|
||||
static double to180(double a);
|
||||
static void toCart(const double* phi, const double* r, double* x, double* y);
|
||||
static void toPol(const double* x, const double* y, double* phi, double* r);
|
||||
static void addPolar(const double* phi1, const double* r1,
|
||||
void toCart(const double* phi, const double* r, double* x, double* y);
|
||||
void toPol(const double* x, const double* y, double* phi, double* r);
|
||||
void addPolar(const double* phi1, const double* r1,
|
||||
const double* phi2, const double* r2,
|
||||
double* phi, double* r);
|
||||
static void calcTwdSA(const double* AWA, const double* AWS,
|
||||
void calcTwdSA(const double* AWA, const double* AWS,
|
||||
const double* CTW, const double* STW, const double* HDT,
|
||||
double* TWD, double* TWS, double* TWA);
|
||||
static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal);
|
||||
static bool calcTrueWind(const double* awaVal, const double* awsVal,
|
||||
bool calcTrueWind(const double* awaVal, const double* awsVal,
|
||||
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
|
||||
const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal);
|
||||
};
|
||||
bool addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog *log);
|
||||
};
|
||||
@@ -1,9 +1,10 @@
|
||||
#ifndef _OBP60FUNCTIONS_H
|
||||
#define _OBP60FUNCTIONS_H
|
||||
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||
#include <Arduino.h>
|
||||
#include "OBP60Hardware.h"
|
||||
|
||||
#include "OBP60Extensions.h" // for buzzer
|
||||
#include "OBPKeyboardTask.h"
|
||||
|
||||
// Global vars
|
||||
|
||||
// Touch keypad over ESP32 touch sensor inputs
|
||||
@@ -58,10 +59,10 @@ void initKeys(CommonData &commonData) {
|
||||
commonData.keydata[5].h = height;
|
||||
}
|
||||
|
||||
#ifdef HARDWARE_V21
|
||||
// Keypad functions for original OBP60 hardware
|
||||
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {
|
||||
|
||||
#ifdef HARDWARE_V21
|
||||
// Keypad functions for original OBP60 hardware
|
||||
int readKeypad(GwLog* logger, uint thSensitivity) {
|
||||
|
||||
// Touch sensor values
|
||||
// 35000 - Not touched
|
||||
// 50000 - Light toched with fingertip
|
||||
@@ -233,35 +234,35 @@ void initKeys(CommonData &commonData) {
|
||||
keycodeold2 = keycode2;
|
||||
|
||||
return keystatus;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef BOARD_OBP40S3
|
||||
int readSensorpads(){
|
||||
// Read key code
|
||||
if(digitalRead(UP) == LOW){
|
||||
#ifdef BOARD_OBP40S3
|
||||
int readSensorpads(){
|
||||
// Read key code
|
||||
if (digitalRead(UP) == LOW) {
|
||||
keycode = 10; // Left swipe
|
||||
}
|
||||
else if(digitalRead(DOWN) == LOW){
|
||||
keycode = 9; // Right swipe
|
||||
}
|
||||
else if(digitalRead(CONF) == LOW){
|
||||
keycode = 3; // Key 3
|
||||
}
|
||||
else if(digitalRead(MENUE) == LOW){
|
||||
keycode = 1; // Key 1
|
||||
}
|
||||
else if(digitalRead(EXIT) == LOW){
|
||||
keycode = 2; // Key 2
|
||||
}
|
||||
else{
|
||||
keycode = 0; // No key activ
|
||||
}
|
||||
return keycode;
|
||||
}
|
||||
else if (digitalRead(DOWN) == LOW) {
|
||||
keycode = 9; // Right swipe
|
||||
}
|
||||
else if (digitalRead(CONF) == LOW) {
|
||||
keycode = 3; // Key 3
|
||||
}
|
||||
else if (digitalRead(MENUE) == LOW) {
|
||||
keycode = 1; // Key 1
|
||||
}
|
||||
else if (digitalRead(EXIT) == LOW) {
|
||||
keycode = 2; // Key 2
|
||||
}
|
||||
else {
|
||||
keycode = 0; // No key activ
|
||||
}
|
||||
return keycode;
|
||||
}
|
||||
|
||||
// Keypad functions for OBP60 clone (thSensitivity is inactiv)
|
||||
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {
|
||||
// Keypad functions for OBP60 clone (thSensitivity is inactiv)
|
||||
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {
|
||||
pinMode(UP, INPUT);
|
||||
pinMode(DOWN, INPUT);
|
||||
pinMode(CONF, INPUT);
|
||||
@@ -273,31 +274,68 @@ void initKeys(CommonData &commonData) {
|
||||
|
||||
// Detect key
|
||||
if (keycode > 0 ){
|
||||
if(keycode != keycodeold){
|
||||
starttime = millis(); // Start key pressed
|
||||
keycodeold = keycode;
|
||||
}
|
||||
// If key pressed longer than 100ms
|
||||
if(millis() > starttime + 100 && keycode == keycodeold) {
|
||||
if (use_syspage and keycode == 3) {
|
||||
keystatus = 12;
|
||||
} else {
|
||||
keystatus = keycode;
|
||||
if(keycode != keycodeold){
|
||||
starttime = millis(); // Start key pressed
|
||||
keycodeold = keycode;
|
||||
}
|
||||
// If key pressed longer than 100ms
|
||||
if(millis() > starttime + 100 && keycode == keycodeold) {
|
||||
if (use_syspage and keycode == 3) {
|
||||
keystatus = 12;
|
||||
} else {
|
||||
keystatus = keycode;
|
||||
}
|
||||
// Copy keycode
|
||||
keycodeold = keycode;
|
||||
// 100% Task-CPU RLY?
|
||||
while(readSensorpads() > 0){} // Wait for pad release
|
||||
delay(keydelay);
|
||||
}
|
||||
// Copy keycode
|
||||
keycodeold = keycode;
|
||||
while(readSensorpads() > 0){} // Wait for pad release
|
||||
delay(keydelay);
|
||||
}
|
||||
}
|
||||
else{
|
||||
keycode = 0;
|
||||
keycodeold = 0;
|
||||
keystatus = 0;
|
||||
else {
|
||||
keycode = 0;
|
||||
keycodeold = 0;
|
||||
keystatus = 0;
|
||||
}
|
||||
|
||||
return keystatus;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void keyboardTask(void *param) {
|
||||
|
||||
// params needed:
|
||||
// queue
|
||||
// logger
|
||||
// sensitivity
|
||||
// use_syspage for deep sleep activation
|
||||
|
||||
KbTaskData *data = (KbTaskData *)param;
|
||||
|
||||
int keycode = 0;
|
||||
data->logger->logDebug(GwLog::LOG, "Start keyboard task");
|
||||
|
||||
while (true) {
|
||||
#ifdef BOARD_OBP40S3
|
||||
keycode = readKeypad(data->logger, data->sensitivity, data->use_syspage);
|
||||
#else
|
||||
keycode = readKeypad(data->logger, data->sensitivity);
|
||||
#endif
|
||||
//send a key event
|
||||
if (keycode != 0) {
|
||||
xQueueSend(data->queue, &keycode, 0);
|
||||
data->logger->logDebug(GwLog::LOG,"kbtask: send keycode: %d", keycode);
|
||||
}
|
||||
delay(20); // 50Hz update rate (20ms)
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void createKeyboardTask(KbTaskData *param) {
|
||||
TaskHandle_t xHandle = NULL;
|
||||
if (xTaskCreate(keyboardTask, "keyboard", configMINIMAL_STACK_SIZE + 1024, param, configMAX_PRIORITIES-1, &xHandle) != pdPASS) {
|
||||
param->logger->logDebug(GwLog::ERROR, "Failed to create keyboard task!");
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
16
lib/obp60task/OBPKeyboardTask.h
Normal file
16
lib/obp60task/OBPKeyboardTask.h
Normal file
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
#include "GwLog.h"
|
||||
#include "Pagedata.h"
|
||||
|
||||
typedef struct {
|
||||
QueueHandle_t queue;
|
||||
GwLog* logger = nullptr;
|
||||
uint sensitivity = 100;
|
||||
#ifdef BOARD_OBP40S3
|
||||
bool use_syspage = true;
|
||||
#endif
|
||||
} KbTaskData;
|
||||
|
||||
void initKeys(CommonData &commonData);
|
||||
void createKeyboardTask(KbTaskData *param);
|
||||
@@ -9,28 +9,32 @@
|
||||
template <typename T>
|
||||
class RingBuffer {
|
||||
private:
|
||||
mutable SemaphoreHandle_t bufLocker;
|
||||
std::vector<T> buffer;
|
||||
std::vector<T> buffer; // THE buffer vector
|
||||
size_t capacity;
|
||||
size_t head; // Points to the next insertion position
|
||||
size_t first; // Points to the first (oldest) valid element
|
||||
size_t last; // Points to the last (newest) valid element
|
||||
size_t count; // Number of valid elements currently in buffer
|
||||
bool is_Full; // Indicates that all buffer elements are used and ringing is in use
|
||||
T MIN_VAL; // lowest possible value of buffer
|
||||
T MAX_VAL; // highest possible value of buffer of type <T>
|
||||
T MIN_VAL; // lowest possible value of buffer of type <T>
|
||||
T MAX_VAL; // highest possible value of buffer of type <T> -> indicates invalid value in buffer
|
||||
mutable SemaphoreHandle_t bufLocker;
|
||||
|
||||
// metadata for buffer
|
||||
String dataName; // Name of boat data in buffer
|
||||
String dataFmt; // Format of boat data in buffer
|
||||
int updFreq; // Update frequency in milliseconds
|
||||
T smallest; // Value range of buffer: smallest value
|
||||
T largest; // Value range of buffer: biggest value
|
||||
T smallest; // Value range of buffer: smallest value; needs to be => MIN_VAL
|
||||
T largest; // Value range of buffer: biggest value; needs to be < MAX_VAL, since MAX_VAL indicates invalid entries
|
||||
|
||||
void initCommon();
|
||||
|
||||
public:
|
||||
RingBuffer();
|
||||
RingBuffer(size_t size);
|
||||
void setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue); // Set meta data for buffer
|
||||
bool getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue); // Get meta data of buffer
|
||||
bool getMetaData(String& name, String& format);
|
||||
String getName() const; // Get buffer name
|
||||
String getFormat() const; // Get buffer data format
|
||||
void add(const T& value); // Add a new value to buffer
|
||||
@@ -51,11 +55,12 @@ public:
|
||||
size_t getLastIdx() const; // Get the index of newest value in buffer
|
||||
bool isEmpty() const; // Check if buffer is empty
|
||||
bool isFull() const; // Check if buffer is full
|
||||
T getMinVal() const; // Get lowest possible value for buffer; used for initialized buffer data
|
||||
T getMaxVal() const; // Get highest possible value for buffer
|
||||
T getMinVal() const; // Get lowest possible value for buffer
|
||||
T getMaxVal() const; // Get highest possible value for buffer; used for unset/invalid buffer data
|
||||
void clear(); // Clear buffer
|
||||
void resize(size_t size); // Delete buffer and set new size
|
||||
T operator[](size_t index) const; // Operator[] for convenient access (same as get())
|
||||
std::vector<T> getAllValues() const; // Get all current values as a vector
|
||||
};
|
||||
|
||||
#include "OBPRingBuffer.tpp"
|
||||
#include "OBPRingBuffer.tpp"
|
||||
@@ -1,5 +1,30 @@
|
||||
#include "OBPRingBuffer.h"
|
||||
|
||||
template <typename T>
|
||||
void RingBuffer<T>::initCommon() {
|
||||
MIN_VAL = std::numeric_limits<T>::lowest();
|
||||
MAX_VAL = std::numeric_limits<T>::max();
|
||||
dataName = "";
|
||||
dataFmt = "";
|
||||
updFreq = -1;
|
||||
smallest = MIN_VAL;
|
||||
largest = MAX_VAL;
|
||||
bufLocker = xSemaphoreCreateMutex();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
RingBuffer<T>::RingBuffer()
|
||||
: capacity(0)
|
||||
, head(0)
|
||||
, first(0)
|
||||
, last(0)
|
||||
, count(0)
|
||||
, is_Full(false)
|
||||
{
|
||||
initCommon();
|
||||
// <buffer> stays empty
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
RingBuffer<T>::RingBuffer(size_t size)
|
||||
: capacity(size)
|
||||
@@ -9,23 +34,8 @@ RingBuffer<T>::RingBuffer(size_t size)
|
||||
, count(0)
|
||||
, is_Full(false)
|
||||
{
|
||||
bufLocker = xSemaphoreCreateMutex();
|
||||
|
||||
if (size == 0) {
|
||||
// return false;
|
||||
}
|
||||
|
||||
MIN_VAL = std::numeric_limits<T>::lowest();
|
||||
MAX_VAL = std::numeric_limits<T>::max();
|
||||
dataName = "";
|
||||
dataFmt = "";
|
||||
updFreq = -1;
|
||||
smallest = MIN_VAL;
|
||||
largest = MAX_VAL;
|
||||
|
||||
buffer.resize(size, MIN_VAL);
|
||||
|
||||
// return true;
|
||||
initCommon();
|
||||
buffer.resize(size, MAX_VAL); // MAX_VAL indicate invalid values
|
||||
}
|
||||
|
||||
// Specify meta data of buffer content
|
||||
@@ -57,6 +67,20 @@ bool RingBuffer<T>::getMetaData(String& name, String& format, int& updateFrequen
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get meta data of buffer content
|
||||
template <typename T>
|
||||
bool RingBuffer<T>::getMetaData(String& name, String& format)
|
||||
{
|
||||
if (dataName == "" || dataFmt == "") {
|
||||
return false; // Meta data not set
|
||||
}
|
||||
|
||||
GWSYNCHRONIZED(&bufLocker);
|
||||
name = dataName;
|
||||
format = dataFmt;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get buffer name
|
||||
template <typename T>
|
||||
String RingBuffer<T>::getName() const
|
||||
@@ -77,7 +101,7 @@ void RingBuffer<T>::add(const T& value)
|
||||
{
|
||||
GWSYNCHRONIZED(&bufLocker);
|
||||
if (value < smallest || value > largest) {
|
||||
buffer[head] = MIN_VAL; // Store MIN_VAL if value is out of range
|
||||
buffer[head] = MAX_VAL; // Store MAX_VAL if value is out of range
|
||||
} else {
|
||||
buffer[head] = value;
|
||||
}
|
||||
@@ -101,7 +125,7 @@ T RingBuffer<T>::get(size_t index) const
|
||||
{
|
||||
GWSYNCHRONIZED(&bufLocker);
|
||||
if (isEmpty() || index < 0 || index >= count) {
|
||||
return MIN_VAL;
|
||||
return MAX_VAL;
|
||||
}
|
||||
|
||||
size_t realIndex = (first + index) % capacity;
|
||||
@@ -120,7 +144,7 @@ template <typename T>
|
||||
T RingBuffer<T>::getFirst() const
|
||||
{
|
||||
if (isEmpty()) {
|
||||
return MIN_VAL;
|
||||
return MAX_VAL;
|
||||
}
|
||||
return get(0);
|
||||
}
|
||||
@@ -130,7 +154,7 @@ template <typename T>
|
||||
T RingBuffer<T>::getLast() const
|
||||
{
|
||||
if (isEmpty()) {
|
||||
return MIN_VAL;
|
||||
return MAX_VAL;
|
||||
}
|
||||
return get(count - 1);
|
||||
}
|
||||
@@ -140,14 +164,14 @@ template <typename T>
|
||||
T RingBuffer<T>::getMin() const
|
||||
{
|
||||
if (isEmpty()) {
|
||||
return MIN_VAL;
|
||||
return MAX_VAL;
|
||||
}
|
||||
|
||||
T minVal = MAX_VAL;
|
||||
T value;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
value = get(i);
|
||||
if (value < minVal && value != MIN_VAL) {
|
||||
if (value < minVal && value != MAX_VAL) {
|
||||
minVal = value;
|
||||
}
|
||||
}
|
||||
@@ -159,7 +183,7 @@ template <typename T>
|
||||
T RingBuffer<T>::getMin(size_t amount) const
|
||||
{
|
||||
if (isEmpty() || amount <= 0) {
|
||||
return MIN_VAL;
|
||||
return MAX_VAL;
|
||||
}
|
||||
if (amount > count)
|
||||
amount = count;
|
||||
@@ -168,7 +192,7 @@ T RingBuffer<T>::getMin(size_t amount) const
|
||||
T value;
|
||||
for (size_t i = 0; i < amount; i++) {
|
||||
value = get(count - 1 - i);
|
||||
if (value < minVal && value != MIN_VAL) {
|
||||
if (value < minVal && value != MAX_VAL) {
|
||||
minVal = value;
|
||||
}
|
||||
}
|
||||
@@ -180,14 +204,14 @@ template <typename T>
|
||||
T RingBuffer<T>::getMax() const
|
||||
{
|
||||
if (isEmpty()) {
|
||||
return MIN_VAL;
|
||||
return MAX_VAL;
|
||||
}
|
||||
|
||||
T maxVal = MIN_VAL;
|
||||
T value;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
value = get(i);
|
||||
if (value > maxVal && value != MIN_VAL) {
|
||||
if (value > maxVal && value != MAX_VAL) {
|
||||
maxVal = value;
|
||||
}
|
||||
}
|
||||
@@ -199,7 +223,7 @@ template <typename T>
|
||||
T RingBuffer<T>::getMax(size_t amount) const
|
||||
{
|
||||
if (isEmpty() || amount <= 0) {
|
||||
return MIN_VAL;
|
||||
return MAX_VAL;
|
||||
}
|
||||
if (amount > count)
|
||||
amount = count;
|
||||
@@ -208,7 +232,7 @@ T RingBuffer<T>::getMax(size_t amount) const
|
||||
T value;
|
||||
for (size_t i = 0; i < amount; i++) {
|
||||
value = get(count - 1 - i);
|
||||
if (value > maxVal && value != MIN_VAL) {
|
||||
if (value > maxVal && value != MAX_VAL) {
|
||||
maxVal = value;
|
||||
}
|
||||
}
|
||||
@@ -220,7 +244,7 @@ template <typename T>
|
||||
T RingBuffer<T>::getMid() const
|
||||
{
|
||||
if (isEmpty()) {
|
||||
return MIN_VAL;
|
||||
return MAX_VAL;
|
||||
}
|
||||
|
||||
return (getMin() + getMax()) / static_cast<T>(2);
|
||||
@@ -231,7 +255,7 @@ template <typename T>
|
||||
T RingBuffer<T>::getMid(size_t amount) const
|
||||
{
|
||||
if (isEmpty() || amount <= 0) {
|
||||
return MIN_VAL;
|
||||
return MAX_VAL;
|
||||
}
|
||||
|
||||
if (amount > count)
|
||||
@@ -245,7 +269,7 @@ template <typename T>
|
||||
T RingBuffer<T>::getMedian() const
|
||||
{
|
||||
if (isEmpty()) {
|
||||
return MIN_VAL;
|
||||
return MAX_VAL;
|
||||
}
|
||||
|
||||
// Create a temporary vector with current valid elements
|
||||
@@ -274,7 +298,7 @@ template <typename T>
|
||||
T RingBuffer<T>::getMedian(size_t amount) const
|
||||
{
|
||||
if (isEmpty() || amount <= 0) {
|
||||
return MIN_VAL;
|
||||
return MAX_VAL;
|
||||
}
|
||||
if (amount > count)
|
||||
amount = count;
|
||||
@@ -342,14 +366,14 @@ bool RingBuffer<T>::isFull() const
|
||||
return is_Full;
|
||||
}
|
||||
|
||||
// Get lowest possible value for buffer; used for non-set buffer data
|
||||
// Get lowest possible value for buffer
|
||||
template <typename T>
|
||||
T RingBuffer<T>::getMinVal() const
|
||||
{
|
||||
return MIN_VAL;
|
||||
}
|
||||
|
||||
// Get highest possible value for buffer
|
||||
// Get highest possible value for buffer; used for unset/invalid buffer data
|
||||
template <typename T>
|
||||
T RingBuffer<T>::getMaxVal() const
|
||||
{
|
||||
@@ -368,6 +392,22 @@ void RingBuffer<T>::clear()
|
||||
is_Full = false;
|
||||
}
|
||||
|
||||
// Delete buffer and set new size
|
||||
template <typename T>
|
||||
void RingBuffer<T>::resize(size_t newSize)
|
||||
{
|
||||
GWSYNCHRONIZED(&bufLocker);
|
||||
capacity = newSize;
|
||||
head = 0;
|
||||
first = 0;
|
||||
last = 0;
|
||||
count = 0;
|
||||
is_Full = false;
|
||||
|
||||
buffer.clear();
|
||||
buffer.resize(newSize, MAX_VAL);
|
||||
}
|
||||
|
||||
// Get all current values as a vector
|
||||
template <typename T>
|
||||
std::vector<T> RingBuffer<T>::getAllValues() const
|
||||
@@ -380,4 +420,4 @@ std::vector<T> RingBuffer<T>::getAllValues() const
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||
#include <Adafruit_Sensor.h> // Adafruit Lib for sensors
|
||||
#include <Adafruit_BME280.h> // Adafruit Lib for BME280
|
||||
@@ -568,6 +569,11 @@ void sensorTask(void *param){
|
||||
// Send data from environment sensor all 2s
|
||||
if(millis() > starttime6 + 2000){
|
||||
starttime6 = millis();
|
||||
|
||||
// DEBUG
|
||||
UBaseType_t stackfree = uxTaskGetStackHighWaterMark(NULL);
|
||||
api->getLogger()->logDebug(GwLog::LOG, "obpSensortask Stack=%d", stackfree);
|
||||
|
||||
unsigned char TempSource = 2; // Inside temperature
|
||||
unsigned char PressureSource = 0; // Atmospheric pressure
|
||||
unsigned char HumiditySource = 0; // Inside humidity
|
||||
@@ -785,8 +791,12 @@ void sensorTask(void *param){
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
|
||||
void createSensorTask(SharedData *shared){
|
||||
xTaskCreate(sensorTask,"readSensors",10000,shared,3,NULL);
|
||||
void createSensorTask(SharedData *shared) {
|
||||
TaskHandle_t xHandle = NULL;
|
||||
GwLog *logger = shared->api->getLogger();
|
||||
esp_err_t err = xTaskCreate(sensorTask, "readSensors", configMINIMAL_STACK_SIZE + 8192, shared, 3, &xHandle);
|
||||
if ( err != pdPASS) {
|
||||
logger->logDebug(GwLog::ERROR, "Failed to create sensor task! (err=%d)", err);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
#include "GwSynchronized.h"
|
||||
#include "GwApi.h"
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
|
||||
Feature possibilities
|
||||
- switch between North up / Heading up
|
||||
- filter
|
||||
- zoom
|
||||
- special vessel symbols
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
Drop / raise function in device OBP40 has to be done inside
|
||||
config mode because of limited number of buttons.
|
||||
|
||||
Save position in FRAM
|
||||
Alarm: gps fix lost
|
||||
switch unit feet/meter
|
||||
|
||||
*/
|
||||
|
||||
#define anchor_width 16
|
||||
|
||||
@@ -227,7 +227,7 @@ public:
|
||||
epd->print(value1,2); // Real value as formated string
|
||||
}
|
||||
else{
|
||||
epd->print("---"); // No sensor data (sensor is off)
|
||||
epd->print(commonData->fmt->placeholder); // No sensor data (sensor is off)
|
||||
}
|
||||
|
||||
// ############### Horizontal Line ################
|
||||
@@ -256,7 +256,7 @@ public:
|
||||
epd->print(value2,1); // Real value as formated string
|
||||
}
|
||||
else{
|
||||
epd->print("---"); // No sensor data (sensor is off)
|
||||
epd->print(commonData->fmt->placeholder); // No sensor data (sensor is off)
|
||||
}
|
||||
|
||||
// ############### Horizontal Line ################
|
||||
@@ -285,7 +285,7 @@ public:
|
||||
epd->print(value3,1); // Real value as formated string
|
||||
}
|
||||
else{
|
||||
epd->print("---"); // No sensor data (sensor is off)
|
||||
epd->print(commonData->fmt->placeholder); // No sensor data (sensor is off)
|
||||
}
|
||||
|
||||
return PAGE_UPDATE;
|
||||
|
||||
@@ -286,7 +286,7 @@ public:
|
||||
if(value1 > 99.9) epd->print(value1, 0);
|
||||
}
|
||||
else{
|
||||
epd->print("---"); // Missing bus data
|
||||
epd->print(commonData->fmt->placeholder); // Missing bus data
|
||||
}
|
||||
}
|
||||
epd->setFont(&Ubuntu_Bold16pt8b);
|
||||
@@ -300,7 +300,9 @@ public:
|
||||
if(value2 > 9.9 && value2 <= 99.9)epd->print(value2, 1);
|
||||
if(value2 > 99.9) epd->print(value2, 0);
|
||||
}
|
||||
else epd->print("---");
|
||||
else {
|
||||
epd->print(commonData->fmt->placeholder);
|
||||
}
|
||||
epd->setFont(&Ubuntu_Bold16pt8b);
|
||||
epd->print("A");
|
||||
|
||||
@@ -312,7 +314,9 @@ public:
|
||||
if(value3 > 9.9 && value3 <= 99.9)epd->print(value3, 1);
|
||||
if(value3 > 99.9) epd->print(value3, 0);
|
||||
}
|
||||
else epd->print("---");
|
||||
else {
|
||||
epd->print(commonData->fmt->placeholder);
|
||||
}
|
||||
epd->setFont(&Ubuntu_Bold16pt8b);
|
||||
epd->print("W");
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
class PageClock : public Page
|
||||
{
|
||||
private:
|
||||
String dateformat;
|
||||
fmtDate dateformat;
|
||||
int simtime;
|
||||
bool keylock = false;
|
||||
char source = 'R'; // time source (R)TC | (G)PS | (N)TP
|
||||
@@ -35,7 +35,7 @@ public:
|
||||
logger->logDebug(GwLog::LOG, "Instantiate PageClock");
|
||||
|
||||
// Get config data
|
||||
dateformat = config->getString(config->dateFormat);
|
||||
dateformat = common.fmt->getDateFormat(config->getString(config->dateFormat));
|
||||
timezone = config->getString(config->timeZone).toDouble();
|
||||
homelat = config->getString(config->homeLAT).toDouble();
|
||||
homelon = config->getString(config->homeLON).toDouble();
|
||||
@@ -208,7 +208,7 @@ public:
|
||||
epd->print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday));
|
||||
}
|
||||
} else {
|
||||
epd->print("---");
|
||||
epd->print(commonData->fmt->placeholder);
|
||||
}
|
||||
} else {
|
||||
epd->print(svalue2old);
|
||||
@@ -229,13 +229,13 @@ public:
|
||||
}
|
||||
else if (commonData->data.rtcValid) {
|
||||
if (tz == 'L') {
|
||||
epd->print(formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec));
|
||||
epd->print(formatTime(fmtTime::MMHHSS, local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec));
|
||||
}
|
||||
else {
|
||||
epd->print(formatTime('s', commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec));
|
||||
epd->print(formatTime(fmtTime::MMHHSS, commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec));
|
||||
}
|
||||
} else {
|
||||
epd->print("---");
|
||||
epd->print(commonData->fmt->placeholder);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -246,7 +246,7 @@ public:
|
||||
epd->print("Time"); // Name
|
||||
|
||||
// Show values sunrise
|
||||
String sunrise = "---";
|
||||
String sunrise = commonData->fmt->placeholder;
|
||||
if (((source == 'G') and gpsvalid) or (homevalid and commonData->data.rtcValid)) {
|
||||
sunrise = String(commonData->sundata.sunriseHour) + ":" + String(commonData->sundata.sunriseMinute + 100).substring(1);
|
||||
svalue5old = sunrise;
|
||||
@@ -266,7 +266,7 @@ public:
|
||||
epd->fillRect(340, 149, 80, 3, commonData->fgcolor);
|
||||
|
||||
// Show values sunset
|
||||
String sunset = "---";
|
||||
String sunset = commonData->fmt->placeholder;
|
||||
if (((source == 'G') and gpsvalid) or (homevalid and commonData->data.rtcValid)) {
|
||||
sunset = String(commonData->sundata.sunsetHour) + ":" + String(commonData->sundata.sunsetMinute + 100).substring(1);
|
||||
svalue6old = sunset;
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
/*
|
||||
Electric propulsion
|
||||
|
||||
- Current, voltage, power
|
||||
- 12, 24, 48 etc. Voltage
|
||||
- rpm
|
||||
- throttle position
|
||||
- controller state
|
||||
- error codes
|
||||
- temperature engine, controller, batteries
|
||||
|
||||
*/
|
||||
|
||||
class PageEPropulsion : public Page
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
#include "BoatDataCalibration.h"
|
||||
#endif
|
||||
|
||||
class PageFourValues : public Page
|
||||
{
|
||||
@@ -54,7 +57,9 @@ public:
|
||||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = commonData->fmt->formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -64,7 +69,9 @@ public:
|
||||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = commonData->fmt->formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -74,7 +81,9 @@ public:
|
||||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
|
||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value3 = bvalue3->value; // Value as double in SI unit
|
||||
bool valid3 = bvalue3->valid; // Valid information
|
||||
String svalue3 = commonData->fmt->formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -84,7 +93,9 @@ public:
|
||||
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Fourth element in list
|
||||
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||
name4 = name4.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value4 = bvalue4->value; // Value as double in SI unit
|
||||
bool valid4 = bvalue4->valid; // Valid information
|
||||
String svalue4 = commonData->fmt->formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -298,8 +309,6 @@ public:
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
static Page *createPage(CommonData &common){
|
||||
return new PageFourValues(common);
|
||||
}/**
|
||||
|
||||
@@ -54,7 +54,9 @@ public:
|
||||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = commonData->fmt->formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -64,7 +66,9 @@ public:
|
||||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue)
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = commonData->fmt->formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -74,7 +78,9 @@ public:
|
||||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue)
|
||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value3 = bvalue3->value; // Value as double in SI unit
|
||||
bool valid3 = bvalue3->valid; // Valid information
|
||||
String svalue3 = commonData->fmt->formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -84,7 +90,9 @@ public:
|
||||
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Second element in list (only one value by PageOneValue)
|
||||
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||
name4 = name4.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value4 = bvalue4->value; // Value as double in SI unit
|
||||
bool valid4 = bvalue4->valid; // Valid information
|
||||
String svalue4 = commonData->fmt->formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
||||
@@ -168,7 +168,7 @@ public:
|
||||
if(value1 > 99.9) epd->print(value1, 0);
|
||||
}
|
||||
else{
|
||||
epd->print("---"); // Missing bus data
|
||||
epd->print(commonData->fmt->placeholder); // Missing bus data
|
||||
}
|
||||
}
|
||||
epd->setFont(&Ubuntu_Bold16pt8b);
|
||||
@@ -177,24 +177,37 @@ public:
|
||||
// Show actual current in A
|
||||
epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||
epd->setCursor(260, 200);
|
||||
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){
|
||||
if(value2 <= 9.9) epd->print(value2, 2);
|
||||
if(value2 > 9.9 && value2 <= 99.9)epd->print(value2, 1);
|
||||
if(value2 > 99.9) epd->print(value2, 0);
|
||||
if ((powerSensor == "INA219" || powerSensor == "INA226") && (simulation == false)) {
|
||||
// TODO use formatter for this?
|
||||
if (value2 <= 9.9) {
|
||||
epd->print(value2, 2);
|
||||
} else if (value2 <= 99.9) {
|
||||
epd->print(value2, 1);
|
||||
} else {
|
||||
epd->print(value2, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
epd->print(commonData->fmt->placeholder);
|
||||
}
|
||||
else epd->print("---");
|
||||
epd->setFont(&Ubuntu_Bold16pt8b);
|
||||
epd->print("A");
|
||||
|
||||
// Show actual consumption in W
|
||||
epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||
epd->setCursor(260, 260);
|
||||
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){
|
||||
if(value3 <= 9.9) epd->print(value3, 2);
|
||||
if(value3 > 9.9 && value3 <= 99.9)epd->print(value3, 1);
|
||||
if(value3 > 99.9) epd->print(value3, 0);
|
||||
if ((powerSensor == "INA219" || powerSensor == "INA226") && (simulation == false)) {
|
||||
if(value3 <= 9.9) {
|
||||
epd->print(value3, 2);
|
||||
} else if (value3 <= 99.9) {
|
||||
epd->print(value3, 1);
|
||||
} else {
|
||||
epd->print(value3, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
epd->print(commonData->fmt->placeholder);
|
||||
}
|
||||
else epd->print("---");
|
||||
epd->setFont(&Ubuntu_Bold16pt8b);
|
||||
epd->print("W");
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
#include "BoatDataCalibration.h"
|
||||
#endif
|
||||
|
||||
class PageOneValue : public Page
|
||||
{
|
||||
@@ -49,7 +52,9 @@ public:
|
||||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = commonData->fmt->formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
#include "BoatDataCalibration.h"
|
||||
#endif
|
||||
|
||||
class PageRudderPosition : public Page
|
||||
{
|
||||
@@ -49,7 +52,9 @@ public:
|
||||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list
|
||||
String name1 = bvalue1->getName().c_str(); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
value1 = bvalue1->value; // Raw value without unit convertion
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = commonData->fmt->formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -67,8 +72,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Logging boat values
|
||||
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||
// Log boat values
|
||||
logger->logDebug(GwLog::LOG, "Drawing at PageRudderPosition, %s:%f", name1.c_str(), value1);
|
||||
|
||||
// Draw page
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
#include "BoatDataCalibration.h"
|
||||
#endif
|
||||
|
||||
const int SixValues_x1 = 5;
|
||||
const int SixValues_DeltaX = 200;
|
||||
@@ -65,7 +68,9 @@ public:
|
||||
bvalue = pageData.values[i];
|
||||
DataName[i] = xdrDelete(bvalue->getName());
|
||||
DataName[i] = DataName[i].substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
DataValue[i] = bvalue->value; // Value as double in SI unit
|
||||
DataValid[i] = bvalue->valid;
|
||||
DataText[i] = commonData->fmt->formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
||||
@@ -4,23 +4,33 @@
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm> // for vector sorting
|
||||
|
||||
/*
|
||||
* SkyView / Satellites
|
||||
*/
|
||||
|
||||
class PageSkyView : public Page
|
||||
{
|
||||
private:
|
||||
GwBoatData *bd;
|
||||
|
||||
public:
|
||||
PageSkyView(CommonData &common) : Page(common)
|
||||
{
|
||||
logger->logDebug(GwLog::LOG, "Instantiate PageSkyView");
|
||||
// task name access is for example purpose only
|
||||
TaskHandle_t currentTaskHandle = xTaskGetCurrentTaskHandle();
|
||||
const char* taskName = pcTaskGetName(currentTaskHandle);
|
||||
logger->logDebug(GwLog::LOG, "Instantiate PageSkyView in task '%s'", taskName);
|
||||
}
|
||||
|
||||
int handleKey(int key){
|
||||
// Code for keylock
|
||||
if(key == 11){
|
||||
int handleKey(int key) {
|
||||
// return 0 to mark the key handled completely
|
||||
// return the key to allow further action
|
||||
if (key == 11) {
|
||||
commonData->keylock = !commonData->keylock;
|
||||
return 0; // Commit the key
|
||||
return 0;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
@@ -33,12 +43,25 @@ public:
|
||||
setFlashLED(false);
|
||||
}
|
||||
#endif
|
||||
bd = pageData.api->getBoatData();
|
||||
};
|
||||
|
||||
// Comparator function to sort by SNR
|
||||
static bool compareBySNR(const GwSatInfo& a, const GwSatInfo& b) {
|
||||
return a.SNR > b.SNR; // Sort in descending order
|
||||
}
|
||||
|
||||
int displayPage(PageData &pageData) {
|
||||
|
||||
// Logging boat values
|
||||
logger->logDebug(GwLog::LOG, "Drawing at PageSkyView");
|
||||
std::vector<GwSatInfo> sats;
|
||||
int nSat = bd->SatInfo->getNumSats();
|
||||
|
||||
logger->logDebug(GwLog::LOG, "Drawing at PageSkyView, %d satellites", nSat);
|
||||
|
||||
for (int i = 0; i < nSat; i++) {
|
||||
sats.push_back(*bd->SatInfo->getAt(i));
|
||||
}
|
||||
std::sort(sats.begin(), sats.end(), compareBySNR);
|
||||
|
||||
// Draw page
|
||||
//***********************************************************
|
||||
@@ -49,6 +72,7 @@ public:
|
||||
// current position
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
|
||||
|
||||
GwApi::BoatValue *bv_lat = pageData.values[0];
|
||||
String sv_lat = commonData->fmt->formatValue(bv_lat, *commonData).svalue;
|
||||
//epd->setCursor(300, 40);
|
||||
@@ -88,7 +112,7 @@ public:
|
||||
|
||||
// directions
|
||||
|
||||
int16_t x1, y1;
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
epd->setFont(&Ubuntu_Bold12pt8b);
|
||||
|
||||
@@ -108,21 +132,42 @@ public:
|
||||
epd->setCursor(c.x - r + 2 , c.y + h / 2);
|
||||
epd->print("W");
|
||||
|
||||
// satellites
|
||||
|
||||
// show satellites in "map"
|
||||
epd->setFont(&Atari6px);
|
||||
for (int i = 0; i < nSat; i++) {
|
||||
float arad = sats[i].Azimut * M_PI / 180.0;
|
||||
float erad = sats[i].Elevation * M_PI / 180.0;
|
||||
uint16_t x = c.x + sin(arad) * erad * r;
|
||||
uint16_t y = c.y + cos(arad) * erad * r;
|
||||
epd->drawRect(x-4, y-4, 8, 8, commonData->fgcolor);
|
||||
// Add Sat number
|
||||
epd->setCursor(x+5, y);
|
||||
char buffer[3];
|
||||
snprintf(buffer, 3, "%02d", static_cast<int>(sats[i].PRN));
|
||||
epd->print(String(buffer));
|
||||
}
|
||||
|
||||
// Signal / Noise bars
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setCursor(325, 34);
|
||||
epd->print("SNR");
|
||||
epd->drawRect(270, 20, 125, 257, commonData->fgcolor);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
int maxsat = std::min(nSat, 12);
|
||||
for (int i = 0; i < maxsat; i++) {
|
||||
uint16_t y = 29 + (i + 1) * 20;
|
||||
epd->setCursor(276, y);
|
||||
char buffer[3];
|
||||
snprintf(buffer, 3, "%02d", i+1);
|
||||
snprintf(buffer, 3, "%02d", static_cast<int>(sats[i].PRN));
|
||||
epd->print(String(buffer));
|
||||
epd->drawRect(305, y-12, 85, 14, commonData->fgcolor);
|
||||
epd->setCursor(315, y);
|
||||
// TODO SNR as number or as bar via mode key?
|
||||
if (sats[i].SNR <= 100) {
|
||||
// epd->print(sats[i].SNR);
|
||||
epd->fillRect(307, y-10, int(81 * sats[i].SNR / 100.0), 10, commonData->fgcolor);
|
||||
} else {
|
||||
epd->print("n/a");
|
||||
}
|
||||
}
|
||||
|
||||
return PAGE_UPDATE;
|
||||
|
||||
@@ -160,12 +160,16 @@ public:
|
||||
// Check for valid real data, display also if hold values activated
|
||||
if(valid1 == true || holdvalues == true){
|
||||
// Resolution switching
|
||||
if(value1 <= 9.9) epd->print(value1, 2);
|
||||
if(value1 > 9.9 && value1 <= 99.9)epd->print(value1, 1);
|
||||
if(value1 > 99.9) epd->print(value1, 0);
|
||||
if (value1 <= 9.9) {
|
||||
epd->print(value1, 2);
|
||||
} else if (value1 <= 99.9) {
|
||||
epd->print(value1, 1);
|
||||
} else {
|
||||
epd->print(value1, 0);
|
||||
}
|
||||
}
|
||||
else{
|
||||
epd->print("---"); // Missing bus data
|
||||
else {
|
||||
epd->print(commonData->fmt->placeholder); // Missing bus data
|
||||
}
|
||||
}
|
||||
epd->setFont(&Ubuntu_Bold16pt8b);
|
||||
@@ -174,24 +178,36 @@ public:
|
||||
// Show actual current in A
|
||||
epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||
epd->setCursor(260, 200);
|
||||
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){
|
||||
if(value2 <= 9.9) epd->print(value2, 2);
|
||||
if(value2 > 9.9 && value2 <= 99.9)epd->print(value2, 1);
|
||||
if(value2 > 99.9) epd->print(value2, 0);
|
||||
if ((powerSensor == "INA219" || powerSensor == "INA226") && (simulation == false)) {
|
||||
if (value2 <= 9.9) {
|
||||
epd->print(value2, 2);
|
||||
} else if (value2 <= 99.9) {
|
||||
epd->print(value2, 1);
|
||||
} else {
|
||||
epd->print(value2, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
epd->print(commonData->fmt->placeholder);
|
||||
}
|
||||
else epd->print("---");
|
||||
epd->setFont(&Ubuntu_Bold16pt8b);
|
||||
epd->print("A");
|
||||
|
||||
// Show actual consumption in W
|
||||
epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||
epd->setCursor(260, 260);
|
||||
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){
|
||||
if(value3 <= 9.9) epd->print(value3, 2);
|
||||
if(value3 > 9.9 && value3 <= 99.9)epd->print(value3, 1);
|
||||
if(value3 > 99.9) epd->print(value3, 0);
|
||||
if ((powerSensor == "INA219" || powerSensor == "INA226") && (simulation == false)) {
|
||||
if (value3 <= 9.9) {
|
||||
epd->print(value3, 2);
|
||||
} else if (value3 <= 99.9) {
|
||||
epd->print(value3, 1);
|
||||
} else {
|
||||
epd->print(value3, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
epd->print(commonData->fmt->placeholder);
|
||||
}
|
||||
else epd->print("---");
|
||||
epd->setFont(&Ubuntu_Bold16pt8b);
|
||||
epd->print("W");
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* 1. Hard and software information
|
||||
* 2. System settings
|
||||
* 3. System configuration: running and NVRAM
|
||||
* 4. NMEA2000 device list
|
||||
* 4. NMEA2000 device list if NMEA2000 enabled
|
||||
* 5. SD Card information if available
|
||||
*
|
||||
* TODO
|
||||
@@ -43,6 +43,8 @@
|
||||
#define DISPLAYINFO STRINGIZE(EPDTYPE)
|
||||
#define GXEPD2INFO STRINGIZE(GXEPD2VERS)
|
||||
|
||||
#define N2K_INACTIVE_AGE 30000
|
||||
|
||||
class PageSystem : public Page
|
||||
{
|
||||
private:
|
||||
@@ -68,6 +70,10 @@ private:
|
||||
double homelon;
|
||||
|
||||
Nmea2kTwai *NMEA2000;
|
||||
unsigned long n2kRxOld = 0; // to detect bus activity
|
||||
unsigned long n2kTxOld = 0;
|
||||
long n2k_ts = 0; // timestamp of last activity
|
||||
bool n2k_active = false;
|
||||
|
||||
char mode = 'N'; // (N)ormal, (S)ettings, (C)onfiguration, (D)evice list, c(A)rd
|
||||
int8_t editmode = -1; // marker for menu/edit/set function
|
||||
@@ -233,7 +239,7 @@ private:
|
||||
epd->print(String(Heap_free));
|
||||
|
||||
// RAM free for task
|
||||
int RAM_free = uxTaskGetStackHighWaterMark(NULL);
|
||||
UBaseType_t RAM_free = uxTaskGetStackHighWaterMark(NULL);
|
||||
epd->setCursor(202, y0 + 32);
|
||||
epd->print("Task free:");
|
||||
epd->setCursor(300, y0 + 32);
|
||||
@@ -390,7 +396,6 @@ private:
|
||||
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setCursor(x0, y0);
|
||||
epd->print("Work in progress...");
|
||||
#ifdef BOARD_OBP60S3
|
||||
// This mode should not be callable by devices without card hardware
|
||||
// In case of accidential reaching this, display a friendly message
|
||||
@@ -402,32 +407,39 @@ private:
|
||||
magic.dat
|
||||
version.dat
|
||||
readme.txt
|
||||
IMAGES/
|
||||
BAK/
|
||||
CHARTS/
|
||||
LOGS/
|
||||
DATA/
|
||||
IMAGES/
|
||||
LOGS/
|
||||
*/
|
||||
|
||||
// Simple test for one file in root
|
||||
epd->setCursor(x0, y0 + 32);
|
||||
String file_test = MOUNT_POINT "/IMG/icon-settings2.pbm";
|
||||
logger->logDebug(GwLog::LOG, "Testfile: %s", file_test.c_str());
|
||||
// Simple test for magic file in root
|
||||
epd->setCursor(x0, y0 + 16);
|
||||
const char* file_magic = MOUNT_POINT "/magic.dat";
|
||||
logger->logDebug(GwLog::LOG, "Test magicfile: %s", file_magic);
|
||||
struct stat st;
|
||||
if (stat(file_test.c_str(), &st) == 0) {
|
||||
epd->printf("File %s exists", file_test.c_str());
|
||||
if (stat(file_magic, &st) == 0) {
|
||||
epd->printf("Magicfile %s exists", file_magic);
|
||||
} else {
|
||||
epd->printf("File %s not found", file_test.c_str());
|
||||
epd->printf("Magicfile %s not found", file_magic);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
while (((entry = readdir(dir)) != NULL) and (dy < 140)) {
|
||||
epd->setCursor(x0, y0 + 64 + dy);
|
||||
epd->print(entry->d_name);
|
||||
// type 1 is file, type 2 is dir
|
||||
if (entry->d_type == 2) {
|
||||
epd->print("/");
|
||||
}
|
||||
dy += 20;
|
||||
logger->logDebug(GwLog::LOG, " %s type %d", entry->d_name, entry->d_type);
|
||||
// type 1 is file
|
||||
// type 2 is dir
|
||||
}
|
||||
closedir(dir);
|
||||
} else {
|
||||
@@ -436,7 +448,7 @@ private:
|
||||
|
||||
// try to load example pbm file
|
||||
// TODO create PBM load function in imglib
|
||||
String filepath = MOUNT_POINT "/IMG/icon-settings2.pbm";
|
||||
String filepath = MOUNT_POINT "/IMAGES/icon-settings2.pbm";
|
||||
const char* file_img = filepath.c_str();
|
||||
FILE* fh = fopen(file_img, "r");
|
||||
if (fh != NULL) {
|
||||
@@ -494,8 +506,12 @@ private:
|
||||
#endif
|
||||
}
|
||||
|
||||
void displayModeDevicelist() {
|
||||
void displayModeDevicelist(bool bus_active) {
|
||||
// NMEA2000 device list
|
||||
|
||||
// TODO Check if NMEA2000 enabled globally
|
||||
// if disabled this page should not be shown
|
||||
|
||||
epd->setFont(&Ubuntu_Bold12pt8b);
|
||||
epd->setCursor(8, 48);
|
||||
epd->print("NMEA2000 device list");
|
||||
@@ -508,6 +524,11 @@ private:
|
||||
epd->print("TxD: ");
|
||||
epd->print(String(commonData->status.n2kTx));
|
||||
|
||||
// show bus activity
|
||||
epd->setCursor(20, 120);
|
||||
epd->print("Bus: ");
|
||||
epd->print(bus_active ? "active" : "inactive");
|
||||
|
||||
epd->setCursor(20, 140);
|
||||
epd->printf("N2k source address: %d", NMEA2000->GetN2kSource());
|
||||
|
||||
@@ -685,7 +706,15 @@ public:
|
||||
displayModeSDCard();
|
||||
break;
|
||||
case 'D':
|
||||
displayModeDevicelist();
|
||||
// check bus status
|
||||
if (commonData->status.n2kRx != n2kRxOld || commonData->status.n2kTx != n2kTxOld) {
|
||||
n2kRxOld = commonData->status.n2kRx;
|
||||
n2kTxOld = commonData->status.n2kTx;
|
||||
n2k_ts = millis();
|
||||
} else {
|
||||
n2k_active = (millis() - n2k_ts <= N2K_INACTIVE_AGE);
|
||||
}
|
||||
displayModeDevicelist(n2k_active);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
#include "BoatDataCalibration.h"
|
||||
#endif
|
||||
|
||||
class PageThreeValues : public Page
|
||||
{
|
||||
@@ -52,7 +55,9 @@ public:
|
||||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = commonData->fmt->formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -62,7 +67,9 @@ public:
|
||||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = commonData->fmt->formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -72,15 +79,19 @@ public:
|
||||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
|
||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value3 = bvalue3->value; // Value as double in SI unit
|
||||
bool valid3 = bvalue3->valid; // Valid information
|
||||
String svalue3 = commonData->fmt->formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
String unit3 = commonData->fmt->formatValue(bvalue3, *commonData).unit; // Unit of value
|
||||
|
||||
// Logging boat values
|
||||
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||
logger->logDebug(GwLog::LOG, "Drawing at PageThreeValues, %s: %f, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3);
|
||||
// Log boat values
|
||||
logger->logDebug(GwLog::LOG, "Drawing at PageThreeValues, %s: %f, %s: %f, %s: %f",
|
||||
name1.c_str(), value1,
|
||||
name2.c_str(), value2,
|
||||
name3.c_str(), value3);
|
||||
|
||||
// Draw page
|
||||
//***********************************************************
|
||||
|
||||
121
lib/obp60task/PageTracker.cpp
Normal file
121
lib/obp60task/PageTracker.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
|
||||
/*
|
||||
Tracker
|
||||
- standalone with SD card backend
|
||||
- standalone with server backend
|
||||
- Regatta Hero integration
|
||||
*/
|
||||
|
||||
class PageTracker : public Page
|
||||
{
|
||||
private:
|
||||
|
||||
char mode = 'N'; // (N)ormal, (C)onfig
|
||||
|
||||
void displayModeNormal(PageData &pageData) {
|
||||
|
||||
// TBD Boatvalues: ...
|
||||
|
||||
logger->logDebug(GwLog::DEBUG,"Drawing at PageTracker");
|
||||
|
||||
// Title
|
||||
epd->setTextColor(commonData->fgcolor);
|
||||
epd->setFont(&Ubuntu_Bold12pt8b);
|
||||
epd->setCursor(8, 48);
|
||||
epd->print("Tracker");
|
||||
|
||||
}
|
||||
|
||||
void displayModeConfig() {
|
||||
|
||||
epd->setTextColor(commonData->fgcolor);
|
||||
epd->setFont(&Ubuntu_Bold12pt8b);
|
||||
epd->setCursor(8, 48);
|
||||
epd->print("Tracker configuration");
|
||||
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
// TODO
|
||||
|
||||
}
|
||||
|
||||
public:
|
||||
PageTracker(CommonData &common) : Page(common)
|
||||
{
|
||||
logger->logDebug(GwLog::LOG, "Instantiate PageTracker");
|
||||
}
|
||||
|
||||
void setupKeys(){
|
||||
Page::setupKeys();
|
||||
commonData->keydata[0].label = "START";
|
||||
commonData->keydata[1].label = "STOP";
|
||||
}
|
||||
|
||||
int handleKey(int key){
|
||||
if (key == 1) { // Switch between normal and config mode
|
||||
if (mode == 'N') {
|
||||
mode = 'C';
|
||||
} else {
|
||||
mode = 'N';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (key == 11) {
|
||||
commonData->keylock = !commonData->keylock;
|
||||
return 0;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
void displayNew(PageData &pageData) {
|
||||
#ifdef BOARD_OBP60S3
|
||||
// Clear optical warning
|
||||
if (flashLED == "Limit Violation") {
|
||||
setBlinkingLED(false);
|
||||
setFlashLED(false);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
int displayPage(PageData &pageData){
|
||||
|
||||
// Logging boat values
|
||||
logger->logDebug(GwLog::LOG, "Drawing at PageTracker; Mode=%c", mode);
|
||||
|
||||
// Set display in partial refresh mode
|
||||
epd->setPartialWindow(0, 0, epd->width(), epd->height());
|
||||
|
||||
if (mode == 'N') {
|
||||
displayModeNormal(pageData);
|
||||
} else if (mode == 'C') {
|
||||
displayModeConfig();
|
||||
}
|
||||
|
||||
return PAGE_UPDATE;
|
||||
};
|
||||
};
|
||||
|
||||
static Page *createPage(CommonData &common){
|
||||
return new PageTracker(common);
|
||||
}
|
||||
|
||||
/**
|
||||
* with the code below we make this page known to the PageTask
|
||||
* we give it a type (name) that can be selected in the config
|
||||
* we define which function is to be called
|
||||
* and we provide the number of user parameters we expect
|
||||
* this will be number of BoatValue pointers in pageData.values
|
||||
*/
|
||||
PageDescription registerPageTracker(
|
||||
"Tracker", // Page name
|
||||
createPage, // Action
|
||||
0, // Number of bus values depends on selection in Web configuration
|
||||
{"LAT", "LON"}, // Names of bus values undepends on selection in Web configuration (refer GwBoatData.h)
|
||||
true // Show display header on/off
|
||||
);
|
||||
|
||||
#endif
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
#include "BoatDataCalibration.h"
|
||||
#endif
|
||||
|
||||
class PageTwoValues : public Page
|
||||
{
|
||||
@@ -51,7 +54,9 @@ public:
|
||||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = commonData->fmt->formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -61,7 +66,9 @@ public:
|
||||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = commonData->fmt->formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
||||
@@ -249,7 +249,7 @@ public:
|
||||
}
|
||||
}
|
||||
else{
|
||||
epd->print("---"); // Missing bus data
|
||||
epd->print(commonData->fmt->placeholder); // Missing bus data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "N2kMessages.h"
|
||||
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
#include "BoatDataCalibration.h"
|
||||
#endif
|
||||
|
||||
#define front_width 120
|
||||
#define front_height 162
|
||||
@@ -331,7 +334,9 @@ public:
|
||||
}
|
||||
String name1 = bvalue1->getName().c_str(); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
// bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = commonData->fmt->formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -345,7 +350,9 @@ public:
|
||||
}
|
||||
String name2 = bvalue2->getName().c_str(); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
// bool valid2 = bvalue2->valid; // Valid information
|
||||
if (simulation) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||
|
||||
#include "BoatDataCalibration.h"
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "OBPRingBuffer.h"
|
||||
#include "Pagedata.h"
|
||||
#include "OBPDataOperations.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
#include <vector>
|
||||
|
||||
static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees
|
||||
@@ -12,7 +13,7 @@ static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians t
|
||||
// Get maximum difference of last <amount> of TWD ringbuffer values to center chart; returns "0" if data is not valid
|
||||
int getCntr(const RingBuffer<int16_t>& windDirHstry, size_t amount)
|
||||
{
|
||||
int minVal = windDirHstry.getMinVal();
|
||||
const int MAX_VAL = windDirHstry.getMaxVal();
|
||||
size_t count = windDirHstry.getCurrentSize();
|
||||
|
||||
if (windDirHstry.isEmpty() || amount <= 0) {
|
||||
@@ -21,11 +22,11 @@ int getCntr(const RingBuffer<int16_t>& windDirHstry, size_t amount)
|
||||
if (amount > count)
|
||||
amount = count;
|
||||
|
||||
int16_t midWndDir, minWndDir, maxWndDir = 0;
|
||||
uint16_t midWndDir, minWndDir, maxWndDir = 0;
|
||||
int wndCenter = 0;
|
||||
|
||||
midWndDir = windDirHstry.getMid(amount);
|
||||
if (midWndDir != INT16_MIN) {
|
||||
if (midWndDir != MAX_VAL) {
|
||||
midWndDir = midWndDir / 1000.0 * radToDeg;
|
||||
wndCenter = int((midWndDir + (midWndDir >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value
|
||||
minWndDir = windDirHstry.getMin(amount) / 1000.0 * radToDeg;
|
||||
@@ -42,10 +43,11 @@ int getCntr(const RingBuffer<int16_t>& windDirHstry, size_t amount)
|
||||
int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount)
|
||||
{
|
||||
int minVal = windDirHstry.getMinVal();
|
||||
const int MAX_VAL = windDirHstry.getMaxVal();
|
||||
size_t count = windDirHstry.getCurrentSize();
|
||||
|
||||
if (windDirHstry.isEmpty() || amount <= 0) {
|
||||
return minVal;
|
||||
return MAX_VAL;
|
||||
}
|
||||
if (amount > count)
|
||||
amount = count;
|
||||
@@ -57,8 +59,8 @@ int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount)
|
||||
for (size_t i = 0; i < amount; i++) {
|
||||
value = windDirHstry.get(count - 1 - i);
|
||||
|
||||
if (value == minVal) {
|
||||
continue;
|
||||
if (value == MAX_VAL) {
|
||||
continue; // ignore invalid values
|
||||
}
|
||||
|
||||
value = value / 1000.0 * radToDeg;
|
||||
@@ -70,7 +72,7 @@ int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount)
|
||||
maxRng = 180;
|
||||
}
|
||||
|
||||
return maxRng;
|
||||
return (maxRng != minVal ? maxRng : MAX_VAL);
|
||||
}
|
||||
|
||||
// ****************************************************************
|
||||
@@ -79,6 +81,8 @@ private:
|
||||
bool keylock = false; // Keylock
|
||||
char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both
|
||||
bool showTruW = true; // Show true wind or apparant wind in chart area
|
||||
bool oldShowTruW = false; // remember recent user selection of wind data type
|
||||
|
||||
int dataIntv = 1; // Update interval for wind history chart:
|
||||
// (1)|(2)|(3)|(4) seconds for approx. 4, 8, 12, 16 min. history chart
|
||||
|
||||
@@ -163,31 +167,25 @@ public:
|
||||
showTruW = false; // Wind source is apparant wind
|
||||
}
|
||||
commonData->logger->logDebug(GwLog::LOG,"New PageWindPlot: wind source=%s", wndSrc);
|
||||
|
||||
#endif
|
||||
};
|
||||
oldShowTruW = !showTruW; // makes wind source being initialized at initial page call
|
||||
}
|
||||
|
||||
int displayPage(PageData& pageData) {
|
||||
|
||||
static RingBuffer<int16_t>* wdHstry; // Wind direction data buffer
|
||||
static RingBuffer<int16_t>* wsHstry; // Wind speed data buffer
|
||||
static RingBuffer<uint16_t>* wsHstry; // Wind speed data buffer
|
||||
static String wdName, wdFormat; // Wind direction name and format
|
||||
static String wsName, wsFormat; // Wind speed name and format
|
||||
static int updFreq; // Update frequency for wind direction
|
||||
static int16_t wdLowest, wdHighest; // Wind direction range
|
||||
static int16_t wdMAX_VAL; // Max. value of wd history buffer, indicating invalid values
|
||||
float wsValue; // Wind speed value in chart area
|
||||
String wsUnit; // Wind speed unit in chart area
|
||||
static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater
|
||||
|
||||
// current boat data values; TWD/AWD only for validation test, TWS/AWS for display of current value
|
||||
const int numBoatData = 4;
|
||||
// current boat data values; TWD/AWD only for validation test
|
||||
const int numBoatData = 2;
|
||||
GwApi::BoatValue* bvalue;
|
||||
String BDataName[numBoatData];
|
||||
double BDataValue[numBoatData];
|
||||
bool BDataValid[numBoatData];
|
||||
String BDataText[numBoatData];
|
||||
String BDataUnit[numBoatData];
|
||||
String BDataFormat[numBoatData];
|
||||
|
||||
static bool isInitialized = false; // Flag to indicate that page is initialized
|
||||
static bool wndDataValid = false; // Flag to indicate if wind data is valid
|
||||
@@ -208,7 +206,6 @@ public:
|
||||
static size_t lastIdx; // Last index of TWD history buffer
|
||||
static size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added
|
||||
static int oldDataIntv; // remember recent user selection of data interval
|
||||
static bool oldShowTruW; // remember recent user selection of wind data type
|
||||
|
||||
static int wndCenter; // chart wind center value position
|
||||
static int wndLeft; // chart wind left value position
|
||||
@@ -225,6 +222,7 @@ public:
|
||||
static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line
|
||||
|
||||
logger->logDebug(GwLog::LOG, "Display PageWindPlot");
|
||||
ulong timer = millis();
|
||||
|
||||
if (!isInitialized) {
|
||||
width = epd->width();
|
||||
@@ -234,18 +232,9 @@ public:
|
||||
numNoData = 0;
|
||||
bufStart = 0;
|
||||
oldDataIntv = 0;
|
||||
oldShowTruW = false; // we want to initialize wind buffers at 1st time routine runs
|
||||
wdHstry = pageData.boatHstry.twdHstry;
|
||||
bufSize = wdHstry->getCapacity();
|
||||
wsHstry = pageData.boatHstry.twsHstry;
|
||||
bufSize = wsHstry->getCapacity();
|
||||
wdHstry->getMetaData(wdName, wdFormat, updFreq, wdLowest, wdHighest);
|
||||
wsHstry->getMetaData(wsName, wsFormat, updFreq, wdLowest, wdHighest);
|
||||
wsValue = 0;
|
||||
wsBVal->setFormat(wsHstry->getFormat());
|
||||
numAddedBufVals, currIdx, lastIdx = 0;
|
||||
lastAddedIdx = wdHstry->getLastIdx();
|
||||
wndCenter = INT_MIN;
|
||||
wndCenter = INT_MAX;
|
||||
midWndDir = 0;
|
||||
diffRng = dfltRng;
|
||||
chrtRng = dfltRng;
|
||||
@@ -256,14 +245,7 @@ public:
|
||||
// read boat data values; TWD only for validation test, TWS for display of current value
|
||||
for (int i = 0; i < numBoatData; i++) {
|
||||
bvalue = pageData.values[i];
|
||||
BDataName[i] = xdrDelete(bvalue->getName());
|
||||
BDataName[i] = BDataName[i].substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue, logger); // Check if boat data value is to be calibrated
|
||||
BDataValue[i] = bvalue->value; // Value as double in SI unit
|
||||
BDataValid[i] = bvalue->valid;
|
||||
BDataText[i] = commonData->fmt->formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
BDataUnit[i] = commonData->fmt->formatValue(bvalue, *commonData).unit;
|
||||
BDataFormat[i] = bvalue->getFormat(); // Unit of value
|
||||
}
|
||||
|
||||
// Optical warning by limit violation (unused)
|
||||
@@ -274,16 +256,18 @@ public:
|
||||
|
||||
if (showTruW != oldShowTruW) {
|
||||
if (showTruW) {
|
||||
wdHstry = pageData.boatHstry.twdHstry;
|
||||
wsHstry = pageData.boatHstry.twsHstry;
|
||||
wdHstry = pageData.boatHstry->hstryBufList.twdHstry;
|
||||
wsHstry = pageData.boatHstry->hstryBufList.twsHstry;
|
||||
} else {
|
||||
wdHstry = pageData.boatHstry.awdHstry;
|
||||
wsHstry = pageData.boatHstry.awsHstry;
|
||||
wdHstry = pageData.boatHstry->hstryBufList.awdHstry;
|
||||
wsHstry = pageData.boatHstry->hstryBufList.awsHstry;
|
||||
}
|
||||
wdHstry->getMetaData(wdName, wdFormat, updFreq, wdLowest, wdHighest);
|
||||
wsHstry->getMetaData(wsName, wsFormat, updFreq, wdLowest, wdHighest);
|
||||
wdHstry->getMetaData(wdName, wdFormat);
|
||||
wsHstry->getMetaData(wsName, wsFormat);
|
||||
wdMAX_VAL = wdHstry->getMaxVal();
|
||||
bufSize = wdHstry->getCapacity();
|
||||
wsBVal->setFormat(wsHstry->getFormat());
|
||||
lastAddedIdx = wdHstry->getLastIdx();
|
||||
|
||||
oldShowTruW = showTruW;
|
||||
}
|
||||
@@ -306,19 +290,19 @@ public:
|
||||
bufStart = max(0, bufStart - numAddedBufVals);
|
||||
}
|
||||
}
|
||||
logger->logDebug(GwLog::DEBUG, "PageWindPlot Dataset: count: %d, xWD: %.0f, xWS: %.1f, xWD_valid? %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d, wind source: %s",
|
||||
count, wdHstry->getLast() / 1000.0 * radToDeg, wsHstry->getLast() / 10.0 * 1.94384, BDataValid[0], intvBufSize, numWndVals, bufStart, numAddedBufVals, wdHstry->getLastIdx(),
|
||||
logger->logDebug(GwLog::DEBUG, "PageWindPlot Dataset: count: %d, xWD: %.1f, xWS: %.2f, xWD_valid? %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d, wind source: %s",
|
||||
count, wdHstry->getLast() / 1000.0 * radToDeg, wsHstry->getLast() / 1000.0 * 1.94384, BDataValid[0], intvBufSize, numWndVals, bufStart, numAddedBufVals, wdHstry->getLastIdx(),
|
||||
showTruW ? "True" : "App");
|
||||
|
||||
// Set wndCenter from 1st real buffer value
|
||||
if (wndCenter == INT_MIN || (wndCenter == 0 && count == 1)) {
|
||||
if (wndCenter == INT_MAX || (wndCenter == 0 && count == 1)) {
|
||||
wndCenter = getCntr(*wdHstry, numWndVals);
|
||||
logger->logDebug(GwLog::DEBUG, "PageWindPlot Range Init: count: %d, xWD: %.0f, wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", count, wdHstry->getLast() / 1000.0 * radToDeg,
|
||||
logger->logDebug(GwLog::DEBUG, "PageWindPlot Range Init: count: %d, xWD: %.1f, wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", count, wdHstry->getLast() / 1000.0 * radToDeg,
|
||||
wndCenter, diffRng, chrtRng, wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 1000.0 * radToDeg);
|
||||
} else {
|
||||
// check and adjust range between left, center, and right chart limit
|
||||
diffRng = getRng(*wdHstry, wndCenter, numWndVals);
|
||||
diffRng = (diffRng == INT16_MIN ? 0 : diffRng);
|
||||
diffRng = (diffRng == wdMAX_VAL ? 0 : diffRng);
|
||||
if (diffRng > chrtRng) {
|
||||
chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value
|
||||
} else if (diffRng + 10 < chrtRng) { // Reduce chart range for higher resolution if possible
|
||||
@@ -366,11 +350,11 @@ public:
|
||||
epd->drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // <degree> symbol
|
||||
epd->drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // <degree> symbol
|
||||
|
||||
if (wdHstry->getMax() == wdHstry->getMinVal()) {
|
||||
// only <INT16_MIN> values in buffer -> no valid wind data available
|
||||
if (wdHstry->getMax() == wdMAX_VAL) {
|
||||
// only <MAX_VAL> values in buffer -> no valid wind data available
|
||||
wndDataValid = false;
|
||||
} else if (!BDataValid[0] && !simulation) {
|
||||
// currently no valid TWD data available and no simulation mode
|
||||
// currently no valid xWD data available and no simulation mode
|
||||
numNoData++;
|
||||
wndDataValid = true;
|
||||
if (numNoData > 3) {
|
||||
@@ -386,8 +370,8 @@ public:
|
||||
if (wndDataValid) {
|
||||
for (int i = 0; i < (numWndVals / dataIntv); i++) {
|
||||
chrtVal = static_cast<int>(wdHstry->get(bufStart + (i * dataIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer
|
||||
if (chrtVal == INT16_MIN) {
|
||||
chrtPrevVal = INT16_MIN;
|
||||
if (chrtVal == wdMAX_VAL) {
|
||||
chrtPrevVal = wdMAX_VAL;
|
||||
} else {
|
||||
chrtVal = static_cast<int>((chrtVal / 1000.0 * radToDeg) + 0.5); // Convert to degrees and round
|
||||
x = ((chrtVal - wndLeft + 360) % 360) * chrtScl;
|
||||
@@ -396,7 +380,7 @@ public:
|
||||
if (i >= (numWndVals / dataIntv) - 1) // log chart data of 1 line (adjust for test purposes)
|
||||
logger->logDebug(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d, count: %d, linesToShow: %d", i, chrtVal, bufStart, count, (numWndVals / dataIntv));
|
||||
|
||||
if ((i == 0) || (chrtPrevVal == INT16_MIN)) {
|
||||
if ((i == 0) || (chrtPrevVal == wdMAX_VAL)) {
|
||||
// just a dot for 1st chart point or after some invalid values
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
@@ -457,13 +441,15 @@ public:
|
||||
lastZone = currentZone;
|
||||
|
||||
wsValue = wsHstry->getLast();
|
||||
wsBVal->value = wsValue; // temp variable to retreive data unit from OBP60Formater
|
||||
wsBVal->valid = (static_cast<int16_t>(wsValue) != wsHstry->getMinVal());
|
||||
wsBVal->value = wsValue / 1000.0; // temp variable to retreive data unit from OBP60Formater
|
||||
wsBVal->valid = (static_cast<uint16_t>(wsValue) != wsHstry->getMinVal());
|
||||
String swsValue = commonData->fmt->formatValue(wsBVal, *commonData).svalue; // value (string)
|
||||
wsUnit = commonData->fmt->formatValue(wsBVal, *commonData).unit; // Unit of value
|
||||
epd->fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value
|
||||
epd->setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||
epd->setCursor(xPosTws, yPosTws);
|
||||
if (!wsBVal->valid) {
|
||||
epd->print(swsValue); // Value
|
||||
/* if (!wsBVal->valid) {
|
||||
epd->print("--.-");
|
||||
} else {
|
||||
wsValue = wsValue / 10.0 * 1.94384; // Wind speed value in knots
|
||||
@@ -472,7 +458,7 @@ public:
|
||||
} else {
|
||||
epd->printf("%4.1f", wsValue); // Value, round to 1 decimal
|
||||
}
|
||||
}
|
||||
} */
|
||||
epd->setFont(&Ubuntu_Bold12pt8b);
|
||||
epd->setCursor(xPosTws + 82, yPosTws - 14);
|
||||
epd->print(wsName); // Name
|
||||
@@ -507,6 +493,7 @@ public:
|
||||
epd->printf("%3d", chrtLbl); // Wind value label
|
||||
}
|
||||
|
||||
logger->logDebug(GwLog::DEBUG, "PageWindPlot time: %ld", millis() - timer);
|
||||
return PAGE_UPDATE;
|
||||
};
|
||||
};
|
||||
@@ -525,7 +512,7 @@ PageDescription registerPageWindPlot(
|
||||
"WindPlot", // Page name
|
||||
createPage, // Action
|
||||
0, // Number of bus values depends on selection in Web configuration
|
||||
{ "TWD", "TWS", "AWD", "AWS" }, // Bus values we need in the page
|
||||
{ "TWD", "AWD"}, // Bus values we need in the page
|
||||
true // Show display header on/off
|
||||
);
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
#include "BoatDataCalibration.h"
|
||||
#endif
|
||||
|
||||
class PageWindRose : public Page
|
||||
{
|
||||
@@ -42,127 +45,108 @@ public:
|
||||
|
||||
int displayPage(PageData &pageData) {
|
||||
|
||||
static String svalue1old = "";
|
||||
static String unit1old = "";
|
||||
static String svalue2old = "";
|
||||
static String unit2old = "";
|
||||
static String svalue3old = "";
|
||||
static String unit3old = "";
|
||||
static String svalue4old = "";
|
||||
static String unit4old = "";
|
||||
static String svalue5old = "";
|
||||
static String unit5old = "";
|
||||
static String svalue6old = "";
|
||||
static String unit6old = "";
|
||||
// storage for hold valued
|
||||
static FormattedData bvf_awa_old;
|
||||
static FormattedData bvf_aws_old;
|
||||
static FormattedData bvf_twd_old;
|
||||
static FormattedData bvf_tws_old;
|
||||
static FormattedData bvf_dbt_old;
|
||||
static FormattedData bvf_stw_old;
|
||||
|
||||
// Get boat value for AWA
|
||||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
value1 = commonData->fmt->formatValue(bvalue1, *commonData).value;// Format only nesaccery for simulation data for pointer
|
||||
String svalue1 = commonData->fmt->formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
String unit1 = commonData->fmt->formatValue(bvalue1, *commonData).unit; // Unit of value
|
||||
if(valid1 == true){
|
||||
svalue1old = svalue1; // Save old value
|
||||
unit1old = unit1; // Save old unit
|
||||
GwApi::BoatValue *bv_awa = pageData.values[0]; // First element in list
|
||||
String name_awa = xdrDelete(bv_awa->getName(), 6); // get name without prefix and limit length
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bv_awa, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
FormattedData bvf_awa = commonData->fmt->formatValue(bv_awa, *commonData);
|
||||
if (bv_awa->valid) { // Save formatted data for hold feature
|
||||
bvf_awa_old = bvf_awa;
|
||||
}
|
||||
|
||||
// Get boat value for AWS
|
||||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = commonData->fmt->formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
String unit2 = commonData->fmt->formatValue(bvalue2, *commonData).unit; // Unit of value
|
||||
if(valid2 == true){
|
||||
svalue2old = svalue2; // Save old value
|
||||
unit2old = unit2; // Save old unit
|
||||
GwApi::BoatValue *bv_aws = pageData.values[1]; // Second element in list
|
||||
String name_aws = xdrDelete(bv_aws->getName(), 6); // get name without prefix and limit length
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bv_aws, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
FormattedData bvf_aws = commonData->fmt->formatValue(bv_aws, *commonData);
|
||||
if (bv_aws->valid) { // Save formatted data for hold feature
|
||||
bvf_aws_old = bvf_aws;
|
||||
}
|
||||
|
||||
// Get boat value for TWD
|
||||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
|
||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||
double value3 = bvalue3->value; // Value as double in SI unit
|
||||
bool valid3 = bvalue3->valid; // Valid information
|
||||
String svalue3 = commonData->fmt->formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
String unit3 = commonData->fmt->formatValue(bvalue3, *commonData).unit; // Unit of value
|
||||
if(valid3 == true){
|
||||
svalue3old = svalue3; // Save old value
|
||||
unit3old = unit3; // Save old unit
|
||||
GwApi::BoatValue *bv_twd = pageData.values[2]; // Third element in list
|
||||
String name_twd = xdrDelete(bv_twd->getName(), 6); // get name without prefix and limit length
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bv_twd, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
FormattedData bvf_twd = commonData->fmt->formatValue(bv_twd, *commonData);
|
||||
if (bv_twd->valid) { // Save formatted data for hold feature
|
||||
bvf_twd_old = bvf_twd;
|
||||
}
|
||||
|
||||
// Get boat value for TWS
|
||||
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Fourth element in list
|
||||
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||
name4 = name4.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
||||
double value4 = bvalue4->value; // Value as double in SI unit
|
||||
bool valid4 = bvalue4->valid; // Valid information
|
||||
String svalue4 = commonData->fmt->formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
String unit4 = commonData->fmt->formatValue(bvalue4, *commonData).unit; // Unit of value
|
||||
if(valid4 == true){
|
||||
svalue4old = svalue4; // Save old value
|
||||
unit4old = unit4; // Save old unit
|
||||
GwApi::BoatValue *bv_tws = pageData.values[3]; // Fourth element in list
|
||||
String name_tws = xdrDelete(bv_tws->getName(), 6); // get name without prefix and limit length
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bv_tws, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
FormattedData bvf_tws = commonData->fmt->formatValue(bv_tws, *commonData);
|
||||
if (bv_tws->valid) { // Save formatted data for hold feature
|
||||
bvf_tws_old = bvf_tws;
|
||||
}
|
||||
|
||||
// Get boat value for DBT
|
||||
GwApi::BoatValue *bvalue5 = pageData.values[4]; // Fifth element in list
|
||||
String name5 = xdrDelete(bvalue5->getName()); // Value name
|
||||
name5 = name5.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
|
||||
double value5 = bvalue5->value; // Value as double in SI unit
|
||||
bool valid5 = bvalue5->valid; // Valid information
|
||||
String svalue5 = commonData->fmt->formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
String unit5 = commonData->fmt->formatValue(bvalue5, *commonData).unit; // Unit of value
|
||||
if(valid5 == true){
|
||||
svalue5old = svalue5; // Save old value
|
||||
unit5old = unit5; // Save old unit
|
||||
GwApi::BoatValue *bv_dbt = pageData.values[4]; // Fifth element in list
|
||||
String name_dbt = xdrDelete(bv_dbt->getName(), 6); // get name without prefix and limit length
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bv_dbt, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
FormattedData bvf_dbt = commonData->fmt->formatValue(bv_dbt, *commonData);
|
||||
if (bv_dbt->valid) { // Save formatted data for hold feature
|
||||
bvf_dbt_old = bvf_dbt;
|
||||
}
|
||||
|
||||
// Get boat value for STW
|
||||
GwApi::BoatValue *bvalue6 = pageData.values[5]; // Sixth element in list
|
||||
String name6 = xdrDelete(bvalue6->getName()); // Value name
|
||||
name6 = name6.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
|
||||
double value6 = bvalue6->value; // Value as double in SI unit
|
||||
bool valid6 = bvalue6->valid; // Valid information
|
||||
String svalue6 = commonData->fmt->formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
String unit6 = commonData->fmt->formatValue(bvalue6, *commonData).unit; // Unit of value
|
||||
if(valid6 == true){
|
||||
svalue6old = svalue6; // Save old value
|
||||
unit6old = unit6; // Save old unit
|
||||
GwApi::BoatValue *bv_stw = pageData.values[5]; // Sixth element in list
|
||||
String name_stw = xdrDelete(bv_stw->getName(), 6); // get name without prefix and limit length
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bv_stw, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
FormattedData bvf_stw = commonData->fmt->formatValue(bv_stw, *commonData);
|
||||
if (bv_stw->valid) { // Save formatted data for hold feature
|
||||
bvf_stw_old = bvf_stw;
|
||||
}
|
||||
|
||||
// Logging boat values
|
||||
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||
logger->logDebug(GwLog::LOG, "Drawing at PageWindRose, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, name6.c_str(), value6);
|
||||
// Log boat values
|
||||
logger->logDebug(GwLog::LOG, "Drawing at PageWindRose, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f",
|
||||
name_awa.c_str(), bv_awa->value,
|
||||
name_aws.c_str(), bv_aws->value,
|
||||
name_twd.c_str(), bv_twd->value,
|
||||
name_tws.c_str(), bv_tws->value,
|
||||
name_dbt.c_str(), bv_dbt->value,
|
||||
name_stw.c_str(), bv_stw->value);
|
||||
|
||||
// Draw page
|
||||
//***********************************************************
|
||||
// *********************************************************************
|
||||
|
||||
// Set display in partial refresh mode
|
||||
epd->setPartialWindow(0, 0, epd->width(), epd->height()); // Set partial update
|
||||
epd->setPartialWindow(0, 0, epd->width(), epd->height());
|
||||
|
||||
epd->setTextColor(commonData->fgcolor);
|
||||
|
||||
// Show values AWA
|
||||
epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||
epd->setCursor(10, 65);
|
||||
epd->print(svalue1); // Value
|
||||
epd->print(holdvalues ? bvf_awa_old.value : bvf_awa.value);
|
||||
epd->setFont(&Ubuntu_Bold12pt8b);
|
||||
epd->setCursor(10, 95);
|
||||
epd->print(name1); // Name
|
||||
epd->print(name_awa);
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setCursor(10, 115);
|
||||
epd->print(" ");
|
||||
epd->print(holdvalues ? unit1old : unit1);
|
||||
epd->print(holdvalues ? bvf_awa_old.unit : bvf_awa.unit);
|
||||
|
||||
// Horizintal separator left
|
||||
epd->fillRect(0, 149, 60, 3, commonData->fgcolor);
|
||||
@@ -170,31 +154,32 @@ public:
|
||||
// Show values AWS
|
||||
epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||
epd->setCursor(10, 270);
|
||||
epd->print(svalue2); // Value
|
||||
epd->print(holdvalues ? bvf_aws_old.value : bvf_aws.value);
|
||||
epd->setFont(&Ubuntu_Bold12pt8b);
|
||||
epd->setCursor(10, 220);
|
||||
epd->print(name2); // Name
|
||||
epd->print(name_aws);
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setCursor(10, 190);
|
||||
epd->print(" ");
|
||||
epd->print(holdvalues ? unit2old : unit2);
|
||||
epd->print(holdvalues ? bvf_aws_old.unit : bvf_aws.unit);
|
||||
|
||||
// Show values TWD
|
||||
// Show value TWD
|
||||
epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||
epd->setCursor(295, 65);
|
||||
if(valid3 == true){
|
||||
epd->print(abs(value3 * 180 / PI), 0); // Value
|
||||
// TODO WTF? Der Formatter sollte das korrekt machen
|
||||
if (bv_twd->valid) {
|
||||
epd->print(abs(bv_twd->value * 180 / PI), 0); // Value
|
||||
}
|
||||
else{
|
||||
epd->print("---"); // Value
|
||||
else {
|
||||
epd->print(commonData->fmt->placeholder);
|
||||
}
|
||||
epd->setFont(&Ubuntu_Bold12pt8b);
|
||||
epd->setCursor(335, 95);
|
||||
epd->print(name3); // Name
|
||||
epd->print(name_twd); // Name
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setCursor(335, 115);
|
||||
epd->print(" ");
|
||||
epd->print(holdvalues ? unit3old : unit3);
|
||||
epd->print(holdvalues ? bvf_twd_old.unit : bvf_twd.unit);
|
||||
|
||||
// Horizintal separator right
|
||||
epd->fillRect(340, 149, 80, 3, commonData->fgcolor);
|
||||
@@ -202,16 +187,16 @@ public:
|
||||
// Show values TWS
|
||||
epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||
epd->setCursor(295, 270);
|
||||
epd->print(svalue4); // Value
|
||||
epd->print(name_tws);
|
||||
epd->setFont(&Ubuntu_Bold12pt8b);
|
||||
epd->setCursor(335, 220);
|
||||
epd->print(name4); // Name
|
||||
epd->print(holdvalues ? bvf_tws_old.value : bvf_tws.value);
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setCursor(335, 190);
|
||||
epd->print(" ");
|
||||
epd->print(holdvalues ? unit4old : unit4);
|
||||
epd->print(holdvalues ? bvf_tws_old.unit : bvf_tws.unit);
|
||||
|
||||
//*******************************************************************************************
|
||||
// *********************************************************************
|
||||
|
||||
// Draw wind rose
|
||||
int rInstrument = 110; // Radius of grafic instrument
|
||||
@@ -250,7 +235,7 @@ public:
|
||||
epd->getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
|
||||
epd->setCursor(x-w/2, y+h/2);
|
||||
if (i % 30 == 0) {
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setFont(&Ubuntu_Bold8pt8b); // TODO move out of loop
|
||||
epd->print(ii);
|
||||
}
|
||||
|
||||
@@ -279,9 +264,9 @@ public:
|
||||
|
||||
// Draw wind pointer
|
||||
float startwidth = 8; // Start width of pointer
|
||||
if(valid2 == true || holdvalues == true || simulation == true){
|
||||
float sinx=sin(value1); // Wind direction
|
||||
float cosx=cos(value1);
|
||||
if (bv_aws->valid || holdvalues || simulation) {
|
||||
float sinx = sin(bv_awa->value); // Wind direction
|
||||
float cosx = cos(bv_awa->value);
|
||||
// Normal pointer
|
||||
// Pointer as triangle with center base 2*width
|
||||
float xx1 = -startwidth;
|
||||
@@ -307,28 +292,33 @@ public:
|
||||
epd->fillCircle(200, 150, startwidth + 6, commonData->bgcolor);
|
||||
epd->fillCircle(200, 150, startwidth + 4, commonData->fgcolor);
|
||||
|
||||
//*******************************************************************************************
|
||||
// *********************************************************************
|
||||
|
||||
// Show values DBT
|
||||
// Show value DBT
|
||||
epd->setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||
epd->setCursor(160, 200);
|
||||
epd->print(svalue5); // Value
|
||||
epd->print(holdvalues ? bvf_dbt_old.value : bvf_dbt.value);
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setCursor(190, 215);
|
||||
epd->print(" ");
|
||||
epd->print(holdvalues ? unit5old : unit5);
|
||||
epd->print(holdvalues ? bvf_dbt_old.unit : bvf_dbt.unit);
|
||||
|
||||
// Show values STW
|
||||
// Show value STW
|
||||
epd->setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||
epd->setCursor(160, 130);
|
||||
epd->print(svalue6); // Value
|
||||
epd->print(holdvalues ? bvf_stw_old.value : bvf_stw.value);
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setCursor(190, 90);
|
||||
epd->print(" ");
|
||||
epd->print(holdvalues ? unit6old : unit6);
|
||||
epd->print(holdvalues ? bvf_stw_old.unit : bvf_stw.unit);
|
||||
|
||||
return PAGE_UPDATE;
|
||||
};
|
||||
|
||||
void leavePage(PageData &pageData) {
|
||||
logger->logDebug(GwLog::LOG, "Leaving PageWindRose");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
static Page *createPage(CommonData &common){
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
#include "BoatDataCalibration.h"
|
||||
#endif
|
||||
|
||||
class PageWindRoseFlex : public Page
|
||||
{
|
||||
@@ -73,7 +76,9 @@ public:
|
||||
}
|
||||
String name1 = bvalue1->getName().c_str(); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = commonData->fmt->formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -91,7 +96,9 @@ public:
|
||||
}
|
||||
String name2 = bvalue2->getName().c_str(); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
if (simulation) {
|
||||
@@ -108,7 +115,9 @@ public:
|
||||
GwApi::BoatValue *bvalue3 = pageData.values[0];
|
||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value3 = bvalue3->value; // Value as double in SI unit
|
||||
bool valid3 = bvalue3->valid; // Valid information
|
||||
String svalue3 = commonData->fmt->formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -122,7 +131,9 @@ public:
|
||||
GwApi::BoatValue *bvalue4 = pageData.values[1];
|
||||
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||
name4 = name4.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value4 = bvalue4->value; // Value as double in SI unit
|
||||
bool valid4 = bvalue4->valid; // Valid information
|
||||
String svalue4 = commonData->fmt->formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -136,7 +147,9 @@ public:
|
||||
GwApi::BoatValue *bvalue5 = pageData.values[2];
|
||||
String name5 = xdrDelete(bvalue5->getName()); // Value name
|
||||
name5 = name5.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value5 = bvalue5->value; // Value as double in SI unit
|
||||
bool valid5 = bvalue5->valid; // Valid information
|
||||
String svalue5 = commonData->fmt->formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -150,7 +163,9 @@ public:
|
||||
GwApi::BoatValue *bvalue6 = pageData.values[3];
|
||||
String name6 = xdrDelete(bvalue6->getName()); // Value name
|
||||
name6 = name6.substring(0, 6); // String length limit for value name
|
||||
#ifdef ENABLE_CALIBRATION
|
||||
calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
|
||||
#endif
|
||||
double value6 = bvalue6->value; // Value as double in SI unit
|
||||
bool valid6 = bvalue6->valid; // Valid information
|
||||
String svalue6 = commonData->fmt->formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -207,7 +222,7 @@ public:
|
||||
epd->print(svalue4); // Value
|
||||
}
|
||||
else{
|
||||
epd->print("---"); // Value
|
||||
epd->print(commonData->fmt->placeholder);
|
||||
}
|
||||
epd->setFont(&Ubuntu_Bold12pt8b);
|
||||
epd->setCursor(335, 95);
|
||||
@@ -331,7 +346,8 @@ public:
|
||||
//*******************************************************************************************
|
||||
|
||||
// Show value6 (=fourth user-configured parameter) and ssource, so that they do not collide with the wind pointer
|
||||
if (cos(value1) > 0) {
|
||||
if (cos(value1) > 0) {
|
||||
// pointer points upwards
|
||||
epd->setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||
epd->setCursor(160, 200);
|
||||
epd->print(svalue6); // Value
|
||||
@@ -346,7 +362,8 @@ public:
|
||||
}
|
||||
epd->print(ssource); // true or app.
|
||||
}
|
||||
else {
|
||||
else {
|
||||
// pointer points downwards
|
||||
epd->setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||
epd->setCursor(160, 130);
|
||||
epd->print(svalue6);
|
||||
@@ -355,11 +372,11 @@ public:
|
||||
epd->print(" ");
|
||||
epd->print(holdvalues ? unit6old : unit6);
|
||||
if (sin(value1) > 0) {
|
||||
epd->setCursor(160, 130);
|
||||
epd->setCursor(160, 200);
|
||||
} else {
|
||||
epd->setCursor(220, 130);
|
||||
epd->setCursor(220, 200);
|
||||
}
|
||||
epd->print(ssource); //true or app.
|
||||
epd->print(ssource); //true or app.
|
||||
}
|
||||
|
||||
return PAGE_UPDATE;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include "LedSpiTask.h"
|
||||
#include "OBPRingBuffer.h"
|
||||
#include "OBPDataOperations.h"
|
||||
|
||||
#define MAX_PAGE_NUMBER 10 // Max number of pages for show data
|
||||
@@ -19,7 +18,7 @@ typedef struct{
|
||||
uint8_t pageNumber; // page number in sequence of visible pages
|
||||
//the values will always contain the user defined values first
|
||||
ValueList values;
|
||||
tBoatHstryData boatHstry;
|
||||
HstryBuf* boatHstry;
|
||||
} PageData;
|
||||
|
||||
// Sensor data structure (only for extended sensors, not for NMEA bus sensors)
|
||||
@@ -128,6 +127,7 @@ typedef struct{
|
||||
//a base class that all pages must inherit from
|
||||
class Page{
|
||||
protected:
|
||||
// TODO Future: GwApi *api;
|
||||
CommonData *commonData;
|
||||
GwConfigHandler *config;
|
||||
GwLog *logger;
|
||||
@@ -208,7 +208,7 @@ class PageDescription{
|
||||
|
||||
class PageStruct{
|
||||
public:
|
||||
Page *page=NULL;
|
||||
Page *page = nullptr;
|
||||
PageData parameters;
|
||||
PageDescription *description=NULL;
|
||||
PageDescription *description = nullptr;
|
||||
};
|
||||
|
||||
@@ -8,9 +8,10 @@ Coding style
|
||||
------------
|
||||
WIP
|
||||
Please format your new code the same as already existing code.
|
||||
Preprocessor directives go to column zero.
|
||||
|
||||
Identation is 4 spaces
|
||||
Some rules:
|
||||
- Preprocessor directives go to column zero
|
||||
- Identation is 4 spaces
|
||||
|
||||
Git commands
|
||||
------------
|
||||
|
||||
@@ -1368,6 +1368,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -1679,6 +1680,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -1981,6 +1983,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -2274,6 +2277,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -2558,6 +2562,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -2833,6 +2838,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -3099,6 +3105,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -3356,6 +3363,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -3604,6 +3612,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -3843,6 +3852,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
|
||||
@@ -1391,6 +1391,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -1722,6 +1723,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -2044,6 +2046,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -2357,6 +2360,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -2661,6 +2665,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -2956,6 +2961,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -3242,6 +3248,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -3519,6 +3526,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -3787,6 +3795,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
@@ -4046,6 +4055,7 @@
|
||||
"SkyView",
|
||||
"Solar",
|
||||
"ThreeValues",
|
||||
"Tracker",
|
||||
"TwoValues",
|
||||
"Voltage",
|
||||
"WhitePage",
|
||||
|
||||
273
lib/obp60task/fonts/Atari6px8b.h
Normal file
273
lib/obp60task/fonts/Atari6px8b.h
Normal file
@@ -0,0 +1,273 @@
|
||||
const uint8_t Atari6pxBitmaps[] PROGMEM = {
|
||||
0x00, 0xF0, 0x30, 0xCF, 0x38, 0x80, 0x53, 0xF5, 0x14, 0xFD, 0x40, 0x7E,
|
||||
0x47, 0x85, 0x13, 0xE1, 0x00, 0xC7, 0x21, 0x00, 0x4E, 0x30, 0x63, 0x26,
|
||||
0x39, 0xC3, 0x26, 0x40, 0x6F, 0x00, 0x7B, 0x24, 0xC0, 0xCD, 0xA5, 0x80,
|
||||
0x8B, 0x3E, 0x00, 0x44, 0x10, 0x4F, 0xC4, 0x10, 0x40, 0x6F, 0x00, 0xF8,
|
||||
0xF0, 0x0C, 0x66, 0x18, 0x82, 0x00, 0x7A, 0x39, 0x58, 0x61, 0xE0, 0x75,
|
||||
0x50, 0xF8, 0x17, 0xA0, 0x83, 0xF0, 0xF8, 0x17, 0x80, 0x07, 0xE0, 0x39,
|
||||
0x28, 0xA2, 0xFC, 0x20, 0xFE, 0x0F, 0x80, 0x07, 0xE0, 0x7A, 0x0F, 0xC0,
|
||||
0x01, 0xE0, 0xF8, 0x44, 0x02, 0x10, 0x7A, 0x17, 0x80, 0x85, 0xE0, 0x7A,
|
||||
0x17, 0xC0, 0x01, 0xE0, 0xF0, 0xF0, 0xF3, 0x58, 0x1B, 0x30, 0x42, 0x0C,
|
||||
0xF8, 0x3E, 0xC3, 0x06, 0xC4, 0x60, 0x7A, 0x31, 0x86, 0x00, 0x01, 0x80,
|
||||
0x7A, 0x19, 0xE6, 0x82, 0x07, 0xC0, 0x7A, 0x1F, 0xE1, 0x86, 0x10, 0xFA,
|
||||
0x1F, 0xA0, 0x87, 0xE0, 0x7E, 0x08, 0x00, 0x01, 0xF0, 0xFA, 0x18, 0x60,
|
||||
0x83, 0xE0, 0xFE, 0x0F, 0xA0, 0x83, 0xF0, 0xFE, 0x0F, 0xA0, 0x82, 0x00,
|
||||
0x7E, 0x08, 0xC1, 0x05, 0xF0, 0x86, 0x1F, 0xE1, 0x86, 0x10, 0xF4, 0x44,
|
||||
0x4F, 0x04, 0x10, 0x41, 0x85, 0xE0, 0x8C, 0xB9, 0x09, 0x44, 0x84, 0x21,
|
||||
0x08, 0x7C, 0x83, 0xDE, 0x4C, 0x18, 0x30, 0x40, 0x83, 0xC6, 0x4C, 0x18,
|
||||
0xF0, 0x40, 0x7A, 0x18, 0x40, 0x01, 0xE0, 0xFA, 0x1F, 0xA0, 0x82, 0x00,
|
||||
0x7A, 0x18, 0x40, 0x01, 0xE0, 0xC0, 0xFA, 0x1F, 0xA4, 0x92, 0x30, 0x7E,
|
||||
0x07, 0x80, 0x07, 0xE0, 0xFC, 0x41, 0x04, 0x10, 0x40, 0x86, 0x18, 0x61,
|
||||
0x85, 0xE0, 0x86, 0x10, 0x00, 0x48, 0x40, 0x83, 0x06, 0x4C, 0x1E, 0xF0,
|
||||
0x40, 0x85, 0x21, 0x00, 0x4A, 0x10, 0x86, 0x14, 0x80, 0x10, 0x40, 0xFC,
|
||||
0x21, 0x10, 0x43, 0xF0, 0xFC, 0xCC, 0xCF, 0xC1, 0x81, 0x86, 0x04, 0x10,
|
||||
0xF3, 0x33, 0x3F, 0x11, 0xEC, 0xC0, 0xFC, 0xD9, 0x80, 0x78, 0x10, 0x7F,
|
||||
0x7C, 0x83, 0xE8, 0x61, 0x83, 0xE0, 0x7E, 0x08, 0x00, 0x7C, 0x05, 0xF8,
|
||||
0x61, 0x05, 0xF0, 0x7B, 0xF8, 0x00, 0x78, 0x1A, 0x3E, 0x84, 0x20, 0x7E,
|
||||
0x17, 0xC1, 0x07, 0xE0, 0x81, 0x33, 0x8C, 0x18, 0x30, 0x40, 0xC4, 0x44,
|
||||
0xF0, 0x08, 0x42, 0x10, 0x87, 0xC0, 0x82, 0x2F, 0x20, 0x8A, 0x10, 0xC4,
|
||||
0x44, 0x4F, 0x4B, 0xF9, 0x61, 0x84, 0xFA, 0x18, 0x61, 0x84, 0x7A, 0x18,
|
||||
0x40, 0x78, 0xFA, 0x18, 0x60, 0xFA, 0x00, 0x7E, 0x18, 0x41, 0x7C, 0x10,
|
||||
0xF4, 0x61, 0x08, 0x00, 0x7E, 0x00, 0x3F, 0x00, 0x4F, 0x44, 0x41, 0x86,
|
||||
0x10, 0x41, 0x7C, 0x86, 0x10, 0x12, 0x10, 0x83, 0x26, 0x4C, 0x1E, 0xE0,
|
||||
0x8B, 0x18, 0x08, 0x80, 0x86, 0x17, 0xC1, 0x07, 0xE0, 0xF8, 0x94, 0x8F,
|
||||
0x80, 0x76, 0xC6, 0x67, 0xFF, 0xFC, 0xE6, 0x36, 0x6E, 0x41, 0x0B, 0x6F,
|
||||
0x08, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
|
||||
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
|
||||
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
|
||||
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
|
||||
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
|
||||
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
|
||||
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
|
||||
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
|
||||
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
|
||||
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
|
||||
0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD,
|
||||
0xFC, 0x11, 0xEC, 0x71, 0xFC, 0x11, 0xEC, 0xC4, 0x10, 0x41, 0x00, 0x10,
|
||||
0x40, 0x33, 0x49, 0xE1, 0x00, 0x08, 0x1B, 0xD8, 0x40, 0x81, 0x00, 0x10,
|
||||
0xE3, 0x39, 0x01, 0x02, 0x00, 0x7A, 0x5E, 0xC4, 0x11, 0xE0, 0xFF, 0xEF,
|
||||
0x3C, 0xC6, 0x30, 0xEE, 0x57, 0xA1, 0x87, 0xB0, 0x06, 0x1A, 0x64, 0x86,
|
||||
0x08, 0x00, 0x7D, 0x06, 0x4C, 0xD8, 0x30, 0x5F, 0x00, 0x18, 0xF9, 0xF7,
|
||||
0xF0, 0x00, 0x06, 0x00, 0x08, 0x30, 0x9E, 0x7B, 0xE6, 0x00, 0xF1, 0x03,
|
||||
0xBC, 0x48, 0x81, 0x82, 0x00, 0xF1, 0x02, 0x77, 0xA1, 0x43, 0x85, 0x80,
|
||||
0x12, 0x24, 0x48, 0x97, 0x3E, 0x70, 0x80, 0x92, 0x4D, 0xD8, 0xFF, 0x1C,
|
||||
0x73, 0xCF, 0x3F, 0xC0, 0xC4, 0x4F, 0xFF, 0xF0, 0x7C, 0x1F, 0xF0, 0xC3,
|
||||
0x0F, 0xC0, 0xF8, 0x2F, 0xC3, 0x0C, 0x3F, 0xC0, 0xC3, 0x0C, 0xFF, 0x0C,
|
||||
0x30, 0xC0, 0xFB, 0x0F, 0xC3, 0x0C, 0x3F, 0xC0, 0xC3, 0x0F, 0xE3, 0xCF,
|
||||
0x3F, 0xC0, 0xFC, 0x13, 0xCC, 0x30, 0xC3, 0x00, 0x71, 0x47, 0x37, 0xDF,
|
||||
0x7F, 0xC0, 0xFF, 0x3C, 0x7F, 0x0C, 0x30, 0xC0, 0x78, 0x00, 0x7F, 0x78,
|
||||
0xF0, 0x80, 0xEF, 0x88, 0x88, 0xF8, 0x0F, 0x1E, 0xFF, 0x84, 0x08, 0x1E,
|
||||
0x26, 0x00, 0xF3, 0x20, 0xC1, 0x05, 0xF1, 0x40, 0x42, 0x2C, 0x50, 0x40,
|
||||
0x1E, 0xF0, 0x00, 0x07, 0x24, 0x80, 0x93, 0x80, 0xFC, 0x7F, 0xDD, 0xFC,
|
||||
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
|
||||
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
|
||||
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
|
||||
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
|
||||
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
|
||||
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
|
||||
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
|
||||
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
|
||||
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
|
||||
0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC, 0xFC, 0x7F, 0xDD, 0xFC,
|
||||
0xFC, 0x7F, 0xDD, 0xFC };
|
||||
|
||||
const GFXglyph Atari6pxGlyphs[] PROGMEM = {
|
||||
{ 0, 1, 1, 7, 0, 0 }, // 0x20 ' ' U+0020
|
||||
{ 1, 2, 6, 6, 2, -5 }, // 0x21 '!' U+0021
|
||||
{ 3, 6, 3, 7, 0, -5 }, // 0x22 '"' U+0022
|
||||
{ 6, 6, 6, 7, 0, -5 }, // 0x23 '#' U+0023
|
||||
{ 11, 6, 7, 7, 0, -5 }, // 0x24 '$' U+0024
|
||||
{ 17, 6, 6, 7, 0, -5 }, // 0x25 '%' U+0025
|
||||
{ 22, 6, 7, 7, 0, -6 }, // 0x26 '&' U+0026
|
||||
{ 28, 3, 3, 6, 1, -5 }, // 0x27 ''' U+0027
|
||||
{ 30, 3, 6, 6, 1, -5 }, // 0x28 '(' U+0028
|
||||
{ 33, 3, 6, 6, 1, -5 }, // 0x29 ')' U+0029
|
||||
{ 36, 5, 6, 7, 1, -5 }, // 0x2a '*' U+002A
|
||||
{ 40, 6, 6, 7, 0, -5 }, // 0x2b '+' U+002B
|
||||
{ 45, 3, 3, 6, 1, -1 }, // 0x2c ',' U+002C
|
||||
{ 47, 5, 1, 7, 1, -3 }, // 0x2d '-' U+002D
|
||||
{ 48, 2, 2, 6, 2, -1 }, // 0x2e '.' U+002E
|
||||
{ 49, 6, 6, 7, 0, -5 }, // 0x2f '/' U+002F
|
||||
{ 54, 6, 6, 7, 0, -5 }, // 0x30 '0' U+0030
|
||||
{ 59, 2, 6, 7, 3, -5 }, // 0x31 '1' U+0031
|
||||
{ 61, 6, 6, 7, 0, -5 }, // 0x32 '2' U+0032
|
||||
{ 66, 6, 6, 7, 0, -5 }, // 0x33 '3' U+0033
|
||||
{ 71, 6, 6, 7, 0, -5 }, // 0x34 '4' U+0034
|
||||
{ 76, 6, 6, 7, 0, -5 }, // 0x35 '5' U+0035
|
||||
{ 81, 6, 6, 7, 0, -5 }, // 0x36 '6' U+0036
|
||||
{ 86, 5, 6, 7, 0, -5 }, // 0x37 '7' U+0037
|
||||
{ 90, 6, 6, 7, 0, -5 }, // 0x38 '8' U+0038
|
||||
{ 95, 6, 6, 7, 0, -5 }, // 0x39 '9' U+0039
|
||||
{ 100, 2, 6, 6, 2, -5 }, // 0x3a ':' U+003A
|
||||
{ 102, 2, 7, 6, 2, -5 }, // 0x3b ';' U+003B
|
||||
{ 104, 5, 6, 7, 1, -5 }, // 0x3c '<' U+003C
|
||||
{ 108, 5, 3, 7, 1, -4 }, // 0x3d '=' U+003D
|
||||
{ 110, 5, 6, 7, 1, -5 }, // 0x3e '>' U+003E
|
||||
{ 114, 6, 7, 7, 0, -5 }, // 0x3f '?' U+003F
|
||||
{ 120, 6, 7, 7, 0, -5 }, // 0x40 '@' U+0040
|
||||
{ 126, 6, 6, 7, 0, -5 }, // 0x41 'A' U+0041
|
||||
{ 131, 6, 6, 7, 0, -5 }, // 0x42 'B' U+0042
|
||||
{ 136, 6, 6, 7, 0, -5 }, // 0x43 'C' U+0043
|
||||
{ 141, 6, 6, 7, 0, -5 }, // 0x44 'D' U+0044
|
||||
{ 146, 6, 6, 7, 0, -5 }, // 0x45 'E' U+0045
|
||||
{ 151, 6, 6, 7, 0, -5 }, // 0x46 'F' U+0046
|
||||
{ 156, 6, 6, 7, 0, -5 }, // 0x47 'G' U+0047
|
||||
{ 161, 6, 6, 7, 0, -5 }, // 0x48 'H' U+0048
|
||||
{ 166, 4, 6, 7, 1, -5 }, // 0x49 'I' U+0049
|
||||
{ 169, 6, 6, 7, 0, -5 }, // 0x4a 'J' U+004A
|
||||
{ 174, 5, 6, 7, 1, -5 }, // 0x4b 'K' U+004B
|
||||
{ 178, 5, 6, 7, 1, -5 }, // 0x4c 'L' U+004C
|
||||
{ 182, 7, 6, 8, 0, -5 }, // 0x4d 'M' U+004D
|
||||
{ 188, 7, 6, 8, 0, -5 }, // 0x4e 'N' U+004E
|
||||
{ 194, 6, 6, 7, 0, -5 }, // 0x4f 'O' U+004F
|
||||
{ 199, 6, 6, 7, 0, -5 }, // 0x50 'P' U+0050
|
||||
{ 204, 6, 7, 7, 0, -5 }, // 0x51 'Q' U+0051
|
||||
{ 210, 6, 6, 7, 0, -5 }, // 0x52 'R' U+0052
|
||||
{ 215, 6, 6, 7, 0, -5 }, // 0x53 'S' U+0053
|
||||
{ 220, 6, 6, 7, 0, -5 }, // 0x54 'T' U+0054
|
||||
{ 225, 6, 6, 7, 0, -5 }, // 0x55 'U' U+0055
|
||||
{ 230, 6, 6, 7, 0, -5 }, // 0x56 'V' U+0056
|
||||
{ 235, 7, 6, 8, 0, -5 }, // 0x57 'W' U+0057
|
||||
{ 241, 6, 6, 7, 0, -5 }, // 0x58 'X' U+0058
|
||||
{ 246, 6, 6, 7, 0, -5 }, // 0x59 'Y' U+0059
|
||||
{ 251, 6, 6, 7, 0, -5 }, // 0x5a 'Z' U+005A
|
||||
{ 256, 4, 6, 7, 1, -5 }, // 0x5b '[' U+005B
|
||||
{ 259, 6, 6, 7, 0, -5 }, // 0x5c '\' U+005C
|
||||
{ 264, 4, 6, 7, 2, -5 }, // 0x5d ']' U+005D
|
||||
{ 267, 6, 3, 7, 0, -5 }, // 0x5e '^' U+005E
|
||||
{ 270, 6, 1, 7, 0, 0 }, // 0x5f '_' U+005F
|
||||
{ 271, 3, 3, 6, 1, -5 }, // 0x60 '`' U+0060
|
||||
{ 273, 6, 5, 7, 0, -4 }, // 0x61 'a' U+0061
|
||||
{ 277, 6, 6, 7, 0, -5 }, // 0x62 'b' U+0062
|
||||
{ 282, 6, 5, 7, 0, -4 }, // 0x63 'c' U+0063
|
||||
{ 286, 6, 6, 7, 0, -5 }, // 0x64 'd' U+0064
|
||||
{ 291, 6, 5, 7, 0, -4 }, // 0x65 'e' U+0065
|
||||
{ 295, 5, 6, 7, 1, -5 }, // 0x66 'f' U+0066
|
||||
{ 299, 6, 6, 7, 0, -4 }, // 0x67 'g' U+0067
|
||||
{ 304, 7, 6, 8, 0, -5 }, // 0x68 'h' U+0068
|
||||
{ 310, 4, 5, 7, 1, -4 }, // 0x69 'i' U+0069
|
||||
{ 313, 5, 7, 7, 0, -5 }, // 0x6a 'j' U+006A
|
||||
{ 318, 6, 6, 7, 0, -5 }, // 0x6b 'k' U+006B
|
||||
{ 323, 4, 6, 7, 1, -5 }, // 0x6c 'l' U+006C
|
||||
{ 326, 6, 5, 7, 0, -4 }, // 0x6d 'm' U+006D
|
||||
{ 330, 6, 5, 7, 0, -4 }, // 0x6e 'n' U+006E
|
||||
{ 334, 6, 5, 7, 0, -4 }, // 0x6f 'o' U+006F
|
||||
{ 338, 6, 6, 7, 0, -4 }, // 0x70 'p' U+0070
|
||||
{ 343, 6, 6, 7, 0, -4 }, // 0x71 'q' U+0071
|
||||
{ 348, 5, 5, 7, 1, -4 }, // 0x72 'r' U+0072
|
||||
{ 352, 5, 5, 7, 1, -4 }, // 0x73 's' U+0073
|
||||
{ 356, 4, 6, 7, 1, -5 }, // 0x74 't' U+0074
|
||||
{ 359, 6, 5, 7, 0, -4 }, // 0x75 'u' U+0075
|
||||
{ 363, 6, 5, 7, 0, -4 }, // 0x76 'v' U+0076
|
||||
{ 367, 7, 5, 8, 0, -4 }, // 0x77 'w' U+0077
|
||||
{ 372, 5, 5, 7, 0, -4 }, // 0x78 'x' U+0078
|
||||
{ 376, 6, 6, 7, 0, -4 }, // 0x79 'y' U+0079
|
||||
{ 381, 5, 5, 7, 1, -4 }, // 0x7a 'z' U+007A
|
||||
{ 385, 4, 6, 6, 1, -5 }, // 0x7b '{' U+007B
|
||||
{ 388, 2, 7, 7, 4, -5 }, // 0x7c '|' U+007C
|
||||
{ 390, 4, 6, 6, 1, -5 }, // 0x7d '}' U+007D
|
||||
{ 393, 6, 5, 7, 0, -5 }, // 0x7e '~' U+007E
|
||||
{ 397, 5, 6, 6, 0, -5 }, // 0x7f 'REPLACEMENT CHARACTER *' U+2370
|
||||
{ 401, 5, 6, 6, 0, -5 }, // 0x80 'NO-BREAK SPACE' U+00A0
|
||||
{ 405, 5, 6, 6, 0, -5 }, // 0x81 'INVERTED EXCLAMATION MARK' U+00A1
|
||||
{ 409, 5, 6, 6, 0, -5 }, // 0x82 'CENT SIGN' U+00A2
|
||||
{ 413, 5, 6, 6, 0, -5 }, // 0x83 'POUND SIGN' U+00A3
|
||||
{ 417, 5, 6, 6, 0, -5 }, // 0x84 'CURRENCY SIGN' U+00A4
|
||||
{ 421, 5, 6, 6, 0, -5 }, // 0x85 'YEN SIGN' U+00A5
|
||||
{ 425, 5, 6, 6, 0, -5 }, // 0x86 'BROKEN BAR' U+00A6
|
||||
{ 429, 5, 6, 6, 0, -5 }, // 0x87 'SECTION SIGN' U+00A7
|
||||
{ 433, 5, 6, 6, 0, -5 }, // 0x88 'DIAERESIS' U+00A8
|
||||
{ 437, 5, 6, 6, 0, -5 }, // 0x89 'COPYRIGHT SIGN' U+00A9
|
||||
{ 441, 5, 6, 6, 0, -5 }, // 0x8a 'FEMININE ORDINAL INDICATOR' U+00AA
|
||||
{ 445, 5, 6, 6, 0, -5 }, // 0x8b 'LEFT-POINTING DOUBLE ANGLE QUOTATION MARK' U+00AB
|
||||
{ 449, 5, 6, 6, 0, -5 }, // 0x8c 'NOT SIGN' U+00AC
|
||||
{ 453, 5, 6, 6, 0, -5 }, // 0x8d 'SOFT HYPHEN' U+00AD
|
||||
{ 457, 5, 6, 6, 0, -5 }, // 0x8e 'REGISTERED SIGN' U+00AE
|
||||
{ 461, 5, 6, 6, 0, -5 }, // 0x8f 'MACRON' U+00AF
|
||||
{ 465, 5, 6, 6, 0, -5 }, // 0x90 'DEGREE SIGN' U+00B0
|
||||
{ 469, 5, 6, 6, 0, -5 }, // 0x91 'PLUS-MINUS SIGN' U+00B1
|
||||
{ 473, 5, 6, 6, 0, -5 }, // 0x92 'SUPERSCRIPT TWO' U+00B2
|
||||
{ 477, 5, 6, 6, 0, -5 }, // 0x93 'SUPERSCRIPT THREE' U+00B3
|
||||
{ 481, 5, 6, 6, 0, -5 }, // 0x94 'ACUTE ACCENT' U+00B4
|
||||
{ 485, 5, 6, 6, 0, -5 }, // 0x95 'MICRO SIGN' U+00B5
|
||||
{ 489, 5, 6, 6, 0, -5 }, // 0x96 'PILCROW SIGN' U+00B6
|
||||
{ 493, 5, 6, 6, 0, -5 }, // 0x97 'MIDDLE DOT' U+00B7
|
||||
{ 497, 5, 6, 6, 0, -5 }, // 0x98 'CEDILLA' U+00B8
|
||||
{ 501, 5, 6, 6, 0, -5 }, // 0x99 'SUPERSCRIPT ONE' U+00B9
|
||||
{ 505, 5, 6, 6, 0, -5 }, // 0x9a 'MASCULINE ORDINAL INDICATOR' U+00BA
|
||||
{ 509, 5, 6, 6, 0, -5 }, // 0x9b 'RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK' U+00BB
|
||||
{ 513, 5, 6, 6, 0, -5 }, // 0x9c 'VULGAR FRACTION ONE QUARTER' U+00BC
|
||||
{ 517, 5, 6, 6, 0, -5 }, // 0x9d 'VULGAR FRACTION ONE HALF' U+00BD
|
||||
{ 521, 5, 6, 6, 0, -5 }, // 0x9e 'VULGAR FRACTION THREE QUARTERS' U+00BE
|
||||
{ 525, 5, 6, 6, 0, -5 }, // 0x9f 'INVERTED QUESTION MARK' U+00BF
|
||||
{ 529, 6, 5, 7, 0, -4 }, // 0xa0 'LATIN CAPITAL LETTER A WITH GRAVE' U+00C0
|
||||
{ 533, 6, 7, 7, 0, -5 }, // 0xa1 'LATIN CAPITAL LETTER A WITH ACUTE' U+00C1
|
||||
{ 539, 6, 7, 7, 0, -5 }, // 0xa2 'LATIN CAPITAL LETTER A WITH CIRCUMFLEX' U+00C2
|
||||
{ 545, 7, 6, 7, 0, -5 }, // 0xa3 'LATIN CAPITAL LETTER A WITH TILDE' U+00C3
|
||||
{ 551, 7, 6, 7, 0, -5 }, // 0xa4 'LATIN CAPITAL LETTER A WITH DIAERESIS' U+00C4
|
||||
{ 557, 6, 6, 7, 0, -5 }, // 0xa5 'LATIN CAPITAL LETTER A WITH RING ABOVE' U+00C5
|
||||
{ 562, 6, 6, 7, 0, -5 }, // 0xa6 'LATIN CAPITAL LETTER AE' U+00C6
|
||||
{ 567, 6, 6, 7, 0, -5 }, // 0xa7 'LATIN CAPITAL LETTER C WITH CEDILLA' U+00C7
|
||||
{ 572, 7, 6, 7, 0, -5 }, // 0xa8 'LATIN CAPITAL LETTER E WITH GRAVE' U+00C8
|
||||
{ 578, 7, 7, 7, 0, -5 }, // 0xa9 'LATIN CAPITAL LETTER E WITH ACUTE' U+00C9
|
||||
{ 585, 7, 7, 7, 0, -5 }, // 0xaa 'LATIN CAPITAL LETTER E WITH CIRCUMFLEX' U+00CA
|
||||
{ 592, 6, 7, 7, 0, -5 }, // 0xab 'LATIN CAPITAL LETTER E WITH DIAERESIS' U+00CB
|
||||
{ 598, 7, 7, 7, 0, -5 }, // 0xac 'LATIN CAPITAL LETTER I WITH GRAVE' U+00CC
|
||||
{ 605, 7, 7, 7, 0, -5 }, // 0xad 'LATIN CAPITAL LETTER I WITH ACUTE' U+00CD
|
||||
{ 612, 7, 7, 8, 1, -5 }, // 0xae 'LATIN CAPITAL LETTER I WITH CIRCUMFLEX' U+00CE
|
||||
{ 619, 3, 7, 6, 1, -5 }, // 0xaf 'LATIN CAPITAL LETTER I WITH DIAERESIS' U+00CF
|
||||
{ 622, 6, 7, 7, 0, -5 }, // 0xb0 'LATIN CAPITAL LETTER ETH' U+00D0
|
||||
{ 628, 4, 7, 7, 1, -5 }, // 0xb1 'LATIN CAPITAL LETTER N WITH TILDE' U+00D1
|
||||
{ 632, 6, 7, 7, 0, -5 }, // 0xb2 'LATIN CAPITAL LETTER O WITH GRAVE' U+00D2
|
||||
{ 638, 6, 7, 7, 0, -5 }, // 0xb3 'LATIN CAPITAL LETTER O WITH ACUTE' U+00D3
|
||||
{ 644, 6, 7, 7, 0, -5 }, // 0xb4 'LATIN CAPITAL LETTER O WITH CIRCUMFLEX' U+00D4
|
||||
{ 650, 6, 7, 7, 0, -5 }, // 0xb5 'LATIN CAPITAL LETTER O WITH TILDE' U+00D5
|
||||
{ 656, 6, 7, 7, 0, -5 }, // 0xb6 'LATIN CAPITAL LETTER O WITH DIAERESIS' U+00D6
|
||||
{ 662, 6, 7, 7, 0, -5 }, // 0xb7 'MULTIPLICATION SIGN' U+00D7
|
||||
{ 668, 6, 7, 7, 0, -5 }, // 0xb8 'LATIN CAPITAL LETTER O WITH STROKE' U+00D8
|
||||
{ 674, 6, 7, 7, 0, -5 }, // 0xb9 'LATIN CAPITAL LETTER U WITH GRAVE' U+00D9
|
||||
{ 680, 6, 5, 7, 0, -4 }, // 0xba 'LATIN CAPITAL LETTER U WITH ACUTE' U+00DA
|
||||
{ 684, 8, 7, 8, 0, -5 }, // 0xbb 'LATIN CAPITAL LETTER U WITH CIRCUMFLEX' U+00DB
|
||||
{ 691, 7, 7, 8, 1, -5 }, // 0xbc 'LATIN CAPITAL LETTER U WITH DIAERESIS' U+00DC
|
||||
{ 698, 6, 7, 7, 0, -5 }, // 0xbd 'LATIN CAPITAL LETTER Y WITH ACUTE' U+00DD
|
||||
{ 704, 7, 7, 7, 0, -5 }, // 0xbe 'LATIN CAPITAL LETTER THORN' U+00DE
|
||||
{ 711, 6, 6, 7, 0, -5 }, // 0xbf 'LATIN SMALL LETTER SHARP S' U+00DF
|
||||
{ 716, 5, 6, 6, 0, -5 }, // 0xc0 'LATIN SMALL LETTER A WITH GRAVE' U+00E0
|
||||
{ 720, 5, 6, 6, 0, -5 }, // 0xc1 'LATIN SMALL LETTER A WITH ACUTE' U+00E1
|
||||
{ 724, 5, 6, 6, 0, -5 }, // 0xc2 'LATIN SMALL LETTER A WITH CIRCUMFLEX' U+00E2
|
||||
{ 728, 5, 6, 6, 0, -5 }, // 0xc3 'LATIN SMALL LETTER A WITH TILDE' U+00E3
|
||||
{ 732, 5, 6, 6, 0, -5 }, // 0xc4 'LATIN SMALL LETTER A WITH DIAERESIS' U+00E4
|
||||
{ 736, 5, 6, 6, 0, -5 }, // 0xc5 'LATIN SMALL LETTER A WITH RING ABOVE' U+00E5
|
||||
{ 740, 5, 6, 6, 0, -5 }, // 0xc6 'LATIN SMALL LETTER AE' U+00E6
|
||||
{ 744, 5, 6, 6, 0, -5 }, // 0xc7 'LATIN SMALL LETTER C WITH CEDILLA' U+00E7
|
||||
{ 748, 5, 6, 6, 0, -5 }, // 0xc8 'LATIN SMALL LETTER E WITH GRAVE' U+00E8
|
||||
{ 752, 5, 6, 6, 0, -5 }, // 0xc9 'LATIN SMALL LETTER E WITH ACUTE' U+00E9
|
||||
{ 756, 5, 6, 6, 0, -5 }, // 0xca 'LATIN SMALL LETTER E WITH CIRCUMFLEX' U+00EA
|
||||
{ 760, 5, 6, 6, 0, -5 }, // 0xcb 'LATIN SMALL LETTER E WITH DIAERESIS' U+00EB
|
||||
{ 764, 5, 6, 6, 0, -5 }, // 0xcc 'LATIN SMALL LETTER I WITH GRAVE' U+00EC
|
||||
{ 768, 5, 6, 6, 0, -5 }, // 0xcd 'LATIN SMALL LETTER I WITH ACUTE' U+00ED
|
||||
{ 772, 5, 6, 6, 0, -5 }, // 0xce 'LATIN SMALL LETTER I WITH CIRCUMFLEX' U+00EE
|
||||
{ 776, 5, 6, 6, 0, -5 }, // 0xcf 'LATIN SMALL LETTER I WITH DIAERESIS' U+00EF
|
||||
{ 780, 5, 6, 6, 0, -5 }, // 0xd0 'LATIN SMALL LETTER ETH' U+00F0
|
||||
{ 784, 5, 6, 6, 0, -5 }, // 0xd1 'LATIN SMALL LETTER N WITH TILDE' U+00F1
|
||||
{ 788, 5, 6, 6, 0, -5 }, // 0xd2 'LATIN SMALL LETTER O WITH GRAVE' U+00F2
|
||||
{ 792, 5, 6, 6, 0, -5 }, // 0xd3 'LATIN SMALL LETTER O WITH ACUTE' U+00F3
|
||||
{ 796, 5, 6, 6, 0, -5 }, // 0xd4 'LATIN SMALL LETTER O WITH CIRCUMFLEX' U+00F4
|
||||
{ 800, 5, 6, 6, 0, -5 }, // 0xd5 'LATIN SMALL LETTER O WITH TILDE' U+00F5
|
||||
{ 804, 5, 6, 6, 0, -5 }, // 0xd6 'LATIN SMALL LETTER O WITH DIAERESIS' U+00F6
|
||||
{ 808, 5, 6, 6, 0, -5 }, // 0xd7 'DIVISION SIGN' U+00F7
|
||||
{ 812, 5, 6, 6, 0, -5 }, // 0xd8 'LATIN SMALL LETTER O WITH STROKE' U+00F8
|
||||
{ 816, 5, 6, 6, 0, -5 }, // 0xd9 'LATIN SMALL LETTER U WITH GRAVE' U+00F9
|
||||
{ 820, 5, 6, 6, 0, -5 }, // 0xda 'LATIN SMALL LETTER U WITH ACUTE' U+00FA
|
||||
{ 824, 5, 6, 6, 0, -5 }, // 0xdb 'LATIN SMALL LETTER U WITH CIRCUMFLEX' U+00FB
|
||||
{ 828, 5, 6, 6, 0, -5 }, // 0xdc 'LATIN SMALL LETTER U WITH DIAERESIS' U+00FC
|
||||
{ 832, 5, 6, 6, 0, -5 }, // 0xdd 'LATIN SMALL LETTER Y WITH ACUTE' U+00FD
|
||||
{ 836, 5, 6, 6, 0, -5 }, // 0xde 'LATIN SMALL LETTER THORN' U+00FE
|
||||
{ 840, 5, 6, 6, 0, -5 } }; // 0xdf 'LATIN SMALL LETTER Y WITH DIAERESIS' U+000FF
|
||||
|
||||
const GFXfont Atari6px PROGMEM = {
|
||||
(uint8_t *)Atari6pxBitmaps,
|
||||
(GFXglyph *)Atari6pxGlyphs,
|
||||
0x20, 0xDF, 9 };
|
||||
|
||||
// Approx. 2195 bytes
|
||||
@@ -56,9 +56,11 @@ public:
|
||||
}
|
||||
uint16_t add() {
|
||||
// returns new head value pointer
|
||||
return 0;
|
||||
}
|
||||
uint8_t* get() {
|
||||
// returns complete buffer in order new to old
|
||||
return 0;
|
||||
}
|
||||
uint8_t getvalue(uint16_t dt) {
|
||||
// Return a single value delta seconds ago
|
||||
@@ -66,6 +68,7 @@ public:
|
||||
return 0;
|
||||
}
|
||||
uint8_t getvalue3() {
|
||||
return 0;
|
||||
}
|
||||
bool clear() {
|
||||
// clears buffer and permanent storage
|
||||
@@ -80,5 +83,6 @@ public:
|
||||
~History() {
|
||||
}
|
||||
void *addSeries() {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// Add a new register card in web configuration interface
|
||||
// This is a Java Script!
|
||||
(function(){
|
||||
const api=window.esp32nmea2k;
|
||||
if (! api) return;
|
||||
const tabName="OBP60";
|
||||
const tabName="Screen";
|
||||
api.registerListener((id, data) => {
|
||||
// if (!data.testboard) return; //do nothing if we are not active
|
||||
let page = api.addTabPage(tabName, "OBP60");
|
||||
let page = api.addTabPage(tabName, "Screen");
|
||||
api.addEl('button', '', page, 'Screenshot').addEventListener('click', function (ev) {
|
||||
window.open('/api/user/OBP60Task/screenshot', 'screenshot');
|
||||
})
|
||||
|
||||
@@ -788,3 +788,70 @@ dict =
|
||||
BMP:Windows bitmap (BMP)
|
||||
category = OBP60 Pages
|
||||
capabilities = obp60:true
|
||||
|
||||
# WIP
|
||||
|
||||
[trackerType]
|
||||
label = Tracker Type
|
||||
type = list
|
||||
default = off
|
||||
description = Type of tracker to use [OFF|SDCARD|SERVER|HERO]
|
||||
dict = OFF:No tracker
|
||||
SDCARD:Log to SD-Card
|
||||
SERVER:Log to Server
|
||||
HERO:Connect with Regatta Hero
|
||||
category = OBP60 Pages
|
||||
capabilities = obp60:true
|
||||
|
||||
[trackerOrganization]
|
||||
label = Tracker team
|
||||
type = string
|
||||
default = demo
|
||||
description = Tracker organization for login
|
||||
category = OBP60 Pages
|
||||
|
||||
[trackerPasscode]
|
||||
label = Tracker team
|
||||
type = password
|
||||
default = 291758
|
||||
description = Your tracker team you belong to. E.g. short name of association
|
||||
category = OBP60 Pages
|
||||
|
||||
[trackerTeam]
|
||||
label = Tracker team
|
||||
type = string
|
||||
default = none
|
||||
description = Your tracker team you belong to. E.g. short name of association
|
||||
category = OBP60 Pages
|
||||
|
||||
[trackerHandicap]
|
||||
label = Boat handicap
|
||||
type = number
|
||||
default = 100
|
||||
check = checkMinMax
|
||||
min = 50
|
||||
max = 1000
|
||||
description = The handicap value of your boat. E.g. yardstick value
|
||||
category = OBP60 Pages
|
||||
|
||||
[boatName]
|
||||
label = Boat Name
|
||||
type = string
|
||||
default = Unsinkbar II
|
||||
description = name of your boat
|
||||
category = OBP60 Pages
|
||||
|
||||
[boatClass]
|
||||
label = Boat Class
|
||||
type = string
|
||||
default = One off
|
||||
description = Class name of your boat if available or "One off"
|
||||
category = OBP60 Pages
|
||||
|
||||
[sailNumber]
|
||||
label = Sail number
|
||||
type = string
|
||||
default = GER 11
|
||||
description = Identification number on sail
|
||||
category = OBP60 Pages
|
||||
|
||||
|
||||
@@ -13,10 +13,13 @@
|
||||
#include <NMEA0183Messages.h>
|
||||
#include <GxEPD2_BW.h> // GxEPD2 lib for b/w E-Ink displays
|
||||
#include "OBP60Extensions.h" // Functions lib for extension board
|
||||
#include "OBP60Keypad.h" // Functions for keypad
|
||||
#include "OBPKeyboardTask.h" // Functions lib for keyboard handling
|
||||
#include "BoatDataCalibration.h" // Functions lib for data instance calibration
|
||||
#include "OBPRingBuffer.h" // Functions lib with ring buffer for history storage of some boat data
|
||||
#include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation
|
||||
#include "OBP60QRWiFi.h" // Functions lib for WiFi QR code
|
||||
#include "OBPSensorTask.h" // Functions lib for sensor data
|
||||
|
||||
#include "freertos/task.h" // WIP possible unused
|
||||
|
||||
#ifdef BOARD_OBP40S3
|
||||
#include "driver/rtc_io.h" // Needs for weakup from deep sleep
|
||||
@@ -26,8 +29,6 @@
|
||||
// Pictures
|
||||
#include "images/OBP_400x300.xbm" // OBP Logo
|
||||
#include "images/unknown.xbm" // unknown page indicator
|
||||
#include "OBP60QRWiFi.h" // Functions lib for WiFi QR code
|
||||
#include "OBPSensorTask.h" // Functions lib for sensor data
|
||||
|
||||
// Global vars
|
||||
bool initComplete = false; // Initialization complete
|
||||
@@ -65,8 +66,8 @@ void OBP60Init(GwApi *api){
|
||||
#endif
|
||||
|
||||
// Settings for e-paper display
|
||||
String fastrefresh = api->getConfig()->getConfigItem(api->getConfig()->fastRefresh,true)->asString();
|
||||
logger->logDebug(GwLog::DEBUG,"Fast Refresh Mode is: %s", fastrefresh.c_str());
|
||||
String fastrefresh = config->getConfigItem(config->fastRefresh,true)->asString();
|
||||
logger->logDebug(GwLog::DEBUG, "Fast Refresh Mode is: %s", fastrefresh.c_str());
|
||||
#ifdef DISPLAY_GDEY042T81
|
||||
if(fastrefresh == "true"){
|
||||
static const bool useFastFullUpdate = true; // Enable fast full display update only for GDEY042T81
|
||||
@@ -88,24 +89,24 @@ void OBP60Init(GwApi *api){
|
||||
logger->logDebug(GwLog::LOG,"CPU speed at boot: %i MHz", freq);
|
||||
|
||||
// Settings for backlight
|
||||
String backlightMode = api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString();
|
||||
logger->logDebug(GwLog::DEBUG,"Backlight Mode is: %s", backlightMode.c_str());
|
||||
uint brightness = uint(api->getConfig()->getConfigItem(api->getConfig()->blBrightness,true)->asInt());
|
||||
String backlightColor = api->getConfig()->getConfigItem(api->getConfig()->blColor,true)->asString();
|
||||
if(String(backlightMode) == "On"){
|
||||
setBacklightLED(brightness, colorMapping(backlightColor));
|
||||
String backlightMode = config->getConfigItem(config->backlight,true)->asString();
|
||||
logger->logDebug(GwLog::DEBUG, "Backlight Mode is: %s", backlightMode.c_str());
|
||||
uint brightness = uint(config->getConfigItem(config->blBrightness,true)->asInt());
|
||||
String backlightColor = config->getConfigItem(config->blColor,true)->asString();
|
||||
if (backlightMode == "On") {
|
||||
setBacklightLED(brightness, colorMapping(backlightColor));
|
||||
}
|
||||
else if(String(backlightMode) == "Off"){
|
||||
setBacklightLED(0, COLOR_BLACK); // Backlight LEDs off (blue without britghness)
|
||||
else if (backlightMode == "Off") {
|
||||
setBacklightLED(0, COLOR_BLACK); // Backlight LEDs off (blue without britghness)
|
||||
}
|
||||
else if(String(backlightMode) == "Control by Key"){
|
||||
setBacklightLED(0, COLOR_BLUE); // Backlight LEDs off (blue without britghness)
|
||||
else if (backlightMode == "Control by Key") {
|
||||
setBacklightLED(0, COLOR_BLUE); // Backlight LEDs off (blue without britghness)
|
||||
}
|
||||
|
||||
// Settings flash LED mode
|
||||
String ledMode = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString();
|
||||
String ledMode = config->getConfigItem(config->flashLED,true)->asString();
|
||||
logger->logDebug(GwLog::DEBUG,"LED Mode is: %s", ledMode.c_str());
|
||||
if(String(ledMode) == "Off"){
|
||||
if (ledMode == "Off") {
|
||||
setBlinkingLED(false);
|
||||
}
|
||||
|
||||
@@ -114,79 +115,61 @@ void OBP60Init(GwApi *api){
|
||||
initComplete = true;
|
||||
|
||||
// Buzzer tone for initialization finish
|
||||
setBuzzerPower(uint(api->getConfig()->getConfigItem(api->getConfig()->buzzerPower,true)->asInt()));
|
||||
setBuzzerPower(uint(config->getConfigItem(config->buzzerPower,true)->asInt()));
|
||||
buzzer(TONE4, 500);
|
||||
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int page0=0;
|
||||
QueueHandle_t queue;
|
||||
GwLog* logger = NULL;
|
||||
// GwApi* api = NULL;
|
||||
uint sensitivity = 100;
|
||||
bool use_syspage = true;
|
||||
} MyData;
|
||||
/* ux-functions not working WTF?
|
||||
bool listTasks(GwLog *logger) {
|
||||
UBaseType_t taskCount = uxTaskGetNumberOfTasks();
|
||||
TaskStatus_t *taskStatusArray;
|
||||
|
||||
// Keyboard Task
|
||||
void keyboardTask(void *param){
|
||||
MyData *data=(MyData *)param;
|
||||
|
||||
int keycode = 0;
|
||||
data->logger->logDebug(GwLog::LOG,"Start keyboard task");
|
||||
|
||||
// Loop for keyboard task
|
||||
while (true){
|
||||
keycode = readKeypad(data->logger, data->sensitivity, data->use_syspage);
|
||||
//send a key event
|
||||
if(keycode != 0){
|
||||
xQueueSend(data->queue, &keycode, 0);
|
||||
data->logger->logDebug(GwLog::LOG,"Send keycode: %d", keycode);
|
||||
taskStatusArray = (TaskStatus_t *)pvPortMalloc(taskCount * sizeof(TaskStatus_t));
|
||||
if (taskStatusArray != NULL) {
|
||||
taskCount = uxTaskGetSystemState(taskStatusArray, taskCount, NULL);
|
||||
for (UBaseType_t i = 0; i < taskCount; i++) {
|
||||
logger->logDebug(GwLog::LOG, "Task Name: %s (Stack=%d)", taskStatusArray[i].pcTaskName,
|
||||
taskStatusArray[i].usStackHighWaterMark);
|
||||
// more fields in task status
|
||||
// xHandle, uxCurrentPriority, uxBasePriority, usStackHighWaterMark
|
||||
}
|
||||
delay(20); // 50Hz update rate (20ms)
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
class BoatValueList{
|
||||
public:
|
||||
static const int MAXVALUES=100;
|
||||
//we create a list containing all our BoatValues
|
||||
//this is the list we later use to let the api fill all the values
|
||||
//additionally we put the necessary values into the paga data - see below
|
||||
GwApi::BoatValue *allBoatValues[MAXVALUES];
|
||||
int numValues=0;
|
||||
|
||||
bool addValueToList(GwApi::BoatValue *v){
|
||||
for (int i=0;i<numValues;i++){
|
||||
if (allBoatValues[i] == v){
|
||||
//already in list...
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (numValues >= MAXVALUES) return false;
|
||||
allBoatValues[numValues]=v;
|
||||
numValues++;
|
||||
vPortFree(taskStatusArray);
|
||||
return true;
|
||||
}
|
||||
//helper to ensure that each BoatValue is only queried once
|
||||
GwApi::BoatValue *findValueOrCreate(String name){
|
||||
for (int i=0;i<numValues;i++){
|
||||
if (allBoatValues[i]->getName() == name) {
|
||||
return allBoatValues[i];
|
||||
}
|
||||
logger->logDebug(GwLog::ERROR, "Failed to allocate memory for task list");
|
||||
return false;
|
||||
} */
|
||||
|
||||
bool BoatValueList::addValueToList(GwApi::BoatValue *v){
|
||||
for (int i=0;i<numValues;i++){
|
||||
if (allBoatValues[i] == v){
|
||||
//already in list...
|
||||
return true;
|
||||
}
|
||||
GwApi::BoatValue *rt=new GwApi::BoatValue(name);
|
||||
addValueToList(rt);
|
||||
return rt;
|
||||
}
|
||||
};
|
||||
if (numValues >= MAXVALUES) return false;
|
||||
allBoatValues[numValues]=v;
|
||||
numValues++;
|
||||
return true;
|
||||
}
|
||||
|
||||
//helper to ensure that each BoatValue is only queried once
|
||||
GwApi::BoatValue *BoatValueList::findValueOrCreate(String name){
|
||||
for (int i=0;i<numValues;i++){
|
||||
if (allBoatValues[i]->getName() == name) {
|
||||
return allBoatValues[i];
|
||||
}
|
||||
}
|
||||
GwApi::BoatValue *rt=new GwApi::BoatValue(name);
|
||||
addValueToList(rt);
|
||||
return rt;
|
||||
}
|
||||
|
||||
//we want to have a list that has all our page definitions
|
||||
//this way each page can easily be added here
|
||||
//needs some minor tricks for the safe static initialization
|
||||
typedef std::vector<PageDescription*> Pages;
|
||||
//the page list class
|
||||
class PageList{
|
||||
public:
|
||||
Pages pages;
|
||||
@@ -276,264 +259,68 @@ void registerAllPages(GwLog *logger, PageList &list){
|
||||
list.add(®isterPageAIS);
|
||||
extern PageDescription registerPageBarograph;
|
||||
list.add(®isterPageBarograph);
|
||||
extern PageDescription registerPageTracker;
|
||||
list.add(®isterPageTracker);
|
||||
logger->logDebug(GwLog::LOG,"Memory after registering pages: stack=%d, heap=%d", uxTaskGetStackHighWaterMark(NULL), ESP.getFreeHeap());
|
||||
}
|
||||
|
||||
// Undervoltage detection for shutdown display
|
||||
void underVoltageDetection(GwApi *api, CommonData &common){
|
||||
// Read settings
|
||||
double voffset = (api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asString()).toFloat();
|
||||
double vslope = (api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asString()).toFloat();
|
||||
void underVoltageError(CommonData &common) {
|
||||
#if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
|
||||
// Switch off all power lines
|
||||
setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
|
||||
setFlashLED(false); // Flash LED Off
|
||||
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
|
||||
// Shutdown EInk display
|
||||
epd->setFullWindow(); // Set full Refresh
|
||||
//epd->setPartialWindow(0, 0, epd->width(), epd->height()); // Set partial update
|
||||
epd->fillScreen(common.bgcolor);// Clear screen
|
||||
epd->setTextColor(common.fgcolor);
|
||||
epd->setFont(&Ubuntu_Bold20pt8b);
|
||||
epd->setCursor(65, 150);
|
||||
epd->print("Undervoltage");
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setCursor(65, 175);
|
||||
epd->print("Charge battery and restart system");
|
||||
epd->nextPage(); // Partial update
|
||||
epd->powerOff(); // Display power off
|
||||
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
|
||||
setPortPin(OBP_POWER_SD, false); // Power off SD card
|
||||
#else
|
||||
// Switch off all power lines
|
||||
setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
|
||||
setFlashLED(false); // Flash LED Off
|
||||
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
|
||||
setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off
|
||||
// Shutdown EInk display
|
||||
epd->setPartialWindow(0, 0, epd->width(), epd->height()); // Set partial update
|
||||
epd->fillScreen(common.bgcolor);// Clear screen
|
||||
epd->setTextColor(common.fgcolor);
|
||||
epd->setFont(&Ubuntu_Bold20pt8b);
|
||||
epd->setCursor(65, 150);
|
||||
epd->print("Undervoltage");
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setCursor(65, 175);
|
||||
epd->print("To wake up repower system");
|
||||
epd->nextPage(); // Partial update
|
||||
epd->powerOff(); // Display power off
|
||||
#endif
|
||||
while (true) {
|
||||
esp_deep_sleep_start(); // Deep Sleep without wakeup. Wakeup only after power cycle (restart).
|
||||
}
|
||||
}
|
||||
|
||||
inline bool underVoltageDetection(float voffset, float vslope) {
|
||||
// Read supply voltage
|
||||
#if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
|
||||
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40
|
||||
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40
|
||||
float minVoltage = 3.65; // Absolut minimum volatge for 3,7V LiPo accu
|
||||
#else
|
||||
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60
|
||||
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60
|
||||
float minVoltage = MIN_VOLTAGE;
|
||||
#endif
|
||||
double calVoltage = actVoltage * vslope + voffset; // Calibration
|
||||
if(calVoltage < minVoltage){
|
||||
#if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
|
||||
// Switch off all power lines
|
||||
setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
|
||||
setFlashLED(false); // Flash LED Off
|
||||
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
|
||||
// Shutdown EInk display
|
||||
epd->setFullWindow(); // Set full Refresh
|
||||
//epd->setPartialWindow(0, 0, epd->width(), epd->height()); // Set partial update
|
||||
epd->fillScreen(common.bgcolor);// Clear screen
|
||||
epd->setTextColor(common.fgcolor);
|
||||
epd->setFont(&Ubuntu_Bold20pt8b);
|
||||
epd->setCursor(65, 150);
|
||||
epd->print("Undervoltage");
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setCursor(65, 175);
|
||||
epd->print("Charge battery and restart system");
|
||||
epd->nextPage(); // Partial update
|
||||
epd->powerOff(); // Display power off
|
||||
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
|
||||
setPortPin(OBP_POWER_SD, false); // Power off SD card
|
||||
#else
|
||||
// Switch off all power lines
|
||||
setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
|
||||
setFlashLED(false); // Flash LED Off
|
||||
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
|
||||
setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off
|
||||
// Shutdown EInk display
|
||||
epd->setPartialWindow(0, 0, epd->width(), epd->height()); // Set partial update
|
||||
epd->fillScreen(common.bgcolor);// Clear screen
|
||||
epd->setTextColor(common.fgcolor);
|
||||
epd->setFont(&Ubuntu_Bold20pt8b);
|
||||
epd->setCursor(65, 150);
|
||||
epd->print("Undervoltage");
|
||||
epd->setFont(&Ubuntu_Bold8pt8b);
|
||||
epd->setCursor(65, 175);
|
||||
epd->print("To wake up repower system");
|
||||
epd->nextPage(); // Partial update
|
||||
epd->powerOff(); // Display power off
|
||||
#endif
|
||||
// Stop system
|
||||
while(true){
|
||||
esp_deep_sleep_start(); // Deep Sleep without weakup. Weakup only after power cycle (restart).
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate true wind data and add to obp60task boat data list
|
||||
bool addTrueWind(GwApi* api, BoatValueList* boatValues) {
|
||||
|
||||
double awaVal, awsVal, cogVal, stwVal, sogVal, hdtVal, hdmVal, varVal;
|
||||
double twd, tws, twa;
|
||||
bool isCalculated = false;
|
||||
const double DBL_MIN = std::numeric_limits<double>::lowest();
|
||||
|
||||
GwApi::BoatValue *twdBVal = boatValues->findValueOrCreate("TWD");
|
||||
GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate("TWS");
|
||||
GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA");
|
||||
GwApi::BoatValue *awaBVal = boatValues->findValueOrCreate("AWA");
|
||||
GwApi::BoatValue *awsBVal = boatValues->findValueOrCreate("AWS");
|
||||
GwApi::BoatValue *cogBVal = boatValues->findValueOrCreate("COG");
|
||||
GwApi::BoatValue *stwBVal = boatValues->findValueOrCreate("STW");
|
||||
GwApi::BoatValue *sogBVal = boatValues->findValueOrCreate("SOG");
|
||||
GwApi::BoatValue *hdtBVal = boatValues->findValueOrCreate("HDT");
|
||||
GwApi::BoatValue *hdmBVal = boatValues->findValueOrCreate("HDM");
|
||||
GwApi::BoatValue *varBVal = boatValues->findValueOrCreate("VAR");
|
||||
awaVal = awaBVal->valid ? awaBVal->value : DBL_MIN;
|
||||
awsVal = awsBVal->valid ? awsBVal->value : DBL_MIN;
|
||||
cogVal = cogBVal->valid ? cogBVal->value : DBL_MIN;
|
||||
stwVal = stwBVal->valid ? stwBVal->value : DBL_MIN;
|
||||
sogVal = sogBVal->valid ? sogBVal->value : DBL_MIN;
|
||||
hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MIN;
|
||||
hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MIN;
|
||||
varVal = varBVal->valid ? varBVal->value : DBL_MIN;
|
||||
api->getLogger()->logDebug(GwLog::DEBUG,"obp60task addTrueWind: 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);
|
||||
|
||||
isCalculated = WindUtils::calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa);
|
||||
|
||||
if (isCalculated) { // Replace values only, if successfully calculated and not already available
|
||||
if (!twdBVal->valid) {
|
||||
twdBVal->value = twd;
|
||||
twdBVal->valid = true;
|
||||
}
|
||||
if (!twsBVal->valid) {
|
||||
twsBVal->value = tws;
|
||||
twsBVal->valid = true;
|
||||
}
|
||||
if (!twaBVal->valid) {
|
||||
twaBVal->value = twa;
|
||||
twaBVal->valid = true;
|
||||
}
|
||||
}
|
||||
api->getLogger()->logDebug(GwLog::DEBUG,"obp60task addTrueWind: isCalculated %d, TWD %.1f, TWA %.1f, TWS %.1f", isCalculated, twdBVal->value * RAD_TO_DEG,
|
||||
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852);
|
||||
|
||||
return isCalculated;
|
||||
}
|
||||
|
||||
// Init history buffers for selected boat data
|
||||
void initHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryBufList) {
|
||||
|
||||
GwApi::BoatValue *calBVal; // temp variable just for data calibration -> we don't want to calibrate the original data here
|
||||
const double DBL_MIN = std::numeric_limits<double>::lowest();
|
||||
|
||||
int hstryUpdFreq = 1000; // Update frequency for history buffers in ms
|
||||
int hstryMinVal = 0; // Minimum value for these history buffers
|
||||
int twdHstryMax = 6283; // Max value for wind direction (TWD, AWD) in rad (0...2*PI), shifted by 1000 for 3 decimals
|
||||
int twsHstryMax = 1000; // Max value for wind speed (TWS, AWS) in m/s, shifted by 10 for 1 decimal
|
||||
// Initialize history buffers with meta data
|
||||
hstryBufList.twdHstry->setMetaData("TWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax);
|
||||
hstryBufList.twsHstry->setMetaData("TWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax);
|
||||
hstryBufList.awdHstry->setMetaData("AWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax);
|
||||
hstryBufList.awsHstry->setMetaData("AWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax);
|
||||
|
||||
// create boat values for history data types, if they don't exist yet
|
||||
GwApi::BoatValue *twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName());
|
||||
GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName());
|
||||
GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA");
|
||||
GwApi::BoatValue *awdBVal = boatValues->findValueOrCreate(hstryBufList.awdHstry->getName());
|
||||
GwApi::BoatValue *awsBVal = boatValues->findValueOrCreate(hstryBufList.awsHstry->getName());
|
||||
|
||||
if (!awdBVal->valid) { // AWD usually does not exist
|
||||
awdBVal->setFormat(hstryBufList.awdHstry->getFormat());
|
||||
awdBVal->value = DBL_MIN;
|
||||
}
|
||||
}
|
||||
|
||||
void handleHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryBufList, bool useSimuData) {
|
||||
// Handle history buffers for TWD, TWS
|
||||
|
||||
GwLog *logger = api->getLogger();
|
||||
|
||||
int16_t twdHstryMin = hstryBufList.twdHstry->getMinVal();
|
||||
int16_t twdHstryMax = hstryBufList.twdHstry->getMaxVal();
|
||||
int16_t twsHstryMin = hstryBufList.twsHstry->getMinVal();
|
||||
int16_t twsHstryMax = hstryBufList.twsHstry->getMaxVal();
|
||||
int16_t awdHstryMin = hstryBufList.awdHstry->getMinVal();
|
||||
int16_t awdHstryMax = hstryBufList.awdHstry->getMaxVal();
|
||||
int16_t awsHstryMin = hstryBufList.awsHstry->getMinVal();
|
||||
int16_t awsHstryMax = hstryBufList.awsHstry->getMaxVal();
|
||||
static int16_t twd, tws = 20; //initial value only relevant if we use simulation data
|
||||
static double awd, aws, hdt = 20; //initial value only relevant if we use simulation data
|
||||
GwApi::BoatValue *calBVal; // temp variable just for data calibration -> we don't want to calibrate the original data here
|
||||
|
||||
GwApi::BoatValue *twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName());
|
||||
GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName());
|
||||
GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA");
|
||||
GwApi::BoatValue *awdBVal = boatValues->findValueOrCreate(hstryBufList.awdHstry->getName());
|
||||
GwApi::BoatValue *awsBVal = boatValues->findValueOrCreate(hstryBufList.awsHstry->getName());
|
||||
GwApi::BoatValue *awaBVal = boatValues->findValueOrCreate("AWA");
|
||||
GwApi::BoatValue *hdtBVal = boatValues->findValueOrCreate("HDT");
|
||||
GwApi::BoatValue *hdmBVal = boatValues->findValueOrCreate("HDM");
|
||||
GwApi::BoatValue *varBVal = boatValues->findValueOrCreate("VAR");
|
||||
GwApi::BoatValue *cogBVal = boatValues->findValueOrCreate("COG");
|
||||
GwApi::BoatValue *sogBVal = boatValues->findValueOrCreate("SOG");
|
||||
|
||||
api->getLogger()->logDebug(GwLog::DEBUG,"obp60task handleHstryBuf: TWD_isValid? %d, twdBVal: %.1f, twaBVal: %.1f, twsBVal: %.1f", twdBVal->valid, twdBVal->value * RAD_TO_DEG,
|
||||
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852);
|
||||
|
||||
if (twdBVal->valid) {
|
||||
calBVal = new GwApi::BoatValue("TWD"); // temporary solution for calibration of history buffer values
|
||||
calBVal->setFormat(twdBVal->getFormat());
|
||||
calBVal->value = twdBVal->value;
|
||||
calBVal->valid = twdBVal->valid;
|
||||
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
|
||||
twd = static_cast<int16_t>(std::round(calBVal->value * 1000));
|
||||
if (twd >= twdHstryMin && twd <= twdHstryMax) {
|
||||
hstryBufList.twdHstry->add(twd);
|
||||
}
|
||||
delete calBVal;
|
||||
calBVal = nullptr;
|
||||
} else if (useSimuData) {
|
||||
twd += random(-20, 20);
|
||||
twd = WindUtils::to360(twd);
|
||||
hstryBufList.twdHstry->add(static_cast<int16_t>(DegToRad(twd) * 1000.0));
|
||||
}
|
||||
|
||||
if (twsBVal->valid) {
|
||||
calBVal = new GwApi::BoatValue("TWS"); // temporary solution for calibration of history buffer values
|
||||
calBVal->setFormat(twsBVal->getFormat());
|
||||
calBVal->value = twsBVal->value;
|
||||
calBVal->valid = twsBVal->valid;
|
||||
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
|
||||
tws = static_cast<int16_t>(std::round(calBVal->value * 10));
|
||||
if (tws >= twsHstryMin && tws <= twsHstryMax) {
|
||||
hstryBufList.twsHstry->add(tws);
|
||||
}
|
||||
delete calBVal;
|
||||
calBVal = nullptr;
|
||||
} else if (useSimuData) {
|
||||
tws += random(-50, 50); // TWS value in m/s; expands to 1 decimal
|
||||
tws = constrain(tws, 0, 250); // Limit TWS to [0..25] m/s
|
||||
hstryBufList.twsHstry->add(tws);
|
||||
}
|
||||
|
||||
if (awaBVal->valid) {
|
||||
if (hdtBVal->valid) {
|
||||
hdt = hdtBVal->value; // Use HDT if available
|
||||
} else {
|
||||
hdt = WindUtils::calcHDT(&hdmBVal->value, &varBVal->value, &cogBVal->value, &sogBVal->value);
|
||||
}
|
||||
|
||||
awd = awaBVal->value + hdt;
|
||||
awd = WindUtils::to2PI(awd);
|
||||
calBVal = new GwApi::BoatValue("AWD"); // temporary solution for calibration of history buffer values
|
||||
calBVal->value = awd;
|
||||
calBVal->setFormat(awdBVal->getFormat());
|
||||
calBVal->valid = true;
|
||||
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
|
||||
awdBVal->value = calBVal->value;
|
||||
awdBVal->valid = true;
|
||||
awd = std::round(calBVal->value * 1000);
|
||||
if (awd >= awdHstryMin && awd <= awdHstryMax) {
|
||||
hstryBufList.awdHstry->add(static_cast<int16_t>(awd));
|
||||
}
|
||||
delete calBVal;
|
||||
calBVal = nullptr;
|
||||
} else if (useSimuData) {
|
||||
awd += random(-20, 20);
|
||||
awd = WindUtils::to360(awd);
|
||||
hstryBufList.awdHstry->add(static_cast<int16_t>(DegToRad(awd) * 1000.0));
|
||||
}
|
||||
|
||||
if (awsBVal->valid) {
|
||||
calBVal = new GwApi::BoatValue("AWS"); // temporary solution for calibration of history buffer values
|
||||
calBVal->setFormat(awsBVal->getFormat());
|
||||
calBVal->value = awsBVal->value;
|
||||
calBVal->valid = awsBVal->valid;
|
||||
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
|
||||
aws = std::round(calBVal->value * 10);
|
||||
if (aws >= awsHstryMin && aws <= awsHstryMax) {
|
||||
hstryBufList.awsHstry->add(static_cast<int16_t>(aws));
|
||||
}
|
||||
delete calBVal;
|
||||
calBVal = nullptr;
|
||||
} else if (useSimuData) {
|
||||
aws += random(-50, 50); // TWS value in m/s; expands to 1 decimal
|
||||
aws = constrain(aws, 0, 250); // Limit TWS to [0..25] m/s
|
||||
hstryBufList.awsHstry->add(aws);
|
||||
}
|
||||
float calVoltage = actVoltage * vslope + voffset; // Calibration
|
||||
return (calVoltage < minVoltage);
|
||||
}
|
||||
|
||||
// OBP60 Task
|
||||
@@ -541,8 +328,8 @@ void handleHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryB
|
||||
void OBP60Task(GwApi *api){
|
||||
// vTaskDelete(NULL);
|
||||
// return;
|
||||
GwLog *logger=api->getLogger();
|
||||
GwConfigHandler *config=api->getConfig();
|
||||
GwLog *logger = api->getLogger();
|
||||
GwConfigHandler *config = api->getConfig();
|
||||
#ifdef HARDWARE_V21
|
||||
startLedTask(api);
|
||||
#endif
|
||||
@@ -566,8 +353,8 @@ void OBP60Task(GwApi *api){
|
||||
}
|
||||
|
||||
// Init E-Ink display
|
||||
String displaymode = api->getConfig()->getConfigItem(api->getConfig()->display,true)->asString();
|
||||
String displaycolor = api->getConfig()->getConfigItem(api->getConfig()->displaycolor,true)->asString();
|
||||
String displaymode = config->getConfigItem(config->display,true)->asString();
|
||||
String displaycolor = config->getConfigItem(config->displaycolor,true)->asString();
|
||||
if (displaycolor == "Normal") {
|
||||
commonData.fgcolor = GxEPD_BLACK;
|
||||
commonData.bgcolor = GxEPD_WHITE;
|
||||
@@ -576,12 +363,12 @@ void OBP60Task(GwApi *api){
|
||||
commonData.fgcolor = GxEPD_WHITE;
|
||||
commonData.bgcolor = GxEPD_BLACK;
|
||||
}
|
||||
String systemname = api->getConfig()->getConfigItem(api->getConfig()->systemName,true)->asString();
|
||||
String wifipass = api->getConfig()->getConfigItem(api->getConfig()->apPassword,true)->asString();
|
||||
bool refreshmode = api->getConfig()->getConfigItem(api->getConfig()->refresh,true)->asBoolean();
|
||||
String systemname = config->getConfigItem(config->systemName, true)->asString();
|
||||
String wifipass = config->getConfigItem(config->apPassword, true)->asString();
|
||||
bool refreshmode = config->getConfigItem(config->refresh, true)->asBoolean();
|
||||
bool symbolmode = (config->getString(config->headerFormat) == "ICON");
|
||||
String fastrefresh = api->getConfig()->getConfigItem(api->getConfig()->fastRefresh,true)->asString();
|
||||
uint fullrefreshtime = uint(api->getConfig()->getConfigItem(api->getConfig()->fullRefreshTime,true)->asInt());
|
||||
String fastrefresh = config->getConfigItem(config->fastRefresh, true)->asString();
|
||||
uint fullrefreshtime = uint(config->getConfigItem(config->fullRefreshTime, true)->asInt());
|
||||
#ifdef BOARD_OBP40S3
|
||||
bool syspage_enabled = config->getBool(config->systemPage);
|
||||
#endif
|
||||
@@ -648,16 +435,11 @@ void OBP60Task(GwApi *api){
|
||||
int lastPage=pageNumber;
|
||||
|
||||
BoatValueList boatValues; //all the boat values for the api query
|
||||
HstryBuf hstryBufList(960); // Create ring buffers for history storage of some boat data
|
||||
WindUtils trueWind(&boatValues); // Create helper object for true wind calculation
|
||||
//commonData.distanceformat=config->getString(xxx);
|
||||
//add all necessary data to common data
|
||||
|
||||
// Create ring buffers for history storage of some boat data
|
||||
RingBuffer<int16_t> twdHstry(960); // Circular buffer to store true wind direction values; store 960 TWD values for 16 minutes history
|
||||
RingBuffer<int16_t> twsHstry(960); // Circular buffer to store true wind speed values (TWS)
|
||||
RingBuffer<int16_t> awdHstry(960); // Circular buffer to store appearant wind direction values; store 960 AWD values for 16 minutes history
|
||||
RingBuffer<int16_t> awsHstry(960); // Circular buffer to store appearant xwind speed values (AWS)
|
||||
tBoatHstryData hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry};
|
||||
|
||||
//fill the page data from config
|
||||
numPages=config->getInt(config->visiblePages,1);
|
||||
if (numPages < 1) numPages=1;
|
||||
@@ -678,6 +460,7 @@ void OBP60Task(GwApi *api){
|
||||
pages[i].page=description->creator(commonData);
|
||||
pages[i].parameters.pageName=pageType;
|
||||
pages[i].parameters.pageNumber = i + 1;
|
||||
pages[i].parameters.api = api;
|
||||
LOG_DEBUG(GwLog::DEBUG,"found page %s for number %d",pageType.c_str(),i);
|
||||
//fill in all the user defined parameters
|
||||
for (int uid=0;uid<description->userParam;uid++){
|
||||
@@ -696,10 +479,8 @@ void OBP60Task(GwApi *api){
|
||||
LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i);
|
||||
pages[i].parameters.values.push_back(value);
|
||||
}
|
||||
if (pages[i].description->pageName == "WindPlot") {
|
||||
// Add boat history data to page parameters
|
||||
pages[i].parameters.boatHstry = hstryBufList;
|
||||
}
|
||||
// Add boat history data to page parameters
|
||||
pages[i].parameters.boatHstry = &hstryBufList;
|
||||
}
|
||||
// add out of band system page (always available)
|
||||
Page *syspage = allPages.pages[0]->creator(commonData);
|
||||
@@ -707,12 +488,12 @@ void OBP60Task(GwApi *api){
|
||||
// Read all calibration data settings from config
|
||||
calibrationData.readConfig(config, logger);
|
||||
|
||||
// Check user setting for true wind calculation
|
||||
// Check user settings for true wind calculation
|
||||
bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false);
|
||||
bool simulation = api->getConfig()->getBool(api->getConfig()->useSimuData, false);
|
||||
bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false);
|
||||
|
||||
// Initialize history buffer for certain boat data
|
||||
initHstryBuf(api, &boatValues, hstryBufList);
|
||||
hstryBufList.init(&boatValues, logger);
|
||||
|
||||
// Display screenshot handler for HTTP request
|
||||
// http://192.168.15.1/api/user/OBP60Task/screenshot
|
||||
@@ -720,39 +501,41 @@ void OBP60Task(GwApi *api){
|
||||
doImageRequest(api, &pageNumber, pages, request);
|
||||
});
|
||||
|
||||
//now we have prepared the page data
|
||||
//we start a separate task that will fetch our keys...
|
||||
MyData allParameters;
|
||||
allParameters.logger=api->getLogger();
|
||||
allParameters.page0=3;
|
||||
allParameters.queue=xQueueCreate(10,sizeof(int));
|
||||
allParameters.sensitivity= api->getConfig()->getInt(GwConfigDefinitions::tSensitivity);
|
||||
// now we have prepared the page data
|
||||
// we start a separate task that will fetch our keys...
|
||||
KbTaskData kbparams;
|
||||
kbparams.logger = api->getLogger();
|
||||
kbparams.queue = xQueueCreate(10, sizeof(int));
|
||||
kbparams.sensitivity = api->getConfig()->getInt(GwConfigDefinitions::tSensitivity);
|
||||
#ifdef BOARD_OBP40S3
|
||||
allParameters.use_syspage = syspage_enabled;
|
||||
kbparams.use_syspage = syspage_enabled;
|
||||
#endif
|
||||
xTaskCreate(keyboardTask,"keyboard",2000,&allParameters,configMAX_PRIORITIES-1,NULL);
|
||||
SharedData *shared=new SharedData(api);
|
||||
createKeyboardTask(&kbparams);
|
||||
// we start a separate task to collect sensor data
|
||||
SharedData *shared = new SharedData(api);
|
||||
createSensorTask(shared);
|
||||
|
||||
// Task Loop
|
||||
//####################################################################################
|
||||
|
||||
// Configuration values for main loop
|
||||
String gpsFix = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString();
|
||||
String gpsOn=api->getConfig()->getConfigItem(api->getConfig()->useGPS,true)->asString();
|
||||
float tz = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asFloat();
|
||||
String gpsFix = config->getConfigItem(config->flashLED,true)->asString();
|
||||
String gpsOn = config->getConfigItem(config->useGPS,true)->asString();
|
||||
float tz = config->getConfigItem(config->timeZone,true)->asFloat();
|
||||
|
||||
commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight,true)->asString());
|
||||
commonData.backlight.color = colorMapping(config->getConfigItem(config->blColor,true)->asString());
|
||||
commonData.backlight.brightness = 2.55 * uint(config->getConfigItem(config->blBrightness,true)->asInt());
|
||||
commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString();
|
||||
commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight, true)->asString());
|
||||
commonData.backlight.color = colorMapping(config->getConfigItem(config->blColor, true)->asString());
|
||||
commonData.backlight.brightness = 2.55 * uint(config->getConfigItem(config->blBrightness, true)->asInt());
|
||||
commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode, true)->asString();
|
||||
|
||||
bool uvoltage = api->getConfig()->getConfigItem(api->getConfig()->underVoltage,true)->asBoolean();
|
||||
String cpuspeed = api->getConfig()->getConfigItem(api->getConfig()->cpuSpeed,true)->asString();
|
||||
uint hdopAccuracy = uint(api->getConfig()->getConfigItem(api->getConfig()->hdopAccuracy,true)->asInt());
|
||||
bool uvoltage = config->getConfigItem(config->underVoltage, true)->asBoolean();
|
||||
float voffset = (config->getConfigItem(config->vOffset,true)->asString()).toFloat();
|
||||
float vslope = (config->getConfigItem(config->vSlope,true)->asString()).toFloat();
|
||||
String cpuspeed = config->getConfigItem(config->cpuSpeed, true)->asString();
|
||||
uint hdopAccuracy = uint(config->getConfigItem(config->hdopAccuracy, true)->asInt());
|
||||
|
||||
double homelat = commonData.config->getString(commonData.config->homeLAT).toDouble();
|
||||
double homelon = commonData.config->getString(commonData.config->homeLON).toDouble();
|
||||
double homelat = config->getString(config->homeLAT).toDouble();
|
||||
double homelon = config->getString(config->homeLON).toDouble();
|
||||
bool homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0;
|
||||
if (homevalid) {
|
||||
logger->logDebug(GwLog::LOG, "Home location set to lat=%f, lon=%f", homelat, homelon);
|
||||
@@ -785,6 +568,8 @@ void OBP60Task(GwApi *api){
|
||||
|
||||
pages[pageNumber].page->setupKeys(); // Initialize keys for first page
|
||||
|
||||
// listTasks(logger);
|
||||
|
||||
// Main loop runs with 100ms
|
||||
//####################################################################################
|
||||
|
||||
@@ -796,8 +581,11 @@ void OBP60Task(GwApi *api){
|
||||
bool keypressed = false;
|
||||
|
||||
// Undervoltage detection
|
||||
if(uvoltage == true){
|
||||
underVoltageDetection(api, commonData);
|
||||
if (uvoltage == true) {
|
||||
if (underVoltageDetection(voffset, vslope)) {
|
||||
LOG_DEBUG(GwLog::ERROR, "Undervoltage detected, shutting down!");
|
||||
underVoltageError(commonData);
|
||||
}
|
||||
}
|
||||
|
||||
// Set CPU speed after boot after 1min
|
||||
@@ -833,7 +621,7 @@ void OBP60Task(GwApi *api){
|
||||
|
||||
// Check the keyboard message
|
||||
int keyboardMessage=0;
|
||||
while (xQueueReceive(allParameters.queue,&keyboardMessage,0)){
|
||||
while (xQueueReceive(kbparams.queue,&keyboardMessage, 0)) {
|
||||
LOG_DEBUG(GwLog::LOG,"new key from keyboard %d",keyboardMessage);
|
||||
keypressed = true;
|
||||
|
||||
@@ -1019,10 +807,10 @@ void OBP60Task(GwApi *api){
|
||||
api->getStatus(commonData.status);
|
||||
|
||||
if (calcTrueWnds) {
|
||||
addTrueWind(api, &boatValues);
|
||||
trueWind.addTrueWind(api, &boatValues, logger);
|
||||
}
|
||||
// Handle history buffers for TWD, TWS for wind plot page and other usage
|
||||
handleHstryBuf(api, &boatValues, hstryBufList, simulation);
|
||||
hstryBufList.handleHstryBuf(useSimuData);
|
||||
|
||||
// Clear display
|
||||
// epd->fillRect(0, 0, epd->width(), epd->height(), commonData.bgcolor);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
#include "GwApi.h"
|
||||
//we only compile for some boards
|
||||
@@ -41,5 +42,24 @@
|
||||
#ifdef BOARD_OBP40S3
|
||||
DECLARE_CAPABILITY(obp40,true)
|
||||
#endif
|
||||
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp60-v2-docu.readthedocs.io/de/latest/"); // Link to help pages
|
||||
#ifdef BOARD_OBP60S3
|
||||
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp60-v2-docu.readthedocs.io/en/latest/"); // Link to help pages
|
||||
#endif
|
||||
#ifdef BOARD_OBP40S3
|
||||
DECLARE_STRING_CAPABILITY(HELP_URL, "https://obp40-v1-docu.readthedocs.io/en/latest/"); // Link to help pages
|
||||
#endif
|
||||
|
||||
class BoatValueList{
|
||||
public:
|
||||
static const int MAXVALUES=100;
|
||||
//we create a list containing all our BoatValues
|
||||
//this is the list we later use to let the api fill all the values
|
||||
//additionally we put the necessary values into the paga data - see below
|
||||
GwApi::BoatValue *allBoatValues[MAXVALUES];
|
||||
int numValues=0;
|
||||
|
||||
bool addValueToList(GwApi::BoatValue *v);
|
||||
//helper to ensure that each BoatValue is only queried once
|
||||
GwApi::BoatValue *findValueOrCreate(String name);
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -22,14 +22,11 @@ lib_deps =
|
||||
Wire
|
||||
SPI
|
||||
ESP32time
|
||||
esphome/AsyncTCP-esphome@2.0.1
|
||||
robtillaart/PCF8574@0.3.9
|
||||
adafruit/Adafruit Unified Sensor @ 1.1.13
|
||||
blemasle/MCP23017@2.0.0
|
||||
adafruit/Adafruit BusIO@1.5.0
|
||||
adafruit/Adafruit GFX Library@1.11.9
|
||||
#zinggjm/GxEPD2@1.5.8
|
||||
#https://github.com/ZinggJM/GxEPD2
|
||||
https://github.com/thooge/GxEPD2
|
||||
sstaub/Ticker@4.4.0
|
||||
adafruit/Adafruit BMP280 Library@2.6.2
|
||||
@@ -54,6 +51,8 @@ build_flags=
|
||||
# -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_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
|
||||
# -D ENABLE_TRUEWIND # calculate true wind data (default off)
|
||||
# -D ENABLE_CALIBRATION # boat data calibration (default off)
|
||||
${env.build_flags}
|
||||
#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
|
||||
@@ -73,14 +72,11 @@ lib_deps =
|
||||
Wire
|
||||
SPI
|
||||
ESP32time
|
||||
esphome/AsyncTCP-esphome@2.0.1
|
||||
robtillaart/PCF8574@0.3.9
|
||||
adafruit/Adafruit Unified Sensor @ 1.1.13
|
||||
blemasle/MCP23017@2.0.0
|
||||
adafruit/Adafruit BusIO@1.5.0
|
||||
adafruit/Adafruit GFX Library@1.11.9
|
||||
#zinggjm/GxEPD2@1.5.8
|
||||
#https://github.com/ZinggJM/GxEPD2
|
||||
https://github.com/thooge/GxEPD2
|
||||
sstaub/Ticker@4.4.0
|
||||
adafruit/Adafruit BMP280 Library@2.6.2
|
||||
@@ -98,8 +94,10 @@ build_flags=
|
||||
-D HARDWARE_V10 #OBP40 hardware revision V1.0 SKU:DIE07300S V1.1 (CrowPanel 4.2)
|
||||
-D DISPLAY_GDEY042T81 #new E-Ink display from Good Display (Waveshare), R10 2.2 ohm - good (contast lost by shunshine)
|
||||
#-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 VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
|
||||
# -D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
|
||||
# -D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
|
||||
# -D ENABLE_TRUEWIND # calculate true wind data (default off)
|
||||
# -D ENABLE_CALIBRATION # boat data calibration (default off)
|
||||
${env.build_flags}
|
||||
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
|
||||
|
||||
17
lib/obp60task/utils/auto_version.py
Normal file
17
lib/obp60task/utils/auto_version.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import subprocess
|
||||
|
||||
# Import("env")
|
||||
|
||||
def get_firmware_specifier_build_flag():
|
||||
#ret = subprocess.run(["git", "describe"], stdout=subprocess.PIPE, text=True) #Uses only annotated tags
|
||||
ret = subprocess.run(["git", "describe", "--tags"], stdout=subprocess.PIPE, text=True) #Uses any tags
|
||||
build_version = ret.stdout.strip()
|
||||
build_flag = "-D AUTO_VERSION=\\\"" + build_version + "\\\""
|
||||
print ("Firmware Revision: " + build_version)
|
||||
return (build_flag)
|
||||
|
||||
#env.Append(
|
||||
# BUILD_FLAGS=[get_firmware_specifier_build_flag()]
|
||||
#)
|
||||
|
||||
get_firmware_specifier_build_flag()
|
||||
61
lib/obp60task/utils/xbmconvert.py
Executable file
61
lib/obp60task/utils/xbmconvert.py
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Convert a Gimp-created XBM file to bitmap useable by drawBitmap()
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
from PIL import Image
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: xbmconvert.py <filename>")
|
||||
sys.exit(1)
|
||||
|
||||
xbmfilename = sys.argv[1]
|
||||
if not os.path.isfile(xbmfilename):
|
||||
print(f"The file '{xbmfilename}' does not exists.")
|
||||
sys.exit(1)
|
||||
|
||||
im = Image.open(xbmfilename)
|
||||
imname = "image"
|
||||
with open(xbmfilename, 'r') as fh:
|
||||
pattern = r'static\s+unsigned\s+char\s+(\w+)_bits$$$$'
|
||||
for line in fh:
|
||||
match = re.search(pattern, line)
|
||||
if match:
|
||||
imname = match.group(1)
|
||||
break
|
||||
bytecount = int(im.width * im.height / 8)
|
||||
|
||||
print(f"#ifndef _{imname.upper()}_H_")
|
||||
print(f"#define _{imname.upper()}_H_ 1\n")
|
||||
print(f"#define {imname}_width {im.width}")
|
||||
print(f"#define {imname}_height {im.height}")
|
||||
print(f"const unsigned char {imname}_bits[{bytecount}] PROGMEM = {{")
|
||||
|
||||
n = 0
|
||||
print(" ", end='')
|
||||
f = im.tobytes()
|
||||
|
||||
switched_bytes = bytearray()
|
||||
for i in range(0, len(f), 2):
|
||||
# Switch LSB and MSB
|
||||
switched_bytes.append(f[i + 1]) # Append MSB
|
||||
switched_bytes.append(f[i]) # Append LSB
|
||||
|
||||
#for b in im.tobytes():
|
||||
for b in switched_bytes:
|
||||
#b2 = 0
|
||||
#for i in range(8):
|
||||
# # b2 |= ((b >> i) & 1) << (7 - i)
|
||||
# b2 <<= 1
|
||||
# b2 |= b & 1
|
||||
# b >>= 1
|
||||
n += 1
|
||||
print(f"0x{b:02x}", end='')
|
||||
if n < bytecount:
|
||||
print(', ', end='')
|
||||
if n % 12 == 0:
|
||||
print("\n ", end='')
|
||||
print("};\n\n#endif")
|
||||
@@ -21,8 +21,8 @@ lib_deps =
|
||||
ttlappalainen/NMEA2000-library @ 4.22.0
|
||||
ttlappalainen/NMEA0183 @ 1.10.1
|
||||
ArduinoJson @ 6.18.5
|
||||
AsyncTCP-esphome @ 2.0.1
|
||||
ottowinter/ESPAsyncWebServer-esphome@2.0.1
|
||||
ESP32Async/AsyncTCP @ 3.4.7
|
||||
ESP32Async/ESPAsyncWebServer @ 3.8.0
|
||||
FS
|
||||
Preferences
|
||||
ESPmDNS
|
||||
@@ -56,6 +56,8 @@ lib_ldf_mode = off
|
||||
monitor_speed = 115200
|
||||
build_flags =
|
||||
-D PIO_ENV_BUILD=$PIOENV
|
||||
# -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 # async_tcp task core assignment (default is any core)
|
||||
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096 # reduce the stack size (default is 16K)
|
||||
-std=gnu++17
|
||||
build_unflags =
|
||||
-std=gnu++11
|
||||
|
||||
Reference in New Issue
Block a user