mirror of
https://github.com/thooge/esp32-nmea2000-obp60.git
synced 2026-03-28 18:06:37 +01:00
Compare commits
41 Commits
06dcd14bdc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 86cd1ed97c | |||
| e33f908187 | |||
|
|
065a9807d2 | ||
|
|
98c318f055 | ||
| 97fcebdcb7 | |||
| a6fd3ef599 | |||
|
|
66e71acac3 | ||
|
|
b85504bf50 | ||
|
|
3043be8e1d | ||
|
|
02b2c888ee | ||
|
|
00b06f458b | ||
|
|
43b0a780d5 | ||
| 0363ba4379 | |||
|
|
4b6e2abe33 | ||
|
|
0401d82b62 | ||
|
|
2fecbee492 | ||
|
|
fc5daaba37 | ||
|
|
04dc09e44a | ||
|
|
6c7997e369 | ||
| 1d2ba2f71d | |||
|
|
7f747e9b35 | ||
|
|
71512e7262 | ||
|
|
4468c0555b | ||
|
|
99404991a3 | ||
|
|
ee5077e0a5 | ||
|
|
bbecf5e55f | ||
|
|
ded1b2b22e | ||
|
|
a0a88fa2c9 | ||
|
|
e9bf54e99f | ||
|
|
64950c3974 | ||
|
|
fb2fbc85a4 | ||
|
|
6b92a5e69c | ||
|
|
ef4546a2e6 | ||
|
|
6870c9b8a4 | ||
|
|
a70d976a6e | ||
|
|
fbba6ffff2 | ||
|
|
d516c82041 | ||
|
|
ccca784ac2 | ||
|
|
337214d650 | ||
|
|
744cf6469b | ||
| 6bc1b60f60 |
@@ -104,8 +104,7 @@ def writeFileIfChanged(fileName,data):
|
||||
return True
|
||||
|
||||
def mergeConfig(base,other):
|
||||
for bdir in other:
|
||||
cname=os.path.join(bdir,"config.json")
|
||||
for cname in other:
|
||||
if os.path.exists(cname):
|
||||
print("merge config %s"%cname)
|
||||
with open(cname,'rb') as ah:
|
||||
@@ -151,13 +150,25 @@ def expandConfig(config):
|
||||
rt.append(replaceTexts(c,replace))
|
||||
return rt
|
||||
|
||||
def generateMergedConfig(inFile,outFile,addDirs=[]):
|
||||
def createUserItemList(dirs,itemName,files):
|
||||
rt=[]
|
||||
for d in dirs:
|
||||
iname=os.path.join(d,itemName)
|
||||
if os.path.exists(iname):
|
||||
rt.append(iname)
|
||||
for f in files:
|
||||
if not os.path.exists(f):
|
||||
raise Exception("user item %s not found"%f)
|
||||
rt.append(f)
|
||||
return rt
|
||||
|
||||
def generateMergedConfig(inFile,outFile,addFiles=[]):
|
||||
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=mergeConfig(config,addFiles)
|
||||
config=expandConfig(config)
|
||||
data=json.dumps(config,indent=2)
|
||||
writeFileIfChanged(outFile,data)
|
||||
@@ -274,9 +285,9 @@ class Grove:
|
||||
def _ss(self,z=False):
|
||||
if z:
|
||||
return self.name
|
||||
return self.name if self.name is not 'Z' else ''
|
||||
return self.name if self.name != 'Z' else ''
|
||||
def _suffix(self):
|
||||
return '_'+self.name if self.name is not 'Z' else ''
|
||||
return '_'+self.name if self.name != 'Z' else ''
|
||||
def replace(self,line):
|
||||
if line is None:
|
||||
return line
|
||||
@@ -377,12 +388,7 @@ def getLibs():
|
||||
|
||||
|
||||
|
||||
def joinFiles(target,pattern,dirlist):
|
||||
flist=[]
|
||||
for dir in dirlist:
|
||||
fn=os.path.join(dir,pattern)
|
||||
if os.path.exists(fn):
|
||||
flist.append(fn)
|
||||
def joinFiles(target,flist):
|
||||
current=False
|
||||
if os.path.exists(target):
|
||||
current=True
|
||||
@@ -453,7 +459,28 @@ def handleDeps(env):
|
||||
)
|
||||
env.AddBuildMiddleware(injectIncludes)
|
||||
|
||||
def getOption(env,name,toArray=True):
|
||||
try:
|
||||
opt=env.GetProjectOption(name)
|
||||
if toArray:
|
||||
if opt is None:
|
||||
return []
|
||||
if isinstance(opt,list):
|
||||
return opt
|
||||
return opt.split("\n" if "\n" in opt else ",")
|
||||
return opt
|
||||
except:
|
||||
pass
|
||||
if toArray:
|
||||
return []
|
||||
|
||||
def getFileList(files):
|
||||
base=basePath()
|
||||
rt=[]
|
||||
for f in files:
|
||||
if f is not None and f != "":
|
||||
rt.append(os.path.join(base,f))
|
||||
return rt
|
||||
def prebuild(env):
|
||||
global userTaskDirs
|
||||
print("#prebuild running")
|
||||
@@ -463,14 +490,18 @@ def prebuild(env):
|
||||
if ldf_mode == 'off':
|
||||
print("##ldf off - own dependency handling")
|
||||
handleDeps(env)
|
||||
extraConfigs=getOption(env,'custom_config',toArray=True)
|
||||
extraJs=getOption(env,'custom_js',toArray=True)
|
||||
extraCss=getOption(env,'custom_css',toArray=True)
|
||||
|
||||
userTaskDirs=getUserTaskDirs()
|
||||
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
|
||||
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
|
||||
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,createUserItemList(userTaskDirs,"config.json", getFileList(extraConfigs)))
|
||||
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)
|
||||
joinFiles(os.path.join(outPath(),INDEXJS+".gz"),createUserItemList(["web"]+userTaskDirs,INDEXJS,getFileList(extraJs)))
|
||||
joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),createUserItemList(["web"]+userTaskDirs,INDEXCSS,getFileList(extraCss)))
|
||||
embedded=getEmbeddedFiles(env)
|
||||
filedefs=[]
|
||||
for ef in embedded:
|
||||
|
||||
@@ -1,542 +0,0 @@
|
||||
print("running extra...")
|
||||
import gzip
|
||||
import shutil
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import json
|
||||
import glob
|
||||
from datetime import datetime
|
||||
import re
|
||||
import pprint
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.exception import InvalidProjectConfError
|
||||
|
||||
Import("env")
|
||||
#print(env.Dump())
|
||||
OWN_FILE="extra_script.py"
|
||||
GEN_DIR='lib/generated'
|
||||
CFG_FILE='web/config.json'
|
||||
XDR_FILE='web/xdrconfig.json'
|
||||
INDEXJS="index.js"
|
||||
INDEXCSS="index.css"
|
||||
CFG_INCLUDE='GwConfigDefinitions.h'
|
||||
CFG_INCLUDE_IMPL='GwConfigDefImpl.h'
|
||||
XDR_INCLUDE='GwXdrTypeMappings.h'
|
||||
TASK_INCLUDE='GwUserTasks.h'
|
||||
GROVE_CONFIG="GwM5GroveGen.h"
|
||||
GROVE_CONFIG_IN="lib/hardware/GwM5Grove.in"
|
||||
EMBEDDED_INCLUDE="GwEmbeddedFiles.h"
|
||||
|
||||
def getEmbeddedFiles(env):
|
||||
rt=[]
|
||||
efiles=env.GetProjectOption("board_build.embed_files")
|
||||
for f in efiles.split("\n"):
|
||||
if f == '':
|
||||
continue
|
||||
rt.append(f)
|
||||
return rt
|
||||
|
||||
def basePath():
|
||||
#see: https://stackoverflow.com/questions/16771894/python-nameerror-global-name-file-is-not-defined
|
||||
return os.path.dirname(inspect.getfile(lambda: None))
|
||||
|
||||
def outPath():
|
||||
return os.path.join(basePath(),GEN_DIR)
|
||||
def checkDir():
|
||||
dn=outPath()
|
||||
if not os.path.exists(dn):
|
||||
os.makedirs(dn)
|
||||
if not os.path.isdir(dn):
|
||||
print("unable to create %s"%dn)
|
||||
return False
|
||||
return True
|
||||
|
||||
def isCurrent(infile,outfile):
|
||||
if os.path.exists(outfile):
|
||||
otime=os.path.getmtime(outfile)
|
||||
itime=os.path.getmtime(infile)
|
||||
if (otime >= itime):
|
||||
own=os.path.join(basePath(),OWN_FILE)
|
||||
if os.path.exists(own):
|
||||
owntime=os.path.getmtime(own)
|
||||
if owntime > otime:
|
||||
return False
|
||||
print("%s is newer then %s, no need to recreate"%(outfile,infile))
|
||||
return True
|
||||
return False
|
||||
def compressFile(inFile,outfile):
|
||||
if isCurrent(inFile,outfile):
|
||||
return
|
||||
print("compressing %s"%inFile)
|
||||
with open(inFile, 'rb') as f_in:
|
||||
with gzip.open(outfile, 'wb') as f_out:
|
||||
shutil.copyfileobj(f_in, f_out)
|
||||
|
||||
def generateFile(infile,outfile,callback,inMode='rb',outMode='w'):
|
||||
if isCurrent(infile,outfile):
|
||||
return
|
||||
print("creating %s"%outfile)
|
||||
oh=None
|
||||
with open(infile,inMode) as ch:
|
||||
with open(outfile,outMode) as oh:
|
||||
try:
|
||||
callback(ch,oh,inFile=infile)
|
||||
oh.close()
|
||||
except Exception as e:
|
||||
try:
|
||||
oh.close()
|
||||
except:
|
||||
pass
|
||||
os.unlink(outfile)
|
||||
raise
|
||||
|
||||
def writeFileIfChanged(fileName,data):
|
||||
if os.path.exists(fileName):
|
||||
with open(fileName,"r") as ih:
|
||||
old=ih.read()
|
||||
ih.close()
|
||||
if old == data:
|
||||
return False
|
||||
print("#generating %s"%fileName)
|
||||
with open(fileName,"w") as oh:
|
||||
oh.write(data)
|
||||
return True
|
||||
|
||||
def mergeConfig(base,other):
|
||||
try:
|
||||
customconfig = env.GetProjectOption("custom_config")
|
||||
except InvalidProjectConfError:
|
||||
customconfig = None
|
||||
for bdir in other:
|
||||
if customconfig and os.path.exists(os.path.join(bdir,customconfig)):
|
||||
cname=os.path.join(bdir,customconfig)
|
||||
print("merge custom config {}".format(cname))
|
||||
with open(cname,'rb') as ah:
|
||||
base += json.load(ah)
|
||||
continue
|
||||
cname=os.path.join(bdir,"config.json")
|
||||
if os.path.exists(cname):
|
||||
print("merge config %s"%cname)
|
||||
with open(cname,'rb') as ah:
|
||||
merge=json.load(ah)
|
||||
base=base+merge
|
||||
return base
|
||||
|
||||
def replaceTexts(data,replacements):
|
||||
if replacements is None:
|
||||
return data
|
||||
if isinstance(data,str):
|
||||
for k,v in replacements.items():
|
||||
data=data.replace("$"+k,str(v))
|
||||
return data
|
||||
if isinstance(data,list):
|
||||
rt=[]
|
||||
for e in data:
|
||||
rt.append(replaceTexts(e,replacements))
|
||||
return rt
|
||||
if isinstance(data,dict):
|
||||
rt={}
|
||||
for k,v in data.items():
|
||||
rt[replaceTexts(k,replacements)]=replaceTexts(v,replacements)
|
||||
return rt
|
||||
return data
|
||||
def expandConfig(config):
|
||||
rt=[]
|
||||
for item in config:
|
||||
type=item.get('type')
|
||||
if type != 'array':
|
||||
rt.append(item)
|
||||
continue
|
||||
replacements=item.get('replace')
|
||||
children=item.get('children')
|
||||
name=item.get('name')
|
||||
if name is None:
|
||||
name="#unknown#"
|
||||
if not isinstance(replacements,list):
|
||||
raise Exception("missing replacements at array %s"%name)
|
||||
for replace in replacements:
|
||||
if children is not None:
|
||||
for c in children:
|
||||
rt.append(replaceTexts(c,replace))
|
||||
return rt
|
||||
|
||||
def generateMergedConfig(inFile,outFile,addDirs=[]):
|
||||
if not os.path.exists(inFile):
|
||||
raise Exception("unable to read cfg file %s"%inFile)
|
||||
data=""
|
||||
with open(inFile,'rb') as ch:
|
||||
config=json.load(ch)
|
||||
config=mergeConfig(config,addDirs)
|
||||
config=expandConfig(config)
|
||||
data=json.dumps(config,indent=2)
|
||||
writeFileIfChanged(outFile,data)
|
||||
|
||||
def generateCfg(inFile,outFile,impl):
|
||||
if not os.path.exists(inFile):
|
||||
raise Exception("unable to read cfg file %s"%inFile)
|
||||
data=""
|
||||
with open(inFile,'rb') as ch:
|
||||
config=json.load(ch)
|
||||
data+="//generated from %s\n"%inFile
|
||||
l=len(config)
|
||||
idx=0
|
||||
if not impl:
|
||||
data+='#include "GwConfigItem.h"\n'
|
||||
data+='class GwConfigDefinitions{\n'
|
||||
data+=' public:\n'
|
||||
data+=' int getNumConfig() const{return %d;}\n'%(l)
|
||||
for item in config:
|
||||
n=item.get('name')
|
||||
if n is None:
|
||||
continue
|
||||
if len(n) > 15:
|
||||
raise Exception("%s: config names must be max 15 caracters"%n)
|
||||
data+=' static constexpr const char* %s="%s";\n'%(n,n)
|
||||
data+="};\n"
|
||||
else:
|
||||
data+='void GwConfigHandler::populateConfigs(GwConfigInterface **config){\n'
|
||||
for item in config:
|
||||
name=item.get('name')
|
||||
if name is None:
|
||||
continue
|
||||
data+=' configs[%d]='%(idx)
|
||||
idx+=1
|
||||
secret="false";
|
||||
if item.get('type') == 'password':
|
||||
secret="true"
|
||||
data+=" new GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret)
|
||||
data+='}\n'
|
||||
writeFileIfChanged(outFile,data)
|
||||
|
||||
def labelFilter(label):
|
||||
return re.sub("[^a-zA-Z0-9]","",re.sub("\([0-9]*\)","",label))
|
||||
def generateXdrMappings(fp,oh,inFile=''):
|
||||
jdoc=json.load(fp)
|
||||
oh.write("static GwXDRTypeMapping* typeMappings[]={\n")
|
||||
first=True
|
||||
for cat in jdoc:
|
||||
item=jdoc[cat]
|
||||
cid=item.get('id')
|
||||
if cid is None:
|
||||
continue
|
||||
tc=item.get('type')
|
||||
if tc is not None:
|
||||
if first:
|
||||
first=False
|
||||
else:
|
||||
oh.write(",\n")
|
||||
oh.write(" new GwXDRTypeMapping(%d,0,%d) /*%s*/"%(cid,tc,cat))
|
||||
fields=item.get('fields')
|
||||
if fields is None:
|
||||
continue
|
||||
idx=0
|
||||
for fe in fields:
|
||||
if not isinstance(fe,dict):
|
||||
continue
|
||||
tc=fe.get('t')
|
||||
id=fe.get('v')
|
||||
if id is None:
|
||||
id=idx
|
||||
idx+=1
|
||||
l=fe.get('l') or ''
|
||||
if tc is None or id is None:
|
||||
continue
|
||||
if first:
|
||||
first=False
|
||||
else:
|
||||
oh.write(",\n")
|
||||
oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,id,tc,cat,l))
|
||||
oh.write("\n")
|
||||
oh.write("};\n")
|
||||
for cat in jdoc:
|
||||
item=jdoc[cat]
|
||||
cid=item.get('id')
|
||||
if cid is None:
|
||||
continue
|
||||
selectors=item.get('selector')
|
||||
if selectors is not None:
|
||||
for selector in selectors:
|
||||
label=selector.get('l')
|
||||
value=selector.get('v')
|
||||
if label is not None and value is not None:
|
||||
label=labelFilter(label)
|
||||
define=("GWXDRSEL_%s_%s"%(cat,label)).upper()
|
||||
oh.write(" #define %s %s\n"%(define,value))
|
||||
fields=item.get('fields')
|
||||
if fields is not None:
|
||||
idx=0
|
||||
for field in fields:
|
||||
v=field.get('v')
|
||||
if v is None:
|
||||
v=idx
|
||||
else:
|
||||
v=int(v)
|
||||
label=field.get('l')
|
||||
if v is not None and label is not None:
|
||||
define=("GWXDRFIELD_%s_%s"%(cat,labelFilter(label))).upper();
|
||||
oh.write(" #define %s %s\n"%(define,str(v)))
|
||||
idx+=1
|
||||
|
||||
class Grove:
|
||||
def __init__(self,name) -> None:
|
||||
self.name=name
|
||||
def _ss(self,z=False):
|
||||
if z:
|
||||
return self.name
|
||||
return self.name if self.name != 'Z' else ''
|
||||
def _suffix(self):
|
||||
return '_'+self.name if self.name != 'Z' else ''
|
||||
def replace(self,line):
|
||||
if line is None:
|
||||
return line
|
||||
return line.replace('$G$',self._ss()).replace('$Z$',self._ss(True)).replace('$GS$',self._suffix())
|
||||
def generateGroveDefs(inh,outh,inFile=''):
|
||||
GROVES=[Grove('Z'),Grove('A'),Grove('B'),Grove('C')]
|
||||
definition=[]
|
||||
started=False
|
||||
def writeConfig():
|
||||
for grove in GROVES:
|
||||
for cl in definition:
|
||||
outh.write(grove.replace(cl))
|
||||
|
||||
for line in inh:
|
||||
if re.match(" *#GROVE",line):
|
||||
started=True
|
||||
if len(definition) > 0:
|
||||
writeConfig()
|
||||
definition=[]
|
||||
continue
|
||||
if started:
|
||||
definition.append(line)
|
||||
if len(definition) > 0:
|
||||
writeConfig()
|
||||
|
||||
|
||||
|
||||
userTaskDirs=[]
|
||||
|
||||
def getUserTaskDirs():
|
||||
rt=[]
|
||||
taskdirs=glob.glob(os.path.join( basePath(),'lib','*task*'))
|
||||
for task in taskdirs:
|
||||
rt.append(task)
|
||||
return rt
|
||||
|
||||
def checkAndAdd(file,names,ilist):
|
||||
if not file.endswith('.h'):
|
||||
return
|
||||
match=False
|
||||
for cmp in names:
|
||||
#print("##check %s<->%s"%(f.lower(),cmp))
|
||||
if file.lower() == cmp:
|
||||
match=True
|
||||
if not match:
|
||||
return
|
||||
ilist.append(file)
|
||||
def genereateUserTasks(outfile):
|
||||
includes=[]
|
||||
for task in userTaskDirs:
|
||||
#print("##taskdir=%s"%task)
|
||||
base=os.path.basename(task)
|
||||
includeNames=[base.lower()+".h",'gw'+base.lower()+'.h']
|
||||
for f in os.listdir(task):
|
||||
checkAndAdd(f,includeNames,includes)
|
||||
includeData=""
|
||||
for i in includes:
|
||||
print("#task include %s"%i)
|
||||
includeData+="#include <%s>\n"%i
|
||||
writeFileIfChanged(outfile,includeData)
|
||||
|
||||
def generateEmbedded(elist,outFile):
|
||||
content=""
|
||||
for entry in elist:
|
||||
content+="EMBED_GZ_FILE(\"%s\",%s,\"%s\");\n"%entry
|
||||
writeFileIfChanged(outFile,content)
|
||||
|
||||
def getContentType(fn):
|
||||
if (fn.endswith('.gz')):
|
||||
fn=fn[0:-3]
|
||||
if (fn.endswith('html')):
|
||||
return "text/html"
|
||||
if (fn.endswith('json')):
|
||||
return "application/json"
|
||||
if (fn.endswith('js')):
|
||||
return "text/javascript"
|
||||
if (fn.endswith('css')):
|
||||
return "text/css"
|
||||
return "application/octet-stream"
|
||||
|
||||
|
||||
def getLibs():
|
||||
base=os.path.join(basePath(),"lib")
|
||||
rt=[]
|
||||
for sd in os.listdir(base):
|
||||
if sd == '..':
|
||||
continue
|
||||
if sd == '.':
|
||||
continue
|
||||
fn=os.path.join(base,sd)
|
||||
if os.path.isdir(fn):
|
||||
rt.append(sd)
|
||||
EXTRAS=['generated']
|
||||
for e in EXTRAS:
|
||||
if not e in rt:
|
||||
rt.append(e)
|
||||
return rt
|
||||
|
||||
|
||||
|
||||
def joinFiles(target,pattern,dirlist):
|
||||
flist=[]
|
||||
for dir in dirlist:
|
||||
fn=os.path.join(dir,pattern)
|
||||
if os.path.exists(fn):
|
||||
flist.append(fn)
|
||||
current=False
|
||||
if os.path.exists(target):
|
||||
current=True
|
||||
for f in flist:
|
||||
if not isCurrent(f,target):
|
||||
current=False
|
||||
break
|
||||
if current:
|
||||
print("%s is up to date"%target)
|
||||
return
|
||||
print("creating %s"%target)
|
||||
with gzip.open(target,"wb") as oh:
|
||||
for fn in flist:
|
||||
print("adding %s to %s"%(fn,target))
|
||||
with open(fn,"rb") as rh:
|
||||
shutil.copyfileobj(rh,oh)
|
||||
|
||||
|
||||
OWNLIBS=getLibs()+["FS","WiFi"]
|
||||
GLOBAL_INCLUDES=[]
|
||||
|
||||
def handleDeps(env):
|
||||
#overwrite the GetProjectConfig
|
||||
#to inject all our libs
|
||||
oldGetProjectConfig=env.GetProjectConfig
|
||||
def GetProjectConfigX(env):
|
||||
rt=oldGetProjectConfig()
|
||||
cenv="env:"+env['PIOENV']
|
||||
libs=[]
|
||||
for section,options in rt.as_tuple():
|
||||
if section == cenv:
|
||||
for key,values in options:
|
||||
if key == 'lib_deps':
|
||||
libs=values
|
||||
|
||||
mustUpdate=False
|
||||
for lib in OWNLIBS:
|
||||
if not lib in libs:
|
||||
libs.append(lib)
|
||||
mustUpdate=True
|
||||
if mustUpdate:
|
||||
update=[(cenv,[('lib_deps',libs)])]
|
||||
rt.update(update)
|
||||
return rt
|
||||
env.AddMethod(GetProjectConfigX,"GetProjectConfig")
|
||||
#store the list of all includes after we resolved
|
||||
#the dependencies for our main project
|
||||
#we will use them for all compilations afterwards
|
||||
oldLibBuilder=env.ConfigureProjectLibBuilder
|
||||
def ConfigureProjectLibBuilderX(env):
|
||||
global GLOBAL_INCLUDES
|
||||
project=oldLibBuilder()
|
||||
#print("##ConfigureProjectLibBuilderX")
|
||||
#pprint.pprint(project)
|
||||
if project.depbuilders:
|
||||
#print("##depbuilders %s"%",".join(map(lambda x: x.path,project.depbuilders)))
|
||||
for db in project.depbuilders:
|
||||
idirs=db.get_include_dirs()
|
||||
for id in idirs:
|
||||
if not id in GLOBAL_INCLUDES:
|
||||
GLOBAL_INCLUDES.append(id)
|
||||
return project
|
||||
env.AddMethod(ConfigureProjectLibBuilderX,"ConfigureProjectLibBuilder")
|
||||
def injectIncludes(env,node):
|
||||
return env.Object(
|
||||
node,
|
||||
CPPPATH=env["CPPPATH"]+GLOBAL_INCLUDES
|
||||
)
|
||||
env.AddBuildMiddleware(injectIncludes)
|
||||
|
||||
|
||||
def prebuild(env):
|
||||
global userTaskDirs
|
||||
print("#prebuild running")
|
||||
if not checkDir():
|
||||
sys.exit(1)
|
||||
ldf_mode=env.GetProjectOption("lib_ldf_mode")
|
||||
if ldf_mode == 'off':
|
||||
print("##ldf off - own dependency handling")
|
||||
handleDeps(env)
|
||||
userTaskDirs=getUserTaskDirs()
|
||||
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
|
||||
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
|
||||
compressFile(mergedConfig,mergedConfig+".gz")
|
||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False)
|
||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True)
|
||||
joinFiles(os.path.join(outPath(),INDEXJS+".gz"),INDEXJS,["web"]+userTaskDirs)
|
||||
joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),INDEXCSS,["web"]+userTaskDirs)
|
||||
embedded=getEmbeddedFiles(env)
|
||||
filedefs=[]
|
||||
for ef in embedded:
|
||||
print("#checking embedded file %s"%ef)
|
||||
(dn,fn)=os.path.split(ef)
|
||||
pureName=fn
|
||||
if pureName.endswith('.gz'):
|
||||
pureName=pureName[0:-3]
|
||||
ct=getContentType(pureName)
|
||||
usname=ef.replace('/','_').replace('.','_')
|
||||
filedefs.append((pureName,usname,ct))
|
||||
inFile=os.path.join(basePath(),"web",pureName)
|
||||
if os.path.exists(inFile):
|
||||
compressFile(inFile,ef)
|
||||
else:
|
||||
print("#WARNING: infile %s for %s not found"%(inFile,ef))
|
||||
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
|
||||
genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
|
||||
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
|
||||
generateFile(os.path.join(basePath(),GROVE_CONFIG_IN),os.path.join(outPath(),GROVE_CONFIG),generateGroveDefs,inMode='r')
|
||||
version="dev"+datetime.now().strftime("%Y%m%d")
|
||||
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
|
||||
|
||||
def cleangenerated(source, target, env):
|
||||
od=outPath()
|
||||
if os.path.isdir(od):
|
||||
print("#cleaning up %s"%od)
|
||||
for f in os.listdir(od):
|
||||
if f == "." or f == "..":
|
||||
continue
|
||||
fn=os.path.join(od,f)
|
||||
os.unlink(f)
|
||||
|
||||
|
||||
print("#prescript...")
|
||||
prebuild(env)
|
||||
board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper()
|
||||
print("Board=#%s#"%board)
|
||||
print("BuildFlags=%s"%(" ".join(env["BUILD_FLAGS"])))
|
||||
env.Append(
|
||||
LINKFLAGS=[ "-u", "custom_app_desc" ],
|
||||
CPPDEFINES=[(board,"1")]
|
||||
)
|
||||
#script does not run on clean yet - maybe in the future
|
||||
env.AddPostAction("clean",cleangenerated)
|
||||
|
||||
#look for extra task scripts and include them here
|
||||
for taskdir in userTaskDirs:
|
||||
script = os.path.join(taskdir, "extra_task.py")
|
||||
if os.path.isfile(script):
|
||||
taskname = os.path.basename(os.path.normpath(taskdir))
|
||||
print("#extra task script for '{}'".format(taskname))
|
||||
with open(script) as fh:
|
||||
try:
|
||||
code = compile(fh.read(), taskname, 'exec')
|
||||
except SyntaxError:
|
||||
print("#ERROR: script does not compile")
|
||||
continue
|
||||
exec(code)
|
||||
@@ -1,518 +0,0 @@
|
||||
print("running extra...")
|
||||
import gzip
|
||||
import shutil
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import json
|
||||
import glob
|
||||
from datetime import datetime
|
||||
import re
|
||||
import pprint
|
||||
from platformio.project.config import ProjectConfig
|
||||
|
||||
|
||||
Import("env")
|
||||
#print(env.Dump())
|
||||
OWN_FILE="extra_script.py"
|
||||
GEN_DIR='lib/generated'
|
||||
CFG_FILE='web/config.json'
|
||||
XDR_FILE='web/xdrconfig.json'
|
||||
INDEXJS="index.js"
|
||||
INDEXCSS="index.css"
|
||||
CFG_INCLUDE='GwConfigDefinitions.h'
|
||||
CFG_INCLUDE_IMPL='GwConfigDefImpl.h'
|
||||
XDR_INCLUDE='GwXdrTypeMappings.h'
|
||||
TASK_INCLUDE='GwUserTasks.h'
|
||||
GROVE_CONFIG="GwM5GroveGen.h"
|
||||
GROVE_CONFIG_IN="lib/hardware/GwM5Grove.in"
|
||||
EMBEDDED_INCLUDE="GwEmbeddedFiles.h"
|
||||
|
||||
def getEmbeddedFiles(env):
|
||||
rt=[]
|
||||
efiles=env.GetProjectOption("board_build.embed_files")
|
||||
for f in efiles.split("\n"):
|
||||
if f == '':
|
||||
continue
|
||||
rt.append(f)
|
||||
return rt
|
||||
|
||||
def basePath():
|
||||
#see: https://stackoverflow.com/questions/16771894/python-nameerror-global-name-file-is-not-defined
|
||||
return os.path.dirname(inspect.getfile(lambda: None))
|
||||
|
||||
def outPath():
|
||||
return os.path.join(basePath(),GEN_DIR)
|
||||
def checkDir():
|
||||
dn=outPath()
|
||||
if not os.path.exists(dn):
|
||||
os.makedirs(dn)
|
||||
if not os.path.isdir(dn):
|
||||
print("unable to create %s"%dn)
|
||||
return False
|
||||
return True
|
||||
|
||||
def isCurrent(infile,outfile):
|
||||
if os.path.exists(outfile):
|
||||
otime=os.path.getmtime(outfile)
|
||||
itime=os.path.getmtime(infile)
|
||||
if (otime >= itime):
|
||||
own=os.path.join(basePath(),OWN_FILE)
|
||||
if os.path.exists(own):
|
||||
owntime=os.path.getmtime(own)
|
||||
if owntime > otime:
|
||||
return False
|
||||
print("%s is newer then %s, no need to recreate"%(outfile,infile))
|
||||
return True
|
||||
return False
|
||||
def compressFile(inFile,outfile):
|
||||
if isCurrent(inFile,outfile):
|
||||
return
|
||||
print("compressing %s"%inFile)
|
||||
with open(inFile, 'rb') as f_in:
|
||||
with gzip.open(outfile, 'wb') as f_out:
|
||||
shutil.copyfileobj(f_in, f_out)
|
||||
|
||||
def generateFile(infile,outfile,callback,inMode='rb',outMode='w'):
|
||||
if isCurrent(infile,outfile):
|
||||
return
|
||||
print("creating %s"%outfile)
|
||||
oh=None
|
||||
with open(infile,inMode) as ch:
|
||||
with open(outfile,outMode) as oh:
|
||||
try:
|
||||
callback(ch,oh,inFile=infile)
|
||||
oh.close()
|
||||
except Exception as e:
|
||||
try:
|
||||
oh.close()
|
||||
except:
|
||||
pass
|
||||
os.unlink(outfile)
|
||||
raise
|
||||
|
||||
def writeFileIfChanged(fileName,data):
|
||||
if os.path.exists(fileName):
|
||||
with open(fileName,"r") as ih:
|
||||
old=ih.read()
|
||||
ih.close()
|
||||
if old == data:
|
||||
return False
|
||||
print("#generating %s"%fileName)
|
||||
with open(fileName,"w") as oh:
|
||||
oh.write(data)
|
||||
return True
|
||||
|
||||
def mergeConfig(base,other):
|
||||
for bdir in other:
|
||||
cname=os.path.join(bdir,"config.json")
|
||||
if os.path.exists(cname):
|
||||
print("merge config %s"%cname)
|
||||
with open(cname,'rb') as ah:
|
||||
merge=json.load(ah)
|
||||
base=base+merge
|
||||
return base
|
||||
|
||||
def replaceTexts(data,replacements):
|
||||
if replacements is None:
|
||||
return data
|
||||
if isinstance(data,str):
|
||||
for k,v in replacements.items():
|
||||
data=data.replace("$"+k,str(v))
|
||||
return data
|
||||
if isinstance(data,list):
|
||||
rt=[]
|
||||
for e in data:
|
||||
rt.append(replaceTexts(e,replacements))
|
||||
return rt
|
||||
if isinstance(data,dict):
|
||||
rt={}
|
||||
for k,v in data.items():
|
||||
rt[replaceTexts(k,replacements)]=replaceTexts(v,replacements)
|
||||
return rt
|
||||
return data
|
||||
def expandConfig(config):
|
||||
rt=[]
|
||||
for item in config:
|
||||
type=item.get('type')
|
||||
if type != 'array':
|
||||
rt.append(item)
|
||||
continue
|
||||
replacements=item.get('replace')
|
||||
children=item.get('children')
|
||||
name=item.get('name')
|
||||
if name is None:
|
||||
name="#unknown#"
|
||||
if not isinstance(replacements,list):
|
||||
raise Exception("missing replacements at array %s"%name)
|
||||
for replace in replacements:
|
||||
if children is not None:
|
||||
for c in children:
|
||||
rt.append(replaceTexts(c,replace))
|
||||
return rt
|
||||
|
||||
def generateMergedConfig(inFile,outFile,addDirs=[]):
|
||||
if not os.path.exists(inFile):
|
||||
raise Exception("unable to read cfg file %s"%inFile)
|
||||
data=""
|
||||
with open(inFile,'rb') as ch:
|
||||
config=json.load(ch)
|
||||
config=mergeConfig(config,addDirs)
|
||||
config=expandConfig(config)
|
||||
data=json.dumps(config,indent=2)
|
||||
writeFileIfChanged(outFile,data)
|
||||
|
||||
def generateCfg(inFile,outFile,impl):
|
||||
if not os.path.exists(inFile):
|
||||
raise Exception("unable to read cfg file %s"%inFile)
|
||||
data=""
|
||||
with open(inFile,'rb') as ch:
|
||||
config=json.load(ch)
|
||||
data+="//generated from %s\n"%inFile
|
||||
l=len(config)
|
||||
idx=0
|
||||
if not impl:
|
||||
data+='#include "GwConfigItem.h"\n'
|
||||
data+='class GwConfigDefinitions{\n'
|
||||
data+=' public:\n'
|
||||
data+=' int getNumConfig() const{return %d;}\n'%(l)
|
||||
for item in config:
|
||||
n=item.get('name')
|
||||
if n is None:
|
||||
continue
|
||||
if len(n) > 15:
|
||||
raise Exception("%s: config names must be max 15 caracters"%n)
|
||||
data+=' static constexpr const char* %s="%s";\n'%(n,n)
|
||||
data+="};\n"
|
||||
else:
|
||||
data+='void GwConfigHandler::populateConfigs(GwConfigInterface **config){\n'
|
||||
for item in config:
|
||||
name=item.get('name')
|
||||
if name is None:
|
||||
continue
|
||||
data+=' configs[%d]='%(idx)
|
||||
idx+=1
|
||||
secret="false";
|
||||
if item.get('type') == 'password':
|
||||
secret="true"
|
||||
data+=" new GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret)
|
||||
data+='}\n'
|
||||
writeFileIfChanged(outFile,data)
|
||||
|
||||
def labelFilter(label):
|
||||
return re.sub("[^a-zA-Z0-9]","",re.sub("\([0-9]*\)","",label))
|
||||
def generateXdrMappings(fp,oh,inFile=''):
|
||||
jdoc=json.load(fp)
|
||||
oh.write("static GwXDRTypeMapping* typeMappings[]={\n")
|
||||
first=True
|
||||
for cat in jdoc:
|
||||
item=jdoc[cat]
|
||||
cid=item.get('id')
|
||||
if cid is None:
|
||||
continue
|
||||
tc=item.get('type')
|
||||
if tc is not None:
|
||||
if first:
|
||||
first=False
|
||||
else:
|
||||
oh.write(",\n")
|
||||
oh.write(" new GwXDRTypeMapping(%d,0,%d) /*%s*/"%(cid,tc,cat))
|
||||
fields=item.get('fields')
|
||||
if fields is None:
|
||||
continue
|
||||
idx=0
|
||||
for fe in fields:
|
||||
if not isinstance(fe,dict):
|
||||
continue
|
||||
tc=fe.get('t')
|
||||
id=fe.get('v')
|
||||
if id is None:
|
||||
id=idx
|
||||
idx+=1
|
||||
l=fe.get('l') or ''
|
||||
if tc is None or id is None:
|
||||
continue
|
||||
if first:
|
||||
first=False
|
||||
else:
|
||||
oh.write(",\n")
|
||||
oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,id,tc,cat,l))
|
||||
oh.write("\n")
|
||||
oh.write("};\n")
|
||||
for cat in jdoc:
|
||||
item=jdoc[cat]
|
||||
cid=item.get('id')
|
||||
if cid is None:
|
||||
continue
|
||||
selectors=item.get('selector')
|
||||
if selectors is not None:
|
||||
for selector in selectors:
|
||||
label=selector.get('l')
|
||||
value=selector.get('v')
|
||||
if label is not None and value is not None:
|
||||
label=labelFilter(label)
|
||||
define=("GWXDRSEL_%s_%s"%(cat,label)).upper()
|
||||
oh.write(" #define %s %s\n"%(define,value))
|
||||
fields=item.get('fields')
|
||||
if fields is not None:
|
||||
idx=0
|
||||
for field in fields:
|
||||
v=field.get('v')
|
||||
if v is None:
|
||||
v=idx
|
||||
else:
|
||||
v=int(v)
|
||||
label=field.get('l')
|
||||
if v is not None and label is not None:
|
||||
define=("GWXDRFIELD_%s_%s"%(cat,labelFilter(label))).upper();
|
||||
oh.write(" #define %s %s\n"%(define,str(v)))
|
||||
idx+=1
|
||||
|
||||
class Grove:
|
||||
def __init__(self,name) -> None:
|
||||
self.name=name
|
||||
def _ss(self,z=False):
|
||||
if z:
|
||||
return self.name
|
||||
return self.name if self.name is not 'Z' else ''
|
||||
def _suffix(self):
|
||||
return '_'+self.name if self.name is not 'Z' else ''
|
||||
def replace(self,line):
|
||||
if line is None:
|
||||
return line
|
||||
return line.replace('$G$',self._ss()).replace('$Z$',self._ss(True)).replace('$GS$',self._suffix())
|
||||
def generateGroveDefs(inh,outh,inFile=''):
|
||||
GROVES=[Grove('Z'),Grove('A'),Grove('B'),Grove('C')]
|
||||
definition=[]
|
||||
started=False
|
||||
def writeConfig():
|
||||
for grove in GROVES:
|
||||
for cl in definition:
|
||||
outh.write(grove.replace(cl))
|
||||
|
||||
for line in inh:
|
||||
if re.match(" *#GROVE",line):
|
||||
started=True
|
||||
if len(definition) > 0:
|
||||
writeConfig()
|
||||
definition=[]
|
||||
continue
|
||||
if started:
|
||||
definition.append(line)
|
||||
if len(definition) > 0:
|
||||
writeConfig()
|
||||
|
||||
|
||||
|
||||
userTaskDirs=[]
|
||||
|
||||
def getUserTaskDirs():
|
||||
rt=[]
|
||||
taskdirs=glob.glob(os.path.join( basePath(),'lib','*task*'))
|
||||
for task in taskdirs:
|
||||
rt.append(task)
|
||||
return rt
|
||||
|
||||
def checkAndAdd(file,names,ilist):
|
||||
if not file.endswith('.h'):
|
||||
return
|
||||
match=False
|
||||
for cmp in names:
|
||||
#print("##check %s<->%s"%(f.lower(),cmp))
|
||||
if file.lower() == cmp:
|
||||
match=True
|
||||
if not match:
|
||||
return
|
||||
ilist.append(file)
|
||||
def genereateUserTasks(outfile):
|
||||
includes=[]
|
||||
for task in userTaskDirs:
|
||||
#print("##taskdir=%s"%task)
|
||||
base=os.path.basename(task)
|
||||
includeNames=[base.lower()+".h",'gw'+base.lower()+'.h']
|
||||
for f in os.listdir(task):
|
||||
checkAndAdd(f,includeNames,includes)
|
||||
includeData=""
|
||||
for i in includes:
|
||||
print("#task include %s"%i)
|
||||
includeData+="#include <%s>\n"%i
|
||||
writeFileIfChanged(outfile,includeData)
|
||||
|
||||
def generateEmbedded(elist,outFile):
|
||||
content=""
|
||||
for entry in elist:
|
||||
content+="EMBED_GZ_FILE(\"%s\",%s,\"%s\");\n"%entry
|
||||
writeFileIfChanged(outFile,content)
|
||||
|
||||
def getContentType(fn):
|
||||
if (fn.endswith('.gz')):
|
||||
fn=fn[0:-3]
|
||||
if (fn.endswith('html')):
|
||||
return "text/html"
|
||||
if (fn.endswith('json')):
|
||||
return "application/json"
|
||||
if (fn.endswith('js')):
|
||||
return "text/javascript"
|
||||
if (fn.endswith('css')):
|
||||
return "text/css"
|
||||
return "application/octet-stream"
|
||||
|
||||
|
||||
def getLibs():
|
||||
base=os.path.join(basePath(),"lib")
|
||||
rt=[]
|
||||
for sd in os.listdir(base):
|
||||
if sd == '..':
|
||||
continue
|
||||
if sd == '.':
|
||||
continue
|
||||
fn=os.path.join(base,sd)
|
||||
if os.path.isdir(fn):
|
||||
rt.append(sd)
|
||||
EXTRAS=['generated']
|
||||
for e in EXTRAS:
|
||||
if not e in rt:
|
||||
rt.append(e)
|
||||
return rt
|
||||
|
||||
|
||||
|
||||
def joinFiles(target,pattern,dirlist):
|
||||
flist=[]
|
||||
for dir in dirlist:
|
||||
fn=os.path.join(dir,pattern)
|
||||
if os.path.exists(fn):
|
||||
flist.append(fn)
|
||||
current=False
|
||||
if os.path.exists(target):
|
||||
current=True
|
||||
for f in flist:
|
||||
if not isCurrent(f,target):
|
||||
current=False
|
||||
break
|
||||
if current:
|
||||
print("%s is up to date"%target)
|
||||
return
|
||||
print("creating %s"%target)
|
||||
with gzip.open(target,"wb") as oh:
|
||||
for fn in flist:
|
||||
print("adding %s to %s"%(fn,target))
|
||||
with open(fn,"rb") as rh:
|
||||
shutil.copyfileobj(rh,oh)
|
||||
|
||||
|
||||
OWNLIBS=getLibs()+["FS","WiFi"]
|
||||
GLOBAL_INCLUDES=[]
|
||||
|
||||
def handleDeps(env):
|
||||
#overwrite the GetProjectConfig
|
||||
#to inject all our libs
|
||||
oldGetProjectConfig=env.GetProjectConfig
|
||||
def GetProjectConfigX(env):
|
||||
rt=oldGetProjectConfig()
|
||||
cenv="env:"+env['PIOENV']
|
||||
libs=[]
|
||||
for section,options in rt.as_tuple():
|
||||
if section == cenv:
|
||||
for key,values in options:
|
||||
if key == 'lib_deps':
|
||||
libs=values
|
||||
|
||||
mustUpdate=False
|
||||
for lib in OWNLIBS:
|
||||
if not lib in libs:
|
||||
libs.append(lib)
|
||||
mustUpdate=True
|
||||
if mustUpdate:
|
||||
update=[(cenv,[('lib_deps',libs)])]
|
||||
rt.update(update)
|
||||
return rt
|
||||
env.AddMethod(GetProjectConfigX,"GetProjectConfig")
|
||||
#store the list of all includes after we resolved
|
||||
#the dependencies for our main project
|
||||
#we will use them for all compilations afterwards
|
||||
oldLibBuilder=env.ConfigureProjectLibBuilder
|
||||
def ConfigureProjectLibBuilderX(env):
|
||||
global GLOBAL_INCLUDES
|
||||
project=oldLibBuilder()
|
||||
#print("##ConfigureProjectLibBuilderX")
|
||||
#pprint.pprint(project)
|
||||
if project.depbuilders:
|
||||
#print("##depbuilders %s"%",".join(map(lambda x: x.path,project.depbuilders)))
|
||||
for db in project.depbuilders:
|
||||
idirs=db.get_include_dirs()
|
||||
for id in idirs:
|
||||
if not id in GLOBAL_INCLUDES:
|
||||
GLOBAL_INCLUDES.append(id)
|
||||
return project
|
||||
env.AddMethod(ConfigureProjectLibBuilderX,"ConfigureProjectLibBuilder")
|
||||
def injectIncludes(env,node):
|
||||
return env.Object(
|
||||
node,
|
||||
CPPPATH=env["CPPPATH"]+GLOBAL_INCLUDES
|
||||
)
|
||||
env.AddBuildMiddleware(injectIncludes)
|
||||
|
||||
|
||||
def prebuild(env):
|
||||
global userTaskDirs
|
||||
print("#prebuild running")
|
||||
if not checkDir():
|
||||
sys.exit(1)
|
||||
ldf_mode=env.GetProjectOption("lib_ldf_mode")
|
||||
if ldf_mode == 'off':
|
||||
print("##ldf off - own dependency handling")
|
||||
handleDeps(env)
|
||||
userTaskDirs=getUserTaskDirs()
|
||||
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
|
||||
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
|
||||
compressFile(mergedConfig,mergedConfig+".gz")
|
||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False)
|
||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True)
|
||||
joinFiles(os.path.join(outPath(),INDEXJS+".gz"),INDEXJS,["web"]+userTaskDirs)
|
||||
joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),INDEXCSS,["web"]+userTaskDirs)
|
||||
embedded=getEmbeddedFiles(env)
|
||||
filedefs=[]
|
||||
for ef in embedded:
|
||||
print("#checking embedded file %s"%ef)
|
||||
(dn,fn)=os.path.split(ef)
|
||||
pureName=fn
|
||||
if pureName.endswith('.gz'):
|
||||
pureName=pureName[0:-3]
|
||||
ct=getContentType(pureName)
|
||||
usname=ef.replace('/','_').replace('.','_')
|
||||
filedefs.append((pureName,usname,ct))
|
||||
inFile=os.path.join(basePath(),"web",pureName)
|
||||
if os.path.exists(inFile):
|
||||
compressFile(inFile,ef)
|
||||
else:
|
||||
print("#WARNING: infile %s for %s not found"%(inFile,ef))
|
||||
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
|
||||
genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
|
||||
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
|
||||
generateFile(os.path.join(basePath(),GROVE_CONFIG_IN),os.path.join(outPath(),GROVE_CONFIG),generateGroveDefs,inMode='r')
|
||||
version="dev"+datetime.now().strftime("%Y%m%d")
|
||||
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
|
||||
|
||||
def cleangenerated(source, target, env):
|
||||
od=outPath()
|
||||
if os.path.isdir(od):
|
||||
print("#cleaning up %s"%od)
|
||||
for f in os.listdir(od):
|
||||
if f == "." or f == "..":
|
||||
continue
|
||||
fn=os.path.join(od,f)
|
||||
os.unlink(f)
|
||||
|
||||
|
||||
print("#prescript...")
|
||||
prebuild(env)
|
||||
board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper()
|
||||
print("Board=#%s#"%board)
|
||||
print("BuildFlags=%s"%(" ".join(env["BUILD_FLAGS"])))
|
||||
env.Append(
|
||||
LINKFLAGS=[ "-u", "custom_app_desc" ],
|
||||
CPPDEFINES=[(board,"1")]
|
||||
)
|
||||
#script does not run on clean yet - maybe in the future
|
||||
env.AddPostAction("clean",cleangenerated)
|
||||
@@ -2,6 +2,9 @@
|
||||
#define _GWWIFI_H
|
||||
#include <WiFi.h>
|
||||
#include <GWConfig.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
class GwWifi{
|
||||
private:
|
||||
const GwConfigHandler *config;
|
||||
@@ -16,13 +19,19 @@ class GwWifi{
|
||||
bool apActive=false;
|
||||
bool fixedApPass=true;
|
||||
bool clientIsConnected=false;
|
||||
SemaphoreHandle_t wifiMutex=nullptr;
|
||||
static const TickType_t WIFI_MUTEX_TIMEOUT=pdMS_TO_TICKS(1000);
|
||||
bool acquireMutex();
|
||||
void releaseMutex();
|
||||
public:
|
||||
const char *AP_password = "esp32nmea2k";
|
||||
GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass=true);
|
||||
~GwWifi();
|
||||
void setup();
|
||||
void loop();
|
||||
bool clientConnected();
|
||||
bool connectClient();
|
||||
bool connectClient(); // Blocking version
|
||||
bool connectClientAsync(); // Non-blocking version for other tasks
|
||||
String apIP();
|
||||
bool isApActive(){return apActive;}
|
||||
bool isClientActive(){return wifiClient->asBoolean();}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <esp_wifi.h>
|
||||
#include "GWWifi.h"
|
||||
|
||||
|
||||
GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
|
||||
this->config=config;
|
||||
this->logger=log;
|
||||
@@ -9,6 +8,28 @@ GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
|
||||
wifiSSID=config->getConfigItem(config->wifiSSID,true);
|
||||
wifiPass=config->getConfigItem(config->wifiPass,true);
|
||||
this->fixedApPass=fixedApPass;
|
||||
wifiMutex=xSemaphoreCreateMutex();
|
||||
if (wifiMutex==nullptr){
|
||||
LOG_DEBUG(GwLog::ERROR,"GwWifi: unable to create mutex");
|
||||
}
|
||||
}
|
||||
|
||||
GwWifi::~GwWifi(){
|
||||
if (wifiMutex!=nullptr){
|
||||
vSemaphoreDelete(wifiMutex);
|
||||
wifiMutex=nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool GwWifi::acquireMutex(){
|
||||
if (wifiMutex==nullptr) return false;
|
||||
return xSemaphoreTake(wifiMutex,WIFI_MUTEX_TIMEOUT)==pdTRUE;
|
||||
}
|
||||
|
||||
void GwWifi::releaseMutex(){
|
||||
if (wifiMutex!=nullptr){
|
||||
xSemaphoreGive(wifiMutex);
|
||||
}
|
||||
}
|
||||
void GwWifi::setup(){
|
||||
LOG_DEBUG(GwLog::LOG,"Wifi setup");
|
||||
@@ -85,8 +106,14 @@ bool GwWifi::connectInternal(){
|
||||
if (wifiClient->asBoolean()){
|
||||
clientIsConnected=false;
|
||||
LOG_DEBUG(GwLog::LOG,"creating wifiClient ssid=%s",wifiSSID->asString().c_str());
|
||||
// CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden
|
||||
if (!acquireMutex()){
|
||||
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in connectInternal");
|
||||
return false;
|
||||
}
|
||||
WiFi.setAutoReconnect(false); //#102
|
||||
wl_status_t rt=WiFi.begin(wifiSSID->asCString(),wifiPass->asCString());
|
||||
releaseMutex();
|
||||
LOG_DEBUG(GwLog::LOG,"wifiClient connect returns %d",(int)rt);
|
||||
lastConnectStart=millis();
|
||||
return true;
|
||||
@@ -104,9 +131,21 @@ void GwWifi::loop(){
|
||||
if (lastConnectStart > now || (lastConnectStart + RETRY_MILLIS) < now)
|
||||
{
|
||||
LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString());
|
||||
WiFi.disconnect();
|
||||
|
||||
// CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden
|
||||
if (acquireMutex()){
|
||||
WiFi.disconnect(true);
|
||||
delay(300);
|
||||
esp_wifi_stop();
|
||||
delay(100);
|
||||
esp_wifi_start();
|
||||
releaseMutex();
|
||||
connectInternal();
|
||||
}
|
||||
else{
|
||||
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop");
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
if (! clientIsConnected){
|
||||
@@ -126,11 +165,42 @@ void GwWifi::loop(){
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GwWifi::clientConnected(){
|
||||
return WiFi.status() == WL_CONNECTED;
|
||||
// CRITICAL SECTION: WiFi.status() muss geschützt werden
|
||||
if (!acquireMutex()){
|
||||
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in clientConnected");
|
||||
return false; // Conservative: nehme an, nicht verbunden
|
||||
}
|
||||
bool result = WiFi.status() == WL_CONNECTED;
|
||||
releaseMutex();
|
||||
return result;
|
||||
};
|
||||
|
||||
bool GwWifi::connectClient(){
|
||||
// CRITICAL SECTION: Disconnect und Connect müssen atomar sein
|
||||
if (!acquireMutex()){
|
||||
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in connectClient");
|
||||
return false;
|
||||
}
|
||||
WiFi.disconnect();
|
||||
releaseMutex();
|
||||
return connectInternal();
|
||||
}
|
||||
|
||||
bool GwWifi::connectClientAsync(){
|
||||
// Non-blocking version: Versuche Mutex zu nehmen, gib aber sofort auf
|
||||
// Ideal für Tasks, die nicht blockieren dürfen
|
||||
if (wifiMutex==nullptr){
|
||||
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex not initialized in connectClientAsync");
|
||||
return false;
|
||||
}
|
||||
if (xSemaphoreTake(wifiMutex, 0)!=pdTRUE){
|
||||
LOG_DEBUG(GwLog::LOG,"GwWifi: connectClientAsync skipped - WiFi busy");
|
||||
return false; // WiFi ist aktuell busy, versuche es später nochmal
|
||||
}
|
||||
WiFi.disconnect();
|
||||
xSemaphoreGive(wifiMutex);
|
||||
return connectInternal();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,30 @@ https://controllerstech.com/ws2812-leds-using-spi/
|
||||
|
||||
*/
|
||||
|
||||
String Color::toHex() {
|
||||
char hexColor[8];
|
||||
sprintf(hexColor, "#%02X%02X%02X", r, g, b);
|
||||
return String(hexColor);
|
||||
}
|
||||
|
||||
String Color::toName() {
|
||||
static std::map<int, String> const names = {
|
||||
{0xff0000, "Red"},
|
||||
{0x00ff00, "Green"},
|
||||
{0x0000ff, "Blue",},
|
||||
{0xff9900, "Orange"},
|
||||
{0xffff00, "Yellow"},
|
||||
{0x3366ff, "Aqua"},
|
||||
{0xff0066, "Violet"},
|
||||
{0xffffff, "White"}
|
||||
};
|
||||
int color = (r << 16) + (g << 8) + b;
|
||||
auto it = names.find(color);
|
||||
if (it == names.end()) {
|
||||
return toHex();
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
static uint8_t mulcolor(uint8_t f1, uint8_t f2){
|
||||
uint16_t rt=f1;
|
||||
|
||||
@@ -10,7 +10,7 @@ class Color{
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
Color():r(0),g(0),b(0){}
|
||||
Color(uint8_t cr, uint8_t cg,uint8_t cb):
|
||||
Color(uint8_t cr, uint8_t cg, uint8_t cb):
|
||||
b(cb),g(cg),r(cr){}
|
||||
Color(const Color &o):b(o.b),g(o.g),r(o.r){}
|
||||
bool equal(const Color &o) const{
|
||||
@@ -22,6 +22,8 @@ class Color{
|
||||
bool operator != (const Color &other) const{
|
||||
return ! equal(other);
|
||||
}
|
||||
String toHex();
|
||||
String toName();
|
||||
};
|
||||
|
||||
static Color COLOR_GREEN=Color(0,255,0);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include "NetworkClient.h"
|
||||
#include "GWWifi.h" // WiFi management (thread-safe)
|
||||
|
||||
extern GwWifi gwWifi; // Extern declaration of global WiFi instance
|
||||
|
||||
extern "C" {
|
||||
#include "puff.h"
|
||||
@@ -51,8 +54,13 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
|
||||
const size_t capacity = READLIMIT; // Read limit for data (can be adjusted in NetworkClient.h)
|
||||
uint8_t* buffer = (uint8_t*)malloc(capacity);
|
||||
|
||||
if (!gwWifi.clientConnected()) {
|
||||
if (DEBUGING) {Serial.println("No WiFi connection");}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!buffer) {
|
||||
if (DEBUG) {Serial.println("Malloc failed (buffer");}
|
||||
if (DEBUGING) {Serial.println("Malloc failed buffer");}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -106,7 +114,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
|
||||
len += read;
|
||||
lastData = millis();
|
||||
|
||||
if (DEBUG) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
|
||||
if (DEBUGING) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
|
||||
|
||||
if (len < 20) continue; // Not enough data for header
|
||||
|
||||
@@ -122,7 +130,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
|
||||
|
||||
int res = puff(test, &testLen, buffer + headerOffset, &srcLen);
|
||||
if (res == 0) {
|
||||
if (DEBUG) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
|
||||
if (DEBUGING) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
|
||||
outData = test;
|
||||
outLen = testLen;
|
||||
complete = true;
|
||||
@@ -167,7 +175,7 @@ bool NetworkClient::fetchAndDecompressJson(const String& url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DEBUG) {Serial.println("JSON OK!");}
|
||||
if (DEBUGING) {Serial.println("JSON OK!");}
|
||||
_valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <WiFi.h>
|
||||
#include <HTTPClient.h>
|
||||
|
||||
#define DEBUG false // Debug flag for NetworkClient for more live information
|
||||
#define DEBUGING false // Debug flag for NetworkClient for more live information
|
||||
#define READLIMIT 200000 // HTTP read limit in byte for gzip content (can be adjusted)
|
||||
#define CONNECTIONTIMEOUT 3000 // Timeout in ms for HTTP connection
|
||||
#define TCPREADTIMEOUT 2000 // Timeout in ms for read HTTP client stack
|
||||
|
||||
@@ -923,7 +923,7 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor){
|
||||
|
||||
}
|
||||
|
||||
// Generator graphic with fill level
|
||||
// Generator graphic
|
||||
void generatorGraphic(uint x, uint y, int pcolor, int bcolor){
|
||||
// Show battery
|
||||
int xb = x; // X position
|
||||
@@ -940,6 +940,74 @@ void generatorGraphic(uint x, uint y, int pcolor, int bcolor){
|
||||
getdisplay().print("G");
|
||||
}
|
||||
|
||||
// Display rudder position as horizontal bargraph with configurable +/- range (degrees)
|
||||
void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg){
|
||||
const int w = 360;
|
||||
const int h = 20;
|
||||
const int t = 3; // Line thickness
|
||||
const int halfw = w/2;
|
||||
const int halfh = h/2;
|
||||
// Calculate top-left of bar (cx,cy are center of 0°)
|
||||
int left = int(cx) - halfw;
|
||||
int top = int(cy) - halfh;
|
||||
|
||||
// clamp provided range to allowed bounds [10,45]
|
||||
if (rangeDeg < 10) rangeDeg = 10;
|
||||
if (rangeDeg > 45) rangeDeg = 45;
|
||||
|
||||
// Pixels per degree for +/-rangeDeg -> total span = 2*rangeDeg
|
||||
const float pxPerDeg = float(w) / (2.0f * float(rangeDeg));
|
||||
|
||||
// Draw outer border (thickness t)
|
||||
for (int i = 0; i < t; i++) {
|
||||
getdisplay().drawRect(left + i, top + i, w - 2 * i, h - 2 * i, fg);
|
||||
}
|
||||
|
||||
// Fill inner area with background
|
||||
getdisplay().fillRect(left + t, top + t, w - 2 * t, h - 2 * t, bg);
|
||||
|
||||
// Draw center line
|
||||
getdisplay().drawRect(cx - 1, top + 1, 3 , h - 2, fg);
|
||||
|
||||
// Clamp rudder position to -rangeDeg..rangeDeg
|
||||
if (rudderPosition > (int)rangeDeg) rudderPosition = (int)rangeDeg;
|
||||
if (rudderPosition < -((int)rangeDeg)) rudderPosition = -((int)rangeDeg);
|
||||
|
||||
// Compute fill width in pixels
|
||||
int fillPx = int(round(rudderPosition * pxPerDeg)); // positive -> right
|
||||
|
||||
// Fill area from center to position (if non-zero)
|
||||
int centerx = cx;
|
||||
int innerTop = top + t;
|
||||
int innerH = h - 2 * t;
|
||||
if (fillPx > 0) {
|
||||
// Right side
|
||||
getdisplay().fillRect(centerx, innerTop, fillPx, innerH, fg);
|
||||
} else if (fillPx < 0) {
|
||||
// Left side
|
||||
getdisplay().fillRect(centerx + fillPx, innerTop, -fillPx, innerH, fg);
|
||||
}
|
||||
|
||||
|
||||
// Draw tick marks every 5° and labels outside the bar
|
||||
getdisplay().setTextColor(fg);
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
for (int angle = -((int)rangeDeg); angle <= (int)rangeDeg; angle += 5) {
|
||||
int xpos = int(round(centerx + angle * pxPerDeg));
|
||||
// Vertical tick inside bar
|
||||
getdisplay().drawLine(xpos, top, xpos, top + h + 2, fg);
|
||||
// Label outside: below the bar
|
||||
String lbl = String(angle);
|
||||
int16_t bx, by;
|
||||
uint16_t bw, bh;
|
||||
getdisplay().getTextBounds(lbl, 0, 0, &bx, &by, &bw, &bh);
|
||||
int16_t tx = xpos - bw/2;
|
||||
int16_t ty = top + h + bh + 5; // A little spacing
|
||||
getdisplay().setCursor(tx, ty);
|
||||
getdisplay().print(lbl);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle HTTP image request
|
||||
// http://192.168.15.1/api/user/OBP60Task/screenshot
|
||||
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request) {
|
||||
|
||||
@@ -128,6 +128,10 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor); // S
|
||||
void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic
|
||||
void startLedTask(GwApi *api);
|
||||
|
||||
// Display rudder position as horizontal bargraph with configurable +/- range (degrees)
|
||||
// 'rangeDeg' is unsigned and will be clamped to [10,45]
|
||||
void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg);
|
||||
|
||||
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request);
|
||||
|
||||
// Icons
|
||||
|
||||
@@ -835,7 +835,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
result.cvalue = dplace;
|
||||
}
|
||||
//########################################################
|
||||
else if (value->getFormat() == "formatXdr:A:D"){
|
||||
else if ((value->getFormat() == "formatXdr:A:D") || ((value->getFormat() == "formatXdr:A:rd"))){
|
||||
double angle = 0;
|
||||
if (usesimudata == false) {
|
||||
angle = value->value;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// General hardware definitions
|
||||
// CAN and RS485 bus pin definitions see obp60task.h
|
||||
|
||||
#ifdef HARDWARE_V21
|
||||
#if defined HARDWARE_V20 || HARDWARE_V21
|
||||
// Direction pin for RS485 NMEA0183
|
||||
#define OBP_DIRECTION_PIN 18
|
||||
// I2C
|
||||
|
||||
@@ -58,7 +58,7 @@ void initKeys(CommonData &commonData) {
|
||||
commonData.keydata[5].h = height;
|
||||
}
|
||||
|
||||
#ifdef HARDWARE_V21
|
||||
#if defined HARDWARE_V20 || HARDWARE_V21
|
||||
// Keypad functions for original OBP60 hardware
|
||||
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
||||
double dataValue = 0;
|
||||
std::string format = "";
|
||||
|
||||
// we test this earlier, but for safety reason ...
|
||||
// we test this earlier, but for safety reasons ...
|
||||
if (calibrationMap.find(instance) == calibrationMap.end()) {
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
||||
return false;
|
||||
@@ -151,7 +151,7 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
||||
slope = calibrationMap[instance].slope;
|
||||
dataValue = boatDataValue->value;
|
||||
format = boatDataValue->getFormat().c_str();
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: value: %f, format: %s", instance.c_str(), dataValue, format.c_str());
|
||||
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: value: %f, format: %s", instance.c_str(), dataValue, format.c_str());
|
||||
|
||||
if (format == "formatWind") { // instance is of type angle
|
||||
dataValue = (dataValue * slope) + offset;
|
||||
@@ -174,7 +174,7 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
||||
calibrationMap[instance].value = dataValue; // store the calibrated value in the list
|
||||
calibrationMap[instance].isCalibrated = true;
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: Offset: %f, Slope: %f, Result: %f", instance.c_str(), offset, slope, calibrationMap[instance].value);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: Offset: %f, Slope: %f, Result: %f", instance.c_str(), offset, slope, calibrationMap[instance].value);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ bool CalibrationData::smoothInstance(GwApi::BoatValue* boatDataValue)
|
||||
|
||||
// we test this earlier, but for safety reason ...
|
||||
if (calibrationMap.find(instance) == calibrationMap.end()) {
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
||||
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ bool CalibrationData::smoothInstance(GwApi::BoatValue* boatDataValue)
|
||||
calibrationMap[instance].value = dataValue; // store the smoothed value in the list
|
||||
calibrationMap[instance].isCalibrated = true;
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: smooth: %f, oldValue: %f, result: %f", instance.c_str(), smoothFactor, oldValue, calibrationMap[instance].value);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: smooth: %f, oldValue: %f, result: %f", instance.c_str(), smoothFactor, oldValue, calibrationMap[instance].value);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -241,7 +241,7 @@ void HstryBuf::add(double value)
|
||||
{
|
||||
if (value >= hstryMin && value <= hstryMax) {
|
||||
hstryBuf.add(value);
|
||||
LOG_DEBUG(GwLog::DEBUG, "HstryBuf::add: name: %s, value: %.3f", hstryBuf.getName(), value);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "HstryBuf::add: name: %s, value: %.3f", hstryBuf.getName(), value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ void WindUtils::calcTwdSA(const double* AWA, const double* AWS,
|
||||
double stw = -*STW;
|
||||
addPolar(AWD, AWS, CTW, &stw, TWD, TWS);
|
||||
|
||||
// Normalize TWD and TWA to 0-360°/2PI
|
||||
// Normalize TWD to [0..360°] (2PI) and TWA to [-180..180] (PI)
|
||||
*TWD = to2PI(*TWD);
|
||||
*TWA = toPI(*TWD - *HDT);
|
||||
}
|
||||
@@ -487,8 +487,8 @@ bool WindUtils::addWinds()
|
||||
double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
|
||||
double hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX;
|
||||
double varVal = varBVal->valid ? varBVal->value : DBL_MAX;
|
||||
LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852,
|
||||
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);
|
||||
//LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852,
|
||||
// cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG);
|
||||
|
||||
// Check if TWD can be calculated from TWA and HDT/HDM
|
||||
if (twaBVal->valid) {
|
||||
@@ -528,8 +528,8 @@ bool WindUtils::addWinds()
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG,
|
||||
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG,
|
||||
// twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG);
|
||||
|
||||
return twCalculated;
|
||||
}
|
||||
|
||||
@@ -49,8 +49,10 @@ void sensorTask(void *param){
|
||||
|
||||
// Init sensor stuff
|
||||
bool oneWire_ready = false; // 1Wire initialized and ready to use
|
||||
bool iRTC_ready = false; // Software RTC initialized and ready to use
|
||||
bool RTC_ready = false; // DS1388 initialized and ready to use
|
||||
bool GPS_ready = false; // GPS initialized and ready to use
|
||||
bool N2K_GPS_ready = false; // GPS time on N2K bus
|
||||
bool BME280_ready = false; // BME280 initialized and ready to use
|
||||
bool BMP280_ready = false; // BMP280 initialized and ready to use
|
||||
bool BMP180_ready = false; // BMP180 initialized and ready to use
|
||||
@@ -369,7 +371,7 @@ void sensorTask(void *param){
|
||||
GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP);
|
||||
GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop};
|
||||
|
||||
// Internal RTC with NTP init
|
||||
// Internal iRTC with NTP init
|
||||
ESP32Time rtc(0);
|
||||
if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") {
|
||||
GwApi::Status status;
|
||||
@@ -382,6 +384,7 @@ void sensorTask(void *param){
|
||||
if (getLocalTime(&timeinfo)) {
|
||||
api->getLogger()->logDebug(GwLog::LOG,"NTP time: %04d-%02d-%02d %02d:%02d:%02d UTC", timeinfo.tm_year+1900, timeinfo.tm_mon+1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
|
||||
rtc.setTimeStruct(timeinfo);
|
||||
iRTC_ready = true;
|
||||
sensors.rtcValid = true;
|
||||
} else {
|
||||
api->getLogger()->logDebug(GwLog::LOG,"NTP time fetch failed!");
|
||||
@@ -400,7 +403,7 @@ void sensorTask(void *param){
|
||||
if (millis() > starttime0 + 100)
|
||||
{
|
||||
starttime0 = millis();
|
||||
// Send NMEA0183 GPS data on several bus systems all 100ms
|
||||
// Send NMEA0183 GPS data on several bus systems (N2K an 0183) all 100ms
|
||||
if (GPS_ready == true && hdop->value <= hdopAccuracy)
|
||||
{
|
||||
SNMEA0183Msg NMEA0183Msg;
|
||||
@@ -412,9 +415,55 @@ void sensorTask(void *param){
|
||||
|
||||
}
|
||||
|
||||
// If RTC DS1388 ready, then copy GPS data to RTC all 5min
|
||||
if(millis() > starttime11 + 5*60*1000){
|
||||
/*
|
||||
Time set logic for RTC and N2K
|
||||
###############################
|
||||
|
||||
iRTC = Software RTC updatetd with NTP via internet
|
||||
RTC = RTC chip on PCB
|
||||
GPS = GPS Receiver on PCB
|
||||
N2K = GPS time on N2K od 183 bus
|
||||
0 = device not ready
|
||||
1 = device ready
|
||||
X = independend
|
||||
() = source for set time N2K
|
||||
-> = set RTC via iRTC
|
||||
<- = set RTC via GPS
|
||||
|
||||
iRTC RTC GPS N2K
|
||||
0 0 0 (1)
|
||||
0 0 (1) X
|
||||
0 (1) 0 X
|
||||
0 1 <-(1) X
|
||||
(1) 0 0 X
|
||||
1 0 (1) X
|
||||
1 ->(1) 0 X
|
||||
1 1 <-(1) X
|
||||
|
||||
*/
|
||||
|
||||
// If RTC DS1388 ready, then copy iRTC and GPS data to RTC all 1 min
|
||||
if(millis() > starttime11 + 1*60*1000){
|
||||
starttime11 = millis();
|
||||
// Set RTC chip via iRTC (NTP)
|
||||
if(iRTC_ready == true && RTC_ready == true && GPS_ready == false){
|
||||
GwApi::Status status;
|
||||
api->getStatus(status);
|
||||
// Check WiFi connection
|
||||
if (status.wifiClientConnected) {
|
||||
sensors.rtcTime = rtc.getTimeStruct(); // Get time from software RTC (iRTC)
|
||||
DateTime now = DateTime(
|
||||
sensors.rtcTime.tm_year + 1900,
|
||||
sensors.rtcTime.tm_mon + 1,
|
||||
sensors.rtcTime.tm_mday,
|
||||
sensors.rtcTime.tm_hour,
|
||||
sensors.rtcTime.tm_min,
|
||||
sensors.rtcTime.tm_sec
|
||||
);
|
||||
ds1388.adjust(now);
|
||||
}
|
||||
}
|
||||
// Set RTC chip via internal GPS
|
||||
if(rtcOn == "DS1388" && RTC_ready == true && GPS_ready == true){
|
||||
api->getBoatDataValues(3,valueList);
|
||||
if(gpsdays->valid && gpsseconds->valid && hdop->valid){
|
||||
@@ -422,40 +471,33 @@ void sensorTask(void *param){
|
||||
// sample input: date = "Dec 26 2009", time = "12:34:56"
|
||||
// ds1388.adjust(DateTime("Dec 26 2009", "12:34:56"));
|
||||
DateTime adjusttime(ts);
|
||||
api->getLogger()->logDebug(GwLog::LOG,"Adjust RTC time: %04d/%02d/%02d %02d:%02d:%02d",adjusttime.year(), adjusttime.month(), adjusttime.day(), adjusttime.hour(), adjusttime.minute(), adjusttime.second());
|
||||
api->getLogger()->logDebug(GwLog::LOG,"Adjust RTC time via internal GPS: %04d/%02d/%02d %02d:%02d:%02d",adjusttime.year(), adjusttime.month(), adjusttime.day(), adjusttime.hour(), adjusttime.minute(), adjusttime.second());
|
||||
// Adjust RTC time as unix time value
|
||||
ds1388.adjust(adjusttime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send 1Wire data for all temperature sensors all 2s
|
||||
if(millis() > starttime13 + 2000 && String(oneWireOn) == "DS18B20" && oneWire_ready == true){
|
||||
starttime13 = millis();
|
||||
float tempC;
|
||||
ds18b20.requestTemperatures(); // Collect all temperature values (max.8)
|
||||
for(int i=0;i<numberOfDevices; i++){
|
||||
// Send only one 1Wire data per loop step (time reduction)
|
||||
if(i == loopCounter % numberOfDevices){
|
||||
if(ds18b20.getAddress(tempDeviceAddress, i)){
|
||||
// Read temperature value in Celsius
|
||||
tempC = ds18b20.getTempC(tempDeviceAddress);
|
||||
// Set RTC chip via N2K or 183 in case the internal GPS is off (only one time)
|
||||
if(N2K_GPS_ready == false && RTC_ready == true && GPS_ready == false){
|
||||
api->getBoatDataValues(3,valueList);
|
||||
if(gpsdays->valid && gpsseconds->valid && hdop->valid){
|
||||
long ts = tNMEA0183Msg::daysToTime_t(gpsdays->value - (30*365+7))+floor(gpsseconds->value); // Adjusted to reference year 2000 (-30 years and 7 days for switch years)
|
||||
// sample input: date = "Dec 26 2009", time = "12:34:56"
|
||||
// ds1388.adjust(DateTime("Dec 26 2009", "12:34:56"));
|
||||
DateTime adjusttime(ts);
|
||||
api->getLogger()->logDebug(GwLog::LOG,"Adjust RTC time via N2K/183: %04d/%02d/%02d %02d:%02d:%02d",adjusttime.year(), adjusttime.month(), adjusttime.day(), adjusttime.hour(), adjusttime.minute(), adjusttime.second());
|
||||
// Adjust RTC time as unix time value
|
||||
ds1388.adjust(adjusttime);
|
||||
// N2K GPS time ready
|
||||
N2K_GPS_ready = true;
|
||||
}
|
||||
// Send to NMEA200 bus for each sensor with instance number
|
||||
if(!isnan(tempC)){
|
||||
sensors.onewireTemp[i] = tempC; // Save values in SensorData
|
||||
api->getLogger()->logDebug(GwLog::DEBUG,"DS18B20-%1d Temp: %.1f",i,tempC);
|
||||
SetN2kPGN130316(N2kMsg, 0, i, N2kts_OutsideTemperature, CToKelvin(tempC), N2kDoubleNA);
|
||||
api->sendN2kMessage(N2kMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
loopCounter++;
|
||||
}
|
||||
|
||||
// Get current RTC date and time all 500ms
|
||||
// Send RTC date and time to N2K all 500ms
|
||||
if (millis() > starttime12 + 500) {
|
||||
starttime12 = millis();
|
||||
// Send date and time from RTC chip if GPS not ready
|
||||
if (rtcOn == "DS1388" && RTC_ready) {
|
||||
DateTime dt = ds1388.now();
|
||||
sensors.rtcTime.tm_year = dt.year() - 1900; // Save values in SensorData
|
||||
@@ -481,21 +523,63 @@ void sensorTask(void *param){
|
||||
}
|
||||
// N2K sysTime is double in n2klib
|
||||
double sysTime = (dt.hour() * 3600) + (dt.minute() * 60) + dt.second();
|
||||
// WHY? isnan should always fail here
|
||||
//if(!isnan(daysAt1970) && !isnan(sysTime)){
|
||||
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
|
||||
if(!isnan(daysAt1970) && !isnan(sysTime)){
|
||||
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon+1, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
|
||||
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
|
||||
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
|
||||
api->sendN2kMessage(N2kMsg);
|
||||
// }
|
||||
}
|
||||
} else if (sensors.rtcValid) {
|
||||
// use internal rtc feature
|
||||
}
|
||||
}
|
||||
// Send date and time from software RTC (iRTC)
|
||||
if (iRTC_ready == true && RTC_ready == false && GPS_ready == false) {
|
||||
sensors.rtcTime = rtc.getTimeStruct();
|
||||
|
||||
const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334};
|
||||
int year = sensors.rtcTime.tm_year + 1900;
|
||||
int month = sensors.rtcTime.tm_mon;
|
||||
int day = sensors.rtcTime.tm_mday;
|
||||
uint16_t switchYear = ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 + ((year - 1) - 1600) / 400;
|
||||
long daysAt1970 = (year - 1970) * 365L + switchYear + daysOfYear[month] + day - 1;
|
||||
|
||||
// Leap day add if date is after Feb (i.e. month >= March)
|
||||
if (month >= 2 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) {
|
||||
daysAt1970 += 1;
|
||||
}
|
||||
double sysTime = sensors.rtcTime.tm_hour * 3600.0 + sensors.rtcTime.tm_min * 60.0 + sensors.rtcTime.tm_sec;
|
||||
//api->getLogger()->logDebug(GwLog::LOG, "iRTC time: %04d/%02d/%02d %02d:%02d:%02d", year, month + 1, day, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
|
||||
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
|
||||
SetN2kPGN126992(N2kMsg, 0, daysAt1970, sysTime, N2ktimes_LocalCrystalClock);
|
||||
api->sendN2kMessage(N2kMsg);
|
||||
}
|
||||
|
||||
// Send supply voltage value all 1s
|
||||
}
|
||||
|
||||
// Send 1Wire data for all temperature sensors to N2K all 2s
|
||||
if(millis() > starttime13 + 2000 && String(oneWireOn) == "DS18B20" && oneWire_ready == true){
|
||||
starttime13 = millis();
|
||||
float tempC;
|
||||
ds18b20.requestTemperatures(); // Collect all temperature values (max.8)
|
||||
for(int i=0;i<numberOfDevices; i++){
|
||||
// Send only one 1Wire data per loop step (time reduction)
|
||||
if(i == loopCounter % numberOfDevices){
|
||||
if(ds18b20.getAddress(tempDeviceAddress, i)){
|
||||
// Read temperature value in Celsius
|
||||
tempC = ds18b20.getTempC(tempDeviceAddress);
|
||||
}
|
||||
// Send to NMEA200 bus for each sensor with instance number
|
||||
if(!isnan(tempC)){
|
||||
sensors.onewireTemp[i] = tempC; // Save values in SensorData
|
||||
api->getLogger()->logDebug(GwLog::DEBUG,"DS18B20-%1d Temp: %.1f",i,tempC);
|
||||
SetN2kPGN130316(N2kMsg, 0, i, N2kts_OutsideTemperature, CToKelvin(tempC), N2kDoubleNA);
|
||||
api->sendN2kMessage(N2kMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
loopCounter++;
|
||||
}
|
||||
|
||||
// Send supply voltage value to N2K all 1s
|
||||
if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){
|
||||
starttime5 = millis();
|
||||
float rawVoltage = 0; // Default value
|
||||
@@ -565,7 +649,7 @@ void sensorTask(void *param){
|
||||
#endif
|
||||
}
|
||||
|
||||
// Send data from environment sensor all 2s
|
||||
// Send data from environment sensor to N2K all 2s
|
||||
if(millis() > starttime6 + 2000){
|
||||
starttime6 = millis();
|
||||
unsigned char TempSource = 2; // Inside temperature
|
||||
@@ -630,7 +714,7 @@ void sensorTask(void *param){
|
||||
}
|
||||
}
|
||||
|
||||
// Send rotation angle all 500ms
|
||||
// Send rotation angle to N2K all 500ms
|
||||
if(millis() > starttime7 + 500){
|
||||
starttime7 = millis();
|
||||
double rotationAngle=0;
|
||||
@@ -678,7 +762,7 @@ void sensorTask(void *param){
|
||||
}
|
||||
}
|
||||
|
||||
// Send battery power value all 1s
|
||||
// Send battery power value to N2K all 1s
|
||||
if(millis() > starttime8 + 1000 && (String(powsensor1) == "INA219" || String(powsensor1) == "INA226")){
|
||||
starttime8 = millis();
|
||||
if(String(powsensor1) == "INA226" && INA226_1_ready == true){
|
||||
@@ -720,7 +804,7 @@ void sensorTask(void *param){
|
||||
}
|
||||
}
|
||||
|
||||
// Send solar power value all 1s
|
||||
// Send solar power value to N2K all 1s
|
||||
if(millis() > starttime9 + 1000 && (String(powsensor2) == "INA219" || String(powsensor2) == "INA226")){
|
||||
starttime9 = millis();
|
||||
if(String(powsensor2) == "INA226" && INA226_2_ready == true){
|
||||
@@ -750,7 +834,7 @@ void sensorTask(void *param){
|
||||
}
|
||||
}
|
||||
|
||||
// Send generator power value all 1s
|
||||
// Send generator power value to N2K all 1s
|
||||
if(millis() > starttime10 + 1000 && (String(powsensor3) == "INA219" || String(powsensor3) == "INA226")){
|
||||
starttime10 = millis();
|
||||
if(String(powsensor3) == "INA226" && INA226_3_ready == true){
|
||||
|
||||
@@ -161,8 +161,8 @@ bool Chart::setChartDimensions(const char direction, const int8_t size)
|
||||
break;
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(GwLog::ERROR, "obp60:setChartDimensions %s: direction: %c, size: %d, dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot{%d, %d}, top: %d, bottom: %d, hGap: %d, vGap: %d",
|
||||
dataBuf.getName(), direction, size, dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, top, bottom, hGap, vGap);
|
||||
//LOG_DEBUG(GwLog::DEBUG, "obp60:setChartDimensions %s: direction: %c, size: %d, dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot{%d, %d}, top: %d, bottom: %d, hGap: %d, vGap: %d",
|
||||
// dataBuf.getName(), direction, size, dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, top, bottom, hGap, vGap);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ void Chart::drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue
|
||||
// LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
|
||||
calcChrtBorders(chrtMin, chrtMid, chrtMax, chrtRng);
|
||||
chrtScale = double(valAxis) / chrtRng; // Chart scale: pixels per value step
|
||||
LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
|
||||
|
||||
// Do we have valid buffer data?
|
||||
if (dataBuf.getMax() == dbMAX_VAL) { // only <MAX_VAL> values in buffer -> no valid wind data available
|
||||
@@ -261,8 +261,8 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
|
||||
}
|
||||
recalcRngMid = false; // Reset flag for <rngMid> determination
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "calcChrtRange: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||
rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "calcChrtRange: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||
// rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,8 +287,8 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
|
||||
|
||||
rng = halfRng * 2.0;
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, tmpRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||
tmpRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, tmpRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||
// tmpRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||
|
||||
} else { // chart data is of any other type
|
||||
|
||||
@@ -320,8 +320,8 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
|
||||
rngMid = (rngMin + rngMax) / 2.0;
|
||||
rng = rngMax - rngMin;
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f",
|
||||
currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f",
|
||||
// currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +397,8 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
taskYIELD(); // we run for 50-150ms; be polite to other tasks with same priority
|
||||
}
|
||||
}
|
||||
|
||||
@@ -656,7 +658,7 @@ void Chart::prntHorizChartThreeValueAxisLabel(const GFXfont* font)
|
||||
|
||||
if (font == &Ubuntu_Bold10pt8b) {
|
||||
xOffset = 39;
|
||||
yOffset = 15;
|
||||
yOffset = 16;
|
||||
} else if (font == &Ubuntu_Bold12pt8b) {
|
||||
xOffset = 51;
|
||||
yOffset = 18;
|
||||
@@ -718,7 +720,7 @@ void Chart::prntHorizChartMultiValueAxisLabel(const GFXfont* font)
|
||||
axSlots = valAxis / static_cast<double>(VALAXIS_STEP); // number of axis labels (and we want to have a double calculation, no integer)
|
||||
axIntv = chrtRng / axSlots;
|
||||
axLabel = chrtMin + axIntv;
|
||||
LOG_DEBUG(GwLog::DEBUG, "Chart::printHorizMultiValueAxisLabel: chrtRng: %.2f, th-chrtRng: %.2f, axSlots: %.2f, axIntv: %.2f, axLabel: %.2f, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, this->chrtRng, axSlots, axIntv, axLabel, this->chrtMin, chrtMid, chrtMax);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "Chart::printHorizMultiValueAxisLabel: chrtRng: %.2f, th-chrtRng: %.2f, axSlots: %.2f, axIntv: %.2f, axLabel: %.2f, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, this->chrtRng, axSlots, axIntv, axLabel, this->chrtMin, chrtMid, chrtMax);
|
||||
|
||||
int loopStrt, loopEnd, loopStp;
|
||||
if (chrtDataFmt == SPEED || chrtDataFmt == TEMPERATURE || chrtDataFmt == OTHER) {
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
|
||||
// These constants have to match the declaration below in :
|
||||
// PageDescription registerPageAutopilot(
|
||||
// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page
|
||||
const int HowManyValues = 9;
|
||||
// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page
|
||||
|
||||
const int HowManyValues = 11;
|
||||
|
||||
const int AverageValues = 4;
|
||||
|
||||
@@ -19,10 +20,13 @@ const int ShowDBT = 5;
|
||||
const int ShowXTE = 6;
|
||||
const int ShowDTW = 7;
|
||||
const int ShowBTW = 8;
|
||||
const int ShowRPOS = 9;
|
||||
const int ShowROT = 10;
|
||||
|
||||
const int Compass_X0 = 200; // X center point of compass band
|
||||
const int Compass_Y0 = 220; // Y position of compass lines
|
||||
const int Compass_LineLength = 22; // Length of compass lines
|
||||
const int Compass_Y0 = 90; // Y position of compass lines
|
||||
//const int Compass_LineLength = 22; // Length of compass lines
|
||||
const int Compass_LineLength = 15; // Length of compass lines
|
||||
const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
|
||||
|
||||
class PageAutopilot : public Page
|
||||
@@ -38,8 +42,11 @@ class PageAutopilot : public Page
|
||||
|
||||
virtual void setupKeys(){
|
||||
Page::setupKeys();
|
||||
commonData->keydata[0].label = "CMP";
|
||||
commonData->keydata[1].label = "SRC";
|
||||
commonData->keydata[0].label = "-10";
|
||||
commonData->keydata[1].label = "-1";
|
||||
commonData->keydata[2].label = "Auto";
|
||||
commonData->keydata[3].label = "+1";
|
||||
commonData->keydata[4].label = "+10";
|
||||
}
|
||||
|
||||
virtual int handleKey(int key){
|
||||
@@ -69,8 +76,8 @@ class PageAutopilot : public Page
|
||||
GwLog *logger = commonData->logger;
|
||||
|
||||
// Old values for hold function
|
||||
static String OldDataText[HowManyValues] = {"", "", "","", "", "","", "", ""};
|
||||
static String OldDataUnits[HowManyValues] = {"", "", "","", "", "","", "", ""};
|
||||
static String OldDataText[HowManyValues] = {"", "", "", "", "", "","", "", "", "", ""};
|
||||
static String OldDataUnits[HowManyValues] = {"", "", "", "", "", "","", "", "", "", ""};
|
||||
|
||||
// Get config data
|
||||
String lengthformat = config->getString(config->lengthFormat);
|
||||
@@ -107,14 +114,12 @@ class PageAutopilot : public Page
|
||||
setFlashLED(false);
|
||||
}
|
||||
|
||||
if (bvalue == NULL) return PAGE_OK; // WTF why this statement?
|
||||
|
||||
//***********************************************************
|
||||
|
||||
// Set display in partial refresh mode
|
||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||
getdisplay().setTextColor(commonData->fgcolor);
|
||||
|
||||
/*
|
||||
// Horizontal line 2 pix top & bottom
|
||||
// Print data on top half
|
||||
getdisplay().fillRect(0, 130, 400, 2, commonData->fgcolor);
|
||||
@@ -138,7 +143,7 @@ class PageAutopilot : public Page
|
||||
OldDataText[WhichDataDisplay] = DataText[WhichDataDisplay]; // Save the old value
|
||||
OldDataUnits[WhichDataDisplay] = DataUnits[WhichDataDisplay]; // Save the old unit
|
||||
}
|
||||
|
||||
*/
|
||||
// Now draw compass band
|
||||
// Get the data
|
||||
double TheAngle = DataValue[WhichDataCompass];
|
||||
@@ -152,13 +157,13 @@ class PageAutopilot : public Page
|
||||
buffer[0]=0;
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
||||
getdisplay().setCursor(10, Compass_Y0-60);
|
||||
getdisplay().setCursor(10, Compass_Y0-40);
|
||||
getdisplay().print(DataName[WhichDataCompass]); // Page name
|
||||
|
||||
|
||||
// Draw compass base line and pointer
|
||||
getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor);
|
||||
getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor);
|
||||
//getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor);
|
||||
getdisplay().fillTriangle(Compass_X0,Compass_Y0-30,Compass_X0-10,Compass_Y0-60,Compass_X0+10,Compass_Y0-60,commonData->fgcolor);
|
||||
// Draw trendlines
|
||||
for ( int i = 1; i < abs(TheTrend) / 2; i++){
|
||||
int x1;
|
||||
@@ -238,6 +243,8 @@ class PageAutopilot : public Page
|
||||
// if ( x_test > 390)
|
||||
// x_test = 320;
|
||||
|
||||
displayRudderPosition(DataValue[ShowSOG], 20, 200, 160, commonData->fgcolor, commonData->bgcolor);
|
||||
|
||||
return PAGE_UPDATE;
|
||||
};
|
||||
|
||||
@@ -256,7 +263,7 @@ PageDescription registerPageAutopilot(
|
||||
"Autopilot", // Page name
|
||||
createPage, // Action
|
||||
0, // Number of bus values depends on selection in Web configuration
|
||||
{"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page
|
||||
{"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page
|
||||
true // Show display header on/off
|
||||
);
|
||||
|
||||
|
||||
@@ -4,91 +4,298 @@
|
||||
#include "OBP60Extensions.h"
|
||||
|
||||
/*
|
||||
* TODO mode: race timer: keys
|
||||
* - prepare: set countdown to 5min
|
||||
* reset: abort current countdown and start over with 5min preparation
|
||||
* - 5min: key press
|
||||
* - 4min: key press to sync
|
||||
* - 1min: buzzer signal
|
||||
* - start: buzzer signal for start
|
||||
* PageClock: Clock page with
|
||||
* - Analog mode (mode == 'A')
|
||||
* - Digital mode (mode == 'D')
|
||||
* - Countdown timer mode (mode == 'T')
|
||||
* - Keys in mode analog and digital clock:
|
||||
* K1: MODE (A/D/T)
|
||||
* K2: POS (select field: HH / MM / SS)
|
||||
* K3:
|
||||
* K4:
|
||||
* K5: TZ (Local/UTC)
|
||||
*
|
||||
* Regatta timer mode:
|
||||
* - Format HH:MM:SS (24h, leading zeros)
|
||||
* - Keys in timer mode:
|
||||
* K1: MODE (A/D/T)
|
||||
* K2: POS (select field: HH / MM / SS)
|
||||
* K3: + (increment selected field)
|
||||
* K4: - (decrement selected field)
|
||||
* K5: RUN (start/stop countdown)
|
||||
* - Selection marker: line under active field (width 2px, not wider than digits)
|
||||
* - Editing only possible when timer is not running
|
||||
* - When page is left, running timer continues in background using RTC time
|
||||
* (on re-entry, remaining time is recalculated from RTC)
|
||||
*/
|
||||
|
||||
class PageClock : public Page
|
||||
{
|
||||
bool simulation = false;
|
||||
int simtime;
|
||||
bool keylock = false;
|
||||
char source = 'R'; // time source (R)TC | (G)PS | (N)TP
|
||||
char mode = 'A'; // display mode (A)nalog | (D)igital | race (T)imer
|
||||
char tz = 'L'; // time zone (L)ocal | (U)TC
|
||||
double timezone = 0; // there are timezones with non int offsets, e.g. 5.5 or 5.75
|
||||
double homelat;
|
||||
double homelon;
|
||||
bool homevalid = false; // homelat and homelon are valid
|
||||
bool simulation = false;
|
||||
int simtime;
|
||||
bool keylock = false;
|
||||
#ifdef BOARD_OBP60S3
|
||||
char source = 'G'; // Time source (R)TC | (G)PS | (N)TP
|
||||
#endif
|
||||
#ifdef BOARD_OBP40S3
|
||||
char source = 'R'; // time source (R)TC | (G)PS | (N)TP
|
||||
#endif
|
||||
char mode = 'A'; // Display mode (A)nalog | (D)igital | race (T)imer
|
||||
char tz = 'L'; // Time zone (L)ocal | (U)TC
|
||||
double timezone = 0; // There are timezones with non int offsets, e.g. 5.5 or 5.75
|
||||
double homelat;
|
||||
double homelon;
|
||||
bool homevalid = false; // Homelat and homelon are valid
|
||||
|
||||
public:
|
||||
PageClock(CommonData &common){
|
||||
// Timer state (static so it survives page switches)
|
||||
static bool timerInitialized;
|
||||
static bool timerRunning;
|
||||
static int timerHours;
|
||||
static int timerMinutes;
|
||||
static int timerSeconds;
|
||||
// Preset seconds for sync button (default 4 minutes)
|
||||
static const int timerPresetSeconds = 4 * 60;
|
||||
// Initial timer setting at start (so we can restore it)
|
||||
static int timerStartHours;
|
||||
static int timerStartMinutes;
|
||||
static int timerStartSeconds;
|
||||
static int selectedField; // 0 = hours, 1 = minutes, 2 = seconds
|
||||
static bool showSelectionMarker;
|
||||
static time_t timerEndEpoch; // Absolute end time based on RTC
|
||||
|
||||
void setupTimerDefaults()
|
||||
{
|
||||
if (!timerInitialized) {
|
||||
timerInitialized = true;
|
||||
timerRunning = false;
|
||||
timerHours = 0;
|
||||
timerMinutes = 0;
|
||||
timerSeconds = 0;
|
||||
timerStartHours = 0;
|
||||
timerStartMinutes = 0;
|
||||
timerStartSeconds = 0;
|
||||
selectedField = 0;
|
||||
showSelectionMarker = true;
|
||||
timerEndEpoch = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Limiter for overrun settings values
|
||||
static int clamp(int value, int minVal, int maxVal)
|
||||
{
|
||||
if (value < minVal) return maxVal;
|
||||
if (value > maxVal) return minVal;
|
||||
return value;
|
||||
}
|
||||
|
||||
void incrementSelected()
|
||||
{
|
||||
if (selectedField == 0) {
|
||||
timerHours = clamp(timerHours + 1, 0, 23);
|
||||
} else if (selectedField == 1) {
|
||||
timerMinutes = clamp(timerMinutes + 1, 0, 59);
|
||||
} else {
|
||||
timerSeconds = clamp(timerSeconds + 1, 0, 59);
|
||||
}
|
||||
}
|
||||
|
||||
void decrementSelected()
|
||||
{
|
||||
if (selectedField == 0) {
|
||||
timerHours = clamp(timerHours - 1, 0, 23);
|
||||
} else if (selectedField == 1) {
|
||||
timerMinutes = clamp(timerMinutes - 1, 0, 59);
|
||||
} else {
|
||||
timerSeconds = clamp(timerSeconds - 1, 0, 59);
|
||||
}
|
||||
}
|
||||
|
||||
int totalTimerSeconds() const
|
||||
{
|
||||
return timerHours * 3600 + timerMinutes * 60 + timerSeconds;
|
||||
}
|
||||
|
||||
public:
|
||||
PageClock(CommonData& common)
|
||||
{
|
||||
commonData = &common;
|
||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageClock");
|
||||
common.logger->logDebug(GwLog::LOG, "Instantiate PageClock");
|
||||
simulation = common.config->getBool(common.config->useSimuData);
|
||||
timezone = common.config->getString(common.config->timeZone).toDouble();
|
||||
homelat = common.config->getString(common.config->homeLAT).toDouble();
|
||||
homelon = common.config->getString(common.config->homeLON).toDouble();
|
||||
homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0;
|
||||
simtime = 38160; // time value 11:36
|
||||
setupTimerDefaults();
|
||||
}
|
||||
|
||||
virtual void setupKeys(){
|
||||
virtual void setupKeys()
|
||||
{
|
||||
Page::setupKeys();
|
||||
commonData->keydata[0].label = "SRC";
|
||||
commonData->keydata[1].label = "MODE";
|
||||
|
||||
if (mode == 'T') {
|
||||
// Timer mode: MODE, POS, +, -, RUN
|
||||
commonData->keydata[0].label = "MODE";
|
||||
commonData->keydata[1].label = "POS";
|
||||
// K3: '+' while editing, 'SYNC' while running to set a preset countdown
|
||||
commonData->keydata[2].label = timerRunning ? "SYNC" : "+";
|
||||
commonData->keydata[3].label = "-";
|
||||
commonData->keydata[4].label = timerRunning ? "RESET" : "START";
|
||||
} else {
|
||||
// Clock modes: like original
|
||||
commonData->keydata[0].label = "MODE";
|
||||
commonData->keydata[1].label = "SRC";
|
||||
commonData->keydata[4].label = "TZ";
|
||||
}
|
||||
}
|
||||
|
||||
// Key functions
|
||||
virtual int handleKey(int key){
|
||||
// Time source
|
||||
if (key == 1) {
|
||||
if (source == 'G') {
|
||||
source = 'R';
|
||||
} else {
|
||||
source = 'G';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (key == 2) {
|
||||
if (mode == 'A') {
|
||||
mode = 'D';
|
||||
} else if (mode == 'D') {
|
||||
mode = 'T';
|
||||
} else {
|
||||
mode = 'A';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// Time zone: Local / UTC
|
||||
if (key == 5) {
|
||||
if (tz == 'L') {
|
||||
tz = 'U';
|
||||
} else {
|
||||
tz = 'L';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
virtual int handleKey(int key)
|
||||
{
|
||||
setupTimerDefaults();
|
||||
|
||||
// Keylock function
|
||||
if(key == 11){ // Code for keylock
|
||||
if (key == 11) { // Code for keylock
|
||||
keylock = !keylock; // Toggle keylock
|
||||
return 0; // Commit the key
|
||||
}
|
||||
|
||||
if (mode == 'T') {
|
||||
// Timer mode key handling
|
||||
|
||||
// MODE (K1): cycle display mode A/D/T
|
||||
if (key == 1) {
|
||||
switch (mode) {
|
||||
case 'A': mode = 'D'; break;
|
||||
case 'D': mode = 'T'; break;
|
||||
case 'T': mode = 'A'; break;
|
||||
default: mode = 'A'; break;
|
||||
}
|
||||
setupKeys();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// POS (K2): select field HH / MM / SS (only if timer not running)
|
||||
if (key == 2 && !timerRunning) {
|
||||
selectedField = (selectedField + 1) % 3;
|
||||
showSelectionMarker = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// + (K3): increment selected field (only if timer not running)
|
||||
if (key == 3 && !timerRunning) {
|
||||
incrementSelected();
|
||||
return 0;
|
||||
}
|
||||
if (key == 3 && timerRunning) {
|
||||
// When timer is running, K3 acts as a synchronization button:
|
||||
// set remaining countdown to the preset value (e.g. 4 minutes).
|
||||
if (commonData->data.rtcValid) {
|
||||
int preset = timerPresetSeconds;
|
||||
// update start-setting so STOP will restore this preset
|
||||
timerStartHours = preset / 3600;
|
||||
timerStartMinutes = (preset % 3600) / 60;
|
||||
timerStartSeconds = preset % 60;
|
||||
|
||||
struct tm rtcCopy = commonData->data.rtcTime;
|
||||
time_t nowEpoch = mktime(&rtcCopy);
|
||||
timerEndEpoch = nowEpoch + preset;
|
||||
|
||||
// Update visible timer fields immediately
|
||||
timerHours = timerStartHours;
|
||||
timerMinutes = timerStartMinutes;
|
||||
timerSeconds = timerStartSeconds;
|
||||
// commonData->keydata[4].label = "RESET";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// - (K4): decrement selected field (only if timer not running)
|
||||
if (key == 4 && !timerRunning) {
|
||||
decrementSelected();
|
||||
return 0;
|
||||
}
|
||||
if (key == 4 && timerRunning) { // No action if timer running
|
||||
return 0;
|
||||
}
|
||||
|
||||
// RUN (K5): start/stop timer
|
||||
if (key == 5) {
|
||||
if (!timerRunning) {
|
||||
// Start timer if a non-zero duration is set
|
||||
int total = totalTimerSeconds();
|
||||
if (total > 0 && commonData->data.rtcValid) {
|
||||
// Remember initial timer setting at start
|
||||
timerStartHours = timerHours;
|
||||
timerStartMinutes = timerMinutes;
|
||||
timerStartSeconds = timerSeconds;
|
||||
|
||||
struct tm rtcCopy = commonData->data.rtcTime;
|
||||
time_t nowEpoch = mktime(&rtcCopy);
|
||||
timerEndEpoch = nowEpoch + total;
|
||||
timerRunning = true;
|
||||
showSelectionMarker = false;
|
||||
}
|
||||
} else {
|
||||
// Stop timer: restore initial start setting
|
||||
timerHours = timerStartHours;
|
||||
timerMinutes = timerStartMinutes;
|
||||
timerSeconds = timerStartSeconds;
|
||||
timerRunning = false;
|
||||
showSelectionMarker = true;
|
||||
// marker will become visible again only after POS press
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// In timer mode, other keys are passed through
|
||||
return key;
|
||||
}
|
||||
|
||||
int displayPage(PageData &pageData)
|
||||
// Clock (A/D) modes key handling – like original PageClock
|
||||
|
||||
// MODE (K1)
|
||||
if (key == 1) {
|
||||
switch (mode) {
|
||||
case 'A': mode = 'D'; break;
|
||||
case 'D': mode = 'T'; break;
|
||||
case 'T': mode = 'A'; break;
|
||||
default: mode = 'A'; break;
|
||||
}
|
||||
setupKeys();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Time source (K2)
|
||||
if (key == 2) {
|
||||
switch (source) {
|
||||
case 'G': source = 'R'; break;
|
||||
case 'R': source = 'G'; break;
|
||||
default: source = 'G'; break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Time zone: Local / UTC (K5)
|
||||
if (key == 5) {
|
||||
switch (tz) {
|
||||
case 'L': tz = 'U'; break;
|
||||
case 'U': tz = 'L'; break;
|
||||
default: tz = 'L'; break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
int displayPage(PageData& pageData)
|
||||
{
|
||||
GwConfigHandler *config = commonData->config;
|
||||
GwLog *logger = commonData->logger;
|
||||
GwConfigHandler* config = commonData->config;
|
||||
GwLog* logger = commonData->logger;
|
||||
|
||||
setupTimerDefaults();
|
||||
setupKeys(); // Ensure correct key labels for current mode
|
||||
|
||||
static String svalue1old = "";
|
||||
static String unit1old = "";
|
||||
@@ -112,58 +319,57 @@ bool homevalid = false; // homelat and homelon are valid
|
||||
String backlightMode = config->getString(config->backlight);
|
||||
|
||||
// Get boat values for GPS time
|
||||
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
|
||||
String name1 = bvalue1->getName().c_str(); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
if(simulation == false){
|
||||
if (simulation == false) {
|
||||
value1 = bvalue1->value; // Value as double in SI unit
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
value1 = simtime++; // Simulation data for time value 11:36 in seconds
|
||||
} // Other simulation data see OBP60Formatter.cpp
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value
|
||||
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
||||
if(valid1 == true){
|
||||
if (valid1 == true) {
|
||||
svalue1old = svalue1; // Save old value
|
||||
unit1old = unit1; // Save old unit
|
||||
}
|
||||
|
||||
// Get boat values for GPS date
|
||||
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
|
||||
String name2 = bvalue2->getName().c_str(); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
bool valid2 = bvalue2->valid; // Valid informationgetdisplay().print("RTC");
|
||||
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value
|
||||
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
|
||||
if(valid2 == true){
|
||||
if (valid2 == true) {
|
||||
svalue2old = svalue2; // Save old value
|
||||
unit2old = unit2; // Save old unit
|
||||
}
|
||||
|
||||
// Get boat values for HDOP date
|
||||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list (only one value by PageOneValue)
|
||||
// Get boat values for HDOP
|
||||
GwApi::BoatValue* bvalue3 = pageData.values[2]; // Third element in list
|
||||
String name3 = bvalue3->getName().c_str(); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
value3 = bvalue3->value; // Value as double in SI unit
|
||||
bool valid3 = bvalue3->valid; // Valid information
|
||||
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value
|
||||
String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value
|
||||
if(valid3 == true){
|
||||
if (valid3 == true) {
|
||||
svalue3old = svalue3; // Save old value
|
||||
unit3old = unit3; // Save old unit
|
||||
}
|
||||
|
||||
// Optical warning by limit violation (unused)
|
||||
if(String(flashLED) == "Limit Violation"){
|
||||
if (String(flashLED) == "Limit Violation") {
|
||||
setBlinkingLED(false);
|
||||
setFlashLED(false);
|
||||
}
|
||||
|
||||
// Logging boat values
|
||||
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement?
|
||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageClock, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2);
|
||||
if (bvalue1 == NULL) return PAGE_OK;
|
||||
LOG_DEBUG(GwLog::LOG, "Drawing at PageClock, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2);
|
||||
|
||||
// Draw page
|
||||
//***********************************************************
|
||||
@@ -174,7 +380,180 @@ bool homevalid = false; // homelat and homelon are valid
|
||||
getdisplay().setTextColor(commonData->fgcolor);
|
||||
|
||||
time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600;
|
||||
struct tm *local_tm = localtime(&tv);
|
||||
struct tm* local_tm = localtime(&tv);
|
||||
|
||||
if (mode == 'T') {
|
||||
// REGATTA TIMER MODE: countdown timer HH:MM:SS in the center with 7-segment font
|
||||
//*******************************************************************************
|
||||
|
||||
int dispH = timerHours;
|
||||
int dispM = timerMinutes;
|
||||
int dispS = timerSeconds;
|
||||
|
||||
// Update remaining time if timer is running (based on RTC)
|
||||
if (timerRunning && commonData->data.rtcValid) {
|
||||
struct tm rtcCopy = commonData->data.rtcTime;
|
||||
time_t nowEpoch = mktime(&rtcCopy);
|
||||
time_t remaining = timerEndEpoch - nowEpoch;
|
||||
if(remaining <= 5 && remaining != 0){
|
||||
// Short pre buzzer alarm (100% power)
|
||||
setBuzzerPower(100);
|
||||
buzzer(TONE2, 75);
|
||||
setBuzzerPower(config->getInt(config->buzzerPower));
|
||||
}
|
||||
if (remaining <= 0) {
|
||||
remaining = 0;
|
||||
timerRunning = false;
|
||||
commonData->keydata[3].label = "-";
|
||||
commonData->keydata[4].label = "START";
|
||||
showSelectionMarker = true;
|
||||
// Buzzer alarm (100% power)
|
||||
setBuzzerPower(100);
|
||||
buzzer(TONE2, 800);
|
||||
setBuzzerPower(config->getInt(config->buzzerPower));
|
||||
|
||||
// When countdown is finished, restore the initial start time
|
||||
timerHours = timerStartHours;
|
||||
timerMinutes = timerStartMinutes;
|
||||
timerSeconds = timerStartSeconds;
|
||||
}
|
||||
else{
|
||||
commonData->keydata[3].label = "";
|
||||
commonData->keydata[4].label = "RESET";
|
||||
}
|
||||
int rem = static_cast<int>(remaining);
|
||||
dispH = rem / 3600;
|
||||
rem -= dispH * 3600;
|
||||
dispM = rem / 60;
|
||||
dispS = rem % 60;
|
||||
}
|
||||
|
||||
char buf[9]; // "HH:MM:SS"
|
||||
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", dispH, dispM, dispS);
|
||||
String timeStr = String(buf);
|
||||
|
||||
// Clear central area and draw large digital time
|
||||
getdisplay().fillRect(0, 110, getdisplay().width(), 80, commonData->bgcolor);
|
||||
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
|
||||
|
||||
// Determine widths for digits and colon to position selection underline exactly
|
||||
int16_t x0, y0;
|
||||
uint16_t wDigit, hDigit;
|
||||
uint16_t wColon, hColon;
|
||||
|
||||
getdisplay().getTextBounds("00", 0, 0, &x0, &y0, &wDigit, &hDigit);
|
||||
getdisplay().getTextBounds(":", 0, 0, &x0, &y0, &wColon, &hColon);
|
||||
|
||||
uint16_t totalWidth = 3 * wDigit + 2 * wColon;
|
||||
|
||||
int16_t baseX = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(totalWidth)) / 2;
|
||||
int16_t centerY = 150;
|
||||
|
||||
// Draw time string centered
|
||||
int16_t x1b, y1b;
|
||||
uint16_t wb, hb;
|
||||
getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb);
|
||||
int16_t textX = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2;
|
||||
int16_t textY = centerY + hb / 2;
|
||||
|
||||
//getdisplay().setCursor(textX, textY); // horzontal jitter
|
||||
getdisplay().setCursor(47, textY); // static X position
|
||||
getdisplay().print(timeStr);
|
||||
|
||||
// Selection marker (only visible when not running and POS pressed)
|
||||
if (!timerRunning && showSelectionMarker) {
|
||||
int16_t selX = baseX - 8; // Hours start
|
||||
if (selectedField == 1) {
|
||||
selX = baseX + wDigit + wColon; // Minutes start
|
||||
} else if (selectedField == 2) {
|
||||
selX = baseX + 2 * wDigit + 2 * wColon + 12; // Seconds start
|
||||
}
|
||||
|
||||
int16_t underlineY = centerY + hb / 2 + 5;
|
||||
//getdisplay().fillRect(selX, underlineY, wDigit, 6, commonData->fgcolor);
|
||||
getdisplay().fillRoundRect(selX, underlineY, wDigit, 6, 2, commonData->fgcolor);
|
||||
}
|
||||
|
||||
// Page label
|
||||
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
||||
getdisplay().setCursor(100, 70);
|
||||
getdisplay().print("Regatta Timer");
|
||||
|
||||
} else if (mode == 'D') {
|
||||
// DIGITAL CLOCK MODE: large 7-segment time based on GPS/RTC
|
||||
//**********************************************************
|
||||
|
||||
int hour24 = 0;
|
||||
int minute24 = 0;
|
||||
int second24 = 0;
|
||||
|
||||
if (source == 'R' && commonData->data.rtcValid) {
|
||||
time_t tv2 = mktime(&commonData->data.rtcTime);
|
||||
if (tz == 'L') {
|
||||
tv2 += static_cast<time_t>(timezone * 3600);
|
||||
}
|
||||
struct tm* tm2 = localtime(&tv2);
|
||||
hour24 = tm2->tm_hour;
|
||||
minute24 = tm2->tm_min;
|
||||
second24 = tm2->tm_sec;
|
||||
} else {
|
||||
double t = value1;
|
||||
if (tz == 'L') {
|
||||
t += timezone * 3600;
|
||||
}
|
||||
if (t >= 86400) t -= 86400;
|
||||
if (t < 0) t += 86400;
|
||||
hour24 = static_cast<int>(t / 3600.0);
|
||||
int rest = static_cast<int>(t) - hour24 * 3600;
|
||||
minute24 = rest / 60;
|
||||
second24 = rest % 60;
|
||||
}
|
||||
|
||||
char buf[9]; // "HH:MM:SS"
|
||||
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour24, minute24, second24);
|
||||
String timeStr = String(buf);
|
||||
|
||||
getdisplay().fillRect(0, 110, getdisplay().width(), 80, commonData->bgcolor);
|
||||
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
|
||||
|
||||
int16_t x1b, y1b;
|
||||
uint16_t wb, hb;
|
||||
getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb);
|
||||
|
||||
int16_t x = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2;
|
||||
int16_t y = 150 + hb / 2;
|
||||
|
||||
//getdisplay().setCursor(x, y); // horizontal jitter
|
||||
getdisplay().setCursor(47, y); // static X position
|
||||
getdisplay().print(timeStr); // Display actual time
|
||||
|
||||
// Small indicators: timezone and source
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
|
||||
getdisplay().setCursor(47, 110);
|
||||
if (source == 'G') {
|
||||
getdisplay().print("GPS");
|
||||
} else {
|
||||
getdisplay().print("RTC");
|
||||
}
|
||||
|
||||
getdisplay().setCursor(47 + 40, 110);
|
||||
if (holdvalues == false) {
|
||||
getdisplay().print(tz == 'L' ? "LOT" : "UTC");
|
||||
} else {
|
||||
getdisplay().print(unit2old); // date unit
|
||||
}
|
||||
|
||||
// Page label
|
||||
getdisplay().setFont(&Ubuntu_Bold16pt8b);
|
||||
getdisplay().setCursor(100, 70);
|
||||
getdisplay().print("Digital Clock");
|
||||
|
||||
} else {
|
||||
// ANALOG CLOCK MODE (mode == 'A')
|
||||
//********************************
|
||||
|
||||
// Show values GPS date
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
@@ -187,8 +566,7 @@ bool homevalid = false; // homelat and homelon are valid
|
||||
// RTC value
|
||||
if (tz == 'L') {
|
||||
getdisplay().print(formatDate(dateformat, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
getdisplay().print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday));
|
||||
}
|
||||
} else {
|
||||
@@ -201,28 +579,25 @@ bool homevalid = false; // homelat and homelon are valid
|
||||
getdisplay().setCursor(10, 95);
|
||||
getdisplay().print("Date"); // Name
|
||||
|
||||
// Horizintal separator left
|
||||
// Horizontal separator left
|
||||
getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor);
|
||||
|
||||
// Show values GPS time
|
||||
// Show values GPS time (small text bottom left)
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
getdisplay().setCursor(10, 250);
|
||||
if (holdvalues == false) {
|
||||
if (source == 'G') {
|
||||
getdisplay().print(svalue1); // Value
|
||||
}
|
||||
else if (commonData->data.rtcValid) {
|
||||
} else if (commonData->data.rtcValid) {
|
||||
if (tz == 'L') {
|
||||
getdisplay().print(formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
getdisplay().print(formatTime('s', commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec));
|
||||
}
|
||||
} else {
|
||||
getdisplay().print("---");
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
getdisplay().print(svalue1old);
|
||||
}
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||
@@ -240,13 +615,13 @@ bool homevalid = false; // homelat and homelon are valid
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
getdisplay().setCursor(335, 65);
|
||||
if(holdvalues == false) getdisplay().print(sunrise); // Value
|
||||
if (holdvalues == false) getdisplay().print(sunrise); // Value
|
||||
else getdisplay().print(svalue5old);
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||
getdisplay().setCursor(335, 95);
|
||||
getdisplay().print("SunR"); // Name
|
||||
|
||||
// Horizintal separator right
|
||||
// Horizontal separator right
|
||||
getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor);
|
||||
|
||||
// Show values sunset
|
||||
@@ -260,50 +635,39 @@ bool homevalid = false; // homelat and homelon are valid
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
getdisplay().setCursor(335, 250);
|
||||
if(holdvalues == false) getdisplay().print(sunset); // Value
|
||||
if (holdvalues == false) getdisplay().print(sunset); // Value
|
||||
else getdisplay().print(svalue6old);
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||
getdisplay().setCursor(335, 220);
|
||||
getdisplay().print("SunS"); // Name
|
||||
|
||||
//*******************************************************************************************
|
||||
|
||||
// Draw clock
|
||||
int rInstrument = 110; // Radius of clock
|
||||
float pi = 3.141592;
|
||||
|
||||
getdisplay().fillCircle(200, 150, rInstrument + 10, commonData->fgcolor); // Outer circle
|
||||
getdisplay().fillCircle(200, 150, rInstrument + 7, commonData->bgcolor); // Outer circle
|
||||
|
||||
for(int i=0; i<360; i=i+1)
|
||||
for (int i = 0; i < 360; i = i + 1)
|
||||
{
|
||||
// Scaling values
|
||||
float x = 200 + (rInstrument-30)*sin(i/180.0*pi); // x-coordinate dots
|
||||
float y = 150 - (rInstrument-30)*cos(i/180.0*pi); // y-coordinate cots
|
||||
const char *ii = "";
|
||||
float x = 200 + (rInstrument - 30) * sin(i / 180.0 * pi); // x-coordinate dots
|
||||
float y = 150 - (rInstrument - 30) * cos(i / 180.0 * pi); // y-coordinate dots
|
||||
const char* ii = "";
|
||||
switch (i)
|
||||
{
|
||||
case 0: ii="12"; break;
|
||||
case 30 : ii=""; break;
|
||||
case 60 : ii=""; break;
|
||||
case 90 : ii="3"; break;
|
||||
case 120 : ii=""; break;
|
||||
case 150 : ii=""; break;
|
||||
case 180 : ii="6"; break;
|
||||
case 210 : ii=""; break;
|
||||
case 240 : ii=""; break;
|
||||
case 270 : ii="9"; break;
|
||||
case 300 : ii=""; break;
|
||||
case 330 : ii=""; break;
|
||||
case 0: ii = "12"; break;
|
||||
case 90: ii = "3"; break;
|
||||
case 180: ii = "6"; break;
|
||||
case 270: ii = "9"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Print text centered on position x, y
|
||||
int16_t x1, y1; // Return values of getTextBounds
|
||||
uint16_t w, h; // Return values of getTextBounds
|
||||
getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string
|
||||
getdisplay().setCursor(x-w/2, y+h/2);
|
||||
if(i % 30 == 0){
|
||||
int16_t x1c, y1c; // Return values of getTextBounds
|
||||
uint16_t wc, hc; // Return values of getTextBounds
|
||||
getdisplay().getTextBounds(ii, int(x), int(y), &x1c, &y1c, &wc, &hc); // Calc width of new string
|
||||
getdisplay().setCursor(x - wc / 2, y + hc / 2);
|
||||
if (i % 90 == 0) {
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||
getdisplay().print(ii);
|
||||
}
|
||||
@@ -311,37 +675,36 @@ bool homevalid = false; // homelat and homelon are valid
|
||||
// Draw sub scale with dots
|
||||
float sinx = 0;
|
||||
float cosx = 0;
|
||||
if(i % 6 == 0){
|
||||
float x1c = 200 + rInstrument*sin(i/180.0*pi);
|
||||
float y1c = 150 - rInstrument*cos(i/180.0*pi);
|
||||
getdisplay().fillCircle((int)x1c, (int)y1c, 2, commonData->fgcolor);
|
||||
sinx=sin(i/180.0*pi);
|
||||
cosx=cos(i/180.0*pi);
|
||||
if (i % 6 == 0) {
|
||||
float x1d = 200 + rInstrument * sin(i / 180.0 * pi);
|
||||
float y1d = 150 - rInstrument * cos(i / 180.0 * pi);
|
||||
getdisplay().fillCircle((int)x1d, (int)y1d, 2, commonData->fgcolor);
|
||||
sinx = sin(i / 180.0 * pi);
|
||||
cosx = cos(i / 180.0 * pi);
|
||||
}
|
||||
|
||||
// Draw sub scale with lines (two triangles)
|
||||
if(i % 30 == 0){
|
||||
float dx=2; // Line thickness = 2*dx+1
|
||||
if (i % 30 == 0) {
|
||||
float dx = 2; // Line thickness = 2*dx+1
|
||||
float xx1 = -dx;
|
||||
float xx2 = +dx;
|
||||
float yy1 = -(rInstrument-10);
|
||||
float yy2 = -(rInstrument+10);
|
||||
getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1),
|
||||
200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1),
|
||||
200+(int)(cosx*xx1-sinx*yy2),150+(int)(sinx*xx1+cosx*yy2),commonData->fgcolor);
|
||||
getdisplay().fillTriangle(200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1),
|
||||
200+(int)(cosx*xx1-sinx*yy2),150+(int)(sinx*xx1+cosx*yy2),
|
||||
200+(int)(cosx*xx2-sinx*yy2),150+(int)(sinx*xx2+cosx*yy2),commonData->fgcolor);
|
||||
float yy1 = -(rInstrument - 10);
|
||||
float yy2 = -(rInstrument + 10);
|
||||
getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1),
|
||||
200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
|
||||
200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2), commonData->fgcolor);
|
||||
getdisplay().fillTriangle(200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
|
||||
200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2),
|
||||
200 + (int)(cosx * xx2 - sinx * yy2), 150 + (int)(sinx * xx2 + cosx * yy2), commonData->fgcolor);
|
||||
}
|
||||
}
|
||||
|
||||
// Print Unit in clock
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||
getdisplay().setCursor(175, 110);
|
||||
if(holdvalues == false){
|
||||
if (holdvalues == false) {
|
||||
getdisplay().print(tz == 'L' ? "LOT" : "UTC");
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
getdisplay().print(unit2old); // date unit
|
||||
}
|
||||
|
||||
@@ -358,10 +721,10 @@ bool homevalid = false; // homelat and homelon are valid
|
||||
double minute = 0;
|
||||
if (source == 'R') {
|
||||
if (tz == 'L') {
|
||||
time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600;
|
||||
struct tm *local_tm = localtime(&tv);
|
||||
minute = local_tm->tm_min;
|
||||
hour = local_tm->tm_hour;
|
||||
time_t tv2 = mktime(&commonData->data.rtcTime) + timezone * 3600;
|
||||
struct tm* local_tm2 = localtime(&tv2);
|
||||
minute = local_tm2->tm_min;
|
||||
hour = local_tm2->tm_hour;
|
||||
} else {
|
||||
minute = commonData->data.rtcTime.tm_min;
|
||||
hour = commonData->data.rtcTime.tm_hour;
|
||||
@@ -371,8 +734,8 @@ bool homevalid = false; // homelat and homelon are valid
|
||||
if (tz == 'L') {
|
||||
value1 += timezone * 3600;
|
||||
}
|
||||
if (value1 > 86400) {value1 -= 86400;}
|
||||
if (value1 < 0) {value1 += 86400;}
|
||||
if (value1 > 86400) { value1 -= 86400; }
|
||||
if (value1 < 0) { value1 += 86400; }
|
||||
hour = (value1 / 3600.0);
|
||||
// minute = (hour - int(hour)) * 3600.0 / 60.0; // Analog minute pointer smooth moving
|
||||
minute = int((hour - int(hour)) * 3600.0 / 60.0); // Jumping minute pointer from minute to minute
|
||||
@@ -380,22 +743,22 @@ bool homevalid = false; // homelat and homelon are valid
|
||||
if (hour > 12) {
|
||||
hour -= 12.0;
|
||||
}
|
||||
LOG_DEBUG(GwLog::DEBUG,"... PageClock, value1: %f hour: %f minute:%f", value1, hour, minute);
|
||||
LOG_DEBUG(GwLog::DEBUG, "... PageClock, value1: %f hour: %f minute:%f", value1, hour, minute);
|
||||
|
||||
// Draw hour pointer
|
||||
float startwidth = 8; // Start width of pointer
|
||||
if(valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true){
|
||||
float sinx=sin(hour * 30.0 * pi / 180); // Hour
|
||||
float cosx=cos(hour * 30.0 * pi / 180);
|
||||
if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) {
|
||||
float sinx = sin(hour * 30.0 * pi / 180); // Hour
|
||||
float cosx = cos(hour * 30.0 * pi / 180);
|
||||
// Normal pointer
|
||||
// Pointer as triangle with center base 2*width
|
||||
float xx1 = -startwidth;
|
||||
float xx2 = startwidth;
|
||||
float yy1 = -startwidth;
|
||||
float yy2 = -(rInstrument * 0.5);
|
||||
getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1),
|
||||
200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1),
|
||||
200+(int)(cosx*0-sinx*yy2),150+(int)(sinx*0+cosx*yy2),commonData->fgcolor);
|
||||
getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1),
|
||||
200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
|
||||
200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor);
|
||||
// Inverted pointer
|
||||
// Pointer as triangle with center base 2*width
|
||||
float endwidth = 2; // End width of pointer
|
||||
@@ -403,25 +766,25 @@ bool homevalid = false; // homelat and homelon are valid
|
||||
float ix2 = -endwidth;
|
||||
float iy1 = -(rInstrument * 0.5);
|
||||
float iy2 = -endwidth;
|
||||
getdisplay().fillTriangle(200+(int)(cosx*ix1-sinx*iy1),150+(int)(sinx*ix1+cosx*iy1),
|
||||
200+(int)(cosx*ix2-sinx*iy1),150+(int)(sinx*ix2+cosx*iy1),
|
||||
200+(int)(cosx*0-sinx*iy2),150+(int)(sinx*0+cosx*iy2),commonData->fgcolor);
|
||||
getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1),
|
||||
200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1),
|
||||
200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor);
|
||||
}
|
||||
|
||||
// Draw minute pointer
|
||||
startwidth = 8; // Start width of pointer
|
||||
if(valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true){
|
||||
float sinx=sin(minute * 6.0 * pi / 180); // Minute
|
||||
float cosx=cos(minute * 6.0 * pi / 180);
|
||||
if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) {
|
||||
float sinx = sin(minute * 6.0 * pi / 180); // Minute
|
||||
float cosx = cos(minute * 6.0 * pi / 180);
|
||||
// Normal pointer
|
||||
// Pointer as triangle with center base 2*width
|
||||
float xx1 = -startwidth;
|
||||
float xx2 = startwidth;
|
||||
float yy1 = -startwidth;
|
||||
float yy2 = -(rInstrument - 15);
|
||||
getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1),
|
||||
200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1),
|
||||
200+(int)(cosx*0-sinx*yy2),150+(int)(sinx*0+cosx*yy2),commonData->fgcolor);
|
||||
getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1),
|
||||
200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
|
||||
200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor);
|
||||
// Inverted pointer
|
||||
// Pointer as triangle with center base 2*width
|
||||
float endwidth = 2; // End width of pointer
|
||||
@@ -429,28 +792,43 @@ bool homevalid = false; // homelat and homelon are valid
|
||||
float ix2 = -endwidth;
|
||||
float iy1 = -(rInstrument - 15);
|
||||
float iy2 = -endwidth;
|
||||
getdisplay().fillTriangle(200+(int)(cosx*ix1-sinx*iy1),150+(int)(sinx*ix1+cosx*iy1),
|
||||
200+(int)(cosx*ix2-sinx*iy1),150+(int)(sinx*ix2+cosx*iy1),
|
||||
200+(int)(cosx*0-sinx*iy2),150+(int)(sinx*0+cosx*iy2),commonData->fgcolor);
|
||||
getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1),
|
||||
200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1),
|
||||
200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor);
|
||||
}
|
||||
|
||||
// Center circle
|
||||
getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor);
|
||||
getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor);
|
||||
}
|
||||
|
||||
return PAGE_UPDATE;
|
||||
};
|
||||
};
|
||||
|
||||
static Page *createPage(CommonData &common){
|
||||
// Static member definitions
|
||||
bool PageClock::timerInitialized = false;
|
||||
bool PageClock::timerRunning = false;
|
||||
int PageClock::timerHours = 0;
|
||||
int PageClock::timerMinutes = 0;
|
||||
int PageClock::timerSeconds = 0;
|
||||
int PageClock::timerStartHours = 0;
|
||||
int PageClock::timerStartMinutes = 0;
|
||||
int PageClock::timerStartSeconds = 0;
|
||||
int PageClock::selectedField = 0;
|
||||
bool PageClock::showSelectionMarker = true;
|
||||
time_t PageClock::timerEndEpoch = 0;
|
||||
|
||||
static Page* createPage(CommonData& common)
|
||||
{
|
||||
return new PageClock(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 (0 here)
|
||||
* and will will provide the names of the fixed values we need
|
||||
* we provide the number of user parameters we expect (0 here)
|
||||
* and we provide the names of the fixed values we need
|
||||
*/
|
||||
PageDescription registerPageClock(
|
||||
"Clock", // Page name
|
||||
@@ -461,3 +839,4 @@ PageDescription registerPageClock(
|
||||
);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -162,9 +162,13 @@ public:
|
||||
constexpr int ZOOM_KEY = 1;
|
||||
#endif
|
||||
|
||||
if (dataHstryBuf) { // show "Mode" key only if chart supported boat data type is available
|
||||
if (dataHstryBuf) { // show "Mode" key only if chart-supported boat data type is available
|
||||
commonData->keydata[0].label = "MODE";
|
||||
if (pageMode != VALUE) { // show "ZOOM" key only if chart is visible
|
||||
commonData->keydata[ZOOM_KEY].label = "ZOOM";
|
||||
} else {
|
||||
commonData->keydata[ZOOM_KEY].label = "";
|
||||
}
|
||||
} else {
|
||||
commonData->keydata[0].label = "";
|
||||
commonData->keydata[ZOOM_KEY].label = "";
|
||||
@@ -189,14 +193,15 @@ public:
|
||||
pageMode = VALUE;
|
||||
break;
|
||||
}
|
||||
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||
return 0; // Commit the key
|
||||
}
|
||||
|
||||
// Set time frame to show for history chart
|
||||
// Set time frame to show for chart
|
||||
#if defined BOARD_OBP60S3
|
||||
if (key == 5) {
|
||||
if (key == 5 && pageMode != VALUE) {
|
||||
#elif defined BOARD_OBP40S3
|
||||
if (key == 2) {
|
||||
if (key == 2 && pageMode != VALUE) {
|
||||
#endif
|
||||
if (dataIntv == 1) {
|
||||
dataIntv = 2;
|
||||
@@ -247,7 +252,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
setupKeys(); // adjust <mode> key depending on chart supported boat data type
|
||||
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||
}
|
||||
|
||||
int displayPage(PageData& pageData)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "images/logo64.xbm"
|
||||
#include <esp32/clk.h>
|
||||
#include "qrcode.h"
|
||||
#include <vector>
|
||||
|
||||
#ifdef BOARD_OBP40S3
|
||||
#include "dirent.h"
|
||||
@@ -37,9 +38,11 @@ private:
|
||||
String buzzer_mode;
|
||||
uint8_t buzzer_power;
|
||||
String cpuspeed;
|
||||
String powermode;
|
||||
String rtc_module;
|
||||
String gps_module;
|
||||
String env_module;
|
||||
String flashLED;
|
||||
|
||||
String batt_sensor;
|
||||
String solar_sensor;
|
||||
@@ -48,152 +51,59 @@ private:
|
||||
double homelat;
|
||||
double homelon;
|
||||
|
||||
char mode = 'N'; // (N)ormal, (S)ettings, (D)evice list, (C)ard
|
||||
char mode = 'N'; // (N)ormal, (S)ettings, (C)onfiguration, (D)evice list, c(A)rd
|
||||
|
||||
public:
|
||||
PageSystem(CommonData &common){
|
||||
commonData = &common;
|
||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageSystem");
|
||||
if (hasFRAM) {
|
||||
mode = fram.read(FRAM_SYSTEM_MODE);
|
||||
common.logger->logDebug(GwLog::DEBUG, "Loaded mode '%c' from FRAM", mode);
|
||||
}
|
||||
chipid = ESP.getEfuseMac();
|
||||
simulation = common.config->getBool(common.config->useSimuData);
|
||||
#ifdef BOARD_OBP40S3
|
||||
use_sdcard = common.config->getBool(common.config->useSDCard);
|
||||
#ifdef PATCH_N2K
|
||||
struct device {
|
||||
uint64_t NAME;
|
||||
uint8_t id;
|
||||
char hex_name[17];
|
||||
uint16_t manuf_code;
|
||||
const char *model;
|
||||
};
|
||||
std::vector<device> devicelist;
|
||||
#endif
|
||||
buzzer_mode = common.config->getString(common.config->buzzerMode);
|
||||
buzzer_mode.toLowerCase();
|
||||
buzzer_power = common.config->getInt(common.config->buzzerPower);
|
||||
cpuspeed = common.config->getString(common.config->cpuSpeed);
|
||||
env_module = common.config->getString(common.config->useEnvSensor);
|
||||
rtc_module = common.config->getString(common.config->useRTC);
|
||||
gps_module = common.config->getString(common.config->useGPS);
|
||||
batt_sensor = common.config->getString(common.config->usePowSensor1);
|
||||
solar_sensor = common.config->getString(common.config->usePowSensor2);
|
||||
gen_sensor = common.config->getString(common.config->usePowSensor3);
|
||||
rot_sensor = common.config->getString(common.config->useRotSensor);
|
||||
homelat = common.config->getString(common.config->homeLAT).toDouble();
|
||||
homelon = common.config->getString(common.config->homeLON).toDouble();
|
||||
}
|
||||
|
||||
void setupKeys() {
|
||||
commonData->keydata[0].label = "EXIT";
|
||||
commonData->keydata[1].label = "MODE";
|
||||
commonData->keydata[2].label = "";
|
||||
commonData->keydata[3].label = "RST";
|
||||
commonData->keydata[4].label = "STBY";
|
||||
commonData->keydata[5].label = "ILUM";
|
||||
}
|
||||
|
||||
int handleKey(int key) {
|
||||
// do *NOT* handle key #1 this handled by obp60task as exit
|
||||
// Switch display mode
|
||||
commonData->logger->logDebug(GwLog::LOG, "System keyboard handler");
|
||||
if (key == 2) {
|
||||
if (mode == 'N') {
|
||||
void incMode() {
|
||||
if (mode == 'N') { // Normal
|
||||
mode = 'S';
|
||||
} else if (mode == 'S') {
|
||||
} else if (mode == 'S') { // Settings
|
||||
mode = 'C';
|
||||
} else if (mode == 'C') { // Config
|
||||
mode = 'D';
|
||||
} else if (mode == 'D') {
|
||||
if (hasSDCard) {
|
||||
} else if (mode == 'D') { // Device list
|
||||
if (use_sdcard) {
|
||||
mode = 'A'; // SD-Card
|
||||
} else {
|
||||
mode = 'N';
|
||||
}
|
||||
} else {
|
||||
mode = 'N';
|
||||
}
|
||||
}
|
||||
|
||||
void decMode() {
|
||||
if (mode == 'N') {
|
||||
if (use_sdcard) {
|
||||
mode = 'A'; // SD-Card
|
||||
} else {
|
||||
mode = 'D'; // Device list
|
||||
}
|
||||
} else if (mode == 'S') { // Settings
|
||||
mode = 'N';
|
||||
} else if (mode == 'C') { // Config
|
||||
mode = 'S';
|
||||
} else if (mode == 'D') { // Device list
|
||||
mode = 'C';
|
||||
} else {
|
||||
mode = 'N';
|
||||
}
|
||||
} else {
|
||||
mode = 'N';
|
||||
}
|
||||
if (hasFRAM) fram.write(FRAM_SYSTEM_MODE, mode);
|
||||
return 0;
|
||||
}
|
||||
#ifdef BOARD_OBP60S3
|
||||
// grab cursor key to disable page navigation
|
||||
if (key == 3) {
|
||||
return 0;
|
||||
}
|
||||
// soft reset
|
||||
if (key == 4) {
|
||||
ESP.restart();
|
||||
}
|
||||
// standby / deep sleep
|
||||
if (key == 5) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "System going into deep sleep mode...");
|
||||
deepSleep(*commonData);
|
||||
}
|
||||
// Code for keylock
|
||||
if (key == 11) {
|
||||
commonData->keylock = !commonData->keylock;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#ifdef BOARD_OBP40S3
|
||||
// grab cursor keys to disable page navigation
|
||||
if (key == 9 or key == 10) {
|
||||
return 0;
|
||||
}
|
||||
// standby / deep sleep
|
||||
if (key == 12) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "System going into deep sleep mode...");
|
||||
deepSleep(*commonData);
|
||||
}
|
||||
#endif
|
||||
return key;
|
||||
}
|
||||
|
||||
void displayBarcode(String serialno, uint16_t x, uint16_t y, uint16_t s) {
|
||||
// Barcode with serial number
|
||||
// x, y is top left corner
|
||||
// s is pixel size of a single box
|
||||
QRCode qrcode;
|
||||
uint8_t qrcodeData[qrcode_getBufferSize(4)];
|
||||
#ifdef BOARD_OBP40S3
|
||||
String prefix = "OBP40:SN:";
|
||||
#endif
|
||||
#ifdef BOARD_OBP60S3
|
||||
String prefix = "OBP60:SN:";
|
||||
#endif
|
||||
qrcode_initText(&qrcode, qrcodeData, 4, 0, (prefix + serialno).c_str());
|
||||
int16_t x0 = x;
|
||||
for (uint8_t j = 0; j < qrcode.size; j++) {
|
||||
for (uint8_t i = 0; i < qrcode.size; i++) {
|
||||
if (qrcode_getModule(&qrcode, i, j)) {
|
||||
getdisplay().fillRect(x, y, s, s, commonData->fgcolor);
|
||||
}
|
||||
x += s;
|
||||
}
|
||||
y += s;
|
||||
x = x0;
|
||||
mode = 'D';
|
||||
}
|
||||
}
|
||||
|
||||
int displayPage(PageData &pageData){
|
||||
GwConfigHandler *config = commonData->config;
|
||||
GwLog *logger = commonData->logger;
|
||||
void displayModeNormal() {
|
||||
// Default system page view
|
||||
|
||||
// Get config data
|
||||
String flashLED = config->getString(config->flashLED);
|
||||
|
||||
// Optical warning by limit violation (unused)
|
||||
if(String(flashLED) == "Limit Violation"){
|
||||
setBlinkingLED(false);
|
||||
setFlashLED(false);
|
||||
}
|
||||
|
||||
// Logging boat values
|
||||
logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
|
||||
|
||||
// Draw page
|
||||
//***********************************************************
|
||||
|
||||
uint16_t x0 = 8; // left column
|
||||
uint16_t y0 = 48; // data table starts here
|
||||
|
||||
// Set display in partial refresh mode
|
||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||
|
||||
if (mode == 'N') {
|
||||
uint16_t y0 = 155;
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||
getdisplay().setCursor(8, 48);
|
||||
@@ -202,7 +112,6 @@ public:
|
||||
getdisplay().drawXBitmap(320, 25, logo64_bits, logo64_width, logo64_height, commonData->fgcolor);
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
y0 = 155;
|
||||
|
||||
char ssid[13];
|
||||
snprintf(ssid, 13, "%04X%08X", (uint16_t)(chipid >> 32), (uint32_t)chipid);
|
||||
@@ -315,16 +224,45 @@ public:
|
||||
getdisplay().setCursor(300, y0 + 32);
|
||||
getdisplay().print(String(RAM_free));
|
||||
|
||||
} else if (mode == 'S') {
|
||||
// Settings
|
||||
}
|
||||
|
||||
void displayModeConfig() {
|
||||
// Configuration interface
|
||||
|
||||
uint16_t x0 = 16;
|
||||
uint16_t y0 = 80;
|
||||
uint16_t dy = 20;
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||
getdisplay().setCursor(8, 48);
|
||||
getdisplay().print("System configuration");
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
|
||||
getdisplay().setCursor(x0, y0);
|
||||
getdisplay().print("CPU speed: 80 | 160 | 240");
|
||||
getdisplay().setCursor(x0, y0 + 1 * dy);
|
||||
getdisplay().print("Power mode: Max | 5V | Min");
|
||||
getdisplay().setCursor(x0, y0 + 2 * dy);
|
||||
getdisplay().print("Accesspoint: On | Off");
|
||||
|
||||
// TODO Change NVRAM-preferences settings here
|
||||
getdisplay().setCursor(x0, y0 + 4 * dy);
|
||||
getdisplay().print("Simulation: On | Off");
|
||||
|
||||
}
|
||||
|
||||
void displayModeSettings() {
|
||||
// View some of the current settings
|
||||
|
||||
const uint16_t x0 = 8;
|
||||
const uint16_t y0 = 72;
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||
getdisplay().setCursor(x0, 48);
|
||||
getdisplay().print("System settings");
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
x0 = 8;
|
||||
y0 = 72;
|
||||
|
||||
// left column
|
||||
getdisplay().setCursor(x0, y0);
|
||||
@@ -367,6 +305,12 @@ public:
|
||||
getdisplay().setCursor(120, y0 + 144);
|
||||
getdisplay().print(formatLongitude(homelon));
|
||||
|
||||
// Power
|
||||
getdisplay().setCursor(x0, y0 + 176);
|
||||
getdisplay().print("Power mode:");
|
||||
getdisplay().setCursor(120, y0 + 176);
|
||||
getdisplay().print(powermode);
|
||||
|
||||
// right column
|
||||
getdisplay().setCursor(202, y0);
|
||||
getdisplay().print("Batt. sensor:");
|
||||
@@ -385,18 +329,46 @@ public:
|
||||
getdisplay().setCursor(320, y0 + 32);
|
||||
getdisplay().print(gen_sensor);
|
||||
|
||||
// Gyro sensor
|
||||
// TODO
|
||||
// Gyro sensor (rotation)
|
||||
getdisplay().setCursor(202, y0 + 48);
|
||||
getdisplay().print("Rot. sensor:");
|
||||
getdisplay().setCursor(320, y0 + 48);
|
||||
getdisplay().print(rot_sensor);
|
||||
|
||||
// Temp.-sensor
|
||||
// Power Mode
|
||||
|
||||
#ifdef BOARD_OBP60S3
|
||||
// Backlight infos
|
||||
getdisplay().setCursor(202, y0 + 64);
|
||||
getdisplay().print("Backlight:");
|
||||
getdisplay().setCursor(320, y0 + 64);
|
||||
getdisplay().printf("%d%%", commonData->backlight.brightness);
|
||||
// TODO test function with OBP60 device
|
||||
getdisplay().setCursor(202, y0 + 80);
|
||||
getdisplay().print("Bl color:");
|
||||
getdisplay().setCursor(320, y0 + 80);
|
||||
getdisplay().print(commonData->backlight.color.toName());
|
||||
getdisplay().setCursor(202, y0 + 96);
|
||||
getdisplay().print("Bl mode:");
|
||||
getdisplay().setCursor(320, y0 + 96);
|
||||
getdisplay().print(commonData->backlight.mode);
|
||||
// TODO Buzzer mode and power
|
||||
#endif
|
||||
}
|
||||
|
||||
void displayModeSDCard() {
|
||||
|
||||
// SD Card info
|
||||
uint16_t x0 = 20;
|
||||
uint16_t y0 = 72;
|
||||
|
||||
} else if (mode == 'C') {
|
||||
// Card info
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||
getdisplay().setCursor(8, 48);
|
||||
getdisplay().print("SD Card info");
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
|
||||
x0 = 20;
|
||||
y0 = 72;
|
||||
getdisplay().setCursor(x0, y0);
|
||||
#ifdef BOARD_OBP60S3
|
||||
// This mode should not be callable by devices without card hardware
|
||||
@@ -423,7 +395,7 @@ public:
|
||||
// Simple test for magic file in root
|
||||
getdisplay().setCursor(x0, y0 + 32);
|
||||
String file_magic = MOUNT_POINT "/magic.dat";
|
||||
logger->logDebug(GwLog::LOG, "Test magicfile: %s", file_magic.c_str());
|
||||
commonData->logger->logDebug(GwLog::LOG, "Test magicfile: %s", file_magic.c_str());
|
||||
struct stat st;
|
||||
if (stat(file_magic.c_str(), &st) == 0) {
|
||||
getdisplay().printf("File %s exists", file_magic.c_str());
|
||||
@@ -435,7 +407,7 @@ public:
|
||||
DIR* dir = opendir(MOUNT_POINT);
|
||||
int dy = 0;
|
||||
if (dir != NULL) {
|
||||
logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT);
|
||||
commonData->logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT);
|
||||
struct dirent* entry;
|
||||
while (((entry = readdir(dir)) != NULL) and (dy < 140)) {
|
||||
getdisplay().setCursor(x0, y0 + 64 + dy);
|
||||
@@ -445,28 +417,243 @@ public:
|
||||
getdisplay().print("/");
|
||||
}
|
||||
dy += 20;
|
||||
logger->logDebug(GwLog::DEBUG, " %s type %d", entry->d_name, entry->d_type);
|
||||
commonData->logger->logDebug(GwLog::DEBUG, " %s type %d", entry->d_name, entry->d_type);
|
||||
}
|
||||
closedir(dir);
|
||||
} else {
|
||||
logger->logDebug(GwLog::LOG, "Failed to open root directory");
|
||||
commonData->logger->logDebug(GwLog::LOG, "Failed to open root directory");
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
} else {
|
||||
void displayModeDevicelist() {
|
||||
// NMEA2000 device list
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||
getdisplay().setCursor(8, 48);
|
||||
getdisplay().print("NMEA2000 device list");
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
getdisplay().setCursor(20, 80);
|
||||
getdisplay().setCursor(20, 70);
|
||||
getdisplay().print("RxD: ");
|
||||
getdisplay().print(String(commonData->status.n2kRx));
|
||||
getdisplay().setCursor(20, 100);
|
||||
getdisplay().setCursor(120, 70);
|
||||
getdisplay().print("TxD: ");
|
||||
getdisplay().print(String(commonData->status.n2kTx));
|
||||
|
||||
#ifdef PATCH_N2K
|
||||
uint16_t x0 = 20;
|
||||
uint16_t y0 = 100;
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold10pt8b);
|
||||
getdisplay().setCursor(x0, y0);
|
||||
getdisplay().print("ID");
|
||||
getdisplay().setCursor(x0 + 50, y0);
|
||||
getdisplay().print("Model");
|
||||
getdisplay().setCursor(x0 + 250, y0);
|
||||
getdisplay().print("Manuf.");
|
||||
getdisplay().drawLine(18, y0 + 4, 360 , y0 + 4 , commonData->fgcolor);
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
y0 = 120;
|
||||
uint8_t n_dev = 0;
|
||||
for (const device& item : devicelist) {
|
||||
if (n_dev > 8) {
|
||||
break;
|
||||
}
|
||||
getdisplay().setCursor(x0, y0 + n_dev * 20);
|
||||
getdisplay().print(item.id);
|
||||
getdisplay().setCursor(x0 + 50, y0 + n_dev * 20);
|
||||
getdisplay().print(item.model);
|
||||
getdisplay().setCursor(x0 + 250, y0 + n_dev * 20);
|
||||
getdisplay().print(item.manuf_code);
|
||||
n_dev++;
|
||||
}
|
||||
getdisplay().setCursor(x0, y0 + (n_dev + 1) * 20);
|
||||
if (n_dev == 0) {
|
||||
getdisplay().printf("no devices found on bus");
|
||||
|
||||
} else {
|
||||
getdisplay().drawLine(18, y0 + n_dev * 20, 360 , y0 + n_dev * 20, commonData->fgcolor);
|
||||
getdisplay().printf("%d devices of %d in total", n_dev, devicelist.size());
|
||||
}
|
||||
#else
|
||||
getdisplay().setCursor(20, 100);
|
||||
getdisplay().print("NMEA2000 not exposed to obp60 task");
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
PageSystem(CommonData &common){
|
||||
commonData = &common;
|
||||
commonData->logger->logDebug(GwLog::LOG,"Instantiate PageSystem");
|
||||
if (hasFRAM) {
|
||||
mode = fram.read(FRAM_SYSTEM_MODE);
|
||||
commonData->logger->logDebug(GwLog::DEBUG, "Loaded mode '%c' from FRAM", mode);
|
||||
}
|
||||
chipid = ESP.getEfuseMac();
|
||||
simulation = common.config->getBool(common.config->useSimuData);
|
||||
#ifdef BOARD_OBP40S3
|
||||
use_sdcard = common.config->getBool(common.config->useSDCard);
|
||||
#endif
|
||||
buzzer_mode = common.config->getString(common.config->buzzerMode);
|
||||
buzzer_mode.toLowerCase();
|
||||
buzzer_power = common.config->getInt(common.config->buzzerPower);
|
||||
cpuspeed = common.config->getString(common.config->cpuSpeed);
|
||||
powermode = common.config->getString(common.config->powerMode);
|
||||
env_module = common.config->getString(common.config->useEnvSensor);
|
||||
rtc_module = common.config->getString(common.config->useRTC);
|
||||
gps_module = common.config->getString(common.config->useGPS);
|
||||
batt_sensor = common.config->getString(common.config->usePowSensor1);
|
||||
solar_sensor = common.config->getString(common.config->usePowSensor2);
|
||||
gen_sensor = common.config->getString(common.config->usePowSensor3);
|
||||
rot_sensor = common.config->getString(common.config->useRotSensor);
|
||||
homelat = common.config->getString(common.config->homeLAT).toDouble();
|
||||
homelon = common.config->getString(common.config->homeLON).toDouble();
|
||||
flashLED = common.config->getString(common.config->flashLED);
|
||||
}
|
||||
|
||||
void setupKeys() {
|
||||
commonData->keydata[0].label = "EXIT";
|
||||
commonData->keydata[1].label = "MODE";
|
||||
commonData->keydata[2].label = "";
|
||||
commonData->keydata[3].label = "RST";
|
||||
commonData->keydata[4].label = "STBY";
|
||||
commonData->keydata[5].label = "ILUM";
|
||||
}
|
||||
|
||||
int handleKey(int key) {
|
||||
// do *NOT* handle key #1 this handled by obp60task as exit
|
||||
// Switch display mode
|
||||
commonData->logger->logDebug(GwLog::LOG, "System keyboard handler");
|
||||
if (key == 2) {
|
||||
incMode();
|
||||
if (hasFRAM) fram.write(FRAM_SYSTEM_MODE, mode);
|
||||
return 0;
|
||||
}
|
||||
#ifdef BOARD_OBP60S3
|
||||
// grab cursor key to disable page navigation
|
||||
if (key == 3) {
|
||||
return 0;
|
||||
}
|
||||
// soft reset
|
||||
if (key == 4) {
|
||||
ESP.restart();
|
||||
}
|
||||
// standby / deep sleep
|
||||
if (key == 5) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "System going into deep sleep mode...");
|
||||
deepSleep(*commonData);
|
||||
}
|
||||
// Code for keylock
|
||||
if (key == 11) {
|
||||
commonData->keylock = !commonData->keylock;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#ifdef BOARD_OBP40S3
|
||||
// use cursor keys for local mode navigation
|
||||
if (key == 9) {
|
||||
incMode();
|
||||
return 0;
|
||||
}
|
||||
if (key == 10) {
|
||||
decMode();
|
||||
return 0;
|
||||
}
|
||||
// standby / deep sleep
|
||||
if (key == 12) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "System going into deep sleep mode...");
|
||||
deepSleep(*commonData);
|
||||
}
|
||||
#endif
|
||||
return key;
|
||||
}
|
||||
|
||||
void displayBarcode(String serialno, uint16_t x, uint16_t y, uint16_t s) {
|
||||
// Barcode with serial number
|
||||
// x, y is top left corner
|
||||
// s is pixel size of a single box
|
||||
QRCode qrcode;
|
||||
uint8_t qrcodeData[qrcode_getBufferSize(4)];
|
||||
#ifdef BOARD_OBP40S3
|
||||
String prefix = "OBP40:SN:";
|
||||
#endif
|
||||
#ifdef BOARD_OBP60S3
|
||||
String prefix = "OBP60:SN:";
|
||||
#endif
|
||||
qrcode_initText(&qrcode, qrcodeData, 4, 0, (prefix + serialno).c_str());
|
||||
int16_t x0 = x;
|
||||
for (uint8_t j = 0; j < qrcode.size; j++) {
|
||||
for (uint8_t i = 0; i < qrcode.size; i++) {
|
||||
if (qrcode_getModule(&qrcode, i, j)) {
|
||||
getdisplay().fillRect(x, y, s, s, commonData->fgcolor);
|
||||
}
|
||||
x += s;
|
||||
}
|
||||
y += s;
|
||||
x = x0;
|
||||
}
|
||||
}
|
||||
|
||||
void displayNew(PageData &pageData) {
|
||||
#ifdef BOARD_OBP60S3
|
||||
// Clear optical warning
|
||||
if (flashLED == "Limit Violation") {
|
||||
setBlinkingLED(false);
|
||||
setFlashLED(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PATCH_N2K
|
||||
// load current device list
|
||||
tN2kDeviceList *pDevList = pageData.api->getN2kDeviceList();
|
||||
// TODO check if changed
|
||||
if (pDevList->ReadResetIsListUpdated()) {
|
||||
// only reload if changed
|
||||
devicelist.clear();
|
||||
for (uint8_t i = 0; i <= 252; i++) {
|
||||
const tNMEA2000::tDevice *d = pDevList->FindDeviceBySource(i);
|
||||
if (d == nullptr) {
|
||||
continue;
|
||||
}
|
||||
device dev;
|
||||
dev.id = i;
|
||||
dev.NAME = d->GetName();
|
||||
snprintf(dev.hex_name, sizeof(dev.hex_name), "%08X%08X", (uint32_t)(dev.NAME >> 32), (uint32_t)(dev.NAME & 0xFFFFFFFF));
|
||||
dev.manuf_code = d->GetManufacturerCode();
|
||||
dev.model = d->GetModelID();
|
||||
devicelist.push_back(dev);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
int displayPage(PageData &pageData){
|
||||
|
||||
// Logging page information
|
||||
commonData->logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
|
||||
|
||||
// Set display in partial refresh mode
|
||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||
|
||||
// call current system page
|
||||
switch (mode) {
|
||||
case 'N':
|
||||
displayModeNormal();
|
||||
break;
|
||||
case 'S':
|
||||
displayModeSettings();
|
||||
break;
|
||||
case 'C':
|
||||
displayModeConfig();
|
||||
break;
|
||||
case 'A':
|
||||
displayModeSDCard();
|
||||
break;
|
||||
case 'D':
|
||||
displayModeDevicelist();
|
||||
break;
|
||||
}
|
||||
|
||||
// Update display
|
||||
|
||||
@@ -149,7 +149,11 @@ public:
|
||||
|
||||
if (dataHstryBuf[0] || dataHstryBuf[1]) { // show "Mode" key only if at least 1 chart supported boat data type is available
|
||||
commonData->keydata[0].label = "MODE";
|
||||
if (pageMode != VALUES) { // show "ZOOM" key only if chart is visible
|
||||
commonData->keydata[ZOOM_KEY].label = "ZOOM";
|
||||
} else {
|
||||
commonData->keydata[ZOOM_KEY].label = "";
|
||||
}
|
||||
} else {
|
||||
commonData->keydata[0].label = "";
|
||||
commonData->keydata[ZOOM_KEY].label = "";
|
||||
@@ -191,14 +195,15 @@ public:
|
||||
pageMode = VALUES;
|
||||
break;
|
||||
}
|
||||
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||
return 0; // Commit the key
|
||||
}
|
||||
|
||||
// Set time frame to show for history chart
|
||||
// Set time frame to show for chart
|
||||
#if defined BOARD_OBP60S3
|
||||
if (key == 5) {
|
||||
if (key == 5 && pageMode != VALUES) {
|
||||
#elif defined BOARD_OBP40S3
|
||||
if (key == 2) {
|
||||
if (key == 2 && pageMode != VALUES) {
|
||||
#endif
|
||||
if (dataIntv == 1) {
|
||||
dataIntv = 2;
|
||||
@@ -251,7 +256,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
setupKeys(); // adjust <mode> key depending on chart supported boat data type
|
||||
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||
}
|
||||
|
||||
int displayPage(PageData& pageData)
|
||||
@@ -285,13 +290,13 @@ public:
|
||||
showData(bValue, FULL);
|
||||
|
||||
} else if (pageMode == VAL1_CHART) { // show data value 1 and chart
|
||||
showData({bValue[0]}, HALF);
|
||||
showData({ bValue[0] }, HALF);
|
||||
if (dataChart[0]) {
|
||||
dataChart[0]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[0]);
|
||||
}
|
||||
|
||||
} else if (pageMode == VAL2_CHART) { // show data value 2 and chart
|
||||
showData({bValue[1]}, HALF);
|
||||
showData({ bValue[1] }, HALF);
|
||||
if (dataChart[1]) {
|
||||
dataChart[1]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[1]);
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ public:
|
||||
int displayPage(PageData& pageData)
|
||||
{
|
||||
LOG_DEBUG(GwLog::LOG, "Display PageWindPlot");
|
||||
ulong pageTime = millis();
|
||||
// ulong pageTime = millis();
|
||||
|
||||
if (showTruW != oldShowTruW) {
|
||||
|
||||
@@ -243,7 +243,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: page time %ldms", millis() - pageTime);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: page time %ldms", millis() - pageTime);
|
||||
return PAGE_UPDATE;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ typedef struct{
|
||||
double rotationAngle = 0; // Rotation angle in radiant
|
||||
bool validRotAngle = false; // Valid flag magnet present for rotation sensor
|
||||
struct tm rtcTime; // UTC time from internal RTC
|
||||
bool rtcValid = false;
|
||||
bool rtcValid = false; // Internal RTC chip
|
||||
int sunsetHour = 0;
|
||||
int sunsetMinute = 0;
|
||||
int sunriseHour = 0;
|
||||
|
||||
@@ -1245,8 +1245,8 @@
|
||||
"name": "timeSource",
|
||||
"label": "Status Time Source",
|
||||
"type": "list",
|
||||
"default": "GPS",
|
||||
"description": "Data source for date and time display in status line [RTC|iRTC|GPS]",
|
||||
"default": "iRTC",
|
||||
"description": "Data source for date and time display in status line [iRTC|RTC|GPS]",
|
||||
"list": [
|
||||
{"l":"Internal real time clock (iRTC)","v":"iRTC"},
|
||||
{"l":"External real time clock (RTC)","v":"RTC"},
|
||||
@@ -1517,6 +1517,7 @@
|
||||
"default": "Voltage",
|
||||
"description": "Type of page for page 1",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -1818,7 +1819,7 @@
|
||||
"description": "Wind source for page 1: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 1",
|
||||
"capabilities": {
|
||||
@@ -1847,6 +1848,7 @@
|
||||
"default": "WindRose",
|
||||
"description": "Type of page for page 2",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2140,7 +2142,7 @@
|
||||
"description": "Wind source for page 2: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 2",
|
||||
"capabilities": {
|
||||
@@ -2168,6 +2170,7 @@
|
||||
"default": "OneValue",
|
||||
"description": "Type of page for page 3",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2453,7 +2456,7 @@
|
||||
"description": "Wind source for page 3: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 3",
|
||||
"capabilities": {
|
||||
@@ -2480,6 +2483,7 @@
|
||||
"default": "TwoValues",
|
||||
"description": "Type of page for page 4",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2757,7 +2761,7 @@
|
||||
"description": "Wind source for page 4: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 4",
|
||||
"capabilities": {
|
||||
@@ -2783,6 +2787,7 @@
|
||||
"default": "ThreeValues",
|
||||
"description": "Type of page for page 5",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3052,7 +3057,7 @@
|
||||
"description": "Wind source for page 5: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 5",
|
||||
"capabilities": {
|
||||
@@ -3077,6 +3082,7 @@
|
||||
"default": "FourValues",
|
||||
"description": "Type of page for page 6",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3338,7 +3344,7 @@
|
||||
"description": "Wind source for page 6: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 6",
|
||||
"capabilities": {
|
||||
@@ -3362,6 +3368,7 @@
|
||||
"default": "FourValues2",
|
||||
"description": "Type of page for page 7",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3615,7 +3622,7 @@
|
||||
"description": "Wind source for page 7: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 7",
|
||||
"capabilities": {
|
||||
@@ -3638,6 +3645,7 @@
|
||||
"default": "Clock",
|
||||
"description": "Type of page for page 8",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3883,7 +3891,7 @@
|
||||
"description": "Wind source for page 8: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 8",
|
||||
"capabilities": {
|
||||
@@ -3905,6 +3913,7 @@
|
||||
"default": "RollPitch",
|
||||
"description": "Type of page for page 9",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -4142,7 +4151,7 @@
|
||||
"description": "Wind source for page 9: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 9",
|
||||
"capabilities": {
|
||||
@@ -4163,6 +4172,7 @@
|
||||
"default": "Battery2",
|
||||
"description": "Type of page for page 10",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -4392,7 +4402,7 @@
|
||||
"description": "Wind source for page 10: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 10",
|
||||
"capabilities": {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"name": "homeLAT",
|
||||
"label": "Home latitude",
|
||||
"type": "number",
|
||||
"default": "",
|
||||
"default": "0.00000",
|
||||
"check": "checkMinMax",
|
||||
"min": -90.0,
|
||||
"max": 90.0,
|
||||
@@ -51,7 +51,7 @@
|
||||
"name": "homeLON",
|
||||
"label": "Home longitude",
|
||||
"type": "number",
|
||||
"default": "",
|
||||
"default": "0.00000",
|
||||
"check": "checkMinMax",
|
||||
"min": -180.0,
|
||||
"max": 180.0,
|
||||
@@ -1235,8 +1235,9 @@
|
||||
"label": "Status Time Source",
|
||||
"type": "list",
|
||||
"default": "GPS",
|
||||
"description": "Data source for date and time display in status line [RTC|GPS]",
|
||||
"description": "Data source for date and time display in status line [iRTC|RTC|GPS]",
|
||||
"list": [
|
||||
{"l":"Internal real time clock (iRTC)","v":"iRTC"},
|
||||
{"l":"Real time clock (RTC)","v":"RTC"},
|
||||
{"l":"Time via bus (GPS)","v":"GPS"}
|
||||
],
|
||||
@@ -1494,6 +1495,7 @@
|
||||
"default": "Voltage",
|
||||
"description": "Type of page for page 1",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -1794,6 +1796,7 @@
|
||||
"default": "WindRose",
|
||||
"description": "Type of page for page 2",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2086,6 +2089,7 @@
|
||||
"default": "OneValue",
|
||||
"description": "Type of page for page 3",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2370,6 +2374,7 @@
|
||||
"default": "TwoValues",
|
||||
"description": "Type of page for page 4",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2646,6 +2651,7 @@
|
||||
"default": "ThreeValues",
|
||||
"description": "Type of page for page 5",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2914,6 +2920,7 @@
|
||||
"default": "FourValues",
|
||||
"description": "Type of page for page 6",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3174,6 +3181,7 @@
|
||||
"default": "FourValues2",
|
||||
"description": "Type of page for page 7",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3426,6 +3434,7 @@
|
||||
"default": "Clock",
|
||||
"description": "Type of page for page 8",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3670,6 +3679,7 @@
|
||||
"default": "RollPitch",
|
||||
"description": "Type of page for page 9",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3906,6 +3916,7 @@
|
||||
"default": "Battery2",
|
||||
"description": "Type of page for page 10",
|
||||
"list": [
|
||||
"Autopilot",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -1,12 +1,29 @@
|
||||
# PlatformIO extra script for obp60task
|
||||
|
||||
import subprocess
|
||||
|
||||
def cleanup_patches(source, target, env):
|
||||
for p in patchfiles:
|
||||
patch = os.path.join(patchdir, p)
|
||||
print(f"removing {patch}")
|
||||
res = subprocess.run(["git", "apply", "-R", patch], capture_output=True, text=True)
|
||||
if res.returncode != 0:
|
||||
print(res.stderr)
|
||||
|
||||
patching = False
|
||||
|
||||
epdtype = "unknown"
|
||||
pcbvers = "unknown"
|
||||
for x in env["BUILD_FLAGS"]:
|
||||
if x.startswith("-D HARDWARE_"):
|
||||
if not x.startswith('-D'):
|
||||
continue
|
||||
opt = x[2:].strip()
|
||||
if opt.startswith("HARDWARE_"):
|
||||
pcbvers = x.split('_')[1]
|
||||
if x.startswith("-D DISPLAY_"):
|
||||
elif opt.startswith("DISPLAY_"):
|
||||
epdtype = x.split('_')[1]
|
||||
elif opt == 'ENABLE_PATCHES':
|
||||
patching = True
|
||||
|
||||
propfilename = os.path.join(env["PROJECT_LIBDEPS_DIR"], env["PIOENV"], "GxEPD2/library.properties")
|
||||
properties = {}
|
||||
@@ -28,3 +45,22 @@ except:
|
||||
env["CPPDEFINES"].extend([("BOARD", env["BOARD"]), ("EPDTYPE", epdtype), ("PCBVERS", pcbvers), ("GXEPD2VERS", gxepd2vers)])
|
||||
|
||||
print("added hardware info to CPPDEFINES")
|
||||
|
||||
if patching:
|
||||
# apply patches to gateway code
|
||||
print("applying gateway patches")
|
||||
patchdir = os.path.join(os.path.dirname(script), "patches")
|
||||
if not os.path.isdir(patchdir):
|
||||
print("patchdir not found, no patches applied")
|
||||
else:
|
||||
patchfiles = [f for f in os.listdir(patchdir)]
|
||||
if len(patchfiles) > 0:
|
||||
for p in patchfiles:
|
||||
patch = os.path.join(patchdir, p)
|
||||
print(f"applying {patch}")
|
||||
res = subprocess.run(["git", "apply", patch], capture_output=True, text=True)
|
||||
if res.returncode != 0:
|
||||
print(res.stderr)
|
||||
env.AddPostAction("$PROGPATH", cleanup_patches)
|
||||
else:
|
||||
print("no patches found")
|
||||
|
||||
@@ -332,7 +332,7 @@ void OBP60Task(GwApi *api){
|
||||
// return;
|
||||
GwLog *logger=api->getLogger();
|
||||
GwConfigHandler *config=api->getConfig();
|
||||
#ifdef HARDWARE_V21
|
||||
#if defined HARDWARE_V20 || HARDWARE_V21
|
||||
startLedTask(api);
|
||||
#endif
|
||||
PageList allPages;
|
||||
@@ -341,7 +341,7 @@ void OBP60Task(GwApi *api){
|
||||
commonData.logger=logger;
|
||||
commonData.config=config;
|
||||
|
||||
#ifdef HARDWARE_V21
|
||||
#if defined HARDWARE_V20 || HARDWARE_V21
|
||||
// Keyboard coordinates for page footer
|
||||
initKeys(commonData);
|
||||
#endif
|
||||
@@ -432,7 +432,7 @@ void OBP60Task(GwApi *api){
|
||||
#endif
|
||||
LOG_DEBUG(GwLog::LOG,"...done");
|
||||
|
||||
int lastPage=-1; // initialize with an impiossible value, so we can detect wether we are during startup and no page has been displayed yet
|
||||
int lastPage=-1; // initialize with an impossible value, so we can detect wether we are during startup and no page has been displayed yet
|
||||
|
||||
BoatValueList boatValues; //all the boat values for the api query
|
||||
HstryBuffers hstryBufferList(1920, &boatValues, logger); // Create empty list of boat data history buffers (1.920 values = seconds = 32 min.)
|
||||
@@ -729,7 +729,7 @@ void OBP60Task(GwApi *api){
|
||||
else{
|
||||
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
||||
#ifdef DISPLAY_GDEY042T81
|
||||
getdisplay().hibernate(); // Set display in hybenate mode
|
||||
getdisplay().hibernate(); // Set display in hibenate mode
|
||||
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
|
||||
#else
|
||||
getdisplay().init(115200); // Init for normal displays
|
||||
@@ -757,7 +757,7 @@ void OBP60Task(GwApi *api){
|
||||
else{
|
||||
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
||||
#ifdef DISPLAY_GDEY042T81
|
||||
getdisplay().hibernate(); // Set display in hybenate mode
|
||||
getdisplay().hibernate(); // Set display in hibernate mode
|
||||
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
|
||||
#else
|
||||
getdisplay().init(115200); // Init for normal displays
|
||||
@@ -782,7 +782,7 @@ void OBP60Task(GwApi *api){
|
||||
else{
|
||||
getdisplay().fillScreen(commonData.fgcolor); // Clear display
|
||||
#ifdef DISPLAY_GDEY042T81
|
||||
getdisplay().hibernate(); // Set display in hybenate mode
|
||||
getdisplay().hibernate(); // Set display in hibernate mode
|
||||
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
|
||||
#else
|
||||
getdisplay().init(115200); // Init for normal displays
|
||||
|
||||
103
lib/obp60task/patches/01-nmea2000.patch
Normal file
103
lib/obp60task/patches/01-nmea2000.patch
Normal file
@@ -0,0 +1,103 @@
|
||||
diff --git a/lib/api/GwApi.h b/lib/api/GwApi.h
|
||||
index 88f9690..9663a65 100644
|
||||
--- a/lib/api/GwApi.h
|
||||
+++ b/lib/api/GwApi.h
|
||||
@@ -2,6 +2,8 @@
|
||||
#define _GWAPI_H
|
||||
#include "GwMessage.h"
|
||||
#include "N2kMsg.h"
|
||||
+#include "Nmea2kTwai.h"
|
||||
+#include "N2kDeviceList.h"
|
||||
#include "NMEA0183Msg.h"
|
||||
#include "GWConfig.h"
|
||||
#include "GwBoatData.h"
|
||||
@@ -222,6 +224,8 @@ class GwApi{
|
||||
* accessing boat data must only be executed from within the main thread
|
||||
* you need to use the request pattern as shown in GwExampleTask.cpp
|
||||
*/
|
||||
+ virtual Nmea2kTwai *getNMEA2000()=0;
|
||||
+ virtual tN2kDeviceList *getN2kDeviceList()=0;
|
||||
virtual GwBoatData *getBoatData()=0;
|
||||
virtual ~GwApi(){}
|
||||
};
|
||||
diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h
|
||||
index 604c356..2fe4496 100644
|
||||
--- a/lib/obp60task/OBP60Extensions.h
|
||||
+++ b/lib/obp60task/OBP60Extensions.h
|
||||
@@ -15,6 +15,9 @@
|
||||
#define MOUNT_POINT "/sdcard"
|
||||
#endif
|
||||
|
||||
+// Patches to apply to gateway code
|
||||
+#define PATCH_N2K
|
||||
+
|
||||
// FRAM address reservations 32kB: 0x0000 - 0x7FFF
|
||||
// 0x0000 - 0x03ff: single variables
|
||||
#define FRAM_PAGE_NO 0x0002
|
||||
diff --git a/lib/usercode/GwUserCode.cpp b/lib/usercode/GwUserCode.cpp
|
||||
index 1b007f8..90087d4 100644
|
||||
--- a/lib/usercode/GwUserCode.cpp
|
||||
+++ b/lib/usercode/GwUserCode.cpp
|
||||
@@ -216,6 +216,14 @@ public:
|
||||
{
|
||||
return api->getLogger();
|
||||
}
|
||||
+ virtual Nmea2kTwai *getNMEA2000()
|
||||
+ {
|
||||
+ return api->getNMEA2000();
|
||||
+ }
|
||||
+ virtual tN2kDeviceList *getN2kDeviceList()
|
||||
+ {
|
||||
+ return api->getN2kDeviceList();
|
||||
+ }
|
||||
virtual GwBoatData *getBoatData()
|
||||
{
|
||||
return api->getBoatData();
|
||||
@@ -428,4 +436,4 @@ void GwUserCode::handleWebRequest(const String &url,AsyncWebServerRequest *req){
|
||||
}
|
||||
LOG_DEBUG(GwLog::DEBUG,"no task found for web request %s[%s]",url.c_str(),tname.c_str());
|
||||
req->send(404, "text/plain", "not found");
|
||||
-}
|
||||
\ No newline at end of file
|
||||
+}
|
||||
diff --git a/src/main.cpp b/src/main.cpp
|
||||
index 44c715f..fdb0366 100644
|
||||
--- a/src/main.cpp
|
||||
+++ b/src/main.cpp
|
||||
@@ -100,6 +100,7 @@ GwLog logger(LOGLEVEL,NULL);
|
||||
GwConfigHandler config(&logger);
|
||||
|
||||
#include "Nmea2kTwai.h"
|
||||
+#include <N2kDeviceList.h>
|
||||
static const unsigned long CAN_RECOVERY_PERIOD=3000; //ms
|
||||
static const unsigned long NMEA2000_HEARTBEAT_INTERVAL=5000;
|
||||
class Nmea2kTwaiLog : public Nmea2kTwai{
|
||||
@@ -126,6 +127,7 @@ class Nmea2kTwaiLog : public Nmea2kTwai{
|
||||
#endif
|
||||
|
||||
Nmea2kTwai &NMEA2000=*(new Nmea2kTwaiLog((gpio_num_t)ESP32_CAN_TX_PIN,(gpio_num_t)ESP32_CAN_RX_PIN,CAN_RECOVERY_PERIOD,&logger));
|
||||
+tN2kDeviceList *pN2kDeviceList;
|
||||
|
||||
#ifdef GWBUTTON_PIN
|
||||
bool fixedApPass=false;
|
||||
@@ -333,6 +335,12 @@ public:
|
||||
status.n2kTx=countNMEA2KOut.getGlobal();
|
||||
channels.fillStatus(status);
|
||||
}
|
||||
+ virtual Nmea2kTwai *getNMEA2000(){
|
||||
+ return &NMEA2000;
|
||||
+ }
|
||||
+ virtual tN2kDeviceList *getN2kDeviceList(){
|
||||
+ return pN2kDeviceList;
|
||||
+ }
|
||||
virtual GwBoatData *getBoatData(){
|
||||
return &boatData;
|
||||
}
|
||||
@@ -935,6 +943,7 @@ void setup() {
|
||||
NMEA2000.SetMsgHandler([](const tN2kMsg &n2kMsg){
|
||||
handleN2kMessage(n2kMsg,N2K_CHANNEL_ID);
|
||||
});
|
||||
+ pN2kDeviceList = new tN2kDeviceList(&NMEA2000);
|
||||
NMEA2000.Open();
|
||||
logger.logDebug(GwLog::LOG,"starting addon tasks");
|
||||
logger.flush();
|
||||
@@ -16,6 +16,8 @@ board_build.variants_dir = variants
|
||||
board = obp60_s3_n16r8 #ESP32-S3 N16R8, 16MB flash, 8MB PSRAM, production series
|
||||
#board_build.partitions = default_8MB.csv #ESP32-S3 N8, 8MB flash
|
||||
board_build.partitions = default_16MB.csv #ESP32-S3 N16, 16MB flash
|
||||
custom_config = lib/obp60task/config_obp60.json
|
||||
custom_script = lib/obp60task/extra_task.py
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
${basedeps.lib_deps}
|
||||
@@ -56,6 +58,7 @@ build_flags=
|
||||
# -D DISPLAY_GYE042A87 #alternativ E-Ink display from Genyo Optical, R10 2.2 ohm - medium
|
||||
# -D DISPLAY_SE0420NQ04 #alternativ E-Ink display from SID Technology, R10 2.2 ohm - bad (burn in effects)
|
||||
# -D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
|
||||
# -D ENABLE_PATCHES #enable patching of gateway code
|
||||
${env.build_flags}
|
||||
#CONFIG_ESP_TASK_WDT_TIMEOUT_S = 10 #Task Watchdog timeout period (seconds) [1...60] 5 default
|
||||
upload_port = /dev/ttyACM0 #OBP60 download via USB-C direct
|
||||
@@ -68,7 +71,8 @@ platform = espressif32@6.8.1
|
||||
board_build.variants_dir = variants
|
||||
board = obp40_s3_n8r8 #ESP32-S3 N8R8, 8MB flash, 8MB PSRAM, OBP60 clone (CrowPanel 4.2)
|
||||
board_build.partitions = default_8MB.csv #ESP32-S3 N8, 8MB flash
|
||||
custom_config = config_obp40.json
|
||||
custom_config = lib/obp60task/config_obp40.json
|
||||
custom_script = lib/obp60task/extra_task.py
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
${basedeps.lib_deps}
|
||||
@@ -105,6 +109,7 @@ build_flags=
|
||||
#-D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
|
||||
-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
|
||||
-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
|
||||
#-D ENABLE_PATCHES #enable patching of gateway code
|
||||
${env.build_flags}
|
||||
upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter
|
||||
upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
|
||||
# Install tools
|
||||
echo "Installing tools"
|
||||
cd /workspace/esp32-nmea2000
|
||||
cd /workspaces/esp32-nmea2000
|
||||
pip3 install -U esptool
|
||||
pip3 install platformio
|
||||
|
||||
Reference in New Issue
Block a user