Compare commits
120 Commits
1174622b4a
...
4aefc99212
Author | SHA1 | Date |
---|---|---|
|
4aefc99212 | |
![]() |
ae7130b86c | |
![]() |
7f9beb064a | |
![]() |
2095efbb01 | |
![]() |
d0e2fc1eac | |
![]() |
d1c620a858 | |
![]() |
07a1b2388e | |
![]() |
c614cae472 | |
![]() |
ec7e1a35e7 | |
![]() |
b82e94d716 | |
![]() |
a2c1c1042d | |
![]() |
77872fea4a | |
![]() |
8a069b9445 | |
![]() |
b3a9b9990f | |
![]() |
dc8b9e48d7 | |
|
c5a1244519 | |
|
99c3470a22 | |
|
f5cf292804 | |
|
77c05de4fc | |
|
1bac5d8b16 | |
|
c7eafbf9b8 | |
![]() |
4b5aa7ff4b | |
![]() |
ebf6e62389 | |
![]() |
29706df6cd | |
![]() |
d6e3c7ad48 | |
![]() |
02712263d3 | |
![]() |
0c4fce0e25 | |
![]() |
4e6d52d197 | |
![]() |
f9cf73ae04 | |
![]() |
bf59cfbae8 | |
![]() |
214c10ff93 | |
![]() |
e72ece8452 | |
![]() |
fca7a47728 | |
![]() |
10951d7f13 | |
![]() |
6dbbd13ead | |
![]() |
fb89dca3be | |
![]() |
3d31fcf4ed | |
![]() |
2c348ca7fb | |
![]() |
6ca40aaa8f | |
![]() |
7ced07d2d9 | |
![]() |
583fbd0db8 | |
![]() |
1862bdc6ae | |
![]() |
c0f36ecdf4 | |
![]() |
2fb59fb118 | |
![]() |
13d6091ae8 | |
![]() |
a5494ccee4 | |
![]() |
e2270d8ed2 | |
![]() |
309d55cdb4 | |
![]() |
0ec2f93915 | |
![]() |
f8b86e67df | |
![]() |
0f0a096219 | |
![]() |
d094bfc32a | |
![]() |
0044fdd3b6 | |
![]() |
57684f77db | |
![]() |
d228f38461 | |
![]() |
417fe3db08 | |
![]() |
44c9e998c9 | |
![]() |
c02122f253 | |
![]() |
30ade0c07a | |
![]() |
38d370dcfb | |
![]() |
52a376c43a | |
![]() |
8c035c4ba1 | |
![]() |
036add6feb | |
![]() |
a1f00dde82 | |
![]() |
756064a362 | |
![]() |
b640d1adda | |
![]() |
c5cadce268 | |
![]() |
293b362ee4 | |
![]() |
98c8d44d2f | |
![]() |
24af837148 | |
![]() |
7c9b615526 | |
![]() |
c55bcc1f94 | |
![]() |
a1b8f06959 | |
![]() |
437b76897a | |
![]() |
2ab6a00883 | |
|
5192e77fab | |
|
8439dcc18a | |
![]() |
5e41c64dd3 | |
![]() |
6a2c623ea0 | |
![]() |
d0256fd37c | |
![]() |
7cf1b0e6af | |
|
1aa18f3ec0 | |
|
00a8aff8ca | |
|
acf75495df | |
|
2a4c351c58 | |
|
e398c7bdce | |
|
eb3a0d5fc0 | |
![]() |
3412da8e18 | |
![]() |
9909bfdb4c | |
|
6f78fa5434 | |
![]() |
3395a63ba1 | |
|
775a22393d | |
|
31c968d0a8 | |
![]() |
175f525bcd | |
![]() |
ba6c1038af | |
![]() |
cbe06f65be | |
![]() |
e7d5ada610 | |
![]() |
38f1d5de44 | |
![]() |
93402841cb | |
|
f0cc5d9966 | |
![]() |
9dc857056b | |
![]() |
c202554c5c | |
![]() |
df81e6e443 | |
|
097111c270 | |
|
bdfaf3c886 | |
![]() |
26e551c616 | |
![]() |
0cfaf1e793 | |
![]() |
ea9a2ff9c4 | |
![]() |
f116e41964 | |
![]() |
cf7ef8d849 | |
![]() |
752388a6e7 | |
![]() |
0b7863cb86 | |
![]() |
e9ee49a6ef | |
![]() |
bba24ac2fc | |
![]() |
7afcb86404 | |
|
7be102127e | |
![]() |
fc851093a6 | |
![]() |
28e4fc0643 | |
![]() |
c85e575378 | |
![]() |
922247ed4a |
10
Readme.md
10
Readme.md
|
@ -68,12 +68,12 @@ Initial Flash
|
|||
__Browser__
|
||||
|
||||
If you run a system with a modern Chrome or Edge Browser you can directly flash your device from within the browser.
|
||||
Just go to the [Flash Page](https://wellenvogel.github.io/esp32-nmea2000/install.html) and select the "Initial" flash for your Hardware. This will install the most current software to your device.
|
||||
Just go to the [Flash Page](https://wellenvogel.de/software/esp32/install.html) and select the "Initial" flash for your Hardware. This will install the most current software to your device. If you are using a forked project (like OBP60) refer to the documentation of the fork. You can just install any flash binary from your local computer with the browser based installation using the "upload" button.<br>
|
||||
If you are on Windows you will need to have the correct driver installed before (see below at [windows users](#windows) - only install the driver, not the flashtool).
|
||||
|
||||
You can also install an update from the flash page but normally it is easier to do this from the Web Gui of the device (see [below](#update)).
|
||||
|
||||
The [Flash Page](https://wellenvogel.github.io/esp32-nmea2000/install.html) will also allow you to open a console window to your ESP32.
|
||||
The [Flash Page](https://wellenvogel.de/software/esp32/install.html) will also allow you to open a console window to your ESP32.
|
||||
|
||||
__Tool based__
|
||||
|
||||
|
@ -170,6 +170,12 @@ For details refer to the [example description](lib/exampletask/Readme.md).
|
|||
|
||||
Changelog
|
||||
---------
|
||||
[20250305](../../releases/tag/20250305)
|
||||
*********
|
||||
* better handling for reconnect to a raspberry pi after reset [#102](../../issues/102)
|
||||
* introduce _custom_config_, _custom_js_, _custom_css_, refer to [extending the core](lib/exampletask/Readme.md) [#100](../../pull/100)
|
||||
* create VWR [#103](../../issues/103)
|
||||
|
||||
[20241128](../../releases/tag/20241128)
|
||||
*********
|
||||
* additional correction for: USB connection on S3 stops [#81](../../issues/81)
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
"flash_mode": "qio",
|
||||
"hwids": [
|
||||
[
|
||||
"0x303A",
|
||||
"0x1001"
|
||||
"0x1A86",
|
||||
"0x7523"
|
||||
]
|
||||
],
|
||||
"mcu": "esp32s3",
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -10,7 +10,7 @@ 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())
|
||||
|
@ -104,7 +104,17 @@ def writeFileIfChanged(fileName,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)
|
||||
|
@ -274,9 +284,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
|
||||
|
|
|
@ -0,0 +1,538 @@
|
|||
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"])))
|
||||
|
||||
epdtype = "unknown"
|
||||
pcbvers = "unknown"
|
||||
for x in env["BUILD_FLAGS"]:
|
||||
if x.startswith("-D HARDWARE_"):
|
||||
pcbvers = x.split('_')[1]
|
||||
if x.startswith("-D DISPLAY_"):
|
||||
epdtype = x.split('_')[1]
|
||||
|
||||
env.Append(
|
||||
LINKFLAGS=[ "-u", "custom_app_desc" ],
|
||||
CPPDEFINES=[(board,"1"), ("BOARD", env["BOARD"]), ("EPDTYPE", epdtype),
|
||||
("PCBVERS", pcbvers)]
|
||||
)
|
||||
#script does not run on clean yet - maybe in the future
|
||||
env.AddPostAction("clean",cleangenerated)
|
|
@ -0,0 +1,518 @@
|
|||
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)
|
|
@ -493,6 +493,11 @@ double formatKnots(double cv)
|
|||
return cv * 3600.0 / 1852.0;
|
||||
}
|
||||
|
||||
double formatKmh(double cv)
|
||||
{
|
||||
return cv *3600.0 / 1000.0;
|
||||
}
|
||||
|
||||
uint32_t mtr2nm(uint32_t m)
|
||||
{
|
||||
return m / 1852;
|
||||
|
|
|
@ -129,6 +129,7 @@ double formatCourse(double cv);
|
|||
double formatDegToRad(double deg);
|
||||
double formatWind(double cv);
|
||||
double formatKnots(double cv);
|
||||
double formatKmh(double cv);
|
||||
uint32_t mtr2nm(uint32_t m);
|
||||
double mtr2nm(double m);
|
||||
|
||||
|
|
|
@ -27,18 +27,19 @@ class DummyConfig : public GwConfigInterface{
|
|||
};
|
||||
|
||||
DummyConfig dummyConfig;
|
||||
String GwConfigHandler::toString() const{
|
||||
String rt;
|
||||
rt+="Config: ";
|
||||
for (int i=0;i<getNumConfig();i++){
|
||||
rt+=configs[i]->getName();
|
||||
rt+="=";
|
||||
rt+=configs[i]->asString();
|
||||
rt+=", ";
|
||||
}
|
||||
return rt;
|
||||
void GwConfigHandler::logConfig(int level) const
|
||||
{
|
||||
if (!logger->isActive(level))
|
||||
return;
|
||||
for (int i = 0; i < getNumConfig(); i++)
|
||||
{
|
||||
String v=configs[i]->asString();
|
||||
bool isChanged=v != configs[i]->getDefault();
|
||||
logger->logDebug(level, "Config[%s]%s='%s'", configs[i]->getName().c_str(),isChanged?"*":"", configs[i]->isSecret() ? "***" : configs[i]->asString().c_str());
|
||||
if ((i%20) == 19) logger->flush();
|
||||
}
|
||||
|
||||
logger->flush();
|
||||
}
|
||||
String GwConfigHandler::toJson() const{
|
||||
String rt;
|
||||
int num=getNumConfig();
|
||||
|
@ -80,6 +81,9 @@ GwConfigHandler::~GwConfigHandler(){
|
|||
bool GwConfigHandler::loadConfig(){
|
||||
prefs->begin(PREF_NAME,true);
|
||||
for (int i=0;i<getNumConfig();i++){
|
||||
if (!prefs->isKey(configs[i]->getName().c_str())) {
|
||||
continue;
|
||||
}
|
||||
String v=prefs->getString(configs[i]->getName().c_str(),configs[i]->getDefault());
|
||||
configs[i]->value=v;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ class GwConfigHandler: public GwConfigDefinitions{
|
|||
void stopChanges();
|
||||
bool updateValue(String name, String value);
|
||||
bool reset();
|
||||
String toString() const;
|
||||
void logConfig(int level) const;
|
||||
String toJson() const;
|
||||
String getString(const String name,const String defaultv="") const;
|
||||
bool getBool(const String name,bool defaultv=false) const ;
|
||||
|
|
|
@ -14,7 +14,7 @@ Files
|
|||
* [platformio.ini](platformio.ini)<br>
|
||||
This file is completely optional.
|
||||
You only need this if you want to
|
||||
extend the base configuration - we add a dummy library here and define one additional build environment (board)
|
||||
extend the base configuration - we add a dummy library here and define additional build environments (boards)
|
||||
* [GwExampleTask.h](GwExampleTask.h) the name of this include must match the name of the directory (ignoring case) with a "gw" in front. This file includes our special hardware definitions and registers our task at the core.<br>
|
||||
This registration can be done statically using [DECLARE_USERTASK](https://github.com/wellenvogel/esp32-nmea2000/blob/9b955d135d74937a60f2926e8bfb9395585ff8cd/lib/api/GwApi.h#L202) in the header file. <br>
|
||||
As an alternative we just only register an [initialization function](https://github.com/wellenvogel/esp32-nmea2000/blob/9b955d135d74937a60f2926e8bfb9395585ff8cd/lib/exampletask/GwExampleTask.h#L19) using DECLARE_INITFUNCTION and later on register the task function itself via the [API](https://github.com/wellenvogel/esp32-nmea2000/blob/9b955d135d74937a60f2926e8bfb9395585ff8cd/lib/exampletask/GwExampleTask.cpp#L32).<br>
|
||||
|
@ -28,11 +28,13 @@ Files
|
|||
|
||||
* [GwExampleTaks.cpp](GwExampleTask.cpp) includes the implementation of our task. This tasks runs in an own thread - see the comments in the code.
|
||||
We can have as many cpp (and header files) as we need to structure our code.
|
||||
* [config.json](config.json)<br>
|
||||
* [config.json](exampleConfig.json)<br>
|
||||
This file allows to add some config definitions that are needed for our task. For the possible options have a look at the global [config.json](../../web/config.json). Be careful not to overwrite config defitions from the global file. A good practice wood be to prefix the names of definitions with parts of the library name. Always put them in a separate category so that they do not interfere with the system ones.
|
||||
The defined config items can later be accessed in the code (see the example in [GwExampleTask.cpp](GwExampleTask.cpp)).
|
||||
The defined config items can later be accessed in the code (see the example in [GwExampleTask.cpp](GwExampleTask.cpp)).<br>
|
||||
|
||||
* [index.js](index.js)<br>
|
||||
Starting from Version 20250305 you should normally not use this file name any more as those configs would be added for all build environments. Instead define a parameter _custom_config_ in your [platformio.ini](platformio.ini) for the environments you would like to add some configurations for. This parameter accepts a list of file names (relative to the project root, separated by ,).
|
||||
|
||||
* [index.js](example.js)<br>
|
||||
You can add javascript code that will contribute to the UI of the system. The WebUI provides a small API that allows you to "hook" into some functions to include your own parts of the UI. This includes adding new tabs, modifying/replacing the data display items, modifying the status display or accessing the config items.
|
||||
For the API refer to [../../web/index.js](../../web/index.js#L2001).
|
||||
To start interacting just register for some events like api.EVENTS.init. You can check the capabilities you have defined to see if your task is active.
|
||||
|
@ -46,10 +48,14 @@ Files
|
|||
tools/testServer.py nnn http://x.x.x.x/api
|
||||
```
|
||||
with nnn being the local port and x.x.x.x the address of a running system. Open `http://localhost:nnn` in your browser.<br>
|
||||
After a change just start the compilation and reload the page.
|
||||
After a change just start the compilation and reload the page.<br>
|
||||
|
||||
Starting from Version 20250305 you should normally not use this file name any more as those js code would be added for all build environments. Instead define a parameter _custom_js_ in your [platformio.ini](platformio.ini) for the environments you would like to add the js code for. This parameter accepts a list of file names (relative to the project root, separated by ,). This will also allow you to skip the check for capabilities in your code.
|
||||
|
||||
* [index.css](index.css)<br>
|
||||
You can add own css to influence the styling of the display.
|
||||
You can add own css to influence the styling of the display.<br>
|
||||
|
||||
Starting from Version 20250305 you should normally not use this file name any more as those styles would be added for all build environments. Instead define a parameter _custom_css_ in your [platformio.ini](platformio.ini) for the environments you would like to add some styles for. This parameter accepts a list of file names (relative to the project root, separated by , or as multi line entry)
|
||||
|
||||
|
||||
Interfaces
|
||||
|
|
|
@ -10,5 +10,9 @@ lib_deps =
|
|||
build_flags=
|
||||
-D BOARD_TEST
|
||||
${env.build_flags}
|
||||
custom_config=
|
||||
lib/exampletask/exampleConfig.json
|
||||
custom_js=lib/exampletask/example.js
|
||||
custom_css=lib/exampletask/example.css
|
||||
upload_port = /dev/esp32
|
||||
upload_protocol = esptool
|
|
@ -85,6 +85,7 @@ bool GwWifi::connectInternal(){
|
|||
if (wifiClient->asBoolean()){
|
||||
clientIsConnected=false;
|
||||
LOG_DEBUG(GwLog::LOG,"creating wifiClient ssid=%s",wifiSSID->asString().c_str());
|
||||
WiFi.setAutoReconnect(false); //#102
|
||||
wl_status_t rt=WiFi.begin(wifiSSID->asCString(),wifiPass->asCString());
|
||||
LOG_DEBUG(GwLog::LOG,"wifiClient connect returns %d",(int)rt);
|
||||
lastConnectStart=millis();
|
||||
|
@ -92,7 +93,8 @@ bool GwWifi::connectInternal(){
|
|||
}
|
||||
return false;
|
||||
}
|
||||
#define RETRY_MILLIS 20000
|
||||
//#102: we should have a wifi connect retry being > 30s - with some headroom
|
||||
#define RETRY_MILLIS 40000
|
||||
void GwWifi::loop(){
|
||||
if (wifiClient->asBoolean())
|
||||
{
|
||||
|
|
|
@ -351,8 +351,8 @@ private:
|
|||
rmb.vmg
|
||||
);
|
||||
send(n2kMsg,msg.sourceId);
|
||||
SetN2kPGN129285(n2kMsg,sourceId,1,1,true,true,"default");
|
||||
AppendN2kPGN129285(n2kMsg,destinationId,rmb.destID,rmb.latitude,rmb.longitude);
|
||||
SetN2kRouteWPInfo(n2kMsg,sourceId,1,1,N2kdir_forward,"default");
|
||||
AppendN2kRouteWPInfo(n2kMsg,destinationId,rmb.destID,rmb.latitude,rmb.longitude);
|
||||
send(n2kMsg,msg.sourceId);
|
||||
}
|
||||
}
|
||||
|
@ -638,8 +638,8 @@ private:
|
|||
for (int i=0;i< 3;i++){
|
||||
if (msg.FieldLen(0)>0){
|
||||
Depth=atof(msg.Field(0));
|
||||
char dt=msg.Field(i+1)[0];
|
||||
switch(dt){
|
||||
char du=msg.Field(i+1)[0];
|
||||
switch(du){
|
||||
case 'f':
|
||||
Depth=Depth/mToFeet;
|
||||
break;
|
||||
|
@ -662,8 +662,9 @@ private:
|
|||
//we can only send if we have a valid depth beloww tranducer
|
||||
//to compute the offset
|
||||
if (! boatData->DBT->isValid()) return;
|
||||
double offset=Depth-boatData->DBT->getData();
|
||||
if (offset >= 0 && dt == DBT){
|
||||
double dbt=boatData->DBT->getData();
|
||||
double offset=Depth-dbt;
|
||||
if (offset >= 0 && dt == DBK){
|
||||
logger->logDebug(GwLog::DEBUG, "strange DBK - more depth then transducer %s", msg.line);
|
||||
return;
|
||||
}
|
||||
|
@ -675,8 +676,8 @@ private:
|
|||
if (! boatData->DBS->update(Depth,msg.sourceId)) return;
|
||||
}
|
||||
tN2kMsg n2kMsg;
|
||||
SetN2kWaterDepth(n2kMsg,1,Depth,offset);
|
||||
send(n2kMsg,msg.sourceId,(n2kMsg.PGN)+String((offset != N2kDoubleNA)?1:0));
|
||||
SetN2kWaterDepth(n2kMsg,1,dbt,offset); //on the N2K side we always have depth below transducer
|
||||
send(n2kMsg,msg.sourceId,(n2kMsg.PGN)+String((offset >=0)?1:0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -267,21 +267,29 @@ private:
|
|||
double DepthBelowTransducer;
|
||||
double Offset;
|
||||
double Range;
|
||||
double WaterDepth;
|
||||
if (ParseN2kWaterDepth(N2kMsg, SID, DepthBelowTransducer, Offset, Range))
|
||||
{
|
||||
|
||||
WaterDepth = DepthBelowTransducer + Offset;
|
||||
updateDouble(boatData->DBS, WaterDepth);
|
||||
updateDouble(boatData->DBT,DepthBelowTransducer);
|
||||
tNMEA0183Msg NMEA0183Msg;
|
||||
if (NMEA0183SetDPT(NMEA0183Msg, DepthBelowTransducer, Offset,talkerId))
|
||||
if (updateDouble(boatData->DBT, DepthBelowTransducer))
|
||||
{
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
if (NMEA0183SetDBx(NMEA0183Msg, DepthBelowTransducer, Offset,talkerId))
|
||||
{
|
||||
SendMessage(NMEA0183Msg);
|
||||
tNMEA0183Msg NMEA0183Msg;
|
||||
bool offsetValid=true;
|
||||
if (N2kIsNA(Offset)) {
|
||||
Offset=NMEA0183DoubleNA;
|
||||
offsetValid=false;
|
||||
}
|
||||
if (NMEA0183SetDPT(NMEA0183Msg, DepthBelowTransducer, Offset, talkerId))
|
||||
{
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
if (offsetValid)
|
||||
{
|
||||
double WaterDepth = DepthBelowTransducer + Offset;
|
||||
updateDouble(boatData->DBS, WaterDepth);
|
||||
}
|
||||
if (NMEA0183SetDBx(NMEA0183Msg, DepthBelowTransducer, Offset, talkerId))
|
||||
{
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -528,6 +536,31 @@ private:
|
|||
{
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
|
||||
if (shouldSend && NMEA0183Reference == NMEA0183Wind_Apparent)
|
||||
{
|
||||
double wa = formatCourse(WindAngle);
|
||||
if (!NMEA0183Msg.Init("VWR", talkerId))
|
||||
return;
|
||||
if (!NMEA0183Msg.AddDoubleField(( wa > 180 ) ? 360-wa : wa))
|
||||
return;
|
||||
if (!NMEA0183Msg.AddStrField(( wa >= 0 && wa <= 180) ? 'R' : 'L'))
|
||||
return;
|
||||
if (!NMEA0183Msg.AddDoubleField(formatKnots(WindSpeed)))
|
||||
return;
|
||||
if (!NMEA0183Msg.AddStrField("N"))
|
||||
return;
|
||||
if (!NMEA0183Msg.AddDoubleField(WindSpeed))
|
||||
return;
|
||||
if (!NMEA0183Msg.AddStrField("M"))
|
||||
return;
|
||||
if (!NMEA0183Msg.AddDoubleField(formatKmh(WindSpeed)))
|
||||
return;
|
||||
if (!NMEA0183Msg.AddStrField("K"))
|
||||
return;
|
||||
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
}
|
||||
|
||||
/* if (WindReference == N2kWind_Apparent && boatData->SOG->isValid())
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||
|
||||
#include "BoatDataCalibration.h"
|
||||
#include <cmath>
|
||||
#include <math.h>
|
||||
#include <unordered_map>
|
||||
|
||||
CalibrationDataList calibrationData;
|
||||
std::unordered_map<std::string, TypeCalibData> CalibrationDataList::calibMap; // list of calibration data instances
|
||||
|
||||
void CalibrationDataList::readConfig(GwConfigHandler* config, GwLog* logger)
|
||||
// Initial load of calibration data into internal list
|
||||
// This method is called once at init phase of <obp60task> to read the configuration values
|
||||
{
|
||||
std::string instance;
|
||||
double offset;
|
||||
double slope;
|
||||
double smooth;
|
||||
|
||||
String calInstance = "";
|
||||
String calOffset = "";
|
||||
String calSlope = "";
|
||||
String calSmooth = "";
|
||||
|
||||
// Load user format configuration values
|
||||
String lengthFormat = config->getString(config->lengthFormat); // [m|ft]
|
||||
String distanceFormat = config->getString(config->distanceFormat); // [m|km|nm]
|
||||
String speedFormat = config->getString(config->speedFormat); // [m/s|km/h|kn]
|
||||
String windspeedFormat = config->getString(config->windspeedFormat); // [m/s|km/h|kn|bft]
|
||||
String tempFormat = config->getString(config->tempFormat); // [K|C|F]
|
||||
|
||||
// Read calibration settings for data instances
|
||||
for (int i = 0; i < MAX_CALIBRATION_DATA; i++) {
|
||||
calInstance = "calInstance" + String(i + 1);
|
||||
calOffset = "calOffset" + String(i + 1);
|
||||
calSlope = "calSlope" + String(i + 1);
|
||||
calSmooth = "calSmooth" + String(i + 1);
|
||||
|
||||
instance = std::string(config->getString(calInstance, "---").c_str());
|
||||
if (instance == "---") {
|
||||
LOG_DEBUG(GwLog::LOG, "no calibration data for instance no. %d", i + 1);
|
||||
continue;
|
||||
}
|
||||
calibMap[instance] = { 0.0f, 1.0f, 1.0f, 0.0f, false };
|
||||
offset = (config->getString(calOffset, "")).toFloat();
|
||||
slope = (config->getString(calSlope, "")).toFloat();
|
||||
smooth = (config->getString(calSmooth, "")).toInt(); // user input is int; further math is done with double
|
||||
|
||||
// Convert calibration values to internal standard formats
|
||||
if (instance == "AWS" || instance == "TWS") {
|
||||
if (windspeedFormat == "m/s") {
|
||||
// No conversion needed
|
||||
} else if (windspeedFormat == "km/h") {
|
||||
offset /= 3.6; // Convert km/h to m/s
|
||||
} else if (windspeedFormat == "kn") {
|
||||
offset /= 1.94384; // Convert kn to m/s
|
||||
} else if (windspeedFormat == "bft") {
|
||||
offset *= 2 + (offset / 2); // Convert Bft to m/s (approx) -> to be improved
|
||||
}
|
||||
|
||||
} else if (instance == "AWA" || instance == "COG" || instance == "TWA" || instance == "TWD" || instance == "HDM" || instance == "PRPOS" || instance == "RPOS") {
|
||||
offset *= M_PI / 180; // Convert deg to rad
|
||||
|
||||
} else if (instance == "DBT") {
|
||||
if (lengthFormat == "m") {
|
||||
// No conversion needed
|
||||
} else if (lengthFormat == "ft") {
|
||||
offset /= 3.28084; // Convert ft to m
|
||||
}
|
||||
|
||||
} else if (instance == "SOG" || instance == "STW") {
|
||||
if (speedFormat == "m/s") {
|
||||
// No conversion needed
|
||||
} else if (speedFormat == "km/h") {
|
||||
offset /= 3.6; // Convert km/h to m/s
|
||||
} else if (speedFormat == "kn") {
|
||||
offset /= 1.94384; // Convert kn to m/s
|
||||
}
|
||||
|
||||
} else if (instance == "WTemp") {
|
||||
if (tempFormat == "K" || tempFormat == "C") {
|
||||
// No conversion needed
|
||||
} else if (tempFormat == "F") {
|
||||
offset *= 9.0 / 5.0; // Convert °F to K
|
||||
slope *= 9.0 / 5.0; // Convert °F to K
|
||||
}
|
||||
}
|
||||
|
||||
// transform smoothing factor from {0.01..10} to {0.3..0.95} and invert for exponential smoothing formula
|
||||
if (smooth <= 0) {
|
||||
smooth = 0;
|
||||
} else {
|
||||
if (smooth > 10) {
|
||||
smooth = 10;
|
||||
}
|
||||
smooth = 0.3 + ((smooth - 0.01) * (0.95 - 0.3) / (10 - 0.01));
|
||||
}
|
||||
smooth = 1 - smooth;
|
||||
|
||||
calibMap[instance].offset = offset;
|
||||
calibMap[instance].slope = slope;
|
||||
calibMap[instance].smooth = smooth;
|
||||
calibMap[instance].isCalibrated = false;
|
||||
LOG_DEBUG(GwLog::LOG, "stored calibration data: %s, offset: %f, slope: %f, smoothing: %f", instance.c_str(),
|
||||
calibMap[instance].offset, calibMap[instance].slope, calibMap[instance].smooth);
|
||||
}
|
||||
LOG_DEBUG(GwLog::LOG, "all calibration data read");
|
||||
}
|
||||
|
||||
void CalibrationDataList::calibrateInstance(GwApi::BoatValue* boatDataValue, GwLog* logger)
|
||||
// Method to calibrate the boat data value
|
||||
{
|
||||
std::string instance = boatDataValue->getName().c_str();
|
||||
double offset = 0;
|
||||
double slope = 1.0;
|
||||
double dataValue = 0;
|
||||
std::string format = "";
|
||||
|
||||
if (calibMap.find(instance) == calibMap.end()) {
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not found in calibration data list", instance.c_str());
|
||||
return;
|
||||
} else if (!boatDataValue->valid) { // no valid boat data value, so we don't want to apply calibration data
|
||||
calibMap[instance].isCalibrated = false;
|
||||
return;
|
||||
} else {
|
||||
offset = calibMap[instance].offset;
|
||||
slope = calibMap[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());
|
||||
|
||||
if (format == "formatWind") { // instance is of type angle
|
||||
dataValue = (dataValue * slope) + offset;
|
||||
dataValue = fmod(dataValue, 2 * M_PI);
|
||||
if (dataValue > (M_PI)) {
|
||||
dataValue -= (2 * M_PI);
|
||||
} else if (dataValue < (M_PI * -1)) {
|
||||
dataValue += (2 * M_PI);
|
||||
}
|
||||
} else if (format == "formatCourse") { // instance is of type direction
|
||||
dataValue = (dataValue * slope) + offset;
|
||||
dataValue = fmod(dataValue, 2 * M_PI);
|
||||
if (dataValue < 0) {
|
||||
dataValue += (2 * M_PI);
|
||||
}
|
||||
} else if (format == "kelvinToC") { // instance is of type temperature
|
||||
dataValue = ((dataValue - 273.15) * slope) + offset + 273.15;
|
||||
} else {
|
||||
|
||||
dataValue = (dataValue * slope) + offset;
|
||||
}
|
||||
|
||||
calibMap[instance].isCalibrated = true;
|
||||
boatDataValue->value = dataValue;
|
||||
|
||||
calibrationData.smoothInstance(boatDataValue, logger); // smooth the boat data value
|
||||
calibMap[instance].value = boatDataValue->value; // store the calibrated + smoothed value in the list
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: Offset: %f, Slope: %f, Result: %f", instance.c_str(), offset, slope, calibMap[instance].value);
|
||||
}
|
||||
}
|
||||
|
||||
void CalibrationDataList::smoothInstance(GwApi::BoatValue* boatDataValue, GwLog* logger)
|
||||
// Method to smoothen the boat data value
|
||||
{
|
||||
static std::unordered_map<std::string, double> lastValue; // array for last values of smoothed boat data values
|
||||
|
||||
std::string instance = boatDataValue->getName().c_str();
|
||||
double oldValue = 0;
|
||||
double dataValue = boatDataValue->value;
|
||||
double smoothFactor = 0;
|
||||
|
||||
if (!boatDataValue->valid) { // no valid boat data value, so we don't want to smoothen value
|
||||
return;
|
||||
} else if (calibMap.find(instance) == calibMap.end()) {
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: smooth factor for %s not found in calibration data list", instance.c_str());
|
||||
return;
|
||||
} else {
|
||||
smoothFactor = calibMap[instance].smooth;
|
||||
|
||||
if (lastValue.find(instance) != lastValue.end()) {
|
||||
oldValue = lastValue[instance];
|
||||
dataValue = oldValue + (smoothFactor * (dataValue - oldValue)); // exponential smoothing algorithm
|
||||
}
|
||||
lastValue[instance] = dataValue; // store the new value for next cycle; first time, store only the current value and return
|
||||
boatDataValue->value = dataValue; // set the smoothed value to the boat data value
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: Smoothing factor: %f, Smoothed value: %f", instance.c_str(), smoothFactor, dataValue);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,33 @@
|
|||
// Functions lib for data instance calibration
|
||||
|
||||
#ifndef _BOATDATACALIBRATION_H
|
||||
#define _BOATDATACALIBRATION_H
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#define MAX_CALIBRATION_DATA 3 // maximum number of calibration data instances
|
||||
|
||||
typedef struct {
|
||||
double offset; // calibration offset
|
||||
double slope; // calibration slope
|
||||
double smooth; // smoothing factor
|
||||
double value; // calibrated data value
|
||||
bool isCalibrated; // is data instance value calibrated?
|
||||
} TypeCalibData;
|
||||
|
||||
class CalibrationDataList {
|
||||
public:
|
||||
static std::unordered_map<std::string, TypeCalibData> calibMap; // list of calibration data instances
|
||||
|
||||
void readConfig(GwConfigHandler* config, GwLog* logger);
|
||||
void calibrateInstance(GwApi::BoatValue* boatDataValue, GwLog* logger);
|
||||
void smoothInstance(GwApi::BoatValue* boatDataValue, GwLog* logger);
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
extern CalibrationDataList calibrationData; // this list holds all calibration data
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -24,6 +24,8 @@
|
|||
#include "DSEG7Classic-BoldItalic60pt7b.h"
|
||||
#include "Atari16px8b.h" // Key label font
|
||||
|
||||
#include "Ubuntu_Bold20pt8b.h"
|
||||
|
||||
// E-Ink Display
|
||||
#define GxEPD_WIDTH 400 // Display width
|
||||
#define GxEPD_HEIGHT 300 // Display height
|
||||
|
@ -141,12 +143,12 @@ void deepSleep(CommonData &common){
|
|||
getdisplay().print("Sleep Mode");
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(65, 175);
|
||||
getdisplay().print("For wakeup press key and wait 5s");
|
||||
getdisplay().print("To wake up press key and wait 5s");
|
||||
getdisplay().nextPage(); // Update display contents
|
||||
getdisplay().powerOff(); // Display power off
|
||||
setPortPin(OBP_POWER_50, false); // Power off ePaper display
|
||||
// Stop system
|
||||
esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin
|
||||
esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin
|
||||
}
|
||||
#endif
|
||||
#ifdef BOARD_OBP40S3
|
||||
|
@ -166,7 +168,7 @@ void deepSleep(CommonData &common){
|
|||
getdisplay().print("Sleep Mode");
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(65, 175);
|
||||
getdisplay().print("For wakeup press wheel and wait 5s");
|
||||
getdisplay().print("To wake up press wheel and wait 5s");
|
||||
getdisplay().nextPage(); // Partial update
|
||||
getdisplay().powerOff(); // Display power off
|
||||
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
|
||||
|
@ -341,13 +343,9 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa
|
|||
static unsigned long tcpClTxOld = 0;
|
||||
static unsigned long n2kRxOld = 0;
|
||||
static unsigned long n2kTxOld = 0;
|
||||
int textcolor = GxEPD_BLACK;
|
||||
|
||||
if(commonData.config->getBool(commonData.config->statusLine) == true){
|
||||
|
||||
// Header separator line (optional)
|
||||
// getdisplay().drawLine(0, 19, 399, 19, commonData.fgcolor);
|
||||
|
||||
// Show status info
|
||||
getdisplay().setTextColor(commonData.fgcolor);
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
|
@ -392,41 +390,82 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa
|
|||
getdisplay().drawXBitmap(166, 1, swipe_bits, swipe_width, swipe_height, commonData.fgcolor);
|
||||
}
|
||||
#endif
|
||||
#ifdef LIPO_ACCU_1200
|
||||
if (commonData.data.BatteryChargeStatus == 1) {
|
||||
getdisplay().drawXBitmap(170, 1, battery_loading_bits, battery_width, battery_height, commonData.fgcolor);
|
||||
} else {
|
||||
#ifdef VOLTAGE_SENSOR
|
||||
if (commonData.data.batteryLevelLiPo < 10) {
|
||||
getdisplay().drawXBitmap(170, 1, battery_0_bits, battery_width, battery_height, commonData.fgcolor);
|
||||
} else if (commonData.data.batteryLevelLiPo < 25) {
|
||||
getdisplay().drawXBitmap(170, 1, battery_25_bits, battery_width, battery_height, commonData.fgcolor);
|
||||
} else if (commonData.data.batteryLevelLiPo < 50) {
|
||||
getdisplay().drawXBitmap(170, 1, battery_50_bits, battery_width, battery_height, commonData.fgcolor);
|
||||
} else if (commonData.data.batteryLevelLiPo < 75) {
|
||||
getdisplay().drawXBitmap(170, 1, battery_75_bits, battery_width, battery_height, commonData.fgcolor);
|
||||
} else {
|
||||
getdisplay().drawXBitmap(170, 1, battery_100_bits, battery_width, battery_height, commonData.fgcolor);
|
||||
}
|
||||
#endif // VOLTAGE_SENSOR
|
||||
}
|
||||
#endif // LIPO_ACCU_1200
|
||||
|
||||
// Heartbeat as dot
|
||||
getdisplay().setTextColor(commonData.fgcolor);
|
||||
getdisplay().setFont(&Ubuntu_Bold32pt7b);
|
||||
getdisplay().setCursor(205, 14);
|
||||
getdisplay().print(heartbeat ? "." : " ");
|
||||
// Heartbeat as page number
|
||||
if (heartbeat) {
|
||||
getdisplay().setTextColor(commonData.bgcolor);
|
||||
getdisplay().fillRect(201, 0, 23, 19, commonData.fgcolor);
|
||||
} else {
|
||||
getdisplay().setTextColor(commonData.fgcolor);
|
||||
getdisplay().drawRect(201, 0, 23, 19, commonData.fgcolor);
|
||||
}
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
drawTextCenter(211, 9, String(commonData.data.actpage));
|
||||
heartbeat = !heartbeat;
|
||||
|
||||
// Date and time
|
||||
String fmttype = commonData.config->getString(commonData.config->dateFormat);
|
||||
String timesource = commonData.config->getString(commonData.config->timeSource);
|
||||
double tz = commonData.config->getString(commonData.config->timeZone).toDouble();
|
||||
getdisplay().setTextColor(commonData.fgcolor);
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(230, 15);
|
||||
// Show date and time if date present
|
||||
if(date->valid == true){
|
||||
String acttime = formatValue(time, commonData).svalue;
|
||||
acttime = acttime.substring(0, 5);
|
||||
String actdate = formatValue(date, commonData).svalue;
|
||||
getdisplay().print(acttime);
|
||||
getdisplay().print(" ");
|
||||
getdisplay().print(actdate);
|
||||
getdisplay().print(" ");
|
||||
if(commonData.config->getInt(commonData.config->timeZone) == 0){
|
||||
getdisplay().print("UTC");
|
||||
}
|
||||
else{
|
||||
getdisplay().print("LOT");
|
||||
if (timesource == "RTC" or timesource == "iRTC") {
|
||||
// TODO take DST into account
|
||||
if (commonData.data.rtcValid) {
|
||||
time_t tv = mktime(&commonData.data.rtcTime) + (int)(tz * 3600);
|
||||
struct tm *local_tm = localtime(&tv);
|
||||
getdisplay().print(formatTime('m', local_tm->tm_hour, local_tm->tm_min, 0));
|
||||
getdisplay().print(" ");
|
||||
getdisplay().print(formatDate(fmttype, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday));
|
||||
getdisplay().print(" ");
|
||||
getdisplay().print(tz == 0 ? "UTC" : "LOT");
|
||||
} else {
|
||||
drawTextRalign(396, 15, "RTC invalid");
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(commonData.config->getBool(commonData.config->useSimuData) == true){
|
||||
getdisplay().print("12:00 01.01.2024 LOT");
|
||||
else if (timesource == "GPS") {
|
||||
// Show date and time if date present
|
||||
if(date->valid == true){
|
||||
String acttime = formatValue(time, commonData).svalue;
|
||||
acttime = acttime.substring(0, 5);
|
||||
String actdate = formatValue(date, commonData).svalue;
|
||||
getdisplay().print(acttime);
|
||||
getdisplay().print(" ");
|
||||
getdisplay().print(actdate);
|
||||
getdisplay().print(" ");
|
||||
getdisplay().print(tz == 0 ? "UTC" : "LOT");
|
||||
}
|
||||
else{
|
||||
getdisplay().print("No GPS data");
|
||||
if(commonData.config->getBool(commonData.config->useSimuData) == true){
|
||||
getdisplay().print("12:00 01.01.2024 LOT");
|
||||
}
|
||||
else{
|
||||
drawTextRalign(396, 15, "No GPS data");
|
||||
}
|
||||
}
|
||||
} // timesource == "GPS"
|
||||
else {
|
||||
getdisplay().print("No time source");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -442,17 +481,16 @@ void displayFooter(CommonData &commonData) {
|
|||
// horizontal elements
|
||||
const uint16_t top = 280;
|
||||
const uint16_t bottom = 299;
|
||||
// horizontal stub lines
|
||||
getdisplay().drawLine(commonData.keydata[0].x, top, commonData.keydata[0].x+10, top, commonData.fgcolor);
|
||||
getdisplay().drawLine(commonData.keydata[1].x-10, top, commonData.keydata[1].x+10, top, commonData.fgcolor);
|
||||
getdisplay().drawLine(commonData.keydata[2].x-10, top, commonData.keydata[2].x+10, top, commonData.fgcolor);
|
||||
getdisplay().drawLine(commonData.keydata[4].x-10, top, commonData.keydata[4].x+10, top, commonData.fgcolor);
|
||||
getdisplay().drawLine(commonData.keydata[5].x-10, top, commonData.keydata[5].x+10, top, commonData.fgcolor);
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
getdisplay().drawLine(commonData.keydata[i].x-10, top, commonData.keydata[i].x+10, top, commonData.fgcolor);
|
||||
}
|
||||
getdisplay().drawLine(commonData.keydata[5].x + commonData.keydata[5].w - 10, top, commonData.keydata[5].x + commonData.keydata[5].w + 1, top, commonData.fgcolor);
|
||||
// vertical key separators
|
||||
getdisplay().drawLine(commonData.keydata[0].x + commonData.keydata[0].w, top, commonData.keydata[0].x + commonData.keydata[0].w, bottom, commonData.fgcolor);
|
||||
getdisplay().drawLine(commonData.keydata[1].x + commonData.keydata[1].w, top, commonData.keydata[1].x + commonData.keydata[1].w, bottom, commonData.fgcolor);
|
||||
getdisplay().drawLine(commonData.keydata[3].x + commonData.keydata[3].w, top, commonData.keydata[3].x + commonData.keydata[3].w, bottom, commonData.fgcolor);
|
||||
getdisplay().drawLine(commonData.keydata[4].x + commonData.keydata[4].w, top, commonData.keydata[4].x + commonData.keydata[4].w, bottom, commonData.fgcolor);
|
||||
for (int i = 0; i <= 4; i++) {
|
||||
getdisplay().drawLine(commonData.keydata[i].x + commonData.keydata[i].w, top, commonData.keydata[i].x + commonData.keydata[i].w, bottom, commonData.fgcolor);
|
||||
}
|
||||
for (int i = 0; i < 6; i++) {
|
||||
uint16_t x, y;
|
||||
if (commonData.keydata[i].label.length() > 0) {
|
||||
|
@ -476,9 +514,6 @@ void displayFooter(CommonData &commonData) {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Current page number in a small box
|
||||
getdisplay().drawRect(190, 280, 23, 19, commonData.fgcolor);
|
||||
drawTextCenter(200, 289, String(commonData.data.actpage));
|
||||
} else {
|
||||
getdisplay().setCursor(65, 295);
|
||||
getdisplay().print("Press 1 and 6 fast to unlock keys");
|
||||
|
@ -512,8 +547,7 @@ void displayFooter(CommonData &commonData) {
|
|||
}
|
||||
|
||||
// Sunset und sunrise calculation
|
||||
SunData calcSunsetSunrise(GwApi *api, double time, double date, double latitude, double longitude, double timezone){
|
||||
GwLog *logger=api->getLogger();
|
||||
SunData calcSunsetSunrise(double time, double date, double latitude, double longitude, float timezone){
|
||||
SunData returnset;
|
||||
SunRise sr;
|
||||
int secPerHour = 3600;
|
||||
|
@ -528,7 +562,6 @@ SunData calcSunsetSunrise(GwApi *api, double time, double date, double latitude,
|
|||
|
||||
// Calculate local epoch
|
||||
t = (date * secPerYear) + time;
|
||||
// api->getLogger()->logDebug(GwLog::DEBUG,"... calcSun: Lat %f, Lon %f, at: %d ", latitude, longitude, t);
|
||||
sr.calculate(latitude, longitude, t); // LAT, LON, EPOCH
|
||||
// Sunrise
|
||||
if (sr.hasRise) {
|
||||
|
@ -551,6 +584,37 @@ SunData calcSunsetSunrise(GwApi *api, double time, double date, double latitude,
|
|||
return returnset;
|
||||
}
|
||||
|
||||
SunData calcSunsetSunriseRTC(struct tm *rtctime, double latitude, double longitude, float timezone) {
|
||||
SunData returnset;
|
||||
SunRise sr;
|
||||
const int secPerHour = 3600;
|
||||
const int secPerYear = 86400;
|
||||
sr.hasRise = false;
|
||||
sr.hasSet = false;
|
||||
time_t t = mktime(rtctime) + timezone * 3600;;
|
||||
time_t sunR = 0;
|
||||
time_t sunS = 0;
|
||||
|
||||
sr.calculate(latitude, longitude, t); // LAT, LON, EPOCH
|
||||
// Sunrise
|
||||
if (sr.hasRise) {
|
||||
sunR = (sr.riseTime + int(timezone * secPerHour) + 30) % secPerYear; // add 30 seconds: round to minutes
|
||||
returnset.sunriseHour = int (sunR / secPerHour);
|
||||
returnset.sunriseMinute = int((sunR - returnset.sunriseHour * secPerHour) / 60);
|
||||
}
|
||||
// Sunset
|
||||
if (sr.hasSet) {
|
||||
sunS = (sr.setTime + int(timezone * secPerHour) + 30) % secPerYear; // add 30 seconds: round to minutes
|
||||
returnset.sunsetHour = int (sunS / secPerHour);
|
||||
returnset.sunsetMinute = int((sunS - returnset.sunsetHour * secPerHour) / 60);
|
||||
}
|
||||
// Sun control (return value by sun on sky = false, sun down = true)
|
||||
if ((t >= sr.riseTime) && (t <= sr.setTime))
|
||||
returnset.sunDown = false;
|
||||
else returnset.sunDown = true;
|
||||
return returnset;
|
||||
}
|
||||
|
||||
// Battery graphic with fill level
|
||||
void batteryGraphic(uint x, uint y, float percent, int pcolor, int bcolor){
|
||||
// Show battery
|
||||
|
|
|
@ -31,6 +31,7 @@ extern const GFXfont Ubuntu_Bold10pt7b;
|
|||
extern const GFXfont Ubuntu_Bold12pt7b;
|
||||
extern const GFXfont Ubuntu_Bold16pt7b;
|
||||
extern const GFXfont Ubuntu_Bold20pt7b;
|
||||
extern const GFXfont Ubuntu_Bold20pt8b;
|
||||
extern const GFXfont Ubuntu_Bold32pt7b;
|
||||
extern const GFXfont DSEG7Classic_BoldItalic16pt7b;
|
||||
extern const GFXfont DSEG7Classic_BoldItalic20pt7b;
|
||||
|
@ -97,11 +98,12 @@ void displayTrendLow(int16_t x, int16_t y, uint16_t size, uint16_t color);
|
|||
void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatValue *time, GwApi::BoatValue *hdop); // Draw display header
|
||||
void displayFooter(CommonData &commonData);
|
||||
|
||||
SunData calcSunsetSunrise(GwApi *api, double time, double date, double latitude, double longitude, double timezone); // Calulate sunset and sunrise
|
||||
SunData calcSunsetSunrise(double time, double date, double latitude, double longitude, float timezone); // Calulate sunset and sunrise
|
||||
SunData calcSunsetSunriseRTC(struct tm *rtctime, double latitude, double longitude, float timezone);
|
||||
|
||||
void batteryGraphic(uint x, uint y, float percent, int pcolor, int bcolor); // Battery graphic with fill level
|
||||
void solarGraphic(uint x, uint y, int pcolor, int bcolor); // Solar graphic with fill level
|
||||
void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic with fill level
|
||||
void solarGraphic(uint x, uint y, int pcolor, int bcolor); // Solar graphic
|
||||
void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic
|
||||
void startLedTask(GwApi *api);
|
||||
|
||||
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request);
|
||||
|
@ -160,6 +162,46 @@ static std::map<String, unsigned char *> iconmap = {
|
|||
{"AP", ap_bits}
|
||||
};
|
||||
|
||||
// Battery
|
||||
#define battery_width 24
|
||||
#define battery_height 16
|
||||
|
||||
static unsigned char battery_0_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f,
|
||||
0x03, 0x00, 0x18, 0x03, 0x00, 0x78, 0x03, 0x00, 0xf8, 0x03, 0x00, 0xd8,
|
||||
0x03, 0x00, 0xd8, 0x03, 0x00, 0xd8, 0x03, 0x00, 0xf8, 0x03, 0x00, 0x78,
|
||||
0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 };
|
||||
|
||||
static unsigned char battery_25_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f,
|
||||
0x03, 0x00, 0x18, 0x3b, 0x00, 0x78, 0x3b, 0x00, 0xf8, 0x3b, 0x00, 0xd8,
|
||||
0x3b, 0x00, 0xd8, 0x3b, 0x00, 0xd8, 0x3b, 0x00, 0xf8, 0x3b, 0x00, 0x78,
|
||||
0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 };
|
||||
|
||||
static unsigned char battery_50_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f,
|
||||
0x03, 0x00, 0x18, 0xbb, 0x03, 0x78, 0xbb, 0x03, 0xf8, 0xbb, 0x03, 0xd8,
|
||||
0xbb, 0x03, 0xd8, 0xbb, 0x03, 0xd8, 0xbb, 0x03, 0xf8, 0xbb, 0x03, 0x78,
|
||||
0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 };
|
||||
|
||||
static unsigned char battery_75_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f,
|
||||
0x03, 0x00, 0x18, 0xbb, 0x3b, 0x78, 0xbb, 0x3b, 0xf8, 0xbb, 0x3b, 0xd8,
|
||||
0xbb, 0x3b, 0xd8, 0xbb, 0x3b, 0xd8, 0xbb, 0x3b, 0xf8, 0xbb, 0x3b, 0x78,
|
||||
0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 };
|
||||
|
||||
static unsigned char battery_100_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f,
|
||||
0x03, 0x00, 0x18, 0xbb, 0xbb, 0x7b, 0xbb, 0xbb, 0xfb, 0xbb, 0xbb, 0xdb,
|
||||
0xbb, 0xbb, 0xdb, 0xbb, 0xbb, 0xdb, 0xbb, 0xbb, 0xfb, 0xbb, 0xbb, 0x7b,
|
||||
0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 };
|
||||
|
||||
static unsigned char battery_loading_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xfe, 0xe4, 0x0f, 0xff, 0xec, 0x1f,
|
||||
0x03, 0x08, 0x18, 0x03, 0x18, 0x78, 0x03, 0x30, 0xf8, 0x83, 0x3f, 0xd8,
|
||||
0x03, 0x7f, 0xd8, 0x03, 0x03, 0xd8, 0x03, 0x06, 0xf8, 0x03, 0x04, 0x78,
|
||||
0x03, 0x0c, 0x18, 0xff, 0xcb, 0x1f, 0xfe, 0xd3, 0x0f, 0x00, 0x10, 0x00 };
|
||||
|
||||
// Other symbols
|
||||
#define swipe_width 24
|
||||
#define swipe_height 16
|
||||
|
|
|
@ -8,6 +8,47 @@
|
|||
// simulation data
|
||||
// hold values by missing data
|
||||
|
||||
String formatDate(String fmttype, uint16_t year, uint8_t month, uint8_t day) {
|
||||
char buffer[12];
|
||||
if (fmttype == "GB") {
|
||||
snprintf(buffer, 12, "%02d/%02d/%04d", day , month, year);
|
||||
}
|
||||
else if (fmttype == "US") {
|
||||
snprintf(buffer, 12, "%02d/%02d/%04d", month, day, year);
|
||||
}
|
||||
else if (fmttype == "ISO") {
|
||||
snprintf(buffer, 12, "%04d-%02d-%02d", year, month, day);
|
||||
}
|
||||
else {
|
||||
snprintf(buffer, 12, "%02d.%02d.%04d", day, month, year);
|
||||
}
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
String formatTime(char fmttype, uint8_t hour, uint8_t minute, uint8_t second) {
|
||||
// fmttype: s: with seconds, m: only minutes
|
||||
char buffer[10];
|
||||
if (fmttype == 'm') {
|
||||
snprintf(buffer, 10, "%02d:%02d", hour , minute);
|
||||
}
|
||||
else {
|
||||
snprintf(buffer, 10, "%02d:%02d:%02d", hour, minute, second);
|
||||
}
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
String formatLatitude(double lat) {
|
||||
float degree = abs(int(lat));
|
||||
float minute = abs((lat - int(lat)) * 60);
|
||||
return String(degree, 0) + "\x90 " + String(minute, 4) + "' " + ((lat > 0) ? "N" : "S");
|
||||
}
|
||||
|
||||
String formatLongitude(double lon) {
|
||||
float degree = abs(int(lon));
|
||||
float minute = abs((lon - int(lon)) * 60);
|
||||
return String(degree, 0) + "\x90 " + String(minute, 4) + "' " + ((lon > 0) ? "E" : "W");
|
||||
}
|
||||
|
||||
FormatedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
|
||||
GwLog *logger = commondata.logger;
|
||||
FormatedData result;
|
||||
|
@ -317,7 +358,7 @@ FormatedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
|
|||
else{
|
||||
latdir = "S";
|
||||
}
|
||||
latitude = String(degree,0) + "\" " + String(minute,4) + "' " + latdir;
|
||||
latitude = String(degree,0) + "\x90 " + String(minute,4) + "' " + latdir;
|
||||
result.unit = "";
|
||||
strcpy(buffer, latitude.c_str());
|
||||
}
|
||||
|
@ -341,7 +382,7 @@ FormatedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
|
|||
else{
|
||||
londir = "W";
|
||||
}
|
||||
longitude = String(degree,0) + "\" " + String(minute,4) + "' " + londir;
|
||||
longitude = String(degree,0) + "\x90 " + String(minute,4) + "' " + londir;
|
||||
result.unit = "";
|
||||
strcpy(buffer, longitude.c_str());
|
||||
}
|
||||
|
@ -380,8 +421,14 @@ FormatedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
|
|||
}
|
||||
//########################################################
|
||||
else if (value->getFormat() == "formatXte"){
|
||||
double xte = abs(value->value);
|
||||
rawvalue = value->value;
|
||||
double xte = 0;
|
||||
if (!usesimudata) {
|
||||
xte = abs(value->value);
|
||||
rawvalue = value->value;
|
||||
} else {
|
||||
rawvalue = 6.0 + float(random(0, 4));
|
||||
xte = rawvalue;
|
||||
}
|
||||
if (xte >= 100) {
|
||||
snprintf(buffer,bsize,"%3.0f",value->value);
|
||||
} else if (xte >= 10) {
|
||||
|
@ -407,7 +454,7 @@ FormatedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
|
|||
result.unit = "C";
|
||||
}
|
||||
else if(String(tempFormat) == "F"){
|
||||
temp = temp - 459.67;
|
||||
temp = (temp - 273.15) * 9 / 5 + 32;
|
||||
result.unit = "F";
|
||||
}
|
||||
else{
|
||||
|
|
|
@ -277,8 +277,8 @@ void initKeys(CommonData &commonData) {
|
|||
starttime = millis(); // Start key pressed
|
||||
keycodeold = keycode;
|
||||
}
|
||||
// If key pressed longer than 200ms
|
||||
if(millis() > starttime + 200 && keycode == keycodeold) {
|
||||
// If key pressed longer than 100ms
|
||||
if(millis() > starttime + 100 && keycode == keycodeold) {
|
||||
if (use_syspage and keycode == 3) {
|
||||
keystatus = 12;
|
||||
} else {
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
#include "ObpNmea0183.h" // Check NMEA0183 sentence for uncorrect content
|
||||
#include "OBP60Extensions.h" // Lib for hardware extensions
|
||||
#include "movingAvg.h" // Lib for moving average building
|
||||
#include "time.h" // For getting NTP time
|
||||
#include <ESP32Time.h> // Internal ESP32 RTC clock
|
||||
|
||||
// Timer for hardware functions
|
||||
Ticker Timer1(blinkingFlashLED, 500); // Satrt Timer1 for flash LED all 500ms
|
||||
Ticker Timer1(blinkingFlashLED, 500); // Start Timer1 for flash LED all 500ms
|
||||
|
||||
// Initialization for all sensors (RS232, I2C, 1Wire, IOs)
|
||||
//####################################################################################
|
||||
|
@ -88,8 +90,16 @@ void sensorTask(void *param){
|
|||
double voffset = (api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asString()).toFloat();
|
||||
double vslope = (api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asString()).toFloat();
|
||||
if(String(powsensor1) == "off"){
|
||||
sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20
|
||||
sensors.batteryVoltage = sensors.batteryVoltage * vslope + voffset; // Calibration
|
||||
#ifdef VOLTAGE_SENSOR
|
||||
float rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40
|
||||
#else
|
||||
float rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60
|
||||
#endif
|
||||
sensors.batteryVoltage = rawVoltage * vslope + voffset; // Calibration
|
||||
#ifdef LIPO_ACCU_1200
|
||||
sensors.BatteryChargeStatus = 0; // Set to discharging
|
||||
sensors.batteryLevelLiPo = 0; // Level 0...100%
|
||||
#endif
|
||||
sensors.batteryCurrent = 0;
|
||||
sensors.batteryPower = 0;
|
||||
// Fill average arrays with start values
|
||||
|
@ -142,6 +152,7 @@ void sensorTask(void *param){
|
|||
// ds1388.adjust(DateTime(__DATE__, __TIME__)); // Set date and time from PC file time
|
||||
}
|
||||
RTC_ready = true;
|
||||
sensors.rtcValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -358,6 +369,28 @@ void sensorTask(void *param){
|
|||
GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP);
|
||||
GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop};
|
||||
|
||||
// Internal RTC with NTP init
|
||||
ESP32Time rtc(0);
|
||||
if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") {
|
||||
GwApi::Status status;
|
||||
api->getStatus(status);
|
||||
if (status.wifiClientConnected) {
|
||||
const char *ntpServer = api->getConfig()->getCString(api->getConfig()->timeServer);
|
||||
api->getLogger()->logDebug(GwLog::LOG,"Fetching date and time from NTP server '%s'.", ntpServer);
|
||||
configTime(0, 0, ntpServer); // get time in UTC
|
||||
struct tm timeinfo;
|
||||
if (getLocalTime(&timeinfo)) {
|
||||
api->getLogger()->logDebug(GwLog::LOG,"NTP time: %04d-%02d-%02d %02d:%02d:%02d UTC", timeinfo.tm_year+1900, timeinfo.tm_mon+1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
|
||||
rtc.setTimeStruct(timeinfo);
|
||||
sensors.rtcValid = true;
|
||||
} else {
|
||||
api->getLogger()->logDebug(GwLog::LOG,"NTP time fetch failed!");
|
||||
}
|
||||
} else {
|
||||
api->getLogger()->logDebug(GwLog::LOG,"Wifi client not connected, NTP not available.");
|
||||
}
|
||||
}
|
||||
|
||||
// Sensor task loop runs with 100ms
|
||||
//####################################################################################
|
||||
|
||||
|
@ -420,58 +453,112 @@ void sensorTask(void *param){
|
|||
loopCounter++;
|
||||
}
|
||||
|
||||
// If GPS not ready or installed then send RTC time on bus all 500ms
|
||||
if(millis() > starttime12 + 500){
|
||||
// Get current RTC date and time all 500ms
|
||||
if (millis() > starttime12 + 500) {
|
||||
starttime12 = millis();
|
||||
if((rtcOn == "DS1388" && RTC_ready == true && GPS_ready == false) || (rtcOn == "DS1388" && RTC_ready == true && GPS_ready == true && hdop->valid == false)){
|
||||
// Convert RTC time to Unix system time
|
||||
// https://de.wikipedia.org/wiki/Unixzeit
|
||||
const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334};
|
||||
long unixtime = ds1388.now().get();
|
||||
uint16_t year = ds1388.now().year();
|
||||
uint8_t month = ds1388.now().month();
|
||||
uint8_t hour = ds1388.now().hour();
|
||||
uint8_t minute = ds1388.now().minute();
|
||||
uint8_t second = ds1388.now().second();
|
||||
uint8_t day = ds1388.now().day();
|
||||
uint16_t switchYear = ((year-1)-1968)/4 - ((year-1)-1900)/100 + ((year-1)-1600)/400;
|
||||
long daysAt1970 = (year-1970)*365 + switchYear + daysOfYear[month-1] + day-1;
|
||||
// If switch year then add one day
|
||||
if ( (month>2) && (year%4==0 && (year%100!=0 || year%400==0)) ){
|
||||
daysAt1970 += 1;
|
||||
}
|
||||
double sysTime = (hour * 3600) + (minute * 60) + second;
|
||||
if(!isnan(daysAt1970) && !isnan(sysTime)){
|
||||
sensors.rtcYear = year; // Save values in SensorData
|
||||
sensors.rtcMonth = month;
|
||||
sensors.rtcDay = day;
|
||||
sensors.rtcHour = hour;
|
||||
sensors.rtcMinute = minute;
|
||||
sensors.rtcSecond = second;
|
||||
// api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",year, month, day, hour, minute, second);
|
||||
// api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
|
||||
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
|
||||
api->sendN2kMessage(N2kMsg);
|
||||
if (rtcOn == "DS1388" && RTC_ready) {
|
||||
DateTime dt = ds1388.now();
|
||||
sensors.rtcTime.tm_year = dt.year() - 1900; // Save values in SensorData
|
||||
sensors.rtcTime.tm_mon = dt.month() - 1;
|
||||
sensors.rtcTime.tm_mday = dt.day();
|
||||
sensors.rtcTime.tm_hour = dt.hour();
|
||||
sensors.rtcTime.tm_min = dt.minute();
|
||||
sensors.rtcTime.tm_sec = dt.second();
|
||||
sensors.rtcTime.tm_isdst = 0; // Not considering daylight saving time
|
||||
|
||||
// If GPS not ready or installed then send RTC time on bus
|
||||
// TODO If there are other time sources on the bus there should
|
||||
// be a logic not to send or to send with lower frequency
|
||||
// or something totally different
|
||||
if ((GPS_ready == false) || (GPS_ready == true && hdop->valid == false)) {
|
||||
// TODO implement daysAt1970 and sysTime as methods of DateTime
|
||||
const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334};
|
||||
uint16_t switchYear = ((dt.year()-1)-1968)/4 - ((dt.year()-1)-1900)/100 + ((dt.year()-1)-1600)/400;
|
||||
long daysAt1970 = (dt.year()-1970)*365 + switchYear + daysOfYear[dt.month()-1] + dt.day()-1;
|
||||
// If switch year then add one day
|
||||
if ((dt.month() > 2) && (dt.year() % 4 == 0 && (dt.year() % 100 != 0 || dt.year() % 400 == 0))) {
|
||||
daysAt1970 += 1;
|
||||
}
|
||||
// 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);
|
||||
//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
|
||||
sensors.rtcTime = rtc.getTimeStruct();
|
||||
}
|
||||
}
|
||||
|
||||
// Send supply voltage value all 1s
|
||||
if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){
|
||||
starttime5 = millis();
|
||||
sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20
|
||||
sensors.batteryVoltage = sensors.batteryVoltage * vslope + voffset; // Calibration
|
||||
float rawVoltage = 0;
|
||||
#if defined(BOARD_OBP40S3) && defined(VOLTAGE_SENSOR)
|
||||
rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40
|
||||
#endif
|
||||
#ifdef BOARD_OBP60S3
|
||||
rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60
|
||||
#endif
|
||||
sensors.batteryVoltage = rawVoltage * vslope + voffset; // Calibration
|
||||
// Save new data in average array
|
||||
batV.reading(int(sensors.batteryVoltage * 100));
|
||||
// Calculate the average values for different time lines from integer values
|
||||
sensors.batteryVoltage10 = batV.getAvg(10) / 100.0;
|
||||
sensors.batteryVoltage60 = batV.getAvg(60) / 100.0;
|
||||
sensors.batteryVoltage300 = batV.getAvg(300) / 100.0;
|
||||
#if BOARD_OBP40S3 && defined LIPO_ACCU_1200 && defined VOLTAGE_SENSOR
|
||||
// Polynomfit for LiPo capacity calculation for 3,7V LiPo accus, 0...100%
|
||||
sensors.batteryLevelLiPo = sensors.batteryVoltage60 * 203.8312 -738.1635;
|
||||
// Limiter
|
||||
if(sensors.batteryLevelLiPo > 100){
|
||||
sensors.batteryLevelLiPo = 100;
|
||||
}
|
||||
if(sensors.batteryLevelLiPo < 0){
|
||||
sensors.batteryLevelLiPo = 0;
|
||||
}
|
||||
// Charging detection
|
||||
float deltaV = sensors.batteryVoltage - sensors.batteryVoltage10;
|
||||
// Higher limits for lower voltages
|
||||
if(sensors.batteryVoltage10 < 4.0){
|
||||
if(deltaV > 0.045){
|
||||
sensors.BatteryChargeStatus = 1; // Charging active
|
||||
}
|
||||
if(deltaV < -0.04){
|
||||
sensors.BatteryChargeStatus = 0; // Discharging
|
||||
}
|
||||
}
|
||||
// Lower limits for higher voltages
|
||||
else{
|
||||
if(deltaV > 0.03){
|
||||
sensors.BatteryChargeStatus = 1; // Charging active
|
||||
}
|
||||
if(deltaV < -0.03){
|
||||
sensors.BatteryChargeStatus = 0; // Discharging
|
||||
}
|
||||
}
|
||||
// Charging stops by grater than 4,15V
|
||||
if(sensors.batteryVoltage10 > 4.15){
|
||||
sensors.BatteryChargeStatus = 0; // Discharging
|
||||
}
|
||||
// Send to NMEA200 bus as instance 10
|
||||
if(!isnan(sensors.batteryVoltage)){
|
||||
SetN2kDCBatStatus(N2kMsg, 10, sensors.batteryVoltage, N2kDoubleNA, N2kDoubleNA, 0);
|
||||
api->sendN2kMessage(N2kMsg);
|
||||
}
|
||||
#endif
|
||||
#ifdef BOARD_OBP60S3
|
||||
// Send to NMEA200 bus
|
||||
if(!isnan(sensors.batteryVoltage)){
|
||||
SetN2kDCBatStatus(N2kMsg, 0, sensors.batteryVoltage, N2kDoubleNA, N2kDoubleNA, 1);
|
||||
api->sendN2kMessage(N2kMsg);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Send data from environment sensor all 2s
|
||||
|
|
|
@ -3,19 +3,83 @@
|
|||
#include "Pagedata.h"
|
||||
#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
|
||||
*
|
||||
*/
|
||||
|
||||
class PageClock : public Page
|
||||
{
|
||||
public:
|
||||
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
|
||||
|
||||
public:
|
||||
PageClock(CommonData &common){
|
||||
commonData = &common;
|
||||
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
|
||||
}
|
||||
|
||||
virtual void setupKeys(){
|
||||
Page::setupKeys();
|
||||
commonData->keydata[0].label = "SRC";
|
||||
commonData->keydata[1].label = "MODE";
|
||||
commonData->keydata[4].label = "TZ";
|
||||
}
|
||||
|
||||
// Key functions
|
||||
virtual int handleKey(int key){
|
||||
// Code for keylock
|
||||
if(key == 11){
|
||||
commonData->keylock = !commonData->keylock;
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Keylock function
|
||||
if(key == 11){ // Code for keylock
|
||||
keylock = !keylock; // Toggle keylock
|
||||
return 0; // Commit the key
|
||||
}
|
||||
return key;
|
||||
|
@ -42,12 +106,10 @@ public:
|
|||
|
||||
// Get config data
|
||||
String lengthformat = config->getString(config->lengthFormat);
|
||||
bool simulation = config->getBool(config->useSimuData);
|
||||
String dateformat = config->getString(config->dateFormat);
|
||||
bool holdvalues = config->getBool(config->holdvalues);
|
||||
String flashLED = config->getString(config->flashLED);
|
||||
String backlightMode = config->getString(config->backlight);
|
||||
String stimezone = config->getString(config->timeZone);
|
||||
double timezone = stimezone.toDouble();
|
||||
|
||||
// Get boat values for GPS time
|
||||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
|
@ -57,13 +119,13 @@ public:
|
|||
value1 = bvalue1->value; // Value as double in SI unit
|
||||
}
|
||||
else{
|
||||
value1 = 38160; // Simulation data for time value 11:36 in seconds
|
||||
value1 = simtime++; // Simulation data for time value 11:36 in seconds
|
||||
} // Other simulation data see OBP60Formater.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 unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
||||
if(valid1 == true){
|
||||
svalue1old = svalue1; // Save old value
|
||||
svalue1old = svalue1; // Save old value
|
||||
unit1old = unit1; // Save old unit
|
||||
}
|
||||
|
||||
|
@ -76,7 +138,7 @@ public:
|
|||
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
|
||||
if(valid2 == true){
|
||||
svalue2old = svalue2; // Save old value
|
||||
svalue2old = svalue2; // Save old value
|
||||
unit2old = unit2; // Save old unit
|
||||
}
|
||||
|
||||
|
@ -89,7 +151,7 @@ public:
|
|||
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value
|
||||
if(valid3 == true){
|
||||
svalue3old = svalue3; // Save old value
|
||||
svalue3old = svalue3; // Save old value
|
||||
unit3old = unit3; // Save old unit
|
||||
}
|
||||
|
||||
|
@ -111,11 +173,30 @@ public:
|
|||
|
||||
getdisplay().setTextColor(commonData->fgcolor);
|
||||
|
||||
time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600;
|
||||
struct tm *local_tm = localtime(&tv);
|
||||
|
||||
// Show values GPS date
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(10, 65);
|
||||
if(holdvalues == false) getdisplay().print(svalue2); // Value
|
||||
else getdisplay().print(svalue2old);
|
||||
if (holdvalues == false) {
|
||||
if (source == 'G') {
|
||||
// GPS value
|
||||
getdisplay().print(svalue2);
|
||||
} else if (commonData->data.rtcValid) {
|
||||
// RTC value
|
||||
if (tz == 'L') {
|
||||
getdisplay().print(formatDate(dateformat, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday));
|
||||
}
|
||||
else {
|
||||
getdisplay().print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday));
|
||||
}
|
||||
} else {
|
||||
getdisplay().print("---");
|
||||
}
|
||||
} else {
|
||||
getdisplay().print(svalue2old);
|
||||
}
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(10, 95);
|
||||
getdisplay().print("Date"); // Name
|
||||
|
@ -126,17 +207,35 @@ public:
|
|||
// Show values GPS time
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(10, 250);
|
||||
if(holdvalues == false) getdisplay().print(svalue1); // Value
|
||||
else getdisplay().print(svalue1old);
|
||||
if (holdvalues == false) {
|
||||
if (source == 'G') {
|
||||
getdisplay().print(svalue1); // Value
|
||||
}
|
||||
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 {
|
||||
getdisplay().print(formatTime('s', commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec));
|
||||
}
|
||||
} else {
|
||||
getdisplay().print("---");
|
||||
}
|
||||
}
|
||||
else {
|
||||
getdisplay().print(svalue1old);
|
||||
}
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(10, 220);
|
||||
getdisplay().print("Time"); // Name
|
||||
|
||||
// Show values sunrise
|
||||
String sunrise = "---";
|
||||
if(valid1 == true && valid2 == true && valid3 == true){
|
||||
if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) {
|
||||
sunrise = String(commonData->sundata.sunriseHour) + ":" + String(commonData->sundata.sunriseMinute + 100).substring(1);
|
||||
svalue5old = sunrise;
|
||||
} else if (simulation) {
|
||||
sunrise = String("06:42");
|
||||
}
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
|
@ -152,9 +251,11 @@ public:
|
|||
|
||||
// Show values sunset
|
||||
String sunset = "---";
|
||||
if(valid1 == true && valid2 == true && valid3 == true){
|
||||
if ((valid1 and valid2 and valid3 == true) or (homevalid and commonData->data.rtcValid)) {
|
||||
sunset = String(commonData->sundata.sunsetHour) + ":" + String(commonData->sundata.sunsetMinute + 100).substring(1);
|
||||
svalue6old = sunset;
|
||||
} else if (simulation) {
|
||||
sunset = String("21:03");
|
||||
}
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
|
@ -238,27 +339,52 @@ public:
|
|||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(175, 110);
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(unit2); // Unit
|
||||
getdisplay().print(tz == 'L' ? "LOT" : "UTC");
|
||||
}
|
||||
else{
|
||||
getdisplay().print(unit2old); // Unit
|
||||
getdisplay().print(unit2old); // date unit
|
||||
}
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(185, 190);
|
||||
if (source == 'G') {
|
||||
getdisplay().print("GPS");
|
||||
} else {
|
||||
getdisplay().print("RTC");
|
||||
}
|
||||
|
||||
// Clock values
|
||||
double hour = 0;
|
||||
double minute = 0;
|
||||
value1 = value1 + int(timezone*3600);
|
||||
if (value1 > 86400) {value1 = value1 - 86400;}
|
||||
if (value1 < 0) {value1 = value1 + 86400;}
|
||||
hour = (value1 / 3600.0);
|
||||
if(hour > 12) hour = hour - 12.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
|
||||
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;
|
||||
} else {
|
||||
minute = commonData->data.rtcTime.tm_min;
|
||||
hour = commonData->data.rtcTime.tm_hour;
|
||||
}
|
||||
hour += minute / 60;
|
||||
} else {
|
||||
if (tz == 'L') {
|
||||
value1 += timezone * 3600;
|
||||
}
|
||||
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
|
||||
}
|
||||
if (hour > 12) {
|
||||
hour -= 12.0;
|
||||
}
|
||||
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 || holdvalues == true || simulation == true){
|
||||
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
|
||||
|
@ -284,7 +410,7 @@ public:
|
|||
|
||||
// Draw minute pointer
|
||||
startwidth = 8; // Start width of pointer
|
||||
if(valid1 == true || holdvalues == true || simulation == true){
|
||||
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
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
|
||||
// these constants have to match the declaration below in :
|
||||
// PageDescription registerPageCompass(
|
||||
// {"COG","HDT", "HDM"}, // Bus values we need in the page
|
||||
const int HowManyValues = 6;
|
||||
|
||||
const int AverageValues = 4;
|
||||
|
||||
const int ShowHDM = 0;
|
||||
const int ShowHDT = 1;
|
||||
const int ShowCOG = 2;
|
||||
const int ShowSTW = 3;
|
||||
const int ShowSOG = 4;
|
||||
const int ShowDBS = 5;
|
||||
|
||||
const int Compass_X0 = 200; // center point of compass band
|
||||
const int Compass_Y0 = 220; // position of compass lines
|
||||
const int Compass_LineLength = 22; // length of compass lines
|
||||
const float Compass_LineDelta = 8.0;// compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
|
||||
|
||||
class PageCompass : public Page
|
||||
{
|
||||
int WhichDataCompass = ShowHDM;
|
||||
int WhichDataDisplay = ShowHDM;
|
||||
|
||||
public:
|
||||
PageCompass(CommonData &common){
|
||||
commonData = &common;
|
||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageCompass");
|
||||
}
|
||||
|
||||
virtual void setupKeys(){
|
||||
Page::setupKeys();
|
||||
commonData->keydata[0].label = "CMP";
|
||||
commonData->keydata[1].label = "SRC";
|
||||
}
|
||||
|
||||
virtual int handleKey(int key){
|
||||
// Code for keylock
|
||||
|
||||
if ( key == 1 ) {
|
||||
WhichDataCompass += 1;
|
||||
if ( WhichDataCompass > ShowCOG)
|
||||
WhichDataCompass = ShowHDM;
|
||||
return 0;
|
||||
}
|
||||
if ( key == 2 ) {
|
||||
WhichDataDisplay += 1;
|
||||
if ( WhichDataDisplay > ShowDBS)
|
||||
WhichDataDisplay = ShowHDM;
|
||||
}
|
||||
|
||||
if(key == 11){
|
||||
commonData->keylock = !commonData->keylock;
|
||||
return 0; // Commit the key
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
virtual void displayPage(PageData &pageData){
|
||||
GwConfigHandler *config = commonData->config;
|
||||
GwLog *logger = commonData->logger;
|
||||
|
||||
// Old values for hold function
|
||||
static String OldDataText[HowManyValues] = {"", "", "","", "", ""};
|
||||
static String OldDataUnits[HowManyValues] = {"", "", "","", "", ""};
|
||||
|
||||
// Get config data
|
||||
String lengthformat = config->getString(config->lengthFormat);
|
||||
// bool simulation = config->getBool(config->useSimuData);
|
||||
bool holdvalues = config->getBool(config->holdvalues);
|
||||
String flashLED = config->getString(config->flashLED);
|
||||
String backlightMode = config->getString(config->backlight);
|
||||
|
||||
GwApi::BoatValue *bvalue;
|
||||
String DataName[HowManyValues];
|
||||
double DataValue[HowManyValues];
|
||||
bool DataValid[HowManyValues];
|
||||
String DataText[HowManyValues];
|
||||
String DataUnits[HowManyValues];
|
||||
String DataFormat[HowManyValues];
|
||||
FormatedData TheFormattedData;
|
||||
|
||||
for (int i = 0; i < HowManyValues; i++){
|
||||
bvalue = pageData.values[i];
|
||||
TheFormattedData = formatValue(bvalue, *commonData);
|
||||
DataName[i] = xdrDelete(bvalue->getName());
|
||||
DataName[i] = DataName[i].substring(0, 6); // String length limit for value name
|
||||
DataUnits[i] = formatValue(bvalue, *commonData).unit;
|
||||
DataText[i] = TheFormattedData.svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
DataValue[i] = TheFormattedData.value; // Value as double in SI unit
|
||||
DataValid[i] = bvalue->valid;
|
||||
DataFormat[i] = bvalue->getFormat(); // Unit of value
|
||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageCompass: %d %s %f %s %s", i, DataName[i], DataValue[i], DataFormat[i], DataText[i] );
|
||||
}
|
||||
|
||||
// Optical warning by limit violation (unused)
|
||||
if(String(flashLED) == "Limit Violation"){
|
||||
setBlinkingLED(false);
|
||||
setFlashLED(false);
|
||||
}
|
||||
|
||||
if (bvalue == NULL) return;
|
||||
|
||||
//***********************************************************
|
||||
|
||||
// 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);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setCursor(10, 70);
|
||||
getdisplay().print(DataName[WhichDataDisplay]); // Page name
|
||||
// Show unit
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(10, 120);
|
||||
getdisplay().print(DataUnits[WhichDataDisplay]);
|
||||
getdisplay().setCursor(190, 120);
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic42pt7b);
|
||||
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(DataText[WhichDataDisplay]); // Real value as formated string
|
||||
}
|
||||
else{
|
||||
getdisplay().print(OldDataText[WhichDataDisplay]); // Old value as formated string
|
||||
}
|
||||
if(DataValid[WhichDataDisplay] == true){
|
||||
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];
|
||||
static double AvgAngle = 0;
|
||||
AvgAngle = ( AvgAngle * AverageValues + TheAngle ) / (AverageValues + 1 );
|
||||
|
||||
int TheTrend = round( ( TheAngle - AvgAngle) * 180.0 / M_PI );
|
||||
|
||||
static const int bsize = 30;
|
||||
char buffer[bsize+1];
|
||||
buffer[0]=0;
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold16pt7b);
|
||||
getdisplay().setCursor(10, Compass_Y0-60);
|
||||
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);
|
||||
// Draw trendlines
|
||||
for ( int i = 1; i < abs(TheTrend) / 2; i++){
|
||||
int x1;
|
||||
if ( TheTrend < 0 )
|
||||
x1 = Compass_X0 + 20 * i;
|
||||
else
|
||||
x1 = Compass_X0 - 20 * ( i + 1 );
|
||||
|
||||
getdisplay().fillRect(x1, Compass_Y0 -55, 10, 6, commonData->fgcolor);
|
||||
}
|
||||
// Central line + satellite lines
|
||||
double NextSector = round(TheAngle / ( M_PI / 9 )) * ( M_PI / 9 ); // Get the next 20degree value
|
||||
double Offset = - ( NextSector - TheAngle); // Offest of the center line compared to TheAngle in Radian
|
||||
|
||||
int Delta_X = int ( Offset * 180.0 / M_PI * Compass_LineDelta );
|
||||
for ( int i = 0; i <=4; i++ ){
|
||||
int x0;
|
||||
x0 = Compass_X0 + Delta_X + 2 * i * 5 * Compass_LineDelta;
|
||||
getdisplay().fillRect(x0-2, Compass_Y0 - 2 * Compass_LineLength, 5, 2 * Compass_LineLength, commonData->fgcolor);
|
||||
x0 = Compass_X0 + Delta_X + ( 2 * i + 1 ) * 5 * Compass_LineDelta;
|
||||
getdisplay().fillRect(x0-1, Compass_Y0 - Compass_LineLength, 3, Compass_LineLength, commonData->fgcolor);
|
||||
|
||||
x0 = Compass_X0 + Delta_X - 2 * i * 5 * Compass_LineDelta;
|
||||
getdisplay().fillRect(x0-2, Compass_Y0 - 2 * Compass_LineLength, 5, 2 * Compass_LineLength, commonData->fgcolor);
|
||||
x0 = Compass_X0 + Delta_X - ( 2 * i + 1 ) * 5 * Compass_LineDelta;
|
||||
getdisplay().fillRect(x0-1, Compass_Y0 - Compass_LineLength, 3, Compass_LineLength, commonData->fgcolor);
|
||||
}
|
||||
|
||||
getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor);
|
||||
// Add the numbers to the compass band
|
||||
int x0;
|
||||
float AngleToDisplay = NextSector * 180.0 / M_PI;
|
||||
|
||||
x0 = Compass_X0 + Delta_X;
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||
|
||||
do {
|
||||
getdisplay().setCursor(x0 - 40, Compass_Y0 + 40);
|
||||
snprintf(buffer,bsize,"%03.0f", AngleToDisplay);
|
||||
getdisplay().print(buffer);
|
||||
AngleToDisplay += 20;
|
||||
if ( AngleToDisplay >= 360.0 )
|
||||
AngleToDisplay -= 360.0;
|
||||
x0 -= 4 * 5 * Compass_LineDelta;
|
||||
} while ( x0 >= 0 - 60 );
|
||||
|
||||
AngleToDisplay = NextSector * 180.0 / M_PI - 20;
|
||||
if ( AngleToDisplay < 0 )
|
||||
AngleToDisplay += 360.0;
|
||||
|
||||
x0 = Compass_X0 + Delta_X + 4 * 5 * Compass_LineDelta;
|
||||
do {
|
||||
getdisplay().setCursor(x0 - 40, Compass_Y0 + 40);
|
||||
snprintf(buffer,bsize,"%03.0f", AngleToDisplay);
|
||||
// Quick and dirty way to prevent wrapping text in next line
|
||||
if ( ( x0 - 40 ) > 380 )
|
||||
buffer[0] = 0;
|
||||
else if ( ( x0 - 40 ) > 355 )
|
||||
buffer[1] = 0;
|
||||
else if ( ( x0 - 40 ) > 325 )
|
||||
buffer[2] = 0;
|
||||
|
||||
getdisplay().print(buffer);
|
||||
|
||||
AngleToDisplay -= 20;
|
||||
if ( AngleToDisplay < 0 )
|
||||
AngleToDisplay += 360.0;
|
||||
x0 += 4 * 5 * Compass_LineDelta;
|
||||
} while (x0 < ( 400 - 20 -40 ) );
|
||||
|
||||
// static int x_test = 320;
|
||||
// x_test += 2;
|
||||
|
||||
// snprintf(buffer,bsize,"%03d", x_test);
|
||||
// getdisplay().setCursor(x_test, Compass_Y0 - 60);
|
||||
// getdisplay().print(buffer);
|
||||
// if ( x_test > 390)
|
||||
// x_test = 320;
|
||||
|
||||
// Update display
|
||||
getdisplay().nextPage(); // Partial update (fast)
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
static Page *createPage(CommonData &common){
|
||||
return new PageCompass(common);
|
||||
}/**
|
||||
* with the code below we make this page known to the PageTask
|
||||
* we give it a type (name) that can be selected in the config
|
||||
* we define which function is to be called
|
||||
* and we provide the number of user parameters we expect
|
||||
* this will be number of BoatValue pointers in pageData.values
|
||||
*/
|
||||
PageDescription registerPageCompass(
|
||||
"Compass", // Page name
|
||||
createPage, // Action
|
||||
0, // Number of bus values depends on selection in Web configuration
|
||||
{"HDM","HDT", "COG", "STW", "SOG", "DBS"}, // Bus values we need in the page
|
||||
true // Show display header on/off
|
||||
);
|
||||
|
||||
#endif
|
|
@ -66,6 +66,10 @@ static unsigned char fish_bits[] = {
|
|||
|
||||
class PageFluid : public Page
|
||||
{
|
||||
bool simulation = false;
|
||||
double simgoto;
|
||||
double simval;
|
||||
double simstep;
|
||||
bool holdvalues = false;
|
||||
int fluidtype;
|
||||
|
||||
|
@ -73,7 +77,11 @@ class PageFluid : public Page
|
|||
PageFluid(CommonData &common){
|
||||
commonData = &common;
|
||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageFluid");
|
||||
simulation = common.config->getBool(common.config->useSimuData);
|
||||
holdvalues = common.config->getBool(common.config->holdvalues);
|
||||
simval = double(random(0, 100));
|
||||
simgoto = double(random(0, 100));
|
||||
simstep = (simgoto - simval) / 20.0;
|
||||
}
|
||||
|
||||
virtual int handleKey(int key){
|
||||
|
@ -109,8 +117,18 @@ class PageFluid : public Page
|
|||
|
||||
GwApi::BoatValue *bvalue1 = pageData.values[0];
|
||||
String name1 = bvalue1->getName();
|
||||
if (holdvalues and bvalue1->valid) {
|
||||
value1old = bvalue1->value;
|
||||
double fluidlevel = bvalue1->value;
|
||||
if (!simulation) {
|
||||
if (holdvalues and bvalue1->valid) {
|
||||
value1old = bvalue1->value;
|
||||
}
|
||||
} else {
|
||||
fluidlevel = simval;
|
||||
simval += simstep;
|
||||
if ((simgoto - simval) < 1.5 * simstep) {
|
||||
simgoto = double(random(0, 100));
|
||||
simstep = (simgoto - simval) / 20.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Logging boat values
|
||||
|
@ -148,8 +166,8 @@ class PageFluid : public Page
|
|||
|
||||
// value down centered
|
||||
char buffer[6];
|
||||
if (bvalue1->valid) {
|
||||
snprintf(buffer, 6, "%3.0f%%", bvalue1->value);
|
||||
if (bvalue1->valid or simulation) {
|
||||
snprintf(buffer, 6, "%3.0f%%", fluidlevel);
|
||||
} else {
|
||||
strcpy(buffer, "---");
|
||||
}
|
||||
|
@ -222,14 +240,14 @@ class PageFluid : public Page
|
|||
}
|
||||
|
||||
// pointer
|
||||
if (bvalue1->valid) {
|
||||
if (bvalue1->valid or simulation) {
|
||||
pts = {
|
||||
{c.x - 1, c.y - (r - 20)},
|
||||
{c.x + 1, c.y - (r - 20)},
|
||||
{c.x + 6, c.y + 15},
|
||||
{c.x - 6, c.y + 15}
|
||||
};
|
||||
fillPoly4(rotatePoints(c, pts, -120 + bvalue1->value * 2.4), commonData->fgcolor);
|
||||
fillPoly4(rotatePoints(c, pts, -120 + fluidlevel * 2.4), commonData->fgcolor);
|
||||
// Pointer axis is white
|
||||
getdisplay().fillCircle(c.x, c.y, 6, commonData->bgcolor);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageFourValues : public Page
|
||||
{
|
||||
|
@ -45,6 +46,7 @@ class PageFourValues : public Page
|
|||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -54,6 +56,7 @@ class PageFourValues : public Page
|
|||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue)
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -63,6 +66,7 @@ class PageFourValues : public Page
|
|||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue)
|
||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||
double value3 = bvalue3->value; // Value as double in SI unit
|
||||
bool valid3 = bvalue3->valid; // Valid information
|
||||
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -72,6 +76,7 @@ class PageFourValues : public Page
|
|||
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Second element in list (only one value by PageOneValue)
|
||||
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||
name4 = name4.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
||||
double value4 = bvalue4->value; // Value as double in SI unit
|
||||
bool valid4 = bvalue4->valid; // Valid information
|
||||
String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageFourValues2 : public Page
|
||||
{
|
||||
|
@ -45,6 +46,7 @@ class PageFourValues2 : public Page
|
|||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -54,6 +56,7 @@ class PageFourValues2 : public Page
|
|||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue)
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -63,6 +66,7 @@ class PageFourValues2 : public Page
|
|||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue)
|
||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||
double value3 = bvalue3->value; // Value as double in SI unit
|
||||
bool valid3 = bvalue3->valid; // Valid information
|
||||
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -72,6 +76,7 @@ class PageFourValues2 : public Page
|
|||
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Second element in list (only one value by PageOneValue)
|
||||
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||
name4 = name4.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
||||
double value4 = bvalue4->value; // Value as double in SI unit
|
||||
bool valid4 = bvalue4->valid; // Valid information
|
||||
String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -98,7 +103,7 @@ class PageFourValues2 : public Page
|
|||
// ############### Value 1 ################
|
||||
|
||||
// Show name
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(20, 55);
|
||||
getdisplay().print(name1); // Page name
|
||||
|
||||
|
@ -146,7 +151,7 @@ class PageFourValues2 : public Page
|
|||
// ############### Value 2 ################
|
||||
|
||||
// Show name
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(20, 145);
|
||||
getdisplay().print(name2); // Page name
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageOneValue : public Page
|
||||
{
|
||||
|
@ -39,6 +40,7 @@ class PageOneValue : public Page
|
|||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -67,7 +69,7 @@ class PageOneValue : public Page
|
|||
getdisplay().print(name1); // Page name
|
||||
|
||||
// Show unit
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(270, 100);
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(unit1); // Unit
|
||||
|
@ -78,7 +80,7 @@ class PageOneValue : public Page
|
|||
|
||||
// Switch font if format for any values
|
||||
if(bvalue1->getFormat() == "formatLatitude" || bvalue1->getFormat() == "formatLongitude"){
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(20, 180);
|
||||
}
|
||||
else if(bvalue1->getFormat() == "formatTime" || bvalue1->getFormat() == "formatDate"){
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageRudderPosition : public Page
|
||||
{
|
||||
|
@ -40,24 +41,25 @@ public:
|
|||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list
|
||||
String name1 = bvalue1->getName().c_str(); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
value1 = bvalue1->value; // Raw value without unit convertion
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
|
||||
|
||||
if(valid1 == true){
|
||||
value1old = value1; // Save old value
|
||||
unit1old = unit1; // Save old unit
|
||||
} else {
|
||||
if(simulation == true){
|
||||
value1 = (3 + float(random(0, 50)) / 10.0)/360*2*PI;
|
||||
unit1 = "Deg";
|
||||
}
|
||||
else{
|
||||
value1 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(simulation == true){
|
||||
value1 = (3 + float(random(0, 50)) / 10.0)/360*2*PI;
|
||||
unit1 = "Deg";
|
||||
}
|
||||
else{
|
||||
value1 = 0;
|
||||
}
|
||||
|
||||
|
||||
// Optical warning by limit violation (unused)
|
||||
if(String(flashLED) == "Limit Violation"){
|
||||
setBlinkingLED(false);
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
#include "DSEG7Classic-BoldItalic26pt7b.h"
|
||||
|
||||
extern const GFXfont DSEG7Classic_BoldItalic30pt7b;
|
||||
|
||||
const int SixValues_x1 = 5;
|
||||
const int SixValues_DeltaX = 200;
|
||||
|
||||
const int SixValues_y1 = 23;
|
||||
const int SixValues_DeltaY = 83;
|
||||
|
||||
const int HowManyValues = 6;
|
||||
|
||||
class PageSixValues : public Page
|
||||
{
|
||||
public:
|
||||
PageSixValues(CommonData &common){
|
||||
commonData = &common;
|
||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageSixValues");
|
||||
}
|
||||
|
||||
virtual int handleKey(int key){
|
||||
// Code for keylock
|
||||
if(key == 11){
|
||||
commonData->keylock = !commonData->keylock;
|
||||
return 0; // Commit the key
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
virtual void displayPage(PageData &pageData){
|
||||
GwConfigHandler *config = commonData->config;
|
||||
GwLog *logger = commonData->logger;
|
||||
|
||||
|
||||
// Old values for hold function
|
||||
static String OldDataText[HowManyValues] = {"", "", "", "", "", ""};
|
||||
static String OldDataUnits[HowManyValues] = {"", "", "", "", "", ""};
|
||||
|
||||
// Get config data
|
||||
String lengthformat = config->getString(config->lengthFormat);
|
||||
// bool simulation = config->getBool(config->useSimuData);
|
||||
bool holdvalues = config->getBool(config->holdvalues);
|
||||
String flashLED = config->getString(config->flashLED);
|
||||
String backlightMode = config->getString(config->backlight);
|
||||
|
||||
GwApi::BoatValue *bvalue;
|
||||
String DataName[HowManyValues];
|
||||
double DataValue[HowManyValues];
|
||||
bool DataValid[HowManyValues];
|
||||
String DataText[HowManyValues];
|
||||
String DataUnits[HowManyValues];
|
||||
String DataFormat[HowManyValues];
|
||||
|
||||
for (int i = 0; i < HowManyValues; i++){
|
||||
bvalue = pageData.values[i];
|
||||
DataName[i] = xdrDelete(bvalue->getName());
|
||||
DataName[i] = DataName[i].substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue, logger); // Check if boat data value is to be calibrated
|
||||
DataValue[i] = bvalue->value; // Value as double in SI unit
|
||||
DataValid[i] = bvalue->valid;
|
||||
DataText[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
DataUnits[i] = formatValue(bvalue, *commonData).unit;
|
||||
DataFormat[i] = bvalue->getFormat(); // Unit of value
|
||||
}
|
||||
|
||||
// Optical warning by limit violation (unused)
|
||||
if(String(flashLED) == "Limit Violation"){
|
||||
setBlinkingLED(false);
|
||||
setFlashLED(false);
|
||||
}
|
||||
|
||||
if (bvalue == NULL) return;
|
||||
|
||||
// Draw page
|
||||
//***********************************************************
|
||||
|
||||
// Set display in partial refresh mode
|
||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||
getdisplay().setTextColor(commonData->fgcolor);
|
||||
|
||||
for (int i = 0; i < ( HowManyValues / 2 ); i++){
|
||||
if (i < (HowManyValues / 2) - 1) { // Don't draw horizontal line after last line of values -> standard design
|
||||
// Horizontal line 3 pix
|
||||
getdisplay().fillRect(0, SixValues_y1+(i+1)*SixValues_DeltaY, 400, 3, commonData->fgcolor);
|
||||
}
|
||||
for (int j = 0; j < 2; j++){
|
||||
int ValueIndex = i * 2 + j;
|
||||
int x0 = SixValues_x1 + j * SixValues_DeltaX;
|
||||
int y0 = SixValues_y1 + i * SixValues_DeltaY;
|
||||
LOG_DEBUG(GwLog::LOG,"Drawing at PageSixValue: %d %s %f %s", ValueIndex, DataName[ValueIndex], DataValue[ValueIndex], DataFormat[ValueIndex] );
|
||||
|
||||
// Show name
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(x0, y0+25);
|
||||
getdisplay().print(DataName[ValueIndex]); // Page name
|
||||
|
||||
// Show unit
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(x0, y0+72);
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(DataUnits[ValueIndex]); // Unit
|
||||
}
|
||||
else{
|
||||
getdisplay().print(OldDataUnits[ValueIndex]);
|
||||
}
|
||||
|
||||
// Switch font if format for any values
|
||||
if(DataFormat[ValueIndex] == "formatLatitude" || DataFormat[ValueIndex] == "formatLongitude"){
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(x0+10, y0+60);
|
||||
}
|
||||
else if(DataFormat[ValueIndex] == "formatTime" || DataFormat[ValueIndex] == "formatDate"){
|
||||
getdisplay().setFont(&Ubuntu_Bold16pt7b);
|
||||
getdisplay().setCursor(x0+20,y0+55);
|
||||
}
|
||||
// pressure in hPa
|
||||
else if(DataFormat[ValueIndex] == "formatXdr:P:P"){
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic26pt7b);
|
||||
getdisplay().setCursor(x0+5, y0+70);
|
||||
}
|
||||
// RPM
|
||||
else if(DataFormat[ValueIndex] == "formatXdr:T:R"){
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||
getdisplay().setCursor(x0+25, y0+70);
|
||||
}
|
||||
else{
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic26pt7b);
|
||||
if ( DataText[ValueIndex][0] == '-' )
|
||||
getdisplay().setCursor(x0+25, y0+70);
|
||||
else
|
||||
getdisplay().setCursor(x0+65, y0+70);
|
||||
}
|
||||
|
||||
// Show bus data
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(DataText[ValueIndex]); // Real value as formated string
|
||||
}
|
||||
else{
|
||||
getdisplay().print(OldDataText[ValueIndex]); // Old value as formated string
|
||||
}
|
||||
if(DataValid[ValueIndex] == true){
|
||||
OldDataText[ValueIndex] = DataText[ValueIndex]; // Save the old value
|
||||
OldDataUnits[ValueIndex] = DataUnits[ValueIndex]; // Save the old unit
|
||||
}
|
||||
}
|
||||
// Vertical line 3 pix
|
||||
getdisplay().fillRect(SixValues_x1+SixValues_DeltaX-8, SixValues_y1+i*SixValues_DeltaY, 3, SixValues_DeltaY, commonData->fgcolor);
|
||||
}
|
||||
|
||||
// Update display
|
||||
getdisplay().nextPage(); // Partial update (fast)
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
static Page *createPage(CommonData &common){
|
||||
return new PageSixValues(common);
|
||||
}/**
|
||||
* with the code below we make this page known to the PageTask
|
||||
* we give it a type (name) that can be selected in the config
|
||||
* we define which function is to be called
|
||||
* and we provide the number of user parameters we expect
|
||||
* this will be number of BoatValue pointers in pageData.values
|
||||
*/
|
||||
PageDescription registerPageSixValues(
|
||||
"SixValues", // Page name
|
||||
createPage, // Action
|
||||
6, // Number of bus values depends on selection in Web configuration
|
||||
true // Show display header on/off
|
||||
);
|
||||
|
||||
#endif
|
|
@ -6,19 +6,32 @@
|
|||
#include <esp32/clk.h>
|
||||
#include "qrcode.h"
|
||||
|
||||
#ifdef BOARD_OBP40S3
|
||||
#include <SD.h>
|
||||
#include <FS.h>
|
||||
#endif
|
||||
|
||||
#define STRINGIZE_IMPL(x) #x
|
||||
#define STRINGIZE(x) STRINGIZE_IMPL(x)
|
||||
#define VERSINFO STRINGIZE(GWDEVVERSION)
|
||||
#define BOARDINFO STRINGIZE(BOARD)
|
||||
#define PCBINFO STRINGIZE(PCBVERS)
|
||||
#define DISPLAYINFO STRINGIZE(EPDTYPE)
|
||||
|
||||
/*
|
||||
* Special system page, called directly with fast key sequence 5,4
|
||||
* Out of normal page order.
|
||||
* Consists of some sub-pages with following content:
|
||||
* 1. Hard and software information
|
||||
* 2. System settings
|
||||
* 3. NMEA2000 device list
|
||||
*/
|
||||
|
||||
class PageSystem : public Page
|
||||
{
|
||||
uint64_t chipid;
|
||||
bool simulation;
|
||||
bool sdcard;
|
||||
String buzzer_mode;
|
||||
uint8_t buzzer_power;
|
||||
String cpuspeed;
|
||||
|
@ -26,7 +39,14 @@ String rtc_module;
|
|||
String gps_module;
|
||||
String env_module;
|
||||
|
||||
char mode = 'N'; // (N)ormal, (D)evice list
|
||||
String batt_sensor;
|
||||
String solar_sensor;
|
||||
String gen_sensor;
|
||||
String rot_sensor;
|
||||
double homelat;
|
||||
double homelon;
|
||||
|
||||
char mode = 'N'; // (N)ormal, (S)ettings, (D)evice list, (C)ard
|
||||
|
||||
public:
|
||||
PageSystem(CommonData &common){
|
||||
|
@ -37,12 +57,22 @@ public:
|
|||
}
|
||||
chipid = ESP.getEfuseMac();
|
||||
simulation = common.config->getBool(common.config->useSimuData);
|
||||
#ifdef BOARD_OBP40S3
|
||||
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);
|
||||
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();
|
||||
}
|
||||
|
||||
virtual void setupKeys(){
|
||||
|
@ -60,7 +90,15 @@ public:
|
|||
commonData->logger->logDebug(GwLog::LOG, "System keyboard handler");
|
||||
if (key == 2) {
|
||||
if (mode == 'N') {
|
||||
mode = 'S';
|
||||
} else if (mode == 'S') {
|
||||
mode = 'D';
|
||||
} else if (mode == 'D') {
|
||||
if (sdcard) {
|
||||
mode = 'C';
|
||||
} else {
|
||||
mode = 'N';
|
||||
}
|
||||
} else {
|
||||
mode = 'N';
|
||||
}
|
||||
|
@ -105,12 +143,12 @@ public:
|
|||
// s is pixel size of a single box
|
||||
QRCode qrcode;
|
||||
uint8_t qrcodeData[qrcode_getBufferSize(4)];
|
||||
#ifdef BOARD_OBP40S3
|
||||
#ifdef BOARD_OBP40S3
|
||||
String prefix = "OBP40:SN:";
|
||||
#endif
|
||||
#ifdef BOARD_OBP60S3
|
||||
#endif
|
||||
#ifdef BOARD_OBP60S3
|
||||
String prefix = "OBP60:SN:";
|
||||
#endif
|
||||
#endif
|
||||
qrcode_initText(&qrcode, qrcodeData, 4, 0, (prefix + serialno).c_str());
|
||||
int16_t x0 = x;
|
||||
for (uint8_t j = 0; j < qrcode.size; j++) {
|
||||
|
@ -144,101 +182,199 @@ public:
|
|||
// Draw page
|
||||
//***********************************************************
|
||||
|
||||
const uint16_t y0 = 120; // data table starts here
|
||||
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') {
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(8, 50);
|
||||
getdisplay().setCursor(8, 48);
|
||||
getdisplay().print("System Information");
|
||||
|
||||
getdisplay().drawXBitmap(320, 25, logo64_bits, logo64_width, logo64_height, commonData->fgcolor);
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
y0 = 155;
|
||||
|
||||
char ssid[13];
|
||||
snprintf(ssid, 13, "%04X%08X", (uint16_t)(chipid >> 32), (uint32_t)chipid);
|
||||
displayBarcode(String(ssid), 320, 200, 2);
|
||||
getdisplay().setCursor(8, 70);
|
||||
getdisplay().print(String("MUDEVICE-") + String(ssid));
|
||||
getdisplay().print(String("MCUDEVICE-") + String(ssid));
|
||||
|
||||
getdisplay().setCursor(8, 90);
|
||||
getdisplay().print("Firmware Version: ");
|
||||
getdisplay().setCursor(8, 95);
|
||||
getdisplay().print("Firmware version: ");
|
||||
getdisplay().setCursor(160, 95);
|
||||
getdisplay().print(VERSINFO);
|
||||
|
||||
getdisplay().setCursor(8, 113);
|
||||
getdisplay().print("Board version: ");
|
||||
getdisplay().setCursor(160, 113);
|
||||
getdisplay().print(BOARDINFO);
|
||||
getdisplay().print(String(" HW ") + String(PCBINFO));
|
||||
|
||||
getdisplay().setCursor(8, 131);
|
||||
getdisplay().print("Display version: ");
|
||||
getdisplay().setCursor(160, 131);
|
||||
getdisplay().print(DISPLAYINFO);
|
||||
|
||||
getdisplay().setCursor(8, 265);
|
||||
#ifdef BOARD_OBP60S3
|
||||
#ifdef BOARD_OBP60S3
|
||||
getdisplay().print("Press STBY to enter deep sleep mode");
|
||||
#endif
|
||||
#ifdef BOARD_OBP40S3
|
||||
#endif
|
||||
#ifdef BOARD_OBP40S3
|
||||
getdisplay().print("Press wheel to enter deep sleep mode");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
getdisplay().setCursor(2, y0);
|
||||
getdisplay().print("Simulation:");
|
||||
getdisplay().setCursor(120, y0);
|
||||
getdisplay().print(simulation ? "on" : "off");
|
||||
// Flash memory size
|
||||
uint32_t flash_size = ESP.getFlashChipSize();
|
||||
getdisplay().setCursor(8, y0);
|
||||
getdisplay().print("FLASH:");
|
||||
getdisplay().setCursor(90, y0);
|
||||
getdisplay().print(String(flash_size / 1024) + String(" kB"));
|
||||
|
||||
getdisplay().setCursor(2, y0 + 16);
|
||||
getdisplay().print("Environment:");
|
||||
getdisplay().setCursor(120, y0 + 16);
|
||||
getdisplay().print(env_module);
|
||||
|
||||
// total RAM free
|
||||
int Heap_free = esp_get_free_heap_size();
|
||||
getdisplay().setCursor(202, y0);
|
||||
getdisplay().print("Total free:");
|
||||
getdisplay().setCursor(300, y0);
|
||||
getdisplay().print(String(Heap_free));
|
||||
|
||||
getdisplay().setCursor(2, y0 + 32);
|
||||
getdisplay().print("Buzzer:");
|
||||
getdisplay().setCursor(120, y0 + 32);
|
||||
getdisplay().print(buzzer_mode);
|
||||
|
||||
// RAM free for task
|
||||
int RAM_free = uxTaskGetStackHighWaterMark(NULL);
|
||||
getdisplay().setCursor(202, y0 + 16);
|
||||
getdisplay().print("Task free:");
|
||||
getdisplay().setCursor(300, y0 + 16);
|
||||
getdisplay().print(String(RAM_free));
|
||||
// PSRAM memory size
|
||||
uint32_t psram_size = ESP.getPsramSize();
|
||||
getdisplay().setCursor(8, y0 + 16);
|
||||
getdisplay().print("PSRAM:");
|
||||
getdisplay().setCursor(90, y0 + 16);
|
||||
getdisplay().print(String(psram_size / 1024) + String(" kB"));
|
||||
|
||||
// FRAM available / status
|
||||
getdisplay().setCursor(202, y0 + 32);
|
||||
getdisplay().setCursor(8, y0 + 32);
|
||||
getdisplay().print("FRAM:");
|
||||
getdisplay().setCursor(300, y0 + 32);
|
||||
getdisplay().setCursor(90, y0 + 32);
|
||||
getdisplay().print(hasFRAM ? "available" : "not found");
|
||||
|
||||
getdisplay().setCursor(202, y0 + 64);
|
||||
#ifdef BOARD_OBP40S3
|
||||
// SD-Card
|
||||
getdisplay().setCursor(8, y0 + 48);
|
||||
getdisplay().print("SD-Card:");
|
||||
getdisplay().setCursor(90, y0 + 48);
|
||||
if (sdcard) {
|
||||
uint64_t cardsize = SD.cardSize() / (1024 * 1024);
|
||||
getdisplay().print(String(cardsize) + String(" MB"));
|
||||
} else {
|
||||
getdisplay().print("off");
|
||||
}
|
||||
#endif
|
||||
|
||||
// CPU speed config / active
|
||||
getdisplay().setCursor(202, y0);
|
||||
getdisplay().print("CPU speed:");
|
||||
getdisplay().setCursor(300, y0 + 64);
|
||||
getdisplay().setCursor(300, y0);
|
||||
getdisplay().print(cpuspeed);
|
||||
getdisplay().print(" / ");
|
||||
int cpu_freq = esp_clk_cpu_freq() / 1000000;
|
||||
getdisplay().print(String(cpu_freq));
|
||||
|
||||
getdisplay().setCursor(2, y0 + 64);
|
||||
// total RAM free
|
||||
int Heap_free = esp_get_free_heap_size();
|
||||
getdisplay().setCursor(202, y0 + 16);
|
||||
getdisplay().print("Total free:");
|
||||
getdisplay().setCursor(300, y0 + 16);
|
||||
getdisplay().print(String(Heap_free));
|
||||
|
||||
// RAM free for task
|
||||
int RAM_free = uxTaskGetStackHighWaterMark(NULL);
|
||||
getdisplay().setCursor(202, y0 + 32);
|
||||
getdisplay().print("Task free:");
|
||||
getdisplay().setCursor(300, y0 + 32);
|
||||
getdisplay().print(String(RAM_free));
|
||||
|
||||
} else if (mode == 'S') {
|
||||
// Settings
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(x0, 48);
|
||||
getdisplay().print("System settings");
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
x0 = 8;
|
||||
y0 = 72;
|
||||
|
||||
// left column
|
||||
getdisplay().setCursor(x0, y0);
|
||||
getdisplay().print("Simulation:");
|
||||
getdisplay().setCursor(120, y0);
|
||||
getdisplay().print(simulation ? "on" : "off");
|
||||
|
||||
getdisplay().setCursor(x0, y0 + 16);
|
||||
getdisplay().print("Environment:");
|
||||
getdisplay().setCursor(120, y0 + 16);
|
||||
getdisplay().print(env_module);
|
||||
|
||||
getdisplay().setCursor(x0, y0 + 32);
|
||||
getdisplay().print("Buzzer:");
|
||||
getdisplay().setCursor(120, y0 + 32);
|
||||
getdisplay().print(buzzer_mode);
|
||||
|
||||
getdisplay().setCursor(x0, y0 + 64);
|
||||
getdisplay().print("GPS:");
|
||||
getdisplay().setCursor(120, y0 + 64);
|
||||
getdisplay().print(gps_module);
|
||||
|
||||
getdisplay().setCursor(2, y0 + 80);
|
||||
getdisplay().setCursor(x0, y0 + 80);
|
||||
getdisplay().print("RTC:");
|
||||
getdisplay().setCursor(120, y0 + 80);
|
||||
getdisplay().print(rtc_module);
|
||||
|
||||
getdisplay().setCursor(2, y0 + 96);
|
||||
getdisplay().setCursor(x0, y0 + 96);
|
||||
getdisplay().print("Wifi:");
|
||||
getdisplay().setCursor(120, y0 + 96);
|
||||
getdisplay().print(commonData->status.wifiApOn ? "On" : "Off");
|
||||
getdisplay().print(commonData->status.wifiApOn ? "on" : "off");
|
||||
|
||||
// Home location
|
||||
getdisplay().setCursor(x0, y0 + 128);
|
||||
getdisplay().print("Home Lat.:");
|
||||
getdisplay().setCursor(120, y0 + 128);
|
||||
getdisplay().print(formatLatitude(homelat));
|
||||
getdisplay().setCursor(x0, y0 + 144);
|
||||
getdisplay().print("Home Lon.:");
|
||||
getdisplay().setCursor(120, y0 + 144);
|
||||
getdisplay().print(formatLongitude(homelon));
|
||||
|
||||
// right column
|
||||
getdisplay().setCursor(202, y0);
|
||||
getdisplay().print("Batt. sensor:");
|
||||
getdisplay().setCursor(320, y0);
|
||||
getdisplay().print(batt_sensor);
|
||||
|
||||
// Solar sensor
|
||||
getdisplay().setCursor(202, y0 + 16);
|
||||
getdisplay().print("Solar sensor:");
|
||||
getdisplay().setCursor(320, y0 + 16);
|
||||
getdisplay().print(solar_sensor);
|
||||
|
||||
// Generator sensor
|
||||
getdisplay().setCursor(202, y0 + 32);
|
||||
getdisplay().print("Gen. sensor:");
|
||||
getdisplay().setCursor(320, y0 + 32);
|
||||
getdisplay().print(gen_sensor);
|
||||
|
||||
// Gyro sensor
|
||||
|
||||
} else if (mode == 'C') {
|
||||
// Card info
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(8, 48);
|
||||
getdisplay().print("SD Card info");
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
|
||||
x0 = 20;
|
||||
y0 = 72;
|
||||
getdisplay().setCursor(x0, y0);
|
||||
getdisplay().print("Work in progress...");
|
||||
|
||||
|
||||
} else {
|
||||
// NMEA2000 device list
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(20, 50);
|
||||
getdisplay().setCursor(8, 48);
|
||||
getdisplay().print("NMEA2000 device list");
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageThreeValues : public Page
|
||||
{
|
||||
|
@ -43,6 +44,7 @@ class PageThreeValues : public Page
|
|||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -52,6 +54,7 @@ class PageThreeValues : public Page
|
|||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue)
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -61,6 +64,7 @@ class PageThreeValues : public Page
|
|||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue)
|
||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||
double value3 = bvalue3->value; // Value as double in SI unit
|
||||
bool valid3 = bvalue3->valid; // Valid information
|
||||
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -86,7 +90,7 @@ class PageThreeValues : public Page
|
|||
|
||||
// Show name
|
||||
getdisplay().setTextColor(commonData->fgcolor);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(20, 55);
|
||||
getdisplay().print(name1); // Page name
|
||||
|
||||
|
@ -102,11 +106,11 @@ class PageThreeValues : public Page
|
|||
|
||||
// Switch font if format for any values
|
||||
if(bvalue1->getFormat() == "formatLatitude" || bvalue1->getFormat() == "formatLongitude"){
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(50, 90);
|
||||
}
|
||||
else if(bvalue1->getFormat() == "formatTime" || bvalue1->getFormat() == "formatDate"){
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(170, 68);
|
||||
}
|
||||
else{
|
||||
|
@ -134,7 +138,7 @@ class PageThreeValues : public Page
|
|||
// ############### Value 2 ################
|
||||
|
||||
// Show name
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(20, 145);
|
||||
getdisplay().print(name2); // Page name
|
||||
|
||||
|
@ -150,11 +154,11 @@ class PageThreeValues : public Page
|
|||
|
||||
// Switch font if format for any values
|
||||
if(bvalue2->getFormat() == "formatLatitude" || bvalue2->getFormat() == "formatLongitude"){
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(50, 180);
|
||||
}
|
||||
else if(bvalue2->getFormat() == "formatTime" || bvalue2->getFormat() == "formatDate"){
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(170, 158);
|
||||
}
|
||||
else{
|
||||
|
@ -182,7 +186,7 @@ class PageThreeValues : public Page
|
|||
// ############### Value 3 ################
|
||||
|
||||
// Show name
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(20, 235);
|
||||
getdisplay().print(name3); // Page name
|
||||
|
||||
|
@ -198,11 +202,11 @@ class PageThreeValues : public Page
|
|||
|
||||
// Switch font if format for any values
|
||||
if(bvalue3->getFormat() == "formatLatitude" || bvalue3->getFormat() == "formatLongitude"){
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(50, 270);
|
||||
}
|
||||
else if(bvalue3->getFormat() == "formatTime" || bvalue3->getFormat() == "formatDate"){
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(170, 248);
|
||||
}
|
||||
else{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageTwoValues : public Page
|
||||
{
|
||||
|
@ -41,6 +42,7 @@ class PageTwoValues : public Page
|
|||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -50,6 +52,7 @@ class PageTwoValues : public Page
|
|||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue)
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -75,7 +78,7 @@ class PageTwoValues : public Page
|
|||
|
||||
// Show name
|
||||
getdisplay().setTextColor(commonData->fgcolor);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(20, 80);
|
||||
getdisplay().print(name1); // Page name
|
||||
|
||||
|
@ -91,11 +94,11 @@ class PageTwoValues : public Page
|
|||
|
||||
// Switch font if format for any values
|
||||
if(bvalue1->getFormat() == "formatLatitude" || bvalue1->getFormat() == "formatLongitude"){
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(50, 130);
|
||||
}
|
||||
else if(bvalue1->getFormat() == "formatTime" || bvalue1->getFormat() == "formatDate"){
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(170, 105);
|
||||
}
|
||||
else{
|
||||
|
@ -123,7 +126,7 @@ class PageTwoValues : public Page
|
|||
// ############### Value 2 ################
|
||||
|
||||
// Show name
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(20, 190);
|
||||
getdisplay().print(name2); // Page name
|
||||
|
||||
|
@ -139,11 +142,11 @@ class PageTwoValues : public Page
|
|||
|
||||
// Switch font if format for any values
|
||||
if(bvalue2->getFormat() == "formatLatitude" || bvalue2->getFormat() == "formatLongitude"){
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(50, 240);
|
||||
}
|
||||
else if(bvalue2->getFormat() == "formatTime" || bvalue2->getFormat() == "formatDate"){
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(170, 215);
|
||||
}
|
||||
else{
|
||||
|
|
|
@ -205,6 +205,18 @@ public:
|
|||
getdisplay().setCursor(20, 100);
|
||||
getdisplay().print(name1); // Value name
|
||||
|
||||
#if defined BOARD_OBP40S3 && defined LIPO_ACCU_1200 && defined VOLTAGE_SENSOR
|
||||
// Show charge status
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(185, 100);
|
||||
if(commonData->data.BatteryChargeStatus == true){
|
||||
getdisplay().print("Charge");
|
||||
}
|
||||
else{
|
||||
getdisplay().print("Discharge");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Show unit
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setCursor(270, 100);
|
||||
|
@ -213,7 +225,12 @@ public:
|
|||
// Show battery type
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(295, 100);
|
||||
#ifdef BOARD_OBP60S3
|
||||
getdisplay().print(batType);
|
||||
#endif
|
||||
#if defined BOARD_OBP40S3 && defined LIPO_ACCU_1200 && defined VOLTAGE_SENSOR
|
||||
getdisplay().print("LiPo");
|
||||
#endif
|
||||
|
||||
// Show average settings
|
||||
printAvg(average, 320, 84, true);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "N2kMessages.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
#define front_width 120
|
||||
#define front_height 162
|
||||
|
@ -307,7 +308,7 @@ public:
|
|||
|
||||
// Get config data
|
||||
String lengthformat = config->getString(config->lengthFormat);
|
||||
// bool simulation = config->getBool(config->useSimuData);
|
||||
bool simulation = config->getBool(config->useSimuData);
|
||||
bool holdvalues = config->getBool(config->holdvalues);
|
||||
String flashLED = config->getString(config->flashLED);
|
||||
String backlightMode = config->getString(config->backlight);
|
||||
|
@ -323,6 +324,7 @@ public:
|
|||
}
|
||||
String name1 = bvalue1->getName().c_str(); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
// bool valid1 = bvalue1->valid; // Valid information
|
||||
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -336,8 +338,12 @@ public:
|
|||
}
|
||||
String name2 = bvalue2->getName().c_str(); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
// bool valid2 = bvalue2->valid; // Valid information
|
||||
if (simulation) {
|
||||
value2 = 0.62731; // some random value
|
||||
}
|
||||
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
|
||||
|
||||
|
@ -402,7 +408,7 @@ public:
|
|||
getdisplay().fillCircle(c.x, c.y, lp + 1, commonData->bgcolor);
|
||||
|
||||
// Wind pointer
|
||||
if (bvalue2->valid) {
|
||||
if (bvalue2->valid or simulation) {
|
||||
uint8_t lp0 = lp * 0.6; // effective pointer outside size
|
||||
uint8_t lp1 = lp * 0.4; // effective pointer inside size
|
||||
// zero position
|
||||
|
@ -478,7 +484,7 @@ public:
|
|||
}
|
||||
|
||||
// Wind pointer (angle)
|
||||
if (bvalue2->valid) {
|
||||
if (bvalue2->valid or simulation) {
|
||||
float alpha = RadToDeg(value2);
|
||||
bool port = (alpha > 180);
|
||||
if (port) {
|
||||
|
@ -594,7 +600,7 @@ public:
|
|||
getdisplay().print("kts");
|
||||
|
||||
// Wind pointer (angle)
|
||||
if (bvalue2->valid) {
|
||||
if (bvalue2->valid or simulation) {
|
||||
float alpha = RadToDeg(value2);
|
||||
getdisplay().fillCircle(c.x, c.y, 8, commonData->fgcolor);
|
||||
pts = {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageWindRose : public Page
|
||||
{
|
||||
|
@ -51,6 +52,7 @@ public:
|
|||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
value1 = formatValue(bvalue1, *commonData).value;// Format only nesaccery for simulation data for pointer
|
||||
|
@ -65,6 +67,7 @@ public:
|
|||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // First element in list (only one value by PageOneValue)
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -78,6 +81,7 @@ public:
|
|||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue)
|
||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||
double value3 = bvalue3->value; // Value as double in SI unit
|
||||
bool valid3 = bvalue3->valid; // Valid information
|
||||
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -91,6 +95,7 @@ public:
|
|||
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Second element in list (only one value by PageOneValue)
|
||||
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||
name4 = name4.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
||||
double value4 = bvalue4->value; // Value as double in SI unit
|
||||
bool valid4 = bvalue4->valid; // Valid information
|
||||
String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -104,6 +109,7 @@ public:
|
|||
GwApi::BoatValue *bvalue5 = pageData.values[4]; // Second element in list (only one value by PageOneValue)
|
||||
String name5 = xdrDelete(bvalue5->getName()); // Value name
|
||||
name5 = name5.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
|
||||
double value5 = bvalue5->value; // Value as double in SI unit
|
||||
bool valid5 = bvalue5->valid; // Valid information
|
||||
String svalue5 = formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -117,6 +123,7 @@ public:
|
|||
GwApi::BoatValue *bvalue6 = pageData.values[5]; // Second element in list (only one value by PageOneValue)
|
||||
String name6 = xdrDelete(bvalue6->getName()); // Value name
|
||||
name6 = name6.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
|
||||
double value6 = bvalue6->value; // Value as double in SI unit
|
||||
bool valid6 = bvalue6->valid; // Valid information
|
||||
String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageWindRoseFlex : public Page
|
||||
{
|
||||
|
@ -51,6 +52,7 @@ public:
|
|||
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
|
||||
String name1 = xdrDelete(bvalue1->getName()); // Value name
|
||||
name1 = name1.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
|
||||
double value1 = bvalue1->value; // Value as double in SI unit
|
||||
bool valid1 = bvalue1->valid; // Valid information
|
||||
value1 = formatValue(bvalue1, *commonData).value;// Format only nesaccery for simulation data for pointer
|
||||
|
@ -65,6 +67,7 @@ public:
|
|||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // First element in list (only one value by PageOneValue)
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -78,6 +81,7 @@ public:
|
|||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue)
|
||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||
double value3 = bvalue3->value; // Value as double in SI unit
|
||||
bool valid3 = bvalue3->valid; // Valid information
|
||||
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -91,6 +95,7 @@ public:
|
|||
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Second element in list (only one value by PageOneValue)
|
||||
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||
name4 = name4.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
||||
double value4 = bvalue4->value; // Value as double in SI unit
|
||||
bool valid4 = bvalue4->valid; // Valid information
|
||||
String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -104,6 +109,7 @@ public:
|
|||
GwApi::BoatValue *bvalue5 = pageData.values[4]; // Second element in list (only one value by PageOneValue)
|
||||
String name5 = xdrDelete(bvalue5->getName()); // Value name
|
||||
name5 = name5.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
|
||||
double value5 = bvalue5->value; // Value as double in SI unit
|
||||
bool valid5 = bvalue5->valid; // Valid information
|
||||
String svalue5 = formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -117,6 +123,7 @@ public:
|
|||
GwApi::BoatValue *bvalue6 = pageData.values[5]; // Second element in list (only one value by PageOneValue)
|
||||
String name6 = xdrDelete(bvalue6->getName()); // Value name
|
||||
name6 = name6.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
|
||||
double value6 = bvalue6->value; // Value as double in SI unit
|
||||
bool valid6 = bvalue6->valid; // Valid information
|
||||
String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
@ -144,35 +151,15 @@ public:
|
|||
|
||||
getdisplay().setTextColor(commonData->fgcolor);
|
||||
|
||||
// Show values AWA
|
||||
// Show value 2 at position of value 1 (top left)
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||
getdisplay().setCursor(10, 65);
|
||||
getdisplay().print(svalue1); // Value
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(10, 95);
|
||||
getdisplay().print(name1); // Name
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(10, 115);
|
||||
getdisplay().print(" ");
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(unit1); // Unit
|
||||
}
|
||||
else{
|
||||
getdisplay().print(unit1old); // Unit
|
||||
}
|
||||
|
||||
// Horizintal separator left
|
||||
getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor);
|
||||
|
||||
// Show values AWS
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||
getdisplay().setCursor(10, 270);
|
||||
getdisplay().print(svalue2); // Value
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(10, 220);
|
||||
getdisplay().setCursor(10, 95);
|
||||
getdisplay().print(name2); // Name
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(10, 190);
|
||||
getdisplay().setCursor(10, 115);
|
||||
getdisplay().print(" ");
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(unit2); // Unit
|
||||
|
@ -181,21 +168,18 @@ public:
|
|||
getdisplay().print(unit2old); // Unit
|
||||
}
|
||||
|
||||
// Show values TWD
|
||||
// Horizintal separator left
|
||||
getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor);
|
||||
|
||||
// Show value 3 at bottom left
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||
getdisplay().setCursor(295, 65);
|
||||
if(valid3 == true){
|
||||
// getdisplay().print(abs(value3 * 180 / PI), 0); // Value
|
||||
getdisplay().print(svalue3); // Value
|
||||
}
|
||||
else{
|
||||
getdisplay().print("---"); // Value
|
||||
}
|
||||
getdisplay().setCursor(10, 270);
|
||||
getdisplay().print(svalue3); // Value
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(335, 95);
|
||||
getdisplay().setCursor(10, 220);
|
||||
getdisplay().print(name3); // Name
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(335, 115);
|
||||
getdisplay().setCursor(10, 190);
|
||||
getdisplay().print(" ");
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(unit3); // Unit
|
||||
|
@ -204,18 +188,21 @@ public:
|
|||
getdisplay().print(unit3old); // Unit
|
||||
}
|
||||
|
||||
// Horizintal separator right
|
||||
getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor);
|
||||
|
||||
// Show values TWS
|
||||
// Show value 4 at top right
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||
getdisplay().setCursor(295, 270);
|
||||
getdisplay().print(svalue4); // Value
|
||||
getdisplay().setCursor(295, 65);
|
||||
if(valid3 == true){
|
||||
// getdisplay().print(abs(value3 * 180 / PI), 0); // Value
|
||||
getdisplay().print(svalue4); // Value
|
||||
}
|
||||
else{
|
||||
getdisplay().print("---"); // Value
|
||||
}
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(335, 220);
|
||||
getdisplay().setCursor(335, 95);
|
||||
getdisplay().print(name4); // Name
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(335, 190);
|
||||
getdisplay().setCursor(335, 115);
|
||||
getdisplay().print(" ");
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(unit4); // Unit
|
||||
|
@ -224,6 +211,27 @@ public:
|
|||
getdisplay().print(unit4old); // Unit
|
||||
}
|
||||
|
||||
// Horizintal separator right
|
||||
getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor);
|
||||
|
||||
// Show value 5 at bottom right
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b);
|
||||
getdisplay().setCursor(295, 270);
|
||||
getdisplay().print(svalue5); // Value
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt7b);
|
||||
getdisplay().setCursor(335, 220);
|
||||
getdisplay().print(name5); // Name
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(335, 190);
|
||||
getdisplay().print(" ");
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(unit5); // Unit
|
||||
}
|
||||
else{
|
||||
getdisplay().print(unit5old); // Unit
|
||||
}
|
||||
|
||||
|
||||
//*******************************************************************************************
|
||||
|
||||
// Draw wind rose
|
||||
|
@ -323,33 +331,36 @@ public:
|
|||
|
||||
//*******************************************************************************************
|
||||
|
||||
// Show values DBT
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||
getdisplay().setCursor(160, 200);
|
||||
getdisplay().print(svalue5); // Value
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(190, 215);
|
||||
getdisplay().print(" ");
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(unit5); // Unit
|
||||
}
|
||||
else{
|
||||
getdisplay().print(unit5old); // Unit
|
||||
}
|
||||
// Show value6, so that it does not collide with the wind pointer
|
||||
if ( cos(value1) > 0){
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||
getdisplay().setCursor(160, 200);
|
||||
getdisplay().print(svalue6); // Value
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(190, 215);
|
||||
getdisplay().print(" ");
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(unit6); // Unit
|
||||
}
|
||||
else{
|
||||
getdisplay().print(unit6old); // Unit
|
||||
}
|
||||
}
|
||||
else{
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||
getdisplay().setCursor(160, 130);
|
||||
getdisplay().print(svalue6); // Value
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(190, 90);
|
||||
getdisplay().print(" ");
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(unit6); // Unit
|
||||
}
|
||||
else{
|
||||
getdisplay().print(unit6old); // Unit
|
||||
}
|
||||
}
|
||||
|
||||
// Show values STW
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||
getdisplay().setCursor(160, 130);
|
||||
getdisplay().print(svalue6); // Value
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(190, 90);
|
||||
getdisplay().print(" ");
|
||||
if(holdvalues == false){
|
||||
getdisplay().print(unit6); // Unit
|
||||
}
|
||||
else{
|
||||
getdisplay().print(unit6old); // Unit
|
||||
}
|
||||
|
||||
// Update display
|
||||
getdisplay().nextPage(); // Partial update (fast)
|
||||
|
|
|
@ -28,10 +28,15 @@ static unsigned char ship_bits[] PROGMEM = {
|
|||
|
||||
class PageXTETrack : public Page
|
||||
{
|
||||
bool simulation = false;
|
||||
bool holdvalues = false;
|
||||
|
||||
public:
|
||||
PageXTETrack(CommonData &common){
|
||||
commonData = &common;
|
||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageXTETrack");
|
||||
simulation = common.config->getBool(common.config->useSimuData);
|
||||
holdvalues = common.config->getBool(common.config->holdvalues);
|
||||
}
|
||||
|
||||
void drawSegment(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1,
|
||||
|
@ -140,7 +145,7 @@ class PageXTETrack : public Page
|
|||
String sval_wpname = "no data";
|
||||
|
||||
if (valid) {
|
||||
sval_wpname = "Tonne 122";
|
||||
sval_wpname = "Tonne 122";
|
||||
}
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold10pt7b);
|
||||
|
@ -153,7 +158,12 @@ class PageXTETrack : public Page
|
|||
|
||||
// draw course segments
|
||||
|
||||
double diff = bv_cog->value - bv_btw->value;
|
||||
double diff;
|
||||
if (!simulation) {
|
||||
diff = bv_cog->value - bv_btw->value;
|
||||
} else {
|
||||
diff = 7.0;
|
||||
}
|
||||
if (diff < -180) {
|
||||
diff += 360;
|
||||
} else if (diff > 180) {
|
||||
|
|
|
@ -31,6 +31,8 @@ typedef struct{
|
|||
double batteryVoltage300 = 0; // Sliding average over 300 values
|
||||
double batteryCurrent300 = 0;
|
||||
double batteryPower300 = 0;
|
||||
double batteryLevelLiPo = 0; // Battery level for OBP40 LiPo accu
|
||||
int BatteryChargeStatus = 0; // LiPo charge status: 0 = discharge, 1 = loading activ
|
||||
double solarVoltage = 0;
|
||||
double solarCurrent = 0;
|
||||
double solarPower = 0;
|
||||
|
@ -41,14 +43,10 @@ typedef struct{
|
|||
double airHumidity = 0;
|
||||
double airPressure = 0;
|
||||
double onewireTemp[8] = {0,0,0,0,0,0,0,0};
|
||||
double rotationAngle = 0; // Rotation angle in radiant
|
||||
bool validRotAngle = false; // Valid flag magnet present for rotation sensor
|
||||
int rtcYear = 0; // UTC time
|
||||
int rtcMonth = 0;
|
||||
int rtcDay = 0;
|
||||
int rtcHour = 0;
|
||||
int rtcMinute = 0;
|
||||
int rtcSecond = 0;
|
||||
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;
|
||||
int sunsetHour = 0;
|
||||
int sunsetMinute = 0;
|
||||
int sunriseHour = 0;
|
||||
|
@ -93,6 +91,7 @@ typedef struct{
|
|||
uint16_t fgcolor;
|
||||
uint16_t bgcolor;
|
||||
bool keylock = false;
|
||||
String powermode;
|
||||
} CommonData;
|
||||
|
||||
//a base class that all pages must inherit from
|
||||
|
@ -110,7 +109,7 @@ class Page{
|
|||
commonData->keydata[2].label = "#LEFT";
|
||||
commonData->keydata[3].label = "#RIGHT";
|
||||
commonData->keydata[4].label = "";
|
||||
if (commonData->backlight.mode == KEY) {
|
||||
if ((commonData->backlight.mode == KEY) && !(commonData->powermode == "Min Power")) {
|
||||
commonData->keydata[5].label = "ILUM";
|
||||
} else {
|
||||
commonData->keydata[5].label = "";
|
||||
|
@ -166,13 +165,18 @@ class PageStruct{
|
|||
PageDescription *description=NULL;
|
||||
};
|
||||
|
||||
// Structure for formated boat values
|
||||
// Standard format functions without overhead
|
||||
String formatDate(String fmttype, uint16_t year, uint8_t month, uint8_t day);
|
||||
String formatTime(char fmttype, uint8_t hour, uint8_t minute, uint8_t second);
|
||||
String formatLatitude(double lat);
|
||||
String formatLongitude(double lon);
|
||||
|
||||
// Structure for formatted boat values
|
||||
typedef struct{
|
||||
double value;
|
||||
String svalue;
|
||||
String unit;
|
||||
} FormatedData;
|
||||
|
||||
|
||||
// Formater for boat values
|
||||
// Formatter for boat values
|
||||
FormatedData formatValue(GwApi::BoatValue *value, CommonData &commondata);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -15,6 +15,7 @@ no_of_fields_per_page = {
|
|||
"Battery": 0,
|
||||
"BME280": 0,
|
||||
"Clock": 0,
|
||||
"Compass" : 0,
|
||||
"DST810": 0,
|
||||
"Fluid": 1,
|
||||
"FourValues2": 4,
|
||||
|
@ -24,6 +25,7 @@ no_of_fields_per_page = {
|
|||
"OneValue": 1,
|
||||
"RollPitch": 2,
|
||||
"RudderPosition": 0,
|
||||
"SixValues" : 6,
|
||||
"Solar": 0,
|
||||
"ThreeValues": 3,
|
||||
"TwoValues": 2,
|
||||
|
@ -31,7 +33,6 @@ no_of_fields_per_page = {
|
|||
"WhitePage": 0,
|
||||
"WindRose": 0,
|
||||
"WindRoseFlex": 6,
|
||||
# "SixValues" : 6,
|
||||
}
|
||||
|
||||
# No changes needed beyond this point
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <GxEPD2_BW.h> // GxEPD2 lib for b/w E-Ink displays
|
||||
#include "OBP60Extensions.h" // Functions lib for extension board
|
||||
#include "OBP60Keypad.h" // Functions for keypad
|
||||
#include "BoatDataCalibration.h" // Functions lib for data instance calibration
|
||||
|
||||
#ifdef BOARD_OBP40S3
|
||||
#include "driver/rtc_io.h" // Needs for weakup from deep sleep
|
||||
|
@ -78,9 +79,8 @@ void OBP60Init(GwApi *api){
|
|||
}
|
||||
|
||||
#ifdef BOARD_OBP40S3
|
||||
//String sdcard = config->getConfigItem(config->useSDCard, true)->asString();
|
||||
String sdcard = "on";
|
||||
if (sdcard == "on") {
|
||||
bool sdcard = config->getBool(config->useSDCard);
|
||||
if (sdcard) {
|
||||
SPIClass SD_SPI = SPIClass(HSPI);
|
||||
SD_SPI.begin(SD_SPI_CLK, SD_SPI_MISO, SD_SPI_MOSI);
|
||||
if (SD.begin(SD_SPI_CS, SD_SPI, 80000000)) {
|
||||
|
@ -267,6 +267,8 @@ void registerAllPages(PageList &list){
|
|||
list.add(®isterPageTwoValues);
|
||||
extern PageDescription registerPageThreeValues;
|
||||
list.add(®isterPageThreeValues);
|
||||
extern PageDescription registerPageSixValues;
|
||||
list.add(®isterPageSixValues);
|
||||
extern PageDescription registerPageFourValues;
|
||||
list.add(®isterPageFourValues);
|
||||
extern PageDescription registerPageFourValues2;
|
||||
|
@ -283,6 +285,8 @@ void registerAllPages(PageList &list){
|
|||
list.add(®isterPageDST810);
|
||||
extern PageDescription registerPageClock;
|
||||
list.add(®isterPageClock);
|
||||
extern PageDescription registerPageCompass;
|
||||
list.add(®isterPageCompass);
|
||||
extern PageDescription registerPageWhite;
|
||||
list.add(®isterPageWhite);
|
||||
extern PageDescription registerPageBME280;
|
||||
|
@ -310,12 +314,39 @@ void registerAllPages(PageList &list){
|
|||
// Undervoltage detection for shutdown display
|
||||
void underVoltageDetection(GwApi *api, CommonData &common){
|
||||
// Read settings
|
||||
float vslope = uint(api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asFloat());
|
||||
float voffset = uint(api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asFloat());
|
||||
double voffset = (api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asString()).toFloat();
|
||||
double vslope = (api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asString()).toFloat();
|
||||
// Read supply voltage
|
||||
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // V = 1/20 * Vin
|
||||
actVoltage = actVoltage * vslope + voffset;
|
||||
if(actVoltage < MIN_VOLTAGE){
|
||||
#if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
|
||||
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40
|
||||
float minVoltage = 3.65; // Absolut minimum volatge for 3,7V LiPo accu
|
||||
#else
|
||||
float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60
|
||||
float minVoltage = MIN_VOLTAGE;
|
||||
#endif
|
||||
double calVoltage = actVoltage * vslope + voffset; // Calibration
|
||||
if(calVoltage < minVoltage){
|
||||
#if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200
|
||||
// Switch off all power lines
|
||||
setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
|
||||
setFlashLED(false); // Flash LED Off
|
||||
buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms
|
||||
// Shutdown EInk display
|
||||
getdisplay().setFullWindow(); // Set full Refresh
|
||||
//getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||
getdisplay().fillScreen(common.bgcolor);// Clear screen
|
||||
getdisplay().setTextColor(common.fgcolor);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setCursor(65, 150);
|
||||
getdisplay().print("Undervoltage");
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(65, 175);
|
||||
getdisplay().print("Charge battery and restart system");
|
||||
getdisplay().nextPage(); // Partial update
|
||||
getdisplay().powerOff(); // Display power off
|
||||
setPortPin(OBP_POWER_EPD, false); // Power off ePaper display
|
||||
setPortPin(OBP_POWER_SD, false); // Power off SD card
|
||||
#else
|
||||
// Switch off all power lines
|
||||
setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off
|
||||
setFlashLED(false); // Flash LED Off
|
||||
|
@ -323,13 +354,17 @@ void underVoltageDetection(GwApi *api, CommonData &common){
|
|||
setPortPin(OBP_POWER_50, false); // Power rail 5.0V Off
|
||||
// Shutdown EInk display
|
||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||
getdisplay().fillScreen(common.bgcolor); // Clear screen
|
||||
getdisplay().fillScreen(common.bgcolor);// Clear screen
|
||||
getdisplay().setTextColor(common.fgcolor);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt7b);
|
||||
getdisplay().setCursor(65, 150);
|
||||
getdisplay().print("Undervoltage");
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt7b);
|
||||
getdisplay().setCursor(65, 175);
|
||||
getdisplay().print("To wake up repower system");
|
||||
getdisplay().nextPage(); // Partial update
|
||||
getdisplay().powerOff(); // Display power off
|
||||
#endif
|
||||
// Stop system
|
||||
while(true){
|
||||
esp_deep_sleep_start(); // Deep Sleep without weakup. Weakup only after power cycle (restart).
|
||||
|
@ -492,6 +527,9 @@ void OBP60Task(GwApi *api){
|
|||
// add out of band system page (always available)
|
||||
Page *syspage = allPages.pages[0]->creator(commonData);
|
||||
|
||||
// Read all calibration data settings from config
|
||||
calibrationData.readConfig(config, logger);
|
||||
|
||||
// Display screenshot handler for HTTP request
|
||||
// http://192.168.15.1/api/user/OBP60Task/screenshot
|
||||
api->registerRequestHandler("screenshot", [api, &pageNumber, pages](AsyncWebServerRequest *request) {
|
||||
|
@ -518,16 +556,26 @@ void OBP60Task(GwApi *api){
|
|||
// Configuration values for main loop
|
||||
String gpsFix = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString();
|
||||
String gpsOn=api->getConfig()->getConfigItem(api->getConfig()->useGPS,true)->asString();
|
||||
String tz = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asString();
|
||||
float tz = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asFloat();
|
||||
|
||||
commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight,true)->asString());
|
||||
commonData.backlight.color = colorMapping(config->getConfigItem(config->blColor,true)->asString());
|
||||
commonData.backlight.brightness = 2.55 * uint(config->getConfigItem(config->blBrightness,true)->asInt());
|
||||
commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString();
|
||||
|
||||
bool uvoltage = api->getConfig()->getConfigItem(api->getConfig()->underVoltage,true)->asBoolean();
|
||||
String cpuspeed = api->getConfig()->getConfigItem(api->getConfig()->cpuSpeed,true)->asString();
|
||||
uint hdopAccuracy = uint(api->getConfig()->getConfigItem(api->getConfig()->hdopAccuracy,true)->asInt());
|
||||
|
||||
double homelat = commonData.config->getString(commonData.config->homeLAT).toDouble();
|
||||
double homelon = commonData.config->getString(commonData.config->homeLON).toDouble();
|
||||
bool homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0;
|
||||
if (homevalid) {
|
||||
LOG_DEBUG(GwLog::LOG, "Home location set to %f : %f", homelat, homelon);
|
||||
} else {
|
||||
LOG_DEBUG(GwLog::LOG, "No valid home location found");
|
||||
}
|
||||
|
||||
// refreshmode defined in init section
|
||||
|
||||
// Boat values for main loop
|
||||
|
@ -677,7 +725,7 @@ void OBP60Task(GwApi *api){
|
|||
starttime5 = millis();
|
||||
if(time->valid == true && date->valid == true && lat->valid == true && lon->valid == true){
|
||||
// Provide sundata to all pages
|
||||
commonData.sundata = calcSunsetSunrise(api, time->value , date->value, lat->value, lon->value, tz.toDouble());
|
||||
commonData.sundata = calcSunsetSunrise(time->value , date->value, lat->value, lon->value, tz);
|
||||
// Backlight with sun control
|
||||
if (commonData.backlight.mode == BacklightMode::SUN) {
|
||||
// if(String(backlight) == "Control by Sun"){
|
||||
|
@ -688,6 +736,9 @@ void OBP60Task(GwApi *api){
|
|||
setBacklightLED(0, COLOR_BLUE); // Backlight LEDs off (blue without britghness)
|
||||
}
|
||||
}
|
||||
} else if (homevalid and commonData.data.rtcValid) {
|
||||
// No gps fix but valid home location and time configured
|
||||
commonData.sundata = calcSunsetSunriseRTC(&commonData.data.rtcTime, homelat, homelon, tz);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
#if you want a pio run to only build
|
||||
#your special environments you can set this here
|
||||
#by uncommenting the next line
|
||||
default_envs = obp60_s3
|
||||
|
||||
default_envs =
|
||||
obp60_s3
|
||||
obp40_s3
|
||||
|
||||
[env:obp60_s3]
|
||||
platform = espressif32@6.8.1
|
||||
|
@ -18,6 +21,7 @@ lib_deps =
|
|||
${basedeps.lib_deps}
|
||||
Wire
|
||||
SPI
|
||||
ESP32time
|
||||
esphome/AsyncTCP-esphome@2.0.1
|
||||
robtillaart/PCF8574@0.3.9
|
||||
adafruit/Adafruit Unified Sensor @ 1.1.13
|
||||
|
@ -68,6 +72,7 @@ lib_deps =
|
|||
Wire
|
||||
SPI
|
||||
SD
|
||||
ESP32time
|
||||
esphome/AsyncTCP-esphome@2.0.1
|
||||
robtillaart/PCF8574@0.3.9
|
||||
adafruit/Adafruit Unified Sensor @ 1.1.13
|
||||
|
@ -89,8 +94,11 @@ lib_deps =
|
|||
adafruit/Adafruit FRAM I2C@^2.0.3
|
||||
build_flags=
|
||||
-D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib
|
||||
-D BOARD_OBP40S3 #Board OBP40 V1.0 with ESP32S3 SKU:DIE07300S (CrowPanel 4.2)
|
||||
-D BOARD_OBP40S3 #Board OBP40 with ESP32S3
|
||||
-D HARDWARE_V10 #OBP40 hardware revision V1.0 SKU:DIE07300S V1.1 (CrowPanel 4.2)
|
||||
-D DISPLAY_GDEY042T81 #new E-Ink display from Waveshare, R10 2.2 ohm
|
||||
#-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
|
||||
#-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
|
||||
${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
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
|
||||
# Compile the firmware
|
||||
echo "Compiling Firmware"
|
||||
platformio run -e obp60_s3
|
||||
platformio run -e obp40_s3
|
|
@ -112,7 +112,7 @@ class SSISensor : public SensorTemplate<BUSTYPE,SensorBase::SPI>{
|
|||
.flags = SPI_TRANS_USE_RXDATA,
|
||||
.cmd = 0,
|
||||
.addr = 0,
|
||||
.length = bits+1,
|
||||
.length = (size_t)bits+1,
|
||||
.rxlength = 0};
|
||||
esp_err_t ret = spi_device_queue_trans(device->device(), &ta, portMAX_DELAY);
|
||||
if (ret != ESP_OK) return ret;
|
||||
|
|
|
@ -801,6 +801,7 @@ void setup() {
|
|||
MDNS.begin(config.getConfigItem(config.systemName)->asCString());
|
||||
channels.begin(fallbackSerial);
|
||||
logger.flush();
|
||||
config.logConfig(GwLog::DEBUG);
|
||||
webserver.registerMainHandler("/api/reset", [](AsyncWebServerRequest *request)->GwRequestMessage *{
|
||||
return new ResetRequest(request->arg("_hash"));
|
||||
});
|
||||
|
@ -916,7 +917,7 @@ void setup() {
|
|||
logger.flush();
|
||||
NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, NodeAddress);
|
||||
NMEA2000.SetForwardOwnMessages(false);
|
||||
NMEA2000.SetHeartbeatInterval(NMEA2000_HEARTBEAT_INTERVAL);
|
||||
NMEA2000.SetHeartbeatIntervalAndOffset(NMEA2000_HEARTBEAT_INTERVAL);
|
||||
if (sendOutN2k){
|
||||
// Set the information for other bus devices, which messages we support
|
||||
unsigned long *pgns=toN2KConverter->handledPgns();
|
||||
|
|
Loading…
Reference in New Issue