1
0
mirror of https://github.com/thooge/esp32-nmea2000-obp60.git synced 2025-12-28 05:03:06 +01:00

15 Commits

59 changed files with 1956 additions and 1948 deletions

View File

@@ -205,6 +205,11 @@ def generateCfg(inFile,outFile,impl):
secret="false"; secret="false";
if item.get('type') == 'password': if item.get('type') == 'password':
secret="true" 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+=" new GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret)
data+='}\n' data+='}\n'
writeFileIfChanged(outFile,data) writeFileIfChanged(outFile,data)
@@ -505,6 +510,8 @@ def prebuild(env):
env.Append(CPPDEFINES=[('GWDEVVERSION',version)]) env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
def cleangenerated(source, target, env): def cleangenerated(source, target, env):
# TODO source / target order?
print("CLEAN: {} - {}".format(source, target))
od=outPath() od=outPath()
if os.path.isdir(od): if os.path.isdir(od):
print("#cleaning up %s"%od) print("#cleaning up %s"%od)
@@ -514,7 +521,6 @@ def cleangenerated(source, target, env):
fn=os.path.join(od,f) fn=os.path.join(od,f)
os.unlink(f) os.unlink(f)
print("#prescript...") print("#prescript...")
prebuild(env) prebuild(env)
board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper() board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper()

View File

@@ -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)

View File

@@ -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)

View File

@@ -12,6 +12,7 @@ public:
static int getType(const double &x) { return GWTYPE_DOUBLE; } static int getType(const double &x) { return GWTYPE_DOUBLE; }
static int getType(const String &x) { return GWTYPE_STRING; } static int getType(const String &x) { return GWTYPE_STRING; }
static int getType(const GwSatInfoList &x) { return GWTYPE_USER + 1; } 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 bool GwBoatItemBase::isValid(unsigned long now) const
@@ -289,6 +290,7 @@ template class GwBoatItem<uint32_t>;
template class GwBoatItem<uint16_t>; template class GwBoatItem<uint16_t>;
template class GwBoatItem<int16_t>; template class GwBoatItem<int16_t>;
template class GwBoatItem<String>; template class GwBoatItem<String>;
void GwSatInfoList::houseKeeping(unsigned long ts) void GwSatInfoList::houseKeeping(unsigned long ts)
{ {
if (ts == 0) if (ts == 0)
@@ -302,6 +304,7 @@ void GwSatInfoList::houseKeeping(unsigned long ts)
}), }),
sats.end()); sats.end());
} }
void GwSatInfoList::update(GwSatInfo entry, unsigned long validTill) void GwSatInfoList::update(GwSatInfo entry, unsigned long validTill)
{ {
entry.validTill = validTill; entry.validTill = validTill;
@@ -344,6 +347,63 @@ void GwBoatDataSatList::toJsonDoc(GwJsonDocument *doc, unsigned long minTime)
GwBoatItem<GwSatInfoList>::toJsonDoc(doc, 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) GwBoatData::GwBoatData(GwLog *logger, GwConfigHandler *cfg)
{ {
this->logger = logger; this->logger = logger;
@@ -513,6 +573,11 @@ bool convertToJson(const GwSatInfoList &si, JsonVariant &variant)
return variant.set(si.getNumSats()); return variant.set(si.getNumSats());
} }
bool convertToJson(const GwAisTargetList &si, JsonVariant &variant)
{
return variant.set(si.getNumTargets());
}
#ifdef _UNDEF #ifdef _UNDEF
#include <ArduinoJson/Json/TextFormatter.hpp> #include <ArduinoJson/Json/TextFormatter.hpp>

View File

@@ -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 class GwBoatItemNameProvider
{ {
public: public:
@@ -256,6 +305,7 @@ class GwBoatData{
GWBOATDATA(double,WPLon,formatLongitude) // waypoint longitude GWBOATDATA(double,WPLon,formatLongitude) // waypoint longitude
GWBOATDATA(String,WPName,formatName) // waypoint name GWBOATDATA(String,WPName,formatName) // waypoint name
GWSPECBOATDATA(GwBoatDataSatList,SatInfo,GwSatInfoList::toType,formatFixed0); GWSPECBOATDATA(GwBoatDataSatList,SatInfo,GwSatInfoList::toType,formatFixed0);
GWSPECBOATDATA(GwBoatDataAisList,AisTarget,GwAisTargetList::toType,formatFixed0);
public: public:
GwBoatData(GwLog *logger, GwConfigHandler *cfg); GwBoatData(GwLog *logger, GwConfigHandler *cfg);
~GwBoatData(); ~GwBoatData();

View File

@@ -79,7 +79,7 @@ GwUpdate::GwUpdate(GwLog *log, GwWebServer *webserver, PasswordChecker ckr)
} }
if (!param->hasError()) if (!param->hasError())
{ {
AsyncWebParameter *hash=request->getParam("_hash"); const AsyncWebParameter *hash=request->getParam("_hash");
if (! hash){ if (! hash){
hash=request->getParam("_hash",true); hash=request->getParam("_hash",true);
} }
@@ -141,4 +141,4 @@ GwUpdate::GwUpdate(GwLog *log, GwWebServer *webserver, PasswordChecker ckr)
} }
} }
}); });
} }

View File

@@ -27,7 +27,7 @@ void sendEmbeddedFile(String name,String contentType,AsyncWebServerRequest *requ
std::map<String,EmbeddedFile*>::iterator it=embeddedFiles.find(name); std::map<String,EmbeddedFile*>::iterator it=embeddedFiles.find(name);
if (it != embeddedFiles.end()){ if (it != embeddedFiles.end()){
EmbeddedFile* found=it->second; 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")); response->addHeader(F("Content-Encoding"), F("gzip"));
request->send(response); request->send(response);
} }

View File

@@ -101,7 +101,7 @@ void CalibrationDataList::readConfig(GwConfigHandler* config, GwLog* logger)
calibMap[instance].slope = slope; calibMap[instance].slope = slope;
calibMap[instance].smooth = smooth; calibMap[instance].smooth = smooth;
calibMap[instance].isCalibrated = false; 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); calibMap[instance].offset, calibMap[instance].slope, calibMap[instance].smooth);
} }
logger->logDebug(GwLog::LOG, "all calibration data read"); logger->logDebug(GwLog::LOG, "all calibration data read");
@@ -117,7 +117,7 @@ void CalibrationDataList::calibrateInstance(GwApi::BoatValue* boatDataValue, GwL
std::string format = ""; std::string format = "";
if (calibMap.find(instance) == calibMap.end()) { 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; return;
} else if (!boatDataValue->valid) { // no valid boat data value, so we don't want to apply calibration data } else if (!boatDataValue->valid) { // no valid boat data value, so we don't want to apply calibration data
calibMap[instance].isCalibrated = false; calibMap[instance].isCalibrated = false;

View File

@@ -3,7 +3,7 @@
#ifndef _BOATDATACALIBRATION_H #ifndef _BOATDATACALIBRATION_H
#define _BOATDATACALIBRATION_H #define _BOATDATACALIBRATION_H
#include "Pagedata.h" #include "GwApi.h"
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@@ -30,4 +30,4 @@ private:
extern CalibrationDataList calibrationData; // this list holds all calibration data extern CalibrationDataList calibrationData; // this list holds all calibration data
#endif #endif

View File

@@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <FreeRTOS.h> #include <FreeRTOS.h>
#include "LedSpiTask.h" #include "LedSpiTask.h"
#include "GwHardware.h" #include "GwHardware.h"
@@ -251,6 +252,11 @@ void handleSpiLeds(void *param){
vTaskDelete(NULL); vTaskDelete(NULL);
} }
void createSpiLedTask(LedTaskData *param){ void createSpiLedTask(LedTaskData *param) {
xTaskCreate(handleSpiLeds,"handleLeds",4000,param,3,NULL); 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);
};
} }

View File

@@ -25,6 +25,7 @@
#include "fonts/Ubuntu_Bold20pt8b.h" #include "fonts/Ubuntu_Bold20pt8b.h"
#include "fonts/Ubuntu_Bold32pt8b.h" #include "fonts/Ubuntu_Bold32pt8b.h"
#include "fonts/Atari16px8b.h" // Key label font #include "fonts/Atari16px8b.h" // Key label font
#include "fonts/Atari6px8b.h" // Very small (6x6) font
// E-Ink Display // E-Ink Display
#define GxEPD_WIDTH 400 // Display width #define GxEPD_WIDTH 400 // Display width
@@ -81,7 +82,7 @@ void hardwareInit(GwApi *api)
Wire.begin(); Wire.begin();
// Init PCF8574 digital outputs // 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 if(pcf8574_Out.begin()){ // Initialize PCF8574
pcf8574_Out.write8(255); // Clear all outputs pcf8574_Out.write8(255); // Clear all outputs
} }
@@ -212,8 +213,8 @@ void deepSleep(CommonData &common){
setFlashLED(false); // Flash LED Off setFlashLED(false); // Flash LED Off
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
// Shutdown EInk display // Shutdown EInk display
epd->setFullWindow(); // Set full Refresh epd->setFullWindow(); // Set full Refresh
epd->fillScreen(common.bgcolor); // Clear screen epd->fillScreen(common.bgcolor); // Clear screen
epd->setTextColor(common.fgcolor); epd->setTextColor(common.fgcolor);
epd->setFont(&Ubuntu_Bold20pt8b); epd->setFont(&Ubuntu_Bold20pt8b);
epd->setCursor(85, 150); epd->setCursor(85, 150);
@@ -221,8 +222,8 @@ void deepSleep(CommonData &common){
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b);
epd->setCursor(65, 175); epd->setCursor(65, 175);
epd->print("To wake up press key and wait 5s"); epd->print("To wake up press key and wait 5s");
epd->nextPage(); // Update display contents epd->nextPage(); // Update display contents
epd->powerOff(); // Display power off epd->powerOff(); // Display power off
setPortPin(OBP_POWER_50, false); // Power off ePaper display setPortPin(OBP_POWER_50, false); // Power off ePaper display
// Stop system // Stop system
esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin
@@ -346,11 +347,14 @@ void setBuzzerPower(uint power){
buzzerpower = power; buzzerpower = power;
} }
// Delete xdr prefix from string // Delete xdr prefix from string and optional limit length
String xdrDelete(String input){ String xdrDelete(String input, uint8_t maxlen) {
if(input.substring(0,3) == "xdr"){ if (input.substring(0, 3) == "xdr") {
input = input.substring(3, input.length()); input = input.substring(3, input.length());
} }
if (maxlen > 0) {
return input.substring(0, maxlen);
}
return input; return input;
} }
@@ -474,6 +478,7 @@ void displayHeader(CommonData &commonData, bool symbolmode, GwApi::BoatValue *da
uint16_t symbol_x = 2; uint16_t symbol_x = 2;
static const uint16_t symbol_offset = 20; static const uint16_t symbol_offset = 20;
// TODO invert and get rid of the if
if(commonData.config->getBool(commonData.config->statusLine) == true){ if(commonData.config->getBool(commonData.config->statusLine) == true){
// Show status info // Show status info
@@ -583,7 +588,7 @@ void displayHeader(CommonData &commonData, bool symbolmode, GwApi::BoatValue *da
heartbeat = !heartbeat; heartbeat = !heartbeat;
// Date and time // 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); String timesource = commonData.config->getString(commonData.config->timeSource);
double tz = commonData.config->getString(commonData.config->timeZone).toDouble(); double tz = commonData.config->getString(commonData.config->timeZone).toDouble();
epd->setTextColor(commonData.fgcolor); epd->setTextColor(commonData.fgcolor);
@@ -594,7 +599,7 @@ void displayHeader(CommonData &commonData, bool symbolmode, GwApi::BoatValue *da
if (commonData.data.rtcValid) { if (commonData.data.rtcValid) {
time_t tv = mktime(&commonData.data.rtcTime) + (int)(tz * 3600); time_t tv = mktime(&commonData.data.rtcTime) + (int)(tz * 3600);
struct tm *local_tm = localtime(&tv); 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(" ");
epd->print(formatDate(fmttype, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday)); epd->print(formatDate(fmttype, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday));
epd->print(" "); 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); 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); response->addHeader("Content-Disposition", "inline; filename=" + filename);
request->send(response); request->send(response);

View File

@@ -55,6 +55,7 @@ extern const GFXfont Ubuntu_Bold16pt8b;
extern const GFXfont Ubuntu_Bold20pt8b; extern const GFXfont Ubuntu_Bold20pt8b;
extern const GFXfont Ubuntu_Bold32pt8b; extern const GFXfont Ubuntu_Bold32pt8b;
extern const GFXfont Atari16px; extern const GFXfont Atari16px;
extern const GFXfont Atari6px;
// Global functions // Global functions
#ifdef DISPLAY_GDEW042T2 #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 buzzer(uint frequency, uint duration); // Buzzer function
void setBuzzerPower(uint power); // Set buzzer power 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 drawTextCenter(int16_t cx, int16_t cy, String text);
void drawTextRalign(int16_t x, int16_t y, String text); void drawTextRalign(int16_t x, int16_t y, String text);

View File

@@ -20,6 +20,7 @@ Formatter::Formatter(GwConfigHandler *config) {
windspeedFormat = config->getString(config->windspeedFormat); windspeedFormat = config->getString(config->windspeedFormat);
tempFormat = config->getString(config->tempFormat); tempFormat = config->getString(config->tempFormat);
dateFormat = config->getString(config->dateFormat); dateFormat = config->getString(config->dateFormat);
dateFmt = getDateFormat(dateFormat);
usesimudata = config->getBool(config->useSimuData); usesimudata = config->getBool(config->useSimuData);
precision = config->getString(config->valueprecision); 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){ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &commondata){
GwLog *logger = commondata.logger; GwLog *logger = commondata.logger;
FormattedData result; FormattedData result;
@@ -43,7 +75,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
// If boat value not valid // If boat value not valid
if (! value->valid && !usesimudata){ if (! value->valid && !usesimudata){
result.svalue = "---"; result.svalue = placeholder;
return result; return result;
} }
@@ -86,12 +118,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
else{ else{
snprintf(buffer, bsize, "01.01.2022"); snprintf(buffer, bsize, "01.01.2022");
} }
if(timeZone == 0){ result.unit = ((timeZone == 0) ? "UTC" : "LOT");
result.unit = "UTC";
}
else{
result.unit = "LOT";
}
} }
//######################################################## //########################################################
else if(value->getFormat() == "formatTime"){ 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)); snprintf(buffer, bsize, "11:36:%02i", int(sec));
lasttime = millis(); lasttime = millis();
} }
if(timeZone == 0){ result.unit = ((timeZone == 0) ? "UTC" : "LOT");
result.unit = "UTC";
}
else{
result.unit = "LOT";
}
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatFixed0"){ else if (value->getFormat() == "formatFixed0"){
@@ -286,13 +308,13 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
if (rotation < -100){ if (rotation < -100){
rotation = -99; rotation = -99;
} }
if (rotation > 100){ else if (rotation > 100){
rotation = 99; rotation = 99;
} }
if (rotation > -10 && rotation < 10){ if (rotation > -10 && rotation < 10){
snprintf(buffer, bsize, "%3.2f", rotation); snprintf(buffer, bsize, "%3.2f", rotation);
} }
if (rotation <= -10 || rotation >= 10){ else {
snprintf(buffer, bsize, "%3.0f", rotation); snprintf(buffer, bsize, "%3.0f", rotation);
} }
} }
@@ -330,12 +352,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
String latdir = ""; String latdir = "";
float degree = abs(int(lat)); float degree = abs(int(lat));
float minute = abs((lat - int(lat)) * 60); float minute = abs((lat - int(lat)) * 60);
if (lat > 0){ latdir = (lat > 0) ? "N" : "S";
latdir = "N";
}
else {
latdir = "S";
}
latitude = String(degree,0) + "\x90 " + String(minute,4) + "' " + latdir; latitude = String(degree,0) + "\x90 " + String(minute,4) + "' " + latdir;
result.unit = ""; result.unit = "";
strcpy(buffer, latitude.c_str()); strcpy(buffer, latitude.c_str());
@@ -354,12 +371,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
String londir = ""; String londir = "";
float degree = abs(int(lon)); float degree = abs(int(lon));
float minute = abs((lon - int(lon)) * 60); float minute = abs((lon - int(lon)) * 60);
if (lon > 0){ londir = (lon > 0) ? "E" : "W";
londir = "E";
}
else {
londir = "W";
}
longitude = String(degree,0) + "\x90 " + String(minute,4) + "' " + londir; longitude = String(degree,0) + "\x90 " + String(minute,4) + "' " + londir;
result.unit = ""; result.unit = "";
strcpy(buffer, longitude.c_str()); strcpy(buffer, longitude.c_str());
@@ -381,7 +393,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
depth = rawvalue; depth = rawvalue;
} }
if(String(lengthFormat) == "ft"){ if(String(lengthFormat) == "ft"){
depth = depth * 3.28084; depth = depth * 3.28084; // TODO use global defined factor
result.unit = "ft"; result.unit = "ft";
} }
else{ else{
@@ -411,7 +423,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
xte = xte * 0.001; xte = xte * 0.001;
result.unit = "km"; result.unit = "km";
} else if (distanceFormat == "nm") { } else if (distanceFormat == "nm") {
xte = xte * 0.000539957; xte = xte * 0.000539957; // TODO use global defined factor
result.unit = "nm"; result.unit = "nm";
} else { } else {
result.unit = "m"; result.unit = "m";
@@ -447,7 +459,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
else{ else{
result.unit = "K"; result.unit = "K";
} }
if(temp < 10) { if (temp < 10) {
snprintf(buffer, bsize, fmt_dec_1, temp); snprintf(buffer, bsize, fmt_dec_1, temp);
} }
else if (temp < 100) { else if (temp < 100) {
@@ -473,7 +485,7 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
result.unit = "km"; result.unit = "km";
} }
else if (String(distanceFormat) == "nm") { else if (String(distanceFormat) == "nm") {
distance = distance * 0.000539957; distance = distance * 0.000539957; // TODO use global defined factor
result.unit = "nm"; result.unit = "nm";
} }
else { else {
@@ -802,32 +814,36 @@ FormattedData Formatter::formatValue(GwApi::BoatValue *value, CommonData &common
return result; 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]; char buffer[12];
if (fmttype == "GB") { if (fmttype == fmtDate::GB) {
snprintf(buffer, 12, "%02d/%02d/%04d", day , month, year); 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); 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); snprintf(buffer, 12, "%04d-%02d-%02d", year, month, day);
} }
else { else if (fmttype == fmtDate::DE) {
snprintf(buffer, 12, "%02d.%02d.%04d", day, month, year); snprintf(buffer, 12, "%02d.%02d.%04d", day, month, year);
} else {
snprintf(buffer, 12, "%04d-%02d-%02d", year, month, day);
} }
return String(buffer); return String(buffer);
} }
String formatTime(char fmttype, uint8_t hour, uint8_t minute, uint8_t second) { String formatTime(fmtTime fmttype, uint8_t hour, uint8_t minute, uint8_t second) {
// fmttype: s: with seconds, m: only minutes
char buffer[10]; char buffer[10];
if (fmttype == 'm') { if (fmttype == fmtTime::MMHH) {
snprintf(buffer, 10, "%02d:%02d", hour , minute); snprintf(buffer, 10, "%02d:%02d", hour , minute);
} }
else { else if (fmttype == fmtTime::MMHHSS) {
snprintf(buffer, 10, "%02d:%02d:%02d", hour, minute, second); snprintf(buffer, 10, "%02d:%02d:%02d", hour, minute, second);
} }
else {
snprintf(buffer, 10, "%02d%02d%02d", hour, minute, second);
}
return String(buffer); return String(buffer);
} }

