mirror of
https://github.com/thooge/esp32-nmea2000-obp60.git
synced 2026-03-28 18:06:37 +01:00
Compare commits
102 Commits
da975b5175
...
anchor
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d052f8827 | |||
| 608b782b43 | |||
| 4ab3749028 | |||
| 47d9d9f5ca | |||
|
|
43b0a780d5 | ||
| 0363ba4379 | |||
|
|
4b6e2abe33 | ||
|
|
0401d82b62 | ||
| 0b02e5b54c | |||
| 48b9380724 | |||
|
|
2fecbee492 | ||
|
|
fc5daaba37 | ||
|
|
04dc09e44a | ||
| 1d2ba2f71d | |||
|
|
bbecf5e55f | ||
|
|
ded1b2b22e | ||
|
|
a0a88fa2c9 | ||
|
|
e9bf54e99f | ||
|
|
64950c3974 | ||
|
|
fb2fbc85a4 | ||
|
|
6b92a5e69c | ||
|
|
ef4546a2e6 | ||
|
|
6870c9b8a4 | ||
|
|
a70d976a6e | ||
|
|
fbba6ffff2 | ||
|
|
d516c82041 | ||
|
|
ccca784ac2 | ||
|
|
337214d650 | ||
|
|
744cf6469b | ||
| 6bc1b60f60 | |||
|
|
06dcd14bdc | ||
|
|
352009073e | ||
|
|
aaa4007ae6 | ||
|
|
5b087e61d2 | ||
|
|
753e87068f | ||
|
|
7445d2db24 | ||
|
|
f517abf001 | ||
|
|
6a56a8fb56 | ||
|
|
576f0a0d4f | ||
|
|
1de936fd47 | ||
|
|
05dad7a23c | ||
|
|
d19da640ae | ||
|
|
1da26a90ec | ||
|
|
cb2b85d505 | ||
|
|
cc1d07fac0 | ||
|
|
b8e64ff64c | ||
|
|
e4214beefe | ||
|
|
02c611ead0 | ||
|
|
0b79b7e2ef | ||
|
|
dd3a4f5093 | ||
| 5b477331de | |||
| 9b9bf76e4d | |||
|
|
32099487fa | ||
|
|
18b46ae5a0 | ||
|
|
fb62e41bd9 | ||
|
|
9211b13dcd | ||
|
|
6da87e4455 | ||
|
|
5493c9695c | ||
|
|
034a338a81 | ||
|
|
3cd508a239 | ||
|
|
68239f6199 | ||
|
|
b683413129 | ||
|
|
566d84d3e6 | ||
|
|
432a10bfb1 | ||
|
|
32862b9e29 | ||
|
|
c21592599f | ||
|
|
fddc3c742b | ||
|
|
9831f8da85 | ||
|
|
8bf8ada30e | ||
|
|
6266f85db6 | ||
|
|
70ad5cc903 | ||
|
|
df9b377b31 | ||
|
|
d0966159c0 | ||
|
|
3f22164b1d | ||
|
|
60d06cd9ee | ||
|
|
9633abc481 | ||
|
|
24502e423e | ||
|
|
4a442c6dfb | ||
|
|
448af708d4 | ||
|
|
78aafd308a | ||
|
|
b7cd8c6bdd | ||
|
|
e5968b8480 | ||
|
|
e5c4f0b179 | ||
|
|
4b03fa5a23 | ||
|
|
13eac9508d | ||
|
|
ec807c6925 | ||
|
|
3df2571ca2 | ||
|
|
e578b428c9 | ||
|
|
6e0d56316b | ||
|
|
7fd1457296 | ||
|
|
e8c5440a79 | ||
|
|
95df5858ac | ||
|
|
d5a9568b67 | ||
|
|
3d131c7d98 | ||
|
|
7ebd582ca0 | ||
|
|
85f49857da | ||
|
|
976e8172e3 | ||
|
|
47fcb26961 | ||
|
|
da6022cb28 | ||
|
|
2c97eacd76 | ||
|
|
370fd47deb | ||
|
|
37d945a0ea |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -62,5 +62,5 @@ jobs:
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ steps.version.outputs.version}}
|
||||
file: ./.pio/build/*/*-{all,update}.bin
|
||||
file: ./.pio/build/*/*${{ steps.version.outputs.version }}*-{all,update}.bin
|
||||
file_glob: true
|
||||
|
||||
23
Readme.md
23
Readme.md
@@ -43,6 +43,10 @@ What is included
|
||||
|
||||
For the details of the mapped PGNs and NMEA sentences refer to [Conversions](doc/Conversions.pdf).
|
||||
|
||||
License
|
||||
-------
|
||||
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either [version 2 of the License](LICENSE), or (at your option) any later version.
|
||||
|
||||
Hardware
|
||||
--------
|
||||
The software is prepared to run on different kinds of ESP32 based modules and accessoirs. For some of them prebuild binaries are available that only need to be flashed, others would require to add some definitions of the used PINs and features and to build the binary.
|
||||
@@ -170,6 +174,25 @@ For details refer to the [example description](lib/exampletask/Readme.md).
|
||||
|
||||
Changelog
|
||||
---------
|
||||
[20251126](../../releases/tag/20251126)
|
||||
* fix a bug in the Actisense reader that could lead to an endless loop (making the device completely non responsive)
|
||||
* upgrade to 4.24.1 of the NMEA2000 library (2025/11/01) - refer to the [changes](https://github.com/ttlappalainen/NMEA2000/blob/master/Documents/src/changes.md) - Especially UTF8 support
|
||||
*********
|
||||
[20251007](../../releases/tag/20251007)
|
||||
*********
|
||||
* add AIS Aton translations (PGN 129041 <-> Ais class 21)
|
||||
* improved mapping of AIS transducer information (NMEA2000) to AIS channel and Talker on NMEA0183
|
||||
* use a forked version of the NMEA2000 library (as an intermediate workaround)
|
||||
* [#114](../../issues/114) correctly translate AIS type 1/3 from PGN 129038
|
||||
* add support for a generic S3 build in the build UI
|
||||
* [#117](../../issues/117) add support for a transmit enable pin for RS 485 conections (also in the build UI)
|
||||
* [#116](../../issues/116) SDA and SCL are swapped in the build UI
|
||||
* [#112](../../issues/112) clearify licenses
|
||||
* [#110](../../issues/110) / [#115](../../pull/115) support for the M5 GPS unit v1.1
|
||||
* [#102](../../issues/102) optimize Wifi reconnect handling
|
||||
* [#111](../../pull/111) allow for a custom python build script
|
||||
* [#113](../../issues/113) support for M5 stack Env4
|
||||
|
||||
[20250305](../../releases/tag/20250305)
|
||||
*********
|
||||
* better handling for reconnect to a raspberry pi after reset [#102](../../issues/102)
|
||||
|
||||
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,18 +104,7 @@ 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")
|
||||
for cname in other:
|
||||
if os.path.exists(cname):
|
||||
print("merge config %s"%cname)
|
||||
with open(cname,'rb') as ah:
|
||||
@@ -161,13 +150,25 @@ def expandConfig(config):
|
||||
rt.append(replaceTexts(c,replace))
|
||||
return rt
|
||||
|
||||
def generateMergedConfig(inFile,outFile,addDirs=[]):
|
||||
def createUserItemList(dirs,itemName,files):
|
||||
rt=[]
|
||||
for d in dirs:
|
||||
iname=os.path.join(d,itemName)
|
||||
if os.path.exists(iname):
|
||||
rt.append(iname)
|
||||
for f in files:
|
||||
if not os.path.exists(f):
|
||||
raise Exception("user item %s not found"%f)
|
||||
rt.append(f)
|
||||
return rt
|
||||
|
||||
def generateMergedConfig(inFile,outFile,addFiles=[]):
|
||||
if not os.path.exists(inFile):
|
||||
raise Exception("unable to read cfg file %s"%inFile)
|
||||
data=""
|
||||
with open(inFile,'rb') as ch:
|
||||
config=json.load(ch)
|
||||
config=mergeConfig(config,addDirs)
|
||||
config=mergeConfig(config,addFiles)
|
||||
config=expandConfig(config)
|
||||
data=json.dumps(config,indent=2)
|
||||
writeFileIfChanged(outFile,data)
|
||||
@@ -387,12 +388,7 @@ def getLibs():
|
||||
|
||||
|
||||
|
||||
def joinFiles(target,pattern,dirlist):
|
||||
flist=[]
|
||||
for dir in dirlist:
|
||||
fn=os.path.join(dir,pattern)
|
||||
if os.path.exists(fn):
|
||||
flist.append(fn)
|
||||
def joinFiles(target,flist):
|
||||
current=False
|
||||
if os.path.exists(target):
|
||||
current=True
|
||||
@@ -463,7 +459,28 @@ def handleDeps(env):
|
||||
)
|
||||
env.AddBuildMiddleware(injectIncludes)
|
||||
|
||||
def getOption(env,name,toArray=True):
|
||||
try:
|
||||
opt=env.GetProjectOption(name)
|
||||
if toArray:
|
||||
if opt is None:
|
||||
return []
|
||||
if isinstance(opt,list):
|
||||
return opt
|
||||
return opt.split("\n" if "\n" in opt else ",")
|
||||
return opt
|
||||
except:
|
||||
pass
|
||||
if toArray:
|
||||
return []
|
||||
|
||||
def getFileList(files):
|
||||
base=basePath()
|
||||
rt=[]
|
||||
for f in files:
|
||||
if f is not None and f != "":
|
||||
rt.append(os.path.join(base,f))
|
||||
return rt
|
||||
def prebuild(env):
|
||||
global userTaskDirs
|
||||
print("#prebuild running")
|
||||
@@ -473,14 +490,18 @@ def prebuild(env):
|
||||
if ldf_mode == 'off':
|
||||
print("##ldf off - own dependency handling")
|
||||
handleDeps(env)
|
||||
extraConfigs=getOption(env,'custom_config',toArray=True)
|
||||
extraJs=getOption(env,'custom_js',toArray=True)
|
||||
extraCss=getOption(env,'custom_css',toArray=True)
|
||||
|
||||
userTaskDirs=getUserTaskDirs()
|
||||
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
|
||||
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
|
||||
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,createUserItemList(userTaskDirs,"config.json", getFileList(extraConfigs)))
|
||||
compressFile(mergedConfig,mergedConfig+".gz")
|
||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False)
|
||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True)
|
||||
joinFiles(os.path.join(outPath(),INDEXJS+".gz"),INDEXJS,["web"]+userTaskDirs)
|
||||
joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),INDEXCSS,["web"]+userTaskDirs)
|
||||
joinFiles(os.path.join(outPath(),INDEXJS+".gz"),createUserItemList(["web"]+userTaskDirs,INDEXJS,getFileList(extraJs)))
|
||||
joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),createUserItemList(["web"]+userTaskDirs,INDEXCSS,getFileList(extraCss)))
|
||||
embedded=getEmbeddedFiles(env)
|
||||
filedefs=[]
|
||||
for ef in embedded:
|
||||
@@ -526,17 +547,16 @@ env.Append(
|
||||
)
|
||||
#script does not run on clean yet - maybe in the future
|
||||
env.AddPostAction("clean",cleangenerated)
|
||||
|
||||
#look for extra task scripts and include them here
|
||||
for taskdir in userTaskDirs:
|
||||
script = os.path.join(taskdir, "extra_task.py")
|
||||
extraScripts=getFileList(getOption(env,'custom_script',toArray=True))
|
||||
for script in extraScripts:
|
||||
if os.path.isfile(script):
|
||||
taskname = os.path.basename(os.path.normpath(taskdir))
|
||||
print("#extra task script for '{}'".format(taskname))
|
||||
print(f"#extra {script}")
|
||||
with open(script) as fh:
|
||||
try:
|
||||
code = compile(fh.read(), taskname, 'exec')
|
||||
except SyntaxError:
|
||||
print("#ERROR: script does not compile")
|
||||
code = compile(fh.read(), script, 'exec')
|
||||
except SyntaxError as e:
|
||||
print(f"#ERROR: script {script} does not compile: {e}")
|
||||
continue
|
||||
exec(code)
|
||||
else:
|
||||
print(f"#ERROR: script {script} not found")
|
||||
|
||||
@@ -1,542 +0,0 @@
|
||||
print("running extra...")
|
||||
import gzip
|
||||
import shutil
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import json
|
||||
import glob
|
||||
from datetime import datetime
|
||||
import re
|
||||
import pprint
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.exception import InvalidProjectConfError
|
||||
|
||||
Import("env")
|
||||
#print(env.Dump())
|
||||
OWN_FILE="extra_script.py"
|
||||
GEN_DIR='lib/generated'
|
||||
CFG_FILE='web/config.json'
|
||||
XDR_FILE='web/xdrconfig.json'
|
||||
INDEXJS="index.js"
|
||||
INDEXCSS="index.css"
|
||||
CFG_INCLUDE='GwConfigDefinitions.h'
|
||||
CFG_INCLUDE_IMPL='GwConfigDefImpl.h'
|
||||
XDR_INCLUDE='GwXdrTypeMappings.h'
|
||||
TASK_INCLUDE='GwUserTasks.h'
|
||||
GROVE_CONFIG="GwM5GroveGen.h"
|
||||
GROVE_CONFIG_IN="lib/hardware/GwM5Grove.in"
|
||||
EMBEDDED_INCLUDE="GwEmbeddedFiles.h"
|
||||
|
||||
def getEmbeddedFiles(env):
|
||||
rt=[]
|
||||
efiles=env.GetProjectOption("board_build.embed_files")
|
||||
for f in efiles.split("\n"):
|
||||
if f == '':
|
||||
continue
|
||||
rt.append(f)
|
||||
return rt
|
||||
|
||||
def basePath():
|
||||
#see: https://stackoverflow.com/questions/16771894/python-nameerror-global-name-file-is-not-defined
|
||||
return os.path.dirname(inspect.getfile(lambda: None))
|
||||
|
||||
def outPath():
|
||||
return os.path.join(basePath(),GEN_DIR)
|
||||
def checkDir():
|
||||
dn=outPath()
|
||||
if not os.path.exists(dn):
|
||||
os.makedirs(dn)
|
||||
if not os.path.isdir(dn):
|
||||
print("unable to create %s"%dn)
|
||||
return False
|
||||
return True
|
||||
|
||||
def isCurrent(infile,outfile):
|
||||
if os.path.exists(outfile):
|
||||
otime=os.path.getmtime(outfile)
|
||||
itime=os.path.getmtime(infile)
|
||||
if (otime >= itime):
|
||||
own=os.path.join(basePath(),OWN_FILE)
|
||||
if os.path.exists(own):
|
||||
owntime=os.path.getmtime(own)
|
||||
if owntime > otime:
|
||||
return False
|
||||
print("%s is newer then %s, no need to recreate"%(outfile,infile))
|
||||
return True
|
||||
return False
|
||||
def compressFile(inFile,outfile):
|
||||
if isCurrent(inFile,outfile):
|
||||
return
|
||||
print("compressing %s"%inFile)
|
||||
with open(inFile, 'rb') as f_in:
|
||||
with gzip.open(outfile, 'wb') as f_out:
|
||||
shutil.copyfileobj(f_in, f_out)
|
||||
|
||||
def generateFile(infile,outfile,callback,inMode='rb',outMode='w'):
|
||||
if isCurrent(infile,outfile):
|
||||
return
|
||||
print("creating %s"%outfile)
|
||||
oh=None
|
||||
with open(infile,inMode) as ch:
|
||||
with open(outfile,outMode) as oh:
|
||||
try:
|
||||
callback(ch,oh,inFile=infile)
|
||||
oh.close()
|
||||
except Exception as e:
|
||||
try:
|
||||
oh.close()
|
||||
except:
|
||||
pass
|
||||
os.unlink(outfile)
|
||||
raise
|
||||
|
||||
def writeFileIfChanged(fileName,data):
|
||||
if os.path.exists(fileName):
|
||||
with open(fileName,"r") as ih:
|
||||
old=ih.read()
|
||||
ih.close()
|
||||
if old == data:
|
||||
return False
|
||||
print("#generating %s"%fileName)
|
||||
with open(fileName,"w") as oh:
|
||||
oh.write(data)
|
||||
return True
|
||||
|
||||
def mergeConfig(base,other):
|
||||
try:
|
||||
customconfig = env.GetProjectOption("custom_config")
|
||||
except InvalidProjectConfError:
|
||||
customconfig = None
|
||||
for bdir in other:
|
||||
if customconfig and os.path.exists(os.path.join(bdir,customconfig)):
|
||||
cname=os.path.join(bdir,customconfig)
|
||||
print("merge custom config {}".format(cname))
|
||||
with open(cname,'rb') as ah:
|
||||
base += json.load(ah)
|
||||
continue
|
||||
cname=os.path.join(bdir,"config.json")
|
||||
if os.path.exists(cname):
|
||||
print("merge config %s"%cname)
|
||||
with open(cname,'rb') as ah:
|
||||
merge=json.load(ah)
|
||||
base=base+merge
|
||||
return base
|
||||
|
||||
def replaceTexts(data,replacements):
|
||||
if replacements is None:
|
||||
return data
|
||||
if isinstance(data,str):
|
||||
for k,v in replacements.items():
|
||||
data=data.replace("$"+k,str(v))
|
||||
return data
|
||||
if isinstance(data,list):
|
||||
rt=[]
|
||||
for e in data:
|
||||
rt.append(replaceTexts(e,replacements))
|
||||
return rt
|
||||
if isinstance(data,dict):
|
||||
rt={}
|
||||
for k,v in data.items():
|
||||
rt[replaceTexts(k,replacements)]=replaceTexts(v,replacements)
|
||||
return rt
|
||||
return data
|
||||
def expandConfig(config):
|
||||
rt=[]
|
||||
for item in config:
|
||||
type=item.get('type')
|
||||
if type != 'array':
|
||||
rt.append(item)
|
||||
continue
|
||||
replacements=item.get('replace')
|
||||
children=item.get('children')
|
||||
name=item.get('name')
|
||||
if name is None:
|
||||
name="#unknown#"
|
||||
if not isinstance(replacements,list):
|
||||
raise Exception("missing replacements at array %s"%name)
|
||||
for replace in replacements:
|
||||
if children is not None:
|
||||
for c in children:
|
||||
rt.append(replaceTexts(c,replace))
|
||||
return rt
|
||||
|
||||
def generateMergedConfig(inFile,outFile,addDirs=[]):
|
||||
if not os.path.exists(inFile):
|
||||
raise Exception("unable to read cfg file %s"%inFile)
|
||||
data=""
|
||||
with open(inFile,'rb') as ch:
|
||||
config=json.load(ch)
|
||||
config=mergeConfig(config,addDirs)
|
||||
config=expandConfig(config)
|
||||
data=json.dumps(config,indent=2)
|
||||
writeFileIfChanged(outFile,data)
|
||||
|
||||
def generateCfg(inFile,outFile,impl):
|
||||
if not os.path.exists(inFile):
|
||||
raise Exception("unable to read cfg file %s"%inFile)
|
||||
data=""
|
||||
with open(inFile,'rb') as ch:
|
||||
config=json.load(ch)
|
||||
data+="//generated from %s\n"%inFile
|
||||
l=len(config)
|
||||
idx=0
|
||||
if not impl:
|
||||
data+='#include "GwConfigItem.h"\n'
|
||||
data+='class GwConfigDefinitions{\n'
|
||||
data+=' public:\n'
|
||||
data+=' int getNumConfig() const{return %d;}\n'%(l)
|
||||
for item in config:
|
||||
n=item.get('name')
|
||||
if n is None:
|
||||
continue
|
||||
if len(n) > 15:
|
||||
raise Exception("%s: config names must be max 15 caracters"%n)
|
||||
data+=' static constexpr const char* %s="%s";\n'%(n,n)
|
||||
data+="};\n"
|
||||
else:
|
||||
data+='void GwConfigHandler::populateConfigs(GwConfigInterface **config){\n'
|
||||
for item in config:
|
||||
name=item.get('name')
|
||||
if name is None:
|
||||
continue
|
||||
data+=' configs[%d]='%(idx)
|
||||
idx+=1
|
||||
secret="false";
|
||||
if item.get('type') == 'password':
|
||||
secret="true"
|
||||
data+=" new GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret)
|
||||
data+='}\n'
|
||||
writeFileIfChanged(outFile,data)
|
||||
|
||||
def labelFilter(label):
|
||||
return re.sub("[^a-zA-Z0-9]","",re.sub("\([0-9]*\)","",label))
|
||||
def generateXdrMappings(fp,oh,inFile=''):
|
||||
jdoc=json.load(fp)
|
||||
oh.write("static GwXDRTypeMapping* typeMappings[]={\n")
|
||||
first=True
|
||||
for cat in jdoc:
|
||||
item=jdoc[cat]
|
||||
cid=item.get('id')
|
||||
if cid is None:
|
||||
continue
|
||||
tc=item.get('type')
|
||||
if tc is not None:
|
||||
if first:
|
||||
first=False
|
||||
else:
|
||||
oh.write(",\n")
|
||||
oh.write(" new GwXDRTypeMapping(%d,0,%d) /*%s*/"%(cid,tc,cat))
|
||||
fields=item.get('fields')
|
||||
if fields is None:
|
||||
continue
|
||||
idx=0
|
||||
for fe in fields:
|
||||
if not isinstance(fe,dict):
|
||||
continue
|
||||
tc=fe.get('t')
|
||||
id=fe.get('v')
|
||||
if id is None:
|
||||
id=idx
|
||||
idx+=1
|
||||
l=fe.get('l') or ''
|
||||
if tc is None or id is None:
|
||||
continue
|
||||
if first:
|
||||
first=False
|
||||
else:
|
||||
oh.write(",\n")
|
||||
oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,id,tc,cat,l))
|
||||
oh.write("\n")
|
||||
oh.write("};\n")
|
||||
for cat in jdoc:
|
||||
item=jdoc[cat]
|
||||
cid=item.get('id')
|
||||
if cid is None:
|
||||
continue
|
||||
selectors=item.get('selector')
|
||||
if selectors is not None:
|
||||
for selector in selectors:
|
||||
label=selector.get('l')
|
||||
value=selector.get('v')
|
||||
if label is not None and value is not None:
|
||||
label=labelFilter(label)
|
||||
define=("GWXDRSEL_%s_%s"%(cat,label)).upper()
|
||||
oh.write(" #define %s %s\n"%(define,value))
|
||||
fields=item.get('fields')
|
||||
if fields is not None:
|
||||
idx=0
|
||||
for field in fields:
|
||||
v=field.get('v')
|
||||
if v is None:
|
||||
v=idx
|
||||
else:
|
||||
v=int(v)
|
||||
label=field.get('l')
|
||||
if v is not None and label is not None:
|
||||
define=("GWXDRFIELD_%s_%s"%(cat,labelFilter(label))).upper();
|
||||
oh.write(" #define %s %s\n"%(define,str(v)))
|
||||
idx+=1
|
||||
|
||||
class Grove:
|
||||
def __init__(self,name) -> None:
|
||||
self.name=name
|
||||
def _ss(self,z=False):
|
||||
if z:
|
||||
return self.name
|
||||
return self.name if self.name != 'Z' else ''
|
||||
def _suffix(self):
|
||||
return '_'+self.name if self.name != 'Z' else ''
|
||||
def replace(self,line):
|
||||
if line is None:
|
||||
return line
|
||||
return line.replace('$G$',self._ss()).replace('$Z$',self._ss(True)).replace('$GS$',self._suffix())
|
||||
def generateGroveDefs(inh,outh,inFile=''):
|
||||
GROVES=[Grove('Z'),Grove('A'),Grove('B'),Grove('C')]
|
||||
definition=[]
|
||||
started=False
|
||||
def writeConfig():
|
||||
for grove in GROVES:
|
||||
for cl in definition:
|
||||
outh.write(grove.replace(cl))
|
||||
|
||||
for line in inh:
|
||||
if re.match(" *#GROVE",line):
|
||||
started=True
|
||||
if len(definition) > 0:
|
||||
writeConfig()
|
||||
definition=[]
|
||||
continue
|
||||
if started:
|
||||
definition.append(line)
|
||||
if len(definition) > 0:
|
||||
writeConfig()
|
||||
|
||||
|
||||
|
||||
userTaskDirs=[]
|
||||
|
||||
def getUserTaskDirs():
|
||||
rt=[]
|
||||
taskdirs=glob.glob(os.path.join( basePath(),'lib','*task*'))
|
||||
for task in taskdirs:
|
||||
rt.append(task)
|
||||
return rt
|
||||
|
||||
def checkAndAdd(file,names,ilist):
|
||||
if not file.endswith('.h'):
|
||||
return
|
||||
match=False
|
||||
for cmp in names:
|
||||
#print("##check %s<->%s"%(f.lower(),cmp))
|
||||
if file.lower() == cmp:
|
||||
match=True
|
||||
if not match:
|
||||
return
|
||||
ilist.append(file)
|
||||
def genereateUserTasks(outfile):
|
||||
includes=[]
|
||||
for task in userTaskDirs:
|
||||
#print("##taskdir=%s"%task)
|
||||
base=os.path.basename(task)
|
||||
includeNames=[base.lower()+".h",'gw'+base.lower()+'.h']
|
||||
for f in os.listdir(task):
|
||||
checkAndAdd(f,includeNames,includes)
|
||||
includeData=""
|
||||
for i in includes:
|
||||
print("#task include %s"%i)
|
||||
includeData+="#include <%s>\n"%i
|
||||
writeFileIfChanged(outfile,includeData)
|
||||
|
||||
def generateEmbedded(elist,outFile):
|
||||
content=""
|
||||
for entry in elist:
|
||||
content+="EMBED_GZ_FILE(\"%s\",%s,\"%s\");\n"%entry
|
||||
writeFileIfChanged(outFile,content)
|
||||
|
||||
def getContentType(fn):
|
||||
if (fn.endswith('.gz')):
|
||||
fn=fn[0:-3]
|
||||
if (fn.endswith('html')):
|
||||
return "text/html"
|
||||
if (fn.endswith('json')):
|
||||
return "application/json"
|
||||
if (fn.endswith('js')):
|
||||
return "text/javascript"
|
||||
if (fn.endswith('css')):
|
||||
return "text/css"
|
||||
return "application/octet-stream"
|
||||
|
||||
|
||||
def getLibs():
|
||||
base=os.path.join(basePath(),"lib")
|
||||
rt=[]
|
||||
for sd in os.listdir(base):
|
||||
if sd == '..':
|
||||
continue
|
||||
if sd == '.':
|
||||
continue
|
||||
fn=os.path.join(base,sd)
|
||||
if os.path.isdir(fn):
|
||||
rt.append(sd)
|
||||
EXTRAS=['generated']
|
||||
for e in EXTRAS:
|
||||
if not e in rt:
|
||||
rt.append(e)
|
||||
return rt
|
||||
|
||||
|
||||
|
||||
def joinFiles(target,pattern,dirlist):
|
||||
flist=[]
|
||||
for dir in dirlist:
|
||||
fn=os.path.join(dir,pattern)
|
||||
if os.path.exists(fn):
|
||||
flist.append(fn)
|
||||
current=False
|
||||
if os.path.exists(target):
|
||||
current=True
|
||||
for f in flist:
|
||||
if not isCurrent(f,target):
|
||||
current=False
|
||||
break
|
||||
if current:
|
||||
print("%s is up to date"%target)
|
||||
return
|
||||
print("creating %s"%target)
|
||||
with gzip.open(target,"wb") as oh:
|
||||
for fn in flist:
|
||||
print("adding %s to %s"%(fn,target))
|
||||
with open(fn,"rb") as rh:
|
||||
shutil.copyfileobj(rh,oh)
|
||||
|
||||
|
||||
OWNLIBS=getLibs()+["FS","WiFi"]
|
||||
GLOBAL_INCLUDES=[]
|
||||
|
||||
def handleDeps(env):
|
||||
#overwrite the GetProjectConfig
|
||||
#to inject all our libs
|
||||
oldGetProjectConfig=env.GetProjectConfig
|
||||
def GetProjectConfigX(env):
|
||||
rt=oldGetProjectConfig()
|
||||
cenv="env:"+env['PIOENV']
|
||||
libs=[]
|
||||
for section,options in rt.as_tuple():
|
||||
if section == cenv:
|
||||
for key,values in options:
|
||||
if key == 'lib_deps':
|
||||
libs=values
|
||||
|
||||
mustUpdate=False
|
||||
for lib in OWNLIBS:
|
||||
if not lib in libs:
|
||||
libs.append(lib)
|
||||
mustUpdate=True
|
||||
if mustUpdate:
|
||||
update=[(cenv,[('lib_deps',libs)])]
|
||||
rt.update(update)
|
||||
return rt
|
||||
env.AddMethod(GetProjectConfigX,"GetProjectConfig")
|
||||
#store the list of all includes after we resolved
|
||||
#the dependencies for our main project
|
||||
#we will use them for all compilations afterwards
|
||||
oldLibBuilder=env.ConfigureProjectLibBuilder
|
||||
def ConfigureProjectLibBuilderX(env):
|
||||
global GLOBAL_INCLUDES
|
||||
project=oldLibBuilder()
|
||||
#print("##ConfigureProjectLibBuilderX")
|
||||
#pprint.pprint(project)
|
||||
if project.depbuilders:
|
||||
#print("##depbuilders %s"%",".join(map(lambda x: x.path,project.depbuilders)))
|
||||
for db in project.depbuilders:
|
||||
idirs=db.get_include_dirs()
|
||||
for id in idirs:
|
||||
if not id in GLOBAL_INCLUDES:
|
||||
GLOBAL_INCLUDES.append(id)
|
||||
return project
|
||||
env.AddMethod(ConfigureProjectLibBuilderX,"ConfigureProjectLibBuilder")
|
||||
def injectIncludes(env,node):
|
||||
return env.Object(
|
||||
node,
|
||||
CPPPATH=env["CPPPATH"]+GLOBAL_INCLUDES
|
||||
)
|
||||
env.AddBuildMiddleware(injectIncludes)
|
||||
|
||||
|
||||
def prebuild(env):
|
||||
global userTaskDirs
|
||||
print("#prebuild running")
|
||||
if not checkDir():
|
||||
sys.exit(1)
|
||||
ldf_mode=env.GetProjectOption("lib_ldf_mode")
|
||||
if ldf_mode == 'off':
|
||||
print("##ldf off - own dependency handling")
|
||||
handleDeps(env)
|
||||
userTaskDirs=getUserTaskDirs()
|
||||
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
|
||||
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
|
||||
compressFile(mergedConfig,mergedConfig+".gz")
|
||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False)
|
||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True)
|
||||
joinFiles(os.path.join(outPath(),INDEXJS+".gz"),INDEXJS,["web"]+userTaskDirs)
|
||||
joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),INDEXCSS,["web"]+userTaskDirs)
|
||||
embedded=getEmbeddedFiles(env)
|
||||
filedefs=[]
|
||||
for ef in embedded:
|
||||
print("#checking embedded file %s"%ef)
|
||||
(dn,fn)=os.path.split(ef)
|
||||
pureName=fn
|
||||
if pureName.endswith('.gz'):
|
||||
pureName=pureName[0:-3]
|
||||
ct=getContentType(pureName)
|
||||
usname=ef.replace('/','_').replace('.','_')
|
||||
filedefs.append((pureName,usname,ct))
|
||||
inFile=os.path.join(basePath(),"web",pureName)
|
||||
if os.path.exists(inFile):
|
||||
compressFile(inFile,ef)
|
||||
else:
|
||||
print("#WARNING: infile %s for %s not found"%(inFile,ef))
|
||||
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
|
||||
genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
|
||||
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
|
||||
generateFile(os.path.join(basePath(),GROVE_CONFIG_IN),os.path.join(outPath(),GROVE_CONFIG),generateGroveDefs,inMode='r')
|
||||
version="dev"+datetime.now().strftime("%Y%m%d")
|
||||
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
|
||||
|
||||
def cleangenerated(source, target, env):
|
||||
od=outPath()
|
||||
if os.path.isdir(od):
|
||||
print("#cleaning up %s"%od)
|
||||
for f in os.listdir(od):
|
||||
if f == "." or f == "..":
|
||||
continue
|
||||
fn=os.path.join(od,f)
|
||||
os.unlink(f)
|
||||
|
||||
|
||||
print("#prescript...")
|
||||
prebuild(env)
|
||||
board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper()
|
||||
print("Board=#%s#"%board)
|
||||
print("BuildFlags=%s"%(" ".join(env["BUILD_FLAGS"])))
|
||||
env.Append(
|
||||
LINKFLAGS=[ "-u", "custom_app_desc" ],
|
||||
CPPDEFINES=[(board,"1")]
|
||||
)
|
||||
#script does not run on clean yet - maybe in the future
|
||||
env.AddPostAction("clean",cleangenerated)
|
||||
|
||||
#look for extra task scripts and include them here
|
||||
for taskdir in userTaskDirs:
|
||||
script = os.path.join(taskdir, "extra_task.py")
|
||||
if os.path.isfile(script):
|
||||
taskname = os.path.basename(os.path.normpath(taskdir))
|
||||
print("#extra task script for '{}'".format(taskname))
|
||||
with open(script) as fh:
|
||||
try:
|
||||
code = compile(fh.read(), taskname, 'exec')
|
||||
except SyntaxError:
|
||||
print("#ERROR: script does not compile")
|
||||
continue
|
||||
exec(code)
|
||||
@@ -1,518 +0,0 @@
|
||||
print("running extra...")
|
||||
import gzip
|
||||
import shutil
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import json
|
||||
import glob
|
||||
from datetime import datetime
|
||||
import re
|
||||
import pprint
|
||||
from platformio.project.config import ProjectConfig
|
||||
|
||||
|
||||
Import("env")
|
||||
#print(env.Dump())
|
||||
OWN_FILE="extra_script.py"
|
||||
GEN_DIR='lib/generated'
|
||||
CFG_FILE='web/config.json'
|
||||
XDR_FILE='web/xdrconfig.json'
|
||||
INDEXJS="index.js"
|
||||
INDEXCSS="index.css"
|
||||
CFG_INCLUDE='GwConfigDefinitions.h'
|
||||
CFG_INCLUDE_IMPL='GwConfigDefImpl.h'
|
||||
XDR_INCLUDE='GwXdrTypeMappings.h'
|
||||
TASK_INCLUDE='GwUserTasks.h'
|
||||
GROVE_CONFIG="GwM5GroveGen.h"
|
||||
GROVE_CONFIG_IN="lib/hardware/GwM5Grove.in"
|
||||
EMBEDDED_INCLUDE="GwEmbeddedFiles.h"
|
||||
|
||||
def getEmbeddedFiles(env):
|
||||
rt=[]
|
||||
efiles=env.GetProjectOption("board_build.embed_files")
|
||||
for f in efiles.split("\n"):
|
||||
if f == '':
|
||||
continue
|
||||
rt.append(f)
|
||||
return rt
|
||||
|
||||
def basePath():
|
||||
#see: https://stackoverflow.com/questions/16771894/python-nameerror-global-name-file-is-not-defined
|
||||
return os.path.dirname(inspect.getfile(lambda: None))
|
||||
|
||||
def outPath():
|
||||
return os.path.join(basePath(),GEN_DIR)
|
||||
def checkDir():
|
||||
dn=outPath()
|
||||
if not os.path.exists(dn):
|
||||
os.makedirs(dn)
|
||||
if not os.path.isdir(dn):
|
||||
print("unable to create %s"%dn)
|
||||
return False
|
||||
return True
|
||||
|
||||
def isCurrent(infile,outfile):
|
||||
if os.path.exists(outfile):
|
||||
otime=os.path.getmtime(outfile)
|
||||
itime=os.path.getmtime(infile)
|
||||
if (otime >= itime):
|
||||
own=os.path.join(basePath(),OWN_FILE)
|
||||
if os.path.exists(own):
|
||||
owntime=os.path.getmtime(own)
|
||||
if owntime > otime:
|
||||
return False
|
||||
print("%s is newer then %s, no need to recreate"%(outfile,infile))
|
||||
return True
|
||||
return False
|
||||
def compressFile(inFile,outfile):
|
||||
if isCurrent(inFile,outfile):
|
||||
return
|
||||
print("compressing %s"%inFile)
|
||||
with open(inFile, 'rb') as f_in:
|
||||
with gzip.open(outfile, 'wb') as f_out:
|
||||
shutil.copyfileobj(f_in, f_out)
|
||||
|
||||
def generateFile(infile,outfile,callback,inMode='rb',outMode='w'):
|
||||
if isCurrent(infile,outfile):
|
||||
return
|
||||
print("creating %s"%outfile)
|
||||
oh=None
|
||||
with open(infile,inMode) as ch:
|
||||
with open(outfile,outMode) as oh:
|
||||
try:
|
||||
callback(ch,oh,inFile=infile)
|
||||
oh.close()
|
||||
except Exception as e:
|
||||
try:
|
||||
oh.close()
|
||||
except:
|
||||
pass
|
||||
os.unlink(outfile)
|
||||
raise
|
||||
|
||||
def writeFileIfChanged(fileName,data):
|
||||
if os.path.exists(fileName):
|
||||
with open(fileName,"r") as ih:
|
||||
old=ih.read()
|
||||
ih.close()
|
||||
if old == data:
|
||||
return False
|
||||
print("#generating %s"%fileName)
|
||||
with open(fileName,"w") as oh:
|
||||
oh.write(data)
|
||||
return True
|
||||
|
||||
def mergeConfig(base,other):
|
||||
for bdir in other:
|
||||
cname=os.path.join(bdir,"config.json")
|
||||
if os.path.exists(cname):
|
||||
print("merge config %s"%cname)
|
||||
with open(cname,'rb') as ah:
|
||||
merge=json.load(ah)
|
||||
base=base+merge
|
||||
return base
|
||||
|
||||
def replaceTexts(data,replacements):
|
||||
if replacements is None:
|
||||
return data
|
||||
if isinstance(data,str):
|
||||
for k,v in replacements.items():
|
||||
data=data.replace("$"+k,str(v))
|
||||
return data
|
||||
if isinstance(data,list):
|
||||
rt=[]
|
||||
for e in data:
|
||||
rt.append(replaceTexts(e,replacements))
|
||||
return rt
|
||||
if isinstance(data,dict):
|
||||
rt={}
|
||||
for k,v in data.items():
|
||||
rt[replaceTexts(k,replacements)]=replaceTexts(v,replacements)
|
||||
return rt
|
||||
return data
|
||||
def expandConfig(config):
|
||||
rt=[]
|
||||
for item in config:
|
||||
type=item.get('type')
|
||||
if type != 'array':
|
||||
rt.append(item)
|
||||
continue
|
||||
replacements=item.get('replace')
|
||||
children=item.get('children')
|
||||
name=item.get('name')
|
||||
if name is None:
|
||||
name="#unknown#"
|
||||
if not isinstance(replacements,list):
|
||||
raise Exception("missing replacements at array %s"%name)
|
||||
for replace in replacements:
|
||||
if children is not None:
|
||||
for c in children:
|
||||
rt.append(replaceTexts(c,replace))
|
||||
return rt
|
||||
|
||||
def generateMergedConfig(inFile,outFile,addDirs=[]):
|
||||
if not os.path.exists(inFile):
|
||||
raise Exception("unable to read cfg file %s"%inFile)
|
||||
data=""
|
||||
with open(inFile,'rb') as ch:
|
||||
config=json.load(ch)
|
||||
config=mergeConfig(config,addDirs)
|
||||
config=expandConfig(config)
|
||||
data=json.dumps(config,indent=2)
|
||||
writeFileIfChanged(outFile,data)
|
||||
|
||||
def generateCfg(inFile,outFile,impl):
|
||||
if not os.path.exists(inFile):
|
||||
raise Exception("unable to read cfg file %s"%inFile)
|
||||
data=""
|
||||
with open(inFile,'rb') as ch:
|
||||
config=json.load(ch)
|
||||
data+="//generated from %s\n"%inFile
|
||||
l=len(config)
|
||||
idx=0
|
||||
if not impl:
|
||||
data+='#include "GwConfigItem.h"\n'
|
||||
data+='class GwConfigDefinitions{\n'
|
||||
data+=' public:\n'
|
||||
data+=' int getNumConfig() const{return %d;}\n'%(l)
|
||||
for item in config:
|
||||
n=item.get('name')
|
||||
if n is None:
|
||||
continue
|
||||
if len(n) > 15:
|
||||
raise Exception("%s: config names must be max 15 caracters"%n)
|
||||
data+=' static constexpr const char* %s="%s";\n'%(n,n)
|
||||
data+="};\n"
|
||||
else:
|
||||
data+='void GwConfigHandler::populateConfigs(GwConfigInterface **config){\n'
|
||||
for item in config:
|
||||
name=item.get('name')
|
||||
if name is None:
|
||||
continue
|
||||
data+=' configs[%d]='%(idx)
|
||||
idx+=1
|
||||
secret="false";
|
||||
if item.get('type') == 'password':
|
||||
secret="true"
|
||||
data+=" new GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret)
|
||||
data+='}\n'
|
||||
writeFileIfChanged(outFile,data)
|
||||
|
||||
def labelFilter(label):
|
||||
return re.sub("[^a-zA-Z0-9]","",re.sub("\([0-9]*\)","",label))
|
||||
def generateXdrMappings(fp,oh,inFile=''):
|
||||
jdoc=json.load(fp)
|
||||
oh.write("static GwXDRTypeMapping* typeMappings[]={\n")
|
||||
first=True
|
||||
for cat in jdoc:
|
||||
item=jdoc[cat]
|
||||
cid=item.get('id')
|
||||
if cid is None:
|
||||
continue
|
||||
tc=item.get('type')
|
||||
if tc is not None:
|
||||
if first:
|
||||
first=False
|
||||
else:
|
||||
oh.write(",\n")
|
||||
oh.write(" new GwXDRTypeMapping(%d,0,%d) /*%s*/"%(cid,tc,cat))
|
||||
fields=item.get('fields')
|
||||
if fields is None:
|
||||
continue
|
||||
idx=0
|
||||
for fe in fields:
|
||||
if not isinstance(fe,dict):
|
||||
continue
|
||||
tc=fe.get('t')
|
||||
id=fe.get('v')
|
||||
if id is None:
|
||||
id=idx
|
||||
idx+=1
|
||||
l=fe.get('l') or ''
|
||||
if tc is None or id is None:
|
||||
continue
|
||||
if first:
|
||||
first=False
|
||||
else:
|
||||
oh.write(",\n")
|
||||
oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,id,tc,cat,l))
|
||||
oh.write("\n")
|
||||
oh.write("};\n")
|
||||
for cat in jdoc:
|
||||
item=jdoc[cat]
|
||||
cid=item.get('id')
|
||||
if cid is None:
|
||||
continue
|
||||
selectors=item.get('selector')
|
||||
if selectors is not None:
|
||||
for selector in selectors:
|
||||
label=selector.get('l')
|
||||
value=selector.get('v')
|
||||
if label is not None and value is not None:
|
||||
label=labelFilter(label)
|
||||
define=("GWXDRSEL_%s_%s"%(cat,label)).upper()
|
||||
oh.write(" #define %s %s\n"%(define,value))
|
||||
fields=item.get('fields')
|
||||
if fields is not None:
|
||||
idx=0
|
||||
for field in fields:
|
||||
v=field.get('v')
|
||||
if v is None:
|
||||
v=idx
|
||||
else:
|
||||
v=int(v)
|
||||
label=field.get('l')
|
||||
if v is not None and label is not None:
|
||||
define=("GWXDRFIELD_%s_%s"%(cat,labelFilter(label))).upper();
|
||||
oh.write(" #define %s %s\n"%(define,str(v)))
|
||||
idx+=1
|
||||
|
||||
class Grove:
|
||||
def __init__(self,name) -> None:
|
||||
self.name=name
|
||||
def _ss(self,z=False):
|
||||
if z:
|
||||
return self.name
|
||||
return self.name if self.name is not 'Z' else ''
|
||||
def _suffix(self):
|
||||
return '_'+self.name if self.name is not 'Z' else ''
|
||||
def replace(self,line):
|
||||
if line is None:
|
||||
return line
|
||||
return line.replace('$G$',self._ss()).replace('$Z$',self._ss(True)).replace('$GS$',self._suffix())
|
||||
def generateGroveDefs(inh,outh,inFile=''):
|
||||
GROVES=[Grove('Z'),Grove('A'),Grove('B'),Grove('C')]
|
||||
definition=[]
|
||||
started=False
|
||||
def writeConfig():
|
||||
for grove in GROVES:
|
||||
for cl in definition:
|
||||
outh.write(grove.replace(cl))
|
||||
|
||||
for line in inh:
|
||||
if re.match(" *#GROVE",line):
|
||||
started=True
|
||||
if len(definition) > 0:
|
||||
writeConfig()
|
||||
definition=[]
|
||||
continue
|
||||
if started:
|
||||
definition.append(line)
|
||||
if len(definition) > 0:
|
||||
writeConfig()
|
||||
|
||||
|
||||
|
||||
userTaskDirs=[]
|
||||
|
||||
def getUserTaskDirs():
|
||||
rt=[]
|
||||
taskdirs=glob.glob(os.path.join( basePath(),'lib','*task*'))
|
||||
for task in taskdirs:
|
||||
rt.append(task)
|
||||
return rt
|
||||
|
||||
def checkAndAdd(file,names,ilist):
|
||||
if not file.endswith('.h'):
|
||||
return
|
||||
match=False
|
||||
for cmp in names:
|
||||
#print("##check %s<->%s"%(f.lower(),cmp))
|
||||
if file.lower() == cmp:
|
||||
match=True
|
||||
if not match:
|
||||
return
|
||||
ilist.append(file)
|
||||
def genereateUserTasks(outfile):
|
||||
includes=[]
|
||||
for task in userTaskDirs:
|
||||
#print("##taskdir=%s"%task)
|
||||
base=os.path.basename(task)
|
||||
includeNames=[base.lower()+".h",'gw'+base.lower()+'.h']
|
||||
for f in os.listdir(task):
|
||||
checkAndAdd(f,includeNames,includes)
|
||||
includeData=""
|
||||
for i in includes:
|
||||
print("#task include %s"%i)
|
||||
includeData+="#include <%s>\n"%i
|
||||
writeFileIfChanged(outfile,includeData)
|
||||
|
||||
def generateEmbedded(elist,outFile):
|
||||
content=""
|
||||
for entry in elist:
|
||||
content+="EMBED_GZ_FILE(\"%s\",%s,\"%s\");\n"%entry
|
||||
writeFileIfChanged(outFile,content)
|
||||
|
||||
def getContentType(fn):
|
||||
if (fn.endswith('.gz')):
|
||||
fn=fn[0:-3]
|
||||
if (fn.endswith('html')):
|
||||
return "text/html"
|
||||
if (fn.endswith('json')):
|
||||
return "application/json"
|
||||
if (fn.endswith('js')):
|
||||
return "text/javascript"
|
||||
if (fn.endswith('css')):
|
||||
return "text/css"
|
||||
return "application/octet-stream"
|
||||
|
||||
|
||||
def getLibs():
|
||||
base=os.path.join(basePath(),"lib")
|
||||
rt=[]
|
||||
for sd in os.listdir(base):
|
||||
if sd == '..':
|
||||
continue
|
||||
if sd == '.':
|
||||
continue
|
||||
fn=os.path.join(base,sd)
|
||||
if os.path.isdir(fn):
|
||||
rt.append(sd)
|
||||
EXTRAS=['generated']
|
||||
for e in EXTRAS:
|
||||
if not e in rt:
|
||||
rt.append(e)
|
||||
return rt
|
||||
|
||||
|
||||
|
||||
def joinFiles(target,pattern,dirlist):
|
||||
flist=[]
|
||||
for dir in dirlist:
|
||||
fn=os.path.join(dir,pattern)
|
||||
if os.path.exists(fn):
|
||||
flist.append(fn)
|
||||
current=False
|
||||
if os.path.exists(target):
|
||||
current=True
|
||||
for f in flist:
|
||||
if not isCurrent(f,target):
|
||||
current=False
|
||||
break
|
||||
if current:
|
||||
print("%s is up to date"%target)
|
||||
return
|
||||
print("creating %s"%target)
|
||||
with gzip.open(target,"wb") as oh:
|
||||
for fn in flist:
|
||||
print("adding %s to %s"%(fn,target))
|
||||
with open(fn,"rb") as rh:
|
||||
shutil.copyfileobj(rh,oh)
|
||||
|
||||
|
||||
OWNLIBS=getLibs()+["FS","WiFi"]
|
||||
GLOBAL_INCLUDES=[]
|
||||
|
||||
def handleDeps(env):
|
||||
#overwrite the GetProjectConfig
|
||||
#to inject all our libs
|
||||
oldGetProjectConfig=env.GetProjectConfig
|
||||
def GetProjectConfigX(env):
|
||||
rt=oldGetProjectConfig()
|
||||
cenv="env:"+env['PIOENV']
|
||||
libs=[]
|
||||
for section,options in rt.as_tuple():
|
||||
if section == cenv:
|
||||
for key,values in options:
|
||||
if key == 'lib_deps':
|
||||
libs=values
|
||||
|
||||
mustUpdate=False
|
||||
for lib in OWNLIBS:
|
||||
if not lib in libs:
|
||||
libs.append(lib)
|
||||
mustUpdate=True
|
||||
if mustUpdate:
|
||||
update=[(cenv,[('lib_deps',libs)])]
|
||||
rt.update(update)
|
||||
return rt
|
||||
env.AddMethod(GetProjectConfigX,"GetProjectConfig")
|
||||
#store the list of all includes after we resolved
|
||||
#the dependencies for our main project
|
||||
#we will use them for all compilations afterwards
|
||||
oldLibBuilder=env.ConfigureProjectLibBuilder
|
||||
def ConfigureProjectLibBuilderX(env):
|
||||
global GLOBAL_INCLUDES
|
||||
project=oldLibBuilder()
|
||||
#print("##ConfigureProjectLibBuilderX")
|
||||
#pprint.pprint(project)
|
||||
if project.depbuilders:
|
||||
#print("##depbuilders %s"%",".join(map(lambda x: x.path,project.depbuilders)))
|
||||
for db in project.depbuilders:
|
||||
idirs=db.get_include_dirs()
|
||||
for id in idirs:
|
||||
if not id in GLOBAL_INCLUDES:
|
||||
GLOBAL_INCLUDES.append(id)
|
||||
return project
|
||||
env.AddMethod(ConfigureProjectLibBuilderX,"ConfigureProjectLibBuilder")
|
||||
def injectIncludes(env,node):
|
||||
return env.Object(
|
||||
node,
|
||||
CPPPATH=env["CPPPATH"]+GLOBAL_INCLUDES
|
||||
)
|
||||
env.AddBuildMiddleware(injectIncludes)
|
||||
|
||||
|
||||
def prebuild(env):
|
||||
global userTaskDirs
|
||||
print("#prebuild running")
|
||||
if not checkDir():
|
||||
sys.exit(1)
|
||||
ldf_mode=env.GetProjectOption("lib_ldf_mode")
|
||||
if ldf_mode == 'off':
|
||||
print("##ldf off - own dependency handling")
|
||||
handleDeps(env)
|
||||
userTaskDirs=getUserTaskDirs()
|
||||
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
|
||||
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
|
||||
compressFile(mergedConfig,mergedConfig+".gz")
|
||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False)
|
||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True)
|
||||
joinFiles(os.path.join(outPath(),INDEXJS+".gz"),INDEXJS,["web"]+userTaskDirs)
|
||||
joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),INDEXCSS,["web"]+userTaskDirs)
|
||||
embedded=getEmbeddedFiles(env)
|
||||
filedefs=[]
|
||||
for ef in embedded:
|
||||
print("#checking embedded file %s"%ef)
|
||||
(dn,fn)=os.path.split(ef)
|
||||
pureName=fn
|
||||
if pureName.endswith('.gz'):
|
||||
pureName=pureName[0:-3]
|
||||
ct=getContentType(pureName)
|
||||
usname=ef.replace('/','_').replace('.','_')
|
||||
filedefs.append((pureName,usname,ct))
|
||||
inFile=os.path.join(basePath(),"web",pureName)
|
||||
if os.path.exists(inFile):
|
||||
compressFile(inFile,ef)
|
||||
else:
|
||||
print("#WARNING: infile %s for %s not found"%(inFile,ef))
|
||||
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
|
||||
genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
|
||||
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
|
||||
generateFile(os.path.join(basePath(),GROVE_CONFIG_IN),os.path.join(outPath(),GROVE_CONFIG),generateGroveDefs,inMode='r')
|
||||
version="dev"+datetime.now().strftime("%Y%m%d")
|
||||
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
|
||||
|
||||
def cleangenerated(source, target, env):
|
||||
od=outPath()
|
||||
if os.path.isdir(od):
|
||||
print("#cleaning up %s"%od)
|
||||
for f in os.listdir(od):
|
||||
if f == "." or f == "..":
|
||||
continue
|
||||
fn=os.path.join(od,f)
|
||||
os.unlink(f)
|
||||
|
||||
|
||||
print("#prescript...")
|
||||
prebuild(env)
|
||||
board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper()
|
||||
print("Board=#%s#"%board)
|
||||
print("BuildFlags=%s"%(" ".join(env["BUILD_FLAGS"])))
|
||||
env.Append(
|
||||
LINKFLAGS=[ "-u", "custom_app_desc" ],
|
||||
CPPDEFINES=[(board,"1")]
|
||||
)
|
||||
#script does not run on clean yet - maybe in the future
|
||||
env.AddPostAction("clean",cleangenerated)
|
||||
@@ -627,7 +627,7 @@ void AisDecoder::decodeType21(PayloadBuffer &_buffer, unsigned int _uMsgType, in
|
||||
}
|
||||
|
||||
// decode message fields (binary buffer has to go through all fields, but some fields are not used)
|
||||
_buffer.getUnsignedValue(2); // repeatIndicator
|
||||
auto repeat=_buffer.getUnsignedValue(2); // repeatIndicator
|
||||
auto mmsi = _buffer.getUnsignedValue(30);
|
||||
auto aidType = _buffer.getUnsignedValue(5);
|
||||
auto name = _buffer.getString(120);
|
||||
@@ -640,11 +640,11 @@ void AisDecoder::decodeType21(PayloadBuffer &_buffer, unsigned int _uMsgType, in
|
||||
auto toStarboard = _buffer.getUnsignedValue(6);
|
||||
|
||||
_buffer.getUnsignedValue(4); // epfd type
|
||||
_buffer.getUnsignedValue(6); // timestamp
|
||||
_buffer.getBoolValue(); // off position
|
||||
auto timestamp=_buffer.getUnsignedValue(6); // timestamp
|
||||
auto offPosition=_buffer.getBoolValue(); // off position
|
||||
_buffer.getUnsignedValue(8); // reserved
|
||||
_buffer.getBoolValue(); // RAIM
|
||||
_buffer.getBoolValue(); // virtual aid
|
||||
auto raim=_buffer.getBoolValue(); // RAIM
|
||||
auto virtualAton=_buffer.getBoolValue(); // virtual aid
|
||||
_buffer.getBoolValue(); // assigned mode
|
||||
_buffer.getUnsignedValue(1); // spare
|
||||
|
||||
@@ -654,7 +654,9 @@ void AisDecoder::decodeType21(PayloadBuffer &_buffer, unsigned int _uMsgType, in
|
||||
nameExt = _buffer.getString(88);
|
||||
}
|
||||
|
||||
onType21(mmsi, aidType, name + nameExt, posAccuracy, posLon, posLat, toBow, toStern, toPort, toStarboard);
|
||||
onType21(mmsi, aidType, name + nameExt, posAccuracy, posLon, posLat,
|
||||
toBow, toStern, toPort, toStarboard,
|
||||
repeat,timestamp, raim, virtualAton, offPosition);
|
||||
}
|
||||
|
||||
/* decode Voyage Report and Static Data (type nibble already pulled from buffer) */
|
||||
|
||||
@@ -297,7 +297,8 @@ namespace AIS
|
||||
bool assigned, unsigned int repeat, bool raim) = 0;
|
||||
|
||||
virtual void onType21(unsigned int _uMmsi, unsigned int _uAidType, const std::string &_strName, bool _bPosAccuracy, int _iPosLon, int _iPosLat,
|
||||
unsigned int _uToBow, unsigned int _uToStern, unsigned int _uToPort, unsigned int _uToStarboard) = 0;
|
||||
unsigned int _uToBow, unsigned int _uToStern, unsigned int _uToPort, unsigned int _uToStarboard,
|
||||
unsigned int repeat,unsigned int timestamp, bool raim, bool virtualAton, bool offPosition) = 0;
|
||||
|
||||
virtual void onType24A(unsigned int _uMsgType, unsigned int _repeat, unsigned int _uMmsi, const std::string &_strName) = 0;
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
#define LOGLEVEL GwLog::DEBUG
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef GWBUILD_NAME
|
||||
#define FIRMWARE_TYPE GWSTRINGIFY(GWBUILD_NAME)
|
||||
#else
|
||||
#define FIRMWARE_TYPE GWSTRINGIFY(PIO_ENV_BUILD)
|
||||
#endif
|
||||
#define IDF_VERSION GWSTRINGIFY(ESP_IDF_VERSION_MAJOR) "." GWSTRINGIFY(ESP_IDF_VERSION_MINOR) "." GWSTRINGIFY(ESP_IDF_VERSION_PATCH)
|
||||
@@ -249,3 +249,16 @@ unsigned long GwChannel::countTx(){
|
||||
if (! countOut) return 0UL;
|
||||
return countOut->getGlobal();
|
||||
}
|
||||
String GwChannel::typeString(int type){
|
||||
switch (type){
|
||||
case GWSERIAL_TYPE_UNI:
|
||||
return "UNI";
|
||||
case GWSERIAL_TYPE_BI:
|
||||
return "BI";
|
||||
case GWSERIAL_TYPE_RX:
|
||||
return "RX";
|
||||
case GWSERIAL_TYPE_TX:
|
||||
return "TX";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
@@ -77,7 +77,8 @@ class GwChannel{
|
||||
if (maxSourceId < 0) return source == sourceId;
|
||||
return (source >= sourceId && source <= maxSourceId);
|
||||
}
|
||||
String getMode(){return impl->getMode();}
|
||||
static String typeString(int type);
|
||||
String getMode(){return typeString(impl->getType());}
|
||||
int getMinId(){return sourceId;};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
#include "GwBuffer.h"
|
||||
#include "GwChannelModes.h"
|
||||
class GwChannelInterface{
|
||||
public:
|
||||
virtual void loop(bool handleRead,bool handleWrite)=0;
|
||||
virtual void readMessages(GwMessageFetcher *writer)=0;
|
||||
virtual size_t sendToClients(const char *buffer, int sourceId, bool partial=false)=0;
|
||||
virtual Stream * getStream(bool partialWrites){ return NULL;}
|
||||
virtual String getMode(){return "UNKNOWN";}
|
||||
virtual int getType(){ return GWSERIAL_TYPE_BI;} //return the numeric type
|
||||
};
|
||||
@@ -15,8 +15,10 @@ class SerInit{
|
||||
int tx=-1;
|
||||
int mode=-1;
|
||||
int fixedBaud=-1;
|
||||
SerInit(int s,int r,int t, int m, int b=-1):
|
||||
serial(s),rx(r),tx(t),mode(m),fixedBaud(b){}
|
||||
int ena=-1;
|
||||
int elow=1;
|
||||
SerInit(int s,int r,int t, int m, int b=-1,int en=-1,int el=-1):
|
||||
serial(s),rx(r),tx(t),mode(m),fixedBaud(b),ena(en),elow(el){}
|
||||
};
|
||||
std::vector<SerInit> serialInits;
|
||||
|
||||
@@ -47,11 +49,20 @@ static int typeFromMode(const char *mode){
|
||||
#ifndef GWSERIAL_RX
|
||||
#define GWSERIAL_RX -1
|
||||
#endif
|
||||
#ifndef GWSERIAL_ENA
|
||||
#define GWSERIAL_ENA -1
|
||||
#endif
|
||||
#ifndef GWSERIAL_ELO
|
||||
#define GWSERIAL_ELO 0
|
||||
#endif
|
||||
#ifndef GWSERIAL_BAUD
|
||||
#define GWSERIAL_BAUD -1
|
||||
#endif
|
||||
#ifdef GWSERIAL_TYPE
|
||||
CFG_SERIAL(SERIAL1_CHANNEL_ID, GWSERIAL_RX, GWSERIAL_TX, GWSERIAL_TYPE)
|
||||
CFG_SERIAL(SERIAL1_CHANNEL_ID, GWSERIAL_RX, GWSERIAL_TX, GWSERIAL_TYPE,GWSERIAL_BAUD,GWSERIAL_ENA,GWSERIAL_ELO)
|
||||
#else
|
||||
#ifdef GWSERIAL_MODE
|
||||
CFG_SERIAL(SERIAL1_CHANNEL_ID, GWSERIAL_RX, GWSERIAL_TX, typeFromMode(GWSERIAL_MODE))
|
||||
CFG_SERIAL(SERIAL1_CHANNEL_ID, GWSERIAL_RX, GWSERIAL_TX, typeFromMode(GWSERIAL_MODE),GWSERIAL_BAUD,GWSERIAL_ENA,GWSERIAL_ELO)
|
||||
#endif
|
||||
#endif
|
||||
// serial 2
|
||||
@@ -61,11 +72,20 @@ CFG_SERIAL(SERIAL1_CHANNEL_ID, GWSERIAL_RX, GWSERIAL_TX, typeFromMode(GWSERIAL_M
|
||||
#ifndef GWSERIAL2_RX
|
||||
#define GWSERIAL2_RX -1
|
||||
#endif
|
||||
#ifndef GWSERIAL2_ENA
|
||||
#define GWSERIAL2_ENA -1
|
||||
#endif
|
||||
#ifndef GWSERIAL2_ELO
|
||||
#define GWSERIAL2_ELO 0
|
||||
#endif
|
||||
#ifndef GWSERIAL2_BAUD
|
||||
#define GWSERIAL2_BAUD -1
|
||||
#endif
|
||||
#ifdef GWSERIAL2_TYPE
|
||||
CFG_SERIAL(SERIAL2_CHANNEL_ID, GWSERIAL2_RX, GWSERIAL2_TX, GWSERIAL2_TYPE)
|
||||
CFG_SERIAL(SERIAL2_CHANNEL_ID, GWSERIAL2_RX, GWSERIAL2_TX, GWSERIAL2_TYPE,GWSERIAL2_BAUD,GWSERIAL2_ENA,GWSERIAL2_ELO)
|
||||
#else
|
||||
#ifdef GWSERIAL2_MODE
|
||||
CFG_SERIAL(SERIAL2_CHANNEL_ID, GWSERIAL2_RX, GWSERIAL2_TX, typeFromMode(GWSERIAL2_MODE))
|
||||
CFG_SERIAL(SERIAL2_CHANNEL_ID, GWSERIAL2_RX, GWSERIAL2_TX, typeFromMode(GWSERIAL2_MODE),GWSERIAL2_BAUD,GWSERIAL2_ENA,GWSERIAL2_ELO)
|
||||
#endif
|
||||
#endif
|
||||
class GwSerialLog : public GwLogWriter
|
||||
@@ -285,8 +305,8 @@ static ChannelParam channelParameters[]={
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
GwSerial* createSerial(GwLog *logger, T* s,int id, bool canRead=true){
|
||||
return new GwSerialImpl<T>(logger,s,id,canRead);
|
||||
GwSerial* createSerial(GwLog *logger, T* s,int id, int type, bool canRead=true){
|
||||
return new GwSerialImpl<T>(logger,s,id,type,canRead);
|
||||
}
|
||||
|
||||
static ChannelParam * findChannelParam(int id){
|
||||
@@ -300,7 +320,7 @@ static ChannelParam * findChannelParam(int id){
|
||||
return param;
|
||||
}
|
||||
|
||||
static GwSerial * createSerialImpl(GwConfigHandler *config,GwLog *logger, int idx,int rx,int tx, bool setLog=false){
|
||||
static GwSerial * createSerialImpl(GwConfigHandler *config,GwLog *logger, int idx,int type,int rx,int tx, bool setLog,int ena=-1,int elow=1){
|
||||
LOG_DEBUG(GwLog::DEBUG,"create serial: channel=%d, rx=%d,tx=%d",
|
||||
idx,rx,tx);
|
||||
ChannelParam *param=findChannelParam(idx);
|
||||
@@ -312,19 +332,45 @@ static GwSerial * createSerialImpl(GwConfigHandler *config,GwLog *logger, int id
|
||||
GwLog *streamLog=setLog?nullptr:logger;
|
||||
switch(param->id){
|
||||
case USB_CHANNEL_ID:
|
||||
serialStream=createSerial(streamLog,&USBSerial,param->id);
|
||||
serialStream=createSerial(streamLog,&USBSerial,param->id,type);
|
||||
break;
|
||||
case SERIAL1_CHANNEL_ID:
|
||||
serialStream=createSerial(streamLog,&Serial1,param->id);
|
||||
serialStream=createSerial(streamLog,&Serial1,param->id,type);
|
||||
break;
|
||||
case SERIAL2_CHANNEL_ID:
|
||||
serialStream=createSerial(streamLog,&Serial2,param->id);
|
||||
serialStream=createSerial(streamLog,&Serial2,param->id,type);
|
||||
break;
|
||||
}
|
||||
if (serialStream == nullptr){
|
||||
LOG_DEBUG(GwLog::ERROR,"invalid serial config with id %d",param->id);
|
||||
return nullptr;
|
||||
}
|
||||
if (ena >= 0){
|
||||
int value=-1;
|
||||
if (type == GWSERIAL_TYPE_UNI){
|
||||
String cfgMode=config->getString(param->direction);
|
||||
if (cfgMode == "send"){
|
||||
value=elow?0:1;
|
||||
}
|
||||
else{
|
||||
value=elow?1:0;
|
||||
}
|
||||
}
|
||||
if (type == GWSERIAL_TYPE_RX){
|
||||
value=elow?1:0;
|
||||
}
|
||||
if (type == GWSERIAL_TYPE_TX){
|
||||
value=elow?0:1;
|
||||
}
|
||||
if (value >= 0){
|
||||
LOG_DEBUG(GwLog::LOG,"serial %d: setting output enable %d to %d",param->id,ena,value);
|
||||
pinMode(ena,OUTPUT);
|
||||
digitalWrite(ena,value);
|
||||
}
|
||||
else{
|
||||
LOG_DEBUG(GwLog::ERROR,"serial %d: output enable ignored for mode %d",param->id, type);
|
||||
}
|
||||
}
|
||||
serialStream->begin(config->getInt(param->baud,115200),SERIAL_8N1,rx,tx);
|
||||
if (setLog){
|
||||
logger->setWriter(new GwSerialLog(serialStream,config->getBool(param->preventLog,false)));
|
||||
@@ -332,12 +378,13 @@ static GwSerial * createSerialImpl(GwConfigHandler *config,GwLog *logger, int id
|
||||
}
|
||||
return serialStream;
|
||||
}
|
||||
static GwChannel * createChannel(GwLog *logger, GwConfigHandler *config, int id,GwChannelInterface *impl, int type=GWSERIAL_TYPE_BI){
|
||||
static GwChannel * createChannel(GwLog *logger, GwConfigHandler *config, int id,GwChannelInterface *impl){
|
||||
ChannelParam *param=findChannelParam(id);
|
||||
if (param == nullptr){
|
||||
LOG_DEBUG(GwLog::ERROR,"invalid channel id %d",id);
|
||||
return nullptr;
|
||||
}
|
||||
int type=impl->getType();
|
||||
bool canRead=false;
|
||||
bool canWrite=false;
|
||||
bool validType=false;
|
||||
@@ -425,10 +472,10 @@ void GwChannelList::begin(bool fallbackSerial){
|
||||
GwChannel *channel=NULL;
|
||||
//usb
|
||||
if (! fallbackSerial){
|
||||
GwSerial *usbSerial=createSerialImpl(config, logger,USB_CHANNEL_ID,GWUSB_RX,GWUSB_TX,true);
|
||||
GwSerial *usbSerial=createSerialImpl(config, logger,USB_CHANNEL_ID,GWSERIAL_TYPE_BI,GWUSB_RX,GWUSB_TX,true);
|
||||
if (usbSerial != nullptr){
|
||||
usbSerial->enableWriteLock(); //as it is used for logging we need this additionally
|
||||
GwChannel *usbChannel=createChannel(logger,config,USB_CHANNEL_ID,usbSerial,GWSERIAL_TYPE_BI);
|
||||
GwChannel *usbChannel=createChannel(logger,config,USB_CHANNEL_ID,usbSerial);
|
||||
if (usbChannel != nullptr){
|
||||
addChannel(usbChannel);
|
||||
}
|
||||
@@ -444,10 +491,11 @@ void GwChannelList::begin(bool fallbackSerial){
|
||||
|
||||
//new serial config handling
|
||||
for (auto &&init:serialInits){
|
||||
LOG_INFO("creating serial channel %d, rx=%d,tx=%d,type=%d",init.serial,init.rx,init.tx,init.mode);
|
||||
GwSerial *ser=createSerialImpl(config,logger,init.serial,init.rx,init.tx);
|
||||
LOG_INFO("creating serial channel %d, rx=%d,tx=%d,type=%d fixedBaud=%d ena=%d elow=%d",
|
||||
init.serial,init.rx,init.tx,init.mode,init.fixedBaud,init.ena,init.elow);
|
||||
GwSerial *ser=createSerialImpl(config,logger,init.serial,init.mode,init.rx,init.tx,false,init.ena,init.elow);
|
||||
if (ser != nullptr){
|
||||
channel=createChannel(logger,config,init.serial,ser,init.mode);
|
||||
channel=createChannel(logger,config,init.serial,ser);
|
||||
if (channel != nullptr){
|
||||
addChannel(channel);
|
||||
}
|
||||
@@ -466,8 +514,8 @@ void GwChannelList::begin(bool fallbackSerial){
|
||||
config->getInt(config->remotePort),
|
||||
config->getBool(config->readTCL)
|
||||
);
|
||||
addChannel(createChannel(logger,config,TCP_CLIENT_CHANNEL_ID,client));
|
||||
}
|
||||
addChannel(createChannel(logger,config,TCP_CLIENT_CHANNEL_ID,client));
|
||||
|
||||
//udp writer
|
||||
if (config->getBool(GwConfigDefinitions::udpwEnabled)){
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|
||||
@@ -57,6 +57,44 @@ Files
|
||||
|
||||
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)
|
||||
|
||||
* [script.py](script.py)<br>
|
||||
Starting from version 20251007 you can define a parameter "custom_script" in your [platformio.ini](platformio.ini).
|
||||
This parameter can contain a list of file names (relative to the project root) that will be added as a [platformio extra script](https://docs.platformio.org/en/latest/scripting/index.html#scripting). The scripts will be loaded at the end of the main [extra_script](../../extra_script.py).
|
||||
You can add code there that is specific for your build.
|
||||
Example:
|
||||
```
|
||||
# PlatformIO extra script for obp60task
|
||||
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]
|
||||
|
||||
propfilename = os.path.join(env["PROJECT_LIBDEPS_DIR"], env ["PIOENV"], "GxEPD2/library.properties")
|
||||
properties = {}
|
||||
with open(propfilename, 'r') as file:
|
||||
for line in file:
|
||||
match = re.match(r'^([^=]+)=(.*)$', line)
|
||||
if match:
|
||||
key = match.group(1).strip()
|
||||
value = match.group(2).strip()
|
||||
properties[key] = value
|
||||
|
||||
gxepd2vers = "unknown"
|
||||
try:
|
||||
if properties["name"] == "GxEPD2":
|
||||
gxepd2vers = properties["version"]
|
||||
except:
|
||||
pass
|
||||
|
||||
env["CPPDEFINES"].extend([("BOARD", env["BOARD"]), ("EPDTYPE", epdtype), ("PCBVERS", pcbvers), ("GXEPD2VERS", gxepd2vers)])
|
||||
|
||||
print("added hardware info to CPPDEFINES")
|
||||
print("friendly board name is '{}'".format(env.GetProjectOption ("board_name")))
|
||||
```
|
||||
|
||||
|
||||
Interfaces
|
||||
----------
|
||||
|
||||
@@ -14,5 +14,6 @@ custom_config=
|
||||
lib/exampletask/exampleConfig.json
|
||||
custom_js=lib/exampletask/example.js
|
||||
custom_css=lib/exampletask/example.css
|
||||
custom_script=lib/exampletask/script.py
|
||||
upload_port = /dev/esp32
|
||||
upload_protocol = esptool
|
||||
4
lib/exampletask/script.py
Normal file
4
lib/exampletask/script.py
Normal file
@@ -0,0 +1,4 @@
|
||||
Import("env")
|
||||
|
||||
print("exampletask extra script running")
|
||||
syntax error here
|
||||
23
lib/hardware/GwChannelModes.h
Normal file
23
lib/hardware/GwChannelModes.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
defines for the channel modes(types)
|
||||
*/
|
||||
#ifndef _GWCHANNELMODES_H
|
||||
#define _GWCHANNELMODES_H
|
||||
#define GWSERIAL_TYPE_UNI 1
|
||||
#define GWSERIAL_TYPE_BI 2
|
||||
#define GWSERIAL_TYPE_RX 3
|
||||
#define GWSERIAL_TYPE_TX 4
|
||||
#define GWSERIAL_TYPE_UNK 0
|
||||
#endif
|
||||
@@ -2,7 +2,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
@@ -20,11 +20,7 @@
|
||||
#endif
|
||||
#ifndef _GWHARDWARE_H
|
||||
#define _GWHARDWARE_H
|
||||
#define GWSERIAL_TYPE_UNI 1
|
||||
#define GWSERIAL_TYPE_BI 2
|
||||
#define GWSERIAL_TYPE_RX 3
|
||||
#define GWSERIAL_TYPE_TX 4
|
||||
#define GWSERIAL_TYPE_UNK 0
|
||||
#include "GwChannelModes.h"
|
||||
#include <GwConfigItem.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include "GwAppInfo.h"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
@@ -35,7 +35,12 @@
|
||||
#ifdef M5_GPS_KIT
|
||||
GWRESOURCE_USE(BASE,M5_GPS_KIT)
|
||||
GWRESOURCE_USE(SERIAL1,M5_GPS_KIT)
|
||||
#define _GWI_SERIAL1 BOARD_LEFT1,-1,GWSERIAL_TYPE_UNI,9600
|
||||
#define _GWI_SERIAL1 BOARD_LEFT1,-1,GWSERIAL_TYPE_RX,9600
|
||||
#endif
|
||||
#ifdef M5_GPSV2_KIT
|
||||
GWRESOURCE_USE(BASE,M5_GPSV2_KIT)
|
||||
GWRESOURCE_USE(SERIAL1,M5_GPSV2_KIT)
|
||||
#define _GWI_SERIAL1 BOARD_LEFT1,-1,GWSERIAL_TYPE_RX,115200
|
||||
#endif
|
||||
|
||||
//M5 ProtoHub
|
||||
@@ -61,11 +66,11 @@
|
||||
#endif
|
||||
|
||||
//can kit for M5 Atom
|
||||
#ifdef M5_CAN_KIT
|
||||
#if defined (M5_CAN_KIT)
|
||||
GWRESOURCE_USE(BASE,M5_CAN_KIT)
|
||||
GWRESOURCE_USE(CAN,M5_CANKIT)
|
||||
#define ESP32_CAN_TX_PIN BOARD_LEFT1
|
||||
#define ESP32_CAN_RX_PIN BOARD_LEFT2
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
@@ -43,6 +43,13 @@
|
||||
#define _GWI_SERIAL_GROOVE$GS$ GWSERIAL_TYPE_RX,9600
|
||||
#endif
|
||||
|
||||
#GROVE
|
||||
//https://docs.m5stack.com/en/unit/Unit-GPS%20v1.1
|
||||
#ifdef M5_GPSV11_UNIT$GS$
|
||||
GWRESOURCE_USE(GROOVE$G$,M5_GPSV11_UNIT$GS$)
|
||||
#define _GWI_SERIAL_GROOVE$GS$ GWSERIAL_TYPE_RX,115200
|
||||
#endif
|
||||
|
||||
#GROVE
|
||||
//CAN via groove
|
||||
#ifdef M5_CANUNIT$GS$
|
||||
@@ -64,15 +71,15 @@
|
||||
#endif
|
||||
|
||||
#GROVE
|
||||
//#ifdef M5_ENV4$GS$
|
||||
// #ifndef M5_GROOVEIIC$GS$
|
||||
// #define M5_GROOVEIIC$GS$
|
||||
// #endif
|
||||
// GROOVE_IIC(SHT3X,$Z$,1)
|
||||
// GROOVE_IIC(BMP280,$Z$,1)
|
||||
// #define _GWSHT3X
|
||||
// #define _GWBMP280
|
||||
//#endif
|
||||
#ifdef M5_ENV4$GS$
|
||||
#ifndef M5_GROOVEIIC$GS$
|
||||
#define M5_GROOVEIIC$GS$
|
||||
#endif
|
||||
GROOVE_IIC(SHT4X,$Z$,1)
|
||||
GROOVE_IIC(BMP280,$Z$,1)
|
||||
#define _GWSHT4X
|
||||
#define _GWBMP280
|
||||
#endif
|
||||
|
||||
#GROVE
|
||||
//example: -DSHT3XG1_A : defines STH3Xn1 on grove A - x depends on the other devices
|
||||
@@ -93,6 +100,25 @@
|
||||
#define _GWSHT3X
|
||||
#endif
|
||||
|
||||
#GROVE
|
||||
//example: -DSHT4XG1_A : defines STH4Xn1 on grove A - x depends on the other devices
|
||||
#ifdef GWSHT4XG1$GS$
|
||||
#ifndef M5_GROOVEIIC$GS$
|
||||
#define M5_GROOVEIIC$GS$
|
||||
#endif
|
||||
GROOVE_IIC(SHT4X,$Z$,1)
|
||||
#define _GWSHT4X
|
||||
#endif
|
||||
|
||||
#GROVE
|
||||
#ifdef GWSHT4XG2$GS$
|
||||
#ifndef M5_GROOVEIIC$GS$
|
||||
#define M5_GROOVEIIC$GS$
|
||||
#endif
|
||||
GROOVE_IIC(SHT4X,$Z$,2)
|
||||
#define _GWSHT4X
|
||||
#endif
|
||||
|
||||
#GROVE
|
||||
#ifdef GWQMP6988G1$GS$
|
||||
#ifndef M5_GROOVEIIC$GS$
|
||||
|
||||
@@ -23,6 +23,7 @@ class BME280Config : public IICSensorBase{
|
||||
bool prAct=true;
|
||||
bool tmAct=true;
|
||||
bool huAct=true;
|
||||
bool sEnv=true;
|
||||
tN2kTempSource tmSrc=tN2kTempSource::N2kts_InsideTemperature;
|
||||
tN2kHumiditySource huSrc=tN2kHumiditySource::N2khs_InsideHumidity;
|
||||
tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric;
|
||||
@@ -152,6 +153,7 @@ SensorBase::Creator registerBME280(GwApi *api){
|
||||
CFG_SGET(s, prNam, prefix); \
|
||||
CFG_SGET(s, tmOff, prefix); \
|
||||
CFG_SGET(s, prOff, prefix); \
|
||||
CFG_SGET(s, sEnv, prefix); \
|
||||
s->busId = bus; \
|
||||
s->addr = baddr; \
|
||||
s->ok = true; \
|
||||
|
||||
@@ -29,6 +29,7 @@ class BMP280Config : public IICSensorBase{
|
||||
public:
|
||||
bool prAct=true;
|
||||
bool tmAct=true;
|
||||
bool sEnv=true;
|
||||
tN2kTempSource tmSrc=tN2kTempSource::N2kts_InsideTemperature;
|
||||
tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric;
|
||||
tN2kHumiditySource huSrc=tN2kHumiditySource::N2khs_Undef;
|
||||
@@ -150,6 +151,7 @@ SensorBase::Creator registerBMP280(GwApi *api){
|
||||
CFG_SGET(s, prNam, prefix); \
|
||||
CFG_SGET(s, tmOff, prefix); \
|
||||
CFG_SGET(s, prOff, prefix); \
|
||||
CFG_SGET(s, sEnv,prefix); \
|
||||
s->busId = bus; \
|
||||
s->addr = baddr; \
|
||||
s->ok = true; \
|
||||
|
||||
@@ -104,12 +104,19 @@ void sendN2kTemperature(GwApi *api,CFG &cfg,double value, int counterId){
|
||||
|
||||
template <class CFG>
|
||||
void sendN2kEnvironmentalParameters(GwApi *api,CFG &cfg,double tmValue, double huValue, double prValue, int counterId){
|
||||
if (! cfg.sEnv) return;
|
||||
tN2kMsg msg;
|
||||
SetN2kEnvironmentalParameters(msg,1,cfg.tmSrc,tmValue,cfg.huSrc,huValue,prValue);
|
||||
api->sendN2kMessage(msg);
|
||||
api->increment(counterId,cfg.prefix+String("hum"));
|
||||
api->increment(counterId,cfg.prefix+String("press"));
|
||||
api->increment(counterId,cfg.prefix+String("temp"));
|
||||
if (huValue != N2kDoubleNA){
|
||||
api->increment(counterId,cfg.prefix+String("ehum"));
|
||||
}
|
||||
if (prValue != N2kDoubleNA){
|
||||
api->increment(counterId,cfg.prefix+String("epress"));
|
||||
}
|
||||
if (tmValue != N2kDoubleNA){
|
||||
api->increment(counterId,cfg.prefix+String("etemp"));
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef _GWI_IIC1
|
||||
|
||||
@@ -23,7 +23,7 @@ static std::vector<IICGrove> iicGroveList;
|
||||
#include "GwBME280.h"
|
||||
#include "GwBMP280.h"
|
||||
#include "GwQMP6988.h"
|
||||
#include "GwSHT3X.h"
|
||||
#include "GwSHTXX.h"
|
||||
#include <map>
|
||||
|
||||
#include "GwTimer.h"
|
||||
@@ -91,6 +91,7 @@ void initIicTask(GwApi *api){
|
||||
GwConfigHandler *config=api->getConfig();
|
||||
std::vector<SensorBase::Creator> creators;
|
||||
creators.push_back(registerSHT3X(api));
|
||||
creators.push_back(registerSHT4X(api));
|
||||
creators.push_back(registerQMP6988(api));
|
||||
creators.push_back(registerBME280(api));
|
||||
creators.push_back(registerBMP280(api));
|
||||
@@ -147,13 +148,13 @@ bool initWire(GwLog *logger, TwoWire &wire, int num){
|
||||
#ifdef _GWI_IIC1
|
||||
return initWireDo(logger,wire,num,_GWI_IIC1);
|
||||
#endif
|
||||
return initWireDo(logger,wire,num,"",GWIIC_SDA,GWIIC_SCL);
|
||||
return initWireDo(logger,wire,num,"",GWIIC_SCL,GWIIC_SDA);
|
||||
}
|
||||
if (num == 2){
|
||||
#ifdef _GWI_IIC2
|
||||
return initWireDo(logger,wire,num,_GWI_IIC2);
|
||||
#endif
|
||||
return initWireDo(logger,wire,num,"",GWIIC_SDA2,GWIIC_SCL2);
|
||||
return initWireDo(logger,wire,num,"",GWIIC_SCL2,GWIIC_SDA2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ class QMP6988Config : public IICSensorBase{
|
||||
public:
|
||||
String prNam="Pressure";
|
||||
bool prAct=true;
|
||||
bool sEnv=true;
|
||||
tN2kTempSource tmSrc=tN2kTempSource::N2kts_InsideTemperature;
|
||||
tN2kHumiditySource huSrc=tN2kHumiditySource::N2khs_Undef;
|
||||
tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric;
|
||||
float prOff=0;
|
||||
QMP6988 *device=nullptr;
|
||||
@@ -39,6 +42,7 @@ class QMP6988Config : public IICSensorBase{
|
||||
float computed=pressure+prOff;
|
||||
LOG_DEBUG(GwLog::DEBUG,"%s measure %2.0fPa, computed %2.0fPa",prefix.c_str(), pressure,computed);
|
||||
sendN2kPressure(api,*this,computed,counterId);
|
||||
sendN2kEnvironmentalParameters(api,*this,N2kDoubleNA,N2kDoubleNA,computed,counterId);
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +94,7 @@ SensorBase::Creator registerQMP6988(GwApi *api){
|
||||
CFG_SGET(s,prAct,prefix); \
|
||||
CFG_SGET(s,intv,prefix); \
|
||||
CFG_SGET(s,prOff,prefix); \
|
||||
CFG_SGET(s,sEnv,prefix); \
|
||||
s->busId = bus; \
|
||||
s->addr = baddr; \
|
||||
s->ok = true; \
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
#include "GwSHT3X.h"
|
||||
#ifdef _GWSHT3X
|
||||
class SHT3XConfig;
|
||||
static GwSensorConfigInitializerList<SHT3XConfig> configs;
|
||||
class SHT3XConfig : public IICSensorBase{
|
||||
public:
|
||||
String tmNam;
|
||||
String huNam;
|
||||
bool tmAct=false;
|
||||
bool huAct=false;
|
||||
tN2kHumiditySource huSrc;
|
||||
tN2kTempSource tmSrc;
|
||||
SHT3X *device=nullptr;
|
||||
using IICSensorBase::IICSensorBase;
|
||||
virtual bool isActive(){
|
||||
return tmAct || huAct;
|
||||
}
|
||||
virtual bool initDevice(GwApi * api,TwoWire *wire){
|
||||
if (! isActive()) return false;
|
||||
device=new SHT3X();
|
||||
device->init(addr,wire);
|
||||
GwLog *logger=api->getLogger();
|
||||
LOG_DEBUG(GwLog::LOG,"initialized %s at address %d, intv %ld",prefix.c_str(),(int)addr,intv);
|
||||
return true;
|
||||
}
|
||||
virtual bool preinit(GwApi * api){
|
||||
GwLog *logger=api->getLogger();
|
||||
LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str());
|
||||
addHumidXdr(api,*this);
|
||||
addTempXdr(api,*this);
|
||||
return isActive();
|
||||
}
|
||||
virtual void measure(GwApi * api,TwoWire *wire, int counterId)
|
||||
{
|
||||
if (!device)
|
||||
return;
|
||||
GwLog *logger=api->getLogger();
|
||||
int rt = 0;
|
||||
if ((rt = device->get()) == 0)
|
||||
{
|
||||
double temp = device->cTemp;
|
||||
temp = CToKelvin(temp);
|
||||
double humid = device->humidity;
|
||||
LOG_DEBUG(GwLog::DEBUG, "%s measure temp=%2.1f, humid=%2.0f",prefix.c_str(), (float)temp, (float)humid);
|
||||
if (huAct)
|
||||
{
|
||||
sendN2kHumidity(api, *this, humid, counterId);
|
||||
}
|
||||
if (tmAct)
|
||||
{
|
||||
sendN2kTemperature(api, *this, temp, counterId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG(GwLog::DEBUG, "unable to query %s: %d",prefix.c_str(), rt);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void readConfig(GwConfigHandler *cfg){
|
||||
if (ok) return;
|
||||
configs.readConfig(this,cfg);
|
||||
return;
|
||||
}
|
||||
};
|
||||
SensorBase::Creator creator=[](GwApi *api,const String &prfx)-> SensorBase*{
|
||||
if (! configs.knowsPrefix(prfx)) return nullptr;
|
||||
return new SHT3XConfig(api,prfx);
|
||||
};
|
||||
SensorBase::Creator registerSHT3X(GwApi *api){
|
||||
GwLog *logger=api->getLogger();
|
||||
#if defined(GWSHT3X) || defined (GWSHT3X11)
|
||||
{
|
||||
api->addSensor(creator(api,"SHT3X11"));
|
||||
CHECK_IIC1();
|
||||
#pragma message "GWSHT3X11 defined"
|
||||
}
|
||||
#endif
|
||||
#if defined(GWSHT3X12)
|
||||
{
|
||||
api->addSensor(creator(api,"SHT3X12"));
|
||||
CHECK_IIC1();
|
||||
#pragma message "GWSHT3X12 defined"
|
||||
}
|
||||
#endif
|
||||
#if defined(GWSHT3X21)
|
||||
{
|
||||
api->addSensor(creator(api,"SHT3X21"));
|
||||
CHECK_IIC2();
|
||||
#pragma message "GWSHT3X21 defined"
|
||||
}
|
||||
#endif
|
||||
#if defined(GWSHT3X22)
|
||||
{
|
||||
api->addSensor(creator(api,"SHT3X22"));
|
||||
CHECK_IIC2();
|
||||
#pragma message "GWSHT3X22 defined"
|
||||
}
|
||||
#endif
|
||||
return creator;
|
||||
};
|
||||
|
||||
/**
|
||||
* we do not dynamically compute the config names
|
||||
* just to get compile time errors if something does not fit
|
||||
* correctly
|
||||
*/
|
||||
#define CFGSHT3X(s, prefix, bus, baddr) \
|
||||
CFG_SGET(s, tmNam, prefix); \
|
||||
CFG_SGET(s, huNam, prefix); \
|
||||
CFG_SGET(s, iid, prefix); \
|
||||
CFG_SGET(s, tmAct, prefix); \
|
||||
CFG_SGET(s, huAct, prefix); \
|
||||
CFG_SGET(s, intv, prefix); \
|
||||
CFG_SGET(s, huSrc, prefix); \
|
||||
CFG_SGET(s, tmSrc, prefix); \
|
||||
s->busId = bus; \
|
||||
s->addr = baddr; \
|
||||
s->ok = true; \
|
||||
s->intv *= 1000;
|
||||
|
||||
#define SCSHT3X(prefix, bus, addr) \
|
||||
GWSENSORDEF(configs, SHT3XConfig, CFGSHT3X, prefix, bus, addr)
|
||||
|
||||
SCSHT3X(SHT3X11, 1, 0x44);
|
||||
SCSHT3X(SHT3X12, 1, 0x45);
|
||||
SCSHT3X(SHT3X21, 2, 0x44);
|
||||
SCSHT3X(SHT3X22, 2, 0x45);
|
||||
|
||||
#else
|
||||
SensorBase::Creator registerSHT3X(GwApi *api){
|
||||
return SensorBase::Creator();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
254
lib/iictask/GwSHTXX.cpp
Normal file
254
lib/iictask/GwSHTXX.cpp
Normal file
@@ -0,0 +1,254 @@
|
||||
#include "GwSHTXX.h"
|
||||
#if defined(_GWSHT3X) || defined(_GWSHT4X)
|
||||
class SHTXXConfig : public IICSensorBase{
|
||||
public:
|
||||
String tmNam;
|
||||
String huNam;
|
||||
bool tmAct=false;
|
||||
bool huAct=false;
|
||||
bool sEnv=true;
|
||||
tN2kHumiditySource huSrc;
|
||||
tN2kTempSource tmSrc;
|
||||
using IICSensorBase::IICSensorBase;
|
||||
virtual bool isActive(){
|
||||
return tmAct || huAct;
|
||||
}
|
||||
virtual bool preinit(GwApi * api){
|
||||
GwLog *logger=api->getLogger();
|
||||
LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str());
|
||||
addHumidXdr(api,*this);
|
||||
addTempXdr(api,*this);
|
||||
return isActive();
|
||||
}
|
||||
virtual bool doMeasure(GwApi * api,double &temp, double &humid){
|
||||
return false;
|
||||
}
|
||||
virtual void measure(GwApi * api,TwoWire *wire, int counterId) override
|
||||
{
|
||||
GwLog *logger=api->getLogger();
|
||||
double temp = N2kDoubleNA;
|
||||
double humid = N2kDoubleNA;
|
||||
if (doMeasure(api,temp,humid)){
|
||||
temp = CToKelvin(temp);
|
||||
LOG_DEBUG(GwLog::DEBUG, "%s measure temp=%2.1f, humid=%2.0f",prefix.c_str(), (float)temp, (float)humid);
|
||||
if (huAct)
|
||||
{
|
||||
sendN2kHumidity(api, *this, humid, counterId);
|
||||
}
|
||||
if (tmAct)
|
||||
{
|
||||
sendN2kTemperature(api, *this, temp, counterId);
|
||||
}
|
||||
if (huAct || tmAct){
|
||||
sendN2kEnvironmentalParameters(api,*this,temp,humid,N2kDoubleNA,counterId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
/**
|
||||
* we do not dynamically compute the config names
|
||||
* just to get compile time errors if something does not fit
|
||||
* correctly
|
||||
*/
|
||||
#define INITSHTXX(type,prefix,bus,baddr) \
|
||||
[] (type *s ,GwConfigHandler *cfg) { \
|
||||
CFG_SGET(s, tmNam, prefix); \
|
||||
CFG_SGET(s, huNam, prefix); \
|
||||
CFG_SGET(s, iid, prefix); \
|
||||
CFG_SGET(s, tmAct, prefix); \
|
||||
CFG_SGET(s, huAct, prefix); \
|
||||
CFG_SGET(s, intv, prefix); \
|
||||
CFG_SGET(s, huSrc, prefix); \
|
||||
CFG_SGET(s, tmSrc, prefix); \
|
||||
CFG_SGET(s, sEnv,prefix); \
|
||||
s->busId = bus; \
|
||||
s->addr = baddr; \
|
||||
s->ok = true; \
|
||||
s->intv *= 1000; \
|
||||
}
|
||||
|
||||
#if defined(_GWSHT3X)
|
||||
class SHT3XConfig;
|
||||
static GwSensorConfigInitializerList<SHT3XConfig> configs3;
|
||||
class SHT3XConfig : public SHTXXConfig{
|
||||
SHT3X *device=nullptr;
|
||||
public:
|
||||
using SHTXXConfig::SHTXXConfig;
|
||||
virtual bool initDevice(GwApi * api,TwoWire *wire)override{
|
||||
if (! isActive()) return false;
|
||||
device=new SHT3X();
|
||||
device->init(addr,wire);
|
||||
GwLog *logger=api->getLogger();
|
||||
LOG_DEBUG(GwLog::LOG,"initialized %s at address %d, intv %ld",prefix.c_str(),(int)addr,intv);
|
||||
return true;
|
||||
}
|
||||
virtual bool doMeasure(GwApi *api,double &temp, double &humid) override{
|
||||
if (!device)
|
||||
return false;
|
||||
int rt=0;
|
||||
GwLog *logger=api->getLogger();
|
||||
if ((rt = device->get()) == 0)
|
||||
{
|
||||
temp = device->cTemp;
|
||||
humid = device->humidity;
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
LOG_DEBUG(GwLog::DEBUG, "unable to query %s: %d",prefix.c_str(), rt);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
virtual void readConfig(GwConfigHandler *cfg) override{
|
||||
if (ok) return;
|
||||
configs3.readConfig(this,cfg);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
SensorBase::Creator creator3=[](GwApi *api,const String &prfx)-> SensorBase*{
|
||||
if (! configs3.knowsPrefix(prfx)) return nullptr;
|
||||
return new SHT3XConfig(api,prfx);
|
||||
};
|
||||
SensorBase::Creator registerSHT3X(GwApi *api){
|
||||
GwLog *logger=api->getLogger();
|
||||
#if defined(GWSHT3X) || defined (GWSHT3X11)
|
||||
{
|
||||
api->addSensor(creator3(api,"SHT3X11"));
|
||||
CHECK_IIC1();
|
||||
#pragma message "GWSHT3X11 defined"
|
||||
}
|
||||
#endif
|
||||
#if defined(GWSHT3X12)
|
||||
{
|
||||
api->addSensor(creator3(api,"SHT3X12"));
|
||||
CHECK_IIC1();
|
||||
#pragma message "GWSHT3X12 defined"
|
||||
}
|
||||
#endif
|
||||
#if defined(GWSHT3X21)
|
||||
{
|
||||
api->addSensor(creator3(api,"SHT3X21"));
|
||||
CHECK_IIC2();
|
||||
#pragma message "GWSHT3X21 defined"
|
||||
}
|
||||
#endif
|
||||
#if defined(GWSHT3X22)
|
||||
{
|
||||
api->addSensor(creator3(api,"SHT3X22"));
|
||||
CHECK_IIC2();
|
||||
#pragma message "GWSHT3X22 defined"
|
||||
}
|
||||
#endif
|
||||
return creator3;
|
||||
};
|
||||
|
||||
|
||||
#define SCSHT3X(prefix, bus, addr) \
|
||||
GwSensorConfigInitializer<SHT3XConfig> __initCFGSHT3X ## prefix \
|
||||
(configs3,GwSensorConfig<SHT3XConfig>(#prefix,INITSHTXX(SHT3XConfig,prefix,bus,addr)));
|
||||
|
||||
SCSHT3X(SHT3X11, 1, 0x44);
|
||||
SCSHT3X(SHT3X12, 1, 0x45);
|
||||
SCSHT3X(SHT3X21, 2, 0x44);
|
||||
SCSHT3X(SHT3X22, 2, 0x45);
|
||||
|
||||
#endif
|
||||
#if defined(_GWSHT4X)
|
||||
class SHT4XConfig;
|
||||
static GwSensorConfigInitializerList<SHT4XConfig> configs4;
|
||||
class SHT4XConfig : public SHTXXConfig{
|
||||
SHT4X *device=nullptr;
|
||||
public:
|
||||
using SHTXXConfig::SHTXXConfig;
|
||||
virtual bool initDevice(GwApi * api,TwoWire *wire)override{
|
||||
if (! isActive()) return false;
|
||||
device=new SHT4X();
|
||||
device->begin(wire,addr);
|
||||
GwLog *logger=api->getLogger();
|
||||
LOG_DEBUG(GwLog::LOG,"initialized %s at address %d, intv %ld",prefix.c_str(),(int)addr,intv);
|
||||
return true;
|
||||
}
|
||||
virtual bool doMeasure(GwApi *api,double &temp, double &humid) override{
|
||||
if (!device)
|
||||
return false;
|
||||
GwLog *logger=api->getLogger();
|
||||
if (device->update())
|
||||
{
|
||||
temp = device->cTemp;
|
||||
humid = device->humidity;
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
LOG_DEBUG(GwLog::DEBUG, "unable to query %s",prefix.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
virtual void readConfig(GwConfigHandler *cfg) override{
|
||||
if (ok) return;
|
||||
configs4.readConfig(this,cfg);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
SensorBase::Creator creator4=[](GwApi *api,const String &prfx)-> SensorBase*{
|
||||
if (! configs4.knowsPrefix(prfx)) return nullptr;
|
||||
return new SHT4XConfig(api,prfx);
|
||||
};
|
||||
SensorBase::Creator registerSHT4X(GwApi *api){
|
||||
GwLog *logger=api->getLogger();
|
||||
#if defined(GWSHT4X) || defined (GWSHT4X11)
|
||||
{
|
||||
api->addSensor(creator3(api,"SHT4X11"));
|
||||
CHECK_IIC1();
|
||||
#pragma message "GWSHT4X11 defined"
|
||||
}
|
||||
#endif
|
||||
#if defined(GWSHT4X12)
|
||||
{
|
||||
api->addSensor(creator3(api,"SHT4X12"));
|
||||
CHECK_IIC1();
|
||||
#pragma message "GWSHT4X12 defined"
|
||||
}
|
||||
#endif
|
||||
#if defined(GWSHT4X21)
|
||||
{
|
||||
api->addSensor(creator3(api,"SHT4X21"));
|
||||
CHECK_IIC2();
|
||||
#pragma message "GWSHT4X21 defined"
|
||||
}
|
||||
#endif
|
||||
#if defined(GWSHT4X22)
|
||||
{
|
||||
api->addSensor(creator3(api,"SHT4X22"));
|
||||
CHECK_IIC2();
|
||||
#pragma message "GWSHT4X22 defined"
|
||||
}
|
||||
#endif
|
||||
return creator4;
|
||||
};
|
||||
|
||||
|
||||
#define SCSHT4X(prefix, bus, addr) \
|
||||
GwSensorConfigInitializer<SHT4XConfig> __initCFGSHT4X ## prefix \
|
||||
(configs4,GwSensorConfig<SHT4XConfig>(#prefix,INITSHTXX(SHT4XConfig,prefix,bus,addr)));
|
||||
|
||||
SCSHT4X(SHT4X11, 1, 0x44);
|
||||
SCSHT4X(SHT4X12, 1, 0x45);
|
||||
SCSHT4X(SHT4X21, 2, 0x44);
|
||||
SCSHT4X(SHT4X22, 2, 0x45);
|
||||
#endif
|
||||
#endif
|
||||
#ifndef _GWSHT3X
|
||||
SensorBase::Creator registerSHT3X(GwApi *api){
|
||||
return SensorBase::Creator();
|
||||
}
|
||||
#endif
|
||||
#ifndef _GWSHT4X
|
||||
SensorBase::Creator registerSHT4X(GwApi *api){
|
||||
return SensorBase::Creator();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#ifndef _GWSHT3X_H
|
||||
#define _GWSHT3X_H
|
||||
#ifndef _GWSHTXX_H
|
||||
#define _GWSHTXX_H
|
||||
#include "GwIicSensors.h"
|
||||
#ifdef _GWIIC
|
||||
#if defined(GWSHT3X) || defined(GWSHT3X11) || defined(GWSHT3X12) || defined(GWSHT3X21) || defined(GWSHT3X22)
|
||||
#define _GWSHT3X
|
||||
#endif
|
||||
#if defined(GWSHT4X) || defined(GWSHT4X11) || defined(GWSHT4X12) || defined(GWSHT4X21) || defined(GWSHT4X22)
|
||||
#define _GWSHT4X
|
||||
#endif
|
||||
#else
|
||||
#undef _GWSHT3X
|
||||
#undef GWSHT3X
|
||||
@@ -12,9 +15,19 @@
|
||||
#undef GWSHT3X12
|
||||
#undef GWSHT3X21
|
||||
#undef GWSHT3X22
|
||||
#undef _GWSHT4X
|
||||
#undef GWSHT4X
|
||||
#undef GWSHT4X11
|
||||
#undef GWSHT4X12
|
||||
#undef GWSHT4X21
|
||||
#undef GWSHT4X22
|
||||
#endif
|
||||
#ifdef _GWSHT3X
|
||||
#include "SHT3X.h"
|
||||
#endif
|
||||
#ifdef _GWSHT4X
|
||||
#include "SHT4X.h"
|
||||
#endif
|
||||
SensorBase::Creator registerSHT3X(GwApi *api);
|
||||
SensorBase::Creator registerSHT4X(GwApi *api);
|
||||
#endif
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "GwSHT3X.h"
|
||||
#include "GwSHTXX.h"
|
||||
#ifdef _GWSHT3X
|
||||
|
||||
bool SHT3X::init(uint8_t slave_addr_in, TwoWire* wire_in)
|
||||
|
||||
131
lib/iictask/SHT4X.cpp
Normal file
131
lib/iictask/SHT4X.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include "GwSHTXX.h"
|
||||
#ifdef _GWSHT4X
|
||||
|
||||
uint8_t crc8(const uint8_t *data, int len) {
|
||||
/*
|
||||
*
|
||||
* CRC-8 formula from page 14 of SHT spec pdf
|
||||
*
|
||||
* Test data 0xBE, 0xEF should yield 0x92
|
||||
*
|
||||
* Initialization data 0xFF
|
||||
* Polynomial 0x31 (x8 + x5 +x4 +1)
|
||||
* Final XOR 0x00
|
||||
*/
|
||||
|
||||
const uint8_t POLYNOMIAL(0x31);
|
||||
uint8_t crc(0xFF);
|
||||
|
||||
for (int j = len; j; --j) {
|
||||
crc ^= *data++;
|
||||
|
||||
for (int i = 8; i; --i) {
|
||||
crc = (crc & 0x80) ? (crc << 1) ^ POLYNOMIAL : (crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool SHT4X::begin(TwoWire* wire, uint8_t addr) {
|
||||
_addr = addr;
|
||||
_wire = wire;
|
||||
int error;
|
||||
_wire->beginTransmission(addr);
|
||||
error = _wire->endTransmission();
|
||||
if (error == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SHT4X::update() {
|
||||
uint8_t readbuffer[6];
|
||||
uint8_t cmd = SHT4x_NOHEAT_HIGHPRECISION;
|
||||
uint16_t duration = 10;
|
||||
|
||||
if (_heater == SHT4X_NO_HEATER) {
|
||||
if (_precision == SHT4X_HIGH_PRECISION) {
|
||||
cmd = SHT4x_NOHEAT_HIGHPRECISION;
|
||||
duration = 10;
|
||||
}
|
||||
if (_precision == SHT4X_MED_PRECISION) {
|
||||
cmd = SHT4x_NOHEAT_MEDPRECISION;
|
||||
duration = 5;
|
||||
}
|
||||
if (_precision == SHT4X_LOW_PRECISION) {
|
||||
cmd = SHT4x_NOHEAT_LOWPRECISION;
|
||||
duration = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (_heater == SHT4X_HIGH_HEATER_1S) {
|
||||
cmd = SHT4x_HIGHHEAT_1S;
|
||||
duration = 1100;
|
||||
}
|
||||
if (_heater == SHT4X_HIGH_HEATER_100MS) {
|
||||
cmd = SHT4x_HIGHHEAT_100MS;
|
||||
duration = 110;
|
||||
}
|
||||
|
||||
if (_heater == SHT4X_MED_HEATER_1S) {
|
||||
cmd = SHT4x_MEDHEAT_1S;
|
||||
duration = 1100;
|
||||
}
|
||||
if (_heater == SHT4X_MED_HEATER_100MS) {
|
||||
cmd = SHT4x_MEDHEAT_100MS;
|
||||
duration = 110;
|
||||
}
|
||||
|
||||
if (_heater == SHT4X_LOW_HEATER_1S) {
|
||||
cmd = SHT4x_LOWHEAT_1S;
|
||||
duration = 1100;
|
||||
}
|
||||
if (_heater == SHT4X_LOW_HEATER_100MS) {
|
||||
cmd = SHT4x_LOWHEAT_100MS;
|
||||
duration = 110;
|
||||
}
|
||||
// _i2c.writeByte(_addr, cmd, 1);
|
||||
_wire->beginTransmission(_addr);
|
||||
_wire->write(cmd);
|
||||
_wire->write(1);
|
||||
_wire->endTransmission();
|
||||
|
||||
|
||||
delay(duration);
|
||||
|
||||
_wire->requestFrom(_addr, (uint8_t)6);
|
||||
|
||||
for (uint16_t i = 0; i < 6; i++) {
|
||||
readbuffer[i] = _wire->read();
|
||||
}
|
||||
|
||||
if (readbuffer[2] != crc8(readbuffer, 2) ||
|
||||
readbuffer[5] != crc8(readbuffer + 3, 2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float t_ticks = (uint16_t)readbuffer[0] * 256 + (uint16_t)readbuffer[1];
|
||||
float rh_ticks = (uint16_t)readbuffer[3] * 256 + (uint16_t)readbuffer[4];
|
||||
|
||||
cTemp = -45 + 175 * t_ticks / 65535;
|
||||
humidity = -6 + 125 * rh_ticks / 65535;
|
||||
humidity = min(max(humidity, (float)0.0), (float)100.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SHT4X::setPrecision(sht4x_precision_t prec) {
|
||||
_precision = prec;
|
||||
}
|
||||
|
||||
sht4x_precision_t SHT4X::getPrecision(void) {
|
||||
return _precision;
|
||||
}
|
||||
|
||||
void SHT4X::setHeater(sht4x_heater_t heat) {
|
||||
_heater = heat;
|
||||
}
|
||||
|
||||
sht4x_heater_t SHT4X::getHeater(void) {
|
||||
return _heater;
|
||||
}
|
||||
#endif
|
||||
76
lib/iictask/SHT4X.h
Normal file
76
lib/iictask/SHT4X.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#ifndef __SHT4X_H_
|
||||
#define __SHT4X_H_
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Wire.h"
|
||||
|
||||
#define SHT40_I2C_ADDR_44 0x44
|
||||
#define SHT40_I2C_ADDR_45 0x45
|
||||
#define SHT41_I2C_ADDR_44 0x44
|
||||
#define SHT41_I2C_ADDR_45 0x45
|
||||
#define SHT45_I2C_ADDR_44 0x44
|
||||
#define SHT45_I2C_ADDR_45 0x45
|
||||
|
||||
#define SHT4x_DEFAULT_ADDR 0x44 /**< SHT4x I2C Address */
|
||||
#define SHT4x_NOHEAT_HIGHPRECISION \
|
||||
0xFD /**< High precision measurement, no heater */
|
||||
#define SHT4x_NOHEAT_MEDPRECISION \
|
||||
0xF6 /**< Medium precision measurement, no heater */
|
||||
#define SHT4x_NOHEAT_LOWPRECISION \
|
||||
0xE0 /**< Low precision measurement, no heater */
|
||||
|
||||
#define SHT4x_HIGHHEAT_1S \
|
||||
0x39 /**< High precision measurement, high heat for 1 sec */
|
||||
#define SHT4x_HIGHHEAT_100MS \
|
||||
0x32 /**< High precision measurement, high heat for 0.1 sec */
|
||||
#define SHT4x_MEDHEAT_1S \
|
||||
0x2F /**< High precision measurement, med heat for 1 sec */
|
||||
#define SHT4x_MEDHEAT_100MS \
|
||||
0x24 /**< High precision measurement, med heat for 0.1 sec */
|
||||
#define SHT4x_LOWHEAT_1S \
|
||||
0x1E /**< High precision measurement, low heat for 1 sec */
|
||||
#define SHT4x_LOWHEAT_100MS \
|
||||
0x15 /**< High precision measurement, low heat for 0.1 sec */
|
||||
|
||||
#define SHT4x_READSERIAL 0x89 /**< Read Out of Serial Register */
|
||||
#define SHT4x_SOFTRESET 0x94 /**< Soft Reset */
|
||||
|
||||
typedef enum {
|
||||
SHT4X_HIGH_PRECISION,
|
||||
SHT4X_MED_PRECISION,
|
||||
SHT4X_LOW_PRECISION,
|
||||
} sht4x_precision_t;
|
||||
|
||||
/** Optional pre-heater configuration setting */
|
||||
typedef enum {
|
||||
SHT4X_NO_HEATER,
|
||||
SHT4X_HIGH_HEATER_1S,
|
||||
SHT4X_HIGH_HEATER_100MS,
|
||||
SHT4X_MED_HEATER_1S,
|
||||
SHT4X_MED_HEATER_100MS,
|
||||
SHT4X_LOW_HEATER_1S,
|
||||
SHT4X_LOW_HEATER_100MS,
|
||||
} sht4x_heater_t;
|
||||
|
||||
class SHT4X {
|
||||
public:
|
||||
bool begin(TwoWire* wire = &Wire, uint8_t addr = SHT40_I2C_ADDR_44);
|
||||
bool update(void);
|
||||
|
||||
float cTemp = 0;
|
||||
float humidity = 0;
|
||||
|
||||
void setPrecision(sht4x_precision_t prec);
|
||||
sht4x_precision_t getPrecision(void);
|
||||
void setHeater(sht4x_heater_t heat);
|
||||
sht4x_heater_t getHeater(void);
|
||||
|
||||
private:
|
||||
TwoWire* _wire;
|
||||
uint8_t _addr;
|
||||
|
||||
sht4x_precision_t _precision = SHT4X_HIGH_PRECISION;
|
||||
sht4x_heater_t _heater = SHT4X_NO_HEATER;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,49 +1,77 @@
|
||||
[
|
||||
{
|
||||
"type": "array",
|
||||
"name": "SHT3X",
|
||||
"name": "SHTXX",
|
||||
"replace": [
|
||||
{
|
||||
"b": "1",
|
||||
"i": "11",
|
||||
"n": "99"
|
||||
"n": "99",
|
||||
"x": "3"
|
||||
},
|
||||
{
|
||||
"b": "1",
|
||||
"i": "12",
|
||||
"n": "98"
|
||||
"n": "98",
|
||||
"x": "3"
|
||||
},
|
||||
{
|
||||
"b": "2",
|
||||
"i": "21",
|
||||
"n": "109"
|
||||
"n": "109",
|
||||
"x": "3"
|
||||
},
|
||||
{
|
||||
"b": "2",
|
||||
"i": "22",
|
||||
"n": "108"
|
||||
"n": "108",
|
||||
"x": "3"
|
||||
},
|
||||
{
|
||||
"b": "1",
|
||||
"i": "11",
|
||||
"n": "119",
|
||||
"x": "4"
|
||||
},
|
||||
{
|
||||
"b": "1",
|
||||
"i": "12",
|
||||
"n": "118",
|
||||
"x": "4"
|
||||
},
|
||||
{
|
||||
"b": "2",
|
||||
"i": "21",
|
||||
"n": "129",
|
||||
"x": "4"
|
||||
},
|
||||
{
|
||||
"b": "2",
|
||||
"i": "22",
|
||||
"n": "128",
|
||||
"x": "4"
|
||||
}
|
||||
|
||||
|
||||
],
|
||||
"children": [
|
||||
{
|
||||
"name": "SHT3X$itmAct",
|
||||
"label": "SHT3X$i Temp",
|
||||
"name": "SHT$xX$itmAct",
|
||||
"label": "SHT$xX$i Temp",
|
||||
"type": "boolean",
|
||||
"default": "true",
|
||||
"description": "Enable the $i. I2C SHT3x temp sensor (bus $b)",
|
||||
"description": "Enable the $i. I2C SHT$xX temp sensor (bus $b)",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"SHT3X$i": "true"
|
||||
"SHT$xX$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SHT3X$itmSrc",
|
||||
"label": "SHT3X$i Temp Type",
|
||||
"name": "SHT$xX$itmSrc",
|
||||
"label": "SHT$xX$i Temp Type",
|
||||
"type": "list",
|
||||
"default": "2",
|
||||
"description": "the NMEA2000 source type for the temperature",
|
||||
"description": "the NMEA2000 source type for the temperature (PGN 130312,130311)",
|
||||
"list": [
|
||||
{
|
||||
"l": "SeaTemperature",
|
||||
@@ -112,23 +140,23 @@
|
||||
],
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"SHT3X$i": "true"
|
||||
"SHT$xX$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SHT3X$ihuAct",
|
||||
"label": "SHT3X$i Humidity",
|
||||
"name": "SHT$xX$ihuAct",
|
||||
"label": "SHT$xX$i Humidity",
|
||||
"type": "boolean",
|
||||
"default": "true",
|
||||
"description": "Enable the $i. I2C SHT3x humidity sensor (bus $b)",
|
||||
"description": "Enable the $i. I2C SHT$xX humidity sensor (bus $b)",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"SHT3X$i": "true"
|
||||
"SHT$xX$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SHT3X$ihuSrc",
|
||||
"label": "SHT3X$i Humid Type",
|
||||
"name": "SHT$xX$ihuSrc",
|
||||
"label": "SHT$xX$i Humid Type",
|
||||
"list": [
|
||||
{
|
||||
"l": "OutsideHumidity",
|
||||
@@ -141,57 +169,68 @@
|
||||
],
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"SHT3X": "true"
|
||||
"SHT$xX": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SHT3X$iiid",
|
||||
"label": "SHT3X$i N2K iid",
|
||||
"name": "SHT$xX$iiid",
|
||||
"label": "SHT$xX$i N2K iid",
|
||||
"type": "number",
|
||||
"default": "$n",
|
||||
"description": "the N2K instance id for the $i. SHT3X Temperature and Humidity ",
|
||||
"description": "the N2K instance id for the $i. SHT$xX Temperature and Humidity (PGN 130312,130311) ",
|
||||
"category": "iicsensors$b",
|
||||
"min": 0,
|
||||
"max": 253,
|
||||
"check": "checkMinMax",
|
||||
"capabilities": {
|
||||
"SHT3X$i": "true"
|
||||
"SHT$xX$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SHT3X$iintv",
|
||||
"label": "SHT3X$i Interval",
|
||||
"name": "SHT$xX$isEnv",
|
||||
"label": "SHT$xX$i send Env",
|
||||
"type": "boolean",
|
||||
"default": "true",
|
||||
"description": "also send PGN 130311",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"SHT$xX$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SHT$xX$iintv",
|
||||
"label": "SHT$xX$i Interval",
|
||||
"type": "number",
|
||||
"default": 2,
|
||||
"description": "Interval(s) to query SHT3X Temperature and Humidity (1...300)",
|
||||
"description": "Interval(s) to query SHT$xX Temperature and Humidity (1...300)",
|
||||
"category": "iicsensors$b",
|
||||
"min": 1,
|
||||
"max": 300,
|
||||
"check": "checkMinMax",
|
||||
"capabilities": {
|
||||
"SHT3X$i": "true"
|
||||
"SHT$xX$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SHT3X$itmNam",
|
||||
"label": "SHT3X$i Temp XDR",
|
||||
"name": "SHT$xX$itmNam",
|
||||
"label": "SHT$xX$i Temp XDR",
|
||||
"type": "String",
|
||||
"default": "Temp$i",
|
||||
"description": "set the XDR transducer name for the $i. SHT3X Temperature, leave empty to disable NMEA0183 XDR ",
|
||||
"description": "set the XDR transducer name for the $i. SHT$xX Temperature, leave empty to disable NMEA0183 XDR ",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"SHT3X$i": "true"
|
||||
"SHT$xX$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SHT3X$ihuNam",
|
||||
"label": "SHT3X$i Humid XDR",
|
||||
"name": "SHT$xX$ihuNam",
|
||||
"label": "SHT$xX$i Humid XDR",
|
||||
"type": "String",
|
||||
"default": "Humidity$i",
|
||||
"description": "set the XDR transducer name for the $i. SHT3X Humidity, leave empty to disable NMEA0183 XDR",
|
||||
"description": "set the XDR transducer name for the $i. SHT$xX Humidity, leave empty to disable NMEA0183 XDR",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"SHT3X$i": "true"
|
||||
"SHT$xX$i": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -247,6 +286,17 @@
|
||||
"QMP6988$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "QMP6988$isEnv",
|
||||
"label": "QMP6988$i send Env",
|
||||
"type": "boolean",
|
||||
"default": "true",
|
||||
"description": "also send PGN 130311",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"QMP6988$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "QMP6988$iintv",
|
||||
"label": "QMP6988-$i Interval",
|
||||
@@ -473,7 +523,7 @@
|
||||
"label": "BME280-$i N2K iid",
|
||||
"type": "number",
|
||||
"default": "$n",
|
||||
"description": "the N2K instance id for the BME280 Temperature and Humidity ",
|
||||
"description": "the N2K instance id for the BME280 Temperature, Humidity, Pressure (PGN 130312,130313, 130314) ",
|
||||
"category": "iicsensors$b",
|
||||
"min": 0,
|
||||
"max": 253,
|
||||
@@ -482,6 +532,17 @@
|
||||
"BME280$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BME280$isEnv",
|
||||
"label": "BME280$i send Env",
|
||||
"type": "boolean",
|
||||
"default": "true",
|
||||
"description": "also send PGN 130311",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"BME280$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BME280$iintv",
|
||||
"label": "BME280-$i Interval",
|
||||
@@ -683,7 +744,7 @@
|
||||
"label": "BMP280-$i N2K iid",
|
||||
"type": "number",
|
||||
"default": "$n",
|
||||
"description": "the N2K instance id for the BMP280 Temperature",
|
||||
"description": "the N2K instance id for the BMP280 Temperature/Pressure (PGN 130312,130314)",
|
||||
"category": "iicsensors$b",
|
||||
"min": 0,
|
||||
"max": 253,
|
||||
@@ -692,6 +753,17 @@
|
||||
"BMP280$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BMP280$isEnv",
|
||||
"label": "BMP280$i send Env",
|
||||
"type": "boolean",
|
||||
"default": "true",
|
||||
"description": "also send PGN 130311",
|
||||
"category": "iicsensors$b",
|
||||
"capabilities": {
|
||||
"BMP280$i": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BMP280$iintv",
|
||||
"label": "BMP280-$i Interval",
|
||||
|
||||
@@ -11,6 +11,17 @@ build_flags=
|
||||
-D M5_CAN_KIT
|
||||
${env.build_flags}
|
||||
|
||||
[env:m5stack-atom-env4]
|
||||
extends = sensors
|
||||
board = m5stack-atom
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
${sensors.lib_deps}
|
||||
build_flags=
|
||||
-D M5_ENV4
|
||||
-D M5_CAN_KIT
|
||||
${env.build_flags}
|
||||
|
||||
|
||||
[env:m5stack-atom-bme280]
|
||||
extends = sensors
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
@@ -27,6 +27,8 @@ const double nmTom = 1.852 * 1000;
|
||||
|
||||
uint16_t DaysSince1970 = 0;
|
||||
|
||||
#define boolbit(b) (b?1:0)
|
||||
|
||||
class MyAisDecoder : public AIS::AisDecoder
|
||||
{
|
||||
public:
|
||||
@@ -82,25 +84,24 @@ class MyAisDecoder : public AIS::AisDecoder
|
||||
|
||||
tN2kMsg N2kMsg;
|
||||
|
||||
// PGN129038
|
||||
|
||||
N2kMsg.SetPGN(129038L);
|
||||
N2kMsg.Priority = 4;
|
||||
N2kMsg.AddByte((_Repeat & 0x03) << 6 | (_uMsgType & 0x3f));
|
||||
N2kMsg.Add4ByteUInt(_uMmsi);
|
||||
N2kMsg.Add4ByteDouble(_iPosLon / 600000.0, 1e-07);
|
||||
N2kMsg.Add4ByteDouble(_iPosLat / 600000.0, 1e-07);
|
||||
N2kMsg.AddByte((_timestamp & 0x3f) << 2 | (_Raim & 0x01) << 1 | (_bPosAccuracy & 0x01));
|
||||
N2kMsg.Add2ByteUDouble(decodeCog(_iCog), 1e-04);
|
||||
N2kMsg.Add2ByteUDouble(_uSog * knToms/10.0, 0.01);
|
||||
N2kMsg.AddByte(0x00); // Communication State (19 bits)
|
||||
N2kMsg.AddByte(0x00);
|
||||
N2kMsg.AddByte(0x00); // AIS transceiver information (5 bits)
|
||||
N2kMsg.Add2ByteUDouble(decodeHeading(_iHeading), 1e-04);
|
||||
N2kMsg.Add2ByteDouble(decodeRot(_iRot), 3.125E-05); // 1e-3/32.0
|
||||
N2kMsg.AddByte(0xF0 | (_uNavstatus & 0x0f));
|
||||
N2kMsg.AddByte(0xff); // Reserved
|
||||
N2kMsg.AddByte(0xff); // SID (NA)
|
||||
SetN2kPGN129038(
|
||||
N2kMsg,
|
||||
_uMsgType,
|
||||
(tN2kAISRepeat)_Repeat,
|
||||
_uMmsi,
|
||||
_iPosLon/ 600000.0,
|
||||
_iPosLat / 600000.0,
|
||||
_bPosAccuracy,
|
||||
_Raim,
|
||||
_timestamp,
|
||||
decodeCog(_iCog),
|
||||
_uSog * knToms/10.0,
|
||||
tN2kAISTransceiverInformation::N2kaischannel_A_VDL_reception,
|
||||
decodeHeading(_iHeading),
|
||||
decodeRot(_iRot),
|
||||
(tN2kAISNavStatus)_uNavstatus,
|
||||
0xff
|
||||
);
|
||||
|
||||
send(N2kMsg);
|
||||
}
|
||||
@@ -255,9 +256,40 @@ class MyAisDecoder : public AIS::AisDecoder
|
||||
send(N2kMsg);
|
||||
}
|
||||
|
||||
|
||||
virtual void onType21(unsigned int , unsigned int , const std::string &, bool , int , int , unsigned int , unsigned int , unsigned int , unsigned int ) override {
|
||||
//mmsi, aidType, name + nameExt, posAccuracy, posLon, posLat, toBow, toStern, toPort, toStarboard
|
||||
virtual void onType21(unsigned int mmsi , unsigned int aidType , const std::string & name, bool accuracy, int posLon, int posLat, unsigned int toBow,
|
||||
unsigned int toStern, unsigned int toPort, unsigned int toStarboard,
|
||||
unsigned int repeat,unsigned int timestamp, bool raim, bool virtualAton, bool offPosition) override {
|
||||
//Serial.println("21");
|
||||
//the name can be at most 120bit+88bit (35 byte) + termination -> 36 Byte
|
||||
//in principle we should use tN2kAISAtoNReportData to directly call the library
|
||||
//function for 129041. But this makes the conversion really complex.
|
||||
bool assignedMode=false;
|
||||
tN2kGNSStype gnssType=tN2kGNSStype::N2kGNSSt_GPS; //canboat considers 0 as undefined...
|
||||
tN2kAISTransceiverInformation transceiverInfo=tN2kAISTransceiverInformation::N2kaischannel_A_VDL_reception;
|
||||
tN2kMsg N2kMsg;
|
||||
N2kMsg.SetPGN(129041);
|
||||
N2kMsg.Priority=4;
|
||||
N2kMsg.AddByte((repeat & 0x03) << 6 | (21 & 0x3f));
|
||||
N2kMsg.Add4ByteUInt(mmsi); //N2kData.UserID
|
||||
N2kMsg.Add4ByteDouble(posLon / 600000.0, 1e-07);
|
||||
N2kMsg.Add4ByteDouble(posLat / 600000.0, 1e-07);
|
||||
N2kMsg.AddByte((timestamp & 0x3f)<<2 | boolbit(raim)<<1 | boolbit(accuracy));
|
||||
N2kMsg.Add2ByteUDouble(toBow+toStern, 0.1);
|
||||
N2kMsg.Add2ByteUDouble(toPort+toStarboard, 0.1);
|
||||
N2kMsg.Add2ByteUDouble(toStarboard, 0.1);
|
||||
N2kMsg.Add2ByteUDouble(toBow, 0.1);
|
||||
N2kMsg.AddByte(boolbit(assignedMode) << 7
|
||||
| boolbit(virtualAton) << 6
|
||||
| boolbit(offPosition) << 5
|
||||
| (aidType & 0x1f));
|
||||
N2kMsg.AddByte((gnssType & 0x0F) << 1 | 0xe0);
|
||||
N2kMsg.AddByte(N2kUInt8NA); //status
|
||||
N2kMsg.AddByte((transceiverInfo & 0x1f) | 0xe0);
|
||||
//bit offset 208 (see canboat/pgns.xml) -> 26 bytes from start
|
||||
//as MaxDataLen is 223 and the string can be at most 36 bytes + 2 byte heading - no further check here
|
||||
N2kMsg.AddVarStr(name.c_str());
|
||||
send(N2kMsg);
|
||||
}
|
||||
|
||||
virtual void onType24A(unsigned int _uMsgType, unsigned int _repeat, unsigned int _uMmsi,
|
||||
|
||||
@@ -143,7 +143,7 @@ private:
|
||||
*/
|
||||
GwXDRFoundMapping getOtherFieldMapping(GwXDRFoundMapping &found, int field){
|
||||
if (found.empty) return GwXDRFoundMapping();
|
||||
return xdrMappings->getMapping(found.definition->category,
|
||||
return xdrMappings->getMapping(0,found.definition->category,
|
||||
found.definition->selector,
|
||||
field,
|
||||
found.instanceId);
|
||||
|
||||
@@ -708,12 +708,37 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
//helper for converting the AIS transceiver info to talker/channel
|
||||
|
||||
void setTalkerChannel(tNMEA0183AISMsg &msg, tN2kAISTransceiverInformation &transceiver){
|
||||
bool channelA=true;
|
||||
bool own=false;
|
||||
switch (transceiver){
|
||||
case tN2kAISTransceiverInformation::N2kaischannel_A_VDL_reception:
|
||||
channelA=true;
|
||||
own=false;
|
||||
break;
|
||||
case tN2kAISTransceiverInformation::N2kaischannel_B_VDL_reception:
|
||||
channelA=false;
|
||||
own=false;
|
||||
break;
|
||||
case tN2kAISTransceiverInformation::N2kaischannel_A_VDL_transmission:
|
||||
channelA=true;
|
||||
own=true;
|
||||
break;
|
||||
case tN2kAISTransceiverInformation::N2kaischannel_B_VDL_transmission:
|
||||
channelA=false;
|
||||
own=true;
|
||||
break;
|
||||
}
|
||||
msg.SetChannelAndTalker(channelA,own);
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
// 129038 AIS Class A Position Report (Message 1, 2, 3)
|
||||
void HandleAISClassAPosReport(const tN2kMsg &N2kMsg)
|
||||
{
|
||||
|
||||
unsigned char SID;
|
||||
tN2kAISRepeat _Repeat;
|
||||
uint32_t _UserID; // MMSI
|
||||
double _Latitude =N2kDoubleNA;
|
||||
@@ -732,64 +757,19 @@ private:
|
||||
uint8_t _MessageType = 1;
|
||||
tNMEA0183AISMsg NMEA0183AISMsg;
|
||||
|
||||
if (ParseN2kPGN129038(N2kMsg, SID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM, _Seconds,
|
||||
if (ParseN2kPGN129038(N2kMsg, _MessageType, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM, _Seconds,
|
||||
_COG, _SOG, _Heading, _ROT, _NavStatus,_AISTransceiverInformation,_SID))
|
||||
{
|
||||
|
||||
// Debug
|
||||
#ifdef SERIAL_PRINT_AIS_FIELDS
|
||||
Serial.println("–––––––––––––––––––––––– Msg 1 ––––––––––––––––––––––––––––––––");
|
||||
|
||||
const double pi = 3.1415926535897932384626433832795;
|
||||
const double radToDeg = 180.0 / pi;
|
||||
const double msTokn = 3600.0 / 1852.0;
|
||||
const double radsToDegMin = 60 * 360.0 / (2 * pi); // [rad/s -> degree/minute]
|
||||
Serial.print("Repeat: ");
|
||||
Serial.println(_Repeat);
|
||||
Serial.print("UserID: ");
|
||||
Serial.println(_UserID);
|
||||
Serial.print("Latitude: ");
|
||||
Serial.println(_Latitude);
|
||||
Serial.print("Longitude: ");
|
||||
Serial.println(_Longitude);
|
||||
Serial.print("Accuracy: ");
|
||||
Serial.println(_Accuracy);
|
||||
Serial.print("RAIM: ");
|
||||
Serial.println(_RAIM);
|
||||
Serial.print("Seconds: ");
|
||||
Serial.println(_Seconds);
|
||||
Serial.print("COG: ");
|
||||
Serial.println(_COG * radToDeg);
|
||||
Serial.print("SOG: ");
|
||||
Serial.println(_SOG * msTokn);
|
||||
Serial.print("Heading: ");
|
||||
Serial.println(_Heading * radToDeg);
|
||||
Serial.print("ROT: ");
|
||||
Serial.println(_ROT * radsToDegMin);
|
||||
Serial.print("NavStatus: ");
|
||||
Serial.println(_NavStatus);
|
||||
#endif
|
||||
|
||||
setTalkerChannel(NMEA0183AISMsg,_AISTransceiverInformation);
|
||||
if (_MessageType < 1 || _MessageType > 3) _MessageType=1; //only allow type 1...3 for 129038
|
||||
if (SetAISClassABMessage1(NMEA0183AISMsg, _MessageType, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy,
|
||||
_RAIM, _Seconds, _COG, _SOG, _Heading, _ROT, _NavStatus))
|
||||
{
|
||||
|
||||
SendMessage(NMEA0183AISMsg);
|
||||
|
||||
#ifdef SERIAL_PRINT_AIS_NMEA
|
||||
// Debug Print AIS-NMEA
|
||||
Serial.print(NMEA0183AISMsg.GetPrefix());
|
||||
Serial.print(NMEA0183AISMsg.Sender());
|
||||
Serial.print(NMEA0183AISMsg.MessageCode());
|
||||
for (int i = 0; i < NMEA0183AISMsg.FieldCount(); i++)
|
||||
{
|
||||
Serial.print(",");
|
||||
Serial.print(NMEA0183AISMsg.Field(i));
|
||||
}
|
||||
char buf[7];
|
||||
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum());
|
||||
Serial.print(buf);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} // end 129038 AIS Class A Position Report Message 1/3
|
||||
@@ -825,84 +805,18 @@ private:
|
||||
_Length, _Beam, _PosRefStbd, _PosRefBow, _ETAdate, _ETAtime, _Draught, _Destination,21,
|
||||
_AISversion, _GNSStype, _DTE, _AISinfo,_SID))
|
||||
{
|
||||
|
||||
#ifdef SERIAL_PRINT_AIS_FIELDS
|
||||
// Debug Print N2k Values
|
||||
Serial.println("––––––––––––––––––––––– Msg 5 –––––––––––––––––––––––––––––––––");
|
||||
Serial.print("MessageID: ");
|
||||
Serial.println(_MessageID);
|
||||
Serial.print("Repeat: ");
|
||||
Serial.println(_Repeat);
|
||||
Serial.print("UserID: ");
|
||||
Serial.println(_UserID);
|
||||
Serial.print("IMONumber: ");
|
||||
Serial.println(_IMONumber);
|
||||
Serial.print("Callsign: ");
|
||||
Serial.println(_Callsign);
|
||||
Serial.print("VesselType: ");
|
||||
Serial.println(_VesselType);
|
||||
Serial.print("Name: ");
|
||||
Serial.println(_Name);
|
||||
Serial.print("Length: ");
|
||||
Serial.println(_Length);
|
||||
Serial.print("Beam: ");
|
||||
Serial.println(_Beam);
|
||||
Serial.print("PosRefStbd: ");
|
||||
Serial.println(_PosRefStbd);
|
||||
Serial.print("PosRefBow: ");
|
||||
Serial.println(_PosRefBow);
|
||||
Serial.print("ETAdate: ");
|
||||
Serial.println(_ETAdate);
|
||||
Serial.print("ETAtime: ");
|
||||
Serial.println(_ETAtime);
|
||||
Serial.print("Draught: ");
|
||||
Serial.println(_Draught);
|
||||
Serial.print("Destination: ");
|
||||
Serial.println(_Destination);
|
||||
Serial.print("GNSStype: ");
|
||||
Serial.println(_GNSStype);
|
||||
Serial.print("DTE: ");
|
||||
Serial.println(_DTE);
|
||||
Serial.println("––––––––––––––––––––––– Msg 5 –––––––––––––––––––––––––––––––––");
|
||||
#endif
|
||||
|
||||
setTalkerChannel(NMEA0183AISMsg,_AISinfo);
|
||||
if (SetAISClassAMessage5(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _IMONumber, _Callsign, _Name, _VesselType,
|
||||
_Length, _Beam, _PosRefStbd, _PosRefBow, _ETAdate, _ETAtime, _Draught, _Destination,
|
||||
_GNSStype, _DTE))
|
||||
_GNSStype, _DTE,_AISversion))
|
||||
{
|
||||
|
||||
SendMessage(NMEA0183AISMsg.BuildMsg5Part1(NMEA0183AISMsg));
|
||||
|
||||
#ifdef SERIAL_PRINT_AIS_NMEA
|
||||
// Debug Print AIS-NMEA Message Type 5, Part 1
|
||||
char buf[7];
|
||||
Serial.print(NMEA0183AISMsg.GetPrefix());
|
||||
Serial.print(NMEA0183AISMsg.Sender());
|
||||
Serial.print(NMEA0183AISMsg.MessageCode());
|
||||
for (int i = 0; i < NMEA0183AISMsg.FieldCount(); i++)
|
||||
{
|
||||
Serial.print(",");
|
||||
Serial.print(NMEA0183AISMsg.Field(i));
|
||||
if (NMEA0183AISMsg.BuildMsg5Part1()){
|
||||
SendMessage(NMEA0183AISMsg);
|
||||
}
|
||||
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum());
|
||||
Serial.print(buf);
|
||||
#endif
|
||||
|
||||
SendMessage(NMEA0183AISMsg.BuildMsg5Part2(NMEA0183AISMsg));
|
||||
|
||||
#ifdef SERIAL_PRINT_AIS_NMEA
|
||||
// Print AIS-NMEA Message Type 5, Part 2
|
||||
Serial.print(NMEA0183AISMsg.GetPrefix());
|
||||
Serial.print(NMEA0183AISMsg.Sender());
|
||||
Serial.print(NMEA0183AISMsg.MessageCode());
|
||||
for (int i = 0; i < NMEA0183AISMsg.FieldCount(); i++)
|
||||
{
|
||||
Serial.print(",");
|
||||
Serial.print(NMEA0183AISMsg.Field(i));
|
||||
if (NMEA0183AISMsg.BuildMsg5Part2()){
|
||||
SendMessage(NMEA0183AISMsg);
|
||||
}
|
||||
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum());
|
||||
Serial.print(buf);
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -926,35 +840,21 @@ private:
|
||||
tN2kAISUnit _Unit;
|
||||
bool _Display, _DSC, _Band, _Msg22, _State;
|
||||
tN2kAISMode _Mode;
|
||||
tN2kAISTransceiverInformation _AISTranceiverInformation;
|
||||
tN2kAISTransceiverInformation _AISTransceiverInformation;
|
||||
uint8_t _SID;
|
||||
|
||||
if (ParseN2kPGN129039(N2kMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM,
|
||||
_Seconds, _COG, _SOG, _AISTranceiverInformation, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State,_SID))
|
||||
_Seconds, _COG, _SOG, _AISTransceiverInformation, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State,_SID))
|
||||
{
|
||||
|
||||
tNMEA0183AISMsg NMEA0183AISMsg;
|
||||
|
||||
setTalkerChannel(NMEA0183AISMsg,_AISTransceiverInformation);
|
||||
if (SetAISClassBMessage18(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM,
|
||||
_Seconds, _COG, _SOG, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State))
|
||||
{
|
||||
|
||||
SendMessage(NMEA0183AISMsg);
|
||||
|
||||
#ifdef SERIAL_PRINT_AIS_NMEA
|
||||
// Debug Print AIS-NMEA
|
||||
Serial.print(NMEA0183AISMsg.GetPrefix());
|
||||
Serial.print(NMEA0183AISMsg.Sender());
|
||||
Serial.print(NMEA0183AISMsg.MessageCode());
|
||||
for (int i = 0; i < NMEA0183AISMsg.FieldCount(); i++)
|
||||
{
|
||||
Serial.print(",");
|
||||
Serial.print(NMEA0183AISMsg.Field(i));
|
||||
}
|
||||
char buf[7];
|
||||
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum());
|
||||
Serial.print(buf);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -976,8 +876,10 @@ private:
|
||||
{
|
||||
|
||||
tNMEA0183AISMsg NMEA0183AISMsg;
|
||||
setTalkerChannel(NMEA0183AISMsg,_AISInfo);
|
||||
if (SetAISClassBMessage24PartA(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _Name))
|
||||
{
|
||||
SendMessage(NMEA0183AISMsg);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -1005,77 +907,51 @@ private:
|
||||
_Length, _Beam, _PosRefStbd, _PosRefBow, _MothershipID,_AISInfo,_SID))
|
||||
{
|
||||
|
||||
//
|
||||
#ifdef SERIAL_PRINT_AIS_FIELDS
|
||||
// Debug Print N2k Values
|
||||
Serial.println("––––––––––––––––––––––– Msg 24 ––––––––––––––––––––––––––––––––");
|
||||
Serial.print("MessageID: ");
|
||||
Serial.println(_MessageID);
|
||||
Serial.print("Repeat: ");
|
||||
Serial.println(_Repeat);
|
||||
Serial.print("UserID: ");
|
||||
Serial.println(_UserID);
|
||||
Serial.print("VesselType: ");
|
||||
Serial.println(_VesselType);
|
||||
Serial.print("Vendor: ");
|
||||
Serial.println(_Vendor);
|
||||
Serial.print("Callsign: ");
|
||||
Serial.println(_Callsign);
|
||||
Serial.print("Length: ");
|
||||
Serial.println(_Length);
|
||||
Serial.print("Beam: ");
|
||||
Serial.println(_Beam);
|
||||
Serial.print("PosRefStbd: ");
|
||||
Serial.println(_PosRefStbd);
|
||||
Serial.print("PosRefBow: ");
|
||||
Serial.println(_PosRefBow);
|
||||
Serial.print("MothershipID: ");
|
||||
Serial.println(_MothershipID);
|
||||
Serial.println("––––––––––––––––––––––– Msg 24 ––––––––––––––––––––––––––––––––");
|
||||
#endif
|
||||
|
||||
tNMEA0183AISMsg NMEA0183AISMsg;
|
||||
|
||||
if (SetAISClassBMessage24(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _VesselType, _Vendor, _Callsign,
|
||||
setTalkerChannel(NMEA0183AISMsg,_AISInfo);
|
||||
if (SetAISClassBMessage24PartB(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _VesselType, _Vendor, _Callsign,
|
||||
_Length, _Beam, _PosRefStbd, _PosRefBow, _MothershipID))
|
||||
{
|
||||
|
||||
SendMessage(NMEA0183AISMsg.BuildMsg24PartA(NMEA0183AISMsg));
|
||||
|
||||
#ifdef SERIAL_PRINT_AIS_NMEA
|
||||
// Debug Print AIS-NMEA
|
||||
char buf[7];
|
||||
Serial.print(NMEA0183AISMsg.GetPrefix());
|
||||
Serial.print(NMEA0183AISMsg.Sender());
|
||||
Serial.print(NMEA0183AISMsg.MessageCode());
|
||||
for (int i = 0; i < NMEA0183AISMsg.FieldCount(); i++)
|
||||
{
|
||||
Serial.print(",");
|
||||
Serial.print(NMEA0183AISMsg.Field(i));
|
||||
}
|
||||
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum());
|
||||
Serial.print(buf);
|
||||
#endif
|
||||
|
||||
SendMessage(NMEA0183AISMsg.BuildMsg24PartB(NMEA0183AISMsg));
|
||||
|
||||
#ifdef SERIAL_PRINT_AIS_NMEA
|
||||
Serial.print(NMEA0183AISMsg.GetPrefix());
|
||||
Serial.print(NMEA0183AISMsg.Sender());
|
||||
Serial.print(NMEA0183AISMsg.MessageCode());
|
||||
for (int i = 0; i < NMEA0183AISMsg.FieldCount(); i++)
|
||||
{
|
||||
Serial.print(",");
|
||||
Serial.print(NMEA0183AISMsg.Field(i));
|
||||
}
|
||||
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum());
|
||||
Serial.print(buf);
|
||||
#endif
|
||||
SendMessage(NMEA0183AISMsg);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
// PGN 129041 Aton
|
||||
void HandleAISMessage21(const tN2kMsg &N2kMsg)
|
||||
{
|
||||
tN2kAISAtoNReportData data;
|
||||
if (ParseN2kPGN129041(N2kMsg,data)){
|
||||
tNMEA0183AISMsg nmea0183Msg;
|
||||
setTalkerChannel(nmea0183Msg,data.AISTransceiverInformation);
|
||||
if (SetAISMessage21(
|
||||
nmea0183Msg,
|
||||
data.Repeat,
|
||||
data.UserID,
|
||||
data.Latitude,
|
||||
data.Longitude,
|
||||
data.Accuracy,
|
||||
data.RAIM,
|
||||
data.Seconds,
|
||||
data.Length,
|
||||
data.Beam,
|
||||
data.PositionReferenceStarboard,
|
||||
data.PositionReferenceTrueNorth,
|
||||
data.AtoNType,
|
||||
data.OffPositionIndicator,
|
||||
data.VirtualAtoNFlag,
|
||||
data.AssignedModeFlag,
|
||||
data.GNSSType,
|
||||
data.AtoNStatus,
|
||||
data.AtoNName
|
||||
)){
|
||||
SendMessage(nmea0183Msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleSystemTime(const tN2kMsg &msg){
|
||||
unsigned char sid=-1;
|
||||
uint16_t DaysSince1970=N2kUInt16NA;
|
||||
@@ -1271,12 +1147,12 @@ private:
|
||||
double Level=N2kDoubleNA;
|
||||
double Capacity=N2kDoubleNA;
|
||||
if (ParseN2kPGN127505(N2kMsg,Instance,FluidType,Level,Capacity)) {
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRFLUID,FluidType,0,Instance);
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(Level,XDRFLUID,FluidType,0,Instance);
|
||||
if (updateDouble(&mapping,Level)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found fluidlevel mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(Level));
|
||||
}
|
||||
mapping=xdrMappings->getMapping(XDRFLUID,FluidType,1,Instance);
|
||||
mapping=xdrMappings->getMapping(Capacity, XDRFLUID,FluidType,1,Instance);
|
||||
if (updateDouble(&mapping,Capacity)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found fluid capacity mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(Capacity));
|
||||
@@ -1294,19 +1170,19 @@ private:
|
||||
double BatteryTemperature=N2kDoubleNA;
|
||||
if (ParseN2kPGN127508(N2kMsg,BatteryInstance,BatteryVoltage,BatteryCurrent,BatteryTemperature,SID)) {
|
||||
int i=0;
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRBAT,0,0,BatteryInstance);
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(BatteryVoltage, XDRBAT,0,0,BatteryInstance);
|
||||
if (updateDouble(&mapping,BatteryVoltage)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found BatteryVoltage mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(BatteryVoltage));
|
||||
i++;
|
||||
}
|
||||
mapping=xdrMappings->getMapping(XDRBAT,0,1,BatteryInstance);
|
||||
mapping=xdrMappings->getMapping(BatteryCurrent,XDRBAT,0,1,BatteryInstance);
|
||||
if (updateDouble(&mapping,BatteryCurrent)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found BatteryCurrent mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(BatteryCurrent));
|
||||
i++;
|
||||
}
|
||||
mapping=xdrMappings->getMapping(XDRBAT,0,2,BatteryInstance);
|
||||
mapping=xdrMappings->getMapping(BatteryTemperature,XDRBAT,0,2,BatteryInstance);
|
||||
if (updateDouble(&mapping,BatteryTemperature)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found BatteryTemperature mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(BatteryTemperature));
|
||||
@@ -1338,13 +1214,13 @@ private:
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
int i=0;
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,N2kts_OutsideTemperature,0,0);
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(OutsideAmbientAirTemperature, XDRTEMP,N2kts_OutsideTemperature,0,0);
|
||||
if (updateDouble(&mapping,OutsideAmbientAirTemperature)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(OutsideAmbientAirTemperature));
|
||||
i++;
|
||||
}
|
||||
mapping=xdrMappings->getMapping(XDRPRESSURE,N2kps_Atmospheric,0,0);
|
||||
mapping=xdrMappings->getMapping(AtmosphericPressure,XDRPRESSURE,N2kps_Atmospheric,0,0);
|
||||
if (updateDouble(&mapping,AtmosphericPressure)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(AtmosphericPressure));
|
||||
@@ -1379,19 +1255,19 @@ private:
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,TempSource,0,0);
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(Temperature, XDRTEMP,TempSource,0,0);
|
||||
if (updateDouble(&mapping,Temperature)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(Temperature));
|
||||
i++;
|
||||
}
|
||||
mapping=xdrMappings->getMapping(XDRHUMIDITY,HumiditySource,0,0);
|
||||
mapping=xdrMappings->getMapping(Humidity, XDRHUMIDITY,HumiditySource,0,0);
|
||||
if (updateDouble(&mapping,Humidity)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found humidity mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(Humidity));
|
||||
i++;
|
||||
}
|
||||
mapping=xdrMappings->getMapping(XDRPRESSURE,N2kps_Atmospheric,0,0);
|
||||
mapping=xdrMappings->getMapping(AtmosphericPressure, XDRPRESSURE,N2kps_Atmospheric,0,0);
|
||||
if (updateDouble(&mapping,AtmosphericPressure)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(AtmosphericPressure));
|
||||
@@ -1426,12 +1302,12 @@ private:
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,(int)TemperatureSource,0,TemperatureInstance);
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(Temperature, XDRTEMP,(int)TemperatureSource,0,TemperatureInstance);
|
||||
if (updateDouble(&mapping,Temperature)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(Temperature));
|
||||
}
|
||||
mapping=xdrMappings->getMapping(XDRTEMP,(int)TemperatureSource,1,TemperatureInstance);
|
||||
mapping=xdrMappings->getMapping(setTemperature, XDRTEMP,(int)TemperatureSource,1,TemperatureInstance);
|
||||
if (updateDouble(&mapping,setTemperature)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(setTemperature));
|
||||
@@ -1449,12 +1325,13 @@ private:
|
||||
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
|
||||
return;
|
||||
}
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRHUMIDITY,(int)HumiditySource,0,HumidityInstance);
|
||||
GwXDRFoundMapping mapping;
|
||||
mapping=xdrMappings->getMapping(ActualHumidity, XDRHUMIDITY,(int)HumiditySource,0,HumidityInstance);
|
||||
if (updateDouble(&mapping,ActualHumidity)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found humidity mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(ActualHumidity));
|
||||
}
|
||||
mapping=xdrMappings->getMapping(XDRHUMIDITY,(int)HumiditySource,1,HumidityInstance);
|
||||
mapping=xdrMappings->getMapping(SetHumidity, XDRHUMIDITY,(int)HumiditySource,1,HumidityInstance);
|
||||
if (updateDouble(&mapping,SetHumidity)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found humidity mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(SetHumidity));
|
||||
@@ -1472,7 +1349,7 @@ private:
|
||||
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
|
||||
return;
|
||||
}
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRPRESSURE,(int)PressureSource,0,PressureInstance);
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(ActualPressure, XDRPRESSURE,(int)PressureSource,0,PressureInstance);
|
||||
if (! updateDouble(&mapping,ActualPressure)) return;
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(ActualPressure));
|
||||
@@ -1490,12 +1367,12 @@ private:
|
||||
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
|
||||
}
|
||||
for (int i=0;i<8;i++){
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRENGINE,0,i,instance);
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(values[i], XDRENGINE,0,i,instance);
|
||||
if (! updateDouble(&mapping,values[i])) continue;
|
||||
addToXdr(mapping.buildXdrEntry(values[i]));
|
||||
}
|
||||
for (int i=0;i< 2;i++){
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRENGINE,0,i+8,instance);
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(ivalues[i],XDRENGINE,0,i+8,instance);
|
||||
if (! updateDouble(&mapping,ivalues[i])) continue;
|
||||
addToXdr(mapping.buildXdrEntry((double)ivalues[i]));
|
||||
}
|
||||
@@ -1511,7 +1388,7 @@ private:
|
||||
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
|
||||
}
|
||||
for (int i=0;i<3;i++){
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRATTITUDE,0,i,instance);
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(values[i], XDRATTITUDE,0,i,instance);
|
||||
if (! updateDouble(&mapping,values[i])) continue;
|
||||
addToXdr(mapping.buildXdrEntry(values[i]));
|
||||
}
|
||||
@@ -1525,15 +1402,15 @@ private:
|
||||
speed,pressure,tilt)){
|
||||
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
|
||||
}
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRENGINE,0,10,instance);
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(speed, XDRENGINE,0,10,instance);
|
||||
if (updateDouble(&mapping,speed)){
|
||||
addToXdr(mapping.buildXdrEntry(speed));
|
||||
}
|
||||
mapping=xdrMappings->getMapping(XDRENGINE,0,11,instance);
|
||||
mapping=xdrMappings->getMapping(pressure, XDRENGINE,0,11,instance);
|
||||
if (updateDouble(&mapping,pressure)){
|
||||
addToXdr(mapping.buildXdrEntry(pressure));
|
||||
}
|
||||
mapping=xdrMappings->getMapping(XDRENGINE,0,12,instance);
|
||||
mapping=xdrMappings->getMapping(tilt, XDRENGINE,0,12,instance);
|
||||
if (updateDouble(&mapping,tilt)){
|
||||
addToXdr(mapping.buildXdrEntry((double)tilt));
|
||||
}
|
||||
@@ -1559,12 +1436,12 @@ private:
|
||||
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
|
||||
return;
|
||||
}
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,(int)TemperatureSource,0,TemperatureInstance);
|
||||
GwXDRFoundMapping mapping=xdrMappings->getMapping(Temperature, XDRTEMP,(int)TemperatureSource,0,TemperatureInstance);
|
||||
if (updateDouble(&mapping,Temperature)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(Temperature));
|
||||
}
|
||||
mapping=xdrMappings->getMapping(XDRTEMP,(int)TemperatureSource,1,TemperatureInstance);
|
||||
mapping=xdrMappings->getMapping(setTemperature, XDRTEMP,(int)TemperatureSource,1,TemperatureInstance);
|
||||
if (updateDouble(&mapping,setTemperature)){
|
||||
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
|
||||
addToXdr(mapping.buildXdrEntry(setTemperature));
|
||||
@@ -1614,6 +1491,7 @@ private:
|
||||
converters.registerConverter(129794UL, &N2kToNMEA0183Functions::HandleAISClassAMessage5); // AIS Class A Ship Static and Voyage related data, Message Type 5
|
||||
converters.registerConverter(129809UL, &N2kToNMEA0183Functions::HandleAISClassBMessage24A); // AIS Class B "CS" Static Data Report, Part A
|
||||
converters.registerConverter(129810UL, &N2kToNMEA0183Functions::HandleAISClassBMessage24B); // AIS Class B "CS" Static Data Report, Part B
|
||||
converters.registerConverter(129041UL, &N2kToNMEA0183Functions::HandleAISMessage21); // AIS Aton
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
#include <NMEA0183AISMessages.h>
|
||||
#include "NMEA0183AISMessages.h"
|
||||
#include <N2kTypes.h>
|
||||
#include <N2kMsg.h>
|
||||
#include <string.h>
|
||||
@@ -34,7 +34,7 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//#include <unordered_map>
|
||||
#include <sstream>
|
||||
#include <math.h>
|
||||
#include <NMEA0183AISMsg.h>
|
||||
#include "NMEA0183AISMsg.h"
|
||||
|
||||
const double pi=3.1415926535897932384626433832795;
|
||||
const double kmhToms=1000.0/3600.0;
|
||||
@@ -47,17 +47,15 @@ const double nmTom=1.852*1000;
|
||||
const double mToFathoms=0.546806649;
|
||||
const double mToFeet=3.2808398950131;
|
||||
const double radsToDegMin = 60 * 360.0 / (2 * pi); // [rad/s -> degree/minute]
|
||||
const char Prefix='!';
|
||||
|
||||
std::vector<ship *> vships;
|
||||
|
||||
int numShips(){return vships.size();}
|
||||
// ************************ Helper for AIS ***********************************
|
||||
static bool AddMessageType(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType);
|
||||
static bool AddRepeat(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Repeat);
|
||||
static bool AddUserID(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t UserID);
|
||||
static bool AddIMONumber(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t &IMONumber);
|
||||
static bool AddText(tNMEA0183AISMsg &NMEA0183AISMsg, char *FieldVal, uint8_t length);
|
||||
//static bool AddVesselType(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t VesselType);
|
||||
static bool AddDimensions(tNMEA0183AISMsg &NMEA0183AISMsg, double Length, double Beam, double PosRefStbd, double PosRefBow);
|
||||
static bool AddNavStatus(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t &NavStatus);
|
||||
static bool AddROT(tNMEA0183AISMsg &NMEA0183AISMsg, double &rot);
|
||||
@@ -81,8 +79,8 @@ static bool AddETADateTime(tNMEA0183AISMsg &NMEA0183AISMsg, uint16_t &ETAdate, d
|
||||
//
|
||||
// Got values from: ParseN2kPGN129038()
|
||||
bool SetAISClassABMessage1( tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType, uint8_t Repeat,
|
||||
uint32_t UserID, double Latitude, double Longitude, bool Accuracy, bool RAIM, uint8_t Seconds,
|
||||
double COG, double SOG, double Heading, double ROT, uint8_t NavStatus ) {
|
||||
uint32_t UserID, double Latitude, double Longitude, bool Accuracy, bool RAIM, uint8_t Seconds,
|
||||
double COG, double SOG, double Heading, double ROT, uint8_t NavStatus ) {
|
||||
|
||||
NMEA0183AISMsg.ClearAIS();
|
||||
if ( !AddMessageType(NMEA0183AISMsg, MessageType) ) return false; // 0 - 5 | 6 Message Type -> Constant: 1
|
||||
@@ -91,7 +89,7 @@ bool SetAISClassABMessage1( tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType
|
||||
if ( !AddNavStatus(NMEA0183AISMsg, NavStatus) ) return false; // 38-41 | 4 Navigational Status e.g.: "Under way sailing"
|
||||
if ( !AddROT(NMEA0183AISMsg, ROT) ) return false; // 42-49 | 8 Rate of Turn (ROT)
|
||||
if ( !AddSOG(NMEA0183AISMsg, SOG) ) return false; // 50-59 | 10 [m/s -> kts] SOG with one digit x10, 1023 = N/A
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Accuracy, 1) ) return false;// 60 | 1 GPS Accuracy 1 oder 0, Default 0
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Accuracy) ) return false;// 60 | 1 GPS Accuracy 1 oder 0, Default 0
|
||||
if ( !AddLongitude(NMEA0183AISMsg, Longitude) ) return false; // 61-88 | 28 Longitude in Minutes / 10000
|
||||
if ( !AddLatitude(NMEA0183AISMsg, Latitude) ) return false; // 89-115 | 27 Latitude in Minutes / 10000
|
||||
if ( !AddCOG(NMEA0183AISMsg, COG) ) return false; // 116-127 | 12 Course over ground will be 3600 (0xE10) if that data is not available.
|
||||
@@ -99,17 +97,12 @@ bool SetAISClassABMessage1( tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType
|
||||
if ( !AddSeconds(NMEA0183AISMsg, Seconds) ) return false; // 137-142 | 6 Seconds in UTC timestamp)
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 2) ) return false; // 143-144 | 2 Maneuver Indicator: 0 (default) 1, 2 (not delivered within this PGN)
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 3) ) return false; // 145-147 | 3 Spare
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(RAIM, 1) ) return false; // 148-148 | 1 RAIM flag 0 = RAIM not in use (default), 1 = RAIM in use
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(RAIM) ) return false; // 148-148 | 1 RAIM flag 0 = RAIM not in use (default), 1 = RAIM in use
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 19) ) return false; // 149-167 | 19 Radio Status (-> 0 NOT SENT WITH THIS PGN!!!!!)
|
||||
|
||||
if ( !NMEA0183AISMsg.Init("VDM","AI", Prefix) ) return false;
|
||||
if ( !NMEA0183AISMsg.AddStrField("1") ) return false;
|
||||
if ( !NMEA0183AISMsg.AddStrField("1") ) return false;
|
||||
if ( !NMEA0183AISMsg.AddEmptyField() ) return false;
|
||||
if ( !NMEA0183AISMsg.AddStrField("A") ) return false;
|
||||
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayload() ) ) return false;
|
||||
if ( !NMEA0183AISMsg.AddStrField("0") ) return false; // Message 1,2,3 has always Zero Padding
|
||||
|
||||
if ( !NMEA0183AISMsg.InitAis()) return false;
|
||||
int padBits=0;
|
||||
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
|
||||
if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -121,14 +114,16 @@ bool SetAISClassAMessage5(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
|
||||
uint32_t UserID, uint32_t IMONumber, char *Callsign, char *Name,
|
||||
uint8_t VesselType, double Length, double Beam, double PosRefStbd,
|
||||
double PosRefBow, uint16_t ETAdate, double ETAtime, double Draught,
|
||||
char *Destination, tN2kGNSStype GNSStype, uint8_t DTE ) {
|
||||
char *Destination, tN2kGNSStype GNSStype, uint8_t DTE,
|
||||
tN2kAISVersion AISversion) {
|
||||
|
||||
// AIS Type 5 Message
|
||||
NMEA0183AISMsg.ClearAIS();
|
||||
if ( !AddMessageType(NMEA0183AISMsg, 5) ) return false; // 0 - 5 | 6 Message Type -> Constant: 5
|
||||
if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false; // 6 - 7 | 2 Repeat Indicator: 0 = default; 3 = do not repeat any more
|
||||
if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false; // 8 - 37 | 30 MMSI
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(1, 2) ) return false; // 38 - 39 | 2 AIS Version -> 0 oder 1 NOT DERIVED FROM N2k, Always 1!!!!
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin((uint32_t)AISversion, 2) )
|
||||
return false; // 38 - 39 | 2 AIS Version -> 0 oder 1 NOT DERIVED FROM N2k, Always 1!!!!
|
||||
if ( !AddIMONumber(NMEA0183AISMsg, IMONumber) ) return false; // 40 - 69 | 30 IMO Number unisgned
|
||||
if ( !AddText(NMEA0183AISMsg, Callsign, 42) ) return false; // 70 - 111 | 42 Call Sign WDE4178 -> 7 6-bit characters -> Ascii lt. Table)
|
||||
if ( !AddText(NMEA0183AISMsg, Name, 120) ) return false; // 112-231 | 120 Vessel Name POINT FERMIN -> 20 6-bit characters -> Ascii lt. Table
|
||||
@@ -146,15 +141,17 @@ bool SetAISClassAMessage5(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
|
||||
|
||||
// ****************************************************************************
|
||||
// AIS position report (class B 129039) -> Type 18: Standard Class B CS Position Report
|
||||
// ParseN2kPGN129039(const tN2kMsg &N2kMsg, uint8_t &MessageID, tN2kAISRepeat &Repeat, uint32_t &UserID,
|
||||
// PGN129039
|
||||
// ParseN2kAISClassBPosition(const tN2kMsg &N2kMsg, uint8_t &MessageID, tN2kAISRepeat &Repeat, uint32_t &UserID,
|
||||
// double &Latitude, double &Longitude, bool &Accuracy, bool &RAIM,
|
||||
// uint8_t &Seconds, double &COG, double &SOG, double &Heading, tN2kAISUnit &Unit,
|
||||
// bool &Display, bool &DSC, bool &Band, bool &Msg22, tN2kAISMode &Mode, bool &State)
|
||||
// uint8_t &Seconds, double &COG, double &SOG, tN2kAISTransceiverInformation &AISTransceiverInformation,
|
||||
// double &Heading, tN2kAISUnit &Unit, bool &Display, bool &DSC, bool &Band, bool &Msg22, tN2kAISMode &Mode,
|
||||
// bool &State)
|
||||
// VDM, VDO (AIS VHF Data-link message 18)
|
||||
bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat, uint32_t UserID,
|
||||
double Latitude, double Longitude, bool Accuracy, bool RAIM,
|
||||
uint8_t Seconds, double COG, double SOG, double Heading, tN2kAISUnit Unit,
|
||||
bool Display, bool DSC, bool Band, bool Msg22, bool Mode, bool State) {
|
||||
double Latitude, double Longitude, bool Accuracy, bool RAIM,
|
||||
uint8_t Seconds, double COG, double SOG, double Heading, tN2kAISUnit Unit,
|
||||
bool Display, bool DSC, bool Band, bool Msg22, bool Mode, bool State) {
|
||||
//
|
||||
NMEA0183AISMsg.ClearAIS();
|
||||
if ( !AddMessageType(NMEA0183AISMsg, MessageID) ) return false; // 0 - 5 | 6 Message Type -> Constant: 18
|
||||
@@ -162,7 +159,7 @@ bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
|
||||
if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false; // 8 - 37 | 30 MMSI
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 8) ) return false; // 38-45 | 8 Regional Reserved
|
||||
if ( !AddSOG(NMEA0183AISMsg, SOG) ) return false; // 46-55 | 10 [m/s -> kts] SOG with one digit x10, 1023 = N/A
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Accuracy, 1)) return false; // 56 | 1 GPS Accuracy 1 oder 0, Default 0
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Accuracy)) return false; // 56 | 1 GPS Accuracy 1 oder 0, Default 0
|
||||
if ( !AddLongitude(NMEA0183AISMsg, Longitude) ) return false; // 57-84 | 28 Longitude in Minutes / 10000
|
||||
if ( !AddLatitude(NMEA0183AISMsg, Latitude) ) return false; // 85-111 | 27 Latitude in Minutes / 10000
|
||||
if ( !AddCOG(NMEA0183AISMsg, COG) ) return false; // 112-123 | 12 Course over ground will be 3600 (0xE10) if that data is not available.
|
||||
@@ -171,20 +168,16 @@ bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 2) ) return false; // 139-140 | 2 Regional Reserved
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(Unit, 1) ) return false; // 141 | 1 0=Class B SOTDMA unit 1=Class B CS (Carrier Sense) unit
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(Display, 1) ) return false; // 142 | 1 0=No visual display, 1=Has display, (Probably not reliable).
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(DSC, 1) ) return false; // 143 | 1 If 1, unit is attached to a VHF voice radio with DSC capability.
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Band, 1) ) return false; // 144 | 1 If this flag is 1, the unit can use any part of the marine channel.
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Msg22, 1) ) return false; // 145 | 1 If 1, unit can accept a channel assignment via Message Type 22.
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Mode, 1) ) return false; // 146 | 1 Assigned-mode flag: 0 = autonomous mode (default), 1 = assigned mode
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(RAIM, 1) ) return false; // 147 | 1 as for Message Type 1,2,3
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(DSC) ) return false; // 143 | 1 If 1, unit is attached to a VHF voice radio with DSC capability.
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Band) ) return false; // 144 | 1 If this flag is 1, the unit can use any part of the marine channel.
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Msg22)) return false; // 145 | 1 If 1, unit can accept a channel assignment via Message Type 22.
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Mode) ) return false; // 146 | 1 Assigned-mode flag: 0 = autonomous mode (default), 1 = assigned mode
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(RAIM) ) return false; // 147 | 1 as for Message Type 1,2,3
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 20) ) return false; // 148-167 | 20 Radio Status not in PGN 129039
|
||||
|
||||
if ( !NMEA0183AISMsg.Init("VDM","AI", Prefix) ) return false;
|
||||
if ( !NMEA0183AISMsg.AddStrField("1") ) return false;
|
||||
if ( !NMEA0183AISMsg.AddStrField("1") ) return false;
|
||||
if ( !NMEA0183AISMsg.AddEmptyField() ) return false;
|
||||
if ( !NMEA0183AISMsg.AddStrField("B") ) return false;
|
||||
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayload() ) ) return false;
|
||||
if ( !NMEA0183AISMsg.AddStrField("0") ) return false; // Message 18, has always Zero Padding
|
||||
if ( !NMEA0183AISMsg.InitAis()) return false;
|
||||
int padBits=0;
|
||||
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
|
||||
if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -209,7 +202,7 @@ bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
|
||||
//
|
||||
// PGN 129809 AIS Class B "CS" Static Data Report, Part A -> AIS VHF Data-link message 24
|
||||
// PGN 129810 AIS Class B "CS" Static Data Report, Part B -> AIS VHF Data-link message 24
|
||||
// ParseN2kPGN129809 (const tN2kMsg &N2kMsg, uint8_t &MessageID, tN2kAISRepeat &Repeat, uint32_t &UserID, char *Name) -> store to vector
|
||||
// ParseN2kPGN129809 (const tN2kMsg &N2kMsg, uint8_t &MessageID, tN2kAISRepeat &Repeat, uint32_t &UserID, char *Name) -> store to vector
|
||||
// ParseN2kPGN129810(const tN2kMsg &N2kMsg, uint8_t &MessageID, tN2kAISRepeat &Repeat, uint32_t &UserID,
|
||||
// uint8_t &VesselType, char *Vendor, char *Callsign, double &Length, double &Beam,
|
||||
// double &PosRefStbd, double &PosRefBow, uint32_t &MothershipID);
|
||||
@@ -217,41 +210,28 @@ bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
|
||||
// Part A: MessageID, Repeat, UserID, ShipName -> store in vector to call on Part B arrivals!!!
|
||||
// Part B: MessageID, Repeat, UserID, VesselType (5), Callsign (5), Length & Beam, PosRefBow,.. (5)
|
||||
bool SetAISClassBMessage24PartA(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat, uint32_t UserID, char *Name) {
|
||||
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < vships.size(); i++) {
|
||||
if ( vships[i]->_userID == UserID ) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( ! found ) {
|
||||
std::string nm;
|
||||
nm+= Name;
|
||||
vships.push_back(new ship(UserID, nm));
|
||||
}
|
||||
// AIS Type 24 Message
|
||||
NMEA0183AISMsg.ClearAIS();
|
||||
// Common for PART A AND Part B Bit 0 - 39 / len 40
|
||||
if ( !AddMessageType(NMEA0183AISMsg, 24) ) return false; // 0 - 5 | 6 Message Type -> Constant: 24
|
||||
if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false; // 6 - 7 | 2 Repeat Indicator: 0 = default; 3 = do not repeat any more
|
||||
if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false; // 8 - 37 | 30 MMSI
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 2) ) return false; // 38-39 | 2 Part Number 0-1 ->
|
||||
// Part A: 40 + 128 = len 168
|
||||
if ( !AddText(NMEA0183AISMsg, Name, 120) ) return false; // 40-159 | 120 Vessel Name 20 6-bit characters -> Ascii Table
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 8) ) return false; // 160-167 | 8 Spare
|
||||
if ( !NMEA0183AISMsg.InitAis() ) return false;
|
||||
int padBits=0;
|
||||
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
|
||||
if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ***************************************************************************************************************
|
||||
bool SetAISClassBMessage24(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat,
|
||||
bool SetAISClassBMessage24PartB(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat,
|
||||
uint32_t UserID, uint8_t VesselType, char *VendorID, char *Callsign,
|
||||
double Length, double Beam, double PosRefStbd, double PosRefBow, uint32_t MothershipID ) {
|
||||
|
||||
uint8_t PartNr = 0; // Identifier for the message part number; always 0 for Part A
|
||||
char *ShipName = (char*)" "; // get from vector to look up for sent Messages Part A
|
||||
|
||||
uint8_t i;
|
||||
for ( i = 0; i < vships.size(); i++) {
|
||||
if ( vships[i]->_userID == UserID ) {
|
||||
ShipName = const_cast<char*>( vships[i]->_shipName.c_str() );
|
||||
}
|
||||
}
|
||||
if ( i > MAX_SHIP_IN_VECTOR ) {
|
||||
std::vector<ship *>::iterator it=vships.begin();
|
||||
delete *it;
|
||||
vships.erase(it);
|
||||
}
|
||||
|
||||
// AIS Type 24 Message
|
||||
NMEA0183AISMsg.ClearAIS();
|
||||
@@ -259,11 +239,7 @@ bool SetAISClassBMessage24(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID,
|
||||
if ( !AddMessageType(NMEA0183AISMsg, 24) ) return false; // 0 - 5 | 6 Message Type -> Constant: 24
|
||||
if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false; // 6 - 7 | 2 Repeat Indicator: 0 = default; 3 = do not repeat any more
|
||||
if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false; // 8 - 37 | 30 MMSI
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(PartNr, 2) ) return false; // 38-39 | 2 Part Number 0-1 ->
|
||||
|
||||
// Part A: 40 + 128 = len 168
|
||||
if ( !AddText(NMEA0183AISMsg, ShipName, 120) ) return false; // 40-159 | 120 Vessel Name 20 6-bit characters -> Ascii Table
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 8) ) return false; // 160-167 | 8 Spare
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(1, 2) ) return false; // 38-39 | 2 Part Number 0-1 ->
|
||||
|
||||
// https://www.navcen.uscg.gov/?pageName=AISMessagesB
|
||||
// PART B: 40 + 128 = len 168
|
||||
@@ -272,6 +248,59 @@ bool SetAISClassBMessage24(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID,
|
||||
if ( !AddText(NMEA0183AISMsg, Callsign, 42) ) return false; // 218-259 | 90-131 | 42 Call Sign WDE4178 -> 7 6-bit characters, as in Msg Type 5
|
||||
if ( !AddDimensions(NMEA0183AISMsg, Length, Beam, PosRefStbd, PosRefBow) ) return false; // 260-289 | 132-161 | 30 Dimensions
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 6) ) return false; // 290-295 | 162-167 | 6 Spare
|
||||
if ( !NMEA0183AISMsg.InitAis() ) return false;
|
||||
int padBits=0;
|
||||
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
|
||||
if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ****************************************************************************
|
||||
// AIS ATON report (129041) -> Type 21: Position and status report for aids-to-navigation
|
||||
// PGN129041
|
||||
|
||||
bool SetAISMessage21(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Repeat, uint32_t UserID,
|
||||
double Latitude, double Longitude, bool Accuracy, bool RAIM,
|
||||
uint8_t Seconds, double Length, double Beam, double PositionReferenceStarboard,
|
||||
double PositionReferenceTrueNord, tN2kAISAtoNType Type, bool OffPositionIndicator,
|
||||
bool VirtualAtoNFlag, bool AssignedModeFlag, tN2kGNSStype GNSSType, uint8_t AtoNStatus,
|
||||
char * atonName ) {
|
||||
//
|
||||
NMEA0183AISMsg.ClearAIS();
|
||||
if ( !AddMessageType(NMEA0183AISMsg, 21) ) return false; // 0 - 5 | 6 Message Type -> Constant: 18
|
||||
if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false; // 6 - 7 | 2 Repeat Indicator: 0 = default; 3 = do not repeat any more
|
||||
if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false; // 8 - 37 | 30 MMSI
|
||||
if ( ! NMEA0183AISMsg.AddIntToPayloadBin(Type,5)) return false; // | 5 aid type
|
||||
//the name must be split:
|
||||
//if it's > 120 bits the rest goes to the last parameter
|
||||
if ( !NMEA0183AISMsg.AddEncodedCharToPayloadBin(atonName,120))
|
||||
return false; // | 120 name
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Accuracy) ) return false; // | 1 accuracy
|
||||
if ( !AddLongitude(NMEA0183AISMsg,Longitude)) return false; // | 28 lon
|
||||
if ( !AddLatitude(NMEA0183AISMsg,Latitude)) return false; // | 27 lat
|
||||
if ( !AddDimensions(NMEA0183AISMsg, Length, Beam,
|
||||
PositionReferenceStarboard, PositionReferenceTrueNord)) return false; // | 30 dim
|
||||
if ( !AddEPFDFixType(NMEA0183AISMsg,GNSSType)) return false; // | 4 fix type
|
||||
if ( !AddSeconds(NMEA0183AISMsg,Seconds)) return false; // | 6 second
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(OffPositionIndicator))
|
||||
return false; // | 1 off
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0,8)) return false; // | 8 reserverd
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(RAIM)) return false; // | 1 raim
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(VirtualAtoNFlag))
|
||||
return false; // | 1 virt
|
||||
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(AssignedModeFlag))
|
||||
return false; // | 1 assigned
|
||||
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0,1)) return false; // | 1 spare
|
||||
size_t l=strlen(atonName);
|
||||
if (l >=20){
|
||||
uint8_t bitlen=(l-20)*6;
|
||||
if (bitlen > 88) bitlen=88;
|
||||
if ( !NMEA0183AISMsg.AddEncodedCharToPayloadBin(atonName+20,bitlen)) return false; // | name
|
||||
}
|
||||
if ( !NMEA0183AISMsg.InitAis() ) return false;
|
||||
int padBits=0;
|
||||
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayload(padBits) ) ) return false;
|
||||
if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -325,7 +354,6 @@ bool AddIMONumber(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t &IMONumber) {
|
||||
// 120bit Name or Destination
|
||||
bool AddText(tNMEA0183AISMsg &NMEA0183AISMsg, char *FieldVal, uint8_t length) {
|
||||
uint8_t len = length/6;
|
||||
|
||||
if ( strlen(FieldVal) > len ) FieldVal[len] = 0;
|
||||
if ( !NMEA0183AISMsg.AddEncodedCharToPayloadBin(FieldVal, length) ) return false;
|
||||
return true;
|
||||
@@ -347,29 +375,26 @@ bool AddDimensions(tNMEA0183AISMsg &NMEA0183AISMsg, double Length, double Beam,
|
||||
uint16_t _PosRefStbd = 0;
|
||||
uint16_t _PosRefPort = 0;
|
||||
|
||||
if (PosRefBow < 0) PosRefBow=0; //could be N2kIsNA
|
||||
if ( PosRefBow <= 511.0 ) {
|
||||
_PosRefBow = round(PosRefBow);
|
||||
if ( PosRefBow >= 0.0 && PosRefBow <= 511.0 ) {
|
||||
_PosRefBow = ceil(PosRefBow);
|
||||
} else {
|
||||
_PosRefBow = 511;
|
||||
}
|
||||
if (PosRefStbd < 0 ) PosRefStbd=0; //could be N2kIsNA
|
||||
if (PosRefStbd <= 63.0 ) {
|
||||
_PosRefStbd = round(PosRefStbd);
|
||||
|
||||
if ( PosRefStbd >= 0.0 && PosRefStbd <= 63.0 ) {
|
||||
_PosRefStbd = ceil(PosRefStbd);
|
||||
} else {
|
||||
_PosRefStbd = 63;
|
||||
}
|
||||
|
||||
if ( !N2kIsNA(Length) ) {
|
||||
if (Length >= PosRefBow){
|
||||
_PosRefStern=round(Length - PosRefBow);
|
||||
}
|
||||
_PosRefStern = ceil( Length ) - _PosRefBow;
|
||||
if ( _PosRefStern < 0 ) _PosRefStern = 0;
|
||||
if ( _PosRefStern > 511 ) _PosRefStern = 511;
|
||||
}
|
||||
if ( !N2kIsNA(Beam) ) {
|
||||
if (Beam >= PosRefStbd){
|
||||
_PosRefPort = round( Beam - PosRefStbd);
|
||||
}
|
||||
_PosRefPort = ceil( Beam ) - _PosRefStbd;
|
||||
if ( _PosRefPort < 0 ) _PosRefPort = 0;
|
||||
if ( _PosRefPort > 63 ) _PosRefPort = 63;
|
||||
}
|
||||
|
||||
@@ -572,3 +597,5 @@ bool AddETADateTime(tNMEA0183AISMsg &NMEA0183AISMsg, uint16_t &ETAdate, double &
|
||||
if ( ! NMEA0183AISMsg.AddIntToPayloadBin(minute, 6) ) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -27,29 +27,21 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#ifndef _tNMEA0183AISMessages_H_
|
||||
#define _tNMEA0183AISMessages_H_
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <N2kTypes.h>
|
||||
#include <NMEA0183AISMsg.h>
|
||||
#include "NMEA0183AISMsg.h"
|
||||
#include <stddef.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#define MAX_SHIP_IN_VECTOR 200
|
||||
class ship {
|
||||
public:
|
||||
uint32_t _userID;
|
||||
std::string _shipName;
|
||||
|
||||
ship(uint32_t UserID, std::string ShipName) : _userID(UserID), _shipName(ShipName) {}
|
||||
};
|
||||
|
||||
|
||||
// Types 1, 2 and 3: Position Report Class A or B
|
||||
bool SetAISClassABMessage1(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType, uint8_t Repeat,
|
||||
uint32_t UserID, double Latitude, double Longitude, bool Accuracy, bool RAIM, uint8_t Seconds,
|
||||
double COG, double SOG, double Heading, double ROT, uint8_t NavStatus);
|
||||
uint32_t UserID, double Latitude, double Longitude, bool Accuracy, bool RAIM, uint8_t Seconds,
|
||||
double COG, double SOG, double Heading, double ROT, uint8_t NavStatus);
|
||||
|
||||
//*****************************************************************************
|
||||
// AIS Class A Static and Voyage Related Data Message Type 5
|
||||
@@ -57,14 +49,15 @@ bool SetAISClassAMessage5(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, ui
|
||||
uint32_t UserID, uint32_t IMONumber, char *Callsign, char *Name,
|
||||
uint8_t VesselType, double Length, double Beam, double PosRefStbd,
|
||||
double PosRefBow, uint16_t ETAdate, double ETAtime, double Draught,
|
||||
char *Destination, tN2kGNSStype GNSStype, uint8_t DTE );
|
||||
char *Destination, tN2kGNSStype GNSStype, uint8_t DTE,
|
||||
tN2kAISVersion AISversion);
|
||||
|
||||
//*****************************************************************************
|
||||
// AIS position report (class B 129039) -> Standard Class B CS Position Report Message Type 18 Part B
|
||||
bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat, uint32_t UserID,
|
||||
double Latitude, double Longitude, bool Accuracy, bool RAIM,
|
||||
uint8_t Seconds, double COG, double SOG, double Heading, tN2kAISUnit Unit,
|
||||
bool Display, bool DSC, bool Band, bool Msg22, bool Mode, bool State);
|
||||
double Latitude, double Longitude, bool Accuracy, bool RAIM,
|
||||
uint8_t Seconds, double COG, double SOG, double Heading, tN2kAISUnit Unit,
|
||||
bool Display, bool DSC, bool Band, bool Msg22, bool Mode, bool State);
|
||||
|
||||
//*****************************************************************************
|
||||
// Static Data Report Class B, Message Type 24
|
||||
@@ -73,11 +66,19 @@ bool SetAISClassBMessage24PartA(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Message
|
||||
|
||||
//*****************************************************************************
|
||||
// Static Data Report Class B, Message Type 24
|
||||
bool SetAISClassBMessage24(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat,
|
||||
bool SetAISClassBMessage24PartB(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat,
|
||||
uint32_t UserID, uint8_t VesselType, char *VendorID, char *Callsign,
|
||||
double Length, double Beam, double PosRefStbd, double PosRefBow, uint32_t MothershipID );
|
||||
|
||||
int numShips();
|
||||
//*****************************************************************************
|
||||
// Aton class 21
|
||||
bool SetAISMessage21(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Repeat, uint32_t UserID,
|
||||
double Latitude, double Longitude, bool Accuracy, bool RAIM,
|
||||
uint8_t Seconds, double Length, double Beam, double PositionReferenceStarboard,
|
||||
double PositionReferenceTrueNord, tN2kAISAtoNType Type, bool OffPositionIndicator,
|
||||
bool VirtualAtoNFlag, bool AssignedModeFlag, tN2kGNSStype GNSSType, uint8_t AtoNStatus,
|
||||
char * atonName );
|
||||
|
||||
inline int32_t aRoundToInt(double x) {
|
||||
return x >= 0
|
||||
? (int32_t) floor(x + 0.5)
|
||||
|
||||
@@ -25,7 +25,7 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "NMEA0183AISMsg.h"
|
||||
#include <NMEA0183Msg.h>
|
||||
#include <Arduino.h>
|
||||
//#include <Arduino.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
@@ -43,52 +43,37 @@ tNMEA0183AISMsg::tNMEA0183AISMsg() {
|
||||
//*****************************************************************************
|
||||
void tNMEA0183AISMsg::ClearAIS() {
|
||||
|
||||
PayloadBin[0]=0;
|
||||
Payload[0]=0;
|
||||
PayloadBin.reset();
|
||||
iAddPldBin=0;
|
||||
iAddPld=0;
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
// Add 6bit with no data.
|
||||
bool tNMEA0183AISMsg::AddEmptyFieldToPayloadBin(uint8_t iBits) {
|
||||
|
||||
if ( (iAddPldBin + iBits * 6) >= AIS_BIN_MAX_LEN ) return false; // Is there room for any data
|
||||
|
||||
for (uint8_t i=0;i<iBits;i++) {
|
||||
strncpy(PayloadBin+iAddPldBin, EmptyAISField, 6);
|
||||
iAddPldBin+=6;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
bool tNMEA0183AISMsg::AddIntToPayloadBin(int32_t ival, uint16_t countBits) {
|
||||
|
||||
if ( (iAddPldBin + countBits ) >= AIS_BIN_MAX_LEN ) return false; // Is there room for any data
|
||||
|
||||
AISBitSet bset(ival);
|
||||
bset = ival;
|
||||
|
||||
PayloadBin[iAddPldBin]=0;
|
||||
uint16_t iAdd=iAddPldBin;
|
||||
|
||||
for(int i = countBits-1; i >= 0 ; i--) {
|
||||
PayloadBin[iAdd] = bset[i]?'1':'0';
|
||||
PayloadBin[iAdd]=bset [i];
|
||||
iAdd++;
|
||||
}
|
||||
|
||||
iAddPldBin += countBits;
|
||||
PayloadBin[iAddPldBin]=0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ****************************************************************************
|
||||
bool tNMEA0183AISMsg::AddBoolToPayloadBin(bool &bval, uint8_t size) {
|
||||
int8_t iTemp;
|
||||
(bval == true)? iTemp = 1 : iTemp = 0;
|
||||
if ( ! AddIntToPayloadBin(iTemp, size) ) return false;
|
||||
//****************************************************************************
|
||||
bool tNMEA0183AISMsg::AddBoolToPayloadBin(bool &bval) {
|
||||
if ( (iAddPldBin + 1 ) >= AIS_BIN_MAX_LEN ) return false;
|
||||
PayloadBin[iAddPldBin]=bval;
|
||||
iAddPldBin++;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -99,13 +84,11 @@ bool tNMEA0183AISMsg::AddEncodedCharToPayloadBin(char *sval, size_t countBits) {
|
||||
|
||||
if ( (iAddPldBin + countBits ) >= AIS_BIN_MAX_LEN ) return false; // Is there room for any data
|
||||
|
||||
PayloadBin[iAddPldBin]=0;
|
||||
std::bitset<6> bs;
|
||||
char * ptr;
|
||||
const char * ptr;
|
||||
size_t len = strlen(sval); // e.g.: should be 7 for Callsign
|
||||
if ( len * 6 > countBits ) len = countBits / 6;
|
||||
|
||||
for (int i = 0; i<len; i++) {
|
||||
for (size_t i = 0; i<len; i++) {
|
||||
|
||||
ptr = strchr(AsciiChar, sval[i]);
|
||||
if ( ptr ) {
|
||||
@@ -117,37 +100,44 @@ bool tNMEA0183AISMsg::AddEncodedCharToPayloadBin(char *sval, size_t countBits) {
|
||||
AddIntToPayloadBin(0, 6);
|
||||
}
|
||||
}
|
||||
|
||||
PayloadBin[iAddPldBin+1]=0;
|
||||
|
||||
// fill up with "@", also covers empty sval
|
||||
if ( len * 6 < countBits ) {
|
||||
for (int i=0;i<(countBits/6-len);i++) {
|
||||
for (size_t i=0;i<(countBits/6-len);i++) {
|
||||
AddIntToPayloadBin(0, 6);
|
||||
}
|
||||
}
|
||||
PayloadBin[iAddPldBin]=0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// *****************************************************************************
|
||||
bool tNMEA0183AISMsg::ConvertBinaryAISPayloadBinToAscii(const char *payloadbin) {
|
||||
uint16_t len;
|
||||
|
||||
len = strlen( payloadbin ) / 6; // 28
|
||||
//*****************************************************************************
|
||||
template <unsigned int S>
|
||||
int tNMEA0183AISMsg::ConvertBinaryAISPayloadBinToAscii(std::bitset<S> &src,uint16_t maxSize,uint16_t bitSize,uint16_t stoffset) {
|
||||
Payload[0]='\0';
|
||||
uint16_t slen=maxSize;
|
||||
if (stoffset >= slen) return 0;
|
||||
slen-=stoffset;
|
||||
uint16_t bitLen=bitSize > 0?bitSize:slen;
|
||||
uint16_t len= bitLen / 6;
|
||||
if ((len * 6) < bitLen) len+=1;
|
||||
uint16_t padBits=0;
|
||||
uint32_t offset;
|
||||
char s[7];
|
||||
std::bitset<6> s;
|
||||
uint8_t dec;
|
||||
int i;
|
||||
for ( i=0; i<len; i++ ) {
|
||||
offset = i * 6;
|
||||
int k = 0;
|
||||
for (int j=offset; j<offset+6; j++ ) {
|
||||
s[k] = payloadbin[j];
|
||||
k++;
|
||||
int k = 5;
|
||||
for (uint32_t j=offset; j<offset+6; j++ ) {
|
||||
if (j < slen){
|
||||
s[k] = src[stoffset+j];
|
||||
}
|
||||
else{
|
||||
s[k] = 0;
|
||||
padBits++;
|
||||
}
|
||||
k--;
|
||||
}
|
||||
s[k]=0;
|
||||
dec = strtoull (s, NULL, 2); //binToDec
|
||||
dec = s.to_ulong();
|
||||
|
||||
if (dec < 40 ) dec += 48;
|
||||
else dec += 56;
|
||||
@@ -156,142 +146,56 @@ bool tNMEA0183AISMsg::ConvertBinaryAISPayloadBinToAscii(const char *payloadbin)
|
||||
}
|
||||
Payload[i]=0;
|
||||
|
||||
return true;
|
||||
return padBits;
|
||||
}
|
||||
|
||||
void tNMEA0183AISMsg::SetChannelAndTalker(bool channelA,bool own){
|
||||
channel[0]=channelA?'A':'B';
|
||||
strcpy(talker,own?"VDO":"VDM");
|
||||
}
|
||||
|
||||
//********************** BUILD 2-parted AIS Sentences ************************
|
||||
const tNMEA0183AISMsg& tNMEA0183AISMsg::BuildMsg5Part1(tNMEA0183AISMsg &AISMsg) {
|
||||
|
||||
Init("VDM", "AI", '!');
|
||||
AddStrField("2");
|
||||
AddStrField("1");
|
||||
AddStrField("5");
|
||||
AddStrField("A");
|
||||
AddStrField( GetPayloadType5_Part1() );
|
||||
AddStrField("0");
|
||||
|
||||
return AISMsg;
|
||||
bool tNMEA0183AISMsg::InitAis(int max,int number,int sequence){
|
||||
if ( !Init(talker,"AI", '!') ) return false;
|
||||
if ( !AddUInt32Field(max) ) return false;
|
||||
if ( !AddUInt32Field(number) ) return false;
|
||||
if (sequence >= 0){
|
||||
if ( !AddUInt32Field(sequence) ) return false;
|
||||
}
|
||||
else{
|
||||
if ( !AddEmptyField() ) return false;
|
||||
}
|
||||
if ( !AddStrField(channel) ) return false;
|
||||
return true;
|
||||
}
|
||||
bool tNMEA0183AISMsg::BuildMsg5Part1() {
|
||||
if ( iAddPldBin != 424 ) return false;
|
||||
InitAis(2,1,5);
|
||||
int padBits=0;
|
||||
AddStrField( GetPayload(padBits,0,336));
|
||||
AddUInt32Field(padBits);
|
||||
return true;
|
||||
}
|
||||
|
||||
const tNMEA0183AISMsg& tNMEA0183AISMsg::BuildMsg5Part2(tNMEA0183AISMsg &AISMsg) {
|
||||
|
||||
Init("VDM", "AI", '!');
|
||||
AddStrField("2");
|
||||
AddStrField("2");
|
||||
AddStrField("5");
|
||||
AddStrField("A");
|
||||
AddStrField( GetPayloadType5_Part2() );
|
||||
AddStrField("2"); // Message 5, Part 2 has always 2 Padding Zeros
|
||||
|
||||
return AISMsg;
|
||||
bool tNMEA0183AISMsg::BuildMsg5Part2() {
|
||||
if ( iAddPldBin != 424 ) return false;
|
||||
InitAis(2,2,5);
|
||||
int padBits=0;
|
||||
AddStrField( GetPayload(padBits,336,88) );
|
||||
AddUInt32Field(padBits);
|
||||
return true;
|
||||
}
|
||||
|
||||
const tNMEA0183AISMsg& tNMEA0183AISMsg::BuildMsg24PartA(tNMEA0183AISMsg &AISMsg) {
|
||||
|
||||
Init("VDM", "AI", '!');
|
||||
AddStrField("1");
|
||||
AddStrField("1");
|
||||
AddEmptyField();
|
||||
AddStrField("A");
|
||||
AddStrField( GetPayloadType24_PartA() );
|
||||
AddStrField("0");
|
||||
|
||||
return AISMsg;
|
||||
}
|
||||
|
||||
const tNMEA0183AISMsg& tNMEA0183AISMsg::BuildMsg24PartB(tNMEA0183AISMsg &AISMsg) {
|
||||
|
||||
Init("VDM", "AI", '!');
|
||||
AddStrField("1");
|
||||
AddStrField("1");
|
||||
AddEmptyField();
|
||||
AddStrField("A");
|
||||
AddStrField( GetPayloadType24_PartB() );
|
||||
AddStrField("0"); // Message 24, both parts have always Zero Padding
|
||||
|
||||
return AISMsg;
|
||||
}
|
||||
|
||||
//******************************* AIS PAYLOADS *********************************
|
||||
//******************************************************************************
|
||||
// get converted Payload for Message 1, 2, 3 & 18, always Length 168
|
||||
const char *tNMEA0183AISMsg::GetPayload() {
|
||||
|
||||
uint16_t lenbin = strlen( PayloadBin);
|
||||
if ( lenbin != 168 ) return nullptr;
|
||||
|
||||
if ( !ConvertBinaryAISPayloadBinToAscii( PayloadBin ) ) return nullptr;
|
||||
const char *tNMEA0183AISMsg::GetPayloadFix(int &padBits,uint16_t fixLen){
|
||||
uint16_t lenbin = iAddPldBin;
|
||||
if ( lenbin != fixLen ) return nullptr;
|
||||
return GetPayload(padBits,0,0);
|
||||
}
|
||||
const char *tNMEA0183AISMsg::GetPayload(int &padBits,uint16_t offset,uint16_t bitLen) {
|
||||
padBits=ConvertBinaryAISPayloadBinToAscii<AIS_BIN_MAX_LEN>(PayloadBin,iAddPldBin, bitLen,offset );
|
||||
return Payload;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
// get converted Part 1 of Payload for Message 5
|
||||
const char *tNMEA0183AISMsg::GetPayloadType5_Part1() {
|
||||
|
||||
uint16_t lenbin = strlen( PayloadBin);
|
||||
if ( lenbin != 424 ) return nullptr;
|
||||
|
||||
char to[337];
|
||||
strncpy(to, PayloadBin, 336); // First Part is always 336 Length
|
||||
to[336]=0;
|
||||
|
||||
if ( !ConvertBinaryAISPayloadBinToAscii( to ) ) return nullptr;
|
||||
|
||||
return Payload;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
// get converted Part 2 of Payload for Message 5
|
||||
const char *tNMEA0183AISMsg::GetPayloadType5_Part2() {
|
||||
|
||||
uint16_t lenbin = strlen( PayloadBin);
|
||||
if ( lenbin != 424 ) return nullptr;
|
||||
|
||||
lenbin = 88; // Second Part is always 424 - 336 + 2 padding Zeros in Length
|
||||
char to[91];
|
||||
strncpy(to, PayloadBin + 336, lenbin);
|
||||
to[88]='0'; to[89]='0'; to[90]=0;
|
||||
|
||||
if ( !ConvertBinaryAISPayloadBinToAscii( to ) ) return nullptr;
|
||||
return Payload;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
// get converted Part A of Payload for Message 24
|
||||
// Bit 0.....167, len 168
|
||||
// In PayloadBin is Part A and Part B chained together with Length 296
|
||||
const char *tNMEA0183AISMsg::GetPayloadType24_PartA() {
|
||||
uint16_t lenbin = strlen( PayloadBin);
|
||||
if ( lenbin != 296 ) return nullptr; // too short for Part A
|
||||
|
||||
char to[169]; // Part A has Length 168
|
||||
*to = '\0';
|
||||
for (int i=0; i<168; i++){
|
||||
to[i] = PayloadBin[i];
|
||||
}
|
||||
to[168]=0;
|
||||
if ( !ConvertBinaryAISPayloadBinToAscii( to ) ) return nullptr;
|
||||
return Payload;
|
||||
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
// get converted Part B of Payload for Message 24
|
||||
// Bit 0.....38 + bit39='1' (part number) + bit 168........295 296='\0' of total PayloadBin
|
||||
// binary part B: len 40 + 128 = len 168
|
||||
const char *tNMEA0183AISMsg::GetPayloadType24_PartB() {
|
||||
uint16_t lenbin = strlen( PayloadBin);
|
||||
if ( lenbin != 296 ) return nullptr; // too short for Part B
|
||||
char to[169]; // Part B has Length 168
|
||||
*to = '\0';
|
||||
for (int i=0; i<39; i++){
|
||||
to[i] = PayloadBin[i];
|
||||
}
|
||||
to[39] = 49; // part number 1
|
||||
for (int i=40; i<168; i++) {
|
||||
to[i] = PayloadBin[i+128];
|
||||
}
|
||||
to[168]=0;
|
||||
if ( !ConvertBinaryAISPayloadBinToAscii( to ) ) return nullptr;
|
||||
return Payload;
|
||||
}
|
||||
|
||||
@@ -45,43 +45,48 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#define BITSET_LENGTH 120
|
||||
|
||||
typedef std::bitset<BITSET_LENGTH> AISBitSet;
|
||||
class tNMEA0183AISMsg : public tNMEA0183Msg {
|
||||
|
||||
protected: // AIS-NMEA
|
||||
std::bitset<BITSET_LENGTH> bset;
|
||||
static const char *EmptyAISField; // 6bits 0 not used yet.....
|
||||
static const char *AsciChar;
|
||||
|
||||
uint16_t iAddPldBin;
|
||||
char Payload[AIS_MSG_MAX_LEN];
|
||||
uint8_t iAddPld;
|
||||
|
||||
char talker[4]="VDM";
|
||||
char channel[2]="A";
|
||||
std::bitset<AIS_BIN_MAX_LEN> PayloadBin;
|
||||
public:
|
||||
char PayloadBin[AIS_BIN_MAX_LEN];
|
||||
char PayloadBin2[AIS_BIN_MAX_LEN];
|
||||
// Clear message
|
||||
void ClearAIS();
|
||||
|
||||
public:
|
||||
tNMEA0183AISMsg();
|
||||
const char *GetPayload();
|
||||
const char *GetPayloadType5_Part1();
|
||||
const char *GetPayloadType5_Part2();
|
||||
const char *GetPayloadType24_PartA();
|
||||
const char *GetPayloadType24_PartB();
|
||||
const char *GetPayloadBin() const { return PayloadBin; }
|
||||
const char *GetPayloadFix(int &padBits,uint16_t fixLen=168);
|
||||
const char *GetPayload(int &padBits,uint16_t offset=0,uint16_t bitLen=0);
|
||||
|
||||
const tNMEA0183AISMsg& BuildMsg5Part1(tNMEA0183AISMsg &AISMsg);
|
||||
const tNMEA0183AISMsg& BuildMsg5Part2(tNMEA0183AISMsg &AISMsg);
|
||||
const tNMEA0183AISMsg& BuildMsg24PartA(tNMEA0183AISMsg &AISMsg);
|
||||
const tNMEA0183AISMsg& BuildMsg24PartB(tNMEA0183AISMsg &AISMsg);
|
||||
bool BuildMsg5Part1();
|
||||
bool BuildMsg5Part2();
|
||||
bool InitAis(int max=1,int number=1,int sequence=-1);
|
||||
|
||||
// Generally Used
|
||||
bool AddIntToPayloadBin(int32_t ival, uint16_t countBits);
|
||||
bool AddBoolToPayloadBin(bool &bval, uint8_t size);
|
||||
bool AddBoolToPayloadBin(bool &bval);
|
||||
bool AddEncodedCharToPayloadBin(char *sval, size_t Length);
|
||||
bool AddEmptyFieldToPayloadBin(uint8_t iBits);
|
||||
bool ConvertBinaryAISPayloadBinToAscii(const char *payloadbin);
|
||||
/**
|
||||
* @param channelA - if set A, otherwise B
|
||||
* @param own - if set VDO, else VDM
|
||||
*/
|
||||
void SetChannelAndTalker(bool channelA,bool own=false);
|
||||
/**
|
||||
* convert the payload to ascii
|
||||
* return the number of padding bits
|
||||
* @param bitSize the number of bits to be used, 0 - use all bits
|
||||
*/
|
||||
template <unsigned int SZ>
|
||||
int ConvertBinaryAISPayloadBinToAscii(std::bitset<SZ> &src,uint16_t maxSize, uint16_t bitSize,uint16_t offset=0);
|
||||
|
||||
// AIS Helper functions
|
||||
protected:
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# NMEA2000 -> NMEA0183 AIS converter v1.0.0
|
||||
# NMEA2000 to NMEA0183 AIS Converter
|
||||
|
||||
Import from https://github.com/ronzeiller/NMEA0183-AIS
|
||||
|
||||
NMEA0183 AIS library © Ronnie Zeiller, www.zeiller.eu
|
||||
|
||||
Addendum for NMEA2000 and NMEA0183 Library from Timo Lappalainen https://github.com/ttlappalainen
|
||||
|
||||
to get NMEA0183 AIS data from N2k-bus
|
||||
|
||||
## Conversions:
|
||||
|
||||
@@ -15,6 +15,33 @@ Addendum for NMEA2000 and NMEA0183 Library from Timo Lappalainen https://github.
|
||||
- NMEA2000 PGN 129809 => AIS Class B "CS" Static Data Report, making a list of UserID (MMSI) and Ship Names used for Message 24 Part A
|
||||
- NMEA2000 PGN 129810 => AIS Class B "CS" Static Data Report, Message 24 Part A+B
|
||||
|
||||
### Versions
|
||||
1.0.6 2024-03-25
|
||||
- fixed to work with Timo´s NMEA2000 v4.21.3
|
||||
|
||||
1.0.5 2023-12-02
|
||||
- removed VDO remote print statements
|
||||
|
||||
1.0.4 2023-12-02
|
||||
- merged @Isoltero master with fixed memory over run, added VDO remote print statements Thanks to Luis Soltero
|
||||
- fixed example, thanks to @arduinomnomnom
|
||||
|
||||
1.0.3 2022-05-01
|
||||
- Update Examples: AISTransceiverInformation in ParseN2kPGN129039 for changes in NMEA2000 library: https://github.com/ttlappalainen/NMEA2000
|
||||
|
||||
|
||||
1.0.2 2022-04-30
|
||||
- bugfix: malloc without free. Thanks to Luis Soltero (Issue https://github.com/ronzeiller/NMEA0183-AIS/issues/3)
|
||||
|
||||
1.0.1 2022-03-15
|
||||
- bugfix: buffer overrun missing space for termination. Thanks to Luis Soltero (Issue https://github.com/ronzeiller/NMEA0183-AIS/issues/2)
|
||||
|
||||
2020-12-25
|
||||
- corrected Navigational Status 0. Thanks to Li-Ren (Issue https://github.com/ronzeiller/NMEA0183-AIS/issues/1)
|
||||
|
||||
1.0.0 2019-11-24
|
||||
- initial upload
|
||||
|
||||
### Remarks
|
||||
1. Message Type could be set to 1 or 3 (identical messages) on demand
|
||||
2. Maneuver Indicator (not part of NMEA2000 PGN 129038) => will be set to 0 (default)
|
||||
@@ -33,17 +60,14 @@ To use this library you need also:
|
||||
|
||||
## License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
MIT license
|
||||
|
||||
Copyright (c) 2019-2022 Ronnie Zeiller, www.zeiller.eu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
#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, "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 in calibration list", instance.c_str());
|
||||
return;
|
||||
} else if (!boatDataValue->valid) { // no valid boat data value, so we don't want to apply calibration data
|
||||
calibMap[instance].isCalibrated = false;
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,34 +0,0 @@
|
||||
// Functions lib for data instance calibration
|
||||
|
||||
#ifndef _BOATDATACALIBRATION_H
|
||||
#define _BOATDATACALIBRATION_H
|
||||
|
||||
// #include "Pagedata.h"
|
||||
#include "GwApi.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
|
||||
217
lib/obp60task/ConfigMenu.cpp
Normal file
217
lib/obp60task/ConfigMenu.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
Menu system for online configuration
|
||||
|
||||
A menu consists of a list of menuitems.
|
||||
|
||||
Graphical representation is stored:
|
||||
upper left corner: x, y
|
||||
bounding box:
|
||||
A menu consists of three columns
|
||||
- menu text, if selected highlighted
|
||||
- menu value with optional unit
|
||||
- menu description or additional data for value
|
||||
|
||||
*/
|
||||
#include "ConfigMenu.h"
|
||||
|
||||
ConfigMenuItem::ConfigMenuItem(String itemtype, String itemlabel, uint16_t itemval, String itemunit) {
|
||||
if (! (itemtype == "int" or itemtype == "bool")) {
|
||||
valtype = "int";
|
||||
} else {
|
||||
valtype = itemtype;
|
||||
}
|
||||
label = itemlabel;
|
||||
min = 0;
|
||||
max = std::numeric_limits<uint16_t>::max();
|
||||
value = itemval;
|
||||
unit = itemunit;
|
||||
step = 1;
|
||||
}
|
||||
|
||||
void ConfigMenuItem::setRange(uint16_t valmin, uint16_t valmax, std::vector<uint16_t> valsteps) {
|
||||
min = valmin;
|
||||
max = valmax;
|
||||
steps = valsteps;
|
||||
step = steps[0];
|
||||
};
|
||||
|
||||
bool ConfigMenuItem::checkRange(uint16_t checkval) {
|
||||
return (checkval >= min) and (checkval <= max);
|
||||
}
|
||||
|
||||
String ConfigMenuItem::getLabel() {
|
||||
return label;
|
||||
};
|
||||
|
||||
uint16_t ConfigMenuItem::getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
bool ConfigMenuItem::setValue(uint16_t newval) {
|
||||
if (valtype == "int") {
|
||||
if (newval >= min and newval <= max) {
|
||||
value = newval;
|
||||
return true;
|
||||
}
|
||||
return false; // out of range
|
||||
} else if (valtype == "bool") {
|
||||
value = (newval != 0) ? 1 : 0;
|
||||
return true;
|
||||
}
|
||||
return false; // invalid type
|
||||
};
|
||||
|
||||
void ConfigMenuItem::incValue() {
|
||||
// increase value by step
|
||||
if (valtype == "int") {
|
||||
if (value + step < max) {
|
||||
value += step;
|
||||
} else {
|
||||
value = max;
|
||||
}
|
||||
} else if (valtype == "bool") {
|
||||
value = !value;
|
||||
}
|
||||
};
|
||||
|
||||
void ConfigMenuItem::decValue() {
|
||||
// decrease value by step
|
||||
if (valtype == "int") {
|
||||
if (value - step > min) {
|
||||
value -= step;
|
||||
} else {
|
||||
value = min;
|
||||
}
|
||||
} else if (valtype == "bool") {
|
||||
value = !value;
|
||||
}
|
||||
};
|
||||
|
||||
String ConfigMenuItem::getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
uint16_t ConfigMenuItem::getStep() {
|
||||
return step;
|
||||
}
|
||||
|
||||
void ConfigMenuItem::setStep(uint16_t newstep) {
|
||||
if (std::find(steps.begin(), steps.end(), newstep) == steps.end()) {
|
||||
return; // invalid step: not in list of possible steps
|
||||
}
|
||||
step = newstep;
|
||||
}
|
||||
|
||||
int8_t ConfigMenuItem::getPos() {
|
||||
return position;
|
||||
};
|
||||
|
||||
void ConfigMenuItem::setPos(int8_t newpos) {
|
||||
position = newpos;
|
||||
};
|
||||
|
||||
String ConfigMenuItem::getType() {
|
||||
return valtype;
|
||||
}
|
||||
|
||||
ConfigMenu::ConfigMenu(String menutitle, uint16_t menu_x, uint16_t menu_y) {
|
||||
title = menutitle;
|
||||
x = menu_x;
|
||||
y = menu_y;
|
||||
};
|
||||
|
||||
ConfigMenuItem* ConfigMenu::addItem(String key, String label, String valtype, uint16_t val, String valunit) {
|
||||
if (items.find(key) != items.end()) {
|
||||
// duplicate keys not allowed
|
||||
return nullptr;
|
||||
}
|
||||
ConfigMenuItem *itm = new ConfigMenuItem(valtype, label, val, valunit);
|
||||
items.insert(std::pair<String, ConfigMenuItem*>(key, itm));
|
||||
// Append key to index, index starting with 0
|
||||
int8_t ix = items.size() - 1;
|
||||
index[ix] = key;
|
||||
itm->setPos(ix);
|
||||
return itm;
|
||||
};
|
||||
|
||||
void ConfigMenu::setItemDimension(uint16_t itemwidth, uint16_t itemheight) {
|
||||
w = itemwidth;
|
||||
h = itemheight;
|
||||
};
|
||||
|
||||
void ConfigMenu::setItemActive(String key) {
|
||||
if (items.find(key) != items.end()) {
|
||||
activeitem = items[key]->getPos();
|
||||
} else {
|
||||
activeitem = -1;
|
||||
}
|
||||
};
|
||||
|
||||
int8_t ConfigMenu::getActiveIndex() {
|
||||
return activeitem;
|
||||
}
|
||||
|
||||
ConfigMenuItem* ConfigMenu::getActiveItem() {
|
||||
if (activeitem < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
return items[index[activeitem]];
|
||||
};
|
||||
|
||||
ConfigMenuItem* ConfigMenu::getItemByIndex(uint8_t ix) {
|
||||
if (ix > index.size() - 1) {
|
||||
return nullptr;
|
||||
}
|
||||
return items[index[ix]];
|
||||
};
|
||||
|
||||
ConfigMenuItem* ConfigMenu::getItemByKey(String key) {
|
||||
if (items.find(key) == items.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return items[key];
|
||||
};
|
||||
|
||||
uint8_t ConfigMenu::getItemCount() {
|
||||
return items.size();
|
||||
};
|
||||
|
||||
void ConfigMenu::goPrev() {
|
||||
if (activeitem == 0) {
|
||||
activeitem = items.size() - 1;
|
||||
} else {
|
||||
activeitem--;
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigMenu::goNext() {
|
||||
if (activeitem == items.size() - 1) {
|
||||
activeitem = 0;
|
||||
} else {
|
||||
activeitem++;
|
||||
}
|
||||
}
|
||||
|
||||
Point ConfigMenu::getXY() {
|
||||
return {static_cast<double>(x), static_cast<double>(y)};
|
||||
}
|
||||
|
||||
Rect ConfigMenu::getRect() {
|
||||
return {static_cast<double>(x), static_cast<double>(y),
|
||||
static_cast<double>(w), static_cast<double>(h)};
|
||||
}
|
||||
|
||||
Rect ConfigMenu::getItemRect(int8_t index) {
|
||||
return {static_cast<double>(x), static_cast<double>(y + index * h),
|
||||
static_cast<double>(w), static_cast<double>(h)};
|
||||
}
|
||||
|
||||
void ConfigMenu::setCallback(void (*callback)()) {
|
||||
fptrCallback = callback;
|
||||
}
|
||||
|
||||
void ConfigMenu::storeValues() {
|
||||
if (fptrCallback) {
|
||||
fptrCallback();
|
||||
}
|
||||
}
|
||||
67
lib/obp60task/ConfigMenu.h
Normal file
67
lib/obp60task/ConfigMenu.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "Graphics.h" // for Point and Rect
|
||||
|
||||
class ConfigMenuItem {
|
||||
private:
|
||||
String label;
|
||||
uint16_t value;
|
||||
String unit;
|
||||
String desc; // optional data to display
|
||||
String valtype; // "int" | "bool" -> TODO "list"
|
||||
uint16_t min;
|
||||
uint16_t max;
|
||||
std::vector<uint16_t> steps;
|
||||
uint16_t step;
|
||||
int8_t position; // counted fom 0
|
||||
|
||||
public:
|
||||
ConfigMenuItem(String itemtype, String itemlabel, uint16_t itemval, String itemunit);
|
||||
void setRange(uint16_t valmin, uint16_t valmax, std::vector<uint16_t> steps);
|
||||
bool checkRange(uint16_t checkval);
|
||||
String getLabel();
|
||||
uint16_t getValue();
|
||||
bool setValue(uint16_t newval);
|
||||
void incValue();
|
||||
void decValue();
|
||||
String getUnit();
|
||||
uint16_t getStep();
|
||||
void setStep(uint16_t newstep);
|
||||
int8_t getPos();
|
||||
void setPos(int8_t newpos);
|
||||
String getType();
|
||||
};
|
||||
|
||||
class ConfigMenu {
|
||||
private:
|
||||
String title;
|
||||
std::map <String,ConfigMenuItem*> items;
|
||||
std::map <uint8_t,String> index;
|
||||
int8_t activeitem = -1; // refers to position of item
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
uint16_t w;
|
||||
uint16_t h;
|
||||
void (*fptrCallback)();
|
||||
|
||||
public:
|
||||
ConfigMenu(String title, uint16_t menu_x, uint16_t menu_y);
|
||||
ConfigMenuItem* addItem(String key, String label, String valtype, uint16_t val, String valunit);
|
||||
void setItemDimension(uint16_t itemwidth, uint16_t itemheight);
|
||||
int8_t getActiveIndex();
|
||||
void setItemActive(String key);
|
||||
ConfigMenuItem* getActiveItem();
|
||||
ConfigMenuItem* getItemByIndex(uint8_t index);
|
||||
ConfigMenuItem* getItemByKey(String key);
|
||||
uint8_t getItemCount();
|
||||
void goPrev();
|
||||
void goNext();
|
||||
Point getXY();
|
||||
Rect getRect();
|
||||
Rect getItemRect(int8_t index);
|
||||
void setCallback(void (*callback)());
|
||||
void storeValues();
|
||||
};
|
||||
@@ -22,9 +22,11 @@ static uint8_t mulcolor(uint8_t f1, uint8_t f2){
|
||||
}
|
||||
|
||||
Color setBrightness(const Color &color,uint8_t brightness){
|
||||
if (brightness > 100) brightness = 100;
|
||||
|
||||
uint16_t br255=brightness*255;
|
||||
br255=br255/100;
|
||||
//very simple for now
|
||||
//Very simple for now
|
||||
Color rt=color;
|
||||
rt.g=mulcolor(rt.g,br255);
|
||||
rt.b=mulcolor(rt.b,br255);
|
||||
|
||||
@@ -57,7 +57,7 @@ GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> & getdisplay(){r
|
||||
#endif
|
||||
|
||||
// Horter I2C moduls
|
||||
PCF8574 pcf8574_Out(PCF8574_I2C_ADDR1); // First digital output modul PCF8574 from Horter
|
||||
PCF8574 pcf8574_Modul1(PCF8574_I2C_ADDR1); // First digital IO modul PCF8574 from Horter
|
||||
|
||||
// FRAM
|
||||
Adafruit_FRAM_I2C fram;
|
||||
@@ -89,8 +89,8 @@ void hardwareInit(GwApi *api)
|
||||
Wire.begin();
|
||||
// Init PCF8574 digital outputs
|
||||
Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz
|
||||
if(pcf8574_Out.begin()){ // Initialize PCF8574
|
||||
pcf8574_Out.write8(255); // Clear all outputs
|
||||
if(pcf8574_Modul1.begin()){ // Initialize PCF8574
|
||||
pcf8574_Modul1.write8(255); // Clear all outputs (low activ)
|
||||
}
|
||||
Wire.setClock(I2C_SPEED); // Set I2C clock on 100 kHz
|
||||
fram = Adafruit_FRAM_I2C();
|
||||
@@ -193,12 +193,25 @@ void powerInit(String powermode) {
|
||||
}
|
||||
}
|
||||
|
||||
void setPCF8574PortPin(uint pin, uint8_t value){
|
||||
Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz
|
||||
if(pcf8574_Out.begin()){ // Check available and initialize PCF8574
|
||||
pcf8574_Out.write(pin, value); // Toggle pin
|
||||
}
|
||||
Wire.setClock(I2C_SPEED); // Set I2C clock on 100 kHz
|
||||
void setPCF8574PortPinModul1(uint8_t pin, uint8_t value)
|
||||
{
|
||||
static bool firstRunFinished;
|
||||
static uint8_t port1; // Retained data for port bits
|
||||
// If fisrt run then set all outputs to low
|
||||
if(firstRunFinished == false){
|
||||
port1 = 255; // Low active
|
||||
firstRunFinished = true;
|
||||
}
|
||||
if (pin > 7) return;
|
||||
Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz for longer wires
|
||||
// Set bit
|
||||
if (pcf8574_Modul1.begin(port1)) // Check module availability and start it
|
||||
{
|
||||
if (value == LOW) port1 &= ~(1 << pin); // Set bit
|
||||
else port1 |= (1 << pin);
|
||||
pcf8574_Modul1.write8(port1); // Write byte
|
||||
}
|
||||
Wire.setClock(I2C_SPEED); // Set I2C clock on 100 kHz
|
||||
}
|
||||
|
||||
|
||||
@@ -318,6 +331,40 @@ void toggleBacklightLED(uint brightness, const Color &color){
|
||||
ledTaskData->setLedData(current);
|
||||
}
|
||||
|
||||
void stepsBacklightLED(uint brightness, const Color &color){
|
||||
static uint step = 0;
|
||||
uint actBrightness = 0;
|
||||
// Different brightness steps
|
||||
if(step == 0){
|
||||
actBrightness = brightness; // 100% from brightess
|
||||
statusBacklightLED = true;
|
||||
}
|
||||
if(step == 1){
|
||||
actBrightness = brightness * 0.5; // 50% from brightess
|
||||
statusBacklightLED = true;
|
||||
}
|
||||
if(step == 2){
|
||||
actBrightness = brightness * 0.2; // 20% from brightess
|
||||
statusBacklightLED = true;
|
||||
}
|
||||
if(step == 3){
|
||||
actBrightness = 0; // 0%
|
||||
statusBacklightLED = false;
|
||||
}
|
||||
if(actBrightness < 5){ // Limiter if values too low
|
||||
actBrightness = 5;
|
||||
}
|
||||
step = step + 1; // Increment step counter
|
||||
if(step == 4){ // Reset counter
|
||||
step = 0;
|
||||
}
|
||||
if (ledTaskData == nullptr) return;
|
||||
Color nv=setBrightness(statusBacklightLED?color:COLOR_BLACK,actBrightness);
|
||||
LedInterface current=ledTaskData->getLedData();
|
||||
current.setBacklight(nv);
|
||||
ledTaskData->setLedData(current);
|
||||
}
|
||||
|
||||
void setFlashLED(bool status){
|
||||
if (ledTaskData == nullptr) return;
|
||||
Color c=status?COLOR_RED:COLOR_BLACK;
|
||||
@@ -428,6 +475,30 @@ std::vector<String> wordwrap(String &line, uint16_t maxwidth) {
|
||||
return lines;
|
||||
}
|
||||
|
||||
// Helper function to just get the exact width of a string
|
||||
uint16_t getStringPixelWidth(const char* str, const GFXfont* font) {
|
||||
int16_t minx = INT16_MAX;
|
||||
int16_t maxx = INT16_MIN;
|
||||
int16_t cursor_x = 0;
|
||||
while (*str) {
|
||||
char c = *str++;
|
||||
if (c < font->first || c > font->last) {
|
||||
continue;
|
||||
}
|
||||
GFXglyph* glyph = &font->glyph[c - font->first];
|
||||
if (glyph->width > 0) {
|
||||
int16_t glyphStart = cursor_x + glyph->xOffset;
|
||||
int16_t glyphEnd = glyphStart + glyph->width;
|
||||
if (glyphStart < minx) minx = glyphStart;
|
||||
if (glyphEnd > maxx) maxx = glyphEnd;
|
||||
}
|
||||
cursor_x += glyph->xAdvance;
|
||||
}
|
||||
if (minx > maxx)
|
||||
return 0;
|
||||
return maxx - minx;
|
||||
}
|
||||
|
||||
// Draw centered text
|
||||
void drawTextCenter(int16_t cx, int16_t cy, String text) {
|
||||
int16_t x1, y1;
|
||||
@@ -437,6 +508,27 @@ void drawTextCenter(int16_t cx, int16_t cy, String text) {
|
||||
getdisplay().print(text);
|
||||
}
|
||||
|
||||
// Draw centered botton with centered text
|
||||
void drawButtonCenter(int16_t cx, int16_t cy, int8_t sx, int8_t sy, String text, uint16_t fg, uint16_t bg, bool inverted) {
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
uint16_t color;
|
||||
|
||||
getdisplay().getTextBounds(text, cx, cy, &x1, &y1, &w, &h); // Find text center
|
||||
getdisplay().setCursor(cx - w/2, cy + h/2); // Set cursor to center
|
||||
//getdisplay().drawPixel(cx, cy, fg); // Debug pixel for center position
|
||||
if (inverted) {
|
||||
getdisplay().fillRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button
|
||||
getdisplay().setTextColor(bg);
|
||||
getdisplay().print(text); // Draw text
|
||||
}
|
||||
else{
|
||||
getdisplay().drawRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button
|
||||
getdisplay().setTextColor(fg);
|
||||
getdisplay().print(text); // Draw text
|
||||
}
|
||||
}
|
||||
|
||||
// Draw right aligned text
|
||||
void drawTextRalign(int16_t x, int16_t y, String text) {
|
||||
int16_t x1, y1;
|
||||
@@ -570,20 +662,19 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa
|
||||
// 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_Bold8pt8b);
|
||||
getdisplay().setCursor(230, 15);
|
||||
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);
|
||||
time_t tv = mktime(&commonData.data.rtcTime) + (int)(commonData.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");
|
||||
getdisplay().print(commonData.tz == 0 ? "UTC" : "LOT");
|
||||
} else {
|
||||
drawTextRalign(396, 15, "RTC invalid");
|
||||
}
|
||||
@@ -598,7 +689,7 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa
|
||||
getdisplay().print(" ");
|
||||
getdisplay().print(actdate);
|
||||
getdisplay().print(" ");
|
||||
getdisplay().print(tz == 0 ? "UTC" : "LOT");
|
||||
getdisplay().print(commonData.tz == 0 ? "UTC" : "LOT");
|
||||
}
|
||||
else{
|
||||
if(commonData.config->getBool(commonData.config->useSimuData) == true){
|
||||
|
||||
@@ -89,13 +89,14 @@ uint8_t getLastPage();
|
||||
void hardwareInit(GwApi *api);
|
||||
void powerInit(String powermode);
|
||||
|
||||
void setPCF8574PortPin(uint pin, uint8_t value);// Set PCF8574 port pin
|
||||
void setPCF8574PortPinModul1(uint8_t pin, uint8_t value);// Set PCF8574 port pin
|
||||
void setPortPin(uint pin, bool value); // Set port pin for extension port
|
||||
void togglePortPin(uint pin); // Toggle extension port pin
|
||||
|
||||
Color colorMapping(const String &colorString); // Color mapping string to CHSV colors
|
||||
void setBacklightLED(uint brightness, const Color &color);// Set backlight LEDs
|
||||
void toggleBacklightLED(uint brightness,const Color &color);// Toggle backlight LEDs
|
||||
void stepsBacklightLED(uint brightness, const Color &color);// Set backlight LEDs in 4 steps (100%, 50%, 10%, 0%)
|
||||
BacklightMode backlightMapping(const String &backlightString);// Configuration string to value
|
||||
|
||||
void setFlashLED(bool status); // Set flash LED
|
||||
@@ -108,6 +109,7 @@ void setBuzzerPower(uint power); // Set buzzer power
|
||||
String xdrDelete(String input); // Delete xdr prefix from string
|
||||
|
||||
void drawTextCenter(int16_t cx, int16_t cy, String text);
|
||||
void drawButtonCenter(int16_t cx, int16_t cy, int8_t sx, int8_t sy, String text, uint16_t fg, uint16_t bg, bool inverted);
|
||||
void drawTextRalign(int16_t x, int16_t y, String text);
|
||||
void drawTextBoxed(Rect box, String text, uint16_t fg, uint16_t bg, bool inverted, bool border);
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
const char* fmt_dec_1;
|
||||
const char* fmt_dec_10;
|
||||
const char* fmt_dec_100;
|
||||
double limit_dec_10;
|
||||
double limit_dec_100;
|
||||
if (precision == "1") {
|
||||
//
|
||||
//All values are displayed using a DSEG7* font. In this font, ' ' is a very short space, and '.' takes up no space at all.
|
||||
@@ -100,10 +102,14 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
fmt_dec_1 = "!%1.1f"; //insert a blank digit and then display a two-digit number
|
||||
fmt_dec_10 = "!%2.0f"; //insert a blank digit and then display a two-digit number
|
||||
fmt_dec_100 = "%3.0f"; //dispay a three digit number
|
||||
limit_dec_10=9.95; // use fmt_dec_1 below this number to avoid formatting 9.96 as 10.0 instead of 10
|
||||
limit_dec_100=99.5;
|
||||
} else {
|
||||
fmt_dec_1 = "%3.2f";
|
||||
fmt_dec_10 = "%3.1f";
|
||||
fmt_dec_100 = "%3.0f";
|
||||
limit_dec_10=9.995;
|
||||
limit_dec_100=99.95;
|
||||
}
|
||||
|
||||
// LOG_DEBUG(GwLog::DEBUG,"formatValue init: getFormat: %s date->value: %f time->value: %f", value->getFormat(), commondata.date->value, commondata.time->value);
|
||||
@@ -243,10 +249,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
speed = speed; // Unit conversion form m/s to m/s
|
||||
result.unit = "m/s";
|
||||
}
|
||||
if(speed < 10) {
|
||||
if(speed < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, speed);
|
||||
}
|
||||
else if (speed < 100) {
|
||||
else if (speed < limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_10, speed);
|
||||
}
|
||||
else {
|
||||
@@ -323,11 +329,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
snprintf(buffer, bsize, "%2.0f", speed);
|
||||
}
|
||||
else{
|
||||
speed = std::round(speed * 100) / 100; // in rare cases, speed could be 9.9999 kn instead of 10.0 kn
|
||||
if (speed < 10.0){
|
||||
if (speed < limit_dec_10){
|
||||
snprintf(buffer, bsize, fmt_dec_1, speed);
|
||||
}
|
||||
else if (speed < 100.0){
|
||||
else if (speed < limit_dec_100){
|
||||
snprintf(buffer, bsize, fmt_dec_10, speed);
|
||||
}
|
||||
else {
|
||||
@@ -378,10 +383,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
if (dop > 99.9){
|
||||
dop = 99.9;
|
||||
}
|
||||
if (dop < 10){
|
||||
if (dop < limit_dec_10){
|
||||
snprintf(buffer, bsize, fmt_dec_1, dop);
|
||||
}
|
||||
else if(dop < 100){
|
||||
else if(dop < limit_dec_100){
|
||||
snprintf(buffer, bsize, fmt_dec_10, dop);
|
||||
}
|
||||
else {
|
||||
@@ -457,10 +462,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
else{
|
||||
result.unit = "m";
|
||||
}
|
||||
if (depth < 10) {
|
||||
if (depth < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, depth);
|
||||
}
|
||||
else if (depth < 100){
|
||||
else if (depth < limit_dec_100){
|
||||
snprintf(buffer, bsize, fmt_dec_10, depth);
|
||||
}
|
||||
else {
|
||||
@@ -523,10 +528,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
else{
|
||||
result.unit = "K";
|
||||
}
|
||||
if(temp < 10) {
|
||||
if(temp < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, temp);
|
||||
}
|
||||
else if (temp < 100) {
|
||||
else if (temp < limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_10, temp);
|
||||
}
|
||||
else {
|
||||
@@ -556,10 +561,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
else {
|
||||
result.unit = "m";
|
||||
}
|
||||
if (distance < 10){
|
||||
if (distance < limit_dec_10){
|
||||
snprintf(buffer, bsize, fmt_dec_1, distance);
|
||||
}
|
||||
else if (distance < 100){
|
||||
else if (distance < limit_dec_100){
|
||||
snprintf(buffer, bsize, fmt_dec_10, distance);
|
||||
}
|
||||
else {
|
||||
@@ -613,7 +618,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
rawvalue = 12 + float(random(0, 30)) / 10.0;
|
||||
voltage = rawvalue;
|
||||
}
|
||||
if (voltage < 10) {
|
||||
if (voltage < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, voltage);
|
||||
}
|
||||
else {
|
||||
@@ -633,10 +638,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
rawvalue = 8.2 + float(random(0, 50)) / 10.0;
|
||||
current = rawvalue;
|
||||
}
|
||||
if (current < 10) {
|
||||
if (current < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, current);
|
||||
}
|
||||
else if(current < 100) {
|
||||
else if(current < limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_10, current);
|
||||
}
|
||||
else {
|
||||
@@ -656,10 +661,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
rawvalue = 21.8 + float(random(0, 50)) / 10.0;
|
||||
temperature = rawvalue;
|
||||
}
|
||||
if (temperature < 10) {
|
||||
if (temperature < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, temperature);
|
||||
}
|
||||
else if (temperature < 100) {
|
||||
else if (temperature < limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_10, temperature);
|
||||
}
|
||||
else {
|
||||
@@ -679,10 +684,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
rawvalue = 21.8 + float(random(0, 50)) / 10.0;
|
||||
temperature = rawvalue;
|
||||
}
|
||||
if (temperature < 10) {
|
||||
if (temperature < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, temperature);
|
||||
}
|
||||
else if(temperature < 100) {
|
||||
else if(temperature < limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_10, temperature);
|
||||
}
|
||||
else {
|
||||
@@ -702,10 +707,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
rawvalue = 41.3 + float(random(0, 50)) / 10.0;
|
||||
humidity = rawvalue;
|
||||
}
|
||||
if (humidity < 10) {
|
||||
if (humidity < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, humidity);
|
||||
}
|
||||
else if(humidity < 100) {
|
||||
else if(humidity < limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_10, humidity);
|
||||
}
|
||||
else {
|
||||
@@ -725,13 +730,13 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
rawvalue = 85.8 + float(random(0, 50)) / 10.0;
|
||||
volume = rawvalue;
|
||||
}
|
||||
if (volume < 10) {
|
||||
if (volume < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, volume);
|
||||
}
|
||||
else if (volume < 100) {
|
||||
else if (volume < limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_10, volume);
|
||||
}
|
||||
else if (volume >= 100) {
|
||||
else if (volume >= limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_100, volume);
|
||||
}
|
||||
result.unit = "%";
|
||||
@@ -748,10 +753,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
rawvalue = 75.2 + float(random(0, 50)) / 10.0;
|
||||
volume = rawvalue;
|
||||
}
|
||||
if (volume < 10) {
|
||||
if (volume < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, volume);
|
||||
}
|
||||
else if (volume < 100) {
|
||||
else if (volume < limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_10, volume);
|
||||
}
|
||||
else {
|
||||
@@ -771,10 +776,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
rawvalue = 7.5 + float(random(0, 20)) / 10.0;
|
||||
flow = rawvalue;
|
||||
}
|
||||
if (flow < 10) {
|
||||
if (flow < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, flow);
|
||||
}
|
||||
else if (flow < 100) {
|
||||
else if (flow < limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_10, flow);
|
||||
}
|
||||
else {
|
||||
@@ -794,10 +799,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
rawvalue = 18.5 + float(random(0, 20)) / 10.0;
|
||||
generic = rawvalue;
|
||||
}
|
||||
if (generic < 10) {
|
||||
if (generic < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, generic);
|
||||
}
|
||||
else if (generic < 100) {
|
||||
else if (generic < limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_10, generic);
|
||||
}
|
||||
else {
|
||||
@@ -817,10 +822,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
rawvalue = 55.3 + float(random(0, 20)) / 10.0;
|
||||
dplace = rawvalue;
|
||||
}
|
||||
if (dplace < 10) {
|
||||
if (dplace < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, dplace);
|
||||
}
|
||||
else if (dplace < 100) {
|
||||
else if (dplace < limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_10, dplace);
|
||||
}
|
||||
else {
|
||||
@@ -861,10 +866,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
rawvalue = 2505 + random(0, 20);
|
||||
rpm = rawvalue;
|
||||
}
|
||||
if (rpm < 10) {
|
||||
if (rpm < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, rpm);
|
||||
}
|
||||
else if (rpm < 100) {
|
||||
else if (rpm < limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_10, rpm);
|
||||
}
|
||||
else {
|
||||
@@ -877,10 +882,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
|
||||
// Default format
|
||||
//########################################################
|
||||
else {
|
||||
if (value->value < 10) {
|
||||
if (value->value < limit_dec_10) {
|
||||
snprintf(buffer, bsize, fmt_dec_1, value->value);
|
||||
}
|
||||
else if (value->value < 100) {
|
||||
else if (value->value < limit_dec_100) {
|
||||
snprintf(buffer, bsize, fmt_dec_10, value->value);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// General hardware definitions
|
||||
// CAN and RS485 bus pin definitions see obp60task.h
|
||||
|
||||
#ifdef HARDWARE_V21
|
||||
#if defined HARDWARE_V20 || HARDWARE_V21
|
||||
// Direction pin for RS485 NMEA0183
|
||||
#define OBP_DIRECTION_PIN 18
|
||||
// I2C
|
||||
@@ -23,8 +23,8 @@
|
||||
#define AS5600_I2C_ADDR 0x36 // Addr. 0x36 (fix)
|
||||
// INA219
|
||||
#define SHUNT_VOLTAGE 0.075 // Shunt voltage in V by max. current (75mV)
|
||||
#define INA219_I2C_ADDR1 0x40 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery
|
||||
#define INA219_I2C_ADDR2 0x41 // Addr. 0x44 (fix A0 = GND, A1 = 5V) for solar panels
|
||||
#define INA219_I2C_ADDR1 0x41 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery
|
||||
#define INA219_I2C_ADDR2 0x44 // Addr. 0x44 (fix A0 = GND, A1 = 5V) for solar panels
|
||||
#define INA219_I2C_ADDR3 0x45 // Addr. 0x45 (fix A0 = 5V, A1 = 5V) for generator
|
||||
// INA226
|
||||
#define INA226_I2C_ADDR1 0x41 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery
|
||||
@@ -103,8 +103,8 @@
|
||||
#define AS5600_I2C_ADDR 0x36 // Addr. 0x36 (fix)
|
||||
// INA219
|
||||
#define SHUNT_VOLTAGE 0.075 // Shunt voltage in V by max. current (75mV)
|
||||
#define INA219_I2C_ADDR1 0x40 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery
|
||||
#define INA219_I2C_ADDR2 0x41 // Addr. 0x44 (fix A0 = GND, A1 = 5V) for solar panels
|
||||
#define INA219_I2C_ADDR1 0x41 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery
|
||||
#define INA219_I2C_ADDR2 0x44 // Addr. 0x44 (fix A0 = GND, A1 = 5V) for solar panels
|
||||
#define INA219_I2C_ADDR3 0x45 // Addr. 0x45 (fix A0 = 5V, A1 = 5V) for generator
|
||||
// INA226
|
||||
#define INA226_I2C_ADDR1 0x41 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery
|
||||
|
||||
@@ -58,7 +58,7 @@ void initKeys(CommonData &commonData) {
|
||||
commonData.keydata[5].h = height;
|
||||
}
|
||||
|
||||
#ifdef HARDWARE_V21
|
||||
#if defined HARDWARE_V20 || HARDWARE_V21
|
||||
// Keypad functions for original OBP60 hardware
|
||||
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {
|
||||
|
||||
|
||||
@@ -1,5 +1,221 @@
|
||||
#include "OBPDataOperations.h"
|
||||
#include "BoatDataCalibration.h" // Functions lib for data instance calibration
|
||||
//#include "BoatDataCalibration.h" // Functions lib for data instance calibration
|
||||
|
||||
// --- Class CalibrationData ---------------
|
||||
CalibrationData::CalibrationData(GwLog* log)
|
||||
{
|
||||
logger = log;
|
||||
}
|
||||
|
||||
void CalibrationData::readConfig(GwConfigHandler* config)
|
||||
// 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;
|
||||
}
|
||||
|
||||
calibrationMap[instance] = { 0.0f, 1.0f, 1.0f, 0.0f, false };
|
||||
offset = (config->getString(calOffset, "")).toDouble();
|
||||
slope = (config->getString(calSlope, "")).toDouble();
|
||||
smooth = (config->getString(calSmooth, "")).toInt(); // user input is int; further math is done with double
|
||||
|
||||
if (slope == 0.0) {
|
||||
slope = 1.0; // eliminate adjustment if user selected "0" -> that would set the calibrated value to "0"
|
||||
}
|
||||
|
||||
// Convert calibration values from user input format to internal standard SI format
|
||||
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 == "HDM" || instance == "HDT" || instance == "PRPOS" || instance == "RPOS" || instance == "TWA" || instance == "TWD") {
|
||||
offset *= DEG_TO_RAD; // Convert deg to rad
|
||||
|
||||
} else if (instance == "DBS" || 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;
|
||||
|
||||
calibrationMap[instance].offset = offset;
|
||||
calibrationMap[instance].slope = slope;
|
||||
calibrationMap[instance].smooth = smooth;
|
||||
calibrationMap[instance].isCalibrated = false;
|
||||
LOG_DEBUG(GwLog::LOG, "Calibration data type added: %s, offset: %f, slope: %f, smoothing: %f", instance.c_str(),
|
||||
calibrationMap[instance].offset, calibrationMap[instance].slope, calibrationMap[instance].smooth);
|
||||
}
|
||||
// LOG_DEBUG(GwLog::LOG, "All calibration data read");
|
||||
}
|
||||
|
||||
// Handle calibrationMap and calibrate all boat data values
|
||||
void CalibrationData::handleCalibration(BoatValueList* boatValueList)
|
||||
{
|
||||
GwApi::BoatValue* bValue;
|
||||
|
||||
for (const auto& cMap : calibrationMap) {
|
||||
std::string instance = cMap.first.c_str();
|
||||
bValue = boatValueList->findValueOrCreate(String(instance.c_str()));
|
||||
|
||||
calibrateInstance(bValue);
|
||||
smoothInstance(bValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Calibrate single boat data value
|
||||
// Return calibrated boat value or DBL_MAX, if no calibration was performed
|
||||
bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
|
||||
{
|
||||
std::string instance = boatDataValue->getName().c_str();
|
||||
double offset = 0;
|
||||
double slope = 1.0;
|
||||
double dataValue = 0;
|
||||
std::string format = "";
|
||||
|
||||
// we test this earlier, but for safety reasons ...
|
||||
if (calibrationMap.find(instance) == calibrationMap.end()) {
|
||||
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
calibrationMap[instance].isCalibrated = false; // reset calibration flag until properly calibrated
|
||||
|
||||
if (!boatDataValue->valid) { // no valid boat data value, so we don't want to apply calibration data
|
||||
return false;
|
||||
}
|
||||
|
||||
offset = calibrationMap[instance].offset;
|
||||
slope = calibrationMap[instance].slope;
|
||||
dataValue = boatDataValue->value;
|
||||
format = boatDataValue->getFormat().c_str();
|
||||
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: value: %f, format: %s", instance.c_str(), dataValue, format.c_str());
|
||||
|
||||
if (format == "formatWind") { // instance is of type angle
|
||||
dataValue = (dataValue * slope) + offset;
|
||||
// dataValue = WindUtils::toPI(dataValue);
|
||||
dataValue = WindUtils::to2PI(dataValue); // we should call <toPI> for format of [-180..180], but pages cannot display negative values properly yet
|
||||
|
||||
} else if (format == "formatCourse") { // instance is of type direction
|
||||
dataValue = (dataValue * slope) + offset;
|
||||
dataValue = WindUtils::to2PI(dataValue);
|
||||
|
||||
} else if (format == "kelvinToC") { // instance is of type temperature
|
||||
dataValue = ((dataValue - 273.15) * slope) + offset + 273.15;
|
||||
|
||||
} else {
|
||||
dataValue = (dataValue * slope) + offset;
|
||||
}
|
||||
|
||||
|
||||
boatDataValue->value = dataValue; // update boat data value with calibrated value
|
||||
calibrationMap[instance].value = dataValue; // store the calibrated value in the list
|
||||
calibrationMap[instance].isCalibrated = true;
|
||||
|
||||
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: Offset: %f, Slope: %f, Result: %f", instance.c_str(), offset, slope, calibrationMap[instance].value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Smooth single boat data value
|
||||
// Return smoothed boat value or DBL_MAX, if no smoothing was performed
|
||||
bool CalibrationData::smoothInstance(GwApi::BoatValue* boatDataValue)
|
||||
{
|
||||
std::string instance = boatDataValue->getName().c_str();
|
||||
double oldValue = 0;
|
||||
double dataValue = boatDataValue->value;
|
||||
double smoothFactor = 0;
|
||||
|
||||
// we test this earlier, but for safety reason ...
|
||||
if (calibrationMap.find(instance) == calibrationMap.end()) {
|
||||
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
calibrationMap[instance].isCalibrated = false; // reset calibration flag until properly calibrated
|
||||
|
||||
if (!boatDataValue->valid) { // no valid boat data value, so we don't need to do anything
|
||||
return false;
|
||||
}
|
||||
|
||||
smoothFactor = calibrationMap[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; // update boat data value with smoothed value
|
||||
calibrationMap[instance].value = dataValue; // store the smoothed value in the list
|
||||
calibrationMap[instance].isCalibrated = true;
|
||||
|
||||
// LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: smooth: %f, oldValue: %f, result: %f", instance.c_str(), smoothFactor, oldValue, calibrationMap[instance].value);
|
||||
|
||||
return true;
|
||||
}
|
||||
// --- End Class CalibrationData ---------------
|
||||
|
||||
// --- Class HstryBuf ---------------
|
||||
HstryBuf::HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log)
|
||||
@@ -25,7 +241,7 @@ void HstryBuf::add(double value)
|
||||
{
|
||||
if (value >= hstryMin && value <= hstryMax) {
|
||||
hstryBuf.add(value);
|
||||
LOG_DEBUG(GwLog::DEBUG, "HstryBuf::add: name: %s, value: %.3f", hstryBuf.getName(), value);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "HstryBuf::add: name: %s, value: %.3f", hstryBuf.getName(), value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,12 +259,15 @@ void HstryBuf::handle(bool useSimuData, CommonData& common)
|
||||
|
||||
if (boatValue->valid) {
|
||||
// Calibrate boat value before adding it to history buffer
|
||||
calibrationData.calibrateInstance(tmpBVal.get(), logger);
|
||||
add(tmpBVal->value);
|
||||
//calibrationData.calibrateInstance(tmpBVal.get(), logger);
|
||||
//add(tmpBVal->value);
|
||||
add(boatValue->value);
|
||||
|
||||
} else if (useSimuData) { // add simulated value to history buffer
|
||||
double simValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at <formatValue>
|
||||
add(simValue);
|
||||
double simSIValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at <formatValue>; here: retreive SI value
|
||||
add(simSIValue);
|
||||
} else {
|
||||
// here we will add invalid (DBL_MAX) value; this will mark periods of missing data in buffer together with a timestamp
|
||||
}
|
||||
}
|
||||
// --- End Class HstryBuf ---------------
|
||||
@@ -186,7 +405,7 @@ void WindUtils::calcTwdSA(const double* AWA, const double* AWS,
|
||||
double stw = -*STW;
|
||||
addPolar(AWD, AWS, CTW, &stw, TWD, TWS);
|
||||
|
||||
// Normalize TWD and TWA to 0-360°/2PI
|
||||
// Normalize TWD to [0..360°] (2PI) and TWA to [-180..180] (PI)
|
||||
*TWD = to2PI(*TWD);
|
||||
*TWA = toPI(*TWD - *HDT);
|
||||
}
|
||||
@@ -268,8 +487,8 @@ bool WindUtils::addWinds()
|
||||
double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
|
||||
double hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX;
|
||||
double varVal = varBVal->valid ? varBVal->value : DBL_MAX;
|
||||
LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852,
|
||||
cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG);
|
||||
//LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852,
|
||||
// cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG);
|
||||
|
||||
// Check if TWD can be calculated from TWA and HDT/HDM
|
||||
if (twaBVal->valid) {
|
||||
@@ -299,7 +518,8 @@ bool WindUtils::addWinds()
|
||||
twsBVal->valid = true;
|
||||
}
|
||||
if (!twaBVal->valid) {
|
||||
twaBVal->value = twa;
|
||||
//twaBVal->value = twa;
|
||||
twaBVal->value = to2PI(twa); // convert to [0..360], because pages cannot display negative values properly yet
|
||||
twaBVal->valid = true;
|
||||
}
|
||||
if (!awdBVal->valid) {
|
||||
@@ -308,8 +528,8 @@ bool WindUtils::addWinds()
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG,
|
||||
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG,
|
||||
// twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG);
|
||||
|
||||
return twCalculated;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,36 @@
|
||||
// Function lib for history buffer handling, true wind calculation, and other operations on boat data
|
||||
// Function lib for boat data calibration, history buffer handling, true wind calculation, and other operations on boat data
|
||||
#pragma once
|
||||
#include "OBPRingBuffer.h"
|
||||
#include "Pagedata.h"
|
||||
#include "obp60task.h"
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
// Calibration of boat data values, when user setting available
|
||||
// supported boat data types are: AWA, AWS, COG, DBS, DBT, HDM, HDT, PRPOS, RPOS, SOG, STW, TWA, TWS, TWD, WTemp
|
||||
class CalibrationData {
|
||||
private:
|
||||
typedef struct {
|
||||
double offset; // calibration offset
|
||||
double slope; // calibration slope
|
||||
double smooth; // smoothing factor
|
||||
double value; // calibrated data value (for future use)
|
||||
bool isCalibrated; // is data instance value calibrated? (for future use)
|
||||
} tCalibrationData;
|
||||
|
||||
std::unordered_map<std::string, tCalibrationData> calibrationMap; // list of calibration data instances
|
||||
std::unordered_map<std::string, double> lastValue; // array for last smoothed values of boat data values
|
||||
GwLog* logger;
|
||||
|
||||
static constexpr int8_t MAX_CALIBRATION_DATA = 4; // maximum number of calibration data instances
|
||||
|
||||
public:
|
||||
CalibrationData(GwLog* log);
|
||||
void readConfig(GwConfigHandler* config);
|
||||
void handleCalibration(BoatValueList* boatValues); // Handle calibrationMap and calibrate all boat data values
|
||||
bool calibrateInstance(GwApi::BoatValue* boatDataValue); // Calibrate single boat data value
|
||||
bool smoothInstance(GwApi::BoatValue* boatDataValue); // Smooth single boat data value
|
||||
};
|
||||
|
||||
class HstryBuf {
|
||||
private:
|
||||
|
||||
@@ -49,8 +49,10 @@ void sensorTask(void *param){
|
||||
|
||||
// Init sensor stuff
|
||||
bool oneWire_ready = false; // 1Wire initialized and ready to use
|
||||
bool iRTC_ready = false; // Software RTC initialized and ready to use
|
||||
bool RTC_ready = false; // DS1388 initialized and ready to use
|
||||
bool GPS_ready = false; // GPS initialized and ready to use
|
||||
bool N2K_GPS_ready = false; // GPS time on N2K bus
|
||||
bool BME280_ready = false; // BME280 initialized and ready to use
|
||||
bool BMP280_ready = false; // BMP280 initialized and ready to use
|
||||
bool BMP180_ready = false; // BMP180 initialized and ready to use
|
||||
@@ -382,6 +384,7 @@ void sensorTask(void *param){
|
||||
if (getLocalTime(&timeinfo)) {
|
||||
api->getLogger()->logDebug(GwLog::LOG,"NTP time: %04d-%02d-%02d %02d:%02d:%02d UTC", timeinfo.tm_year+1900, timeinfo.tm_mon+1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
|
||||
rtc.setTimeStruct(timeinfo);
|
||||
iRTC_ready = true;
|
||||
sensors.rtcValid = true;
|
||||
} else {
|
||||
api->getLogger()->logDebug(GwLog::LOG,"NTP time fetch failed!");
|
||||
@@ -400,7 +403,7 @@ void sensorTask(void *param){
|
||||
if (millis() > starttime0 + 100)
|
||||
{
|
||||
starttime0 = millis();
|
||||
// Send NMEA0183 GPS data on several bus systems all 100ms
|
||||
// Send NMEA0183 GPS data on several bus systems (N2K an 0183) all 100ms
|
||||
if (GPS_ready == true && hdop->value <= hdopAccuracy)
|
||||
{
|
||||
SNMEA0183Msg NMEA0183Msg;
|
||||
@@ -412,9 +415,55 @@ void sensorTask(void *param){
|
||||
|
||||
}
|
||||
|
||||
// If RTC DS1388 ready, then copy GPS data to RTC all 5min
|
||||
if(millis() > starttime11 + 5*60*1000){
|
||||
/*
|
||||
Time set logic for RTC and N2K
|
||||
###############################
|
||||
|
||||
iRTC = Software RTC updatetd with NTP via internet
|
||||
RTC = RTC chip on PCB
|
||||
GPS = GPS Receiver on PCB
|
||||
N2K = GPS time on N2K od 183 bus
|
||||
0 = device not ready
|
||||
1 = device ready
|
||||
X = independend
|
||||
() = source for set time N2K
|
||||
-> = set RTC via iRTC
|
||||
<- = set RTC via GPS
|
||||
|
||||
iRTC RTC GPS N2K
|
||||
0 0 0 (1)
|
||||
0 0 (1) (X)
|
||||
0 (1) 0 (X)
|
||||
0 1 <-(1) (X)
|
||||
(1) 0 0 (X)
|
||||
1 0 (1) (X)
|
||||
1 ->(1) 0 (X)
|
||||
1 1 <-(1) (X)
|
||||
|
||||
*/
|
||||
|
||||
// If RTC DS1388 ready, then copy iRTC and GPS data to RTC all 1min
|
||||
if(millis() > starttime11 + 1*60*1000){
|
||||
starttime11 = millis();
|
||||
// Set RTC chip via iRTC (NTP)
|
||||
if(iRTC_ready == true && RTC_ready == true && GPS_ready == false){
|
||||
GwApi::Status status;
|
||||
api->getStatus(status);
|
||||
// Check WiFi connection
|
||||
if (status.wifiClientConnected) {
|
||||
sensors.rtcTime = rtc.getTimeStruct(); // Get time from software RTC (iRTC)
|
||||
DateTime now = DateTime(
|
||||
sensors.rtcTime.tm_year + 1900,
|
||||
sensors.rtcTime.tm_mon + 1,
|
||||
sensors.rtcTime.tm_mday,
|
||||
sensors.rtcTime.tm_hour,
|
||||
sensors.rtcTime.tm_min,
|
||||
sensors.rtcTime.tm_sec
|
||||
);
|
||||
ds1388.adjust(now);
|
||||
}
|
||||
}
|
||||
// Set RTC chip via internal GPS
|
||||
if(rtcOn == "DS1388" && RTC_ready == true && GPS_ready == true){
|
||||
api->getBoatDataValues(3,valueList);
|
||||
if(gpsdays->valid && gpsseconds->valid && hdop->valid){
|
||||
@@ -422,40 +471,33 @@ void sensorTask(void *param){
|
||||
// sample input: date = "Dec 26 2009", time = "12:34:56"
|
||||
// ds1388.adjust(DateTime("Dec 26 2009", "12:34:56"));
|
||||
DateTime adjusttime(ts);
|
||||
api->getLogger()->logDebug(GwLog::LOG,"Adjust RTC time: %04d/%02d/%02d %02d:%02d:%02d",adjusttime.year(), adjusttime.month(), adjusttime.day(), adjusttime.hour(), adjusttime.minute(), adjusttime.second());
|
||||
api->getLogger()->logDebug(GwLog::LOG,"Adjust RTC time via internal GPS: %04d/%02d/%02d %02d:%02d:%02d",adjusttime.year(), adjusttime.month(), adjusttime.day(), adjusttime.hour(), adjusttime.minute(), adjusttime.second());
|
||||
// Adjust RTC time as unix time value
|
||||
ds1388.adjust(adjusttime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send 1Wire data for all temperature sensors all 2s
|
||||
if(millis() > starttime13 + 2000 && String(oneWireOn) == "DS18B20" && oneWire_ready == true){
|
||||
starttime13 = millis();
|
||||
float tempC;
|
||||
ds18b20.requestTemperatures(); // Collect all temperature values (max.8)
|
||||
for(int i=0;i<numberOfDevices; i++){
|
||||
// Send only one 1Wire data per loop step (time reduction)
|
||||
if(i == loopCounter % numberOfDevices){
|
||||
if(ds18b20.getAddress(tempDeviceAddress, i)){
|
||||
// Read temperature value in Celsius
|
||||
tempC = ds18b20.getTempC(tempDeviceAddress);
|
||||
}
|
||||
// Send to NMEA200 bus for each sensor with instance number
|
||||
if(!isnan(tempC)){
|
||||
sensors.onewireTemp[i] = tempC; // Save values in SensorData
|
||||
api->getLogger()->logDebug(GwLog::DEBUG,"DS18B20-%1d Temp: %.1f",i,tempC);
|
||||
SetN2kPGN130316(N2kMsg, 0, i, N2kts_OutsideTemperature, CToKelvin(tempC), N2kDoubleNA);
|
||||
api->sendN2kMessage(N2kMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
loopCounter++;
|
||||
// Set RTC chip via N2K or 183 in case the internal GPS is off (only one time)
|
||||
if(N2K_GPS_ready == false && RTC_ready == true && GPS_ready == false){
|
||||
api->getBoatDataValues(3,valueList);
|
||||
if(gpsdays->valid && gpsseconds->valid && hdop->valid){
|
||||
long ts = tNMEA0183Msg::daysToTime_t(gpsdays->value - (30*365+7))+floor(gpsseconds->value); // Adjusted to reference year 2000 (-30 years and 7 days for switch years)
|
||||
// sample input: date = "Dec 26 2009", time = "12:34:56"
|
||||
// ds1388.adjust(DateTime("Dec 26 2009", "12:34:56"));
|
||||
DateTime adjusttime(ts);
|
||||
api->getLogger()->logDebug(GwLog::LOG,"Adjust RTC time via N2K/183: %04d/%02d/%02d %02d:%02d:%02d",adjusttime.year(), adjusttime.month(), adjusttime.day(), adjusttime.hour(), adjusttime.minute(), adjusttime.second());
|
||||
// Adjust RTC time as unix time value
|
||||
ds1388.adjust(adjusttime);
|
||||
// N2K GPS time ready
|
||||
N2K_GPS_ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get current RTC date and time all 500ms
|
||||
// Send RTC date and time to N2K all 500ms
|
||||
if (millis() > starttime12 + 500) {
|
||||
starttime12 = millis();
|
||||
// Send date and time from RTC chip if GPS not ready
|
||||
if (rtcOn == "DS1388" && RTC_ready) {
|
||||
DateTime dt = ds1388.now();
|
||||
sensors.rtcTime.tm_year = dt.year() - 1900; // Save values in SensorData
|
||||
@@ -481,21 +523,62 @@ void sensorTask(void *param){
|
||||
}
|
||||
// N2K sysTime is double in n2klib
|
||||
double sysTime = (dt.hour() * 3600) + (dt.minute() * 60) + dt.second();
|
||||
// WHY? isnan should always fail here
|
||||
//if(!isnan(daysAt1970) && !isnan(sysTime)){
|
||||
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);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
// Send date and time from software RTC (iRTC)
|
||||
if (iRTC_ready == true && RTC_ready == false && GPS_ready == false) {
|
||||
// Use internal RTC feature
|
||||
sensors.rtcTime = rtc.getTimeStruct(); // Save software RTC values in SensorData
|
||||
// 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 = ((sensors.rtcTime.tm_year-1)-1968)/4 - ((sensors.rtcTime.tm_year-1)-1900)/100 + ((sensors.rtcTime.tm_year-1)-1600)/400;
|
||||
long daysAt1970 = (sensors.rtcTime.tm_year-1970)*365 + switchYear + daysOfYear[sensors.rtcTime.tm_mon-1] + sensors.rtcTime.tm_mday-1;
|
||||
// If switch year then add one day
|
||||
if ((sensors.rtcTime.tm_mon > 2) && (sensors.rtcTime.tm_year % 4 == 0 && (sensors.rtcTime.tm_year % 100 != 0 || sensors.rtcTime.tm_year % 400 == 0))) {
|
||||
daysAt1970 += 1;
|
||||
}
|
||||
// N2K sysTime is double in n2klib
|
||||
double sysTime = (sensors.rtcTime.tm_hour * 3600) + (sensors.rtcTime.tm_min * 60) + sensors.rtcTime.tm_sec;
|
||||
if(!isnan(daysAt1970) && !isnan(sysTime)){
|
||||
//api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, 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
|
||||
// Send 1Wire data for all temperature sensors to N2K all 2s
|
||||
if(millis() > starttime13 + 2000 && String(oneWireOn) == "DS18B20" && oneWire_ready == true){
|
||||
starttime13 = millis();
|
||||
float tempC;
|
||||
ds18b20.requestTemperatures(); // Collect all temperature values (max.8)
|
||||
for(int i=0;i<numberOfDevices; i++){
|
||||
// Send only one 1Wire data per loop step (time reduction)
|
||||
if(i == loopCounter % numberOfDevices){
|
||||
if(ds18b20.getAddress(tempDeviceAddress, i)){
|
||||
// Read temperature value in Celsius
|
||||
tempC = ds18b20.getTempC(tempDeviceAddress);
|
||||
}
|
||||
// Send to NMEA200 bus for each sensor with instance number
|
||||
if(!isnan(tempC)){
|
||||
sensors.onewireTemp[i] = tempC; // Save values in SensorData
|
||||
api->getLogger()->logDebug(GwLog::DEBUG,"DS18B20-%1d Temp: %.1f",i,tempC);
|
||||
SetN2kPGN130316(N2kMsg, 0, i, N2kts_OutsideTemperature, CToKelvin(tempC), N2kDoubleNA);
|
||||
api->sendN2kMessage(N2kMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
loopCounter++;
|
||||
}
|
||||
|
||||
// Send supply voltage value to N2K all 1s
|
||||
if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){
|
||||
starttime5 = millis();
|
||||
float rawVoltage = 0; // Default value
|
||||
@@ -565,7 +648,7 @@ void sensorTask(void *param){
|
||||
#endif
|
||||
}
|
||||
|
||||
// Send data from environment sensor all 2s
|
||||
// Send data from environment sensor to N2K all 2s
|
||||
if(millis() > starttime6 + 2000){
|
||||
starttime6 = millis();
|
||||
unsigned char TempSource = 2; // Inside temperature
|
||||
@@ -630,7 +713,7 @@ void sensorTask(void *param){
|
||||
}
|
||||
}
|
||||
|
||||
// Send rotation angle all 500ms
|
||||
// Send rotation angle to N2K all 500ms
|
||||
if(millis() > starttime7 + 500){
|
||||
starttime7 = millis();
|
||||
double rotationAngle=0;
|
||||
@@ -678,7 +761,7 @@ void sensorTask(void *param){
|
||||
}
|
||||
}
|
||||
|
||||
// Send battery power value all 1s
|
||||
// Send battery power value to N2K all 1s
|
||||
if(millis() > starttime8 + 1000 && (String(powsensor1) == "INA219" || String(powsensor1) == "INA226")){
|
||||
starttime8 = millis();
|
||||
if(String(powsensor1) == "INA226" && INA226_1_ready == true){
|
||||
@@ -720,7 +803,7 @@ void sensorTask(void *param){
|
||||
}
|
||||
}
|
||||
|
||||
// Send solar power value all 1s
|
||||
// Send solar power value to N2K all 1s
|
||||
if(millis() > starttime9 + 1000 && (String(powsensor2) == "INA219" || String(powsensor2) == "INA226")){
|
||||
starttime9 = millis();
|
||||
if(String(powsensor2) == "INA226" && INA226_2_ready == true){
|
||||
@@ -750,7 +833,7 @@ void sensorTask(void *param){
|
||||
}
|
||||
}
|
||||
|
||||
// Send generator power value all 1s
|
||||
// Send generator power value to N2K all 1s
|
||||
if(millis() > starttime10 + 1000 && (String(powsensor3) == "INA219" || String(powsensor3) == "INA226")){
|
||||
starttime10 = millis();
|
||||
if(String(powsensor3) == "INA226" && INA226_3_ready == true){
|
||||
|
||||
@@ -161,8 +161,8 @@ bool Chart::setChartDimensions(const char direction, const int8_t size)
|
||||
break;
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(GwLog::ERROR, "obp60:setChartDimensions %s: direction: %c, size: %d, dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot{%d, %d}, top: %d, bottom: %d, hGap: %d, vGap: %d",
|
||||
dataBuf.getName(), direction, size, dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, top, bottom, hGap, vGap);
|
||||
//LOG_DEBUG(GwLog::DEBUG, "obp60:setChartDimensions %s: direction: %c, size: %d, dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot{%d, %d}, top: %d, bottom: %d, hGap: %d, vGap: %d",
|
||||
// dataBuf.getName(), direction, size, dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, top, bottom, hGap, vGap);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ void Chart::drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue
|
||||
// LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
|
||||
calcChrtBorders(chrtMin, chrtMid, chrtMax, chrtRng);
|
||||
chrtScale = double(valAxis) / chrtRng; // Chart scale: pixels per value step
|
||||
LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng);
|
||||
|
||||
// Do we have valid buffer data?
|
||||
if (dataBuf.getMax() == dbMAX_VAL) { // only <MAX_VAL> values in buffer -> no valid wind data available
|
||||
@@ -261,8 +261,8 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
|
||||
}
|
||||
recalcRngMid = false; // Reset flag for <rngMid> determination
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "calcChrtRange: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||
rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "calcChrtRange: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||
// rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,8 +287,8 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
|
||||
|
||||
rng = halfRng * 2.0;
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, tmpRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||
tmpRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, tmpRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG,
|
||||
// tmpRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG);
|
||||
|
||||
} else { // chart data is of any other type
|
||||
|
||||
@@ -320,8 +320,8 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub
|
||||
rngMid = (rngMin + rngMax) / 2.0;
|
||||
rng = rngMax - rngMin;
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f",
|
||||
currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f",
|
||||
// currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +397,8 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
taskYIELD(); // we run for 50-150ms; be polite to other tasks with same priority
|
||||
}
|
||||
}
|
||||
|
||||
@@ -656,7 +658,7 @@ void Chart::prntHorizChartThreeValueAxisLabel(const GFXfont* font)
|
||||
|
||||
if (font == &Ubuntu_Bold10pt8b) {
|
||||
xOffset = 39;
|
||||
yOffset = 15;
|
||||
yOffset = 16;
|
||||
} else if (font == &Ubuntu_Bold12pt8b) {
|
||||
xOffset = 51;
|
||||
yOffset = 18;
|
||||
@@ -718,7 +720,7 @@ void Chart::prntHorizChartMultiValueAxisLabel(const GFXfont* font)
|
||||
axSlots = valAxis / static_cast<double>(VALAXIS_STEP); // number of axis labels (and we want to have a double calculation, no integer)
|
||||
axIntv = chrtRng / axSlots;
|
||||
axLabel = chrtMin + axIntv;
|
||||
LOG_DEBUG(GwLog::DEBUG, "Chart::printHorizMultiValueAxisLabel: chrtRng: %.2f, th-chrtRng: %.2f, axSlots: %.2f, axIntv: %.2f, axLabel: %.2f, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, this->chrtRng, axSlots, axIntv, axLabel, this->chrtMin, chrtMid, chrtMax);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "Chart::printHorizMultiValueAxisLabel: chrtRng: %.2f, th-chrtRng: %.2f, axSlots: %.2f, axIntv: %.2f, axLabel: %.2f, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, this->chrtRng, axSlots, axIntv, axLabel, this->chrtMin, chrtMid, chrtMax);
|
||||
|
||||
int loopStrt, loopEnd, loopStp;
|
||||
if (chrtDataFmt == SPEED || chrtDataFmt == TEMPERATURE || chrtDataFmt == OTHER) {
|
||||
|
||||
771
lib/obp60task/PageAnchor.cpp
Normal file
771
lib/obp60task/PageAnchor.cpp
Normal file
@@ -0,0 +1,771 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||
|
||||
/*
|
||||
This page is in experimental stage so be warned!
|
||||
North is up.
|
||||
Anchor page with background map from mapservice
|
||||
|
||||
Boatdata used
|
||||
DBS - Water depth
|
||||
HDT - Boat heading, true
|
||||
AWS - Wind strength; Boat not moving so we assume AWS=TWS and AWD=TWD
|
||||
AWD - Wind direction
|
||||
LAT/LON - Boat position, current
|
||||
HDOP - Position error, horizontal
|
||||
|
||||
Raise function in device OBP40 has to be done inside config mode
|
||||
because of limited number of buttons.
|
||||
|
||||
TODO
|
||||
gzip for data transfer,
|
||||
miniz.c from ROM?
|
||||
manually inflating with tinflate from ROM
|
||||
Save position in FRAM
|
||||
Alarm: gps fix lost
|
||||
switch unit feet/meter
|
||||
force map update if new position is different from old position by
|
||||
a certain level (e.g. 10m)
|
||||
windlass integration
|
||||
chain counter
|
||||
|
||||
Map service options / URL parameters
|
||||
- mandatory
|
||||
lat: latitude
|
||||
lon: longitude
|
||||
width: image width in px (400)
|
||||
height: image height in px (300)
|
||||
- optional
|
||||
zoom: zoom level, default 15
|
||||
mrot: map rotation angle in degrees
|
||||
mtype: map type, default="Open Street Map"
|
||||
dtype: dithering type, default="Atkinson"
|
||||
cutout: image cutout type 0=none
|
||||
alpha: alpha blending for cutout
|
||||
tab: tab size, 0=none
|
||||
border: border line zize in px, default 2
|
||||
symbol: synmol number, default=2 triangle
|
||||
srot: symbol rotation in degrees
|
||||
ssize: symbol size in px, default=15
|
||||
grid: show map grid
|
||||
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <HTTPClient.h>
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "ConfigMenu.h"
|
||||
// #include "miniz.h" // devices without PSRAM use <rom/miniz.h>
|
||||
|
||||
// extern "C" {
|
||||
#include "rom/miniz.h"
|
||||
// }
|
||||
|
||||
#define anchor_width 16
|
||||
#define anchor_height 16
|
||||
static unsigned char anchor_bits[] PROGMEM = {
|
||||
0x80, 0x01, 0x40, 0x02, 0x40, 0x02, 0x80, 0x01, 0xf0, 0x0f, 0x80, 0x01,
|
||||
0x80, 0x01, 0x88, 0x11, 0x8c, 0x31, 0x8e, 0x71, 0x84, 0x21, 0x86, 0x61,
|
||||
0x86, 0x61, 0xfc, 0x3f, 0xf8, 0x1f, 0x80, 0x01 };
|
||||
|
||||
class PageAnchor : public Page
|
||||
{
|
||||
private:
|
||||
char mode = 'N'; // (N)ormal, (C)onfig
|
||||
int8_t editmode = -1; // marker for menu/edit/set function
|
||||
ConfigMenu *menu;
|
||||
|
||||
//uint8_t *mapbuf = new uint8_t[10000]; // 8450 Byte without header
|
||||
//int mapbuf_size = 10000;
|
||||
//uint8_t *mapbuf = (uint8_t*) heap_caps_malloc(mapbuf_size, MALLOC_CAP_SPIRAM);
|
||||
GFXcanvas1 *canvas;
|
||||
const uint16_t map_width = 264;
|
||||
const uint16_t map_height = 260;
|
||||
bool map_valid = false;
|
||||
char map_service = 'R'; // (O)BP Service, (L)ocal Service, (R)emote Service
|
||||
double map_lat = 0; // current center of valid map
|
||||
double map_lon = 0;
|
||||
String server_name; // server with map service
|
||||
uint16_t server_port = 80;
|
||||
String tile_path;
|
||||
|
||||
String lengthformat;
|
||||
|
||||
double scale = 50; // Radius of display circle in meter, depends on lat
|
||||
uint8_t zoom = 15; // map zoom level
|
||||
|
||||
bool alarm = false;
|
||||
bool alarm_enabled = false;
|
||||
uint8_t alarm_range;
|
||||
|
||||
uint8_t chain_length;
|
||||
uint8_t chain = 0;
|
||||
|
||||
bool anchor_set = false;
|
||||
double anchor_lat;
|
||||
double anchor_lon;
|
||||
double anchor_depth;
|
||||
int anchor_ts; // time stamp anchor dropped
|
||||
|
||||
GwApi::BoatValue *bv_dbs; // depth below surface
|
||||
GwApi::BoatValue *bv_hdt; // true heading
|
||||
GwApi::BoatValue *bv_aws; // apparent wind speed
|
||||
GwApi::BoatValue *bv_awd; // apparent wind direction
|
||||
GwApi::BoatValue *bv_lat; // latitude, current
|
||||
GwApi::BoatValue *bv_lon; // longitude, current
|
||||
GwApi::BoatValue *bv_hdop; // horizontal position error
|
||||
|
||||
bool simulation = false;
|
||||
int last_mapsize = 0;
|
||||
String errmsg = "";
|
||||
int loops;
|
||||
int readbytes = 0;
|
||||
|
||||
void displayModeNormal(PageData &pageData) {
|
||||
|
||||
// get currrent boatvalues
|
||||
bv_dbs = pageData.values[0]; // DBS
|
||||
String sval_dbs = formatValue(bv_dbs, *commonData).svalue;
|
||||
String sunit_dbs = formatValue(bv_dbs, *commonData).unit;
|
||||
bv_hdt = pageData.values[1]; // HDT
|
||||
String sval_hdt = formatValue(bv_hdt, *commonData).svalue;
|
||||
bv_aws = pageData.values[2]; // AWS
|
||||
String sval_aws = formatValue(bv_aws, *commonData).svalue;
|
||||
String sunit_aws = formatValue(bv_aws, *commonData).unit;
|
||||
bv_awd = pageData.values[3]; // AWD
|
||||
String sval_awd = formatValue(bv_awd, *commonData).svalue;
|
||||
bv_lat = pageData.values[4]; // LAT
|
||||
String sval_lat = formatValue(bv_lat, *commonData).svalue;
|
||||
bv_lon = pageData.values[5]; // LON
|
||||
String sval_lon = formatValue(bv_lon, *commonData).svalue;
|
||||
bv_hdop = pageData.values[6]; // HDOP
|
||||
String sval_hdop = formatValue(bv_hdop, *commonData).svalue;
|
||||
String sunit_hdop = formatValue(bv_hdop, *commonData).unit;
|
||||
|
||||
commonData->logger->logDebug(GwLog::DEBUG, "Drawing at PageAnchor; DBS=%f, HDT=%f, AWS=%f", bv_dbs->value, bv_hdt->value, bv_aws->value);
|
||||
|
||||
// Draw canvas with background map
|
||||
// rhumb(map_lat, map_lon, bv_lat->value, bv_lon->value)
|
||||
int posdiff = 0;
|
||||
if (map_valid) {
|
||||
if (bv_lat->valid and bv_lon->valid) {
|
||||
// calculate movement since last map refresh
|
||||
posdiff = rhumb(map_lat, map_lon, bv_lat->value, bv_lon->value);
|
||||
if (posdiff > 25) {
|
||||
map_lat = bv_lat->value;
|
||||
map_lon = bv_lon->value;
|
||||
map_valid = getBackgroundMap(map_lat, map_lon, zoom);
|
||||
if (map_valid) {
|
||||
// prepare visible space for anchor-symbol or boat
|
||||
canvas->fillCircle(132, 130, 12, commonData->fgcolor);
|
||||
}
|
||||
}
|
||||
}
|
||||
getdisplay().drawBitmap(68, 20, canvas->getBuffer(), map_width, map_height, commonData->fgcolor);
|
||||
}
|
||||
|
||||
Point c = {200, 150}; // center = anchor position
|
||||
uint16_t r = 125;
|
||||
|
||||
// Circle as map border
|
||||
getdisplay().drawCircle(c.x, c.y, r, commonData->fgcolor);
|
||||
getdisplay().drawCircle(c.x, c.y, r + 1, commonData->fgcolor);
|
||||
|
||||
Point b = {200, 180}; // boat position while dropping anchor
|
||||
|
||||
const std::vector<Point> pts_boat = { // polygon lines
|
||||
{b.x - 5, b.y},
|
||||
{b.x - 5, b.y - 10},
|
||||
{b.x, b.y - 16},
|
||||
{b.x + 5, b.y - 10},
|
||||
{b.x + 5, b.y}
|
||||
};
|
||||
//rotatePoints then draw lines
|
||||
// TODO rotate boat according to current heading
|
||||
if (bv_hdt->valid) {
|
||||
if (map_valid) {
|
||||
Point b1 = rotatePoint(c, {b.x, b.y - 8}, bv_hdt->value * RAD_TO_DEG);
|
||||
getdisplay().fillCircle(b1.x, b1.y, 10, commonData->bgcolor);
|
||||
}
|
||||
drawPoly(rotatePoints(c, pts_boat, bv_hdt->value * RAD_TO_DEG), commonData->fgcolor);
|
||||
} else {
|
||||
// no heading available: draw north oriented
|
||||
if (map_valid) {
|
||||
getdisplay().fillCircle(b.x, b.y - 8, 10, commonData->bgcolor);
|
||||
}
|
||||
drawPoly(pts_boat, commonData->fgcolor);
|
||||
}
|
||||
|
||||
// Draw wind arrow
|
||||
const std::vector<Point> pts_wind = {
|
||||
{c.x, c.y - r + 25},
|
||||
{c.x - 12, c.y - r - 4},
|
||||
{c.x, c.y - r + 6},
|
||||
{c.x + 12, c.y - r - 4}
|
||||
};
|
||||
if (bv_awd->valid) {
|
||||
fillPoly4(rotatePoints(c, pts_wind, bv_awd->value), commonData->fgcolor);
|
||||
}
|
||||
|
||||
// Title and corner value headings
|
||||
getdisplay().setTextColor(commonData->fgcolor);
|
||||
getdisplay().setFont(&Ubuntu_Bold10pt8b);
|
||||
// Left
|
||||
getdisplay().setCursor(8, 36);
|
||||
getdisplay().print("Anchor");
|
||||
getdisplay().setCursor(8, 210);
|
||||
getdisplay().print("Depth");
|
||||
// Right
|
||||
drawTextRalign(392, 80, "Chain");
|
||||
drawTextRalign(392, 210, "Wind");
|
||||
|
||||
// Units
|
||||
getdisplay().setCursor(8, 272);
|
||||
getdisplay().print(sunit_dbs);
|
||||
drawTextRalign(392, 272, sunit_aws);
|
||||
// drawTextRalign(392, 100, lengthformat); // chain unit not implemented
|
||||
|
||||
// Corner values
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
getdisplay().setCursor(8, 54);
|
||||
getdisplay().print(anchor_set ? "Dropped" : "Ready"); // Anchor state
|
||||
getdisplay().setCursor(8, 72);
|
||||
getdisplay().print("Alarm: "); // Alarm state
|
||||
getdisplay().print(alarm_enabled ? "on" : "off");
|
||||
|
||||
getdisplay().setCursor(8, 120);
|
||||
getdisplay().print("Zoom");
|
||||
getdisplay().setCursor(8, 136);
|
||||
getdisplay().print(zoom);
|
||||
|
||||
getdisplay().setCursor(8, 160);
|
||||
getdisplay().print("diff");
|
||||
getdisplay().setCursor(8, 176);
|
||||
if (map_valid and bv_lat->valid and bv_lon->valid) {
|
||||
getdisplay().print(String(posdiff));
|
||||
} else {
|
||||
getdisplay().print("n/a");
|
||||
}
|
||||
|
||||
// Chain out TODO lengthformat ft/m
|
||||
drawTextRalign(392, 96, String(chain) + " m");
|
||||
drawTextRalign(392, 96+16, "of " + String(chain_length) + " m");
|
||||
|
||||
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
|
||||
|
||||
// Depth
|
||||
getdisplay().setCursor(8, 250);
|
||||
getdisplay().print(sval_dbs);
|
||||
|
||||
// Wind
|
||||
getdisplay().setCursor(320, 250);
|
||||
getdisplay().print(sval_aws);
|
||||
|
||||
// Position of boat in center of map
|
||||
getdisplay().setFont(&IBM8x8px);
|
||||
drawTextRalign(392, 34, sval_lat);
|
||||
drawTextRalign(392, 44, sval_lon);
|
||||
// quality
|
||||
String hdop = "HDOP: ";
|
||||
if (bv_hdop->valid) {
|
||||
hdop += String(round(bv_hdop->value));
|
||||
} else {
|
||||
hdop += " n/a";
|
||||
}
|
||||
drawTextRalign(392, 54, hdop);
|
||||
|
||||
// zoom scale
|
||||
getdisplay().drawLine(c.x + 10, c.y, c.x + r - 4, c.y, commonData->fgcolor);
|
||||
// arrow left
|
||||
getdisplay().drawLine(c.x + 10, c.y, c.x + 16, c.y - 4, commonData->fgcolor);
|
||||
getdisplay().drawLine(c.x + 10, c.y, c.x + 16, c.y + 4, commonData->fgcolor);
|
||||
// arrow right
|
||||
getdisplay().drawLine(c.x + r - 4, c.y, c.x + r - 10, c.y - 4, commonData->fgcolor);
|
||||
getdisplay().drawLine(c.x + r - 4, c.y, c.x + r - 10, c.y + 4, commonData->fgcolor);
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
drawTextCenter(c.x + r / 2, c.y + 8, String(scale, 0) + "m");
|
||||
|
||||
// draw anchor symbol (as bitmap)
|
||||
getdisplay().drawXBitmap(c.x - anchor_width / 2, c.y - anchor_height / 2,
|
||||
anchor_bits, anchor_width, anchor_height, commonData->fgcolor);
|
||||
|
||||
}
|
||||
|
||||
void displayModeConfig(PageData &pageData) {
|
||||
|
||||
getdisplay().setTextColor(commonData->fgcolor);
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||
getdisplay().setCursor(8, 48);
|
||||
getdisplay().print("Anchor configuration");
|
||||
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
|
||||
getdisplay().setCursor(8, 260);
|
||||
getdisplay().print("Press BACK to leave config");
|
||||
|
||||
/* getdisplay().setCursor(8, 68);
|
||||
getdisplay().printf("Server: %s", server_name.c_str());
|
||||
getdisplay().setCursor(8, 88);
|
||||
getdisplay().printf("Port: %d", server_port);
|
||||
getdisplay().setCursor(8, 108);
|
||||
getdisplay().printf("Tilepath: %s", tile_path.c_str());
|
||||
getdisplay().setCursor(8, 128);
|
||||
getdisplay().printf("Last mapsize: %d", last_mapsize);
|
||||
getdisplay().setCursor(8, 148);
|
||||
getdisplay().printf("Last error: %s", errmsg);
|
||||
getdisplay().setCursor(8, 168);
|
||||
getdisplay().printf("Loops: %d, Readbytes: %d", loops, readbytes);
|
||||
*/
|
||||
|
||||
GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT
|
||||
GwApi::BoatValue *bv_lon = pageData.values[5]; // LON
|
||||
if (!bv_lat->valid or !bv_lon->valid) {
|
||||
getdisplay().setCursor(8, 228);
|
||||
getdisplay().printf("No valid position: background map disabled");
|
||||
}
|
||||
|
||||
// Display menu
|
||||
getdisplay().setFont(&Ubuntu_Bold8pt8b);
|
||||
for (int i = 0 ; i < menu->getItemCount(); i++) {
|
||||
ConfigMenuItem *itm = menu->getItemByIndex(i);
|
||||
if (!itm) {
|
||||
commonData->logger->logDebug(GwLog::ERROR, "Menu item not found: %d", i);
|
||||
} else {
|
||||
Rect r = menu->getItemRect(i);
|
||||
bool inverted = (i == menu->getActiveIndex());
|
||||
drawTextBoxed(r, itm->getLabel(), commonData->fgcolor, commonData->bgcolor, inverted, false);
|
||||
if (inverted and editmode > 0) {
|
||||
// triangle as edit marker
|
||||
getdisplay().fillTriangle(r.x + r.w + 20, r.y, r.x + r.w + 30, r.y + r.h / 2, r.x + r.w + 20, r.y + r.h, commonData->fgcolor);
|
||||
}
|
||||
getdisplay().setCursor(r.x + r.w + 40, r.y + r.h - 4);
|
||||
if (itm->getType() == "int") {
|
||||
getdisplay().print(itm->getValue());
|
||||
getdisplay().print(itm->getUnit());
|
||||
} else {
|
||||
getdisplay().print(itm->getValue() == 0 ? "No" : "Yes");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public:
|
||||
PageAnchor(CommonData &common)
|
||||
{
|
||||
commonData = &common;
|
||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageAnchor");
|
||||
|
||||
String mapsource = common.config->getString(common.config->mapsource);
|
||||
if (mapsource == "Local Service") {
|
||||
map_service = 'L';
|
||||
server_name = common.config->getString(common.config->ipAddress);
|
||||
server_port = common.config->getInt(common.config->localPort);
|
||||
tile_path = "";
|
||||
} else if (mapsource == "Remote Service") {
|
||||
map_service = 'R';
|
||||
server_name = common.config->getString(common.config->mapServer);
|
||||
tile_path = common.config->getString(common.config->mapTilePath);
|
||||
} else { // OBP Service or undefined
|
||||
map_service = 'O';
|
||||
server_name = "norbert-walter.dnshome.de";
|
||||
tile_path = "";
|
||||
}
|
||||
zoom = common.config->getInt(common.config->zoomlevel);
|
||||
|
||||
lengthformat = common.config->getString(common.config->lengthFormat);
|
||||
chain_length = common.config->getInt(common.config->chainLength);
|
||||
|
||||
if (simulation) {
|
||||
map_lat = 53.56938345759218;
|
||||
map_lon = 9.679658234303275;
|
||||
}
|
||||
|
||||
canvas = new GFXcanvas1(264, 260); // Byte aligned, no padding!
|
||||
|
||||
// Initialize config menu
|
||||
menu = new ConfigMenu("Options", 40, 80);
|
||||
menu->setItemDimension(150, 20);
|
||||
ConfigMenuItem *newitem;
|
||||
newitem = menu->addItem("chain", "Chain out", "int", 0, "m");
|
||||
newitem->setRange(0, 200, {1, 2, 5, 10});
|
||||
newitem = menu->addItem("alarm", "Alarm", "bool", 0, "");
|
||||
newitem = menu->addItem("alarm", "Alarm range", "int", 50, "m");
|
||||
newitem->setRange(0, 200, {1, 2, 5, 10});
|
||||
newitem = menu->addItem("raise", "Raise Anchor", "bool", 0, "");
|
||||
newitem = menu->addItem("zoom", "Zoom", "int", 15, "");
|
||||
newitem->setRange(14, 17, {1});
|
||||
menu->setItemActive("chain");
|
||||
|
||||
}
|
||||
|
||||
void setupKeys(){
|
||||
Page::setupKeys();
|
||||
commonData->keydata[0].label = "CFG";
|
||||
#ifdef BOARD_OBP40S3
|
||||
commonData->keydata[1].label = "DROP";
|
||||
#endif
|
||||
#ifdef BOARD_OBP60S3
|
||||
commonData->keydata[4].label = "DROP";
|
||||
#endif
|
||||
}
|
||||
|
||||
// TODO OBP40 / OBP60 different handling
|
||||
int handleKey(int key) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "Page Anchor handle key %d", key);
|
||||
if (key == 1) { // Switch between normal and config mode
|
||||
if (mode == 'N') {
|
||||
mode = 'C';
|
||||
#ifdef BOARD_OBP40S3
|
||||
commonData->keydata[0].label = "BACK";
|
||||
commonData->keydata[1].label = "EDIT";
|
||||
#endif
|
||||
} else {
|
||||
mode = 'N';
|
||||
#ifdef BOARD_OBP40S3
|
||||
commonData->keydata[0].label = "CFG";
|
||||
commonData->keydata[1].label = anchor_set ? "RAISE": "DROP";
|
||||
#endif
|
||||
#ifdef BOARD_OBP60S3
|
||||
commonData->keydata[4].label = anchor_set ? "RAISE": "DROP";
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (key == 2) {
|
||||
if (mode == 'N') {
|
||||
anchor_set = !anchor_set;
|
||||
commonData->keydata[1].label = anchor_set ? "ALARM": "DROP";
|
||||
if (anchor_set) {
|
||||
anchor_lat = bv_lat->value;
|
||||
anchor_lon = bv_lon->value;
|
||||
anchor_depth = bv_dbs->value;
|
||||
// TODO set timestamp
|
||||
// anchor_ts =
|
||||
}
|
||||
return 0;
|
||||
} else if (mode == 'C') {
|
||||
// Change edit mode
|
||||
if (editmode > 0) {
|
||||
editmode = 0;
|
||||
commonData->keydata[1].label = "EDIT";
|
||||
} else {
|
||||
editmode = 1;
|
||||
commonData->keydata[1].label = "OK";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key == 9) {
|
||||
// OBP40 Down
|
||||
if (mode == 'C') {
|
||||
if (editmode > 0) {
|
||||
// decrease current menu item
|
||||
menu->getActiveItem()->decValue();
|
||||
} else {
|
||||
// move to next menu item
|
||||
menu->goNext();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (key == 10) {
|
||||
// OBP40 Up
|
||||
if (mode == 'C') {
|
||||
if (editmode > 0) {
|
||||
// increase current menu item
|
||||
ConfigMenuItem *itm = menu->getActiveItem();
|
||||
commonData->logger->logDebug(GwLog::LOG, "step = %d", itm->getStep());
|
||||
itm->incValue();
|
||||
} else {
|
||||
// move to previous menu item
|
||||
menu->goPrev();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// Code for keylock
|
||||
if (key == 11) {
|
||||
commonData->keylock = !commonData->keylock;
|
||||
return 0;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
int rhumb(double lat1, double lon1, double lat2, double lon2) {
|
||||
// calc distance in m between two geo points
|
||||
static const double degToRad = M_PI / 180.0;
|
||||
lat1 = degToRad * lat1;
|
||||
lon1 = degToRad * lon1;
|
||||
lat2 = degToRad * lat2;
|
||||
lon2 = degToRad * lon2;
|
||||
double dlon = lon2 - lon1;
|
||||
double dlat = lat2 - lat1;
|
||||
double mlat = (lat1 + lat2) / 2;
|
||||
return (int) (6371000 * sqrt(pow(dlat, 2) + pow(cos(mlat) * dlon, 2)));
|
||||
}
|
||||
|
||||
bool getBackgroundMap(double lat, double lon, uint8_t zoom) {
|
||||
// HTTP-Request for map
|
||||
// TODO über pagedata -> status abfragen?
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
return false;
|
||||
}
|
||||
bool valid = false;
|
||||
HTTPClient http;
|
||||
const char* headerKeys[] = { "Content-Encoding", "Content-Length" };
|
||||
http.collectHeaders(headerKeys, 2);
|
||||
String url = "http://" + server_name + "/" + tile_path;
|
||||
String parameter = "?lat=" + String(lat, 6) + "&lon=" + String(lon, 6)+ "&zoom=" + String(zoom)
|
||||
+ "&width=" + String(map_width) + "&height=" + String(map_height);
|
||||
commonData->logger->logDebug(GwLog::LOG, "HTTP query: %s", String(url + parameter).c_str());
|
||||
http.begin(url + parameter);
|
||||
http.addHeader("Accept-Encoding", "deflate");
|
||||
int httpCode = http.GET();
|
||||
if (httpCode > 0) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "HTTP GET result code: %d", httpCode);
|
||||
if (httpCode == HTTP_CODE_OK) {
|
||||
WiFiClient* stream = http.getStreamPtr();
|
||||
int size = http.getSize();
|
||||
String encoding = http.header("Content-Encoding");
|
||||
commonData->logger->logDebug(GwLog::LOG, "HTTP size: %d, encoding: '%s'", size, encoding);
|
||||
bool is_gzip = encoding.equalsIgnoreCase("deflate");
|
||||
|
||||
uint8_t header[14]; // max: P4<LF>wwww wwww<LF>
|
||||
int header_size = 0;
|
||||
bool header_read = false;
|
||||
int n = 0;
|
||||
int ix = 0;
|
||||
|
||||
uint8_t* buf = canvas->getBuffer();
|
||||
|
||||
if (is_gzip) {
|
||||
/* gzip compressed data
|
||||
* has to be decompressed into a buffer big enough
|
||||
* to hold the whole data.
|
||||
* so the PBM header is included
|
||||
* search a method to use that as canvas without
|
||||
* additional copy
|
||||
*/
|
||||
commonData->logger->logDebug(GwLog::LOG, "Map received in gzip encoding");
|
||||
|
||||
#define HEADER_MAX 24
|
||||
#define HTTP_CHUNK 512
|
||||
uint8_t in_buf[HTTP_CHUNK];
|
||||
uint8_t header_buf[HEADER_MAX];
|
||||
tinfl_decompressor decomp;
|
||||
tinfl_init(&decomp);
|
||||
size_t bitmap_written = 0;
|
||||
size_t header_written = 0;
|
||||
bool header_done = false;
|
||||
int row_bytes = 0;
|
||||
size_t expected_bitmap = 0;
|
||||
|
||||
while (stream->connected() || stream->available()) {
|
||||
int bytes_read = stream->read(in_buf, HTTP_CHUNK);
|
||||
if (bytes_read <= 0) break;
|
||||
commonData->logger->logDebug(GwLog::LOG, "stream: bytes_read=%d", bytes_read);
|
||||
size_t in_ofs = 0; // offset
|
||||
while (in_ofs < (size_t)bytes_read) {
|
||||
size_t in_size = bytes_read - in_ofs;
|
||||
size_t out_size;
|
||||
uint8_t *out_ptr;
|
||||
uint8_t *out_ptr_next;
|
||||
if (!header_done) {
|
||||
if (header_written >= HEADER_MAX) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "PBM header too large");
|
||||
return false;
|
||||
}
|
||||
out_ptr = header_buf + header_written;
|
||||
out_size = HEADER_MAX - header_written;
|
||||
} else {
|
||||
out_ptr = buf + bitmap_written;
|
||||
out_size = expected_bitmap - bitmap_written;
|
||||
}
|
||||
commonData->logger->logDebug(GwLog::LOG, "in_size=%d, out_size=%d", in_size, out_size);
|
||||
// TODO correct loop !!!
|
||||
// tinfl_status tinfl_decompress(
|
||||
// tinfl_decompressor *r,
|
||||
// const mz_uint8 *pIn_buf_next,
|
||||
// size_t *pIn_buf_size,
|
||||
// mz_uint8 *pOut_buf_start
|
||||
// mz_uint8 *pOut_buf_next,
|
||||
// size_t *pOut_buf_size,
|
||||
// const mz_uint32 decomp_flags)
|
||||
tinfl_status status = tinfl_decompress(
|
||||
&decomp,
|
||||
in_buf + in_ofs, // start address in input buffer
|
||||
&in_size, // number of bytes to process
|
||||
out_ptr, // start of output buffer
|
||||
out_ptr, // next write position in output buffer
|
||||
&out_size, // free size in output buffer
|
||||
// TINFL_FLAG_PARSE_ZLIB_HEADER |
|
||||
TINFL_FLAG_HAS_MORE_INPUT |
|
||||
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF
|
||||
);
|
||||
if (status < 0) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "Decompression error (%d)", status);
|
||||
return false;
|
||||
}
|
||||
in_ofs += in_size;
|
||||
commonData->logger->logDebug(GwLog::LOG, "in_size=%d, in_ofs=%d", in_size, in_ofs);
|
||||
|
||||
if (!header_done) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "Decoding header");
|
||||
header_written += out_size;
|
||||
|
||||
// Detect header end: two '\n'
|
||||
char *first_nl = strchr((char*)header_buf, '\n');
|
||||
if (!first_nl) continue;
|
||||
|
||||
char *second_nl = strchr(first_nl + 1, '\n');
|
||||
if (!second_nl) continue;
|
||||
|
||||
// Null-terminate header for sscanf
|
||||
header_buf[header_written < HEADER_MAX ? header_written : HEADER_MAX - 1] = 0;
|
||||
|
||||
// Check magic
|
||||
if (strncmp((char*)header_buf, "P4", 2) != 0) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "Invalid PBM magic");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse width and height strictly
|
||||
int header_width = 0, header_height = 0;
|
||||
if (sscanf((char*)header_buf, "P4\n%d %d", &header_width, &header_height) != 2) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "Failed to parse PBM dimensions");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header_width != map_width || header_height != map_height) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "PBM size mismatch: header %dx%d, requested %dx%d\n",
|
||||
header_width, header_height, map_width, map_height);
|
||||
return false;
|
||||
}
|
||||
commonData->logger->logDebug(GwLog::LOG, "Header: %dx%d", header_width, header_height);
|
||||
|
||||
// Compute row bytes and expected bitmap size
|
||||
row_bytes = (header_width + 7) / 8;
|
||||
commonData->logger->logDebug(GwLog::LOG, "row_bytes=%d", row_bytes);
|
||||
expected_bitmap = (size_t)row_bytes * header_height;
|
||||
commonData->logger->logDebug(GwLog::LOG, "expected_bitmap=%d", expected_bitmap);
|
||||
|
||||
// Copy any extra decompressed bitmap after header
|
||||
size_t header_size = (second_nl + 1) - (char*)header_buf;
|
||||
commonData->logger->logDebug(GwLog::LOG, "header_size=%d", header_size);
|
||||
size_t extra_bitmap = header_written - header_size;
|
||||
commonData->logger->logDebug(GwLog::LOG, "extra bitmap=%d", extra_bitmap);
|
||||
|
||||
header_done = true;
|
||||
|
||||
if (extra_bitmap > 0) {
|
||||
memcpy(buf, header_buf + header_size, extra_bitmap);
|
||||
bitmap_written = extra_bitmap;
|
||||
}
|
||||
} else {
|
||||
bitmap_written += out_size;
|
||||
if (bitmap_written >= expected_bitmap) {
|
||||
commonData->logger->logDebug(GwLog::LOG, "Image fully received");
|
||||
}
|
||||
}
|
||||
commonData->logger->logDebug(GwLog::LOG, "bitmap_written=%d", bitmap_written);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// uncompressed data
|
||||
commonData->logger->logDebug(GwLog::LOG, "Map received uncompressed");
|
||||
while (stream->available()) {
|
||||
uint8_t b = stream->read();
|
||||
n += 1;
|
||||
if ((! header_read) and (n < 13) ) {
|
||||
header[n-1] = b;
|
||||
if ((n > 3) and (b == 0x0a)) {
|
||||
header_read = true;
|
||||
header_size = n;
|
||||
header[n] = 0;
|
||||
}
|
||||
} else {
|
||||
// write image data to canvas buffer
|
||||
buf[ix++] = b;
|
||||
}
|
||||
}
|
||||
if (n == size) {
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
commonData->logger->logDebug(GwLog::LOG, "HTTP: final bytesRead=%d, header-size=%d", n, header_size);
|
||||
} else {
|
||||
commonData->logger->logDebug(GwLog::LOG, "HTTP result #%d", httpCode);
|
||||
}
|
||||
} else {
|
||||
commonData->logger->logDebug(GwLog::ERROR, "HTTP error #%d", httpCode);
|
||||
}
|
||||
http.end();
|
||||
return valid;
|
||||
}
|
||||
|
||||
void displayNew(PageData &pageData){
|
||||
|
||||
GwApi::BoatValue *bv_lat = pageData.values[4]; // LAT
|
||||
GwApi::BoatValue *bv_lon = pageData.values[5]; // LON
|
||||
|
||||
// check if valid data available
|
||||
if (!bv_lat->valid or !bv_lon->valid) {
|
||||
map_valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
errmsg = "";
|
||||
|
||||
map_lat = bv_lat->value; // save for later comparison
|
||||
map_lon = bv_lon->value;
|
||||
map_valid = getBackgroundMap(map_lat, map_lon, zoom);
|
||||
|
||||
if (map_valid) {
|
||||
// prepare visible space for anchor-symbol or boat
|
||||
canvas->fillCircle(132, 130, 10, commonData->fgcolor);
|
||||
}
|
||||
};
|
||||
|
||||
void display_side_keys() {
|
||||
// An rechter Seite neben dem Rad inc, dec, set etc ?
|
||||
}
|
||||
|
||||
int displayPage(PageData &pageData) {
|
||||
|
||||
// Logging boat values
|
||||
commonData->logger->logDebug(GwLog::LOG, "Drawing at PageAnchor; Mode=%c", mode);
|
||||
|
||||
// Set display in partial refresh mode
|
||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||
|
||||
if (mode == 'N') {
|
||||
displayModeNormal(pageData);
|
||||
} else if (mode == 'C') {
|
||||
displayModeConfig(pageData);
|
||||
}
|
||||
|
||||
return PAGE_UPDATE;
|
||||
};
|
||||
};
|
||||
|
||||
static Page *createPage(CommonData &common){
|
||||
return new PageAnchor(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 registerPageAnchor(
|
||||
"Anchor", // Page name
|
||||
createPage, // Action
|
||||
0, // Number of bus values depends on selection in Web configuration
|
||||
{"DBS", "HDT", "AWS", "AWD", "LAT", "LON", "HDOP"}, // Names of bus values undepends on selection in Web configuration (refer GwBoatData.h)
|
||||
true // Show display header on/off
|
||||
);
|
||||
|
||||
#endif
|
||||
263
lib/obp60task/PageAutopilot.cpp
Normal file
263
lib/obp60task/PageAutopilot.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
|
||||
// These constants have to match the declaration below in :
|
||||
// PageDescription registerPageAutopilot(
|
||||
// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page
|
||||
const int HowManyValues = 9;
|
||||
|
||||
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 ShowDBT = 5;
|
||||
const int ShowXTE = 6;
|
||||
const int ShowDTW = 7;
|
||||
const int ShowBTW = 8;
|
||||
|
||||
const int Compass_X0 = 200; // X center point of compass band
|
||||
const int Compass_Y0 = 220; // Y position of compass lines
|
||||
const int Compass_LineLength = 22; // Length of compass lines
|
||||
const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
|
||||
|
||||
class PageAutopilot : public Page
|
||||
{
|
||||
int WhichDataCompass = ShowHDM; // Start value
|
||||
int WhichDataDisplay = ShowHDM; // Start value
|
||||
|
||||
public:
|
||||
PageAutopilot(CommonData &common){
|
||||
commonData = &common;
|
||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageAutopilot");
|
||||
}
|
||||
|
||||
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 > ShowDBT)
|
||||
WhichDataDisplay = ShowHDM;
|
||||
}
|
||||
|
||||
if(key == 11){
|
||||
commonData->keylock = !commonData->keylock;
|
||||
return 0; // Commit the key
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
int 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];
|
||||
FormattedData 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 PageAutopilot: %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 PAGE_OK; // WTF why this statement?
|
||||
|
||||
//***********************************************************
|
||||
|
||||
// Set display in partial refresh mode
|
||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||
getdisplay().setTextColor(commonData->fgcolor);
|
||||
|
||||
// Horizontal line 2 pix top & bottom
|
||||
// Print data on top half
|
||||
getdisplay().fillRect(0, 130, 400, 2, commonData->fgcolor);
|
||||
getdisplay().setFont(&Ubuntu_Bold20pt8b);
|
||||
getdisplay().setCursor(10, 70);
|
||||
getdisplay().print(DataName[WhichDataDisplay]); // Page name
|
||||
// Show unit
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||
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_Bold16pt8b);
|
||||
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;
|
||||
|
||||
return PAGE_UPDATE;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
static Page *createPage(CommonData &common){
|
||||
return new PageAutopilot(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 registerPageAutopilot(
|
||||
"Autopilot", // Page name
|
||||
createPage, // Action
|
||||
0, // Number of bus values depends on selection in Web configuration
|
||||
{"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page
|
||||
true // Show display header on/off
|
||||
);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,10 +17,10 @@ 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
|
||||
const int Compass_X0 = 200; // X center point of compass band
|
||||
const int Compass_Y0 = 220; // Y position of compass lines
|
||||
const int Compass_LineLength = 22; // Length of compass lines
|
||||
const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
|
||||
|
||||
class PageCompass : public Page
|
||||
{
|
||||
|
||||
@@ -22,12 +22,22 @@ bool button3 = false;
|
||||
bool button4 = false;
|
||||
bool button5 = false;
|
||||
|
||||
public:
|
||||
public:
|
||||
PageDigitalOut(CommonData &common){
|
||||
commonData = &common;
|
||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageDigitalOut");
|
||||
}
|
||||
|
||||
// Set botton labels
|
||||
virtual void setupKeys(){
|
||||
Page::setupKeys();
|
||||
commonData->keydata[0].label = "1";
|
||||
commonData->keydata[1].label = "2";
|
||||
commonData->keydata[2].label = "3";
|
||||
commonData->keydata[3].label = "4";
|
||||
commonData->keydata[4].label = "5";
|
||||
}
|
||||
|
||||
virtual int handleKey(int key){
|
||||
// Code for keylock
|
||||
if(key == 11){
|
||||
@@ -37,31 +47,31 @@ public:
|
||||
// Code for button 1
|
||||
if(key == 1){
|
||||
button1 = !button1;
|
||||
setPCF8574PortPin(0, button1 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||
setPCF8574PortPinModul1(0, button1 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||
return 0; // Commit the key
|
||||
}
|
||||
// Code for button 2
|
||||
if(key == 2){
|
||||
button2 = !button2;
|
||||
setPCF8574PortPin(1, button2 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||
setPCF8574PortPinModul1(1, button2 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||
return 0; // Commit the key
|
||||
}
|
||||
// Code for button 3
|
||||
if(key == 3){
|
||||
button3 = !button3;
|
||||
setPCF8574PortPin(2, button3 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||
setPCF8574PortPinModul1(2, button3 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||
return 0; // Commit the key
|
||||
}
|
||||
// Code for button 4
|
||||
if(key == 4){
|
||||
button4 = !button4;
|
||||
setPCF8574PortPin(3, button4 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||
setPCF8574PortPinModul1(3, button4 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||
return 0; // Commit the key
|
||||
}
|
||||
// Code for button 5
|
||||
if(key == 5){
|
||||
button5 = !button5;
|
||||
setPCF8574PortPin(4, button5 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||
setPCF8574PortPinModul1(4, button5 ? 0 : 1); // Attention! Inverse logic for PCF8574
|
||||
return 0; // Commit the key
|
||||
}
|
||||
return key;
|
||||
@@ -77,6 +87,11 @@ public:
|
||||
bool holdvalues = config->getBool(config->holdvalues);
|
||||
String flashLED = config->getString(config->flashLED);
|
||||
String backlightMode = config->getString(config->backlight);
|
||||
String name1 = config->getString(config->mod1Out1);
|
||||
String name2 = config->getString(config->mod1Out2);
|
||||
String name3 = config->getString(config->mod1Out3);
|
||||
String name4 = config->getString(config->mod1Out4);
|
||||
String name5 = config->getString(config->mod1Out5);
|
||||
|
||||
// Optical warning by limit violation (unused)
|
||||
if(String(flashLED) == "Limit Violation"){
|
||||
@@ -94,17 +109,23 @@ public:
|
||||
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
|
||||
getdisplay().setTextColor(commonData->fgcolor);
|
||||
getdisplay().setFont(&Ubuntu_Bold12pt8b);
|
||||
getdisplay().fillRoundRect(200, 250 , 200, 25, 5, commonData->fgcolor); // Black rect
|
||||
getdisplay().fillRoundRect(202, 252 , 196, 21, 5, commonData->bgcolor); // White rect
|
||||
getdisplay().setCursor(210, 270);
|
||||
getdisplay().print("Map server lost");
|
||||
|
||||
// Set botton labels
|
||||
commonData->keydata[0].label = "BTN 1";
|
||||
commonData->keydata[1].label = "BTN 2";
|
||||
commonData->keydata[2].label = "BTN 3";
|
||||
commonData->keydata[3].label = "BTN 4";
|
||||
commonData->keydata[4].label = "BTN 5";
|
||||
// Write text
|
||||
getdisplay().setCursor(100, 50 + 8);
|
||||
getdisplay().print(name1);
|
||||
getdisplay().setCursor(100, 100 + 8);
|
||||
getdisplay().print(name2);
|
||||
getdisplay().setCursor(100, 150 + 8);
|
||||
getdisplay().print(name3);
|
||||
getdisplay().setCursor(100,200 + 8);
|
||||
getdisplay().print(name4);
|
||||
getdisplay().setCursor(100, 250 + 8);
|
||||
getdisplay().print(name5);
|
||||
// Draw bottons
|
||||
drawButtonCenter(50, 50, 40, 27, "1", commonData->fgcolor, commonData->bgcolor, button1);
|
||||
drawButtonCenter(50, 100, 40, 27, "2", commonData->fgcolor, commonData->bgcolor, button2);
|
||||
drawButtonCenter(50, 150, 40, 27, "3", commonData->fgcolor, commonData->bgcolor, button3);
|
||||
drawButtonCenter(50, 200, 40, 27, "4", commonData->fgcolor, commonData->bgcolor, button4);
|
||||
drawButtonCenter(50, 250, 40, 27, "5", commonData->fgcolor, commonData->bgcolor, button5);
|
||||
|
||||
return PAGE_UPDATE;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageFourValues : public Page
|
||||
{
|
||||
@@ -46,7 +45,6 @@ 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
|
||||
@@ -56,7 +54,6 @@ class PageFourValues : public Page
|
||||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -66,7 +63,6 @@ class PageFourValues : public Page
|
||||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
|
||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||
double value3 = bvalue3->value; // Value as double in SI unit
|
||||
bool valid3 = bvalue3->valid; // Valid information
|
||||
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -76,7 +72,6 @@ class PageFourValues : public Page
|
||||
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Fourth element in list
|
||||
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||
name4 = name4.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
|
||||
double value4 = bvalue4->value; // Value as double in SI unit
|
||||
bool valid4 = bvalue4->valid; // Valid information
|
||||
String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageFourValues2 : public Page
|
||||
{
|
||||
@@ -46,7 +45,6 @@ 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
|
||||
@@ -56,7 +54,6 @@ 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
|
||||
@@ -66,7 +63,6 @@ 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
|
||||
@@ -76,7 +72,6 @@ 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
|
||||
|
||||
@@ -19,20 +19,28 @@ bool firstRun = true; // Detect the first page run
|
||||
int zoom = 15; // Default zoom level
|
||||
bool showValues = false; // Show values HDT, SOG, DBT in navigation map
|
||||
|
||||
private:
|
||||
private:
|
||||
uint8_t* imageBackupData = nullptr;
|
||||
int imageBackupWidth = 0;
|
||||
int imageBackupHeight = 0;
|
||||
size_t imageBackupSize = 0;
|
||||
bool hasImageBackup = false;
|
||||
|
||||
public:
|
||||
public:
|
||||
PageNavigation(CommonData &common){
|
||||
commonData = &common;
|
||||
common.logger->logDebug(GwLog::LOG,"Instantiate PageNavigation");
|
||||
imageBackupData = (uint8_t*)heap_caps_malloc((GxEPD_WIDTH * GxEPD_HEIGHT), MALLOC_CAP_SPIRAM);
|
||||
}
|
||||
|
||||
// Set botton labels
|
||||
virtual void setupKeys(){
|
||||
Page::setupKeys();
|
||||
commonData->keydata[0].label = "ZOOM -";
|
||||
commonData->keydata[1].label = "ZOOM +";
|
||||
commonData->keydata[4].label = "VALUES";
|
||||
}
|
||||
|
||||
virtual int handleKey(int key){
|
||||
// Code for keylock
|
||||
if(key == 11){
|
||||
@@ -475,12 +483,7 @@ public:
|
||||
getdisplay().setCursor(70, 85);
|
||||
getdisplay().print(svalue6);
|
||||
}
|
||||
|
||||
// Set botton labels
|
||||
commonData->keydata[0].label = "ZOOM -";
|
||||
commonData->keydata[1].label = "ZOOM +";
|
||||
commonData->keydata[4].label = "VALUES";
|
||||
|
||||
|
||||
return PAGE_UPDATE;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
#include "OBPDataOperations.h"
|
||||
#include "OBPcharts.h"
|
||||
|
||||
@@ -88,7 +87,6 @@ private:
|
||||
|
||||
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
|
||||
@@ -164,9 +162,13 @@ public:
|
||||
constexpr int ZOOM_KEY = 1;
|
||||
#endif
|
||||
|
||||
if (dataHstryBuf) { // show "Mode" key only if chart supported boat data type is available
|
||||
if (dataHstryBuf) { // show "Mode" key only if chart-supported boat data type is available
|
||||
commonData->keydata[0].label = "MODE";
|
||||
commonData->keydata[ZOOM_KEY].label = "ZOOM";
|
||||
if (pageMode != VALUE) { // show "ZOOM" key only if chart is visible
|
||||
commonData->keydata[ZOOM_KEY].label = "ZOOM";
|
||||
} else {
|
||||
commonData->keydata[ZOOM_KEY].label = "";
|
||||
}
|
||||
} else {
|
||||
commonData->keydata[0].label = "";
|
||||
commonData->keydata[ZOOM_KEY].label = "";
|
||||
@@ -191,14 +193,15 @@ public:
|
||||
pageMode = VALUE;
|
||||
break;
|
||||
}
|
||||
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||
return 0; // Commit the key
|
||||
}
|
||||
|
||||
// Set time frame to show for history chart
|
||||
// Set time frame to show for chart
|
||||
#if defined BOARD_OBP60S3
|
||||
if (key == 5) {
|
||||
if (key == 5 && pageMode != VALUE) {
|
||||
#elif defined BOARD_OBP40S3
|
||||
if (key == 2) {
|
||||
if (key == 2 && pageMode != VALUE) {
|
||||
#endif
|
||||
if (dataIntv == 1) {
|
||||
dataIntv = 2;
|
||||
@@ -249,7 +252,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
setupKeys(); // adjust <mode> key depending on chart supported boat data type
|
||||
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||
}
|
||||
|
||||
int displayPage(PageData& pageData)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageRudderPosition : public Page
|
||||
{
|
||||
@@ -41,7 +40,6 @@ 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
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
const int SixValues_x1 = 5;
|
||||
const int SixValues_DeltaX = 200;
|
||||
@@ -57,7 +56,6 @@ class PageSixValues : public Page
|
||||
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
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageThreeValues : public Page
|
||||
{
|
||||
@@ -44,7 +43,6 @@ 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
|
||||
@@ -54,7 +52,6 @@ class PageThreeValues : public Page
|
||||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
|
||||
double value2 = bvalue2->value; // Value as double in SI unit
|
||||
bool valid2 = bvalue2->valid; // Valid information
|
||||
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -64,7 +61,6 @@ class PageThreeValues : public Page
|
||||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
|
||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
|
||||
double value3 = bvalue3->value; // Value as double in SI unit
|
||||
bool valid3 = bvalue3->valid; // Valid information
|
||||
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
#include "OBPDataOperations.h"
|
||||
#include "OBPcharts.h"
|
||||
|
||||
@@ -69,7 +68,6 @@ private:
|
||||
int yOffset = YOFFSET * i;
|
||||
String name = xdrDelete(bValue[i]->getName()); // Value name
|
||||
name = name.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bValue[i], logger); // Check if boat data value is to be calibrated
|
||||
double value = bValue[i]->value; // Value as double in SI unit
|
||||
bool valid = bValue[i]->valid; // Valid information
|
||||
String sValue = formatValue(bValue[i], *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -116,7 +114,7 @@ private:
|
||||
}
|
||||
|
||||
if (numValues == 2 && mode == FULL) { // print line only, if we want to show 2 data values
|
||||
getdisplay().fillRect(0, 145, width, 3, commonData->fgcolor); // Horizontal line 3 pix
|
||||
getdisplay().fillRect(0, 145, width, 3, commonData->fgcolor); // Horizontal line 3 pix
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +149,11 @@ public:
|
||||
|
||||
if (dataHstryBuf[0] || dataHstryBuf[1]) { // show "Mode" key only if at least 1 chart supported boat data type is available
|
||||
commonData->keydata[0].label = "MODE";
|
||||
commonData->keydata[ZOOM_KEY].label = "ZOOM";
|
||||
if (pageMode != VALUES) { // show "ZOOM" key only if chart is visible
|
||||
commonData->keydata[ZOOM_KEY].label = "ZOOM";
|
||||
} else {
|
||||
commonData->keydata[ZOOM_KEY].label = "";
|
||||
}
|
||||
} else {
|
||||
commonData->keydata[0].label = "";
|
||||
commonData->keydata[ZOOM_KEY].label = "";
|
||||
@@ -193,14 +195,15 @@ public:
|
||||
pageMode = VALUES;
|
||||
break;
|
||||
}
|
||||
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||
return 0; // Commit the key
|
||||
}
|
||||
|
||||
// Set time frame to show for history chart
|
||||
// Set time frame to show for chart
|
||||
#if defined BOARD_OBP60S3
|
||||
if (key == 5) {
|
||||
if (key == 5 && pageMode != VALUES) {
|
||||
#elif defined BOARD_OBP40S3
|
||||
if (key == 2) {
|
||||
if (key == 2 && pageMode != VALUES) {
|
||||
#endif
|
||||
if (dataIntv == 1) {
|
||||
dataIntv = 2;
|
||||
@@ -253,7 +256,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
setupKeys(); // adjust <mode> key depending on chart supported boat data type
|
||||
setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
|
||||
}
|
||||
|
||||
int displayPage(PageData& pageData)
|
||||
@@ -287,13 +290,13 @@ public:
|
||||
showData(bValue, FULL);
|
||||
|
||||
} else if (pageMode == VAL1_CHART) { // show data value 1 and chart
|
||||
showData({bValue[0]}, HALF);
|
||||
showData({ bValue[0] }, HALF);
|
||||
if (dataChart[0]) {
|
||||
dataChart[0]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[0]);
|
||||
}
|
||||
|
||||
} else if (pageMode == VAL2_CHART) { // show data value 2 and chart
|
||||
showData({bValue[1]}, HALF);
|
||||
showData({ bValue[1] }, HALF);
|
||||
if (dataChart[1]) {
|
||||
dataChart[1]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[1]);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "N2kMessages.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
#define front_width 120
|
||||
#define front_height 162
|
||||
@@ -324,7 +323,6 @@ 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
|
||||
@@ -338,7 +336,6 @@ 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) {
|
||||
|
||||
@@ -196,7 +196,7 @@ public:
|
||||
int displayPage(PageData& pageData)
|
||||
{
|
||||
LOG_DEBUG(GwLog::LOG, "Display PageWindPlot");
|
||||
ulong pageTime = millis();
|
||||
// ulong pageTime = millis();
|
||||
|
||||
if (showTruW != oldShowTruW) {
|
||||
|
||||
@@ -243,7 +243,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: page time %ldms", millis() - pageTime);
|
||||
// LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: page time %ldms", millis() - pageTime);
|
||||
return PAGE_UPDATE;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageWindRose : public Page
|
||||
{
|
||||
@@ -52,7 +51,6 @@ 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
|
||||
@@ -67,7 +65,6 @@ public:
|
||||
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
|
||||
String name2 = xdrDelete(bvalue2->getName()); // Value name
|
||||
name2 = name2.substring(0, 6); // String length limit for value name
|
||||
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
|
||||
@@ -81,7 +78,6 @@ public:
|
||||
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
|
||||
String name3 = xdrDelete(bvalue3->getName()); // Value name
|
||||
name3 = name3.substring(0, 6); // String length limit for value name
|
||||
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
|
||||
@@ -95,7 +91,6 @@ public:
|
||||
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Fourth element in list
|
||||
String name4 = xdrDelete(bvalue4->getName()); // Value name
|
||||
name4 = name4.substring(0, 6); // String length limit for value name
|
||||
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
|
||||
@@ -109,7 +104,6 @@ public:
|
||||
GwApi::BoatValue *bvalue5 = pageData.values[4]; // Fifth element in list
|
||||
String name5 = xdrDelete(bvalue5->getName()); // Value name
|
||||
name5 = name5.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
|
||||
double value5 = bvalue5->value; // Value as double in SI unit
|
||||
bool valid5 = bvalue5->valid; // Valid information
|
||||
String svalue5 = formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
@@ -123,7 +117,6 @@ public:
|
||||
GwApi::BoatValue *bvalue6 = pageData.values[5]; // Sixth element in list
|
||||
String name6 = xdrDelete(bvalue6->getName()); // Value name
|
||||
name6 = name6.substring(0, 6); // String length limit for value name
|
||||
calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
|
||||
double value6 = bvalue6->value; // Value as double in SI unit
|
||||
bool valid6 = bvalue6->valid; // Valid information
|
||||
String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Pagedata.h"
|
||||
#include "OBP60Extensions.h"
|
||||
#include "BoatDataCalibration.h"
|
||||
|
||||
class PageWindRoseFlex : public Page
|
||||
{
|
||||
@@ -79,7 +78,6 @@ 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
|
||||
@@ -97,7 +95,6 @@ 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) {
|
||||
@@ -122,7 +119,6 @@ public:
|
||||
else{
|
||||
name3font=Ubuntu_Bold12pt8b;
|
||||
}
|
||||
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
|
||||
@@ -142,7 +138,6 @@ public:
|
||||
else{
|
||||
name4font=Ubuntu_Bold12pt8b;
|
||||
}
|
||||
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
|
||||
@@ -162,7 +157,6 @@ public:
|
||||
else{
|
||||
name5font=Ubuntu_Bold12pt8b;
|
||||
}
|
||||
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
|
||||
@@ -182,7 +176,6 @@ public:
|
||||
else{
|
||||
name6font=Ubuntu_Bold8pt8b;
|
||||
}
|
||||
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
|
||||
|
||||
@@ -51,7 +51,7 @@ typedef struct{
|
||||
double rotationAngle = 0; // Rotation angle in radiant
|
||||
bool validRotAngle = false; // Valid flag magnet present for rotation sensor
|
||||
struct tm rtcTime; // UTC time from internal RTC
|
||||
bool rtcValid = false;
|
||||
bool rtcValid = false; // Internal RTC chip
|
||||
int sunsetHour = 0;
|
||||
int sunsetMinute = 0;
|
||||
int sunriseHour = 0;
|
||||
@@ -110,6 +110,7 @@ typedef struct{
|
||||
AlarmData alarm;
|
||||
GwApi::BoatValue *time = nullptr;
|
||||
GwApi::BoatValue *date = nullptr;
|
||||
float tz = 0.0; // timezone from config
|
||||
uint16_t fgcolor;
|
||||
uint16_t bgcolor;
|
||||
bool keylock = false;
|
||||
|
||||
@@ -75,6 +75,20 @@
|
||||
"obp40": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "chainLength",
|
||||
"label": "Anchor Chain Length [m]",
|
||||
"type": "number",
|
||||
"default": "0",
|
||||
"check": "checkMinMax",
|
||||
"min": 0,
|
||||
"max": 255,
|
||||
"description": "The length of the anchor chain [0...255m]",
|
||||
"category": "OBP40 Settings",
|
||||
"capabilities": {
|
||||
"obp40": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fuelTank",
|
||||
"label": "Fuel Tank [l]",
|
||||
@@ -672,6 +686,61 @@
|
||||
"obp40": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mod1Out1",
|
||||
"label": "Name1",
|
||||
"type": "string",
|
||||
"default": "text1",
|
||||
"description": "Button name",
|
||||
"category": "OBP40 IO-Modul1",
|
||||
"capabilities": {
|
||||
"obp40":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mod1Out2",
|
||||
"label": "Name2",
|
||||
"type": "string",
|
||||
"default": "text2",
|
||||
"description": "Button name",
|
||||
"category": "OBP40 IO-Modul1",
|
||||
"capabilities": {
|
||||
"obp40":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mod1Out3",
|
||||
"label": "Name3",
|
||||
"type": "string",
|
||||
"default": "text3",
|
||||
"description": "Button name",
|
||||
"category": "OBP40 IO-Modul1",
|
||||
"capabilities": {
|
||||
"obp40":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mod1Out4",
|
||||
"label": "Name4",
|
||||
"type": "string",
|
||||
"default": "text4",
|
||||
"description": "Button name",
|
||||
"category": "OBP40 IO-Modul1",
|
||||
"capabilities": {
|
||||
"obp40":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mod1Out5",
|
||||
"label": "Name5",
|
||||
"type": "string",
|
||||
"default": "text5",
|
||||
"description": "Button name",
|
||||
"category": "OBP40 IO-Modul1",
|
||||
"capabilities": {
|
||||
"obp40":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tSensitivity",
|
||||
"label": "Touch Sensitivity [%]",
|
||||
@@ -719,8 +788,10 @@
|
||||
"AWA",
|
||||
"AWS",
|
||||
"COG",
|
||||
"DBS",
|
||||
"DBT",
|
||||
"HDM",
|
||||
"HDT",
|
||||
"PRPOS",
|
||||
"RPOS",
|
||||
"SOG",
|
||||
@@ -746,7 +817,7 @@
|
||||
"obp40":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance1": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance1": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -754,13 +825,13 @@
|
||||
"label": "Data Instance 1 Calibration Slope",
|
||||
"type": "number",
|
||||
"default": "1.00",
|
||||
"description": "Slope for data instance 1",
|
||||
"description": "Slope for data instance 1, Default: 1(!)",
|
||||
"category": "OBP40 Calibrations",
|
||||
"capabilities": {
|
||||
"obp40":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance1": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance1": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -777,7 +848,7 @@
|
||||
"obp40":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance1": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance1": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -791,8 +862,10 @@
|
||||
"AWA",
|
||||
"AWS",
|
||||
"COG",
|
||||
"DBS",
|
||||
"DBT",
|
||||
"HDM",
|
||||
"HDT",
|
||||
"PRPOS",
|
||||
"RPOS",
|
||||
"SOG",
|
||||
@@ -818,7 +891,7 @@
|
||||
"obp40":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance2": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance2": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -826,13 +899,13 @@
|
||||
"label": "Data Instance 2 Calibration Slope",
|
||||
"type": "number",
|
||||
"default": "1.00",
|
||||
"description": "Slope for data instance 2",
|
||||
"description": "Slope for data instance 2; Default: 1(!)",
|
||||
"category": "OBP40 Calibrations",
|
||||
"capabilities": {
|
||||
"obp40":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance2": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance2": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -849,7 +922,7 @@
|
||||
"obp40":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance2": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance2": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -863,8 +936,10 @@
|
||||
"AWA",
|
||||
"AWS",
|
||||
"COG",
|
||||
"DBS",
|
||||
"DBT",
|
||||
"HDM",
|
||||
"HDT",
|
||||
"PRPOS",
|
||||
"RPOS",
|
||||
"SOG",
|
||||
@@ -890,7 +965,7 @@
|
||||
"obp40":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance3": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance3": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -898,13 +973,13 @@
|
||||
"label": "Data Instance 3 Calibration Slope",
|
||||
"type": "number",
|
||||
"default": "1.00",
|
||||
"description": "Slope for data instance 3",
|
||||
"description": "Slope for data instance 3, Default: 1(!)",
|
||||
"category": "OBP40 Calibrations",
|
||||
"capabilities": {
|
||||
"obp40":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance3": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance3": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -921,7 +996,81 @@
|
||||
"obp40":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance3": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance3": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "calInstance4",
|
||||
"label": "Calibration Data Instance 4",
|
||||
"type": "list",
|
||||
"default": "---",
|
||||
"description": "Data instance for calibration",
|
||||
"list": [
|
||||
"---",
|
||||
"AWA",
|
||||
"AWS",
|
||||
"COG",
|
||||
"DBS",
|
||||
"DBT",
|
||||
"HDM",
|
||||
"HDT",
|
||||
"PRPOS",
|
||||
"RPOS",
|
||||
"SOG",
|
||||
"STW",
|
||||
"TWA",
|
||||
"TWS",
|
||||
"TWD",
|
||||
"WTemp"
|
||||
],
|
||||
"category": "OBP40 Calibrations",
|
||||
"capabilities": {
|
||||
"obp40": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "calOffset4",
|
||||
"label": "Data Instance 4 Calibration Offset",
|
||||
"type": "number",
|
||||
"default": "0.00",
|
||||
"description": "Offset for data instance 4",
|
||||
"category": "OBP40 Calibrations",
|
||||
"capabilities": {
|
||||
"obp40":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance4": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "calSlope4",
|
||||
"label": "Data Instance 4 Calibration Slope",
|
||||
"type": "number",
|
||||
"default": "1.00",
|
||||
"description": "Slope for data instance 4, Default: 1(!)",
|
||||
"category": "OBP40 Calibrations",
|
||||
"capabilities": {
|
||||
"obp40":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance4": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "calSmooth4",
|
||||
"label": "Data Instance 4 Smoothing",
|
||||
"type": "number",
|
||||
"default": "0",
|
||||
"check": "checkMinMax",
|
||||
"min": 0,
|
||||
"max": 10,
|
||||
"description": "Smoothing factor [0..10]; 0 = no smoothing",
|
||||
"category": "OBP40 Calibrations",
|
||||
"capabilities": {
|
||||
"obp40":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance4": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -932,7 +1081,8 @@
|
||||
"description": "Type of map source, cloud service or local service",
|
||||
"list": [
|
||||
"OBP Service",
|
||||
"Local Service"
|
||||
"Local Service",
|
||||
"Remote Service"
|
||||
],
|
||||
"category": "OBP40 Navigation",
|
||||
"capabilities": {
|
||||
@@ -969,6 +1119,34 @@
|
||||
{ "mapsource": ["Local Service"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mapServer",
|
||||
"label": "map server",
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Server for converting map tiles. Use only one hostname or IP address",
|
||||
"category": "OBP40 Navigation",
|
||||
"capabilities": {
|
||||
"obp40": "true"
|
||||
},
|
||||
"condition": [
|
||||
{ "mapsource": ["Remote Service"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mapTilePath",
|
||||
"label": "map tile path",
|
||||
"type": "string",
|
||||
"default": "map.php",
|
||||
"description": "Path to converter access e.g. index.php or map.php",
|
||||
"category": "OBP40 Navigation",
|
||||
"capabilities": {
|
||||
"obp40": "true"
|
||||
},
|
||||
"condition": [
|
||||
{ "mapsource": ["Remote Service"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "maptype",
|
||||
"label": "Map Type",
|
||||
@@ -1230,9 +1408,9 @@
|
||||
"type": "number",
|
||||
"default": "50",
|
||||
"check": "checkMinMax",
|
||||
"min": 20,
|
||||
"min": 5,
|
||||
"max": 100,
|
||||
"description": "Backlight brightness [20...100%]",
|
||||
"description": "Backlight brightness [5...100%]",
|
||||
"category": "OBP40 Display",
|
||||
"capabilities": {
|
||||
"obp40": "false"
|
||||
@@ -1374,7 +1552,6 @@
|
||||
"obp40": "true"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "page1type",
|
||||
"label": "Type",
|
||||
@@ -1382,6 +1559,7 @@
|
||||
"default": "Voltage",
|
||||
"description": "Type of page for page 1",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -1683,7 +1861,7 @@
|
||||
"description": "Wind source for page 1: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 1",
|
||||
"capabilities": {
|
||||
@@ -1712,6 +1890,7 @@
|
||||
"default": "WindRose",
|
||||
"description": "Type of page for page 2",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2005,7 +2184,7 @@
|
||||
"description": "Wind source for page 2: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 2",
|
||||
"capabilities": {
|
||||
@@ -2033,6 +2212,7 @@
|
||||
"default": "OneValue",
|
||||
"description": "Type of page for page 3",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2318,7 +2498,7 @@
|
||||
"description": "Wind source for page 3: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 3",
|
||||
"capabilities": {
|
||||
@@ -2345,6 +2525,7 @@
|
||||
"default": "TwoValues",
|
||||
"description": "Type of page for page 4",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2622,7 +2803,7 @@
|
||||
"description": "Wind source for page 4: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 4",
|
||||
"capabilities": {
|
||||
@@ -2648,6 +2829,7 @@
|
||||
"default": "ThreeValues",
|
||||
"description": "Type of page for page 5",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2917,7 +3099,7 @@
|
||||
"description": "Wind source for page 5: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 5",
|
||||
"capabilities": {
|
||||
@@ -2942,6 +3124,7 @@
|
||||
"default": "FourValues",
|
||||
"description": "Type of page for page 6",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3203,7 +3386,7 @@
|
||||
"description": "Wind source for page 6: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 6",
|
||||
"capabilities": {
|
||||
@@ -3227,6 +3410,7 @@
|
||||
"default": "FourValues2",
|
||||
"description": "Type of page for page 7",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3480,7 +3664,7 @@
|
||||
"description": "Wind source for page 7: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 7",
|
||||
"capabilities": {
|
||||
@@ -3503,6 +3687,7 @@
|
||||
"default": "Clock",
|
||||
"description": "Type of page for page 8",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3748,7 +3933,7 @@
|
||||
"description": "Wind source for page 8: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 8",
|
||||
"capabilities": {
|
||||
@@ -3770,6 +3955,7 @@
|
||||
"default": "RollPitch",
|
||||
"description": "Type of page for page 9",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -4007,7 +4193,7 @@
|
||||
"description": "Wind source for page 9: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 9",
|
||||
"capabilities": {
|
||||
@@ -4028,6 +4214,7 @@
|
||||
"default": "Battery2",
|
||||
"description": "Type of page for page 10",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -4257,7 +4444,7 @@
|
||||
"description": "Wind source for page 10: [true|apparent]",
|
||||
"list": [
|
||||
"True wind",
|
||||
"apparent wind"
|
||||
"Apparent wind"
|
||||
],
|
||||
"category": "OBP40 Page 10",
|
||||
"capabilities": {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"name": "homeLAT",
|
||||
"label": "Home latitude",
|
||||
"type": "number",
|
||||
"default": "",
|
||||
"default": "0.00000",
|
||||
"check": "checkMinMax",
|
||||
"min": -90.0,
|
||||
"max": 90.0,
|
||||
@@ -51,7 +51,7 @@
|
||||
"name": "homeLON",
|
||||
"label": "Home longitude",
|
||||
"type": "number",
|
||||
"default": "",
|
||||
"default": "0.00000",
|
||||
"check": "checkMinMax",
|
||||
"min": -180.0,
|
||||
"max": 180.0,
|
||||
@@ -75,6 +75,20 @@
|
||||
"obp60":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "chainLength",
|
||||
"label": "Anchor Chain Length [m]",
|
||||
"type": "number",
|
||||
"default": "0",
|
||||
"check": "checkMinMax",
|
||||
"min": 0,
|
||||
"max": 255,
|
||||
"description": "The length of the anchor chain [0...255m]",
|
||||
"category": "OBP60 Settings",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fuelTank",
|
||||
"label": "Fuel Tank [l]",
|
||||
@@ -661,6 +675,61 @@
|
||||
"obp60":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mod1Out1",
|
||||
"label": "Name1",
|
||||
"type": "string",
|
||||
"default": "text1",
|
||||
"description": "Button name",
|
||||
"category": "OBP60 IO-Modul1",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mod1Out2",
|
||||
"label": "Name2",
|
||||
"type": "string",
|
||||
"default": "text2",
|
||||
"description": "Button name",
|
||||
"category": "OBP60 IO-Modul1",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mod1Out3",
|
||||
"label": "Name3",
|
||||
"type": "string",
|
||||
"default": "text3",
|
||||
"description": "Button name",
|
||||
"category": "OBP60 IO-Modul1",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mod1Out4",
|
||||
"label": "Name4",
|
||||
"type": "string",
|
||||
"default": "text4",
|
||||
"description": "Button name",
|
||||
"category": "OBP60 IO-Modul1",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mod1Out5",
|
||||
"label": "Name5",
|
||||
"type": "string",
|
||||
"default": "text5",
|
||||
"description": "Button name",
|
||||
"category": "OBP60 IO-Modul1",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tSensitivity",
|
||||
"label": "Touch Sensitivity [%]",
|
||||
@@ -708,8 +777,10 @@
|
||||
"AWA",
|
||||
"AWS",
|
||||
"COG",
|
||||
"DBS",
|
||||
"DBT",
|
||||
"HDM",
|
||||
"HDT",
|
||||
"PRPOS",
|
||||
"RPOS",
|
||||
"SOG",
|
||||
@@ -735,7 +806,7 @@
|
||||
"obp60":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance1": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance1": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -743,13 +814,13 @@
|
||||
"label": "Data Instance 1 Calibration Slope",
|
||||
"type": "number",
|
||||
"default": "1.00",
|
||||
"description": "Slope for data instance 1",
|
||||
"description": "Slope for data instance 1; Default: 1(!)",
|
||||
"category": "OBP60 Calibrations",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance1": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance1": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -766,7 +837,7 @@
|
||||
"obp60":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance1": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance1": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -780,8 +851,10 @@
|
||||
"AWA",
|
||||
"AWS",
|
||||
"COG",
|
||||
"DBS",
|
||||
"DBT",
|
||||
"HDM",
|
||||
"HDT",
|
||||
"PRPOS",
|
||||
"RPOS",
|
||||
"SOG",
|
||||
@@ -807,7 +880,7 @@
|
||||
"obp60":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance2": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance2": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -815,13 +888,13 @@
|
||||
"label": "Data Instance 2 Calibration Slope",
|
||||
"type": "number",
|
||||
"default": "1.00",
|
||||
"description": "Slope for data instance 2",
|
||||
"description": "Slope for data instance 2; Default: 1(!)",
|
||||
"category": "OBP60 Calibrations",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance2": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance2": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -838,7 +911,7 @@
|
||||
"obp60":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance2": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance2": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -852,8 +925,10 @@
|
||||
"AWA",
|
||||
"AWS",
|
||||
"COG",
|
||||
"DBS",
|
||||
"DBT",
|
||||
"HDM",
|
||||
"HDT",
|
||||
"PRPOS",
|
||||
"RPOS",
|
||||
"SOG",
|
||||
@@ -879,7 +954,7 @@
|
||||
"obp60":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance3": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance3": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -887,13 +962,13 @@
|
||||
"label": "Data Instance 3 Calibration Slope",
|
||||
"type": "number",
|
||||
"default": "1.00",
|
||||
"description": "Slope for data instance 3",
|
||||
"description": "Slope for data instance 3; Default: 1(!)",
|
||||
"category": "OBP60 Calibrations",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance3": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance3": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -910,7 +985,81 @@
|
||||
"obp60":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance3": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
{ "calInstance3": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "calInstance4",
|
||||
"label": "Calibration Data Instance 4",
|
||||
"type": "list",
|
||||
"default": "---",
|
||||
"description": "Data instance for calibration",
|
||||
"list": [
|
||||
"---",
|
||||
"AWA",
|
||||
"AWS",
|
||||
"COG",
|
||||
"DBS",
|
||||
"DBT",
|
||||
"HDM",
|
||||
"HDT",
|
||||
"PRPOS",
|
||||
"RPOS",
|
||||
"SOG",
|
||||
"STW",
|
||||
"TWA",
|
||||
"TWS",
|
||||
"TWD",
|
||||
"WTemp"
|
||||
],
|
||||
"category": "OBP60 Calibrations",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "calOffset4",
|
||||
"label": "Data Instance 4 Calibration Offset",
|
||||
"type": "number",
|
||||
"default": "0.00",
|
||||
"description": "Offset for data instance 4",
|
||||
"category": "OBP60 Calibrations",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance4": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "calSlope4",
|
||||
"label": "Data Instance 4 Calibration Slope",
|
||||
"type": "number",
|
||||
"default": "1.00",
|
||||
"description": "Slope for data instance 3; Default: 1(!)",
|
||||
"category": "OBP60 Calibrations",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance4": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "calSmooth4",
|
||||
"label": "Data Instance 4 Smoothing",
|
||||
"type": "number",
|
||||
"default": "0",
|
||||
"check": "checkMinMax",
|
||||
"min": 0,
|
||||
"max": 10,
|
||||
"description": "Smoothing factor [0..10]; 0 = no smoothing",
|
||||
"category": "OBP60 Calibrations",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
},
|
||||
"condition": [
|
||||
{ "calInstance4": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -958,6 +1107,34 @@
|
||||
{ "mapsource": ["Local Service"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mapServer",
|
||||
"label": "map server",
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Server for converting map tiles. Use only one hostname or IP address",
|
||||
"category": "OBP60 Navigation",
|
||||
"capabilities": {
|
||||
"obp60": "true"
|
||||
},
|
||||
"condition": [
|
||||
{ "mapsource": ["Remote Service"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mapTilePath",
|
||||
"label": "map tile path",
|
||||
"type": "string",
|
||||
"default": "map.php",
|
||||
"description": "Path to converter access e.g. index.php or map.php",
|
||||
"category": "OBP40 Navigation",
|
||||
"capabilities": {
|
||||
"obp40": "true"
|
||||
},
|
||||
"condition": [
|
||||
{ "mapsource": ["Remote Service"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "maptype",
|
||||
"label": "Map Type",
|
||||
@@ -1218,9 +1395,9 @@
|
||||
"type": "number",
|
||||
"default": "50",
|
||||
"check": "checkMinMax",
|
||||
"min": 20,
|
||||
"min": 5,
|
||||
"max": 100,
|
||||
"description": "Backlight brightness [20...100%]",
|
||||
"description": "Backlight brightness [5...100%]",
|
||||
"category": "OBP60 Display",
|
||||
"capabilities": {
|
||||
"obp60":"true"
|
||||
@@ -1351,7 +1528,6 @@
|
||||
"obp60":"true"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "page1type",
|
||||
"label": "Type",
|
||||
@@ -1359,6 +1535,7 @@
|
||||
"default": "Voltage",
|
||||
"description": "Type of page for page 1",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -1659,6 +1836,7 @@
|
||||
"default": "WindRose",
|
||||
"description": "Type of page for page 2",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -1951,6 +2129,7 @@
|
||||
"default": "OneValue",
|
||||
"description": "Type of page for page 3",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2235,6 +2414,7 @@
|
||||
"default": "TwoValues",
|
||||
"description": "Type of page for page 4",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2511,6 +2691,7 @@
|
||||
"default": "ThreeValues",
|
||||
"description": "Type of page for page 5",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -2779,6 +2960,7 @@
|
||||
"default": "FourValues",
|
||||
"description": "Type of page for page 6",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3039,6 +3221,7 @@
|
||||
"default": "FourValues2",
|
||||
"description": "Type of page for page 7",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3291,6 +3474,7 @@
|
||||
"default": "Clock",
|
||||
"description": "Type of page for page 8",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3535,6 +3719,7 @@
|
||||
"default": "RollPitch",
|
||||
"description": "Type of page for page 9",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -3771,6 +3956,7 @@
|
||||
"default": "Battery2",
|
||||
"description": "Type of page for page 10",
|
||||
"list": [
|
||||
"Anchor",
|
||||
"BME280",
|
||||
"Battery",
|
||||
"Battery2",
|
||||
@@ -1,12 +1,21 @@
|
||||
# PlatformIO extra script for obp60task
|
||||
|
||||
import subprocess
|
||||
|
||||
patching = False
|
||||
|
||||
epdtype = "unknown"
|
||||
pcbvers = "unknown"
|
||||
for x in env["BUILD_FLAGS"]:
|
||||
if x.startswith("-D HARDWARE_"):
|
||||
if not x.startswith('-D'):
|
||||
continue
|
||||
opt = x[2:].strip()
|
||||
if opt.startswith("HARDWARE_"):
|
||||
pcbvers = x.split('_')[1]
|
||||
if x.startswith("-D DISPLAY_"):
|
||||
elif opt.startswith("DISPLAY_"):
|
||||
epdtype = x.split('_')[1]
|
||||
elif opt == 'ENABLE_PATCHES':
|
||||
patching = True
|
||||
|
||||
propfilename = os.path.join(env["PROJECT_LIBDEPS_DIR"], env["PIOENV"], "GxEPD2/library.properties")
|
||||
properties = {}
|
||||
@@ -28,3 +37,20 @@ except:
|
||||
env["CPPDEFINES"].extend([("BOARD", env["BOARD"]), ("EPDTYPE", epdtype), ("PCBVERS", pcbvers), ("GXEPD2VERS", gxepd2vers)])
|
||||
|
||||
print("added hardware info to CPPDEFINES")
|
||||
|
||||
if patching:
|
||||
# apply patches to gateway code
|
||||
print("applying gateway patches")
|
||||
patchdir = os.path.join(os.path.dirname(script), "patches")
|
||||
if not os.path.isdir(patchdir):
|
||||
print("patchdir not found, no patches applied")
|
||||
else:
|
||||
patchfiles = [f for f in os.listdir(patchdir)]
|
||||
for p in patchfiles:
|
||||
patch = os.path.join(patchdir, p)
|
||||
print(f"applying {patch}")
|
||||
res = subprocess.run(["git", "apply", patch], capture_output=True, text=True)
|
||||
if res.returncode != 0:
|
||||
print(res.stderr)
|
||||
else:
|
||||
print("no patches found")
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#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
|
||||
#include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation
|
||||
|
||||
#ifdef BOARD_OBP40S3
|
||||
@@ -147,7 +146,6 @@ void keyboardTask(void *param){
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
// Scorgan: moved class declaration to header file <obp60task.h> to make class available to other functions
|
||||
// --- Class BoatValueList --------------
|
||||
bool BoatValueList::addValueToList(GwApi::BoatValue *v){
|
||||
for (int i=0;i<numValues;i++){
|
||||
@@ -172,7 +170,7 @@ GwApi::BoatValue *BoatValueList::findValueOrCreate(String name){
|
||||
addValueToList(rt);
|
||||
return rt;
|
||||
}
|
||||
// --- Class BoatValueList --------------
|
||||
// --- End Class BoatValueList --------------
|
||||
|
||||
//we want to have a list that has all our page definitions
|
||||
//this way each page can easily be added here
|
||||
@@ -264,6 +262,10 @@ void registerAllPages(PageList &list){
|
||||
list.add(®isterPageNavigation);
|
||||
extern PageDescription registerPageDigitalOut;
|
||||
list.add(®isterPageDigitalOut);
|
||||
extern PageDescription registerPageAutopilot;
|
||||
list.add(®isterPageAutopilot);
|
||||
extern PageDescription registerPageAnchor;
|
||||
list.add(®isterPageAnchor);
|
||||
}
|
||||
|
||||
// Undervoltage detection for shutdown display
|
||||
@@ -332,7 +334,7 @@ void OBP60Task(GwApi *api){
|
||||
// return;
|
||||
GwLog *logger=api->getLogger();
|
||||
GwConfigHandler *config=api->getConfig();
|
||||
#ifdef HARDWARE_V21
|
||||
#if defined HARDWARE_V20 || HARDWARE_V21
|
||||
startLedTask(api);
|
||||
#endif
|
||||
PageList allPages;
|
||||
@@ -341,7 +343,7 @@ void OBP60Task(GwApi *api){
|
||||
commonData.logger=logger;
|
||||
commonData.config=config;
|
||||
|
||||
#ifdef HARDWARE_V21
|
||||
#if defined HARDWARE_V20 || HARDWARE_V21
|
||||
// Keyboard coordinates for page footer
|
||||
initKeys(commonData);
|
||||
#endif
|
||||
@@ -435,10 +437,9 @@ void OBP60Task(GwApi *api){
|
||||
int lastPage=-1; // initialize with an impiossible value, so we can detect wether we are during startup and no page has been displayed yet
|
||||
|
||||
BoatValueList boatValues; //all the boat values for the api query
|
||||
HstryBuffers hstryBufList(1920, &boatValues, logger); // Create empty list of boat data history buffers
|
||||
HstryBuffers hstryBufferList(1920, &boatValues, logger); // Create empty list of boat data history buffers (1.920 values = seconds = 32 min.)
|
||||
WindUtils trueWind(&boatValues, logger); // Create helper object for true wind calculation
|
||||
//commonData.distanceformat=config->getString(xxx);
|
||||
//add all necessary data to common data
|
||||
CalibrationData calibrationDataList(logger); // all boat data types which are supposed to be calibrated
|
||||
|
||||
//fill the page data from config
|
||||
numPages=config->getInt(config->visiblePages,1);
|
||||
@@ -480,26 +481,25 @@ void OBP60Task(GwApi *api){
|
||||
pages[i].parameters.values.push_back(value);
|
||||
}
|
||||
|
||||
// Read the specified boat data type of relevant pages and create a history buffer for each type
|
||||
// Read the specified boat data types of relevant pages and create a history buffer for each type
|
||||
if (pages[i].parameters.pageName == "OneValue" || pages[i].parameters.pageName == "TwoValues" || pages[i].parameters.pageName == "WindPlot") {
|
||||
for (auto pVal : pages[i].parameters.values) {
|
||||
hstryBufList.addBuffer(pVal->getName());
|
||||
hstryBufferList.addBuffer(pVal->getName());
|
||||
}
|
||||
}
|
||||
// Add list of history buffers to page parameters
|
||||
pages[i].parameters.hstryBuffers = &hstryBufList;
|
||||
pages[i].parameters.hstryBuffers = &hstryBufferList;
|
||||
|
||||
}
|
||||
|
||||
// add out of band system page (always available)
|
||||
Page *syspage = allPages.pages[0]->creator(commonData);
|
||||
|
||||
// Check user settings for true wind calculation
|
||||
// Read user settings from config file
|
||||
bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false);
|
||||
bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false);
|
||||
|
||||
// Read all calibration data settings from config
|
||||
calibrationData.readConfig(config, logger);
|
||||
// Read user calibration data settings from config file
|
||||
calibrationDataList.readConfig(config);
|
||||
|
||||
// Display screenshot handler for HTTP request
|
||||
// http://192.168.15.1/api/user/OBP60Task/screenshot
|
||||
@@ -527,11 +527,11 @@ 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();
|
||||
float tz = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asFloat();
|
||||
|
||||
commonData.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.backlight.brightness = uint(config->getConfigItem(config->blBrightness,true)->asInt());
|
||||
commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString();
|
||||
|
||||
bool uvoltage = config->getConfigItem(config->underVoltage, true)->asBoolean();
|
||||
@@ -658,7 +658,7 @@ void OBP60Task(GwApi *api){
|
||||
// if(String(backlight) == "Control by Key"){
|
||||
if(keyboardMessage == 6){
|
||||
LOG_DEBUG(GwLog::LOG,"Toggle Backlight LED");
|
||||
toggleBacklightLED(commonData.backlight.brightness, commonData.backlight.color);
|
||||
stepsBacklightLED(commonData.backlight.brightness, commonData.backlight.color);
|
||||
}
|
||||
}
|
||||
#ifdef BOARD_OBP40S3
|
||||
@@ -703,7 +703,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(time->value , date->value, lat->value, lon->value, tz);
|
||||
commonData.sundata = calcSunsetSunrise(time->value , date->value, lat->value, lon->value, commonData.tz);
|
||||
// Backlight with sun control
|
||||
if (commonData.backlight.mode == BacklightMode::SUN) {
|
||||
// if(String(backlight) == "Control by Sun"){
|
||||
@@ -716,7 +716,7 @@ void OBP60Task(GwApi *api){
|
||||
}
|
||||
} 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);
|
||||
commonData.sundata = calcSunsetSunriseRTC(&commonData.data.rtcTime, homelat, homelon, commonData.tz);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,10 +814,10 @@ void OBP60Task(GwApi *api){
|
||||
api->getStatus(commonData.status);
|
||||
|
||||
if (calcTrueWnds) {
|
||||
trueWind.addWinds();
|
||||
trueWind.addWinds(); // calculate true wind data from apparent wind values
|
||||
}
|
||||
// Handle history buffers for certain boat data for windplot page and other usage
|
||||
hstryBufList.handleHstryBufs(useSimuData, commonData);
|
||||
calibrationDataList.handleCalibration(&boatValues); // Process calibration for all boat data in <calibrationDataList>
|
||||
hstryBufferList.handleHstryBufs(useSimuData, commonData); // Handle history buffers for certain boat data for windplot page and other usage
|
||||
|
||||
// Clear display
|
||||
// getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);
|
||||
|
||||
@@ -16,6 +16,8 @@ board_build.variants_dir = variants
|
||||
board = obp60_s3_n16r8 #ESP32-S3 N16R8, 16MB flash, 8MB PSRAM, production series
|
||||
#board_build.partitions = default_8MB.csv #ESP32-S3 N8, 8MB flash
|
||||
board_build.partitions = default_16MB.csv #ESP32-S3 N16, 16MB flash
|
||||
custom_config = lib/obp60task/config_obp60.json
|
||||
custom_script = lib/obp60task/extra_task.py
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
${basedeps.lib_deps}
|
||||
@@ -56,6 +58,7 @@ build_flags=
|
||||
# -D DISPLAY_GYE042A87 #alternativ E-Ink display from Genyo Optical, R10 2.2 ohm - medium
|
||||
# -D DISPLAY_SE0420NQ04 #alternativ E-Ink display from SID Technology, R10 2.2 ohm - bad (burn in effects)
|
||||
# -D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
|
||||
# -D ENABLE_PATCHES #enable patching of gateway code
|
||||
${env.build_flags}
|
||||
#CONFIG_ESP_TASK_WDT_TIMEOUT_S = 10 #Task Watchdog timeout period (seconds) [1...60] 5 default
|
||||
upload_port = /dev/ttyACM0 #OBP60 download via USB-C direct
|
||||
@@ -68,7 +71,8 @@ platform = espressif32@6.8.1
|
||||
board_build.variants_dir = variants
|
||||
board = obp40_s3_n8r8 #ESP32-S3 N8R8, 8MB flash, 8MB PSRAM, OBP60 clone (CrowPanel 4.2)
|
||||
board_build.partitions = default_8MB.csv #ESP32-S3 N8, 8MB flash
|
||||
custom_config = config_obp40.json
|
||||
custom_config = lib/obp60task/config_obp40.json
|
||||
custom_script = lib/obp60task/extra_task.py
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
${basedeps.lib_deps}
|
||||
@@ -103,8 +107,9 @@ build_flags=
|
||||
-D HARDWARE_V10 #OBP40 hardware revision V1.0 SKU:DIE07300S V1.1 (CrowPanel 4.2)
|
||||
-D DISPLAY_GDEY042T81 #new E-Ink display from Good Display (Waveshare), R10 2.2 ohm - good (contast lost by shunshine)
|
||||
#-D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
|
||||
-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
|
||||
-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
|
||||
#-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
|
||||
#-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
|
||||
#-D ENABLE_PATCHES #enable patching of gateway code
|
||||
${env.build_flags}
|
||||
upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter
|
||||
upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
|
||||
# Install tools
|
||||
echo "Installing tools"
|
||||
cd /workspace/esp32-nmea2000
|
||||
cd /workspaces/esp32-nmea2000
|
||||
pip3 install -U esptool
|
||||
pip3 install platformio
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
@@ -93,6 +93,7 @@ class GwSensorConfig{
|
||||
}
|
||||
bool readConfig(T* s,GwConfigHandler *cfg){
|
||||
if (s == nullptr) return false;
|
||||
if (prefix != s->prefix) return false;
|
||||
configReader(s,cfg);
|
||||
return s->ok;
|
||||
}
|
||||
|
||||
@@ -66,18 +66,8 @@ GwSerial::~GwSerial()
|
||||
if (lock != nullptr) vSemaphoreDelete(lock);
|
||||
}
|
||||
|
||||
String GwSerial::getMode(){
|
||||
switch (type){
|
||||
case GWSERIAL_TYPE_UNI:
|
||||
return "UNI";
|
||||
case GWSERIAL_TYPE_BI:
|
||||
return "BI";
|
||||
case GWSERIAL_TYPE_RX:
|
||||
return "RX";
|
||||
case GWSERIAL_TYPE_TX:
|
||||
return "TX";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
int GwSerial::getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
bool GwSerial::isInitialized() { return initialized; }
|
||||
|
||||
@@ -42,7 +42,7 @@ class GwSerial : public GwChannelInterface{
|
||||
virtual Stream *getStream(bool partialWrites);
|
||||
bool getAvailableWrite(){return availableWrite;}
|
||||
virtual void begin(unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1)=0;
|
||||
virtual String getMode() override;
|
||||
virtual int getType() override;
|
||||
friend GwSerialStream;
|
||||
};
|
||||
|
||||
@@ -122,6 +122,7 @@ template<typename T>
|
||||
setError(serial,logger);
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -185,4 +185,4 @@ int GwSocketServer::numClients()
|
||||
}
|
||||
GwSocketServer::~GwSocketServer()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,4 +291,4 @@ void GwTcpClient::setResolved(IPAddress addr, bool valid){
|
||||
GwTcpClient::ResolvedAddress GwTcpClient::getResolved(){
|
||||
GWSYNCHRONIZED(locker);
|
||||
return resolvedAddress;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "GwSocketHelper.h"
|
||||
#include "GWWifi.h"
|
||||
|
||||
|
||||
GwUdpReader::GwUdpReader(const GwConfigHandler *config, GwLog *logger, int minId)
|
||||
{
|
||||
this->config = config;
|
||||
|
||||
@@ -200,4 +200,4 @@ size_t GwUdpWriter::sendToClients(const char *buf, int source,bool partial)
|
||||
|
||||
GwUdpWriter::~GwUdpWriter()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
|
||||
@@ -431,7 +431,8 @@ GwXDRFoundMapping GwXDRMappings::getMapping(String xName,String xType,String xUn
|
||||
}
|
||||
return selectMapping(&(it->second),instance,n183Key.c_str());
|
||||
}
|
||||
GwXDRFoundMapping GwXDRMappings::getMapping(GwXDRCategory category,int selector,int field,int instance){
|
||||
GwXDRFoundMapping GwXDRMappings::getMapping(double value,GwXDRCategory category,int selector,int field,int instance){
|
||||
if (value == N2kDoubleNA) return GwXDRFoundMapping(); //do not add to unknown mappings
|
||||
unsigned long n2kKey=GwXDRMappingDef::n2kKey(category,selector,field);
|
||||
auto it=n2kMap.find(n2kKey);
|
||||
if (it == n2kMap.end()){
|
||||
|
||||
@@ -244,7 +244,7 @@ class GwXDRMappings{
|
||||
//get the mappings
|
||||
//the returned mapping will exactly contain one mapping def
|
||||
GwXDRFoundMapping getMapping(String xName,String xType,String xUnit);
|
||||
GwXDRFoundMapping getMapping(GwXDRCategory category,int selector,int field=0,int instance=-1);
|
||||
GwXDRFoundMapping getMapping(double value,GwXDRCategory category,int selector,int field=0,int instance=-1);
|
||||
String getXdrEntry(String mapping, double value,int instance=0);
|
||||
const char * getUnMapped();
|
||||
const GwXDRType * findType(const String &typeString, const String &unitString) const;
|
||||
|
||||
@@ -18,7 +18,7 @@ extra_configs=
|
||||
|
||||
[basedeps]
|
||||
lib_deps =
|
||||
ttlappalainen/NMEA2000-library @ 4.22.0
|
||||
ttlappalainen_NMEA2000=https://github.com/wellenvogel/NMEA2000.git#20251126
|
||||
ttlappalainen/NMEA0183 @ 1.10.1
|
||||
ArduinoJson @ 6.18.5
|
||||
AsyncTCP-esphome @ 2.0.1
|
||||
@@ -29,6 +29,18 @@ lib_deps =
|
||||
WiFi
|
||||
Update
|
||||
|
||||
[devdeps]
|
||||
lib_deps=
|
||||
ttlappalainen_NMEA2000=symlink://../NMEA2000
|
||||
ttlappalainen/NMEA0183 @ 1.10.1
|
||||
ArduinoJson @ 6.18.5
|
||||
AsyncTCP-esphome @ 2.0.1
|
||||
ottowinter/ESPAsyncWebServer-esphome@2.0.1
|
||||
FS
|
||||
Preferences
|
||||
ESPmDNS
|
||||
WiFi
|
||||
Update
|
||||
[env]
|
||||
platform = espressif32 @ 6.8.1
|
||||
framework = arduino
|
||||
@@ -67,6 +79,17 @@ lib_deps =
|
||||
adafruit/Adafruit BusIO @ 1.14.5
|
||||
adafruit/Adafruit Unified Sensor @ 1.1.13
|
||||
|
||||
[env:m5stack-atom-dev]
|
||||
board = m5stack-atom
|
||||
lib_deps =
|
||||
${devdeps.lib_deps}
|
||||
fastled/FastLED @ 3.6.0
|
||||
|
||||
build_flags =
|
||||
-D BOARD_M5ATOM
|
||||
${env.build_flags}
|
||||
upload_port = /dev/esp32
|
||||
upload_protocol = esptool
|
||||
[env:m5stack-atom]
|
||||
board = m5stack-atom
|
||||
lib_deps = ${env.lib_deps}
|
||||
@@ -185,3 +208,14 @@ build_flags =
|
||||
${env.build_flags}
|
||||
upload_port = /dev/esp32
|
||||
upload_protocol = esptool
|
||||
|
||||
[env:s3devkitm-generic]
|
||||
extends = sensors
|
||||
board = esp32-s3-devkitm-1
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
${sensors.lib_deps}
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
upload_port = /dev/esp32
|
||||
upload_protocol = esptool
|
||||
|
||||
8
post.py
8
post.py
@@ -2,6 +2,7 @@ Import("env", "projenv")
|
||||
import os
|
||||
import glob
|
||||
import shutil
|
||||
import re
|
||||
|
||||
print("##post script running")
|
||||
HDROFFSET=288
|
||||
@@ -39,6 +40,7 @@ def post(source,target,env):
|
||||
appoffset=env.subst("$ESP32_APP_OFFSET")
|
||||
firmware=env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||
(fwname,version)=getFirmwareInfo(firmware)
|
||||
fwname=re.sub(r"[^0-9A-Za-z_.-]*","",fwname)
|
||||
print("found fwname=%s, fwversion=%s"%(fwname,version))
|
||||
python=env.subst("$PYTHONEXE")
|
||||
print("base=%s,esptool=%s,appoffset=%s,uploaderflags=%s"%(base,esptool,appoffset,uploaderflags))
|
||||
@@ -70,10 +72,12 @@ def post(source,target,env):
|
||||
print("running %s"%" ".join(cmd))
|
||||
env.Execute(" ".join(cmd),"#testpost")
|
||||
ofversion="-"+version
|
||||
versionedFile=os.path.join(outdir,"%s%s-update.bin"%(base,ofversion))
|
||||
versionedFile=os.path.join(outdir,"%s%s-update.bin"%(fwname,ofversion))
|
||||
shutil.copyfile(firmware,versionedFile)
|
||||
versioneOutFile=os.path.join(outdir,"%s%s-all.bin"%(base,ofversion))
|
||||
print(f"wrote {versionedFile}")
|
||||
versioneOutFile=os.path.join(outdir,"%s%s-all.bin"%(fwname,ofversion))
|
||||
shutil.copyfile(outfile,versioneOutFile)
|
||||
print(f"wrote {versioneOutFile}")
|
||||
env.AddPostAction(
|
||||
"$BUILD_DIR/${PROGNAME}.bin",
|
||||
post
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
@@ -72,9 +72,9 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
|
||||
#define MAX_NMEA2000_MESSAGE_SEASMART_SIZE 500
|
||||
#define MAX_NMEA0183_MESSAGE_SIZE MAX_NMEA2000_MESSAGE_SEASMART_SIZE
|
||||
//assert length of firmware name and version
|
||||
CASSERT(strlen(FIRMWARE_TYPE) <= 32, "environment name (FIRMWARE_TYPE) must not exceed 32 chars");
|
||||
CASSERT(strlen(VERSION) <= 32, "VERSION must not exceed 32 chars");
|
||||
CASSERT(strlen(IDF_VERSION) <= 32,"IDF_VERSION must not exceed 32 chars");
|
||||
CASSERT(strlen(FIRMWARE_TYPE) <= 31, "environment name (FIRMWARE_TYPE) must not exceed 32 chars");
|
||||
CASSERT(strlen(VERSION) <= 31, "VERSION must not exceed 32 chars");
|
||||
CASSERT(strlen(IDF_VERSION) <= 31,"IDF_VERSION must not exceed 32 chars");
|
||||
//https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/app_image_format.html
|
||||
//and removed the bugs in the doc...
|
||||
__attribute__((section(".rodata_custom_desc"))) esp_app_desc_t custom_app_desc = {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user