View File

@@ -2,41 +2,12 @@
#ifndef _OBP60FORMATTER_H #ifndef _OBP60FORMATTER_H
#define _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 XDR types
A Angular displacement A Angular displacement
C Temperature 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 // 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 fmtTime {MMHH, MMHHSS};
enum class fmtLength {METER, FEET, FATHOM, CABLE}; enum class fmtLength {METER, FEET, FATHOM, CABLE};
enum class fmtDepth {METER, FEET, FATHOM}; enum class fmtDepth {METER, FEET, FATHOM};
@@ -106,6 +146,7 @@ private:
String windspeedFormat = "kn"; // [m/s|km/h|kn|bft] String windspeedFormat = "kn"; // [m/s|km/h|kn|bft]
String tempFormat = "C"; // [K|°C|°F] String tempFormat = "C"; // [K|°C|°F]
String dateFormat = "ISO"; // [DE|GB|US|ISO] String dateFormat = "ISO"; // [DE|GB|US|ISO]
fmtDate dateFmt;
bool usesimudata = false; // [on|off] bool usesimudata = false; // [on|off]
String precision = "2"; // [1|2] String precision = "2"; // [1|2]
@@ -115,12 +156,16 @@ private:
public: public:
Formatter(GwConfigHandler *config); Formatter(GwConfigHandler *config);
fmtType stringToFormat(const char* formatStr);
fmtDate getDateFormat(String sformat);
fmtTime getTimeFormat(String sformat);
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata); FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata);
String placeholder = "---";
}; };
// Standard format functions without overhead // Standard format functions without class and overhead
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);
String formatTime(char fmttype, uint8_t hour, uint8_t minute, uint8_t second); String formatTime(fmtTime fmttype, uint8_t hour, uint8_t minute, uint8_t second);
String formatLatitude(double lat); String formatLatitude(double lat);
String formatLongitude(double lon); String formatLongitude(double lon);

View File

@@ -1,5 +1,147 @@
#include "OBPDataOperations.h" #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) double WindUtils::to2PI(double a)
{ {
a = fmod(a, 2 * M_PI); a = fmod(a, 2 * M_PI);
@@ -68,29 +210,25 @@ void WindUtils::calcTwdSA(const double* AWA, const double* AWS,
double awd = *AWA + *HDT; double awd = *AWA + *HDT;
awd = to2PI(awd); awd = to2PI(awd);
double stw = -*STW; 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); addPolar(&awd, AWS, CTW, &stw, TWD, TWS);
// Normalize TWD and TWA to 0-360° // Normalize TWD and TWA to 0-360°
*TWD = to2PI(*TWD); *TWD = to2PI(*TWD);
*TWA = toPI(*TWD - *HDT); *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 WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal)
{ {
double hdt; double hdt;
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor 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_MAX) {
if (*hdmVal != DBL_MIN) { 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 = *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); 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 hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise
} else { } 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; return hdt;
@@ -103,37 +241,23 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal,
double stw, hdt, ctw; double stw, hdt, ctw;
double twd, tws, twa; double twd, tws, twa;
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor 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_MAX) {
/* 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) {
hdt = *hdtVal; // Use HDT if available hdt = *hdtVal; // Use HDT if available
} else { } else {
hdt = calcHDT(hdmVal, varVal, cogVal, sogVal); 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 ctw = *cogVal; // Use COG for CTW if available
} else { } else {
ctw = hdt; // 2nd approximation for CTW; hdt must exist if we reach this part of the code 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 stw = *stwVal; // Use STW if available
} else if (*sogVal != DBL_MIN) { } else if (*sogVal != DBL_MAX) {
stw = *sogVal; stw = *sogVal;
} else { } else {
// If STW and SOG are not available, we cannot calculate true wind // 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)); // 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 // Cannot calculate true wind without valid AWA, AWS; other checks are done earlier
return false; return false;
} else { } else {
@@ -153,3 +277,46 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal,
return true; 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 --------------

View File

@@ -1,39 +1,90 @@
#pragma once #pragma once
#include "GwApi.h" #include <N2kMessages.h>
#include "OBPRingBuffer.h" #include "OBPRingBuffer.h"
// #include <Arduino.h> #include "BoatDataCalibration.h" // Functions lib for data instance calibration
#include "obp60task.h"
#include <math.h> #include <math.h>
typedef struct { typedef struct {
RingBuffer<int16_t>* twdHstry; RingBuffer<int16_t>* twdHstry;
RingBuffer<int16_t>* twsHstry; RingBuffer<uint16_t>* twsHstry;
RingBuffer<int16_t>* awdHstry; RingBuffer<int16_t>* awdHstry;
RingBuffer<int16_t>* awsHstry; RingBuffer<uint16_t>* awsHstry;
} tBoatHstryData; // Holds pointers to all history buffers for boat data } tBoatHstryData; // Holds pointers to all history buffers for boat data
class HstryBuf { 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: 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 { 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: 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 to2PI(double a);
static double toPI(double a); static double toPI(double a);
static double to360(double a); static double to360(double a);
static double to180(double a); static double to180(double a);
static void toCart(const double* phi, const double* r, double* x, double* y); 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); void toPol(const double* x, const double* y, double* phi, double* r);
static void addPolar(const double* phi1, const double* r1, void addPolar(const double* phi1, const double* r1,
const double* phi2, const double* r2, const double* phi2, const double* r2,
double* phi, double* r); 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, const double* CTW, const double* STW, const double* HDT,
double* TWD, double* TWS, double* TWA); double* TWD, double* TWS, double* TWA);
static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal); 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* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal); const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal);
}; bool addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog *log);
};

View File

@@ -1,9 +1,10 @@
#ifndef _OBP60FUNCTIONS_H // SPDX-License-Identifier: GPL-2.0-or-later
#define _OBP60FUNCTIONS_H #if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#include <Arduino.h> #include <Arduino.h>
#include "OBP60Hardware.h" #include "OBP60Hardware.h"
#include "OBP60Extensions.h" // for buzzer
#include "OBPKeyboardTask.h"
// Global vars // Global vars
// Touch keypad over ESP32 touch sensor inputs // Touch keypad over ESP32 touch sensor inputs
@@ -58,10 +59,10 @@ void initKeys(CommonData &commonData) {
commonData.keydata[5].h = height; commonData.keydata[5].h = height;
} }
#ifdef HARDWARE_V21 #ifdef HARDWARE_V21
// Keypad functions for original OBP60 hardware // Keypad functions for original OBP60 hardware
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) { int readKeypad(GwLog* logger, uint thSensitivity) {
// Touch sensor values // Touch sensor values
// 35000 - Not touched // 35000 - Not touched
// 50000 - Light toched with fingertip // 50000 - Light toched with fingertip
@@ -233,35 +234,35 @@ void initKeys(CommonData &commonData) {
keycodeold2 = keycode2; keycodeold2 = keycode2;
return keystatus; return keystatus;
} }
#endif #endif
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
int readSensorpads(){ int readSensorpads(){
// Read key code // Read key code
if(digitalRead(UP) == LOW){ if (digitalRead(UP) == LOW) {
keycode = 10; // Left swipe 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) // Keypad functions for OBP60 clone (thSensitivity is inactiv)
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) { int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {
pinMode(UP, INPUT); pinMode(UP, INPUT);
pinMode(DOWN, INPUT); pinMode(DOWN, INPUT);
pinMode(CONF, INPUT); pinMode(CONF, INPUT);
@@ -273,31 +274,68 @@ void initKeys(CommonData &commonData) {
// Detect key // Detect key
if (keycode > 0 ){ if (keycode > 0 ){
if(keycode != keycodeold){ if(keycode != keycodeold){
starttime = millis(); // Start key pressed starttime = millis(); // Start key pressed
keycodeold = keycode; keycodeold = keycode;
} }
// If key pressed longer than 100ms // If key pressed longer than 100ms
if(millis() > starttime + 100 && keycode == keycodeold) { if(millis() > starttime + 100 && keycode == keycodeold) {
if (use_syspage and keycode == 3) { if (use_syspage and keycode == 3) {
keystatus = 12; keystatus = 12;
} else { } else {
keystatus = keycode; 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{ else {
keycode = 0; keycode = 0;
keycodeold = 0; keycodeold = 0;
keystatus = 0; keystatus = 0;
} }
return keystatus; 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 #endif

View 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);

View File

@@ -9,28 +9,32 @@
template <typename T> template <typename T>
class RingBuffer { class RingBuffer {
private: private:
mutable SemaphoreHandle_t bufLocker; std::vector<T> buffer; // THE buffer vector
std::vector<T> buffer;
size_t capacity; size_t capacity;
size_t head; // Points to the next insertion position size_t head; // Points to the next insertion position
size_t first; // Points to the first (oldest) valid element size_t first; // Points to the first (oldest) valid element
size_t last; // Points to the last (newest) valid element size_t last; // Points to the last (newest) valid element
size_t count; // Number of valid elements currently in buffer 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 bool is_Full; // Indicates that all buffer elements are used and ringing is in use
T MIN_VAL; // lowest possible value of buffer T MIN_VAL; // lowest possible value of buffer of type <T>
T MAX_VAL; // highest 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 // metadata for buffer
String dataName; // Name of boat data in buffer String dataName; // Name of boat data in buffer
String dataFmt; // Format of boat data in buffer String dataFmt; // Format of boat data in buffer
int updFreq; // Update frequency in milliseconds int updFreq; // Update frequency in milliseconds
T smallest; // Value range of buffer: smallest value T smallest; // Value range of buffer: smallest value; needs to be => MIN_VAL
T largest; // Value range of buffer: biggest value T largest; // Value range of buffer: biggest value; needs to be < MAX_VAL, since MAX_VAL indicates invalid entries
void initCommon();
public: public:
RingBuffer();
RingBuffer(size_t size); RingBuffer(size_t size);
void setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue); // Set meta data for buffer 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, int& updateFrequency, T& minValue, T& maxValue); // Get meta data of buffer
bool getMetaData(String& name, String& format);
String getName() const; // Get buffer name String getName() const; // Get buffer name
String getFormat() const; // Get buffer data format String getFormat() const; // Get buffer data format
void add(const T& value); // Add a new value to buffer 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 size_t getLastIdx() const; // Get the index of newest value in buffer
bool isEmpty() const; // Check if buffer is empty bool isEmpty() const; // Check if buffer is empty
bool isFull() const; // Check if buffer is full bool isFull() const; // Check if buffer is full
T getMinVal() const; // Get lowest possible value for buffer; used for initialized buffer data T getMinVal() const; // Get lowest possible value for buffer
T getMaxVal() const; // Get highest possible value for buffer T getMaxVal() const; // Get highest possible value for buffer; used for unset/invalid buffer data
void clear(); // Clear buffer 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()) 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 std::vector<T> getAllValues() const; // Get all current values as a vector
}; };
#include "OBPRingBuffer.tpp" #include "OBPRingBuffer.tpp"

View File

@@ -1,5 +1,30 @@
#include "OBPRingBuffer.h" #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> template <typename T>
RingBuffer<T>::RingBuffer(size_t size) RingBuffer<T>::RingBuffer(size_t size)
: capacity(size) : capacity(size)
@@ -9,23 +34,8 @@ RingBuffer<T>::RingBuffer(size_t size)
, count(0) , count(0)
, is_Full(false) , is_Full(false)
{ {
bufLocker = xSemaphoreCreateMutex(); initCommon();
buffer.resize(size, MAX_VAL); // MAX_VAL indicate invalid values
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;
} }
// Specify meta data of buffer content // Specify meta data of buffer content
@@ -57,6 +67,20 @@ bool RingBuffer<T>::getMetaData(String& name, String& format, int& updateFrequen
return true; 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 // Get buffer name
template <typename T> template <typename T>
String RingBuffer<T>::getName() const String RingBuffer<T>::getName() const
@@ -77,7 +101,7 @@ void RingBuffer<T>::add(const T& value)
{ {
GWSYNCHRONIZED(&bufLocker); GWSYNCHRONIZED(&bufLocker);
if (value < smallest || value > largest) { 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 { } else {
buffer[head] = value; buffer[head] = value;
} }
@@ -101,7 +125,7 @@ T RingBuffer<T>::get(size_t index) const
{ {
GWSYNCHRONIZED(&bufLocker); GWSYNCHRONIZED(&bufLocker);
if (isEmpty() || index < 0 || index >= count) { if (isEmpty() || index < 0 || index >= count) {
return MIN_VAL; return MAX_VAL;
} }
size_t realIndex = (first + index) % capacity; size_t realIndex = (first + index) % capacity;
@@ -120,7 +144,7 @@ template <typename T>
T RingBuffer<T>::getFirst() const T RingBuffer<T>::getFirst() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
return get(0); return get(0);
} }
@@ -130,7 +154,7 @@ template <typename T>
T RingBuffer<T>::getLast() const T RingBuffer<T>::getLast() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
return get(count - 1); return get(count - 1);
} }
@@ -140,14 +164,14 @@ template <typename T>
T RingBuffer<T>::getMin() const T RingBuffer<T>::getMin() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
T minVal = MAX_VAL; T minVal = MAX_VAL;
T value; T value;
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
value = get(i); value = get(i);
if (value < minVal && value != MIN_VAL) { if (value < minVal && value != MAX_VAL) {
minVal = value; minVal = value;
} }
} }
@@ -159,7 +183,7 @@ template <typename T>
T RingBuffer<T>::getMin(size_t amount) const T RingBuffer<T>::getMin(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return MIN_VAL; return MAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
@@ -168,7 +192,7 @@ T RingBuffer<T>::getMin(size_t amount) const
T value; T value;
for (size_t i = 0; i < amount; i++) { for (size_t i = 0; i < amount; i++) {
value = get(count - 1 - i); value = get(count - 1 - i);
if (value < minVal && value != MIN_VAL) { if (value < minVal && value != MAX_VAL) {
minVal = value; minVal = value;
} }
} }
@@ -180,14 +204,14 @@ template <typename T>
T RingBuffer<T>::getMax() const T RingBuffer<T>::getMax() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
T maxVal = MIN_VAL; T maxVal = MIN_VAL;
T value; T value;
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
value = get(i); value = get(i);
if (value > maxVal && value != MIN_VAL) { if (value > maxVal && value != MAX_VAL) {
maxVal = value; maxVal = value;
} }
} }
@@ -199,7 +223,7 @@ template <typename T>
T RingBuffer<T>::getMax(size_t amount) const T RingBuffer<T>::getMax(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return MIN_VAL; return MAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
@@ -208,7 +232,7 @@ T RingBuffer<T>::getMax(size_t amount) const
T value; T value;
for (size_t i = 0; i < amount; i++) { for (size_t i = 0; i < amount; i++) {
value = get(count - 1 - i); value = get(count - 1 - i);
if (value > maxVal && value != MIN_VAL) { if (value > maxVal && value != MAX_VAL) {
maxVal = value; maxVal = value;
} }
} }
@@ -220,7 +244,7 @@ template <typename T>
T RingBuffer<T>::getMid() const T RingBuffer<T>::getMid() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
return (getMin() + getMax()) / static_cast<T>(2); return (getMin() + getMax()) / static_cast<T>(2);
@@ -231,7 +255,7 @@ template <typename T>
T RingBuffer<T>::getMid(size_t amount) const T RingBuffer<T>::getMid(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return MIN_VAL; return MAX_VAL;
} }
if (amount > count) if (amount > count)
@@ -245,7 +269,7 @@ template <typename T>
T RingBuffer<T>::getMedian() const T RingBuffer<T>::getMedian() const
{ {
if (isEmpty()) { if (isEmpty()) {
return MIN_VAL; return MAX_VAL;
} }
// Create a temporary vector with current valid elements // Create a temporary vector with current valid elements
@@ -274,7 +298,7 @@ template <typename T>
T RingBuffer<T>::getMedian(size_t amount) const T RingBuffer<T>::getMedian(size_t amount) const
{ {
if (isEmpty() || amount <= 0) { if (isEmpty() || amount <= 0) {
return MIN_VAL; return MAX_VAL;
} }
if (amount > count) if (amount > count)
amount = count; amount = count;
@@ -342,14 +366,14 @@ bool RingBuffer<T>::isFull() const
return is_Full; return is_Full;
} }
// Get lowest possible value for buffer; used for non-set buffer data // Get lowest possible value for buffer
template <typename T> template <typename T>
T RingBuffer<T>::getMinVal() const T RingBuffer<T>::getMinVal() const
{ {
return MIN_VAL; return MIN_VAL;
} }
// Get highest possible value for buffer // Get highest possible value for buffer; used for unset/invalid buffer data
template <typename T> template <typename T>
T RingBuffer<T>::getMaxVal() const T RingBuffer<T>::getMaxVal() const
{ {
@@ -368,6 +392,22 @@ void RingBuffer<T>::clear()
is_Full = false; 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 // Get all current values as a vector
template <typename T> template <typename T>
std::vector<T> RingBuffer<T>::getAllValues() const std::vector<T> RingBuffer<T>::getAllValues() const
@@ -380,4 +420,4 @@ std::vector<T> RingBuffer<T>::getAllValues() const
} }
return result; return result;
} }

View File

@@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 #if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#include <Adafruit_Sensor.h> // Adafruit Lib for sensors #include <Adafruit_Sensor.h> // Adafruit Lib for sensors
#include <Adafruit_BME280.h> // Adafruit Lib for BME280 #include <Adafruit_BME280.h> // Adafruit Lib for BME280
@@ -568,6 +569,11 @@ void sensorTask(void *param){
// Send data from environment sensor all 2s // Send data from environment sensor all 2s
if(millis() > starttime6 + 2000){ if(millis() > starttime6 + 2000){
starttime6 = millis(); 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 TempSource = 2; // Inside temperature
unsigned char PressureSource = 0; // Atmospheric pressure unsigned char PressureSource = 0; // Atmospheric pressure
unsigned char HumiditySource = 0; // Inside humidity unsigned char HumiditySource = 0; // Inside humidity
@@ -785,8 +791,12 @@ void sensorTask(void *param){
vTaskDelete(NULL); vTaskDelete(NULL);
} }
void createSensorTask(SharedData *shared) {
void createSensorTask(SharedData *shared){ TaskHandle_t xHandle = NULL;
xTaskCreate(sensorTask,"readSensors",10000,shared,3,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 #endif

View File

@@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "GwSynchronized.h" #include "GwSynchronized.h"
#include "GwApi.h" #include "GwApi.h"

View File

@@ -13,6 +13,9 @@
Feature possibilities Feature possibilities
- switch between North up / Heading up - switch between North up / Heading up
- filter
- zoom
- special vessel symbols
*/ */

View File

@@ -39,6 +39,10 @@
Drop / raise function in device OBP40 has to be done inside Drop / raise function in device OBP40 has to be done inside
config mode because of limited number of buttons. config mode because of limited number of buttons.
Save position in FRAM
Alarm: gps fix lost
switch unit feet/meter
*/ */
#define anchor_width 16 #define anchor_width 16

View File

@@ -227,7 +227,7 @@ public:
epd->print(value1,2); // Real value as formated string epd->print(value1,2); // Real value as formated string
} }
else{ else{
epd->print("---"); // No sensor data (sensor is off) epd->print(commonData->fmt->placeholder); // No sensor data (sensor is off)
} }
// ############### Horizontal Line ################ // ############### Horizontal Line ################
@@ -256,7 +256,7 @@ public:
epd->print(value2,1); // Real value as formated string epd->print(value2,1); // Real value as formated string
} }
else{ else{
epd->print("---"); // No sensor data (sensor is off) epd->print(commonData->fmt->placeholder); // No sensor data (sensor is off)
} }
// ############### Horizontal Line ################ // ############### Horizontal Line ################
@@ -285,7 +285,7 @@ public:
epd->print(value3,1); // Real value as formated string epd->print(value3,1); // Real value as formated string
} }
else{ else{
epd->print("---"); // No sensor data (sensor is off) epd->print(commonData->fmt->placeholder); // No sensor data (sensor is off)
} }
return PAGE_UPDATE; return PAGE_UPDATE;

View File

@@ -286,7 +286,7 @@ public:
if(value1 > 99.9) epd->print(value1, 0); if(value1 > 99.9) epd->print(value1, 0);
} }
else{ else{
epd->print("---"); // Missing bus data epd->print(commonData->fmt->placeholder); // Missing bus data
} }
} }
epd->setFont(&Ubuntu_Bold16pt8b); epd->setFont(&Ubuntu_Bold16pt8b);
@@ -300,7 +300,9 @@ public:
if(value2 > 9.9 && value2 <= 99.9)epd->print(value2, 1); if(value2 > 9.9 && value2 <= 99.9)epd->print(value2, 1);
if(value2 > 99.9) epd->print(value2, 0); if(value2 > 99.9) epd->print(value2, 0);
} }
else epd->print("---"); else {
epd->print(commonData->fmt->placeholder);
}
epd->setFont(&Ubuntu_Bold16pt8b); epd->setFont(&Ubuntu_Bold16pt8b);
epd->print("A"); epd->print("A");
@@ -312,7 +314,9 @@ public:
if(value3 > 9.9 && value3 <= 99.9)epd->print(value3, 1); if(value3 > 9.9 && value3 <= 99.9)epd->print(value3, 1);
if(value3 > 99.9) epd->print(value3, 0); if(value3 > 99.9) epd->print(value3, 0);
} }
else epd->print("---"); else {
epd->print(commonData->fmt->placeholder);
}
epd->setFont(&Ubuntu_Bold16pt8b); epd->setFont(&Ubuntu_Bold16pt8b);
epd->print("W"); epd->print("W");

View File

@@ -18,7 +18,7 @@
class PageClock : public Page class PageClock : public Page
{ {
private: private:
String dateformat; fmtDate dateformat;
int simtime; int simtime;
bool keylock = false; bool keylock = false;
char source = 'R'; // time source (R)TC | (G)PS | (N)TP char source = 'R'; // time source (R)TC | (G)PS | (N)TP
@@ -35,7 +35,7 @@ public:
logger->logDebug(GwLog::LOG, "Instantiate PageClock"); logger->logDebug(GwLog::LOG, "Instantiate PageClock");
// Get config data // Get config data
dateformat = config->getString(config->dateFormat); dateformat = common.fmt->getDateFormat(config->getString(config->dateFormat));
timezone = config->getString(config->timeZone).toDouble(); timezone = config->getString(config->timeZone).toDouble();
homelat = config->getString(config->homeLAT).toDouble(); homelat = config->getString(config->homeLAT).toDouble();
homelon = config->getString(config->homeLON).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)); epd->print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday));
} }
} else { } else {
epd->print("---"); epd->print(commonData->fmt->placeholder);
} }
} else { } else {
epd->print(svalue2old); epd->print(svalue2old);
@@ -229,13 +229,13 @@ public:
} }
else if (commonData->data.rtcValid) { else if (commonData->data.rtcValid) {
if (tz == 'L') { 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 { 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 { } else {
epd->print("---"); epd->print(commonData->fmt->placeholder);
} }
} }
else { else {
@@ -246,7 +246,7 @@ public:
epd->print("Time"); // Name epd->print("Time"); // Name
// Show values sunrise // Show values sunrise
String sunrise = "---"; String sunrise = commonData->fmt->placeholder;
if (((source == 'G') and gpsvalid) or (homevalid and commonData->data.rtcValid)) { if (((source == 'G') and gpsvalid) or (homevalid and commonData->data.rtcValid)) {
sunrise = String(commonData->sundata.sunriseHour) + ":" + String(commonData->sundata.sunriseMinute + 100).substring(1); sunrise = String(commonData->sundata.sunriseHour) + ":" + String(commonData->sundata.sunriseMinute + 100).substring(1);
svalue5old = sunrise; svalue5old = sunrise;
@@ -266,7 +266,7 @@ public:
epd->fillRect(340, 149, 80, 3, commonData->fgcolor); epd->fillRect(340, 149, 80, 3, commonData->fgcolor);
// Show values sunset // Show values sunset
String sunset = "---"; String sunset = commonData->fmt->placeholder;
if (((source == 'G') and gpsvalid) or (homevalid and commonData->data.rtcValid)) { if (((source == 'G') and gpsvalid) or (homevalid and commonData->data.rtcValid)) {
sunset = String(commonData->sundata.sunsetHour) + ":" + String(commonData->sundata.sunsetMinute + 100).substring(1); sunset = String(commonData->sundata.sunsetHour) + ":" + String(commonData->sundata.sunsetMinute + 100).substring(1);
svalue6old = sunset; svalue6old = sunset;

View File

@@ -7,6 +7,14 @@
/* /*
Electric propulsion 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 class PageEPropulsion : public Page

View File

@@ -3,7 +3,10 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#ifdef ENABLE_CALIBRATION
#include "BoatDataCalibration.h" #include "BoatDataCalibration.h"
#endif
class PageFourValues : public Page class PageFourValues : public Page
{ {
@@ -54,7 +57,9 @@ public:
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
String name1 = xdrDelete(bvalue1->getName()); // Value name String name1 = xdrDelete(bvalue1->getName()); // Value name
name1 = name1.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
#endif
double value1 = bvalue1->value; // Value as double in SI unit double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information 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 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 GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
String name2 = xdrDelete(bvalue2->getName()); // Value name String name2 = xdrDelete(bvalue2->getName()); // Value name
name2 = name2.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
#endif
double value2 = bvalue2->value; // Value as double in SI unit double value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information 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 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 GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
String name3 = xdrDelete(bvalue3->getName()); // Value name String name3 = xdrDelete(bvalue3->getName()); // Value name
name3 = name3.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
#endif
double value3 = bvalue3->value; // Value as double in SI unit double value3 = bvalue3->value; // Value as double in SI unit
bool valid3 = bvalue3->valid; // Valid information 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 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 GwApi::BoatValue *bvalue4 = pageData.values[3]; // Fourth element in list
String name4 = xdrDelete(bvalue4->getName()); // Value name String name4 = xdrDelete(bvalue4->getName()); // Value name
name4 = name4.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
#endif
double value4 = bvalue4->value; // Value as double in SI unit double value4 = bvalue4->value; // Value as double in SI unit
bool valid4 = bvalue4->valid; // Valid information 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 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){ static Page *createPage(CommonData &common){
return new PageFourValues(common); return new PageFourValues(common);
}/** }/**

View File

@@ -54,7 +54,9 @@ public:
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
String name1 = xdrDelete(bvalue1->getName()); // Value name String name1 = xdrDelete(bvalue1->getName()); // Value name
name1 = name1.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
#endif
double value1 = bvalue1->value; // Value as double in SI unit double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information 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 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) GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue)
String name2 = xdrDelete(bvalue2->getName()); // Value name String name2 = xdrDelete(bvalue2->getName()); // Value name
name2 = name2.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
#endif
double value2 = bvalue2->value; // Value as double in SI unit double value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information 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 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) GwApi::BoatValue *bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue)
String name3 = xdrDelete(bvalue3->getName()); // Value name String name3 = xdrDelete(bvalue3->getName()); // Value name
name3 = name3.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
#endif
double value3 = bvalue3->value; // Value as double in SI unit double value3 = bvalue3->value; // Value as double in SI unit
bool valid3 = bvalue3->valid; // Valid information 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 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) GwApi::BoatValue *bvalue4 = pageData.values[3]; // Second element in list (only one value by PageOneValue)
String name4 = xdrDelete(bvalue4->getName()); // Value name String name4 = xdrDelete(bvalue4->getName()); // Value name
name4 = name4.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
#endif
double value4 = bvalue4->value; // Value as double in SI unit double value4 = bvalue4->value; // Value as double in SI unit
bool valid4 = bvalue4->valid; // Valid information 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 svalue4 = commonData->fmt->formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -168,7 +168,7 @@ public:
if(value1 > 99.9) epd->print(value1, 0); if(value1 > 99.9) epd->print(value1, 0);
} }
else{ else{
epd->print("---"); // Missing bus data epd->print(commonData->fmt->placeholder); // Missing bus data
} }
} }
epd->setFont(&Ubuntu_Bold16pt8b); epd->setFont(&Ubuntu_Bold16pt8b);
@@ -177,24 +177,37 @@ public:
// Show actual current in A // Show actual current in A
epd->setFont(&DSEG7Classic_BoldItalic20pt7b); epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
epd->setCursor(260, 200); epd->setCursor(260, 200);
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){ if ((powerSensor == "INA219" || powerSensor == "INA226") && (simulation == false)) {
if(value2 <= 9.9) epd->print(value2, 2); // TODO use formatter for this?
if(value2 > 9.9 && value2 <= 99.9)epd->print(value2, 1); if (value2 <= 9.9) {
if(value2 > 99.9) epd->print(value2, 0); 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->setFont(&Ubuntu_Bold16pt8b);
epd->print("A"); epd->print("A");
// Show actual consumption in W // Show actual consumption in W
epd->setFont(&DSEG7Classic_BoldItalic20pt7b); epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
epd->setCursor(260, 260); epd->setCursor(260, 260);
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){ if ((powerSensor == "INA219" || powerSensor == "INA226") && (simulation == false)) {
if(value3 <= 9.9) epd->print(value3, 2); if(value3 <= 9.9) {
if(value3 > 9.9 && value3 <= 99.9)epd->print(value3, 1); epd->print(value3, 2);
if(value3 > 99.9) epd->print(value3, 0); } 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->setFont(&Ubuntu_Bold16pt8b);
epd->print("W"); epd->print("W");

View File

@@ -3,7 +3,10 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#ifdef ENABLE_CALIBRATION
#include "BoatDataCalibration.h" #include "BoatDataCalibration.h"
#endif
class PageOneValue : public Page class PageOneValue : public Page
{ {
@@ -49,7 +52,9 @@ public:
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
String name1 = xdrDelete(bvalue1->getName()); // Value name String name1 = xdrDelete(bvalue1->getName()); // Value name
name1 = name1.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
#endif
double value1 = bvalue1->value; // Value as double in SI unit double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information 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 String svalue1 = commonData->fmt->formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -3,7 +3,10 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#ifdef ENABLE_CALIBRATION
#include "BoatDataCalibration.h" #include "BoatDataCalibration.h"
#endif
class PageRudderPosition : public Page class PageRudderPosition : public Page
{ {
@@ -49,7 +52,9 @@ public:
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list
String name1 = bvalue1->getName().c_str(); // Value name String name1 = bvalue1->getName().c_str(); // Value name
name1 = name1.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
#endif
value1 = bvalue1->value; // Raw value without unit convertion value1 = bvalue1->value; // Raw value without unit convertion
bool valid1 = bvalue1->valid; // Valid information 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 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 // Log boat values
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
logger->logDebug(GwLog::LOG, "Drawing at PageRudderPosition, %s:%f", name1.c_str(), value1); logger->logDebug(GwLog::LOG, "Drawing at PageRudderPosition, %s:%f", name1.c_str(), value1);
// Draw page // Draw page

View File

@@ -3,7 +3,10 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#ifdef ENABLE_CALIBRATION
#include "BoatDataCalibration.h" #include "BoatDataCalibration.h"
#endif
const int SixValues_x1 = 5; const int SixValues_x1 = 5;
const int SixValues_DeltaX = 200; const int SixValues_DeltaX = 200;
@@ -65,7 +68,9 @@ public:
bvalue = pageData.values[i]; bvalue = pageData.values[i];
DataName[i] = xdrDelete(bvalue->getName()); DataName[i] = xdrDelete(bvalue->getName());
DataName[i] = DataName[i].substring(0, 6); // String length limit for value name 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 calibrationData.calibrateInstance(bvalue, logger); // Check if boat data value is to be calibrated
#endif
DataValue[i] = bvalue->value; // Value as double in SI unit DataValue[i] = bvalue->value; // Value as double in SI unit
DataValid[i] = bvalue->valid; DataValid[i] = bvalue->valid;
DataText[i] = commonData->fmt->formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places DataText[i] = commonData->fmt->formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -4,23 +4,33 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include <vector>
#include <algorithm> // for vector sorting
/* /*
* SkyView / Satellites * SkyView / Satellites
*/ */
class PageSkyView : public Page class PageSkyView : public Page
{ {
private:
GwBoatData *bd;
public: public:
PageSkyView(CommonData &common) : Page(common) 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){ int handleKey(int key) {
// Code for keylock // return 0 to mark the key handled completely
if(key == 11){ // return the key to allow further action
if (key == 11) {
commonData->keylock = !commonData->keylock; commonData->keylock = !commonData->keylock;
return 0; // Commit the key return 0;
} }
return key; return key;
} }
@@ -33,12 +43,25 @@ public:
setFlashLED(false); setFlashLED(false);
} }
#endif #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) { int displayPage(PageData &pageData) {
// Logging boat values std::vector<GwSatInfo> sats;
logger->logDebug(GwLog::LOG, "Drawing at PageSkyView"); 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 // Draw page
//*********************************************************** //***********************************************************
@@ -49,6 +72,7 @@ public:
// current position // current position
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b);
GwApi::BoatValue *bv_lat = pageData.values[0]; GwApi::BoatValue *bv_lat = pageData.values[0];
String sv_lat = commonData->fmt->formatValue(bv_lat, *commonData).svalue; String sv_lat = commonData->fmt->formatValue(bv_lat, *commonData).svalue;
//epd->setCursor(300, 40); //epd->setCursor(300, 40);
@@ -88,7 +112,7 @@ public:
// directions // directions
int16_t x1, y1; int16_t x1, y1;
uint16_t w, h; uint16_t w, h;
epd->setFont(&Ubuntu_Bold12pt8b); epd->setFont(&Ubuntu_Bold12pt8b);
@@ -108,21 +132,42 @@ public:
epd->setCursor(c.x - r + 2 , c.y + h / 2); epd->setCursor(c.x - r + 2 , c.y + h / 2);
epd->print("W"); 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 // Signal / Noise bars
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b);
epd->setCursor(325, 34); epd->setCursor(325, 34);
epd->print("SNR"); epd->print("SNR");
epd->drawRect(270, 20, 125, 257, commonData->fgcolor); 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; uint16_t y = 29 + (i + 1) * 20;
epd->setCursor(276, y); epd->setCursor(276, y);
char buffer[3]; char buffer[3];
snprintf(buffer, 3, "%02d", i+1); snprintf(buffer, 3, "%02d", static_cast<int>(sats[i].PRN));
epd->print(String(buffer)); epd->print(String(buffer));
epd->drawRect(305, y-12, 85, 14, commonData->fgcolor); 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; return PAGE_UPDATE;

View File

@@ -160,12 +160,16 @@ public:
// Check for valid real data, display also if hold values activated // Check for valid real data, display also if hold values activated
if(valid1 == true || holdvalues == true){ if(valid1 == true || holdvalues == true){
// Resolution switching // Resolution switching
if(value1 <= 9.9) epd->print(value1, 2); if (value1 <= 9.9) {
if(value1 > 9.9 && value1 <= 99.9)epd->print(value1, 1); epd->print(value1, 2);
if(value1 > 99.9) epd->print(value1, 0); } else if (value1 <= 99.9) {
epd->print(value1, 1);
} else {
epd->print(value1, 0);
}
} }
else{ else {
epd->print("---"); // Missing bus data epd->print(commonData->fmt->placeholder); // Missing bus data
} }
} }
epd->setFont(&Ubuntu_Bold16pt8b); epd->setFont(&Ubuntu_Bold16pt8b);
@@ -174,24 +178,36 @@ public:
// Show actual current in A // Show actual current in A
epd->setFont(&DSEG7Classic_BoldItalic20pt7b); epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
epd->setCursor(260, 200); epd->setCursor(260, 200);
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){ if ((powerSensor == "INA219" || powerSensor == "INA226") && (simulation == false)) {
if(value2 <= 9.9) epd->print(value2, 2); if (value2 <= 9.9) {
if(value2 > 9.9 && value2 <= 99.9)epd->print(value2, 1); epd->print(value2, 2);
if(value2 > 99.9) epd->print(value2, 0); } 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->setFont(&Ubuntu_Bold16pt8b);
epd->print("A"); epd->print("A");
// Show actual consumption in W // Show actual consumption in W
epd->setFont(&DSEG7Classic_BoldItalic20pt7b); epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
epd->setCursor(260, 260); epd->setCursor(260, 260);
if((powerSensor == "INA219" || powerSensor == "INA226") && simulation == false){ if ((powerSensor == "INA219" || powerSensor == "INA226") && (simulation == false)) {
if(value3 <= 9.9) epd->print(value3, 2); if (value3 <= 9.9) {
if(value3 > 9.9 && value3 <= 99.9)epd->print(value3, 1); epd->print(value3, 2);
if(value3 > 99.9) epd->print(value3, 0); } 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->setFont(&Ubuntu_Bold16pt8b);
epd->print("W"); epd->print("W");

View File

@@ -8,7 +8,7 @@
* 1. Hard and software information * 1. Hard and software information
* 2. System settings * 2. System settings
* 3. System configuration: running and NVRAM * 3. System configuration: running and NVRAM
* 4. NMEA2000 device list * 4. NMEA2000 device list if NMEA2000 enabled
* 5. SD Card information if available * 5. SD Card information if available
* *
* TODO * TODO
@@ -43,6 +43,8 @@
#define DISPLAYINFO STRINGIZE(EPDTYPE) #define DISPLAYINFO STRINGIZE(EPDTYPE)
#define GXEPD2INFO STRINGIZE(GXEPD2VERS) #define GXEPD2INFO STRINGIZE(GXEPD2VERS)
#define N2K_INACTIVE_AGE 30000
class PageSystem : public Page class PageSystem : public Page
{ {
private: private:
@@ -68,6 +70,10 @@ private:
double homelon; double homelon;
Nmea2kTwai *NMEA2000; 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 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 int8_t editmode = -1; // marker for menu/edit/set function
@@ -233,7 +239,7 @@ private:
epd->print(String(Heap_free)); epd->print(String(Heap_free));
// RAM free for task // RAM free for task
int RAM_free = uxTaskGetStackHighWaterMark(NULL); UBaseType_t RAM_free = uxTaskGetStackHighWaterMark(NULL);
epd->setCursor(202, y0 + 32); epd->setCursor(202, y0 + 32);
epd->print("Task free:"); epd->print("Task free:");
epd->setCursor(300, y0 + 32); epd->setCursor(300, y0 + 32);
@@ -390,7 +396,6 @@ private:
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b);
epd->setCursor(x0, y0); epd->setCursor(x0, y0);
epd->print("Work in progress...");
#ifdef BOARD_OBP60S3 #ifdef BOARD_OBP60S3
// This mode should not be callable by devices without card hardware // This mode should not be callable by devices without card hardware
// In case of accidential reaching this, display a friendly message // In case of accidential reaching this, display a friendly message
@@ -402,32 +407,39 @@ private:
magic.dat magic.dat
version.dat version.dat
readme.txt readme.txt
IMAGES/ BAK/
CHARTS/ CHARTS/
LOGS/
DATA/ DATA/
IMAGES/
LOGS/
*/ */
// Simple test for one file in root // Simple test for magic file in root
epd->setCursor(x0, y0 + 32); epd->setCursor(x0, y0 + 16);
String file_test = MOUNT_POINT "/IMG/icon-settings2.pbm"; const char* file_magic = MOUNT_POINT "/magic.dat";
logger->logDebug(GwLog::LOG, "Testfile: %s", file_test.c_str()); logger->logDebug(GwLog::LOG, "Test magicfile: %s", file_magic);
struct stat st; struct stat st;
if (stat(file_test.c_str(), &st) == 0) { if (stat(file_magic, &st) == 0) {
epd->printf("File %s exists", file_test.c_str()); epd->printf("Magicfile %s exists", file_magic);
} else { } else {
epd->printf("File %s not found", file_test.c_str()); epd->printf("Magicfile %s not found", file_magic);
} }
// Root directory check // Root directory check
DIR* dir = opendir(MOUNT_POINT); DIR* dir = opendir(MOUNT_POINT);
int dy = 0;
if (dir != NULL) { if (dir != NULL) {
logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT); logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT);
struct dirent* entry; 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); logger->logDebug(GwLog::LOG, " %s type %d", entry->d_name, entry->d_type);
// type 1 is file
// type 2 is dir
} }
closedir(dir); closedir(dir);
} else { } else {
@@ -436,7 +448,7 @@ private:
// try to load example pbm file // try to load example pbm file
// TODO create PBM load function in imglib // 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(); const char* file_img = filepath.c_str();
FILE* fh = fopen(file_img, "r"); FILE* fh = fopen(file_img, "r");
if (fh != NULL) { if (fh != NULL) {
@@ -494,8 +506,12 @@ private:
#endif #endif
} }
void displayModeDevicelist() { void displayModeDevicelist(bool bus_active) {
// NMEA2000 device list // NMEA2000 device list
// TODO Check if NMEA2000 enabled globally
// if disabled this page should not be shown
epd->setFont(&Ubuntu_Bold12pt8b); epd->setFont(&Ubuntu_Bold12pt8b);
epd->setCursor(8, 48); epd->setCursor(8, 48);
epd->print("NMEA2000 device list"); epd->print("NMEA2000 device list");
@@ -508,6 +524,11 @@ private:
epd->print("TxD: "); epd->print("TxD: ");
epd->print(String(commonData->status.n2kTx)); 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->setCursor(20, 140);
epd->printf("N2k source address: %d", NMEA2000->GetN2kSource()); epd->printf("N2k source address: %d", NMEA2000->GetN2kSource());
@@ -685,7 +706,15 @@ public:
displayModeSDCard(); displayModeSDCard();
break; break;
case 'D': 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; break;
} }

View File

@@ -3,7 +3,10 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#ifdef ENABLE_CALIBRATION
#include "BoatDataCalibration.h" #include "BoatDataCalibration.h"
#endif
class PageThreeValues : public Page class PageThreeValues : public Page
{ {
@@ -52,7 +55,9 @@ public:
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
String name1 = xdrDelete(bvalue1->getName()); // Value name String name1 = xdrDelete(bvalue1->getName()); // Value name
name1 = name1.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
#endif
double value1 = bvalue1->value; // Value as double in SI unit double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information 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 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 GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
String name2 = xdrDelete(bvalue2->getName()); // Value name String name2 = xdrDelete(bvalue2->getName()); // Value name
name2 = name2.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
#endif
double value2 = bvalue2->value; // Value as double in SI unit double value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information 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 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 GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
String name3 = xdrDelete(bvalue3->getName()); // Value name String name3 = xdrDelete(bvalue3->getName()); // Value name
name3 = name3.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
#endif
double value3 = bvalue3->value; // Value as double in SI unit double value3 = bvalue3->value; // Value as double in SI unit
bool valid3 = bvalue3->valid; // Valid information 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 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 String unit3 = commonData->fmt->formatValue(bvalue3, *commonData).unit; // Unit of value
// Logging boat values // Log 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",
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); name1.c_str(), value1,
name2.c_str(), value2,
name3.c_str(), value3);
// Draw page // Draw page
//*********************************************************** //***********************************************************

View 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

View File

@@ -3,7 +3,10 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#ifdef ENABLE_CALIBRATION
#include "BoatDataCalibration.h" #include "BoatDataCalibration.h"
#endif
class PageTwoValues : public Page class PageTwoValues : public Page
{ {
@@ -51,7 +54,9 @@ public:
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
String name1 = xdrDelete(bvalue1->getName()); // Value name String name1 = xdrDelete(bvalue1->getName()); // Value name
name1 = name1.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
#endif
double value1 = bvalue1->value; // Value as double in SI unit double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information 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 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 GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
String name2 = xdrDelete(bvalue2->getName()); // Value name String name2 = xdrDelete(bvalue2->getName()); // Value name
name2 = name2.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
#endif
double value2 = bvalue2->value; // Value as double in SI unit double value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information 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 svalue2 = commonData->fmt->formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -249,7 +249,7 @@ public:
} }
} }
else{ else{
epd->print("---"); // Missing bus data epd->print(commonData->fmt->placeholder); // Missing bus data
} }
} }

View File

@@ -4,7 +4,10 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "N2kMessages.h" #include "N2kMessages.h"
#ifdef ENABLE_CALIBRATION
#include "BoatDataCalibration.h" #include "BoatDataCalibration.h"
#endif
#define front_width 120 #define front_width 120
#define front_height 162 #define front_height 162
@@ -331,7 +334,9 @@ public:
} }
String name1 = bvalue1->getName().c_str(); // Value name String name1 = bvalue1->getName().c_str(); // Value name
name1 = name1.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
#endif
double value1 = bvalue1->value; // Value as double in SI unit double value1 = bvalue1->value; // Value as double in SI unit
// bool valid1 = bvalue1->valid; // Valid information // 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 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 String name2 = bvalue2->getName().c_str(); // Value name
name2 = name2.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
#endif
double value2 = bvalue2->value; // Value as double in SI unit double value2 = bvalue2->value; // Value as double in SI unit
// bool valid2 = bvalue2->valid; // Valid information // bool valid2 = bvalue2->valid; // Valid information
if (simulation) { if (simulation) {

View File

@@ -1,10 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 #if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#include "BoatDataCalibration.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "OBPRingBuffer.h" #include "OBPRingBuffer.h"
#include "Pagedata.h" #include "OBPDataOperations.h"
#include "BoatDataCalibration.h"
#include <vector> #include <vector>
static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees 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 // 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 getCntr(const RingBuffer<int16_t>& windDirHstry, size_t amount)
{ {
int minVal = windDirHstry.getMinVal(); const int MAX_VAL = windDirHstry.getMaxVal();
size_t count = windDirHstry.getCurrentSize(); size_t count = windDirHstry.getCurrentSize();
if (windDirHstry.isEmpty() || amount <= 0) { if (windDirHstry.isEmpty() || amount <= 0) {
@@ -21,11 +22,11 @@ int getCntr(const RingBuffer<int16_t>& windDirHstry, size_t amount)
if (amount > count) if (amount > count)
amount = count; amount = count;
int16_t midWndDir, minWndDir, maxWndDir = 0; uint16_t midWndDir, minWndDir, maxWndDir = 0;
int wndCenter = 0; int wndCenter = 0;
midWndDir = windDirHstry.getMid(amount); midWndDir = windDirHstry.getMid(amount);
if (midWndDir != INT16_MIN) { if (midWndDir != MAX_VAL) {
midWndDir = midWndDir / 1000.0 * radToDeg; midWndDir = midWndDir / 1000.0 * radToDeg;
wndCenter = int((midWndDir + (midWndDir >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value 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; 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 getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount)
{ {
int minVal = windDirHstry.getMinVal(); int minVal = windDirHstry.getMinVal();
const int MAX_VAL = windDirHstry.getMaxVal();
size_t count = windDirHstry.getCurrentSize(); size_t count = windDirHstry.getCurrentSize();
if (windDirHstry.isEmpty() || amount <= 0) { if (windDirHstry.isEmpty() || amount <= 0) {
return minVal; return MAX_VAL;
} }
if (amount > count) if (amount > count)
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++) { for (size_t i = 0; i < amount; i++) {
value = windDirHstry.get(count - 1 - i); value = windDirHstry.get(count - 1 - i);
if (value == minVal) { if (value == MAX_VAL) {
continue; continue; // ignore invalid values
} }
value = value / 1000.0 * radToDeg; value = value / 1000.0 * radToDeg;
@@ -70,7 +72,7 @@ int getRng(const RingBuffer<int16_t>& windDirHstry, int center, size_t amount)
maxRng = 180; maxRng = 180;
} }
return maxRng; return (maxRng != minVal ? maxRng : MAX_VAL);
} }
// **************************************************************** // ****************************************************************
@@ -79,6 +81,8 @@ private:
bool keylock = false; // Keylock bool keylock = false; // Keylock
char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both 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 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: int dataIntv = 1; // Update interval for wind history chart:
// (1)|(2)|(3)|(4) seconds for approx. 4, 8, 12, 16 min. 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 showTruW = false; // Wind source is apparant wind
} }
commonData->logger->logDebug(GwLog::LOG,"New PageWindPlot: wind source=%s", wndSrc); commonData->logger->logDebug(GwLog::LOG,"New PageWindPlot: wind source=%s", wndSrc);
#endif #endif
}; oldShowTruW = !showTruW; // makes wind source being initialized at initial page call
}
int displayPage(PageData& pageData) { int displayPage(PageData& pageData) {
static RingBuffer<int16_t>* wdHstry; // Wind direction data buffer 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 wdName, wdFormat; // Wind direction name and format
static String wsName, wsFormat; // Wind speed name and format static String wsName, wsFormat; // Wind speed name and format
static int updFreq; // Update frequency for wind direction static int16_t wdMAX_VAL; // Max. value of wd history buffer, indicating invalid values
static int16_t wdLowest, wdHighest; // Wind direction range
float wsValue; // Wind speed value in chart area float wsValue; // Wind speed value in chart area
String wsUnit; // Wind speed unit 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 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 // current boat data values; TWD/AWD only for validation test
const int numBoatData = 4; const int numBoatData = 2;
GwApi::BoatValue* bvalue; GwApi::BoatValue* bvalue;
String BDataName[numBoatData];
double BDataValue[numBoatData];
bool BDataValid[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 isInitialized = false; // Flag to indicate that page is initialized
static bool wndDataValid = false; // Flag to indicate if wind data is valid 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 lastIdx; // Last index of TWD history buffer
static size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added 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 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 wndCenter; // chart wind center value position
static int wndLeft; // chart wind left 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 static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line
logger->logDebug(GwLog::LOG, "Display PageWindPlot"); logger->logDebug(GwLog::LOG, "Display PageWindPlot");
ulong timer = millis();
if (!isInitialized) { if (!isInitialized) {
width = epd->width(); width = epd->width();
@@ -234,18 +232,9 @@ public:
numNoData = 0; numNoData = 0;
bufStart = 0; bufStart = 0;
oldDataIntv = 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; wsValue = 0;
wsBVal->setFormat(wsHstry->getFormat());
numAddedBufVals, currIdx, lastIdx = 0; numAddedBufVals, currIdx, lastIdx = 0;
lastAddedIdx = wdHstry->getLastIdx(); wndCenter = INT_MAX;
wndCenter = INT_MIN;
midWndDir = 0; midWndDir = 0;
diffRng = dfltRng; diffRng = dfltRng;
chrtRng = dfltRng; chrtRng = dfltRng;
@@ -256,14 +245,7 @@ public:
// read boat data values; TWD only for validation test, TWS for display of current value // read boat data values; TWD only for validation test, TWS for display of current value
for (int i = 0; i < numBoatData; i++) { for (int i = 0; i < numBoatData; i++) {
bvalue = pageData.values[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; 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) // Optical warning by limit violation (unused)
@@ -274,16 +256,18 @@ public:
if (showTruW != oldShowTruW) { if (showTruW != oldShowTruW) {
if (showTruW) { if (showTruW) {
wdHstry = pageData.boatHstry.twdHstry; wdHstry = pageData.boatHstry->hstryBufList.twdHstry;
wsHstry = pageData.boatHstry.twsHstry; wsHstry = pageData.boatHstry->hstryBufList.twsHstry;
} else { } else {
wdHstry = pageData.boatHstry.awdHstry; wdHstry = pageData.boatHstry->hstryBufList.awdHstry;
wsHstry = pageData.boatHstry.awsHstry; wsHstry = pageData.boatHstry->hstryBufList.awsHstry;
} }
wdHstry->getMetaData(wdName, wdFormat, updFreq, wdLowest, wdHighest); wdHstry->getMetaData(wdName, wdFormat);
wsHstry->getMetaData(wsName, wsFormat, updFreq, wdLowest, wdHighest); wsHstry->getMetaData(wsName, wsFormat);
wdMAX_VAL = wdHstry->getMaxVal();
bufSize = wdHstry->getCapacity(); bufSize = wdHstry->getCapacity();
wsBVal->setFormat(wsHstry->getFormat()); wsBVal->setFormat(wsHstry->getFormat());
lastAddedIdx = wdHstry->getLastIdx();
oldShowTruW = showTruW; oldShowTruW = showTruW;
} }
@@ -306,19 +290,19 @@ public:
bufStart = max(0, bufStart - numAddedBufVals); 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", 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() / 10.0 * 1.94384, BDataValid[0], intvBufSize, numWndVals, bufStart, numAddedBufVals, wdHstry->getLastIdx(), count, wdHstry->getLast() / 1000.0 * radToDeg, wsHstry->getLast() / 1000.0 * 1.94384, BDataValid[0], intvBufSize, numWndVals, bufStart, numAddedBufVals, wdHstry->getLastIdx(),
showTruW ? "True" : "App"); showTruW ? "True" : "App");
// Set wndCenter from 1st real buffer value // 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); 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); wndCenter, diffRng, chrtRng, wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 1000.0 * radToDeg);
} else { } else {
// check and adjust range between left, center, and right chart limit // check and adjust range between left, center, and right chart limit
diffRng = getRng(*wdHstry, wndCenter, numWndVals); diffRng = getRng(*wdHstry, wndCenter, numWndVals);
diffRng = (diffRng == INT16_MIN ? 0 : diffRng); diffRng = (diffRng == wdMAX_VAL ? 0 : diffRng);
if (diffRng > chrtRng) { if (diffRng > chrtRng) {
chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value 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 } 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, 2, commonData->fgcolor); // <degree> symbol
epd->drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // <degree> symbol epd->drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // <degree> symbol
if (wdHstry->getMax() == wdHstry->getMinVal()) { if (wdHstry->getMax() == wdMAX_VAL) {
// only <INT16_MIN> values in buffer -> no valid wind data available // only <MAX_VAL> values in buffer -> no valid wind data available
wndDataValid = false; wndDataValid = false;
} else if (!BDataValid[0] && !simulation) { } 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++; numNoData++;
wndDataValid = true; wndDataValid = true;
if (numNoData > 3) { if (numNoData > 3) {
@@ -386,8 +370,8 @@ public:
if (wndDataValid) { if (wndDataValid) {
for (int i = 0; i < (numWndVals / dataIntv); i++) { 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 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) { if (chrtVal == wdMAX_VAL) {
chrtPrevVal = INT16_MIN; chrtPrevVal = wdMAX_VAL;
} else { } else {
chrtVal = static_cast<int>((chrtVal / 1000.0 * radToDeg) + 0.5); // Convert to degrees and round chrtVal = static_cast<int>((chrtVal / 1000.0 * radToDeg) + 0.5); // Convert to degrees and round
x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; 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) 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)); 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 // just a dot for 1st chart point or after some invalid values
prevX = x; prevX = x;
prevY = y; prevY = y;
@@ -457,13 +441,15 @@ public:
lastZone = currentZone; lastZone = currentZone;
wsValue = wsHstry->getLast(); wsValue = wsHstry->getLast();
wsBVal->value = wsValue; // temp variable to retreive data unit from OBP60Formater wsBVal->value = wsValue / 1000.0; // temp variable to retreive data unit from OBP60Formater
wsBVal->valid = (static_cast<int16_t>(wsValue) != wsHstry->getMinVal()); 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 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->fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value
epd->setFont(&DSEG7Classic_BoldItalic16pt7b); epd->setFont(&DSEG7Classic_BoldItalic16pt7b);
epd->setCursor(xPosTws, yPosTws); epd->setCursor(xPosTws, yPosTws);
if (!wsBVal->valid) { epd->print(swsValue); // Value
/* if (!wsBVal->valid) {
epd->print("--.-"); epd->print("--.-");
} else { } else {
wsValue = wsValue / 10.0 * 1.94384; // Wind speed value in knots wsValue = wsValue / 10.0 * 1.94384; // Wind speed value in knots
@@ -472,7 +458,7 @@ public:
} else { } else {
epd->printf("%4.1f", wsValue); // Value, round to 1 decimal epd->printf("%4.1f", wsValue); // Value, round to 1 decimal
} }
} } */
epd->setFont(&Ubuntu_Bold12pt8b); epd->setFont(&Ubuntu_Bold12pt8b);
epd->setCursor(xPosTws + 82, yPosTws - 14); epd->setCursor(xPosTws + 82, yPosTws - 14);
epd->print(wsName); // Name epd->print(wsName); // Name
@@ -507,6 +493,7 @@ public:
epd->printf("%3d", chrtLbl); // Wind value label epd->printf("%3d", chrtLbl); // Wind value label
} }
logger->logDebug(GwLog::DEBUG, "PageWindPlot time: %ld", millis() - timer);
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
}; };
@@ -525,7 +512,7 @@ PageDescription registerPageWindPlot(
"WindPlot", // Page name "WindPlot", // Page name
createPage, // Action createPage, // Action
0, // Number of bus values depends on selection in Web configuration 0, // Number of bus values depends on selection in Web configuration
{ "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 true // Show display header on/off
); );

View File

@@ -3,7 +3,10 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#ifdef ENABLE_CALIBRATION
#include "BoatDataCalibration.h" #include "BoatDataCalibration.h"
#endif
class PageWindRose : public Page class PageWindRose : public Page
{ {
@@ -42,127 +45,108 @@ public:
int displayPage(PageData &pageData) { int displayPage(PageData &pageData) {
static String svalue1old = ""; // storage for hold valued
static String unit1old = ""; static FormattedData bvf_awa_old;
static String svalue2old = ""; static FormattedData bvf_aws_old;
static String unit2old = ""; static FormattedData bvf_twd_old;
static String svalue3old = ""; static FormattedData bvf_tws_old;
static String unit3old = ""; static FormattedData bvf_dbt_old;
static String svalue4old = ""; static FormattedData bvf_stw_old;
static String unit4old = "";
static String svalue5old = "";
static String unit5old = "";
static String svalue6old = "";
static String unit6old = "";
// Get boat value for AWA // Get boat value for AWA
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) GwApi::BoatValue *bv_awa = pageData.values[0]; // First element in list
String name1 = xdrDelete(bvalue1->getName()); // Value name String name_awa = xdrDelete(bv_awa->getName(), 6); // get name without prefix and limit length
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 calibrationData.calibrateInstance(bv_awa, logger); // Check if boat data value is to be calibrated
double value1 = bvalue1->value; // Value as double in SI unit #endif
bool valid1 = bvalue1->valid; // Valid information FormattedData bvf_awa = commonData->fmt->formatValue(bv_awa, *commonData);
value1 = commonData->fmt->formatValue(bvalue1, *commonData).value;// Format only nesaccery for simulation data for pointer if (bv_awa->valid) { // Save formatted data for hold feature
String svalue1 = commonData->fmt->formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places bvf_awa_old = bvf_awa;
String unit1 = commonData->fmt->formatValue(bvalue1, *commonData).unit; // Unit of value
if(valid1 == true){
svalue1old = svalue1; // Save old value
unit1old = unit1; // Save old unit
} }
// Get boat value for AWS // Get boat value for AWS
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list GwApi::BoatValue *bv_aws = pageData.values[1]; // Second element in list
String name2 = xdrDelete(bvalue2->getName()); // Value name String name_aws = xdrDelete(bv_aws->getName(), 6); // get name without prefix and limit length
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 calibrationData.calibrateInstance(bv_aws, logger); // Check if boat data value is to be calibrated
double value2 = bvalue2->value; // Value as double in SI unit #endif
bool valid2 = bvalue2->valid; // Valid information FormattedData bvf_aws = commonData->fmt->formatValue(bv_aws, *commonData);
String svalue2 = commonData->fmt->formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places if (bv_aws->valid) { // Save formatted data for hold feature
String unit2 = commonData->fmt->formatValue(bvalue2, *commonData).unit; // Unit of value bvf_aws_old = bvf_aws;
if(valid2 == true){
svalue2old = svalue2; // Save old value
unit2old = unit2; // Save old unit
} }
// Get boat value for TWD // Get boat value for TWD
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list GwApi::BoatValue *bv_twd = pageData.values[2]; // Third element in list
String name3 = xdrDelete(bvalue3->getName()); // Value name String name_twd = xdrDelete(bv_twd->getName(), 6); // get name without prefix and limit length
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 calibrationData.calibrateInstance(bv_twd, logger); // Check if boat data value is to be calibrated
double value3 = bvalue3->value; // Value as double in SI unit #endif
bool valid3 = bvalue3->valid; // Valid information FormattedData bvf_twd = commonData->fmt->formatValue(bv_twd, *commonData);
String svalue3 = commonData->fmt->formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places if (bv_twd->valid) { // Save formatted data for hold feature
String unit3 = commonData->fmt->formatValue(bvalue3, *commonData).unit; // Unit of value bvf_twd_old = bvf_twd;
if(valid3 == true){
svalue3old = svalue3; // Save old value
unit3old = unit3; // Save old unit
} }
// Get boat value for TWS // Get boat value for TWS
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Fourth element in list GwApi::BoatValue *bv_tws = pageData.values[3]; // Fourth element in list
String name4 = xdrDelete(bvalue4->getName()); // Value name String name_tws = xdrDelete(bv_tws->getName(), 6); // get name without prefix and limit length
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 calibrationData.calibrateInstance(bv_tws, logger); // Check if boat data value is to be calibrated
double value4 = bvalue4->value; // Value as double in SI unit #endif
bool valid4 = bvalue4->valid; // Valid information FormattedData bvf_tws = commonData->fmt->formatValue(bv_tws, *commonData);
String svalue4 = commonData->fmt->formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places if (bv_tws->valid) { // Save formatted data for hold feature
String unit4 = commonData->fmt->formatValue(bvalue4, *commonData).unit; // Unit of value bvf_tws_old = bvf_tws;
if(valid4 == true){
svalue4old = svalue4; // Save old value
unit4old = unit4; // Save old unit
} }
// Get boat value for DBT // Get boat value for DBT
GwApi::BoatValue *bvalue5 = pageData.values[4]; // Fifth element in list GwApi::BoatValue *bv_dbt = pageData.values[4]; // Fifth element in list
String name5 = xdrDelete(bvalue5->getName()); // Value name String name_dbt = xdrDelete(bv_dbt->getName(), 6); // get name without prefix and limit length
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 calibrationData.calibrateInstance(bv_dbt, logger); // Check if boat data value is to be calibrated
double value5 = bvalue5->value; // Value as double in SI unit #endif
bool valid5 = bvalue5->valid; // Valid information FormattedData bvf_dbt = commonData->fmt->formatValue(bv_dbt, *commonData);
String svalue5 = commonData->fmt->formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places if (bv_dbt->valid) { // Save formatted data for hold feature
String unit5 = commonData->fmt->formatValue(bvalue5, *commonData).unit; // Unit of value bvf_dbt_old = bvf_dbt;
if(valid5 == true){
svalue5old = svalue5; // Save old value
unit5old = unit5; // Save old unit
} }
// Get boat value for STW // Get boat value for STW
GwApi::BoatValue *bvalue6 = pageData.values[5]; // Sixth element in list GwApi::BoatValue *bv_stw = pageData.values[5]; // Sixth element in list
String name6 = xdrDelete(bvalue6->getName()); // Value name String name_stw = xdrDelete(bv_stw->getName(), 6); // get name without prefix and limit length
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 calibrationData.calibrateInstance(bv_stw, logger); // Check if boat data value is to be calibrated
double value6 = bvalue6->value; // Value as double in SI unit #endif
bool valid6 = bvalue6->valid; // Valid information FormattedData bvf_stw = commonData->fmt->formatValue(bv_stw, *commonData);
String svalue6 = commonData->fmt->formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places if (bv_stw->valid) { // Save formatted data for hold feature
String unit6 = commonData->fmt->formatValue(bvalue6, *commonData).unit; // Unit of value bvf_stw_old = bvf_stw;
if(valid6 == true){
svalue6old = svalue6; // Save old value
unit6old = unit6; // Save old unit
} }
// Logging boat values // Log 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",
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); 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 // Draw page
//*********************************************************** // *********************************************************************
// Set display in partial refresh mode // 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); epd->setTextColor(commonData->fgcolor);
// Show values AWA // Show values AWA
epd->setFont(&DSEG7Classic_BoldItalic20pt7b); epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
epd->setCursor(10, 65); epd->setCursor(10, 65);
epd->print(svalue1); // Value epd->print(holdvalues ? bvf_awa_old.value : bvf_awa.value);
epd->setFont(&Ubuntu_Bold12pt8b); epd->setFont(&Ubuntu_Bold12pt8b);
epd->setCursor(10, 95); epd->setCursor(10, 95);
epd->print(name1); // Name epd->print(name_awa);
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b);
epd->setCursor(10, 115); epd->setCursor(10, 115);
epd->print(" "); epd->print(" ");
epd->print(holdvalues ? unit1old : unit1); epd->print(holdvalues ? bvf_awa_old.unit : bvf_awa.unit);
// Horizintal separator left // Horizintal separator left
epd->fillRect(0, 149, 60, 3, commonData->fgcolor); epd->fillRect(0, 149, 60, 3, commonData->fgcolor);
@@ -170,31 +154,32 @@ public:
// Show values AWS // Show values AWS
epd->setFont(&DSEG7Classic_BoldItalic20pt7b); epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
epd->setCursor(10, 270); epd->setCursor(10, 270);
epd->print(svalue2); // Value epd->print(holdvalues ? bvf_aws_old.value : bvf_aws.value);
epd->setFont(&Ubuntu_Bold12pt8b); epd->setFont(&Ubuntu_Bold12pt8b);
epd->setCursor(10, 220); epd->setCursor(10, 220);
epd->print(name2); // Name epd->print(name_aws);
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b);
epd->setCursor(10, 190); epd->setCursor(10, 190);
epd->print(" "); 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->setFont(&DSEG7Classic_BoldItalic20pt7b);
epd->setCursor(295, 65); epd->setCursor(295, 65);
if(valid3 == true){ // TODO WTF? Der Formatter sollte das korrekt machen
epd->print(abs(value3 * 180 / PI), 0); // Value if (bv_twd->valid) {
epd->print(abs(bv_twd->value * 180 / PI), 0); // Value
} }
else{ else {
epd->print("---"); // Value epd->print(commonData->fmt->placeholder);
} }
epd->setFont(&Ubuntu_Bold12pt8b); epd->setFont(&Ubuntu_Bold12pt8b);
epd->setCursor(335, 95); epd->setCursor(335, 95);
epd->print(name3); // Name epd->print(name_twd); // Name
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b);
epd->setCursor(335, 115); epd->setCursor(335, 115);
epd->print(" "); epd->print(" ");
epd->print(holdvalues ? unit3old : unit3); epd->print(holdvalues ? bvf_twd_old.unit : bvf_twd.unit);
// Horizintal separator right // Horizintal separator right
epd->fillRect(340, 149, 80, 3, commonData->fgcolor); epd->fillRect(340, 149, 80, 3, commonData->fgcolor);
@@ -202,16 +187,16 @@ public:
// Show values TWS // Show values TWS
epd->setFont(&DSEG7Classic_BoldItalic20pt7b); epd->setFont(&DSEG7Classic_BoldItalic20pt7b);
epd->setCursor(295, 270); epd->setCursor(295, 270);
epd->print(svalue4); // Value epd->print(name_tws);
epd->setFont(&Ubuntu_Bold12pt8b); epd->setFont(&Ubuntu_Bold12pt8b);
epd->setCursor(335, 220); epd->setCursor(335, 220);
epd->print(name4); // Name epd->print(holdvalues ? bvf_tws_old.value : bvf_tws.value);
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b);
epd->setCursor(335, 190); epd->setCursor(335, 190);
epd->print(" "); epd->print(" ");
epd->print(holdvalues ? unit4old : unit4); epd->print(holdvalues ? bvf_tws_old.unit : bvf_tws.unit);
//******************************************************************************************* // *********************************************************************
// Draw wind rose // Draw wind rose
int rInstrument = 110; // Radius of grafic instrument 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->getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
epd->setCursor(x-w/2, y+h/2); epd->setCursor(x-w/2, y+h/2);
if (i % 30 == 0) { if (i % 30 == 0) {
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b); // TODO move out of loop
epd->print(ii); epd->print(ii);
} }
@@ -279,9 +264,9 @@ public:
// Draw wind pointer // Draw wind pointer
float startwidth = 8; // Start width of pointer float startwidth = 8; // Start width of pointer
if(valid2 == true || holdvalues == true || simulation == true){ if (bv_aws->valid || holdvalues || simulation) {
float sinx=sin(value1); // Wind direction float sinx = sin(bv_awa->value); // Wind direction
float cosx=cos(value1); float cosx = cos(bv_awa->value);
// Normal pointer // Normal pointer
// Pointer as triangle with center base 2*width // Pointer as triangle with center base 2*width
float xx1 = -startwidth; float xx1 = -startwidth;
@@ -307,28 +292,33 @@ public:
epd->fillCircle(200, 150, startwidth + 6, commonData->bgcolor); epd->fillCircle(200, 150, startwidth + 6, commonData->bgcolor);
epd->fillCircle(200, 150, startwidth + 4, commonData->fgcolor); epd->fillCircle(200, 150, startwidth + 4, commonData->fgcolor);
//******************************************************************************************* // *********************************************************************
// Show values DBT // Show value DBT
epd->setFont(&DSEG7Classic_BoldItalic16pt7b); epd->setFont(&DSEG7Classic_BoldItalic16pt7b);
epd->setCursor(160, 200); epd->setCursor(160, 200);
epd->print(svalue5); // Value epd->print(holdvalues ? bvf_dbt_old.value : bvf_dbt.value);
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b);
epd->setCursor(190, 215); epd->setCursor(190, 215);
epd->print(" "); 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->setFont(&DSEG7Classic_BoldItalic16pt7b);
epd->setCursor(160, 130); epd->setCursor(160, 130);
epd->print(svalue6); // Value epd->print(holdvalues ? bvf_stw_old.value : bvf_stw.value);
epd->setFont(&Ubuntu_Bold8pt8b); epd->setFont(&Ubuntu_Bold8pt8b);
epd->setCursor(190, 90); epd->setCursor(190, 90);
epd->print(" "); epd->print(" ");
epd->print(holdvalues ? unit6old : unit6); epd->print(holdvalues ? bvf_stw_old.unit : bvf_stw.unit);
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
void leavePage(PageData &pageData) {
logger->logDebug(GwLog::LOG, "Leaving PageWindRose");
}
}; };
static Page *createPage(CommonData &common){ static Page *createPage(CommonData &common){

View File

@@ -3,7 +3,10 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#ifdef ENABLE_CALIBRATION
#include "BoatDataCalibration.h" #include "BoatDataCalibration.h"
#endif
class PageWindRoseFlex : public Page class PageWindRoseFlex : public Page
{ {
@@ -73,7 +76,9 @@ public:
} }
String name1 = bvalue1->getName().c_str(); // Value name String name1 = bvalue1->getName().c_str(); // Value name
name1 = name1.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
#endif
double value1 = bvalue1->value; // Value as double in SI unit double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information 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 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 String name2 = bvalue2->getName().c_str(); // Value name
name2 = name2.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
#endif
double value2 = bvalue2->value; // Value as double in SI unit double value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information bool valid2 = bvalue2->valid; // Valid information
if (simulation) { if (simulation) {
@@ -108,7 +115,9 @@ public:
GwApi::BoatValue *bvalue3 = pageData.values[0]; GwApi::BoatValue *bvalue3 = pageData.values[0];
String name3 = xdrDelete(bvalue3->getName()); // Value name String name3 = xdrDelete(bvalue3->getName()); // Value name
name3 = name3.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
#endif
double value3 = bvalue3->value; // Value as double in SI unit double value3 = bvalue3->value; // Value as double in SI unit
bool valid3 = bvalue3->valid; // Valid information 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 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]; GwApi::BoatValue *bvalue4 = pageData.values[1];
String name4 = xdrDelete(bvalue4->getName()); // Value name String name4 = xdrDelete(bvalue4->getName()); // Value name
name4 = name4.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
#endif
double value4 = bvalue4->value; // Value as double in SI unit double value4 = bvalue4->value; // Value as double in SI unit
bool valid4 = bvalue4->valid; // Valid information 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 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]; GwApi::BoatValue *bvalue5 = pageData.values[2];
String name5 = xdrDelete(bvalue5->getName()); // Value name String name5 = xdrDelete(bvalue5->getName()); // Value name
name5 = name5.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
#endif
double value5 = bvalue5->value; // Value as double in SI unit double value5 = bvalue5->value; // Value as double in SI unit
bool valid5 = bvalue5->valid; // Valid information 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 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]; GwApi::BoatValue *bvalue6 = pageData.values[3];
String name6 = xdrDelete(bvalue6->getName()); // Value name String name6 = xdrDelete(bvalue6->getName()); // Value name
name6 = name6.substring(0, 6); // String length limit for 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 calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
#endif
double value6 = bvalue6->value; // Value as double in SI unit double value6 = bvalue6->value; // Value as double in SI unit
bool valid6 = bvalue6->valid; // Valid information 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 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 epd->print(svalue4); // Value
} }
else{ else{
epd->print("---"); // Value epd->print(commonData->fmt->placeholder);
} }
epd->setFont(&Ubuntu_Bold12pt8b); epd->setFont(&Ubuntu_Bold12pt8b);
epd->setCursor(335, 95); 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 // 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->setFont(&DSEG7Classic_BoldItalic16pt7b);
epd->setCursor(160, 200); epd->setCursor(160, 200);
epd->print(svalue6); // Value epd->print(svalue6); // Value
@@ -346,7 +362,8 @@ public:
} }
epd->print(ssource); // true or app. epd->print(ssource); // true or app.
} }
else { else {
// pointer points downwards
epd->setFont(&DSEG7Classic_BoldItalic16pt7b); epd->setFont(&DSEG7Classic_BoldItalic16pt7b);
epd->setCursor(160, 130); epd->setCursor(160, 130);
epd->print(svalue6); epd->print(svalue6);
@@ -355,11 +372,11 @@ public:
epd->print(" "); epd->print(" ");
epd->print(holdvalues ? unit6old : unit6); epd->print(holdvalues ? unit6old : unit6);
if (sin(value1) > 0) { if (sin(value1) > 0) {
epd->setCursor(160, 130); epd->setCursor(160, 200);
} else { } else {
epd->setCursor(220, 130); epd->setCursor(220, 200);
} }
epd->print(ssource); //true or app. epd->print(ssource); //true or app.
} }
return PAGE_UPDATE; return PAGE_UPDATE;

View File

@@ -6,7 +6,6 @@
#include <functional> #include <functional>
#include <vector> #include <vector>
#include "LedSpiTask.h" #include "LedSpiTask.h"
#include "OBPRingBuffer.h"
#include "OBPDataOperations.h" #include "OBPDataOperations.h"
#define MAX_PAGE_NUMBER 10 // Max number of pages for show data #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 uint8_t pageNumber; // page number in sequence of visible pages
//the values will always contain the user defined values first //the values will always contain the user defined values first
ValueList values; ValueList values;
tBoatHstryData boatHstry; HstryBuf* boatHstry;
} PageData; } PageData;
// Sensor data structure (only for extended sensors, not for NMEA bus sensors) // 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 //a base class that all pages must inherit from
class Page{ class Page{
protected: protected:
// TODO Future: GwApi *api;
CommonData *commonData; CommonData *commonData;
GwConfigHandler *config; GwConfigHandler *config;
GwLog *logger; GwLog *logger;
@@ -208,7 +208,7 @@ class PageDescription{
class PageStruct{ class PageStruct{
public: public:
Page *page=NULL; Page *page = nullptr;
PageData parameters; PageData parameters;
PageDescription *description=NULL; PageDescription *description = nullptr;
}; };

View File

@@ -8,9 +8,10 @@ Coding style
------------ ------------
WIP WIP
Please format your new code the same as already existing code. 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 Git commands
------------ ------------

View File

@@ -1368,6 +1368,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -1679,6 +1680,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -1981,6 +1983,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2274,6 +2277,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2558,6 +2562,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2833,6 +2838,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3099,6 +3105,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3356,6 +3363,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3604,6 +3612,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3843,6 +3852,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",

View File

@@ -1391,6 +1391,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -1722,6 +1723,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2044,6 +2046,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2357,6 +2360,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2661,6 +2665,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -2956,6 +2961,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3242,6 +3248,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3519,6 +3526,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -3787,6 +3795,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",
@@ -4046,6 +4055,7 @@
"SkyView", "SkyView",
"Solar", "Solar",
"ThreeValues", "ThreeValues",
"Tracker",
"TwoValues", "TwoValues",
"Voltage", "Voltage",
"WhitePage", "WhitePage",

View 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

View File

@@ -56,9 +56,11 @@ public:
} }
uint16_t add() { uint16_t add() {
// returns new head value pointer // returns new head value pointer
return 0;
} }
uint8_t* get() { uint8_t* get() {
// returns complete buffer in order new to old // returns complete buffer in order new to old
return 0;
} }
uint8_t getvalue(uint16_t dt) { uint8_t getvalue(uint16_t dt) {
// Return a single value delta seconds ago // Return a single value delta seconds ago
@@ -66,6 +68,7 @@ public:
return 0; return 0;
} }
uint8_t getvalue3() { uint8_t getvalue3() {
return 0;
} }
bool clear() { bool clear() {
// clears buffer and permanent storage // clears buffer and permanent storage
@@ -80,5 +83,6 @@ public:
~History() { ~History() {
} }
void *addSeries() { void *addSeries() {
return nullptr;
} }
}; };

View File

@@ -1,10 +1,12 @@
// Add a new register card in web configuration interface
// This is a Java Script!
(function(){ (function(){
const api=window.esp32nmea2k; const api=window.esp32nmea2k;
if (! api) return; if (! api) return;
const tabName="OBP60"; const tabName="Screen";
api.registerListener((id, data) => { api.registerListener((id, data) => {
// if (!data.testboard) return; //do nothing if we are not active // 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) { api.addEl('button', '', page, 'Screenshot').addEventListener('click', function (ev) {
window.open('/api/user/OBP60Task/screenshot', 'screenshot'); window.open('/api/user/OBP60Task/screenshot', 'screenshot');
}) })

View File

@@ -788,3 +788,70 @@ dict =
BMP:Windows bitmap (BMP) BMP:Windows bitmap (BMP)
category = OBP60 Pages category = OBP60 Pages
capabilities = obp60:true 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

View File

@@ -13,10 +13,13 @@
#include <NMEA0183Messages.h> #include <NMEA0183Messages.h>
#include <GxEPD2_BW.h> // GxEPD2 lib for b/w E-Ink displays #include <GxEPD2_BW.h> // GxEPD2 lib for b/w E-Ink displays
#include "OBP60Extensions.h" // Functions lib for extension board #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 "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 "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 #ifdef BOARD_OBP40S3
#include "driver/rtc_io.h" // Needs for weakup from deep sleep #include "driver/rtc_io.h" // Needs for weakup from deep sleep
@@ -26,8 +29,6 @@
// Pictures // Pictures
#include "images/OBP_400x300.xbm" // OBP Logo #include "images/OBP_400x300.xbm" // OBP Logo
#include "images/unknown.xbm" // unknown page indicator #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 // Global vars
bool initComplete = false; // Initialization complete bool initComplete = false; // Initialization complete
@@ -65,8 +66,8 @@ void OBP60Init(GwApi *api){
#endif #endif
// Settings for e-paper display // Settings for e-paper display
String fastrefresh = api->getConfig()->getConfigItem(api->getConfig()->fastRefresh,true)->asString(); String fastrefresh = config->getConfigItem(config->fastRefresh,true)->asString();
logger->logDebug(GwLog::DEBUG,"Fast Refresh Mode is: %s", fastrefresh.c_str()); logger->logDebug(GwLog::DEBUG, "Fast Refresh Mode is: %s", fastrefresh.c_str());
#ifdef DISPLAY_GDEY042T81 #ifdef DISPLAY_GDEY042T81
if(fastrefresh == "true"){ if(fastrefresh == "true"){
static const bool useFastFullUpdate = true; // Enable fast full display update only for GDEY042T81 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); logger->logDebug(GwLog::LOG,"CPU speed at boot: %i MHz", freq);
// Settings for backlight // Settings for backlight
String backlightMode = api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString(); String backlightMode = config->getConfigItem(config->backlight,true)->asString();
logger->logDebug(GwLog::DEBUG,"Backlight Mode is: %s", backlightMode.c_str()); logger->logDebug(GwLog::DEBUG, "Backlight Mode is: %s", backlightMode.c_str());
uint brightness = uint(api->getConfig()->getConfigItem(api->getConfig()->blBrightness,true)->asInt()); uint brightness = uint(config->getConfigItem(config->blBrightness,true)->asInt());
String backlightColor = api->getConfig()->getConfigItem(api->getConfig()->blColor,true)->asString(); String backlightColor = config->getConfigItem(config->blColor,true)->asString();
if(String(backlightMode) == "On"){ if (backlightMode == "On") {
setBacklightLED(brightness, colorMapping(backlightColor)); setBacklightLED(brightness, colorMapping(backlightColor));
} }
else if(String(backlightMode) == "Off"){ else if (backlightMode == "Off") {
setBacklightLED(0, COLOR_BLACK); // Backlight LEDs off (blue without britghness) setBacklightLED(0, COLOR_BLACK); // Backlight LEDs off (blue without britghness)
} }
else if(String(backlightMode) == "Control by Key"){ else if (backlightMode == "Control by Key") {
setBacklightLED(0, COLOR_BLUE); // Backlight LEDs off (blue without britghness) setBacklightLED(0, COLOR_BLUE); // Backlight LEDs off (blue without britghness)
} }
// Settings flash LED mode // 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()); logger->logDebug(GwLog::DEBUG,"LED Mode is: %s", ledMode.c_str());
if(String(ledMode) == "Off"){ if (ledMode == "Off") {
setBlinkingLED(false); setBlinkingLED(false);
} }
@@ -114,79 +115,61 @@ void OBP60Init(GwApi *api){
initComplete = true; initComplete = true;
// Buzzer tone for initialization finish // 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); buzzer(TONE4, 500);
} }
typedef struct { /* ux-functions not working WTF?
int page0=0; bool listTasks(GwLog *logger) {
QueueHandle_t queue; UBaseType_t taskCount = uxTaskGetNumberOfTasks();
GwLog* logger = NULL; TaskStatus_t *taskStatusArray;
// GwApi* api = NULL;
uint sensitivity = 100;
bool use_syspage = true;
} MyData;
// Keyboard Task taskStatusArray = (TaskStatus_t *)pvPortMalloc(taskCount * sizeof(TaskStatus_t));
void keyboardTask(void *param){ if (taskStatusArray != NULL) {
MyData *data=(MyData *)param; taskCount = uxTaskGetSystemState(taskStatusArray, taskCount, NULL);
for (UBaseType_t i = 0; i < taskCount; i++) {
int keycode = 0; logger->logDebug(GwLog::LOG, "Task Name: %s (Stack=%d)", taskStatusArray[i].pcTaskName,
data->logger->logDebug(GwLog::LOG,"Start keyboard task"); taskStatusArray[i].usStackHighWaterMark);
// more fields in task status
// Loop for keyboard task // xHandle, uxCurrentPriority, uxBasePriority, usStackHighWaterMark
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);
} }
delay(20); // 50Hz update rate (20ms) vPortFree(taskStatusArray);
}
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++;
return true; return true;
} }
//helper to ensure that each BoatValue is only queried once logger->logDebug(GwLog::ERROR, "Failed to allocate memory for task list");
GwApi::BoatValue *findValueOrCreate(String name){ return false;
for (int i=0;i<numValues;i++){ } */
if (allBoatValues[i]->getName() == name) {
return allBoatValues[i]; 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 //we want to have a list that has all our page definitions
//this way each page can easily be added here //this way each page can easily be added here
//needs some minor tricks for the safe static initialization //needs some minor tricks for the safe static initialization
typedef std::vector<PageDescription*> Pages; typedef std::vector<PageDescription*> Pages;
//the page list class
class PageList{ class PageList{
public: public:
Pages pages; Pages pages;
@@ -276,264 +259,68 @@ void registerAllPages(GwLog *logger, PageList &list){
list.add(&registerPageAIS); list.add(&registerPageAIS);
extern PageDescription registerPageBarograph; extern PageDescription registerPageBarograph;
list.add(&registerPageBarograph); list.add(&registerPageBarograph);
extern PageDescription registerPageTracker;
list.add(&registerPageTracker);
logger->logDebug(GwLog::LOG,"Memory after registering pages: stack=%d, heap=%d", uxTaskGetStackHighWaterMark(NULL), ESP.getFreeHeap()); logger->logDebug(GwLog::LOG,"Memory after registering pages: stack=%d, heap=%d", uxTaskGetStackHighWaterMark(NULL), ESP.getFreeHeap());
} }
// Undervoltage detection for shutdown display // Undervoltage detection for shutdown display
void underVoltageDetection(GwApi *api, CommonData &common){ void underVoltageError(CommonData &common) {
// Read settings #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
double voffset = (api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asString()).toFloat(); // Switch off all power lines
double vslope = (api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asString()).toFloat(); 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 // Read supply voltage
#if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200 #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 float minVoltage = 3.65; // Absolut minimum volatge for 3,7V LiPo accu
#else #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; float minVoltage = MIN_VOLTAGE;
#endif #endif
double calVoltage = actVoltage * vslope + voffset; // Calibration float calVoltage = actVoltage * vslope + voffset; // Calibration
if(calVoltage < minVoltage){ return (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);
}
} }
// OBP60 Task // OBP60 Task
@@ -541,8 +328,8 @@ void handleHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryB
void OBP60Task(GwApi *api){ void OBP60Task(GwApi *api){
// vTaskDelete(NULL); // vTaskDelete(NULL);
// return; // return;
GwLog *logger=api->getLogger(); GwLog *logger = api->getLogger();
GwConfigHandler *config=api->getConfig(); GwConfigHandler *config = api->getConfig();
#ifdef HARDWARE_V21 #ifdef HARDWARE_V21
startLedTask(api); startLedTask(api);
#endif #endif
@@ -566,8 +353,8 @@ void OBP60Task(GwApi *api){
} }
// Init E-Ink display // Init E-Ink display
String displaymode = api->getConfig()->getConfigItem(api->getConfig()->display,true)->asString(); String displaymode = config->getConfigItem(config->display,true)->asString();
String displaycolor = api->getConfig()->getConfigItem(api->getConfig()->displaycolor,true)->asString(); String displaycolor = config->getConfigItem(config->displaycolor,true)->asString();
if (displaycolor == "Normal") { if (displaycolor == "Normal") {
commonData.fgcolor = GxEPD_BLACK; commonData.fgcolor = GxEPD_BLACK;
commonData.bgcolor = GxEPD_WHITE; commonData.bgcolor = GxEPD_WHITE;
@@ -576,12 +363,12 @@ void OBP60Task(GwApi *api){
commonData.fgcolor = GxEPD_WHITE; commonData.fgcolor = GxEPD_WHITE;
commonData.bgcolor = GxEPD_BLACK; commonData.bgcolor = GxEPD_BLACK;
} }
String systemname = api->getConfig()->getConfigItem(api->getConfig()->systemName,true)->asString(); String systemname = config->getConfigItem(config->systemName, true)->asString();
String wifipass = api->getConfig()->getConfigItem(api->getConfig()->apPassword,true)->asString(); String wifipass = config->getConfigItem(config->apPassword, true)->asString();
bool refreshmode = api->getConfig()->getConfigItem(api->getConfig()->refresh,true)->asBoolean(); bool refreshmode = config->getConfigItem(config->refresh, true)->asBoolean();
bool symbolmode = (config->getString(config->headerFormat) == "ICON"); bool symbolmode = (config->getString(config->headerFormat) == "ICON");
String fastrefresh = api->getConfig()->getConfigItem(api->getConfig()->fastRefresh,true)->asString(); String fastrefresh = config->getConfigItem(config->fastRefresh, true)->asString();
uint fullrefreshtime = uint(api->getConfig()->getConfigItem(api->getConfig()->fullRefreshTime,true)->asInt()); uint fullrefreshtime = uint(config->getConfigItem(config->fullRefreshTime, true)->asInt());
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
bool syspage_enabled = config->getBool(config->systemPage); bool syspage_enabled = config->getBool(config->systemPage);
#endif #endif
@@ -648,16 +435,11 @@ void OBP60Task(GwApi *api){
int lastPage=pageNumber; int lastPage=pageNumber;
BoatValueList boatValues; //all the boat values for the api query 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); //commonData.distanceformat=config->getString(xxx);
//add all necessary data to common data //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 //fill the page data from config
numPages=config->getInt(config->visiblePages,1); numPages=config->getInt(config->visiblePages,1);
if (numPages < 1) numPages=1; if (numPages < 1) numPages=1;
@@ -678,6 +460,7 @@ void OBP60Task(GwApi *api){
pages[i].page=description->creator(commonData); pages[i].page=description->creator(commonData);
pages[i].parameters.pageName=pageType; pages[i].parameters.pageName=pageType;
pages[i].parameters.pageNumber = i + 1; 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); LOG_DEBUG(GwLog::DEBUG,"found page %s for number %d",pageType.c_str(),i);
//fill in all the user defined parameters //fill in all the user defined parameters
for (int uid=0;uid<description->userParam;uid++){ 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); LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i);
pages[i].parameters.values.push_back(value); pages[i].parameters.values.push_back(value);
} }
if (pages[i].description->pageName == "WindPlot") { // Add boat history data to page parameters
// Add boat history data to page parameters pages[i].parameters.boatHstry = &hstryBufList;
pages[i].parameters.boatHstry = hstryBufList;
}
} }
// add out of band system page (always available) // add out of band system page (always available)
Page *syspage = allPages.pages[0]->creator(commonData); Page *syspage = allPages.pages[0]->creator(commonData);
@@ -707,12 +488,12 @@ void OBP60Task(GwApi *api){
// Read all calibration data settings from config // Read all calibration data settings from config
calibrationData.readConfig(config, logger); 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 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 // Initialize history buffer for certain boat data
initHstryBuf(api, &boatValues, hstryBufList); hstryBufList.init(&boatValues, logger);
// Display screenshot handler for HTTP request // Display screenshot handler for HTTP request
// http://192.168.15.1/api/user/OBP60Task/screenshot // http://192.168.15.1/api/user/OBP60Task/screenshot
@@ -720,39 +501,41 @@ void OBP60Task(GwApi *api){
doImageRequest(api, &pageNumber, pages, request); doImageRequest(api, &pageNumber, pages, request);
}); });
//now we have prepared the page data // now we have prepared the page data
//we start a separate task that will fetch our keys... // we start a separate task that will fetch our keys...
MyData allParameters; KbTaskData kbparams;
allParameters.logger=api->getLogger(); kbparams.logger = api->getLogger();
allParameters.page0=3; kbparams.queue = xQueueCreate(10, sizeof(int));
allParameters.queue=xQueueCreate(10,sizeof(int)); kbparams.sensitivity = api->getConfig()->getInt(GwConfigDefinitions::tSensitivity);
allParameters.sensitivity= api->getConfig()->getInt(GwConfigDefinitions::tSensitivity);
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
allParameters.use_syspage = syspage_enabled; kbparams.use_syspage = syspage_enabled;
#endif #endif
xTaskCreate(keyboardTask,"keyboard",2000,&allParameters,configMAX_PRIORITIES-1,NULL); createKeyboardTask(&kbparams);
SharedData *shared=new SharedData(api); // we start a separate task to collect sensor data
SharedData *shared = new SharedData(api);
createSensorTask(shared); createSensorTask(shared);
// Task Loop // Task Loop
//#################################################################################### //####################################################################################
// Configuration values for main loop // Configuration values for main loop
String gpsFix = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString(); String gpsFix = config->getConfigItem(config->flashLED,true)->asString();
String gpsOn=api->getConfig()->getConfigItem(api->getConfig()->useGPS,true)->asString(); String gpsOn = config->getConfigItem(config->useGPS,true)->asString();
float tz = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asFloat(); float tz = config->getConfigItem(config->timeZone,true)->asFloat();
commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight,true)->asString()); commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight, true)->asString());
commonData.backlight.color = colorMapping(config->getConfigItem(config->blColor,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.backlight.brightness = 2.55 * uint(config->getConfigItem(config->blBrightness, true)->asInt());
commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString(); commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode, true)->asString();
bool uvoltage = api->getConfig()->getConfigItem(api->getConfig()->underVoltage,true)->asBoolean(); bool uvoltage = config->getConfigItem(config->underVoltage, true)->asBoolean();
String cpuspeed = api->getConfig()->getConfigItem(api->getConfig()->cpuSpeed,true)->asString(); float voffset = (config->getConfigItem(config->vOffset,true)->asString()).toFloat();
uint hdopAccuracy = uint(api->getConfig()->getConfigItem(api->getConfig()->hdopAccuracy,true)->asInt()); 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 homelat = config->getString(config->homeLAT).toDouble();
double homelon = commonData.config->getString(commonData.config->homeLON).toDouble(); double homelon = config->getString(config->homeLON).toDouble();
bool homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0; bool homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0;
if (homevalid) { if (homevalid) {
logger->logDebug(GwLog::LOG, "Home location set to lat=%f, lon=%f", homelat, homelon); 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 pages[pageNumber].page->setupKeys(); // Initialize keys for first page
// listTasks(logger);
// Main loop runs with 100ms // Main loop runs with 100ms
//#################################################################################### //####################################################################################
@@ -796,8 +581,11 @@ void OBP60Task(GwApi *api){
bool keypressed = false; bool keypressed = false;
// Undervoltage detection // Undervoltage detection
if(uvoltage == true){ if (uvoltage == true) {
underVoltageDetection(api, commonData); if (underVoltageDetection(voffset, vslope)) {
LOG_DEBUG(GwLog::ERROR, "Undervoltage detected, shutting down!");
underVoltageError(commonData);
}
} }
// Set CPU speed after boot after 1min // Set CPU speed after boot after 1min
@@ -833,7 +621,7 @@ void OBP60Task(GwApi *api){
// Check the keyboard message // Check the keyboard message
int keyboardMessage=0; 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); LOG_DEBUG(GwLog::LOG,"new key from keyboard %d",keyboardMessage);
keypressed = true; keypressed = true;
@@ -1019,10 +807,10 @@ void OBP60Task(GwApi *api){
api->getStatus(commonData.status); api->getStatus(commonData.status);
if (calcTrueWnds) { if (calcTrueWnds) {
addTrueWind(api, &boatValues); trueWind.addTrueWind(api, &boatValues, logger);
} }
// Handle history buffers for TWD, TWS for wind plot page and other usage // Handle history buffers for TWD, TWS for wind plot page and other usage
handleHstryBuf(api, &boatValues, hstryBufList, simulation); hstryBufList.handleHstryBuf(useSimuData);
// Clear display // Clear display
// epd->fillRect(0, 0, epd->width(), epd->height(), commonData.bgcolor); // epd->fillRect(0, 0, epd->width(), epd->height(), commonData.bgcolor);

View File

@@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "GwApi.h" #include "GwApi.h"
//we only compile for some boards //we only compile for some boards
@@ -41,5 +42,24 @@
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
DECLARE_CAPABILITY(obp40,true) DECLARE_CAPABILITY(obp40,true)
#endif #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 #endif

View File

@@ -22,14 +22,11 @@ lib_deps =
Wire Wire
SPI SPI
ESP32time ESP32time
esphome/AsyncTCP-esphome@2.0.1
robtillaart/PCF8574@0.3.9 robtillaart/PCF8574@0.3.9
adafruit/Adafruit Unified Sensor @ 1.1.13 adafruit/Adafruit Unified Sensor @ 1.1.13
blemasle/MCP23017@2.0.0 blemasle/MCP23017@2.0.0
adafruit/Adafruit BusIO@1.5.0 adafruit/Adafruit BusIO@1.5.0
adafruit/Adafruit GFX Library@1.11.9 adafruit/Adafruit GFX Library@1.11.9
#zinggjm/GxEPD2@1.5.8
#https://github.com/ZinggJM/GxEPD2
https://github.com/thooge/GxEPD2 https://github.com/thooge/GxEPD2
sstaub/Ticker@4.4.0 sstaub/Ticker@4.4.0
adafruit/Adafruit BMP280 Library@2.6.2 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_GYE042A87 #alternativ E-Ink display from Genyo Optical, R10 2.2 ohm - medium
# -D DISPLAY_SE0420NQ04 #alternativ E-Ink display from SID Technology, R10 2.2 ohm - bad (burn in effects) # -D DISPLAY_SE0420NQ04 #alternativ E-Ink display from SID Technology, R10 2.2 ohm - bad (burn in effects)
# -D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good # -D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
# -D ENABLE_TRUEWIND # calculate true wind data (default off)
# -D ENABLE_CALIBRATION # boat data calibration (default off)
${env.build_flags} ${env.build_flags}
#CONFIG_ESP_TASK_WDT_TIMEOUT_S = 10 #Task Watchdog timeout period (seconds) [1...60] 5 default #CONFIG_ESP_TASK_WDT_TIMEOUT_S = 10 #Task Watchdog timeout period (seconds) [1...60] 5 default
upload_port = /dev/ttyACM0 #OBP60 download via USB-C direct upload_port = /dev/ttyACM0 #OBP60 download via USB-C direct
@@ -73,14 +72,11 @@ lib_deps =
Wire Wire
SPI SPI
ESP32time ESP32time
esphome/AsyncTCP-esphome@2.0.1
robtillaart/PCF8574@0.3.9 robtillaart/PCF8574@0.3.9
adafruit/Adafruit Unified Sensor @ 1.1.13 adafruit/Adafruit Unified Sensor @ 1.1.13
blemasle/MCP23017@2.0.0 blemasle/MCP23017@2.0.0
adafruit/Adafruit BusIO@1.5.0 adafruit/Adafruit BusIO@1.5.0
adafruit/Adafruit GFX Library@1.11.9 adafruit/Adafruit GFX Library@1.11.9
#zinggjm/GxEPD2@1.5.8
#https://github.com/ZinggJM/GxEPD2
https://github.com/thooge/GxEPD2 https://github.com/thooge/GxEPD2
sstaub/Ticker@4.4.0 sstaub/Ticker@4.4.0
adafruit/Adafruit BMP280 Library@2.6.2 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 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_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 DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
#-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh # -D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
#-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors # -D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
# -D ENABLE_TRUEWIND # calculate true wind data (default off)
# -D ENABLE_CALIBRATION # boat data calibration (default off)
${env.build_flags} ${env.build_flags}
upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter
upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27 upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27

View 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()

View 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")

View File

@@ -21,8 +21,8 @@ lib_deps =
ttlappalainen/NMEA2000-library @ 4.22.0 ttlappalainen/NMEA2000-library @ 4.22.0
ttlappalainen/NMEA0183 @ 1.10.1 ttlappalainen/NMEA0183 @ 1.10.1
ArduinoJson @ 6.18.5 ArduinoJson @ 6.18.5
AsyncTCP-esphome @ 2.0.1 ESP32Async/AsyncTCP @ 3.4.7
ottowinter/ESPAsyncWebServer-esphome@2.0.1 ESP32Async/ESPAsyncWebServer @ 3.8.0
FS FS
Preferences Preferences
ESPmDNS ESPmDNS
@@ -56,6 +56,8 @@ lib_ldf_mode = off
monitor_speed = 115200 monitor_speed = 115200
build_flags = build_flags =
-D PIO_ENV_BUILD=$PIOENV -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 -std=gnu++17
build_unflags = build_unflags =
-std=gnu++11 -std=gnu++11