1
0
mirror of https://github.com/thooge/esp32-nmea2000-obp60.git synced 2026-03-28 18:06:37 +01:00

128 Commits

Author SHA1 Message Date
86cd1ed97c Add missing functions to LedSpiTask 2026-03-08 15:22:26 +01:00
e33f908187 Page system rework and improvements 2026-02-25 20:03:54 +01:00
Norbert Walter
065a9807d2 Merge pull request #229 from thooge/formatter
Added format "formatXdr:A:rd" to formatter (see #159)
2026-02-25 10:21:45 +01:00
Norbert Walter
98c318f055 Merge pull request #228 from thooge/patching
System page with N2K device list by improved patching feature and patch
2026-02-25 10:21:02 +01:00
97fcebdcb7 Added format "formatXdr:A:rd" to formatter (see #159) 2026-02-23 18:56:20 +01:00
a6fd3ef599 System page with N2K device list by improved patching feature and patch 2026-02-23 15:34:10 +01:00
norbert-walter
66e71acac3 Fix for GwWifi 2026-02-20 09:52:41 +01:00
norbert-walter
b85504bf50 Merge branch 'autopilot2' 2026-02-20 09:43:17 +01:00
norbert-walter
3043be8e1d Code cleaning 2026-02-15 19:17:52 +01:00
norbert-walter
02b2c888ee Fix for GwWifi 2026-02-15 19:16:24 +01:00
Norbert Walter
00b06f458b GwWifi race condition safe, PageNavigation check the Wifi connection befor using 2026-02-15 16:11:19 +00:00
Norbert Walter
43b0a780d5 Merge pull request #227 from thooge/master
Add feature to optionally apply patches to gateway code
2026-02-15 15:53:26 +01:00
0363ba4379 Add feature to optionally apply patches to gateway code 2026-02-15 13:13:19 +01:00
Norbert Walter
4b6e2abe33 Merge pull request #226 from Scorgan01/master
Small improvements to charts + pages OneValue + TwoValues
2026-02-15 12:21:32 +01:00
Norbert Walter
0401d82b62 Merge pull request #225 from thooge/master
Make code compile for OBP60 v2.0 again
2026-02-15 12:20:41 +01:00
Scorgan01
2fecbee492 Merge branch 'norbert-walter:master' into master 2026-02-11 22:19:11 +01:00
Ulrich Meine
fc5daaba37 - change control of key settings on PageOneValue + PageTwoValues
- added taskYIELD() to chart loop to be nice to other tasks
- fix typo in config_obp40.json
- fine tune chart labels
- disable debug messages
2026-02-09 22:31:07 +01:00
Norbert Walter
04dc09e44a Fix directory path for tool installation 2026-02-09 15:25:27 +01:00
norbert-walter
6c7997e369 Fix iRTC time for N2K bus 2026-02-08 22:00:22 +01:00
1d2ba2f71d Make code compile for OBP60 v2.0 again 2026-02-08 18:02:50 +01:00
norbert-walter
7f747e9b35 Add data connections for PageAutopilot 2026-02-08 14:49:40 +01:00
Norbert Walter
71512e7262 Actualize PageAutopilot 2026-02-08 13:18:39 +00:00
norbert-walter
4468c0555b Implement rudder bargraf in PageAutopilot 2026-02-08 14:09:10 +01:00
Norbert Walter
99404991a3 Add rudder bargraf 2026-02-08 12:40:20 +00:00
Norbert Walter
ee5077e0a5 Add PageAutopilot 2026-02-07 16:59:17 +00:00
norbert-walter
bbecf5e55f Code ceaning 2026-02-07 17:36:14 +01:00
norbert-walter
ded1b2b22e Typo 2026-02-07 17:10:59 +01:00
norbert-walter
a0a88fa2c9 Modify reagatta timer funktion 2026-02-07 17:05:41 +01:00
Norbert Walter
e9bf54e99f Add sync button for Timer 2026-02-07 14:34:54 +00:00
norbert-walter
64950c3974 Merge branch 'newclock' 2026-02-06 23:07:26 +01:00
norbert-walter
fb2fbc85a4 Typo and formats for PageClock 2026-02-06 23:05:26 +01:00
norbert-walter
6b92a5e69c Add more functionality for time and date synchrinisation 2026-02-06 22:41:34 +01:00
norbert-walter
ef4546a2e6 Fix for PageClock 2026-02-06 15:20:43 +01:00
norbert-walter
6870c9b8a4 Modify button labels in PageClock 2026-02-06 12:52:14 +01:00
norbert-walter
a70d976a6e Change design for PageClock 2026-02-06 11:46:35 +01:00
norbert-walter
fbba6ffff2 Fix for PageClock 2026-02-05 23:52:10 +01:00
norbert-walter
d516c82041 New clock page with extended functions 2026-02-05 16:32:15 +01:00
norbert-walter
ccca784ac2 Delete old extra.script.py 2026-02-04 16:56:11 +01:00
norbert-walter
337214d650 Fix config problem for OBP60, missing setup values 2026-02-04 16:48:50 +01:00
Norbert Walter
744cf6469b Merge pull request #224 from thooge/master
Fix build for new gateway extra_script.py
2026-02-04 08:28:52 +01:00
6bc1b60f60 Fix build for new gateway extra_script.py 2026-02-04 08:18:39 +01:00
Norbert Walter
06dcd14bdc Merge branch 'wellenvogel:master' into master 2026-02-02 21:39:00 +01:00
norbert-walter
352009073e Setup to remote project 2026-02-02 21:38:14 +01:00
norbert-walter
aaa4007ae6 Merge remote-tracking branch 'origin/master' 2026-02-02 21:29:23 +01:00
norbert-walter
5b087e61d2 Merge branch 'autopilot' 2026-02-02 21:28:39 +01:00
norbert-walter
753e87068f New function for backlight - 3 brigjhtness steps 2026-02-02 21:26:43 +01:00
Norbert Walter
7445d2db24 Merge pull request #222 from Scorgan01/CalibrationData-v31
Data calibration: Move calibration of boat data to main loop to make calibrated data available everywhere
2026-02-02 18:15:23 +01:00
Norbert Walter
f517abf001 Merge pull request #220 from TobiasE-github/master
use 9.95 as the limit for formatting numbers below and above 10
2026-02-02 18:15:04 +01:00
norbert-walter
6a56a8fb56 Fix I2C adresses for INA219 2026-02-02 17:00:27 +01:00
norbert-walter
576f0a0d4f Fix for LED brightness and add Page Autopilot 2026-02-02 16:29:18 +01:00
norbert-walter
1de936fd47 Typo 2026-02-01 18:09:44 +01:00
TobiasE-github
05dad7a23c Merge branch 'norbert-walter:master' into master 2026-01-31 08:32:06 +01:00
norbert-walter
d19da640ae Add PageDigitalOut 2026-01-30 17:26:10 +01:00
norbert-walter
1da26a90ec Auto stash before merge of "master" and "origin/master" 2026-01-30 10:14:11 +01:00
Ulrich Meine
cb2b85d505 Data Calibration: Extend no. of calibration instances from 3 to 4 2026-01-17 13:45:41 +01:00
Ulrich Meine
cc1d07fac0 True Wind Calculation: change wind angle range to [0..360] temporarily for proper display on pages 2026-01-17 13:07:48 +01:00
Ulrich Meine
b8e64ff64c Update data calibration: calibration in main loop on boat data values:
- modified code to calibrate affected boat data each second in main obp60task loop;
- removed individual calibration call in all pages
- moved code from BoatDataCalibration to OBPDataOperations
- added DBS and HDT to list of supported data calibration types
- changed output format for wind angles from [-180..180] to [0..360], because negative value are not displayed properly on pages
2026-01-17 12:25:57 +01:00
TobiasE-github
e4214beefe general but flexible limits for formatting numbers 2026-01-17 11:02:25 +01:00
TobiasE-github
02c611ead0 use 9.95 as the limit for formatting numbers below and above 10 2026-01-15 17:56:52 +01:00
norbert-walter
0b79b7e2ef Modify PageDigitalOut 2026-01-15 15:08:42 +01:00
Norbert Walter
da975b5175 Merge pull request #217 from Scorgan01/WindPlot-v3
Graphical charts v3 + PageOneValue/PageTwoValues with chart option + updated PageWindPlot
2026-01-11 17:18:24 +01:00
Ulrich Meine
cd3c99d509 PageOneValue: fix unit position 2026-01-11 17:07:27 +01:00
Ulrich Meine
86a078690a PageTwoValues: added chart display option 2026-01-11 15:30:13 +01:00
Ulrich Meine
4747336a69 Code rework for OBPcharts, part 3 2026-01-10 12:31:37 +01:00
Ulrich Meine
84736e6769 OBP60Formatter: add option to switch of creation of simulation data and use pure conversion/formatting function 2026-01-10 01:50:19 +01:00
Scorgan01
1557126823 Merge branch 'norbert-walter:master' into WindPlot-v3 2026-01-07 19:32:58 +01:00
Norbert Walter
9ff5ae36db Merge pull request #216 from TobiasE-github/master
don't skip displayNew at startup (fixes issue 215)
2026-01-07 17:20:51 +01:00
Ulrich Meine
2d4f49659d Code rework for OBPcharts, part 2 2026-01-06 22:57:07 +01:00
Ulrich Meine
559042da78 Code rework for OBPcharts, part 1 2026-01-05 23:19:12 +01:00
TobiasE-github
2b6fc09b7e don't skip displayNew at startup (fixes issue 215) 2026-01-05 13:34:32 +01:00
Ulrich Meine
2e836bc750 added helper method for boat value conversion to OBP60Formatter;
fixed range calculation for temperature and other value formats;
fixed printing for names > len(3);
show "mode" only for supported data types
2026-01-01 22:52:33 +01:00
Ulrich Meine
784cc15b8f adjusted simulation calc in OBPFormatter;
WindPlot + PageOneValue: aligned simulation data handling to standard; added "holdValues";
improved data check for chart buffer data; changed handling of tmpBVal -> always unique_ptr
2025-12-24 01:40:37 +01:00
Ulrich Meine
69754b85fd optimized chart initialization for PageWindPlot; added chart options to PageOneValue;
printing of current boat value on horizontal half charts is selectable;
fixed value axis direction for depth and other boat data; changed time axis labels to full numbers;
changed "INTV" button label to "ZOOM"
2025-12-23 18:16:53 +01:00
Ulrich Meine
362338a7dd Optimized PageWindPlot code;
added WindUtils AWD/TWD calculation from AWA/TWA if available
2025-12-21 13:10:44 +01:00
Ulrich Meine
41a8e7d078 General history buffers working; fine tuning required 2025-12-20 22:42:42 +01:00
Ulrich Meine
d655674529 Revert wind speed chart for horizontal charts; changed chart format parameters to <char>; adjusted chart size to OBP standard
Revert wind speed chart for horizontal charts; changed chart format parameters to <char>; adjusted chart size to OBP standard - commit from PageWindPlot-v2
2025-12-18 20:24:14 +01:00
Ulrich Meine
3ce1e31e64 Initial change to history buffers taking any boat value 2025-12-13 17:59:16 +01:00
wellenvogel
dd3a4f5093 fix a bug in the actisense reader, version 20251126 2025-11-26 18:02:41 +01:00
wellenvogel
32099487fa prepare relase 20251007 2025-10-07 13:02:19 +02:00
wellenvogel
18b46ae5a0 prepare relase 20251007 2025-10-07 13:00:28 +02:00
wellenvogel
fb62e41bd9 prepare relase 20251007 2025-10-07 12:58:28 +02:00
wellenvogel
9211b13dcd #113: add env4 to cibuild 2025-09-30 20:09:13 +02:00
wellenvogel
6da87e4455 add buildname to ci output file names, correctly set initial build name 2025-09-30 20:03:45 +02:00
wellenvogel
5493c9695c add buildname to cibuild, use the firmware name for file names 2025-09-30 15:27:50 +02:00
wellenvogel
034a338a81 set default for serial enable low = 0 2025-09-30 12:16:51 +02:00
wellenvogel
3cd508a239 better description of pins for cibuild 2025-09-30 12:13:06 +02:00
wellenvogel
68239f6199 add fixed baud to cibuild, allow enable pin also for pure rx/tx serial 2025-09-29 19:34:35 +02:00
wellenvogel
b683413129 #117: add handling for an output enable pin for serial channels 2025-09-29 19:13:02 +02:00
wellenvogel
566d84d3e6 correctly handle ifdefs for SHT4X 2025-09-29 18:04:04 +02:00
wellenvogel
432a10bfb1 correctly send 130311 for QMP6988 2025-09-29 17:54:16 +02:00
wellenvogel
32862b9e29 avoid creating unmapped XDR entries for unset N2K values 2025-09-29 17:53:14 +02:00
wellenvogel
c21592599f re-add GwSHTXX* 2025-09-29 17:51:41 +02:00
wellenvogel
fddc3c742b allow to switch PGN 130311 for sensors on/off, simplify config.json for i2csensors 2025-09-29 17:05:48 +02:00
wellenvogel
9831f8da85 add code for SHT4X, ENV4 2025-09-29 12:43:21 +02:00
wellenvogel
8bf8ada30e #111: allow to add extra scripts with custom_script 2025-09-29 09:35:14 +02:00
wellenvogel
6266f85db6 #116: sda and scl are swapped when building with online service 2025-09-28 19:58:54 +02:00
wellenvogel
70ad5cc903 omit external nmea2ktoais 2025-09-28 19:28:30 +02:00
wellenvogel
df9b377b31 move the nmea2ktoais functions back into our code base. 2025-09-28 18:47:17 +02:00
wellenvogel
d0966159c0 separate building AIS class 24 2025-09-28 18:37:26 +02:00
wellenvogel
3f22164b1d use forked NMEA2000 lib 2025-09-26 20:00:30 +02:00
wellenvogel
60d06cd9ee remove wrong addEmptyField 2025-09-26 19:57:08 +02:00
wellenvogel
9633abc481 correct timestamp for pass format 2025-09-26 19:56:44 +02:00
wellenvogel
24502e423e set talker/channel when converting AIS from N2K, new lib version n2ktoais 2025-09-25 19:25:54 +02:00
wellenvogel
4a442c6dfb Merge branch 'master' into new-n2ktoais 2025-09-25 17:38:23 +02:00
wellenvogel
448af708d4 fill timestamp for actisense with frame timestamp in sendN2K 2025-09-25 17:37:59 +02:00
wellenvogel
78aafd308a seasmart for sendN2K 2025-09-24 20:30:44 +02:00
wellenvogel
b7cd8c6bdd add pass format to sendN2K 2025-09-24 18:29:22 +02:00
wellenvogel
e5968b8480 some error handling and stats to sendN2K 2025-09-24 18:10:06 +02:00
wellenvogel
e5c4f0b179 add actisense mode to sendN2K 2025-09-23 20:33:34 +02:00
wellenvogel
4b03fa5a23 add filter to sendN2K 2025-09-23 19:07:53 +02:00
wellenvogel
13eac9508d add some helper tools for converting candumps 2025-09-23 18:29:43 +02:00
wellenvogel
ec807c6925 intermediate: adapt handling to new n2ktoais lib 2025-09-23 13:05:28 +02:00
wellenvogel
3df2571ca2 intermediate: prepare AIS class 21 to 129041 2025-09-21 21:02:31 +02:00
wellenvogel
e578b428c9 more parameters for AIS type 21 2025-09-21 19:32:02 +02:00
wellenvogel
6e0d56316b add some testing tools 2025-09-21 16:40:24 +02:00
wellenvogel
7fd1457296 add conversion from NMEA0183 AIS type 21 (aton) to PGN 129041 2025-09-19 20:43:28 +02:00
wellenvogel
e8c5440a79 #114: correctly distinguish between type 1..3 ais messages when converting from N2K 2025-09-19 12:13:41 +02:00
wellenvogel
95df5858ac #112: clearify licenses to be GPL v2 or later 2025-09-17 20:05:45 +02:00
wellenvogel
d5a9568b67 make birectional channels the default, clean up some channel handling 2025-09-17 17:57:03 +02:00
wellenvogel
3d131c7d98 correctly set the mode/type for serial channels 2025-09-17 17:40:28 +02:00
wellenvogel
7ebd582ca0 set serial mode to RX for M5 GPS base 2025-09-17 17:39:57 +02:00
wellenvogel
85f49857da Merge branch 'master' into pr_115 2025-09-17 15:46:33 +02:00
wellenvogel
976e8172e3 Merge branch 'master' of github.com:wellenvogel/esp32-nmea2000 2025-09-16 20:22:50 +02:00
wellenvogel
47fcb26961 add a generic s3 (devkit-m) to cibuild 2025-09-16 20:22:09 +02:00
free-x
da6022cb28 #110: add GPS v1.1 unit 2025-09-11 20:23:52 +02:00
free-x
2c97eacd76 minor corrections 2025-09-11 19:36:09 +02:00
free-x
370fd47deb add GPS V2 Base to webinstall configs 2025-09-09 15:03:48 +02:00
free-x
37d945a0ea intermediate GPS 2.0 base 2025-09-09 13:07:16 +02:00
112 changed files with 6957 additions and 4122 deletions

View File

@@ -62,5 +62,5 @@ jobs:
with: with:
repo_token: ${{ secrets.GITHUB_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ steps.version.outputs.version}} tag: ${{ steps.version.outputs.version}}
file: ./.pio/build/*/*-{all,update}.bin file: ./.pio/build/*/*${{ steps.version.outputs.version }}*-{all,update}.bin
file_glob: true file_glob: true

View File

@@ -43,6 +43,10 @@ What is included
For the details of the mapped PGNs and NMEA sentences refer to [Conversions](doc/Conversions.pdf). 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 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. 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 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) [20250305](../../releases/tag/20250305)
********* *********
* better handling for reconnect to a raspberry pi after reset [#102](../../issues/102) * better handling for reconnect to a raspberry pi after reset [#102](../../issues/102)

Binary file not shown.

Binary file not shown.

View File

@@ -10,7 +10,7 @@ from datetime import datetime
import re import re
import pprint import pprint
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
from platformio.project.exception import InvalidProjectConfError
Import("env") Import("env")
#print(env.Dump()) #print(env.Dump())
@@ -104,18 +104,7 @@ def writeFileIfChanged(fileName,data):
return True return True
def mergeConfig(base,other): def mergeConfig(base,other):
try: for cname in other:
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): if os.path.exists(cname):
print("merge config %s"%cname) print("merge config %s"%cname)
with open(cname,'rb') as ah: with open(cname,'rb') as ah:
@@ -161,13 +150,25 @@ def expandConfig(config):
rt.append(replaceTexts(c,replace)) rt.append(replaceTexts(c,replace))
return rt 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): if not os.path.exists(inFile):
raise Exception("unable to read cfg file %s"%inFile) raise Exception("unable to read cfg file %s"%inFile)
data="" data=""
with open(inFile,'rb') as ch: with open(inFile,'rb') as ch:
config=json.load(ch) config=json.load(ch)
config=mergeConfig(config,addDirs) config=mergeConfig(config,addFiles)
config=expandConfig(config) config=expandConfig(config)
data=json.dumps(config,indent=2) data=json.dumps(config,indent=2)
writeFileIfChanged(outFile,data) writeFileIfChanged(outFile,data)
@@ -387,12 +388,7 @@ def getLibs():
def joinFiles(target,pattern,dirlist): def joinFiles(target,flist):
flist=[]
for dir in dirlist:
fn=os.path.join(dir,pattern)
if os.path.exists(fn):
flist.append(fn)
current=False current=False
if os.path.exists(target): if os.path.exists(target):
current=True current=True
@@ -463,7 +459,28 @@ def handleDeps(env):
) )
env.AddBuildMiddleware(injectIncludes) 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): def prebuild(env):
global userTaskDirs global userTaskDirs
print("#prebuild running") print("#prebuild running")
@@ -473,14 +490,18 @@ def prebuild(env):
if ldf_mode == 'off': if ldf_mode == 'off':
print("##ldf off - own dependency handling") print("##ldf off - own dependency handling")
handleDeps(env) 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() userTaskDirs=getUserTaskDirs()
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE)) 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") compressFile(mergedConfig,mergedConfig+".gz")
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False) generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False)
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True) 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(),INDEXJS+".gz"),createUserItemList(["web"]+userTaskDirs,INDEXJS,getFileList(extraJs)))
joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),INDEXCSS,["web"]+userTaskDirs) joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),createUserItemList(["web"]+userTaskDirs,INDEXCSS,getFileList(extraCss)))
embedded=getEmbeddedFiles(env) embedded=getEmbeddedFiles(env)
filedefs=[] filedefs=[]
for ef in embedded: for ef in embedded:
@@ -526,17 +547,16 @@ env.Append(
) )
#script does not run on clean yet - maybe in the future #script does not run on clean yet - maybe in the future
env.AddPostAction("clean",cleangenerated) env.AddPostAction("clean",cleangenerated)
extraScripts=getFileList(getOption(env,'custom_script',toArray=True))
#look for extra task scripts and include them here for script in extraScripts:
for taskdir in userTaskDirs:
script = os.path.join(taskdir, "extra_task.py")
if os.path.isfile(script): if os.path.isfile(script):
taskname = os.path.basename(os.path.normpath(taskdir)) print(f"#extra {script}")
print("#extra task script for '{}'".format(taskname))
with open(script) as fh: with open(script) as fh:
try: try:
code = compile(fh.read(), taskname, 'exec') code = compile(fh.read(), script, 'exec')
except SyntaxError: except SyntaxError as e:
print("#ERROR: script does not compile") print(f"#ERROR: script {script} does not compile: {e}")
continue continue
exec(code) exec(code)
else:
print(f"#ERROR: script {script} not found")

View File

@@ -1,542 +0,0 @@
print("running extra...")
import gzip
import shutil
import os
import sys
import inspect
import json
import glob
from datetime import datetime
import re
import pprint
from platformio.project.config import ProjectConfig
from platformio.project.exception import InvalidProjectConfError
Import("env")
#print(env.Dump())
OWN_FILE="extra_script.py"
GEN_DIR='lib/generated'
CFG_FILE='web/config.json'
XDR_FILE='web/xdrconfig.json'
INDEXJS="index.js"
INDEXCSS="index.css"
CFG_INCLUDE='GwConfigDefinitions.h'
CFG_INCLUDE_IMPL='GwConfigDefImpl.h'
XDR_INCLUDE='GwXdrTypeMappings.h'
TASK_INCLUDE='GwUserTasks.h'
GROVE_CONFIG="GwM5GroveGen.h"
GROVE_CONFIG_IN="lib/hardware/GwM5Grove.in"
EMBEDDED_INCLUDE="GwEmbeddedFiles.h"
def getEmbeddedFiles(env):
rt=[]
efiles=env.GetProjectOption("board_build.embed_files")
for f in efiles.split("\n"):
if f == '':
continue
rt.append(f)
return rt
def basePath():
#see: https://stackoverflow.com/questions/16771894/python-nameerror-global-name-file-is-not-defined
return os.path.dirname(inspect.getfile(lambda: None))
def outPath():
return os.path.join(basePath(),GEN_DIR)
def checkDir():
dn=outPath()
if not os.path.exists(dn):
os.makedirs(dn)
if not os.path.isdir(dn):
print("unable to create %s"%dn)
return False
return True
def isCurrent(infile,outfile):
if os.path.exists(outfile):
otime=os.path.getmtime(outfile)
itime=os.path.getmtime(infile)
if (otime >= itime):
own=os.path.join(basePath(),OWN_FILE)
if os.path.exists(own):
owntime=os.path.getmtime(own)
if owntime > otime:
return False
print("%s is newer then %s, no need to recreate"%(outfile,infile))
return True
return False
def compressFile(inFile,outfile):
if isCurrent(inFile,outfile):
return
print("compressing %s"%inFile)
with open(inFile, 'rb') as f_in:
with gzip.open(outfile, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
def generateFile(infile,outfile,callback,inMode='rb',outMode='w'):
if isCurrent(infile,outfile):
return
print("creating %s"%outfile)
oh=None
with open(infile,inMode) as ch:
with open(outfile,outMode) as oh:
try:
callback(ch,oh,inFile=infile)
oh.close()
except Exception as e:
try:
oh.close()
except:
pass
os.unlink(outfile)
raise
def writeFileIfChanged(fileName,data):
if os.path.exists(fileName):
with open(fileName,"r") as ih:
old=ih.read()
ih.close()
if old == data:
return False
print("#generating %s"%fileName)
with open(fileName,"w") as oh:
oh.write(data)
return True
def mergeConfig(base,other):
try:
customconfig = env.GetProjectOption("custom_config")
except InvalidProjectConfError:
customconfig = None
for bdir in other:
if customconfig and os.path.exists(os.path.join(bdir,customconfig)):
cname=os.path.join(bdir,customconfig)
print("merge custom config {}".format(cname))
with open(cname,'rb') as ah:
base += json.load(ah)
continue
cname=os.path.join(bdir,"config.json")
if os.path.exists(cname):
print("merge config %s"%cname)
with open(cname,'rb') as ah:
merge=json.load(ah)
base=base+merge
return base
def replaceTexts(data,replacements):
if replacements is None:
return data
if isinstance(data,str):
for k,v in replacements.items():
data=data.replace("$"+k,str(v))
return data
if isinstance(data,list):
rt=[]
for e in data:
rt.append(replaceTexts(e,replacements))
return rt
if isinstance(data,dict):
rt={}
for k,v in data.items():
rt[replaceTexts(k,replacements)]=replaceTexts(v,replacements)
return rt
return data
def expandConfig(config):
rt=[]
for item in config:
type=item.get('type')
if type != 'array':
rt.append(item)
continue
replacements=item.get('replace')
children=item.get('children')
name=item.get('name')
if name is None:
name="#unknown#"
if not isinstance(replacements,list):
raise Exception("missing replacements at array %s"%name)
for replace in replacements:
if children is not None:
for c in children:
rt.append(replaceTexts(c,replace))
return rt
def generateMergedConfig(inFile,outFile,addDirs=[]):
if not os.path.exists(inFile):
raise Exception("unable to read cfg file %s"%inFile)
data=""
with open(inFile,'rb') as ch:
config=json.load(ch)
config=mergeConfig(config,addDirs)
config=expandConfig(config)
data=json.dumps(config,indent=2)
writeFileIfChanged(outFile,data)
def generateCfg(inFile,outFile,impl):
if not os.path.exists(inFile):
raise Exception("unable to read cfg file %s"%inFile)
data=""
with open(inFile,'rb') as ch:
config=json.load(ch)
data+="//generated from %s\n"%inFile
l=len(config)
idx=0
if not impl:
data+='#include "GwConfigItem.h"\n'
data+='class GwConfigDefinitions{\n'
data+=' public:\n'
data+=' int getNumConfig() const{return %d;}\n'%(l)
for item in config:
n=item.get('name')
if n is None:
continue
if len(n) > 15:
raise Exception("%s: config names must be max 15 caracters"%n)
data+=' static constexpr const char* %s="%s";\n'%(n,n)
data+="};\n"
else:
data+='void GwConfigHandler::populateConfigs(GwConfigInterface **config){\n'
for item in config:
name=item.get('name')
if name is None:
continue
data+=' configs[%d]='%(idx)
idx+=1
secret="false";
if item.get('type') == 'password':
secret="true"
data+=" new GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret)
data+='}\n'
writeFileIfChanged(outFile,data)
def labelFilter(label):
return re.sub("[^a-zA-Z0-9]","",re.sub("\([0-9]*\)","",label))
def generateXdrMappings(fp,oh,inFile=''):
jdoc=json.load(fp)
oh.write("static GwXDRTypeMapping* typeMappings[]={\n")
first=True
for cat in jdoc:
item=jdoc[cat]
cid=item.get('id')
if cid is None:
continue
tc=item.get('type')
if tc is not None:
if first:
first=False
else:
oh.write(",\n")
oh.write(" new GwXDRTypeMapping(%d,0,%d) /*%s*/"%(cid,tc,cat))
fields=item.get('fields')
if fields is None:
continue
idx=0
for fe in fields:
if not isinstance(fe,dict):
continue
tc=fe.get('t')
id=fe.get('v')
if id is None:
id=idx
idx+=1
l=fe.get('l') or ''
if tc is None or id is None:
continue
if first:
first=False
else:
oh.write(",\n")
oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,id,tc,cat,l))
oh.write("\n")
oh.write("};\n")
for cat in jdoc:
item=jdoc[cat]
cid=item.get('id')
if cid is None:
continue
selectors=item.get('selector')
if selectors is not None:
for selector in selectors:
label=selector.get('l')
value=selector.get('v')
if label is not None and value is not None:
label=labelFilter(label)
define=("GWXDRSEL_%s_%s"%(cat,label)).upper()
oh.write(" #define %s %s\n"%(define,value))
fields=item.get('fields')
if fields is not None:
idx=0
for field in fields:
v=field.get('v')
if v is None:
v=idx
else:
v=int(v)
label=field.get('l')
if v is not None and label is not None:
define=("GWXDRFIELD_%s_%s"%(cat,labelFilter(label))).upper();
oh.write(" #define %s %s\n"%(define,str(v)))
idx+=1
class Grove:
def __init__(self,name) -> None:
self.name=name
def _ss(self,z=False):
if z:
return self.name
return self.name if self.name != 'Z' else ''
def _suffix(self):
return '_'+self.name if self.name != 'Z' else ''
def replace(self,line):
if line is None:
return line
return line.replace('$G$',self._ss()).replace('$Z$',self._ss(True)).replace('$GS$',self._suffix())
def generateGroveDefs(inh,outh,inFile=''):
GROVES=[Grove('Z'),Grove('A'),Grove('B'),Grove('C')]
definition=[]
started=False
def writeConfig():
for grove in GROVES:
for cl in definition:
outh.write(grove.replace(cl))
for line in inh:
if re.match(" *#GROVE",line):
started=True
if len(definition) > 0:
writeConfig()
definition=[]
continue
if started:
definition.append(line)
if len(definition) > 0:
writeConfig()
userTaskDirs=[]
def getUserTaskDirs():
rt=[]
taskdirs=glob.glob(os.path.join( basePath(),'lib','*task*'))
for task in taskdirs:
rt.append(task)
return rt
def checkAndAdd(file,names,ilist):
if not file.endswith('.h'):
return
match=False
for cmp in names:
#print("##check %s<->%s"%(f.lower(),cmp))
if file.lower() == cmp:
match=True
if not match:
return
ilist.append(file)
def genereateUserTasks(outfile):
includes=[]
for task in userTaskDirs:
#print("##taskdir=%s"%task)
base=os.path.basename(task)
includeNames=[base.lower()+".h",'gw'+base.lower()+'.h']
for f in os.listdir(task):
checkAndAdd(f,includeNames,includes)
includeData=""
for i in includes:
print("#task include %s"%i)
includeData+="#include <%s>\n"%i
writeFileIfChanged(outfile,includeData)
def generateEmbedded(elist,outFile):
content=""
for entry in elist:
content+="EMBED_GZ_FILE(\"%s\",%s,\"%s\");\n"%entry
writeFileIfChanged(outFile,content)
def getContentType(fn):
if (fn.endswith('.gz')):
fn=fn[0:-3]
if (fn.endswith('html')):
return "text/html"
if (fn.endswith('json')):
return "application/json"
if (fn.endswith('js')):
return "text/javascript"
if (fn.endswith('css')):
return "text/css"
return "application/octet-stream"
def getLibs():
base=os.path.join(basePath(),"lib")
rt=[]
for sd in os.listdir(base):
if sd == '..':
continue
if sd == '.':
continue
fn=os.path.join(base,sd)
if os.path.isdir(fn):
rt.append(sd)
EXTRAS=['generated']
for e in EXTRAS:
if not e in rt:
rt.append(e)
return rt
def joinFiles(target,pattern,dirlist):
flist=[]
for dir in dirlist:
fn=os.path.join(dir,pattern)
if os.path.exists(fn):
flist.append(fn)
current=False
if os.path.exists(target):
current=True
for f in flist:
if not isCurrent(f,target):
current=False
break
if current:
print("%s is up to date"%target)
return
print("creating %s"%target)
with gzip.open(target,"wb") as oh:
for fn in flist:
print("adding %s to %s"%(fn,target))
with open(fn,"rb") as rh:
shutil.copyfileobj(rh,oh)
OWNLIBS=getLibs()+["FS","WiFi"]
GLOBAL_INCLUDES=[]
def handleDeps(env):
#overwrite the GetProjectConfig
#to inject all our libs
oldGetProjectConfig=env.GetProjectConfig
def GetProjectConfigX(env):
rt=oldGetProjectConfig()
cenv="env:"+env['PIOENV']
libs=[]
for section,options in rt.as_tuple():
if section == cenv:
for key,values in options:
if key == 'lib_deps':
libs=values
mustUpdate=False
for lib in OWNLIBS:
if not lib in libs:
libs.append(lib)
mustUpdate=True
if mustUpdate:
update=[(cenv,[('lib_deps',libs)])]
rt.update(update)
return rt
env.AddMethod(GetProjectConfigX,"GetProjectConfig")
#store the list of all includes after we resolved
#the dependencies for our main project
#we will use them for all compilations afterwards
oldLibBuilder=env.ConfigureProjectLibBuilder
def ConfigureProjectLibBuilderX(env):
global GLOBAL_INCLUDES
project=oldLibBuilder()
#print("##ConfigureProjectLibBuilderX")
#pprint.pprint(project)
if project.depbuilders:
#print("##depbuilders %s"%",".join(map(lambda x: x.path,project.depbuilders)))
for db in project.depbuilders:
idirs=db.get_include_dirs()
for id in idirs:
if not id in GLOBAL_INCLUDES:
GLOBAL_INCLUDES.append(id)
return project
env.AddMethod(ConfigureProjectLibBuilderX,"ConfigureProjectLibBuilder")
def injectIncludes(env,node):
return env.Object(
node,
CPPPATH=env["CPPPATH"]+GLOBAL_INCLUDES
)
env.AddBuildMiddleware(injectIncludes)
def prebuild(env):
global userTaskDirs
print("#prebuild running")
if not checkDir():
sys.exit(1)
ldf_mode=env.GetProjectOption("lib_ldf_mode")
if ldf_mode == 'off':
print("##ldf off - own dependency handling")
handleDeps(env)
userTaskDirs=getUserTaskDirs()
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
compressFile(mergedConfig,mergedConfig+".gz")
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False)
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True)
joinFiles(os.path.join(outPath(),INDEXJS+".gz"),INDEXJS,["web"]+userTaskDirs)
joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),INDEXCSS,["web"]+userTaskDirs)
embedded=getEmbeddedFiles(env)
filedefs=[]
for ef in embedded:
print("#checking embedded file %s"%ef)
(dn,fn)=os.path.split(ef)
pureName=fn
if pureName.endswith('.gz'):
pureName=pureName[0:-3]
ct=getContentType(pureName)
usname=ef.replace('/','_').replace('.','_')
filedefs.append((pureName,usname,ct))
inFile=os.path.join(basePath(),"web",pureName)
if os.path.exists(inFile):
compressFile(inFile,ef)
else:
print("#WARNING: infile %s for %s not found"%(inFile,ef))
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
generateFile(os.path.join(basePath(),GROVE_CONFIG_IN),os.path.join(outPath(),GROVE_CONFIG),generateGroveDefs,inMode='r')
version="dev"+datetime.now().strftime("%Y%m%d")
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
def cleangenerated(source, target, env):
od=outPath()
if os.path.isdir(od):
print("#cleaning up %s"%od)
for f in os.listdir(od):
if f == "." or f == "..":
continue
fn=os.path.join(od,f)
os.unlink(f)
print("#prescript...")
prebuild(env)
board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper()
print("Board=#%s#"%board)
print("BuildFlags=%s"%(" ".join(env["BUILD_FLAGS"])))
env.Append(
LINKFLAGS=[ "-u", "custom_app_desc" ],
CPPDEFINES=[(board,"1")]
)
#script does not run on clean yet - maybe in the future
env.AddPostAction("clean",cleangenerated)
#look for extra task scripts and include them here
for taskdir in userTaskDirs:
script = os.path.join(taskdir, "extra_task.py")
if os.path.isfile(script):
taskname = os.path.basename(os.path.normpath(taskdir))
print("#extra task script for '{}'".format(taskname))
with open(script) as fh:
try:
code = compile(fh.read(), taskname, 'exec')
except SyntaxError:
print("#ERROR: script does not compile")
continue
exec(code)

View File

@@ -1,518 +0,0 @@
print("running extra...")
import gzip
import shutil
import os
import sys
import inspect
import json
import glob
from datetime import datetime
import re
import pprint
from platformio.project.config import ProjectConfig
Import("env")
#print(env.Dump())
OWN_FILE="extra_script.py"
GEN_DIR='lib/generated'
CFG_FILE='web/config.json'
XDR_FILE='web/xdrconfig.json'
INDEXJS="index.js"
INDEXCSS="index.css"
CFG_INCLUDE='GwConfigDefinitions.h'
CFG_INCLUDE_IMPL='GwConfigDefImpl.h'
XDR_INCLUDE='GwXdrTypeMappings.h'
TASK_INCLUDE='GwUserTasks.h'
GROVE_CONFIG="GwM5GroveGen.h"
GROVE_CONFIG_IN="lib/hardware/GwM5Grove.in"
EMBEDDED_INCLUDE="GwEmbeddedFiles.h"
def getEmbeddedFiles(env):
rt=[]
efiles=env.GetProjectOption("board_build.embed_files")
for f in efiles.split("\n"):
if f == '':
continue
rt.append(f)
return rt
def basePath():
#see: https://stackoverflow.com/questions/16771894/python-nameerror-global-name-file-is-not-defined
return os.path.dirname(inspect.getfile(lambda: None))
def outPath():
return os.path.join(basePath(),GEN_DIR)
def checkDir():
dn=outPath()
if not os.path.exists(dn):
os.makedirs(dn)
if not os.path.isdir(dn):
print("unable to create %s"%dn)
return False
return True
def isCurrent(infile,outfile):
if os.path.exists(outfile):
otime=os.path.getmtime(outfile)
itime=os.path.getmtime(infile)
if (otime >= itime):
own=os.path.join(basePath(),OWN_FILE)
if os.path.exists(own):
owntime=os.path.getmtime(own)
if owntime > otime:
return False
print("%s is newer then %s, no need to recreate"%(outfile,infile))
return True
return False
def compressFile(inFile,outfile):
if isCurrent(inFile,outfile):
return
print("compressing %s"%inFile)
with open(inFile, 'rb') as f_in:
with gzip.open(outfile, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
def generateFile(infile,outfile,callback,inMode='rb',outMode='w'):
if isCurrent(infile,outfile):
return
print("creating %s"%outfile)
oh=None
with open(infile,inMode) as ch:
with open(outfile,outMode) as oh:
try:
callback(ch,oh,inFile=infile)
oh.close()
except Exception as e:
try:
oh.close()
except:
pass
os.unlink(outfile)
raise
def writeFileIfChanged(fileName,data):
if os.path.exists(fileName):
with open(fileName,"r") as ih:
old=ih.read()
ih.close()
if old == data:
return False
print("#generating %s"%fileName)
with open(fileName,"w") as oh:
oh.write(data)
return True
def mergeConfig(base,other):
for bdir in other:
cname=os.path.join(bdir,"config.json")
if os.path.exists(cname):
print("merge config %s"%cname)
with open(cname,'rb') as ah:
merge=json.load(ah)
base=base+merge
return base
def replaceTexts(data,replacements):
if replacements is None:
return data
if isinstance(data,str):
for k,v in replacements.items():
data=data.replace("$"+k,str(v))
return data
if isinstance(data,list):
rt=[]
for e in data:
rt.append(replaceTexts(e,replacements))
return rt
if isinstance(data,dict):
rt={}
for k,v in data.items():
rt[replaceTexts(k,replacements)]=replaceTexts(v,replacements)
return rt
return data
def expandConfig(config):
rt=[]
for item in config:
type=item.get('type')
if type != 'array':
rt.append(item)
continue
replacements=item.get('replace')
children=item.get('children')
name=item.get('name')
if name is None:
name="#unknown#"
if not isinstance(replacements,list):
raise Exception("missing replacements at array %s"%name)
for replace in replacements:
if children is not None:
for c in children:
rt.append(replaceTexts(c,replace))
return rt
def generateMergedConfig(inFile,outFile,addDirs=[]):
if not os.path.exists(inFile):
raise Exception("unable to read cfg file %s"%inFile)
data=""
with open(inFile,'rb') as ch:
config=json.load(ch)
config=mergeConfig(config,addDirs)
config=expandConfig(config)
data=json.dumps(config,indent=2)
writeFileIfChanged(outFile,data)
def generateCfg(inFile,outFile,impl):
if not os.path.exists(inFile):
raise Exception("unable to read cfg file %s"%inFile)
data=""
with open(inFile,'rb') as ch:
config=json.load(ch)
data+="//generated from %s\n"%inFile
l=len(config)
idx=0
if not impl:
data+='#include "GwConfigItem.h"\n'
data+='class GwConfigDefinitions{\n'
data+=' public:\n'
data+=' int getNumConfig() const{return %d;}\n'%(l)
for item in config:
n=item.get('name')
if n is None:
continue
if len(n) > 15:
raise Exception("%s: config names must be max 15 caracters"%n)
data+=' static constexpr const char* %s="%s";\n'%(n,n)
data+="};\n"
else:
data+='void GwConfigHandler::populateConfigs(GwConfigInterface **config){\n'
for item in config:
name=item.get('name')
if name is None:
continue
data+=' configs[%d]='%(idx)
idx+=1
secret="false";
if item.get('type') == 'password':
secret="true"
data+=" new GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret)
data+='}\n'
writeFileIfChanged(outFile,data)
def labelFilter(label):
return re.sub("[^a-zA-Z0-9]","",re.sub("\([0-9]*\)","",label))
def generateXdrMappings(fp,oh,inFile=''):
jdoc=json.load(fp)
oh.write("static GwXDRTypeMapping* typeMappings[]={\n")
first=True
for cat in jdoc:
item=jdoc[cat]
cid=item.get('id')
if cid is None:
continue
tc=item.get('type')
if tc is not None:
if first:
first=False
else:
oh.write(",\n")
oh.write(" new GwXDRTypeMapping(%d,0,%d) /*%s*/"%(cid,tc,cat))
fields=item.get('fields')
if fields is None:
continue
idx=0
for fe in fields:
if not isinstance(fe,dict):
continue
tc=fe.get('t')
id=fe.get('v')
if id is None:
id=idx
idx+=1
l=fe.get('l') or ''
if tc is None or id is None:
continue
if first:
first=False
else:
oh.write(",\n")
oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,id,tc,cat,l))
oh.write("\n")
oh.write("};\n")
for cat in jdoc:
item=jdoc[cat]
cid=item.get('id')
if cid is None:
continue
selectors=item.get('selector')
if selectors is not None:
for selector in selectors:
label=selector.get('l')
value=selector.get('v')
if label is not None and value is not None:
label=labelFilter(label)
define=("GWXDRSEL_%s_%s"%(cat,label)).upper()
oh.write(" #define %s %s\n"%(define,value))
fields=item.get('fields')
if fields is not None:
idx=0
for field in fields:
v=field.get('v')
if v is None:
v=idx
else:
v=int(v)
label=field.get('l')
if v is not None and label is not None:
define=("GWXDRFIELD_%s_%s"%(cat,labelFilter(label))).upper();
oh.write(" #define %s %s\n"%(define,str(v)))
idx+=1
class Grove:
def __init__(self,name) -> None:
self.name=name
def _ss(self,z=False):
if z:
return self.name
return self.name if self.name is not 'Z' else ''
def _suffix(self):
return '_'+self.name if self.name is not 'Z' else ''
def replace(self,line):
if line is None:
return line
return line.replace('$G$',self._ss()).replace('$Z$',self._ss(True)).replace('$GS$',self._suffix())
def generateGroveDefs(inh,outh,inFile=''):
GROVES=[Grove('Z'),Grove('A'),Grove('B'),Grove('C')]
definition=[]
started=False
def writeConfig():
for grove in GROVES:
for cl in definition:
outh.write(grove.replace(cl))
for line in inh:
if re.match(" *#GROVE",line):
started=True
if len(definition) > 0:
writeConfig()
definition=[]
continue
if started:
definition.append(line)
if len(definition) > 0:
writeConfig()
userTaskDirs=[]
def getUserTaskDirs():
rt=[]
taskdirs=glob.glob(os.path.join( basePath(),'lib','*task*'))
for task in taskdirs:
rt.append(task)
return rt
def checkAndAdd(file,names,ilist):
if not file.endswith('.h'):
return
match=False
for cmp in names:
#print("##check %s<->%s"%(f.lower(),cmp))
if file.lower() == cmp:
match=True
if not match:
return
ilist.append(file)
def genereateUserTasks(outfile):
includes=[]
for task in userTaskDirs:
#print("##taskdir=%s"%task)
base=os.path.basename(task)
includeNames=[base.lower()+".h",'gw'+base.lower()+'.h']
for f in os.listdir(task):
checkAndAdd(f,includeNames,includes)
includeData=""
for i in includes:
print("#task include %s"%i)
includeData+="#include <%s>\n"%i
writeFileIfChanged(outfile,includeData)
def generateEmbedded(elist,outFile):
content=""
for entry in elist:
content+="EMBED_GZ_FILE(\"%s\",%s,\"%s\");\n"%entry
writeFileIfChanged(outFile,content)
def getContentType(fn):
if (fn.endswith('.gz')):
fn=fn[0:-3]
if (fn.endswith('html')):
return "text/html"
if (fn.endswith('json')):
return "application/json"
if (fn.endswith('js')):
return "text/javascript"
if (fn.endswith('css')):
return "text/css"
return "application/octet-stream"
def getLibs():
base=os.path.join(basePath(),"lib")
rt=[]
for sd in os.listdir(base):
if sd == '..':
continue
if sd == '.':
continue
fn=os.path.join(base,sd)
if os.path.isdir(fn):
rt.append(sd)
EXTRAS=['generated']
for e in EXTRAS:
if not e in rt:
rt.append(e)
return rt
def joinFiles(target,pattern,dirlist):
flist=[]
for dir in dirlist:
fn=os.path.join(dir,pattern)
if os.path.exists(fn):
flist.append(fn)
current=False
if os.path.exists(target):
current=True
for f in flist:
if not isCurrent(f,target):
current=False
break
if current:
print("%s is up to date"%target)
return
print("creating %s"%target)
with gzip.open(target,"wb") as oh:
for fn in flist:
print("adding %s to %s"%(fn,target))
with open(fn,"rb") as rh:
shutil.copyfileobj(rh,oh)
OWNLIBS=getLibs()+["FS","WiFi"]
GLOBAL_INCLUDES=[]
def handleDeps(env):
#overwrite the GetProjectConfig
#to inject all our libs
oldGetProjectConfig=env.GetProjectConfig
def GetProjectConfigX(env):
rt=oldGetProjectConfig()
cenv="env:"+env['PIOENV']
libs=[]
for section,options in rt.as_tuple():
if section == cenv:
for key,values in options:
if key == 'lib_deps':
libs=values
mustUpdate=False
for lib in OWNLIBS:
if not lib in libs:
libs.append(lib)
mustUpdate=True
if mustUpdate:
update=[(cenv,[('lib_deps',libs)])]
rt.update(update)
return rt
env.AddMethod(GetProjectConfigX,"GetProjectConfig")
#store the list of all includes after we resolved
#the dependencies for our main project
#we will use them for all compilations afterwards
oldLibBuilder=env.ConfigureProjectLibBuilder
def ConfigureProjectLibBuilderX(env):
global GLOBAL_INCLUDES
project=oldLibBuilder()
#print("##ConfigureProjectLibBuilderX")
#pprint.pprint(project)
if project.depbuilders:
#print("##depbuilders %s"%",".join(map(lambda x: x.path,project.depbuilders)))
for db in project.depbuilders:
idirs=db.get_include_dirs()
for id in idirs:
if not id in GLOBAL_INCLUDES:
GLOBAL_INCLUDES.append(id)
return project
env.AddMethod(ConfigureProjectLibBuilderX,"ConfigureProjectLibBuilder")
def injectIncludes(env,node):
return env.Object(
node,
CPPPATH=env["CPPPATH"]+GLOBAL_INCLUDES
)
env.AddBuildMiddleware(injectIncludes)
def prebuild(env):
global userTaskDirs
print("#prebuild running")
if not checkDir():
sys.exit(1)
ldf_mode=env.GetProjectOption("lib_ldf_mode")
if ldf_mode == 'off':
print("##ldf off - own dependency handling")
handleDeps(env)
userTaskDirs=getUserTaskDirs()
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
compressFile(mergedConfig,mergedConfig+".gz")
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False)
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True)
joinFiles(os.path.join(outPath(),INDEXJS+".gz"),INDEXJS,["web"]+userTaskDirs)
joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),INDEXCSS,["web"]+userTaskDirs)
embedded=getEmbeddedFiles(env)
filedefs=[]
for ef in embedded:
print("#checking embedded file %s"%ef)
(dn,fn)=os.path.split(ef)
pureName=fn
if pureName.endswith('.gz'):
pureName=pureName[0:-3]
ct=getContentType(pureName)
usname=ef.replace('/','_').replace('.','_')
filedefs.append((pureName,usname,ct))
inFile=os.path.join(basePath(),"web",pureName)
if os.path.exists(inFile):
compressFile(inFile,ef)
else:
print("#WARNING: infile %s for %s not found"%(inFile,ef))
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
generateFile(os.path.join(basePath(),GROVE_CONFIG_IN),os.path.join(outPath(),GROVE_CONFIG),generateGroveDefs,inMode='r')
version="dev"+datetime.now().strftime("%Y%m%d")
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
def cleangenerated(source, target, env):
od=outPath()
if os.path.isdir(od):
print("#cleaning up %s"%od)
for f in os.listdir(od):
if f == "." or f == "..":
continue
fn=os.path.join(od,f)
os.unlink(f)
print("#prescript...")
prebuild(env)
board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper()
print("Board=#%s#"%board)
print("BuildFlags=%s"%(" ".join(env["BUILD_FLAGS"])))
env.Append(
LINKFLAGS=[ "-u", "custom_app_desc" ],
CPPDEFINES=[(board,"1")]
)
#script does not run on clean yet - maybe in the future
env.AddPostAction("clean",cleangenerated)

View File

@@ -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) // 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 mmsi = _buffer.getUnsignedValue(30);
auto aidType = _buffer.getUnsignedValue(5); auto aidType = _buffer.getUnsignedValue(5);
auto name = _buffer.getString(120); auto name = _buffer.getString(120);
@@ -640,11 +640,11 @@ void AisDecoder::decodeType21(PayloadBuffer &_buffer, unsigned int _uMsgType, in
auto toStarboard = _buffer.getUnsignedValue(6); auto toStarboard = _buffer.getUnsignedValue(6);
_buffer.getUnsignedValue(4); // epfd type _buffer.getUnsignedValue(4); // epfd type
_buffer.getUnsignedValue(6); // timestamp auto timestamp=_buffer.getUnsignedValue(6); // timestamp
_buffer.getBoolValue(); // off position auto offPosition=_buffer.getBoolValue(); // off position
_buffer.getUnsignedValue(8); // reserved _buffer.getUnsignedValue(8); // reserved
_buffer.getBoolValue(); // RAIM auto raim=_buffer.getBoolValue(); // RAIM
_buffer.getBoolValue(); // virtual aid auto virtualAton=_buffer.getBoolValue(); // virtual aid
_buffer.getBoolValue(); // assigned mode _buffer.getBoolValue(); // assigned mode
_buffer.getUnsignedValue(1); // spare _buffer.getUnsignedValue(1); // spare
@@ -654,7 +654,9 @@ void AisDecoder::decodeType21(PayloadBuffer &_buffer, unsigned int _uMsgType, in
nameExt = _buffer.getString(88); 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) */ /* decode Voyage Report and Static Data (type nibble already pulled from buffer) */

View File

@@ -297,7 +297,8 @@ namespace AIS
bool assigned, unsigned int repeat, bool raim) = 0; 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, 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; virtual void onType24A(unsigned int _uMsgType, unsigned int _repeat, unsigned int _uMmsi, const std::string &_strName) = 0;

View File

@@ -14,6 +14,9 @@
#define LOGLEVEL GwLog::DEBUG #define LOGLEVEL GwLog::DEBUG
#endif #endif
#endif #endif
#ifdef GWBUILD_NAME
#define FIRMWARE_TYPE GWSTRINGIFY(GWBUILD_NAME)
#else
#define FIRMWARE_TYPE GWSTRINGIFY(PIO_ENV_BUILD) #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) #define IDF_VERSION GWSTRINGIFY(ESP_IDF_VERSION_MAJOR) "." GWSTRINGIFY(ESP_IDF_VERSION_MINOR) "." GWSTRINGIFY(ESP_IDF_VERSION_PATCH)

View File

@@ -249,3 +249,16 @@ unsigned long GwChannel::countTx(){
if (! countOut) return 0UL; if (! countOut) return 0UL;
return countOut->getGlobal(); 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";
}

View File

@@ -77,7 +77,8 @@ class GwChannel{
if (maxSourceId < 0) return source == sourceId; if (maxSourceId < 0) return source == sourceId;
return (source >= sourceId && source <= maxSourceId); 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;}; int getMinId(){return sourceId;};
}; };

View File

@@ -1,10 +1,11 @@
#pragma once #pragma once
#include "GwBuffer.h" #include "GwBuffer.h"
#include "GwChannelModes.h"
class GwChannelInterface{ class GwChannelInterface{
public: public:
virtual void loop(bool handleRead,bool handleWrite)=0; virtual void loop(bool handleRead,bool handleWrite)=0;
virtual void readMessages(GwMessageFetcher *writer)=0; virtual void readMessages(GwMessageFetcher *writer)=0;
virtual size_t sendToClients(const char *buffer, int sourceId, bool partial=false)=0; virtual size_t sendToClients(const char *buffer, int sourceId, bool partial=false)=0;
virtual Stream * getStream(bool partialWrites){ return NULL;} virtual Stream * getStream(bool partialWrites){ return NULL;}
virtual String getMode(){return "UNKNOWN";} virtual int getType(){ return GWSERIAL_TYPE_BI;} //return the numeric type
}; };

View File

@@ -15,8 +15,10 @@ class SerInit{
int tx=-1; int tx=-1;
int mode=-1; int mode=-1;
int fixedBaud=-1; int fixedBaud=-1;
SerInit(int s,int r,int t, int m, int b=-1): int ena=-1;
serial(s),rx(r),tx(t),mode(m),fixedBaud(b){} 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; std::vector<SerInit> serialInits;
@@ -47,11 +49,20 @@ static int typeFromMode(const char *mode){
#ifndef GWSERIAL_RX #ifndef GWSERIAL_RX
#define GWSERIAL_RX -1 #define GWSERIAL_RX -1
#endif #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 #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 #else
#ifdef GWSERIAL_MODE #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
#endif #endif
// serial 2 // serial 2
@@ -61,11 +72,20 @@ CFG_SERIAL(SERIAL1_CHANNEL_ID, GWSERIAL_RX, GWSERIAL_TX, typeFromMode(GWSERIAL_M
#ifndef GWSERIAL2_RX #ifndef GWSERIAL2_RX
#define GWSERIAL2_RX -1 #define GWSERIAL2_RX -1
#endif #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 #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 #else
#ifdef GWSERIAL2_MODE #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
#endif #endif
class GwSerialLog : public GwLogWriter class GwSerialLog : public GwLogWriter
@@ -285,8 +305,8 @@ static ChannelParam channelParameters[]={
}; };
template<typename T> template<typename T>
GwSerial* createSerial(GwLog *logger, T* s,int id, bool canRead=true){ GwSerial* createSerial(GwLog *logger, T* s,int id, int type, bool canRead=true){
return new GwSerialImpl<T>(logger,s,id,canRead); return new GwSerialImpl<T>(logger,s,id,type,canRead);
} }
static ChannelParam * findChannelParam(int id){ static ChannelParam * findChannelParam(int id){
@@ -300,7 +320,7 @@ static ChannelParam * findChannelParam(int id){
return param; 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", LOG_DEBUG(GwLog::DEBUG,"create serial: channel=%d, rx=%d,tx=%d",
idx,rx,tx); idx,rx,tx);
ChannelParam *param=findChannelParam(idx); ChannelParam *param=findChannelParam(idx);
@@ -312,19 +332,45 @@ static GwSerial * createSerialImpl(GwConfigHandler *config,GwLog *logger, int id
GwLog *streamLog=setLog?nullptr:logger; GwLog *streamLog=setLog?nullptr:logger;
switch(param->id){ switch(param->id){
case USB_CHANNEL_ID: case USB_CHANNEL_ID:
serialStream=createSerial(streamLog,&USBSerial,param->id); serialStream=createSerial(streamLog,&USBSerial,param->id,type);
break; break;
case SERIAL1_CHANNEL_ID: case SERIAL1_CHANNEL_ID:
serialStream=createSerial(streamLog,&Serial1,param->id); serialStream=createSerial(streamLog,&Serial1,param->id,type);
break; break;
case SERIAL2_CHANNEL_ID: case SERIAL2_CHANNEL_ID:
serialStream=createSerial(streamLog,&Serial2,param->id); serialStream=createSerial(streamLog,&Serial2,param->id,type);
break; break;
} }
if (serialStream == nullptr){ if (serialStream == nullptr){
LOG_DEBUG(GwLog::ERROR,"invalid serial config with id %d",param->id); LOG_DEBUG(GwLog::ERROR,"invalid serial config with id %d",param->id);
return nullptr; 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); serialStream->begin(config->getInt(param->baud,115200),SERIAL_8N1,rx,tx);
if (setLog){ if (setLog){
logger->setWriter(new GwSerialLog(serialStream,config->getBool(param->preventLog,false))); 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; 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); ChannelParam *param=findChannelParam(id);
if (param == nullptr){ if (param == nullptr){
LOG_DEBUG(GwLog::ERROR,"invalid channel id %d",id); LOG_DEBUG(GwLog::ERROR,"invalid channel id %d",id);
return nullptr; return nullptr;
} }
int type=impl->getType();
bool canRead=false; bool canRead=false;
bool canWrite=false; bool canWrite=false;
bool validType=false; bool validType=false;
@@ -425,10 +472,10 @@ void GwChannelList::begin(bool fallbackSerial){
GwChannel *channel=NULL; GwChannel *channel=NULL;
//usb //usb
if (! fallbackSerial){ 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){ if (usbSerial != nullptr){
usbSerial->enableWriteLock(); //as it is used for logging we need this additionally 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){ if (usbChannel != nullptr){
addChannel(usbChannel); addChannel(usbChannel);
} }
@@ -444,10 +491,11 @@ void GwChannelList::begin(bool fallbackSerial){
//new serial config handling //new serial config handling
for (auto &&init:serialInits){ for (auto &&init:serialInits){
LOG_INFO("creating serial channel %d, rx=%d,tx=%d,type=%d",init.serial,init.rx,init.tx,init.mode); LOG_INFO("creating serial channel %d, rx=%d,tx=%d,type=%d fixedBaud=%d ena=%d elow=%d",
GwSerial *ser=createSerialImpl(config,logger,init.serial,init.rx,init.tx); 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){ if (ser != nullptr){
channel=createChannel(logger,config,init.serial,ser,init.mode); channel=createChannel(logger,config,init.serial,ser);
if (channel != nullptr){ if (channel != nullptr){
addChannel(channel); addChannel(channel);
} }
@@ -466,8 +514,8 @@ void GwChannelList::begin(bool fallbackSerial){
config->getInt(config->remotePort), config->getInt(config->remotePort),
config->getBool(config->readTCL) config->getBool(config->readTCL)
); );
}
addChannel(createChannel(logger,config,TCP_CLIENT_CHANNEL_ID,client)); addChannel(createChannel(logger,config,TCP_CLIENT_CHANNEL_ID,client));
}
//udp writer //udp writer
if (config->getBool(GwConfigDefinitions::udpwEnabled)){ if (config->getBool(GwConfigDefinitions::udpwEnabled)){

View File

@@ -3,7 +3,7 @@
This code is free software; you can redistribute it and/or This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 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, This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU

View File

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

View File

@@ -14,5 +14,6 @@ custom_config=
lib/exampletask/exampleConfig.json lib/exampletask/exampleConfig.json
custom_js=lib/exampletask/example.js custom_js=lib/exampletask/example.js
custom_css=lib/exampletask/example.css custom_css=lib/exampletask/example.css
custom_script=lib/exampletask/script.py
upload_port = /dev/esp32 upload_port = /dev/esp32
upload_protocol = esptool upload_protocol = esptool

View File

@@ -0,0 +1,4 @@
Import("env")
print("exampletask extra script running")
syntax error here

View File

@@ -2,6 +2,9 @@
#define _GWWIFI_H #define _GWWIFI_H
#include <WiFi.h> #include <WiFi.h>
#include <GWConfig.h> #include <GWConfig.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
class GwWifi{ class GwWifi{
private: private:
const GwConfigHandler *config; const GwConfigHandler *config;
@@ -16,13 +19,19 @@ class GwWifi{
bool apActive=false; bool apActive=false;
bool fixedApPass=true; bool fixedApPass=true;
bool clientIsConnected=false; bool clientIsConnected=false;
SemaphoreHandle_t wifiMutex=nullptr;
static const TickType_t WIFI_MUTEX_TIMEOUT=pdMS_TO_TICKS(1000);
bool acquireMutex();
void releaseMutex();
public: public:
const char *AP_password = "esp32nmea2k"; const char *AP_password = "esp32nmea2k";
GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass=true); GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass=true);
~GwWifi();
void setup(); void setup();
void loop(); void loop();
bool clientConnected(); bool clientConnected();
bool connectClient(); bool connectClient(); // Blocking version
bool connectClientAsync(); // Non-blocking version for other tasks
String apIP(); String apIP();
bool isApActive(){return apActive;} bool isApActive(){return apActive;}
bool isClientActive(){return wifiClient->asBoolean();} bool isClientActive(){return wifiClient->asBoolean();}

View File

@@ -1,7 +1,6 @@
#include <esp_wifi.h> #include <esp_wifi.h>
#include "GWWifi.h" #include "GWWifi.h"
GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){ GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
this->config=config; this->config=config;
this->logger=log; this->logger=log;
@@ -9,6 +8,28 @@ GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
wifiSSID=config->getConfigItem(config->wifiSSID,true); wifiSSID=config->getConfigItem(config->wifiSSID,true);
wifiPass=config->getConfigItem(config->wifiPass,true); wifiPass=config->getConfigItem(config->wifiPass,true);
this->fixedApPass=fixedApPass; this->fixedApPass=fixedApPass;
wifiMutex=xSemaphoreCreateMutex();
if (wifiMutex==nullptr){
LOG_DEBUG(GwLog::ERROR,"GwWifi: unable to create mutex");
}
}
GwWifi::~GwWifi(){
if (wifiMutex!=nullptr){
vSemaphoreDelete(wifiMutex);
wifiMutex=nullptr;
}
}
bool GwWifi::acquireMutex(){
if (wifiMutex==nullptr) return false;
return xSemaphoreTake(wifiMutex,WIFI_MUTEX_TIMEOUT)==pdTRUE;
}
void GwWifi::releaseMutex(){
if (wifiMutex!=nullptr){
xSemaphoreGive(wifiMutex);
}
} }
void GwWifi::setup(){ void GwWifi::setup(){
LOG_DEBUG(GwLog::LOG,"Wifi setup"); LOG_DEBUG(GwLog::LOG,"Wifi setup");
@@ -85,8 +106,14 @@ bool GwWifi::connectInternal(){
if (wifiClient->asBoolean()){ if (wifiClient->asBoolean()){
clientIsConnected=false; clientIsConnected=false;
LOG_DEBUG(GwLog::LOG,"creating wifiClient ssid=%s",wifiSSID->asString().c_str()); LOG_DEBUG(GwLog::LOG,"creating wifiClient ssid=%s",wifiSSID->asString().c_str());
// CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden
if (!acquireMutex()){
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in connectInternal");
return false;
}
WiFi.setAutoReconnect(false); //#102 WiFi.setAutoReconnect(false); //#102
wl_status_t rt=WiFi.begin(wifiSSID->asCString(),wifiPass->asCString()); wl_status_t rt=WiFi.begin(wifiSSID->asCString(),wifiPass->asCString());
releaseMutex();
LOG_DEBUG(GwLog::LOG,"wifiClient connect returns %d",(int)rt); LOG_DEBUG(GwLog::LOG,"wifiClient connect returns %d",(int)rt);
lastConnectStart=millis(); lastConnectStart=millis();
return true; return true;
@@ -104,9 +131,21 @@ void GwWifi::loop(){
if (lastConnectStart > now || (lastConnectStart + RETRY_MILLIS) < now) if (lastConnectStart > now || (lastConnectStart + RETRY_MILLIS) < now)
{ {
LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString()); LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString());
WiFi.disconnect();
// CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden
if (acquireMutex()){
WiFi.disconnect(true);
delay(300);
esp_wifi_stop();
delay(100);
esp_wifi_start();
releaseMutex();
connectInternal(); connectInternal();
} }
else{
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop");
}
}
} }
else{ else{
if (! clientIsConnected){ if (! clientIsConnected){
@@ -126,11 +165,42 @@ void GwWifi::loop(){
} }
} }
} }
bool GwWifi::clientConnected(){ bool GwWifi::clientConnected(){
return WiFi.status() == WL_CONNECTED; // CRITICAL SECTION: WiFi.status() muss geschützt werden
if (!acquireMutex()){
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in clientConnected");
return false; // Conservative: nehme an, nicht verbunden
}
bool result = WiFi.status() == WL_CONNECTED;
releaseMutex();
return result;
}; };
bool GwWifi::connectClient(){ bool GwWifi::connectClient(){
// CRITICAL SECTION: Disconnect und Connect müssen atomar sein
if (!acquireMutex()){
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in connectClient");
return false;
}
WiFi.disconnect(); WiFi.disconnect();
releaseMutex();
return connectInternal();
}
bool GwWifi::connectClientAsync(){
// Non-blocking version: Versuche Mutex zu nehmen, gib aber sofort auf
// Ideal für Tasks, die nicht blockieren dürfen
if (wifiMutex==nullptr){
LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex not initialized in connectClientAsync");
return false;
}
if (xSemaphoreTake(wifiMutex, 0)!=pdTRUE){
LOG_DEBUG(GwLog::LOG,"GwWifi: connectClientAsync skipped - WiFi busy");
return false; // WiFi ist aktuell busy, versuche es später nochmal
}
WiFi.disconnect();
xSemaphoreGive(wifiMutex);
return connectInternal(); return connectInternal();
} }

View 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

View File

@@ -2,7 +2,7 @@
This code is free software; you can redistribute it and/or This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 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, This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
@@ -20,11 +20,7 @@
#endif #endif
#ifndef _GWHARDWARE_H #ifndef _GWHARDWARE_H
#define _GWHARDWARE_H #define _GWHARDWARE_H
#define GWSERIAL_TYPE_UNI 1 #include "GwChannelModes.h"
#define GWSERIAL_TYPE_BI 2
#define GWSERIAL_TYPE_RX 3
#define GWSERIAL_TYPE_TX 4
#define GWSERIAL_TYPE_UNK 0
#include <GwConfigItem.h> #include <GwConfigItem.h>
#include <HardwareSerial.h> #include <HardwareSerial.h>
#include "GwAppInfo.h" #include "GwAppInfo.h"

View File

@@ -2,7 +2,7 @@
This code is free software; you can redistribute it and/or This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 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, This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
@@ -35,7 +35,12 @@
#ifdef M5_GPS_KIT #ifdef M5_GPS_KIT
GWRESOURCE_USE(BASE,M5_GPS_KIT) GWRESOURCE_USE(BASE,M5_GPS_KIT)
GWRESOURCE_USE(SERIAL1,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 #endif
//M5 ProtoHub //M5 ProtoHub
@@ -61,7 +66,7 @@
#endif #endif
//can kit for M5 Atom //can kit for M5 Atom
#ifdef M5_CAN_KIT #if defined (M5_CAN_KIT)
GWRESOURCE_USE(BASE,M5_CAN_KIT) GWRESOURCE_USE(BASE,M5_CAN_KIT)
GWRESOURCE_USE(CAN,M5_CANKIT) GWRESOURCE_USE(CAN,M5_CANKIT)
#define ESP32_CAN_TX_PIN BOARD_LEFT1 #define ESP32_CAN_TX_PIN BOARD_LEFT1

View File

@@ -2,7 +2,7 @@
This code is free software; you can redistribute it and/or This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 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, This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU

View File

@@ -2,7 +2,7 @@
This code is free software; you can redistribute it and/or This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 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, This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
@@ -43,6 +43,13 @@
#define _GWI_SERIAL_GROOVE$GS$ GWSERIAL_TYPE_RX,9600 #define _GWI_SERIAL_GROOVE$GS$ GWSERIAL_TYPE_RX,9600
#endif #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 #GROVE
//CAN via groove //CAN via groove
#ifdef M5_CANUNIT$GS$ #ifdef M5_CANUNIT$GS$
@@ -64,15 +71,15 @@
#endif #endif
#GROVE #GROVE
//#ifdef M5_ENV4$GS$ #ifdef M5_ENV4$GS$
// #ifndef M5_GROOVEIIC$GS$ #ifndef M5_GROOVEIIC$GS$
// #define M5_GROOVEIIC$GS$ #define M5_GROOVEIIC$GS$
// #endif #endif
// GROOVE_IIC(SHT3X,$Z$,1) GROOVE_IIC(SHT4X,$Z$,1)
// GROOVE_IIC(BMP280,$Z$,1) GROOVE_IIC(BMP280,$Z$,1)
// #define _GWSHT3X #define _GWSHT4X
// #define _GWBMP280 #define _GWBMP280
//#endif #endif
#GROVE #GROVE
//example: -DSHT3XG1_A : defines STH3Xn1 on grove A - x depends on the other devices //example: -DSHT3XG1_A : defines STH3Xn1 on grove A - x depends on the other devices
@@ -93,6 +100,25 @@
#define _GWSHT3X #define _GWSHT3X
#endif #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 #GROVE
#ifdef GWQMP6988G1$GS$ #ifdef GWQMP6988G1$GS$
#ifndef M5_GROOVEIIC$GS$ #ifndef M5_GROOVEIIC$GS$

View File

@@ -23,6 +23,7 @@ class BME280Config : public IICSensorBase{
bool prAct=true; bool prAct=true;
bool tmAct=true; bool tmAct=true;
bool huAct=true; bool huAct=true;
bool sEnv=true;
tN2kTempSource tmSrc=tN2kTempSource::N2kts_InsideTemperature; tN2kTempSource tmSrc=tN2kTempSource::N2kts_InsideTemperature;
tN2kHumiditySource huSrc=tN2kHumiditySource::N2khs_InsideHumidity; tN2kHumiditySource huSrc=tN2kHumiditySource::N2khs_InsideHumidity;
tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric; tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric;
@@ -152,6 +153,7 @@ SensorBase::Creator registerBME280(GwApi *api){
CFG_SGET(s, prNam, prefix); \ CFG_SGET(s, prNam, prefix); \
CFG_SGET(s, tmOff, prefix); \ CFG_SGET(s, tmOff, prefix); \
CFG_SGET(s, prOff, prefix); \ CFG_SGET(s, prOff, prefix); \
CFG_SGET(s, sEnv, prefix); \
s->busId = bus; \ s->busId = bus; \
s->addr = baddr; \ s->addr = baddr; \
s->ok = true; \ s->ok = true; \

View File

@@ -29,6 +29,7 @@ class BMP280Config : public IICSensorBase{
public: public:
bool prAct=true; bool prAct=true;
bool tmAct=true; bool tmAct=true;
bool sEnv=true;
tN2kTempSource tmSrc=tN2kTempSource::N2kts_InsideTemperature; tN2kTempSource tmSrc=tN2kTempSource::N2kts_InsideTemperature;
tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric; tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric;
tN2kHumiditySource huSrc=tN2kHumiditySource::N2khs_Undef; tN2kHumiditySource huSrc=tN2kHumiditySource::N2khs_Undef;
@@ -150,6 +151,7 @@ SensorBase::Creator registerBMP280(GwApi *api){
CFG_SGET(s, prNam, prefix); \ CFG_SGET(s, prNam, prefix); \
CFG_SGET(s, tmOff, prefix); \ CFG_SGET(s, tmOff, prefix); \
CFG_SGET(s, prOff, prefix); \ CFG_SGET(s, prOff, prefix); \
CFG_SGET(s, sEnv,prefix); \
s->busId = bus; \ s->busId = bus; \
s->addr = baddr; \ s->addr = baddr; \
s->ok = true; \ s->ok = true; \

View File

@@ -104,12 +104,19 @@ void sendN2kTemperature(GwApi *api,CFG &cfg,double value, int counterId){
template <class CFG> template <class CFG>
void sendN2kEnvironmentalParameters(GwApi *api,CFG &cfg,double tmValue, double huValue, double prValue, int counterId){ void sendN2kEnvironmentalParameters(GwApi *api,CFG &cfg,double tmValue, double huValue, double prValue, int counterId){
if (! cfg.sEnv) return;
tN2kMsg msg; tN2kMsg msg;
SetN2kEnvironmentalParameters(msg,1,cfg.tmSrc,tmValue,cfg.huSrc,huValue,prValue); SetN2kEnvironmentalParameters(msg,1,cfg.tmSrc,tmValue,cfg.huSrc,huValue,prValue);
api->sendN2kMessage(msg); api->sendN2kMessage(msg);
api->increment(counterId,cfg.prefix+String("hum")); if (huValue != N2kDoubleNA){
api->increment(counterId,cfg.prefix+String("press")); api->increment(counterId,cfg.prefix+String("ehum"));
api->increment(counterId,cfg.prefix+String("temp")); }
if (prValue != N2kDoubleNA){
api->increment(counterId,cfg.prefix+String("epress"));
}
if (tmValue != N2kDoubleNA){
api->increment(counterId,cfg.prefix+String("etemp"));
}
} }
#ifndef _GWI_IIC1 #ifndef _GWI_IIC1

View File

@@ -23,7 +23,7 @@ static std::vector<IICGrove> iicGroveList;
#include "GwBME280.h" #include "GwBME280.h"
#include "GwBMP280.h" #include "GwBMP280.h"
#include "GwQMP6988.h" #include "GwQMP6988.h"
#include "GwSHT3X.h" #include "GwSHTXX.h"
#include <map> #include <map>
#include "GwTimer.h" #include "GwTimer.h"
@@ -91,6 +91,7 @@ void initIicTask(GwApi *api){
GwConfigHandler *config=api->getConfig(); GwConfigHandler *config=api->getConfig();
std::vector<SensorBase::Creator> creators; std::vector<SensorBase::Creator> creators;
creators.push_back(registerSHT3X(api)); creators.push_back(registerSHT3X(api));
creators.push_back(registerSHT4X(api));
creators.push_back(registerQMP6988(api)); creators.push_back(registerQMP6988(api));
creators.push_back(registerBME280(api)); creators.push_back(registerBME280(api));
creators.push_back(registerBMP280(api)); creators.push_back(registerBMP280(api));
@@ -147,13 +148,13 @@ bool initWire(GwLog *logger, TwoWire &wire, int num){
#ifdef _GWI_IIC1 #ifdef _GWI_IIC1
return initWireDo(logger,wire,num,_GWI_IIC1); return initWireDo(logger,wire,num,_GWI_IIC1);
#endif #endif
return initWireDo(logger,wire,num,"",GWIIC_SDA,GWIIC_SCL); return initWireDo(logger,wire,num,"",GWIIC_SCL,GWIIC_SDA);
} }
if (num == 2){ if (num == 2){
#ifdef _GWI_IIC2 #ifdef _GWI_IIC2
return initWireDo(logger,wire,num,_GWI_IIC2); return initWireDo(logger,wire,num,_GWI_IIC2);
#endif #endif
return initWireDo(logger,wire,num,"",GWIIC_SDA2,GWIIC_SCL2); return initWireDo(logger,wire,num,"",GWIIC_SCL2,GWIIC_SDA2);
} }
return false; return false;
} }

View File

@@ -9,6 +9,9 @@ class QMP6988Config : public IICSensorBase{
public: public:
String prNam="Pressure"; String prNam="Pressure";
bool prAct=true; bool prAct=true;
bool sEnv=true;
tN2kTempSource tmSrc=tN2kTempSource::N2kts_InsideTemperature;
tN2kHumiditySource huSrc=tN2kHumiditySource::N2khs_Undef;
tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric; tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric;
float prOff=0; float prOff=0;
QMP6988 *device=nullptr; QMP6988 *device=nullptr;
@@ -39,6 +42,7 @@ class QMP6988Config : public IICSensorBase{
float computed=pressure+prOff; float computed=pressure+prOff;
LOG_DEBUG(GwLog::DEBUG,"%s measure %2.0fPa, computed %2.0fPa",prefix.c_str(), pressure,computed); LOG_DEBUG(GwLog::DEBUG,"%s measure %2.0fPa, computed %2.0fPa",prefix.c_str(), pressure,computed);
sendN2kPressure(api,*this,computed,counterId); 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,prAct,prefix); \
CFG_SGET(s,intv,prefix); \ CFG_SGET(s,intv,prefix); \
CFG_SGET(s,prOff,prefix); \ CFG_SGET(s,prOff,prefix); \
CFG_SGET(s,sEnv,prefix); \
s->busId = bus; \ s->busId = bus; \
s->addr = baddr; \ s->addr = baddr; \
s->ok = true; \ s->ok = true; \

View File

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

View File

@@ -1,10 +1,13 @@
#ifndef _GWSHT3X_H #ifndef _GWSHTXX_H
#define _GWSHT3X_H #define _GWSHTXX_H
#include "GwIicSensors.h" #include "GwIicSensors.h"
#ifdef _GWIIC #ifdef _GWIIC
#if defined(GWSHT3X) || defined(GWSHT3X11) || defined(GWSHT3X12) || defined(GWSHT3X21) || defined(GWSHT3X22) #if defined(GWSHT3X) || defined(GWSHT3X11) || defined(GWSHT3X12) || defined(GWSHT3X21) || defined(GWSHT3X22)
#define _GWSHT3X #define _GWSHT3X
#endif #endif
#if defined(GWSHT4X) || defined(GWSHT4X11) || defined(GWSHT4X12) || defined(GWSHT4X21) || defined(GWSHT4X22)
#define _GWSHT4X
#endif
#else #else
#undef _GWSHT3X #undef _GWSHT3X
#undef GWSHT3X #undef GWSHT3X
@@ -12,9 +15,19 @@
#undef GWSHT3X12 #undef GWSHT3X12
#undef GWSHT3X21 #undef GWSHT3X21
#undef GWSHT3X22 #undef GWSHT3X22
#undef _GWSHT4X
#undef GWSHT4X
#undef GWSHT4X11
#undef GWSHT4X12
#undef GWSHT4X21
#undef GWSHT4X22
#endif #endif
#ifdef _GWSHT3X #ifdef _GWSHT3X
#include "SHT3X.h" #include "SHT3X.h"
#endif #endif
SensorBase::Creator registerSHT3X(GwApi *api); #ifdef _GWSHT4X
#include "SHT4X.h"
#endif
SensorBase::Creator registerSHT3X(GwApi *api);
SensorBase::Creator registerSHT4X(GwApi *api);
#endif #endif

View File

@@ -1,4 +1,4 @@
#include "GwSHT3X.h" #include "GwSHTXX.h"
#ifdef _GWSHT3X #ifdef _GWSHT3X
bool SHT3X::init(uint8_t slave_addr_in, TwoWire* wire_in) bool SHT3X::init(uint8_t slave_addr_in, TwoWire* wire_in)

131
lib/iictask/SHT4X.cpp Normal file
View 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
View 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

View File

@@ -1,49 +1,77 @@
[ [
{ {
"type": "array", "type": "array",
"name": "SHT3X", "name": "SHTXX",
"replace": [ "replace": [
{ {
"b": "1", "b": "1",
"i": "11", "i": "11",
"n": "99" "n": "99",
"x": "3"
}, },
{ {
"b": "1", "b": "1",
"i": "12", "i": "12",
"n": "98" "n": "98",
"x": "3"
}, },
{ {
"b": "2", "b": "2",
"i": "21", "i": "21",
"n": "109" "n": "109",
"x": "3"
}, },
{ {
"b": "2", "b": "2",
"i": "22", "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": [ "children": [
{ {
"name": "SHT3X$itmAct", "name": "SHT$xX$itmAct",
"label": "SHT3X$i Temp", "label": "SHT$xX$i Temp",
"type": "boolean", "type": "boolean",
"default": "true", "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", "category": "iicsensors$b",
"capabilities": { "capabilities": {
"SHT3X$i": "true" "SHT$xX$i": "true"
} }
}, },
{ {
"name": "SHT3X$itmSrc", "name": "SHT$xX$itmSrc",
"label": "SHT3X$i Temp Type", "label": "SHT$xX$i Temp Type",
"type": "list", "type": "list",
"default": "2", "default": "2",
"description": "the NMEA2000 source type for the temperature", "description": "the NMEA2000 source type for the temperature (PGN 130312,130311)",
"list": [ "list": [
{ {
"l": "SeaTemperature", "l": "SeaTemperature",
@@ -112,23 +140,23 @@
], ],
"category": "iicsensors$b", "category": "iicsensors$b",
"capabilities": { "capabilities": {
"SHT3X$i": "true" "SHT$xX$i": "true"
} }
}, },
{ {
"name": "SHT3X$ihuAct", "name": "SHT$xX$ihuAct",
"label": "SHT3X$i Humidity", "label": "SHT$xX$i Humidity",
"type": "boolean", "type": "boolean",
"default": "true", "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", "category": "iicsensors$b",
"capabilities": { "capabilities": {
"SHT3X$i": "true" "SHT$xX$i": "true"
} }
}, },
{ {
"name": "SHT3X$ihuSrc", "name": "SHT$xX$ihuSrc",
"label": "SHT3X$i Humid Type", "label": "SHT$xX$i Humid Type",
"list": [ "list": [
{ {
"l": "OutsideHumidity", "l": "OutsideHumidity",
@@ -141,57 +169,68 @@
], ],
"category": "iicsensors$b", "category": "iicsensors$b",
"capabilities": { "capabilities": {
"SHT3X": "true" "SHT$xX": "true"
} }
}, },
{ {
"name": "SHT3X$iiid", "name": "SHT$xX$iiid",
"label": "SHT3X$i N2K iid", "label": "SHT$xX$i N2K iid",
"type": "number", "type": "number",
"default": "$n", "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", "category": "iicsensors$b",
"min": 0, "min": 0,
"max": 253, "max": 253,
"check": "checkMinMax", "check": "checkMinMax",
"capabilities": { "capabilities": {
"SHT3X$i": "true" "SHT$xX$i": "true"
} }
}, },
{ {
"name": "SHT3X$iintv", "name": "SHT$xX$isEnv",
"label": "SHT3X$i Interval", "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", "type": "number",
"default": 2, "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", "category": "iicsensors$b",
"min": 1, "min": 1,
"max": 300, "max": 300,
"check": "checkMinMax", "check": "checkMinMax",
"capabilities": { "capabilities": {
"SHT3X$i": "true" "SHT$xX$i": "true"
} }
}, },
{ {
"name": "SHT3X$itmNam", "name": "SHT$xX$itmNam",
"label": "SHT3X$i Temp XDR", "label": "SHT$xX$i Temp XDR",
"type": "String", "type": "String",
"default": "Temp$i", "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", "category": "iicsensors$b",
"capabilities": { "capabilities": {
"SHT3X$i": "true" "SHT$xX$i": "true"
} }
}, },
{ {
"name": "SHT3X$ihuNam", "name": "SHT$xX$ihuNam",
"label": "SHT3X$i Humid XDR", "label": "SHT$xX$i Humid XDR",
"type": "String", "type": "String",
"default": "Humidity$i", "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", "category": "iicsensors$b",
"capabilities": { "capabilities": {
"SHT3X$i": "true" "SHT$xX$i": "true"
} }
} }
] ]
@@ -247,6 +286,17 @@
"QMP6988$i": "true" "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", "name": "QMP6988$iintv",
"label": "QMP6988-$i Interval", "label": "QMP6988-$i Interval",
@@ -473,7 +523,7 @@
"label": "BME280-$i N2K iid", "label": "BME280-$i N2K iid",
"type": "number", "type": "number",
"default": "$n", "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", "category": "iicsensors$b",
"min": 0, "min": 0,
"max": 253, "max": 253,
@@ -482,6 +532,17 @@
"BME280$i": "true" "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", "name": "BME280$iintv",
"label": "BME280-$i Interval", "label": "BME280-$i Interval",
@@ -683,7 +744,7 @@
"label": "BMP280-$i N2K iid", "label": "BMP280-$i N2K iid",
"type": "number", "type": "number",
"default": "$n", "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", "category": "iicsensors$b",
"min": 0, "min": 0,
"max": 253, "max": 253,
@@ -692,6 +753,17 @@
"BMP280$i": "true" "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", "name": "BMP280$iintv",
"label": "BMP280-$i Interval", "label": "BMP280-$i Interval",

View File

@@ -11,6 +11,17 @@ build_flags=
-D M5_CAN_KIT -D M5_CAN_KIT
${env.build_flags} ${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] [env:m5stack-atom-bme280]
extends = sensors extends = sensors

View File

@@ -2,7 +2,7 @@
This code is free software; you can redistribute it and/or This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 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, This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
@@ -27,6 +27,8 @@ const double nmTom = 1.852 * 1000;
uint16_t DaysSince1970 = 0; uint16_t DaysSince1970 = 0;
#define boolbit(b) (b?1:0)
class MyAisDecoder : public AIS::AisDecoder class MyAisDecoder : public AIS::AisDecoder
{ {
public: public:
@@ -82,25 +84,24 @@ class MyAisDecoder : public AIS::AisDecoder
tN2kMsg N2kMsg; tN2kMsg N2kMsg;
// PGN129038 SetN2kPGN129038(
N2kMsg,
N2kMsg.SetPGN(129038L); _uMsgType,
N2kMsg.Priority = 4; (tN2kAISRepeat)_Repeat,
N2kMsg.AddByte((_Repeat & 0x03) << 6 | (_uMsgType & 0x3f)); _uMmsi,
N2kMsg.Add4ByteUInt(_uMmsi); _iPosLon/ 600000.0,
N2kMsg.Add4ByteDouble(_iPosLon / 600000.0, 1e-07); _iPosLat / 600000.0,
N2kMsg.Add4ByteDouble(_iPosLat / 600000.0, 1e-07); _bPosAccuracy,
N2kMsg.AddByte((_timestamp & 0x3f) << 2 | (_Raim & 0x01) << 1 | (_bPosAccuracy & 0x01)); _Raim,
N2kMsg.Add2ByteUDouble(decodeCog(_iCog), 1e-04); _timestamp,
N2kMsg.Add2ByteUDouble(_uSog * knToms/10.0, 0.01); decodeCog(_iCog),
N2kMsg.AddByte(0x00); // Communication State (19 bits) _uSog * knToms/10.0,
N2kMsg.AddByte(0x00); tN2kAISTransceiverInformation::N2kaischannel_A_VDL_reception,
N2kMsg.AddByte(0x00); // AIS transceiver information (5 bits) decodeHeading(_iHeading),
N2kMsg.Add2ByteUDouble(decodeHeading(_iHeading), 1e-04); decodeRot(_iRot),
N2kMsg.Add2ByteDouble(decodeRot(_iRot), 3.125E-05); // 1e-3/32.0 (tN2kAISNavStatus)_uNavstatus,
N2kMsg.AddByte(0xF0 | (_uNavstatus & 0x0f)); 0xff
N2kMsg.AddByte(0xff); // Reserved );
N2kMsg.AddByte(0xff); // SID (NA)
send(N2kMsg); send(N2kMsg);
} }
@@ -255,9 +256,40 @@ class MyAisDecoder : public AIS::AisDecoder
send(N2kMsg); send(N2kMsg);
} }
//mmsi, aidType, name + nameExt, posAccuracy, posLon, posLat, toBow, toStern, toPort, toStarboard
virtual void onType21(unsigned int , unsigned int , const std::string &, bool , int , int , unsigned int , unsigned int , unsigned int , unsigned int ) override { 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"); //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, virtual void onType24A(unsigned int _uMsgType, unsigned int _repeat, unsigned int _uMmsi,

View File

@@ -143,7 +143,7 @@ private:
*/ */
GwXDRFoundMapping getOtherFieldMapping(GwXDRFoundMapping &found, int field){ GwXDRFoundMapping getOtherFieldMapping(GwXDRFoundMapping &found, int field){
if (found.empty) return GwXDRFoundMapping(); if (found.empty) return GwXDRFoundMapping();
return xdrMappings->getMapping(found.definition->category, return xdrMappings->getMapping(0,found.definition->category,
found.definition->selector, found.definition->selector,
field, field,
found.instanceId); found.instanceId);

View File

@@ -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) // 129038 AIS Class A Position Report (Message 1, 2, 3)
void HandleAISClassAPosReport(const tN2kMsg &N2kMsg) void HandleAISClassAPosReport(const tN2kMsg &N2kMsg)
{ {
unsigned char SID;
tN2kAISRepeat _Repeat; tN2kAISRepeat _Repeat;
uint32_t _UserID; // MMSI uint32_t _UserID; // MMSI
double _Latitude =N2kDoubleNA; double _Latitude =N2kDoubleNA;
@@ -732,64 +757,19 @@ private:
uint8_t _MessageType = 1; uint8_t _MessageType = 1;
tNMEA0183AISMsg NMEA0183AISMsg; 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)) _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, if (SetAISClassABMessage1(NMEA0183AISMsg, _MessageType, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy,
_RAIM, _Seconds, _COG, _SOG, _Heading, _ROT, _NavStatus)) _RAIM, _Seconds, _COG, _SOG, _Heading, _ROT, _NavStatus))
{ {
SendMessage(NMEA0183AISMsg); 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 } // end 129038 AIS Class A Position Report Message 1/3
@@ -825,84 +805,18 @@ private:
_Length, _Beam, _PosRefStbd, _PosRefBow, _ETAdate, _ETAtime, _Draught, _Destination,21, _Length, _Beam, _PosRefStbd, _PosRefBow, _ETAdate, _ETAtime, _Draught, _Destination,21,
_AISversion, _GNSStype, _DTE, _AISinfo,_SID)) _AISversion, _GNSStype, _DTE, _AISinfo,_SID))
{ {
setTalkerChannel(NMEA0183AISMsg,_AISinfo);
#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
if (SetAISClassAMessage5(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _IMONumber, _Callsign, _Name, _VesselType, if (SetAISClassAMessage5(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _IMONumber, _Callsign, _Name, _VesselType,
_Length, _Beam, _PosRefStbd, _PosRefBow, _ETAdate, _ETAtime, _Draught, _Destination, _Length, _Beam, _PosRefStbd, _PosRefBow, _ETAdate, _ETAtime, _Draught, _Destination,
_GNSStype, _DTE)) _GNSStype, _DTE,_AISversion))
{ {
if (NMEA0183AISMsg.BuildMsg5Part1()){
SendMessage(NMEA0183AISMsg.BuildMsg5Part1(NMEA0183AISMsg)); SendMessage(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));
} }
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum()); if (NMEA0183AISMsg.BuildMsg5Part2()){
Serial.print(buf); SendMessage(NMEA0183AISMsg);
#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));
} }
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum());
Serial.print(buf);
#endif
} }
} }
} }
@@ -926,35 +840,21 @@ private:
tN2kAISUnit _Unit; tN2kAISUnit _Unit;
bool _Display, _DSC, _Band, _Msg22, _State; bool _Display, _DSC, _Band, _Msg22, _State;
tN2kAISMode _Mode; tN2kAISMode _Mode;
tN2kAISTransceiverInformation _AISTranceiverInformation; tN2kAISTransceiverInformation _AISTransceiverInformation;
uint8_t _SID; uint8_t _SID;
if (ParseN2kPGN129039(N2kMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM, 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; tNMEA0183AISMsg NMEA0183AISMsg;
setTalkerChannel(NMEA0183AISMsg,_AISTransceiverInformation);
if (SetAISClassBMessage18(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM, if (SetAISClassBMessage18(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM,
_Seconds, _COG, _SOG, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State)) _Seconds, _COG, _SOG, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State))
{ {
SendMessage(NMEA0183AISMsg); 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; return;
@@ -976,8 +876,10 @@ private:
{ {
tNMEA0183AISMsg NMEA0183AISMsg; tNMEA0183AISMsg NMEA0183AISMsg;
setTalkerChannel(NMEA0183AISMsg,_AISInfo);
if (SetAISClassBMessage24PartA(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _Name)) if (SetAISClassBMessage24PartA(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _Name))
{ {
SendMessage(NMEA0183AISMsg);
} }
} }
return; return;
@@ -1005,77 +907,51 @@ private:
_Length, _Beam, _PosRefStbd, _PosRefBow, _MothershipID,_AISInfo,_SID)) _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; tNMEA0183AISMsg NMEA0183AISMsg;
setTalkerChannel(NMEA0183AISMsg,_AISInfo);
if (SetAISClassBMessage24(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _VesselType, _Vendor, _Callsign, if (SetAISClassBMessage24PartB(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _VesselType, _Vendor, _Callsign,
_Length, _Beam, _PosRefStbd, _PosRefBow, _MothershipID)) _Length, _Beam, _PosRefStbd, _PosRefBow, _MothershipID))
{ {
SendMessage(NMEA0183AISMsg);
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
} }
} }
return; 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){ void HandleSystemTime(const tN2kMsg &msg){
unsigned char sid=-1; unsigned char sid=-1;
uint16_t DaysSince1970=N2kUInt16NA; uint16_t DaysSince1970=N2kUInt16NA;
@@ -1271,12 +1147,12 @@ private:
double Level=N2kDoubleNA; double Level=N2kDoubleNA;
double Capacity=N2kDoubleNA; double Capacity=N2kDoubleNA;
if (ParseN2kPGN127505(N2kMsg,Instance,FluidType,Level,Capacity)) { 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)){ if (updateDouble(&mapping,Level)){
LOG_DEBUG(GwLog::DEBUG+1,"found fluidlevel mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found fluidlevel mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(Level)); addToXdr(mapping.buildXdrEntry(Level));
} }
mapping=xdrMappings->getMapping(XDRFLUID,FluidType,1,Instance); mapping=xdrMappings->getMapping(Capacity, XDRFLUID,FluidType,1,Instance);
if (updateDouble(&mapping,Capacity)){ if (updateDouble(&mapping,Capacity)){
LOG_DEBUG(GwLog::DEBUG+1,"found fluid capacity mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found fluid capacity mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(Capacity)); addToXdr(mapping.buildXdrEntry(Capacity));
@@ -1294,19 +1170,19 @@ private:
double BatteryTemperature=N2kDoubleNA; double BatteryTemperature=N2kDoubleNA;
if (ParseN2kPGN127508(N2kMsg,BatteryInstance,BatteryVoltage,BatteryCurrent,BatteryTemperature,SID)) { if (ParseN2kPGN127508(N2kMsg,BatteryInstance,BatteryVoltage,BatteryCurrent,BatteryTemperature,SID)) {
int i=0; int i=0;
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRBAT,0,0,BatteryInstance); GwXDRFoundMapping mapping=xdrMappings->getMapping(BatteryVoltage, XDRBAT,0,0,BatteryInstance);
if (updateDouble(&mapping,BatteryVoltage)){ if (updateDouble(&mapping,BatteryVoltage)){
LOG_DEBUG(GwLog::DEBUG+1,"found BatteryVoltage mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found BatteryVoltage mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(BatteryVoltage)); addToXdr(mapping.buildXdrEntry(BatteryVoltage));
i++; i++;
} }
mapping=xdrMappings->getMapping(XDRBAT,0,1,BatteryInstance); mapping=xdrMappings->getMapping(BatteryCurrent,XDRBAT,0,1,BatteryInstance);
if (updateDouble(&mapping,BatteryCurrent)){ if (updateDouble(&mapping,BatteryCurrent)){
LOG_DEBUG(GwLog::DEBUG+1,"found BatteryCurrent mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found BatteryCurrent mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(BatteryCurrent)); addToXdr(mapping.buildXdrEntry(BatteryCurrent));
i++; i++;
} }
mapping=xdrMappings->getMapping(XDRBAT,0,2,BatteryInstance); mapping=xdrMappings->getMapping(BatteryTemperature,XDRBAT,0,2,BatteryInstance);
if (updateDouble(&mapping,BatteryTemperature)){ if (updateDouble(&mapping,BatteryTemperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found BatteryTemperature mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found BatteryTemperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(BatteryTemperature)); addToXdr(mapping.buildXdrEntry(BatteryTemperature));
@@ -1338,13 +1214,13 @@ private:
SendMessage(NMEA0183Msg); SendMessage(NMEA0183Msg);
} }
int i=0; 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)){ if (updateDouble(&mapping,OutsideAmbientAirTemperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(OutsideAmbientAirTemperature)); addToXdr(mapping.buildXdrEntry(OutsideAmbientAirTemperature));
i++; i++;
} }
mapping=xdrMappings->getMapping(XDRPRESSURE,N2kps_Atmospheric,0,0); mapping=xdrMappings->getMapping(AtmosphericPressure,XDRPRESSURE,N2kps_Atmospheric,0,0);
if (updateDouble(&mapping,AtmosphericPressure)){ if (updateDouble(&mapping,AtmosphericPressure)){
LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(AtmosphericPressure)); addToXdr(mapping.buildXdrEntry(AtmosphericPressure));
@@ -1379,19 +1255,19 @@ private:
SendMessage(NMEA0183Msg); SendMessage(NMEA0183Msg);
} }
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,TempSource,0,0); GwXDRFoundMapping mapping=xdrMappings->getMapping(Temperature, XDRTEMP,TempSource,0,0);
if (updateDouble(&mapping,Temperature)){ if (updateDouble(&mapping,Temperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(Temperature)); addToXdr(mapping.buildXdrEntry(Temperature));
i++; i++;
} }
mapping=xdrMappings->getMapping(XDRHUMIDITY,HumiditySource,0,0); mapping=xdrMappings->getMapping(Humidity, XDRHUMIDITY,HumiditySource,0,0);
if (updateDouble(&mapping,Humidity)){ if (updateDouble(&mapping,Humidity)){
LOG_DEBUG(GwLog::DEBUG+1,"found humidity mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found humidity mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(Humidity)); addToXdr(mapping.buildXdrEntry(Humidity));
i++; i++;
} }
mapping=xdrMappings->getMapping(XDRPRESSURE,N2kps_Atmospheric,0,0); mapping=xdrMappings->getMapping(AtmosphericPressure, XDRPRESSURE,N2kps_Atmospheric,0,0);
if (updateDouble(&mapping,AtmosphericPressure)){ if (updateDouble(&mapping,AtmosphericPressure)){
LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(AtmosphericPressure)); addToXdr(mapping.buildXdrEntry(AtmosphericPressure));
@@ -1426,12 +1302,12 @@ private:
SendMessage(NMEA0183Msg); 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)){ if (updateDouble(&mapping,Temperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(Temperature)); 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)){ if (updateDouble(&mapping,setTemperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(setTemperature)); addToXdr(mapping.buildXdrEntry(setTemperature));
@@ -1449,12 +1325,13 @@ private:
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN); LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
return; 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)){ if (updateDouble(&mapping,ActualHumidity)){
LOG_DEBUG(GwLog::DEBUG+1,"found humidity mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found humidity mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(ActualHumidity)); 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)){ if (updateDouble(&mapping,SetHumidity)){
LOG_DEBUG(GwLog::DEBUG+1,"found humidity mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found humidity mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(SetHumidity)); addToXdr(mapping.buildXdrEntry(SetHumidity));
@@ -1472,7 +1349,7 @@ private:
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN); LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
return; 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; if (! updateDouble(&mapping,ActualPressure)) return;
LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(ActualPressure)); addToXdr(mapping.buildXdrEntry(ActualPressure));
@@ -1490,12 +1367,12 @@ private:
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN); LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
} }
for (int i=0;i<8;i++){ 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; if (! updateDouble(&mapping,values[i])) continue;
addToXdr(mapping.buildXdrEntry(values[i])); addToXdr(mapping.buildXdrEntry(values[i]));
} }
for (int i=0;i< 2;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; if (! updateDouble(&mapping,ivalues[i])) continue;
addToXdr(mapping.buildXdrEntry((double)ivalues[i])); addToXdr(mapping.buildXdrEntry((double)ivalues[i]));
} }
@@ -1511,7 +1388,7 @@ private:
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN); LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
} }
for (int i=0;i<3;i++){ 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; if (! updateDouble(&mapping,values[i])) continue;
addToXdr(mapping.buildXdrEntry(values[i])); addToXdr(mapping.buildXdrEntry(values[i]));
} }
@@ -1525,15 +1402,15 @@ private:
speed,pressure,tilt)){ speed,pressure,tilt)){
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN); 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)){ if (updateDouble(&mapping,speed)){
addToXdr(mapping.buildXdrEntry(speed)); addToXdr(mapping.buildXdrEntry(speed));
} }
mapping=xdrMappings->getMapping(XDRENGINE,0,11,instance); mapping=xdrMappings->getMapping(pressure, XDRENGINE,0,11,instance);
if (updateDouble(&mapping,pressure)){ if (updateDouble(&mapping,pressure)){
addToXdr(mapping.buildXdrEntry(pressure)); addToXdr(mapping.buildXdrEntry(pressure));
} }
mapping=xdrMappings->getMapping(XDRENGINE,0,12,instance); mapping=xdrMappings->getMapping(tilt, XDRENGINE,0,12,instance);
if (updateDouble(&mapping,tilt)){ if (updateDouble(&mapping,tilt)){
addToXdr(mapping.buildXdrEntry((double)tilt)); addToXdr(mapping.buildXdrEntry((double)tilt));
} }
@@ -1559,12 +1436,12 @@ private:
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN); LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
return; return;
} }
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,(int)TemperatureSource,0,TemperatureInstance); GwXDRFoundMapping mapping=xdrMappings->getMapping(Temperature, XDRTEMP,(int)TemperatureSource,0,TemperatureInstance);
if (updateDouble(&mapping,Temperature)){ if (updateDouble(&mapping,Temperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(Temperature)); 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)){ if (updateDouble(&mapping,setTemperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str()); LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(setTemperature)); 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(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(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(129810UL, &N2kToNMEA0183Functions::HandleAISClassBMessage24B); // AIS Class B "CS" Static Data Report, Part B
converters.registerConverter(129041UL, &N2kToNMEA0183Functions::HandleAISMessage21); // AIS Aton
#endif #endif
} }

View File

@@ -26,7 +26,7 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
#include <NMEA0183AISMessages.h> #include "NMEA0183AISMessages.h"
#include <N2kTypes.h> #include <N2kTypes.h>
#include <N2kMsg.h> #include <N2kMsg.h>
#include <string.h> #include <string.h>
@@ -34,7 +34,7 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//#include <unordered_map> //#include <unordered_map>
#include <sstream> #include <sstream>
#include <math.h> #include <math.h>
#include <NMEA0183AISMsg.h> #include "NMEA0183AISMsg.h"
const double pi=3.1415926535897932384626433832795; const double pi=3.1415926535897932384626433832795;
const double kmhToms=1000.0/3600.0; const double kmhToms=1000.0/3600.0;
@@ -47,17 +47,15 @@ const double nmTom=1.852*1000;
const double mToFathoms=0.546806649; const double mToFathoms=0.546806649;
const double mToFeet=3.2808398950131; const double mToFeet=3.2808398950131;
const double radsToDegMin = 60 * 360.0 / (2 * pi); // [rad/s -> degree/minute] 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 *********************************** // ************************ Helper for AIS ***********************************
static bool AddMessageType(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType); static bool AddMessageType(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType);
static bool AddRepeat(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Repeat); static bool AddRepeat(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Repeat);
static bool AddUserID(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t UserID); static bool AddUserID(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t UserID);
static bool AddIMONumber(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t &IMONumber); static bool AddIMONumber(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t &IMONumber);
static bool AddText(tNMEA0183AISMsg &NMEA0183AISMsg, char *FieldVal, uint8_t length); 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 AddDimensions(tNMEA0183AISMsg &NMEA0183AISMsg, double Length, double Beam, double PosRefStbd, double PosRefBow);
static bool AddNavStatus(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t &NavStatus); static bool AddNavStatus(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t &NavStatus);
static bool AddROT(tNMEA0183AISMsg &NMEA0183AISMsg, double &rot); static bool AddROT(tNMEA0183AISMsg &NMEA0183AISMsg, double &rot);
@@ -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 ( !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 ( !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 ( !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 ( !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 ( !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. 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 ( !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, 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.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.AddIntToPayloadBin(0, 19) ) return false; // 149-167 | 19 Radio Status (-> 0 NOT SENT WITH THIS PGN!!!!!)
if ( !NMEA0183AISMsg.InitAis()) return false;
if ( !NMEA0183AISMsg.Init("VDM","AI", Prefix) ) return false; int padBits=0;
if ( !NMEA0183AISMsg.AddStrField("1") ) return false; if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
if ( !NMEA0183AISMsg.AddStrField("1") ) return false; if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) 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
return true; return true;
} }
@@ -121,14 +114,16 @@ bool SetAISClassAMessage5(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
uint32_t UserID, uint32_t IMONumber, char *Callsign, char *Name, uint32_t UserID, uint32_t IMONumber, char *Callsign, char *Name,
uint8_t VesselType, double Length, double Beam, double PosRefStbd, uint8_t VesselType, double Length, double Beam, double PosRefStbd,
double PosRefBow, uint16_t ETAdate, double ETAtime, double Draught, 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 // AIS Type 5 Message
NMEA0183AISMsg.ClearAIS(); NMEA0183AISMsg.ClearAIS();
if ( !AddMessageType(NMEA0183AISMsg, 5) ) return false; // 0 - 5 | 6 Message Type -> Constant: 5 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 ( !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 ( !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 ( !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, 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 if ( !AddText(NMEA0183AISMsg, Name, 120) ) return false; // 112-231 | 120 Vessel Name POINT FERMIN -> 20 6-bit characters -> Ascii lt. Table
@@ -146,10 +141,12 @@ bool SetAISClassAMessage5(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
// **************************************************************************** // ****************************************************************************
// AIS position report (class B 129039) -> Type 18: Standard Class B CS Position Report // 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, // double &Latitude, double &Longitude, bool &Accuracy, bool &RAIM,
// uint8_t &Seconds, double &COG, double &SOG, double &Heading, tN2kAISUnit &Unit, // uint8_t &Seconds, double &COG, double &SOG, tN2kAISTransceiverInformation &AISTransceiverInformation,
// bool &Display, bool &DSC, bool &Band, bool &Msg22, tN2kAISMode &Mode, bool &State) // double &Heading, tN2kAISUnit &Unit, bool &Display, bool &DSC, bool &Band, bool &Msg22, tN2kAISMode &Mode,
// bool &State)
// VDM, VDO (AIS VHF Data-link message 18) // VDM, VDO (AIS VHF Data-link message 18)
bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat, uint32_t UserID, bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat, uint32_t UserID,
double Latitude, double Longitude, bool Accuracy, bool RAIM, double Latitude, double Longitude, bool Accuracy, bool RAIM,
@@ -162,7 +159,7 @@ bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false; // 8 - 37 | 30 MMSI if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false; // 8 - 37 | 30 MMSI
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 8) ) return false; // 38-45 | 8 Regional Reserved 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 ( !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 ( !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 ( !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. 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(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(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.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(DSC) ) 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(Band) ) 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(Msg22)) 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(Mode) ) 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(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.AddIntToPayloadBin(0, 20) ) return false; // 148-167 | 20 Radio Status not in PGN 129039
if ( !NMEA0183AISMsg.InitAis()) return false;
if ( !NMEA0183AISMsg.Init("VDM","AI", Prefix) ) return false; int padBits=0;
if ( !NMEA0183AISMsg.AddStrField("1") ) return false; if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
if ( !NMEA0183AISMsg.AddStrField("1") ) return false; if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) 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
return true; return true;
} }
@@ -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 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) // 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 SetAISClassBMessage24PartA(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat, uint32_t UserID, char *Name) {
// AIS Type 24 Message
bool found = false; NMEA0183AISMsg.ClearAIS();
for (size_t i = 0; i < vships.size(); i++) { // Common for PART A AND Part B Bit 0 - 39 / len 40
if ( vships[i]->_userID == UserID ) { if ( !AddMessageType(NMEA0183AISMsg, 24) ) return false; // 0 - 5 | 6 Message Type -> Constant: 24
found = true; if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false; // 6 - 7 | 2 Repeat Indicator: 0 = default; 3 = do not repeat any more
break; 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 ( ! found ) { if ( !AddText(NMEA0183AISMsg, Name, 120) ) return false; // 40-159 | 120 Vessel Name 20 6-bit characters -> Ascii Table
std::string nm; if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 8) ) return false; // 160-167 | 8 Spare
nm+= Name; if ( !NMEA0183AISMsg.InitAis() ) return false;
vships.push_back(new ship(UserID, nm)); int padBits=0;
} if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;
return true; 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, uint32_t UserID, uint8_t VesselType, char *VendorID, char *Callsign,
double Length, double Beam, double PosRefStbd, double PosRefBow, uint32_t MothershipID ) { 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 // AIS Type 24 Message
NMEA0183AISMsg.ClearAIS(); 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 ( !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 ( !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 ( !AddUserID(NMEA0183AISMsg, UserID) ) return false; // 8 - 37 | 30 MMSI
if ( !NMEA0183AISMsg.AddIntToPayloadBin(PartNr, 2) ) return false; // 38-39 | 2 Part Number 0-1 -> if ( !NMEA0183AISMsg.AddIntToPayloadBin(1, 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
// https://www.navcen.uscg.gov/?pageName=AISMessagesB // https://www.navcen.uscg.gov/?pageName=AISMessagesB
// PART B: 40 + 128 = len 168 // 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 ( !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 ( !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.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; return true;
} }
@@ -325,7 +354,6 @@ bool AddIMONumber(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t &IMONumber) {
// 120bit Name or Destination // 120bit Name or Destination
bool AddText(tNMEA0183AISMsg &NMEA0183AISMsg, char *FieldVal, uint8_t length) { bool AddText(tNMEA0183AISMsg &NMEA0183AISMsg, char *FieldVal, uint8_t length) {
uint8_t len = length/6; uint8_t len = length/6;
if ( strlen(FieldVal) > len ) FieldVal[len] = 0; if ( strlen(FieldVal) > len ) FieldVal[len] = 0;
if ( !NMEA0183AISMsg.AddEncodedCharToPayloadBin(FieldVal, length) ) return false; if ( !NMEA0183AISMsg.AddEncodedCharToPayloadBin(FieldVal, length) ) return false;
return true; return true;
@@ -347,29 +375,26 @@ bool AddDimensions(tNMEA0183AISMsg &NMEA0183AISMsg, double Length, double Beam,
uint16_t _PosRefStbd = 0; uint16_t _PosRefStbd = 0;
uint16_t _PosRefPort = 0; uint16_t _PosRefPort = 0;
if (PosRefBow < 0) PosRefBow=0; //could be N2kIsNA if ( PosRefBow >= 0.0 && PosRefBow <= 511.0 ) {
if ( PosRefBow <= 511.0 ) { _PosRefBow = ceil(PosRefBow);
_PosRefBow = round(PosRefBow);
} else { } else {
_PosRefBow = 511; _PosRefBow = 511;
} }
if (PosRefStbd < 0 ) PosRefStbd=0; //could be N2kIsNA
if (PosRefStbd <= 63.0 ) { if ( PosRefStbd >= 0.0 && PosRefStbd <= 63.0 ) {
_PosRefStbd = round(PosRefStbd); _PosRefStbd = ceil(PosRefStbd);
} else { } else {
_PosRefStbd = 63; _PosRefStbd = 63;
} }
if ( !N2kIsNA(Length) ) { if ( !N2kIsNA(Length) ) {
if (Length >= PosRefBow){ _PosRefStern = ceil( Length ) - _PosRefBow;
_PosRefStern=round(Length - PosRefBow); if ( _PosRefStern < 0 ) _PosRefStern = 0;
}
if ( _PosRefStern > 511 ) _PosRefStern = 511; if ( _PosRefStern > 511 ) _PosRefStern = 511;
} }
if ( !N2kIsNA(Beam) ) { if ( !N2kIsNA(Beam) ) {
if (Beam >= PosRefStbd){ _PosRefPort = ceil( Beam ) - _PosRefStbd;
_PosRefPort = round( Beam - PosRefStbd); if ( _PosRefPort < 0 ) _PosRefPort = 0;
}
if ( _PosRefPort > 63 ) _PosRefPort = 63; if ( _PosRefPort > 63 ) _PosRefPort = 63;
} }
@@ -572,3 +597,5 @@ bool AddETADateTime(tNMEA0183AISMsg &NMEA0183AISMsg, uint16_t &ETAdate, double &
if ( ! NMEA0183AISMsg.AddIntToPayloadBin(minute, 6) ) return false; if ( ! NMEA0183AISMsg.AddIntToPayloadBin(minute, 6) ) return false;
return true; return true;
} }

View File

@@ -27,24 +27,16 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef _tNMEA0183AISMessages_H_ #ifndef _tNMEA0183AISMessages_H_
#define _tNMEA0183AISMessages_H_ #define _tNMEA0183AISMessages_H_
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
#include <string.h> #include <string.h>
#include <N2kTypes.h> #include <N2kTypes.h>
#include <NMEA0183AISMsg.h> #include "NMEA0183AISMsg.h"
#include <stddef.h> #include <stddef.h>
#include <vector> #include <vector>
#include <string> #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 // Types 1, 2 and 3: Position Report Class A or B
bool SetAISClassABMessage1(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType, uint8_t Repeat, bool SetAISClassABMessage1(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType, uint8_t Repeat,
@@ -57,7 +49,8 @@ bool SetAISClassAMessage5(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, ui
uint32_t UserID, uint32_t IMONumber, char *Callsign, char *Name, uint32_t UserID, uint32_t IMONumber, char *Callsign, char *Name,
uint8_t VesselType, double Length, double Beam, double PosRefStbd, uint8_t VesselType, double Length, double Beam, double PosRefStbd,
double PosRefBow, uint16_t ETAdate, double ETAtime, double Draught, 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 // AIS position report (class B 129039) -> Standard Class B CS Position Report Message Type 18 Part B
@@ -73,11 +66,19 @@ bool SetAISClassBMessage24PartA(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Message
//***************************************************************************** //*****************************************************************************
// Static Data Report Class B, Message Type 24 // 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, uint32_t UserID, uint8_t VesselType, char *VendorID, char *Callsign,
double Length, double Beam, double PosRefStbd, double PosRefBow, uint32_t MothershipID ); 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) { inline int32_t aRoundToInt(double x) {
return x >= 0 return x >= 0
? (int32_t) floor(x + 0.5) ? (int32_t) floor(x + 0.5)

View File

@@ -25,7 +25,7 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "NMEA0183AISMsg.h" #include "NMEA0183AISMsg.h"
#include <NMEA0183Msg.h> #include <NMEA0183Msg.h>
#include <Arduino.h> //#include <Arduino.h>
#include <math.h> #include <math.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
@@ -43,52 +43,37 @@ tNMEA0183AISMsg::tNMEA0183AISMsg() {
//***************************************************************************** //*****************************************************************************
void tNMEA0183AISMsg::ClearAIS() { void tNMEA0183AISMsg::ClearAIS() {
PayloadBin[0]=0;
Payload[0]=0; Payload[0]=0;
PayloadBin.reset();
iAddPldBin=0; iAddPldBin=0;
iAddPld=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) { bool tNMEA0183AISMsg::AddIntToPayloadBin(int32_t ival, uint16_t countBits) {
if ( (iAddPldBin + countBits ) >= AIS_BIN_MAX_LEN ) return false; // Is there room for any data 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; uint16_t iAdd=iAddPldBin;
for(int i = countBits-1; i >= 0 ; i--) { for(int i = countBits-1; i >= 0 ; i--) {
PayloadBin[iAdd] = bset[i]?'1':'0'; PayloadBin[iAdd]=bset [i];
iAdd++; iAdd++;
} }
iAddPldBin += countBits; iAddPldBin += countBits;
PayloadBin[iAddPldBin]=0;
return true; return true;
} }
// **************************************************************************** //****************************************************************************
bool tNMEA0183AISMsg::AddBoolToPayloadBin(bool &bval, uint8_t size) { bool tNMEA0183AISMsg::AddBoolToPayloadBin(bool &bval) {
int8_t iTemp; if ( (iAddPldBin + 1 ) >= AIS_BIN_MAX_LEN ) return false;
(bval == true)? iTemp = 1 : iTemp = 0; PayloadBin[iAddPldBin]=bval;
if ( ! AddIntToPayloadBin(iTemp, size) ) return false; iAddPldBin++;
return true; 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 if ( (iAddPldBin + countBits ) >= AIS_BIN_MAX_LEN ) return false; // Is there room for any data
PayloadBin[iAddPldBin]=0; const char * ptr;
std::bitset<6> bs;
char * ptr;
size_t len = strlen(sval); // e.g.: should be 7 for Callsign size_t len = strlen(sval); // e.g.: should be 7 for Callsign
if ( len * 6 > countBits ) len = countBits / 6; 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]); ptr = strchr(AsciiChar, sval[i]);
if ( ptr ) { if ( ptr ) {
@@ -117,37 +100,44 @@ bool tNMEA0183AISMsg::AddEncodedCharToPayloadBin(char *sval, size_t countBits) {
AddIntToPayloadBin(0, 6); AddIntToPayloadBin(0, 6);
} }
} }
PayloadBin[iAddPldBin+1]=0;
// fill up with "@", also covers empty sval // fill up with "@", also covers empty sval
if ( len * 6 < countBits ) { 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); AddIntToPayloadBin(0, 6);
} }
} }
PayloadBin[iAddPldBin]=0;
return true; return true;
} }
// ***************************************************************************** //*****************************************************************************
bool tNMEA0183AISMsg::ConvertBinaryAISPayloadBinToAscii(const char *payloadbin) { template <unsigned int S>
uint16_t len; int tNMEA0183AISMsg::ConvertBinaryAISPayloadBinToAscii(std::bitset<S> &src,uint16_t maxSize,uint16_t bitSize,uint16_t stoffset) {
Payload[0]='\0';
len = strlen( payloadbin ) / 6; // 28 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; uint32_t offset;
char s[7]; std::bitset<6> s;
uint8_t dec; uint8_t dec;
int i; int i;
for ( i=0; i<len; i++ ) { for ( i=0; i<len; i++ ) {
offset = i * 6; offset = i * 6;
int k = 0; int k = 5;
for (int j=offset; j<offset+6; j++ ) { for (uint32_t j=offset; j<offset+6; j++ ) {
s[k] = payloadbin[j]; if (j < slen){
k++; s[k] = src[stoffset+j];
} }
s[k]=0; else{
dec = strtoull (s, NULL, 2); //binToDec s[k] = 0;
padBits++;
}
k--;
}
dec = s.to_ulong();
if (dec < 40 ) dec += 48; if (dec < 40 ) dec += 48;
else dec += 56; else dec += 56;
@@ -156,142 +146,56 @@ bool tNMEA0183AISMsg::ConvertBinaryAISPayloadBinToAscii(const char *payloadbin)
} }
Payload[i]=0; 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 ************************ //********************** BUILD 2-parted AIS Sentences ************************
const tNMEA0183AISMsg& tNMEA0183AISMsg::BuildMsg5Part1(tNMEA0183AISMsg &AISMsg) { bool tNMEA0183AISMsg::InitAis(int max,int number,int sequence){
if ( !Init(talker,"AI", '!') ) return false;
Init("VDM", "AI", '!'); if ( !AddUInt32Field(max) ) return false;
AddStrField("2"); if ( !AddUInt32Field(number) ) return false;
AddStrField("1"); if (sequence >= 0){
AddStrField("5"); if ( !AddUInt32Field(sequence) ) return false;
AddStrField("A"); }
AddStrField( GetPayloadType5_Part1() ); else{
AddStrField("0"); if ( !AddEmptyField() ) return false;
}
return AISMsg; 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) { bool tNMEA0183AISMsg::BuildMsg5Part2() {
if ( iAddPldBin != 424 ) return false;
Init("VDM", "AI", '!'); InitAis(2,2,5);
AddStrField("2"); int padBits=0;
AddStrField("2"); AddStrField( GetPayload(padBits,336,88) );
AddStrField("5"); AddUInt32Field(padBits);
AddStrField("A"); return true;
AddStrField( GetPayloadType5_Part2() );
AddStrField("2"); // Message 5, Part 2 has always 2 Padding Zeros
return AISMsg;
} }
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 ********************************* //******************************* AIS PAYLOADS *********************************
//******************************************************************************
// get converted Payload for Message 1, 2, 3 & 18, always Length 168 // get converted Payload for Message 1, 2, 3 & 18, always Length 168
const char *tNMEA0183AISMsg::GetPayload() { const char *tNMEA0183AISMsg::GetPayloadFix(int &padBits,uint16_t fixLen){
uint16_t lenbin = iAddPldBin;
uint16_t lenbin = strlen( PayloadBin); if ( lenbin != fixLen ) return nullptr;
if ( lenbin != 168 ) return nullptr; return GetPayload(padBits,0,0);
}
if ( !ConvertBinaryAISPayloadBinToAscii( PayloadBin ) ) return nullptr; const char *tNMEA0183AISMsg::GetPayload(int &padBits,uint16_t offset,uint16_t bitLen) {
padBits=ConvertBinaryAISPayloadBinToAscii<AIS_BIN_MAX_LEN>(PayloadBin,iAddPldBin, bitLen,offset );
return Payload; 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;
}

View File

@@ -45,43 +45,48 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#define BITSET_LENGTH 120 #define BITSET_LENGTH 120
typedef std::bitset<BITSET_LENGTH> AISBitSet;
class tNMEA0183AISMsg : public tNMEA0183Msg { class tNMEA0183AISMsg : public tNMEA0183Msg {
protected: // AIS-NMEA protected: // AIS-NMEA
std::bitset<BITSET_LENGTH> bset;
static const char *EmptyAISField; // 6bits 0 not used yet..... static const char *EmptyAISField; // 6bits 0 not used yet.....
static const char *AsciChar; static const char *AsciChar;
uint16_t iAddPldBin; uint16_t iAddPldBin;
char Payload[AIS_MSG_MAX_LEN]; char Payload[AIS_MSG_MAX_LEN];
uint8_t iAddPld; uint8_t iAddPld;
char talker[4]="VDM";
char channel[2]="A";
std::bitset<AIS_BIN_MAX_LEN> PayloadBin;
public: public:
char PayloadBin[AIS_BIN_MAX_LEN];
char PayloadBin2[AIS_BIN_MAX_LEN];
// Clear message // Clear message
void ClearAIS(); void ClearAIS();
public: public:
tNMEA0183AISMsg(); tNMEA0183AISMsg();
const char *GetPayload(); const char *GetPayloadFix(int &padBits,uint16_t fixLen=168);
const char *GetPayloadType5_Part1(); const char *GetPayload(int &padBits,uint16_t offset=0,uint16_t bitLen=0);
const char *GetPayloadType5_Part2();
const char *GetPayloadType24_PartA();
const char *GetPayloadType24_PartB();
const char *GetPayloadBin() const { return PayloadBin; }
const tNMEA0183AISMsg& BuildMsg5Part1(tNMEA0183AISMsg &AISMsg); bool BuildMsg5Part1();
const tNMEA0183AISMsg& BuildMsg5Part2(tNMEA0183AISMsg &AISMsg); bool BuildMsg5Part2();
const tNMEA0183AISMsg& BuildMsg24PartA(tNMEA0183AISMsg &AISMsg); bool InitAis(int max=1,int number=1,int sequence=-1);
const tNMEA0183AISMsg& BuildMsg24PartB(tNMEA0183AISMsg &AISMsg);
// Generally Used // Generally Used
bool AddIntToPayloadBin(int32_t ival, uint16_t countBits); 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 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 // AIS Helper functions
protected: protected:

View File

@@ -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 NMEA0183 AIS library © Ronnie Zeiller, www.zeiller.eu
Addendum for NMEA2000 and NMEA0183 Library from Timo Lappalainen https://github.com/ttlappalainen Addendum for NMEA2000 and NMEA0183 Library from Timo Lappalainen https://github.com/ttlappalainen
to get NMEA0183 AIS data from N2k-bus
## Conversions: ## 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 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 - 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 ### Remarks
1. Message Type could be set to 1 or 3 (identical messages) on demand 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) 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 ## License
Permission is hereby granted, free of charge, to any person obtaining a copy of MIT license
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to use, Copyright (c) 2019-2022 Ronnie Zeiller, www.zeiller.eu
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, 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:
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 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 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.
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.

View File

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

View File

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

View File

@@ -14,6 +14,30 @@ https://controllerstech.com/ws2812-leds-using-spi/
*/ */
String Color::toHex() {
char hexColor[8];
sprintf(hexColor, "#%02X%02X%02X", r, g, b);
return String(hexColor);
}
String Color::toName() {
static std::map<int, String> const names = {
{0xff0000, "Red"},
{0x00ff00, "Green"},
{0x0000ff, "Blue",},
{0xff9900, "Orange"},
{0xffff00, "Yellow"},
{0x3366ff, "Aqua"},
{0xff0066, "Violet"},
{0xffffff, "White"}
};
int color = (r << 16) + (g << 8) + b;
auto it = names.find(color);
if (it == names.end()) {
return toHex();
}
return it->second;
}
static uint8_t mulcolor(uint8_t f1, uint8_t f2){ static uint8_t mulcolor(uint8_t f1, uint8_t f2){
uint16_t rt=f1; uint16_t rt=f1;
@@ -22,9 +46,11 @@ static uint8_t mulcolor(uint8_t f1, uint8_t f2){
} }
Color setBrightness(const Color &color,uint8_t brightness){ Color setBrightness(const Color &color,uint8_t brightness){
if (brightness > 100) brightness = 100;
uint16_t br255=brightness*255; uint16_t br255=brightness*255;
br255=br255/100; br255=br255/100;
//very simple for now //Very simple for now
Color rt=color; Color rt=color;
rt.g=mulcolor(rt.g,br255); rt.g=mulcolor(rt.g,br255);
rt.b=mulcolor(rt.b,br255); rt.b=mulcolor(rt.b,br255);

View File

@@ -10,7 +10,7 @@ class Color{
uint8_t g; uint8_t g;
uint8_t b; uint8_t b;
Color():r(0),g(0),b(0){} Color():r(0),g(0),b(0){}
Color(uint8_t cr, uint8_t cg,uint8_t cb): Color(uint8_t cr, uint8_t cg, uint8_t cb):
b(cb),g(cg),r(cr){} b(cb),g(cg),r(cr){}
Color(const Color &o):b(o.b),g(o.g),r(o.r){} Color(const Color &o):b(o.b),g(o.g),r(o.r){}
bool equal(const Color &o) const{ bool equal(const Color &o) const{
@@ -22,6 +22,8 @@ class Color{
bool operator != (const Color &other) const{ bool operator != (const Color &other) const{
return ! equal(other); return ! equal(other);
} }
String toHex();
String toName();
}; };
static Color COLOR_GREEN=Color(0,255,0); static Color COLOR_GREEN=Color(0,255,0);

View File

@@ -1,4 +1,7 @@
#include "NetworkClient.h" #include "NetworkClient.h"
#include "GWWifi.h" // WiFi management (thread-safe)
extern GwWifi gwWifi; // Extern declaration of global WiFi instance
extern "C" { extern "C" {
#include "puff.h" #include "puff.h"
@@ -51,8 +54,13 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
const size_t capacity = READLIMIT; // Read limit for data (can be adjusted in NetworkClient.h) const size_t capacity = READLIMIT; // Read limit for data (can be adjusted in NetworkClient.h)
uint8_t* buffer = (uint8_t*)malloc(capacity); uint8_t* buffer = (uint8_t*)malloc(capacity);
if (!gwWifi.clientConnected()) {
if (DEBUGING) {Serial.println("No WiFi connection");}
return false;
}
if (!buffer) { if (!buffer) {
if (DEBUG) {Serial.println("Malloc failed (buffer");} if (DEBUGING) {Serial.println("Malloc failed buffer");}
return false; return false;
} }
@@ -106,7 +114,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
len += read; len += read;
lastData = millis(); lastData = millis();
if (DEBUG) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);} if (DEBUGING) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);}
if (len < 20) continue; // Not enough data for header if (len < 20) continue; // Not enough data for header
@@ -122,7 +130,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou
int res = puff(test, &testLen, buffer + headerOffset, &srcLen); int res = puff(test, &testLen, buffer + headerOffset, &srcLen);
if (res == 0) { if (res == 0) {
if (DEBUG) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);} if (DEBUGING) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);}
outData = test; outData = test;
outLen = testLen; outLen = testLen;
complete = true; complete = true;
@@ -167,7 +175,7 @@ bool NetworkClient::fetchAndDecompressJson(const String& url) {
return false; return false;
} }
if (DEBUG) {Serial.println("JSON OK!");} if (DEBUGING) {Serial.println("JSON OK!");}
_valid = true; _valid = true;
return true; return true;
} }

View File

@@ -3,7 +3,7 @@
#include <WiFi.h> #include <WiFi.h>
#include <HTTPClient.h> #include <HTTPClient.h>
#define DEBUG false // Debug flag for NetworkClient for more live information #define DEBUGING false // Debug flag for NetworkClient for more live information
#define READLIMIT 200000 // HTTP read limit in byte for gzip content (can be adjusted) #define READLIMIT 200000 // HTTP read limit in byte for gzip content (can be adjusted)
#define CONNECTIONTIMEOUT 3000 // Timeout in ms for HTTP connection #define CONNECTIONTIMEOUT 3000 // Timeout in ms for HTTP connection
#define TCPREADTIMEOUT 2000 // Timeout in ms for read HTTP client stack #define TCPREADTIMEOUT 2000 // Timeout in ms for read HTTP client stack

View File

@@ -57,7 +57,7 @@ GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> & getdisplay(){r
#endif #endif
// Horter I2C moduls // 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 // FRAM
Adafruit_FRAM_I2C fram; Adafruit_FRAM_I2C fram;
@@ -89,8 +89,8 @@ void hardwareInit(GwApi *api)
Wire.begin(); Wire.begin();
// Init PCF8574 digital outputs // Init PCF8574 digital outputs
Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz
if(pcf8574_Out.begin()){ // Initialize PCF8574 if(pcf8574_Modul1.begin()){ // Initialize PCF8574
pcf8574_Out.write8(255); // Clear all outputs pcf8574_Modul1.write8(255); // Clear all outputs (low activ)
} }
Wire.setClock(I2C_SPEED); // Set I2C clock on 100 kHz Wire.setClock(I2C_SPEED); // Set I2C clock on 100 kHz
fram = Adafruit_FRAM_I2C(); fram = Adafruit_FRAM_I2C();
@@ -193,10 +193,23 @@ void powerInit(String powermode) {
} }
} }
void setPCF8574PortPin(uint pin, uint8_t value){ void setPCF8574PortPinModul1(uint8_t pin, uint8_t value)
Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz {
if(pcf8574_Out.begin()){ // Check available and initialize PCF8574 static bool firstRunFinished;
pcf8574_Out.write(pin, value); // Toggle pin 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 Wire.setClock(I2C_SPEED); // Set I2C clock on 100 kHz
} }
@@ -318,6 +331,40 @@ void toggleBacklightLED(uint brightness, const Color &color){
ledTaskData->setLedData(current); 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){ void setFlashLED(bool status){
if (ledTaskData == nullptr) return; if (ledTaskData == nullptr) return;
Color c=status?COLOR_RED:COLOR_BLACK; Color c=status?COLOR_RED:COLOR_BLACK;
@@ -437,12 +484,33 @@ void drawTextCenter(int16_t cx, int16_t cy, String text) {
getdisplay().print(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 // Draw right aligned text
void drawTextRalign(int16_t x, int16_t y, String text) { void drawTextRalign(int16_t x, int16_t y, String text) {
int16_t x1, y1; int16_t x1, y1;
uint16_t w, h; uint16_t w, h;
getdisplay().getTextBounds(text, 0, 150, &x1, &y1, &w, &h); getdisplay().getTextBounds(text, 0, 150, &x1, &y1, &w, &h);
getdisplay().setCursor(x - w, y); getdisplay().setCursor(x - w - 1, y); // '-1' required since some strings wrap around w/o it
getdisplay().print(text); getdisplay().print(text);
} }
@@ -855,7 +923,7 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor){
} }
// Generator graphic with fill level // Generator graphic
void generatorGraphic(uint x, uint y, int pcolor, int bcolor){ void generatorGraphic(uint x, uint y, int pcolor, int bcolor){
// Show battery // Show battery
int xb = x; // X position int xb = x; // X position
@@ -872,6 +940,74 @@ void generatorGraphic(uint x, uint y, int pcolor, int bcolor){
getdisplay().print("G"); getdisplay().print("G");
} }
// Display rudder position as horizontal bargraph with configurable +/- range (degrees)
void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg){
const int w = 360;
const int h = 20;
const int t = 3; // Line thickness
const int halfw = w/2;
const int halfh = h/2;
// Calculate top-left of bar (cx,cy are center of 0°)
int left = int(cx) - halfw;
int top = int(cy) - halfh;
// clamp provided range to allowed bounds [10,45]
if (rangeDeg < 10) rangeDeg = 10;
if (rangeDeg > 45) rangeDeg = 45;
// Pixels per degree for +/-rangeDeg -> total span = 2*rangeDeg
const float pxPerDeg = float(w) / (2.0f * float(rangeDeg));
// Draw outer border (thickness t)
for (int i = 0; i < t; i++) {
getdisplay().drawRect(left + i, top + i, w - 2 * i, h - 2 * i, fg);
}
// Fill inner area with background
getdisplay().fillRect(left + t, top + t, w - 2 * t, h - 2 * t, bg);
// Draw center line
getdisplay().drawRect(cx - 1, top + 1, 3 , h - 2, fg);
// Clamp rudder position to -rangeDeg..rangeDeg
if (rudderPosition > (int)rangeDeg) rudderPosition = (int)rangeDeg;
if (rudderPosition < -((int)rangeDeg)) rudderPosition = -((int)rangeDeg);
// Compute fill width in pixels
int fillPx = int(round(rudderPosition * pxPerDeg)); // positive -> right
// Fill area from center to position (if non-zero)
int centerx = cx;
int innerTop = top + t;
int innerH = h - 2 * t;
if (fillPx > 0) {
// Right side
getdisplay().fillRect(centerx, innerTop, fillPx, innerH, fg);
} else if (fillPx < 0) {
// Left side
getdisplay().fillRect(centerx + fillPx, innerTop, -fillPx, innerH, fg);
}
// Draw tick marks every 5° and labels outside the bar
getdisplay().setTextColor(fg);
getdisplay().setFont(&Ubuntu_Bold8pt8b);
for (int angle = -((int)rangeDeg); angle <= (int)rangeDeg; angle += 5) {
int xpos = int(round(centerx + angle * pxPerDeg));
// Vertical tick inside bar
getdisplay().drawLine(xpos, top, xpos, top + h + 2, fg);
// Label outside: below the bar
String lbl = String(angle);
int16_t bx, by;
uint16_t bw, bh;
getdisplay().getTextBounds(lbl, 0, 0, &bx, &by, &bw, &bh);
int16_t tx = xpos - bw/2;
int16_t ty = top + h + bh + 5; // A little spacing
getdisplay().setCursor(tx, ty);
getdisplay().print(lbl);
}
}
// Function to handle HTTP image request // Function to handle HTTP image request
// http://192.168.15.1/api/user/OBP60Task/screenshot // http://192.168.15.1/api/user/OBP60Task/screenshot
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request) { void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request) {

View File

@@ -89,13 +89,14 @@ uint8_t getLastPage();
void hardwareInit(GwApi *api); void hardwareInit(GwApi *api);
void powerInit(String powermode); 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 setPortPin(uint pin, bool value); // Set port pin for extension port
void togglePortPin(uint pin); // Toggle extension port pin void togglePortPin(uint pin); // Toggle extension port pin
Color colorMapping(const String &colorString); // Color mapping string to CHSV colors Color colorMapping(const String &colorString); // Color mapping string to CHSV colors
void setBacklightLED(uint brightness, const Color &color);// Set backlight LEDs void setBacklightLED(uint brightness, const Color &color);// Set backlight LEDs
void toggleBacklightLED(uint brightness,const Color &color);// Toggle 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 BacklightMode backlightMapping(const String &backlightString);// Configuration string to value
void setFlashLED(bool status); // Set flash LED 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 String xdrDelete(String input); // Delete xdr prefix from string
void drawTextCenter(int16_t cx, int16_t cy, String text); 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 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); void drawTextBoxed(Rect box, String text, uint16_t fg, uint16_t bg, bool inverted, bool border);
@@ -126,6 +128,10 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor); // S
void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic
void startLedTask(GwApi *api); void startLedTask(GwApi *api);
// Display rudder position as horizontal bargraph with configurable +/- range (degrees)
// 'rangeDeg' is unsigned and will be clamped to [10,45]
void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg);
void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request); void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request);
// Icons // Icons

View File

@@ -49,7 +49,15 @@ String formatLongitude(double lon) {
return String(degree, 0) + "\x90 " + String(minute, 4) + "' " + ((lon > 0) ? "E" : "W"); return String(degree, 0) + "\x90 " + String(minute, 4) + "' " + ((lon > 0) ? "E" : "W");
} }
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ // Convert and format boat value from SI to user defined format (definition for compatibility purposes)
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata) {
return formatValue(value, commondata, false); // call <formatValue> with standard handling of user setting for simulation data
}
// Convert and format boat value from SI to user defined format
// generate random simulation data; can be deselected to use conversion+formatting function even in simulation mode
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool ignoreSimuDataSetting){
GwLog *logger = commondata.logger; GwLog *logger = commondata.logger;
FormattedData result; FormattedData result;
static int dayoffset = 0; static int dayoffset = 0;
@@ -66,9 +74,15 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
String windspeedFormat = commondata.config->getString(commondata.config->windspeedFormat); // [m/s|km/h|kn|bft] String windspeedFormat = commondata.config->getString(commondata.config->windspeedFormat); // [m/s|km/h|kn|bft]
String tempFormat = commondata.config->getString(commondata.config->tempFormat); // [K|°C|°F] String tempFormat = commondata.config->getString(commondata.config->tempFormat); // [K|°C|°F]
String dateFormat = commondata.config->getString(commondata.config->dateFormat); // [DE|GB|US] String dateFormat = commondata.config->getString(commondata.config->dateFormat); // [DE|GB|US]
bool usesimudata = commondata.config->getBool(commondata.config->useSimuData); // [on|off]
String precision = commondata.config->getString(commondata.config->valueprecision); // [1|2] String precision = commondata.config->getString(commondata.config->valueprecision); // [1|2]
bool usesimudata;
if (ignoreSimuDataSetting){
usesimudata = false; // ignore user setting for simulation data; we want to format the boat value passed to this function
} else {
usesimudata = commondata.config->getBool(commondata.config->useSimuData); // [on|off]
}
// If boat value not valid // If boat value not valid
if (! value->valid && !usesimudata){ if (! value->valid && !usesimudata){
result.svalue = "---"; result.svalue = "---";
@@ -78,6 +92,8 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
const char* fmt_dec_1; const char* fmt_dec_1;
const char* fmt_dec_10; const char* fmt_dec_10;
const char* fmt_dec_100; const char* fmt_dec_100;
double limit_dec_10;
double limit_dec_100;
if (precision == "1") { 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. //All values are displayed using a DSEG7* font. In this font, ' ' is a very short space, and '.' takes up no space at all.
@@ -86,10 +102,14 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
fmt_dec_1 = "!%1.1f"; //insert a blank digit and then display a two-digit number 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_10 = "!%2.0f"; //insert a blank digit and then display a two-digit number
fmt_dec_100 = "%3.0f"; //dispay a three 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 { } else {
fmt_dec_1 = "%3.2f"; fmt_dec_1 = "%3.2f";
fmt_dec_10 = "%3.1f"; fmt_dec_10 = "%3.1f";
fmt_dec_100 = "%3.0f"; 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); // LOG_DEBUG(GwLog::DEBUG,"formatValue init: getFormat: %s date->value: %f time->value: %f", value->getFormat(), commondata.date->value, commondata.time->value);
@@ -196,10 +216,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = value->value; rawvalue = value->value;
} }
else { else {
course = 2.53 + float(random(0, 10) / 100.0); course = M_PI_2 + float(random(-17, 17) / 100.0); // create random course/wind values with 90° +/- 10°
rawvalue = course; rawvalue = course;
} }
course = course * 57.2958; // Unit conversion form rad to deg course = course * RAD_TO_DEG; // Unit conversion form rad to deg
// Format 3 numbers with prefix zero // Format 3 numbers with prefix zero
snprintf(buffer,bsize,"%03.0f",course); snprintf(buffer,bsize,"%03.0f",course);
@@ -214,7 +234,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = value->value; rawvalue = value->value;
} }
else{ else{
rawvalue = 4.0 + float(random(0, 40)); rawvalue = 4.0 + float(random(-30, 40) / 10.0); // create random speed values from [1..8] m/s
speed = rawvalue; speed = rawvalue;
} }
if (String(speedFormat) == "km/h"){ if (String(speedFormat) == "km/h"){
@@ -229,10 +249,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
speed = speed; // Unit conversion form m/s to m/s speed = speed; // Unit conversion form m/s to m/s
result.unit = "m/s"; result.unit = "m/s";
} }
if(speed < 10) { if(speed < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, speed); snprintf(buffer, bsize, fmt_dec_1, speed);
} }
else if (speed < 100) { else if (speed < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, speed); snprintf(buffer, bsize, fmt_dec_10, speed);
} }
else { else {
@@ -248,7 +268,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = value->value; rawvalue = value->value;
} }
else { else {
rawvalue = 4.0 + float(random(0, 40)); rawvalue = 4.0 + float(random(0, 40) / 10.0); // create random wind speed values from [4..8] m/s
speed = rawvalue; speed = rawvalue;
} }
if (String(windspeedFormat) == "km/h"){ if (String(windspeedFormat) == "km/h"){
@@ -309,11 +329,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
snprintf(buffer, bsize, "%2.0f", speed); snprintf(buffer, bsize, "%2.0f", speed);
} }
else{ else{
speed = std::round(speed * 100) / 100; // in rare cases, speed could be 9.9999 kn instead of 10.0 kn if (speed < limit_dec_10){
if (speed < 10.0){
snprintf(buffer, bsize, fmt_dec_1, speed); 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); snprintf(buffer, bsize, fmt_dec_10, speed);
} }
else { else {
@@ -364,10 +383,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
if (dop > 99.9){ if (dop > 99.9){
dop = 99.9; dop = 99.9;
} }
if (dop < 10){ if (dop < limit_dec_10){
snprintf(buffer, bsize, fmt_dec_1, dop); snprintf(buffer, bsize, fmt_dec_1, dop);
} }
else if(dop < 100){ else if(dop < limit_dec_100){
snprintf(buffer, bsize, fmt_dec_10, dop); snprintf(buffer, bsize, fmt_dec_10, dop);
} }
else { else {
@@ -433,7 +452,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = value->value; rawvalue = value->value;
} }
else { else {
rawvalue = 18.0 + float(random(0, 100)) / 10.0; rawvalue = 18.0 + float(random(0, 100)) / 10.0; // create random depth values from [18..28] metres
depth = rawvalue; depth = rawvalue;
} }
if(String(lengthFormat) == "ft"){ if(String(lengthFormat) == "ft"){
@@ -443,10 +462,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
else{ else{
result.unit = "m"; result.unit = "m";
} }
if (depth < 10) { if (depth < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, depth); snprintf(buffer, bsize, fmt_dec_1, depth);
} }
else if (depth < 100){ else if (depth < limit_dec_100){
snprintf(buffer, bsize, fmt_dec_10, depth); snprintf(buffer, bsize, fmt_dec_10, depth);
} }
else { else {
@@ -509,10 +528,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
else{ else{
result.unit = "K"; result.unit = "K";
} }
if(temp < 10) { if(temp < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, temp); snprintf(buffer, bsize, fmt_dec_1, temp);
} }
else if (temp < 100) { else if (temp < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, temp); snprintf(buffer, bsize, fmt_dec_10, temp);
} }
else { else {
@@ -542,10 +561,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
else { else {
result.unit = "m"; result.unit = "m";
} }
if (distance < 10){ if (distance < limit_dec_10){
snprintf(buffer, bsize, fmt_dec_1, distance); snprintf(buffer, bsize, fmt_dec_1, distance);
} }
else if (distance < 100){ else if (distance < limit_dec_100){
snprintf(buffer, bsize, fmt_dec_10, distance); snprintf(buffer, bsize, fmt_dec_10, distance);
} }
else { else {
@@ -599,7 +618,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 12 + float(random(0, 30)) / 10.0; rawvalue = 12 + float(random(0, 30)) / 10.0;
voltage = rawvalue; voltage = rawvalue;
} }
if (voltage < 10) { if (voltage < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, voltage); snprintf(buffer, bsize, fmt_dec_1, voltage);
} }
else { else {
@@ -619,10 +638,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 8.2 + float(random(0, 50)) / 10.0; rawvalue = 8.2 + float(random(0, 50)) / 10.0;
current = rawvalue; current = rawvalue;
} }
if (current < 10) { if (current < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, current); snprintf(buffer, bsize, fmt_dec_1, current);
} }
else if(current < 100) { else if(current < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, current); snprintf(buffer, bsize, fmt_dec_10, current);
} }
else { else {
@@ -642,10 +661,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 21.8 + float(random(0, 50)) / 10.0; rawvalue = 21.8 + float(random(0, 50)) / 10.0;
temperature = rawvalue; temperature = rawvalue;
} }
if (temperature < 10) { if (temperature < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, temperature); snprintf(buffer, bsize, fmt_dec_1, temperature);
} }
else if (temperature < 100) { else if (temperature < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, temperature); snprintf(buffer, bsize, fmt_dec_10, temperature);
} }
else { else {
@@ -665,10 +684,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 21.8 + float(random(0, 50)) / 10.0; rawvalue = 21.8 + float(random(0, 50)) / 10.0;
temperature = rawvalue; temperature = rawvalue;
} }
if (temperature < 10) { if (temperature < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, temperature); snprintf(buffer, bsize, fmt_dec_1, temperature);
} }
else if(temperature < 100) { else if(temperature < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, temperature); snprintf(buffer, bsize, fmt_dec_10, temperature);
} }
else { else {
@@ -688,10 +707,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 41.3 + float(random(0, 50)) / 10.0; rawvalue = 41.3 + float(random(0, 50)) / 10.0;
humidity = rawvalue; humidity = rawvalue;
} }
if (humidity < 10) { if (humidity < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, humidity); snprintf(buffer, bsize, fmt_dec_1, humidity);
} }
else if(humidity < 100) { else if(humidity < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, humidity); snprintf(buffer, bsize, fmt_dec_10, humidity);
} }
else { else {
@@ -711,13 +730,13 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 85.8 + float(random(0, 50)) / 10.0; rawvalue = 85.8 + float(random(0, 50)) / 10.0;
volume = rawvalue; volume = rawvalue;
} }
if (volume < 10) { if (volume < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, volume); snprintf(buffer, bsize, fmt_dec_1, volume);
} }
else if (volume < 100) { else if (volume < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, volume); snprintf(buffer, bsize, fmt_dec_10, volume);
} }
else if (volume >= 100) { else if (volume >= limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_100, volume); snprintf(buffer, bsize, fmt_dec_100, volume);
} }
result.unit = "%"; result.unit = "%";
@@ -734,10 +753,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 75.2 + float(random(0, 50)) / 10.0; rawvalue = 75.2 + float(random(0, 50)) / 10.0;
volume = rawvalue; volume = rawvalue;
} }
if (volume < 10) { if (volume < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, volume); snprintf(buffer, bsize, fmt_dec_1, volume);
} }
else if (volume < 100) { else if (volume < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, volume); snprintf(buffer, bsize, fmt_dec_10, volume);
} }
else { else {
@@ -757,10 +776,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 7.5 + float(random(0, 20)) / 10.0; rawvalue = 7.5 + float(random(0, 20)) / 10.0;
flow = rawvalue; flow = rawvalue;
} }
if (flow < 10) { if (flow < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, flow); snprintf(buffer, bsize, fmt_dec_1, flow);
} }
else if (flow < 100) { else if (flow < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, flow); snprintf(buffer, bsize, fmt_dec_10, flow);
} }
else { else {
@@ -780,10 +799,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 18.5 + float(random(0, 20)) / 10.0; rawvalue = 18.5 + float(random(0, 20)) / 10.0;
generic = rawvalue; generic = rawvalue;
} }
if (generic < 10) { if (generic < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, generic); snprintf(buffer, bsize, fmt_dec_1, generic);
} }
else if (generic < 100) { else if (generic < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, generic); snprintf(buffer, bsize, fmt_dec_10, generic);
} }
else { else {
@@ -803,10 +822,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 55.3 + float(random(0, 20)) / 10.0; rawvalue = 55.3 + float(random(0, 20)) / 10.0;
dplace = rawvalue; dplace = rawvalue;
} }
if (dplace < 10) { if (dplace < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, dplace); snprintf(buffer, bsize, fmt_dec_1, dplace);
} }
else if (dplace < 100) { else if (dplace < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, dplace); snprintf(buffer, bsize, fmt_dec_10, dplace);
} }
else { else {
@@ -816,7 +835,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
result.cvalue = dplace; result.cvalue = dplace;
} }
//######################################################## //########################################################
else if (value->getFormat() == "formatXdr:A:D"){ else if ((value->getFormat() == "formatXdr:A:D") || ((value->getFormat() == "formatXdr:A:rd"))){
double angle = 0; double angle = 0;
if (usesimudata == false) { if (usesimudata == false) {
angle = value->value; angle = value->value;
@@ -847,10 +866,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
rawvalue = 2505 + random(0, 20); rawvalue = 2505 + random(0, 20);
rpm = rawvalue; rpm = rawvalue;
} }
if (rpm < 10) { if (rpm < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, rpm); snprintf(buffer, bsize, fmt_dec_1, rpm);
} }
else if (rpm < 100) { else if (rpm < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, rpm); snprintf(buffer, bsize, fmt_dec_10, rpm);
} }
else { else {
@@ -863,10 +882,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
// Default format // Default format
//######################################################## //########################################################
else { else {
if (value->value < 10) { if (value->value < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, value->value); 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); snprintf(buffer, bsize, fmt_dec_10, value->value);
} }
else { else {
@@ -881,4 +900,30 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){
return result; return result;
} }
// Helper method for conversion of any data value from SI to user defined format
double convertValue(const double &value, const String &name, const String &format, CommonData &commondata)
{
std::unique_ptr<GwApi::BoatValue> tmpBValue; // Temp variable to get converted data value from <OBP60Formatter::formatValue>
double result; // data value converted to user defined target data format
constexpr bool NO_SIMUDATA = true; // switch off simulation feature of <formatValue> function
// prepare temporary BoatValue structure for use in <formatValue>
tmpBValue = std::unique_ptr<GwApi::BoatValue>(new GwApi::BoatValue(name)); // we don't need boat value name for pure value conversion
tmpBValue->setFormat(format);
tmpBValue->valid = true;
tmpBValue->value = value;
result = formatValue(tmpBValue.get(), commondata, NO_SIMUDATA).cvalue; // get value (converted); ignore any simulation data setting
return result;
}
// Helper method for conversion of any data value from SI to user defined format
double convertValue(const double &value, const String &format, CommonData &commondata)
{
double result; // data value converted to user defined target data format
result = convertValue(value, "dummy", format, commondata);
return result;
}
#endif #endif

View File

@@ -1,7 +1,7 @@
// General hardware definitions // General hardware definitions
// CAN and RS485 bus pin definitions see obp60task.h // CAN and RS485 bus pin definitions see obp60task.h
#ifdef HARDWARE_V21 #if defined HARDWARE_V20 || HARDWARE_V21
// Direction pin for RS485 NMEA0183 // Direction pin for RS485 NMEA0183
#define OBP_DIRECTION_PIN 18 #define OBP_DIRECTION_PIN 18
// I2C // I2C
@@ -23,8 +23,8 @@
#define AS5600_I2C_ADDR 0x36 // Addr. 0x36 (fix) #define AS5600_I2C_ADDR 0x36 // Addr. 0x36 (fix)
// INA219 // INA219
#define SHUNT_VOLTAGE 0.075 // Shunt voltage in V by max. current (75mV) #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_ADDR1 0x41 // 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_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 #define INA219_I2C_ADDR3 0x45 // Addr. 0x45 (fix A0 = 5V, A1 = 5V) for generator
// INA226 // INA226
#define INA226_I2C_ADDR1 0x41 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery #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) #define AS5600_I2C_ADDR 0x36 // Addr. 0x36 (fix)
// INA219 // INA219
#define SHUNT_VOLTAGE 0.075 // Shunt voltage in V by max. current (75mV) #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_ADDR1 0x41 // 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_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 #define INA219_I2C_ADDR3 0x45 // Addr. 0x45 (fix A0 = 5V, A1 = 5V) for generator
// INA226 // INA226
#define INA226_I2C_ADDR1 0x41 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery #define INA226_I2C_ADDR1 0x41 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery

View File

@@ -58,7 +58,7 @@ void initKeys(CommonData &commonData) {
commonData.keydata[5].h = height; commonData.keydata[5].h = height;
} }
#ifdef HARDWARE_V21 #if defined HARDWARE_V20 || HARDWARE_V21
// Keypad functions for original OBP60 hardware // Keypad functions for original OBP60 hardware
int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) { int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) {

View File

@@ -1,156 +1,338 @@
#include "OBPDataOperations.h" #include "OBPDataOperations.h"
#include "BoatDataCalibration.h" // Functions lib for data instance calibration //#include "BoatDataCalibration.h" // Functions lib for data instance calibration
#include <math.h>
// --- 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 --------------- // --- Class HstryBuf ---------------
HstryBuf::HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log)
: logger(log)
, boatDataName(name)
{
hstryBuf.resize(size);
boatValue = boatValues->findValueOrCreate(name);
}
// Init history buffers for selected boat data void HstryBuf::init(const String& format, int updFreq, int mltplr, double minVal, double maxVal)
void HstryBuf::init(BoatValueList* boatValues, GwLog *log) { {
hstryBuf.setMetaData(boatDataName, format, updFreq, mltplr, minVal, maxVal);
logger = log; hstryMin = minVal;
hstryMax = maxVal;
int hstryUpdFreq = 1000; // Update frequency for history buffers in ms if (!boatValue->valid) {
int mltplr = 1000; // Multiplier which transforms original <double> value into buffer type format boatValue->setFormat(format);
double hstryMinVal = 0; // Minimum value for these history buffers boatValue->value = std::numeric_limits<double>::max(); // mark current value invalid
twdHstryMax = 2 * M_PI; // Max value for wind direction (TWD, AWD) in rad [0...2*PI]
twsHstryMax = 65; // Max value for wind speed (TWS, AWS) in m/s [0..65] (limit due to type capacity of buffer - shifted by <mltplr>)
awdHstryMax = twdHstryMax;
awsHstryMax = twsHstryMax;
twdHstryMin = hstryMinVal;
twsHstryMin = hstryMinVal;
awdHstryMin = hstryMinVal;
awsHstryMin = hstryMinVal;
const double DBL_MAX = std::numeric_limits<double>::max();
// Initialize history buffers with meta data
mltplr = 10000; // Store 4 decimals for course data
hstryBufList.twdHstry->setMetaData("TWD", "formatCourse", hstryUpdFreq, mltplr, hstryMinVal, twdHstryMax);
hstryBufList.awdHstry->setMetaData("AWD", "formatCourse", hstryUpdFreq, mltplr, hstryMinVal, twdHstryMax);
mltplr = 1000; // Store 3 decimals for windspeed data
hstryBufList.twsHstry->setMetaData("TWS", "formatKnots", hstryUpdFreq, mltplr, hstryMinVal, twsHstryMax);
hstryBufList.awsHstry->setMetaData("AWS", "formatKnots", hstryUpdFreq, mltplr, hstryMinVal, twsHstryMax);
// create boat values for history data types, if they don't exist yet
twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName());
twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName());
twaBVal = boatValues->findValueOrCreate("TWA");
awdBVal = boatValues->findValueOrCreate(hstryBufList.awdHstry->getName());
awsBVal = boatValues->findValueOrCreate(hstryBufList.awsHstry->getName());
if (!awdBVal->valid) { // AWD usually does not exist
awdBVal->setFormat(hstryBufList.awdHstry->getFormat());
awdBVal->value = DBL_MAX;
} }
}
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);
}
}
void HstryBuf::handle(bool useSimuData, CommonData& common)
{
// GwApi::BoatValue* tmpBVal;
std::unique_ptr<GwApi::BoatValue> tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter
// create temporary boat value for calibration purposes and retrieval of simulation value
// tmpBVal = new GwApi::BoatValue(boatDataName.c_str());
tmpBVal = std::unique_ptr<GwApi::BoatValue>(new GwApi::BoatValue(boatDataName));
tmpBVal->setFormat(boatValue->getFormat());
tmpBVal->value = boatValue->value;
tmpBVal->valid = boatValue->valid;
if (boatValue->valid) {
// Calibrate boat value before adding it to history buffer
//calibrationData.calibrateInstance(tmpBVal.get(), logger);
//add(tmpBVal->value);
add(boatValue->value);
} else if (useSimuData) { // add simulated value to history buffer
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 ---------------
// --- Class HstryBuffers ---------------
HstryBuffers::HstryBuffers(int size, BoatValueList* boatValues, GwLog* log)
: size(size)
, boatValueList(boatValues)
, logger(log)
{
// collect boat values for true wind calculation // collect boat values for true wind calculation
awaBVal = boatValues->findValueOrCreate("AWA"); // should all have been already created at true wind object initialization
hdtBVal = boatValues->findValueOrCreate("HDT"); // potentially to be moved to history buffer handling
hdmBVal = boatValues->findValueOrCreate("HDM"); awaBVal = boatValueList->findValueOrCreate("AWA");
varBVal = boatValues->findValueOrCreate("VAR"); hdtBVal = boatValueList->findValueOrCreate("HDT");
cogBVal = boatValues->findValueOrCreate("COG"); hdmBVal = boatValueList->findValueOrCreate("HDM");
sogBVal = boatValues->findValueOrCreate("SOG"); varBVal = boatValueList->findValueOrCreate("VAR");
cogBVal = boatValueList->findValueOrCreate("COG");
sogBVal = boatValueList->findValueOrCreate("SOG");
awdBVal = boatValueList->findValueOrCreate("AWD");
} }
// Handle history buffers for TWD, TWS, AWD, AWS // Create history buffer for boat data type
//void HstryBuf::handleHstryBuf(GwApi* api, BoatValueList* boatValues, bool useSimuData) { void HstryBuffers::addBuffer(const String& name)
void HstryBuf::handleHstryBuf(bool useSimuData) { {
if (HstryBuffers::getBuffer(name) != nullptr) { // buffer for this data type already exists
static double twd, tws, awd, aws, hdt = 20; //initial value only relevant if we use simulation data return;
GwApi::BoatValue *calBVal; // temp variable just for data calibration -> we don't want to calibrate the original data here
LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf: TWD_isValid? %d, twdBVal: %.1f, twaBVal: %.1f, twsBVal: %.1f", twdBVal->valid, twdBVal->value * RAD_TO_DEG,
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852);
if (twdBVal->valid) {
// if (!useSimuData) {
calBVal = new GwApi::BoatValue("TWD"); // temporary solution for calibration of history buffer values
calBVal->setFormat(twdBVal->getFormat());
calBVal->value = twdBVal->value;
calBVal->valid = twdBVal->valid;
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
twd = calBVal->value;
if (twd >= twdHstryMin && twd <= twdHstryMax) {
hstryBufList.twdHstry->add(twd);
LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf: calBVal.value %.2f, twd: %.2f, twdHstryMin: %.1f, twdHstryMax: %.2f", calBVal->value, twd, twdHstryMin, twdHstryMax);
} }
delete calBVal; if (bufferParams.find(name) == bufferParams.end()) { // requested boat data type is not supported in list of <bufferParams>
calBVal = nullptr; return;
} else if (useSimuData) {
// } else {
twd += random(-20, 20);
twd += static_cast<double>(random(-349, 349) / 1000.0); // add up to +/- 20 degree in RAD
twd = WindUtils::to2PI(twd);
hstryBufList.twdHstry->add(twd);
} }
if (twsBVal->valid) { hstryBuffers[name] = std::unique_ptr<HstryBuf>(new HstryBuf(name, size, boatValueList, logger));
calBVal = new GwApi::BoatValue("TWS"); // temporary solution for calibration of history buffer values
calBVal->setFormat(twsBVal->getFormat());
calBVal->value = twsBVal->value;
calBVal->valid = twsBVal->valid;
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
tws = calBVal->value;
if (tws >= twsHstryMin && tws <= twsHstryMax) {
hstryBufList.twsHstry->add(tws);
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
// tws += random(-5000, 5000); // TWS value in m/s; expands to 3 decimals
tws += static_cast<double>(random(-5000, 5000) / 1000.0); // add up to +/- 5 m/s TWS speed
tws = constrain(tws, 0, 40); // Limit TWS to [0..40] m/s
hstryBufList.twsHstry->add(tws);
}
if (awaBVal->valid) { // Initialize metadata for buffer
if (hdtBVal->valid) { String valueFormat = bufferParams[name].format; // Data format of boat data type
hdt = hdtBVal->value; // Use HDT if available // String valueFormat = boatValueList->findValueOrCreate(name)->getFormat().c_str(); // Unfortunately, format is not yet available during system initialization
} else { int hstryUpdFreq = bufferParams[name].hstryUpdFreq; // Update frequency for history buffers in ms
hdt = WindUtils::calcHDT(&hdmBVal->value, &varBVal->value, &cogBVal->value, &sogBVal->value); int mltplr = bufferParams[name].mltplr; // default multiplier which transforms original <double> value into buffer type format
} double bufferMinVal = bufferParams[name].bufferMinVal; // Min value for this history buffer
double bufferMaxVal = bufferParams[name].bufferMaxVal; // Max value for this history buffer
awd = awaBVal->value + hdt; hstryBuffers[name]->init(valueFormat, hstryUpdFreq, mltplr, bufferMinVal, bufferMaxVal);
awd = WindUtils::to2PI(awd); LOG_DEBUG(GwLog::DEBUG, "HstryBuffers: new buffer added: name: %s, format: %s, multiplier: %d, min value: %.2f, max value: %.2f", name, valueFormat, mltplr, bufferMinVal, bufferMaxVal);
calBVal = new GwApi::BoatValue("AWD"); // temporary solution for calibration of history buffer values
calBVal->value = awd;
calBVal->setFormat(awdBVal->getFormat());
calBVal->valid = true;
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
awdBVal->value = calBVal->value;
awdBVal->valid = true;
awd = calBVal->value;
if (awd >= awdHstryMin && awd <= awdHstryMax) {
hstryBufList.awdHstry->add(awd);
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
awd += static_cast<double>(random(-349, 349) / 1000.0); // add up to +/- 20 degree in RAD
awd = WindUtils::to2PI(awd);
hstryBufList.awdHstry->add(awd);
}
if (awsBVal->valid) {
calBVal = new GwApi::BoatValue("AWS"); // temporary solution for calibration of history buffer values
calBVal->setFormat(awsBVal->getFormat());
calBVal->value = awsBVal->value;
calBVal->valid = awsBVal->valid;
calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated
aws = calBVal->value;
if (aws >= awsHstryMin && aws <= awsHstryMax) {
hstryBufList.awsHstry->add(aws);
}
delete calBVal;
calBVal = nullptr;
} else if (useSimuData) {
aws += static_cast<double>(random(-5000, 5000) / 1000.0); // add up to +/- 5 m/s TWS speed
aws = constrain(aws, 0, 40); // Limit TWS to [0..40] m/s
hstryBufList.awsHstry->add(aws);
}
LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf-End: Buffer twdHstry: %.3f, twsHstry: %.3f, awdHstry: %.3f, awsHstry: %.3f", hstryBufList.twdHstry->getLast(), hstryBufList.twsHstry->getLast(),
hstryBufList.awdHstry->getLast(),hstryBufList.awsHstry->getLast());
} }
// --- Class HstryBuf ---------------
// Handle all registered history buffers
void HstryBuffers::handleHstryBufs(bool useSimuData, CommonData& common)
{
for (auto& bufMap : hstryBuffers) {
auto& buf = bufMap.second;
buf->handle(useSimuData, common);
}
}
RingBuffer<uint16_t>* HstryBuffers::getBuffer(const String& name)
{
auto it = hstryBuffers.find(name);
if (it != hstryBuffers.end()) {
return &it->second->hstryBuf;
}
return nullptr;
}
// --- End Class HstryBuffers ---------------
// --- Class WindUtils -------------- // --- Class WindUtils --------------
double WindUtils::to2PI(double a) double WindUtils::to2PI(double a)
@@ -216,14 +398,14 @@ void WindUtils::addPolar(const double* phi1, const double* r1,
void WindUtils::calcTwdSA(const double* AWA, const double* AWS, void WindUtils::calcTwdSA(const double* AWA, const double* AWS,
const double* CTW, const double* STW, const double* HDT, const double* CTW, const double* STW, const double* HDT,
double* TWD, double* TWS, double* TWA) double* TWD, double* TWS, double* TWA, double* AWD)
{ {
double awd = *AWA + *HDT; *AWD = *AWA + *HDT;
awd = to2PI(awd); *AWD = to2PI(*AWD);
double stw = -*STW; double stw = -*STW;
addPolar(&awd, AWS, CTW, &stw, TWD, TWS); addPolar(AWD, AWS, CTW, &stw, TWD, TWS);
// Normalize TWD and TWA to 0-360° // Normalize TWD to [0..360°] (2PI) and TWA to [-180..180] (PI)
*TWD = to2PI(*TWD); *TWD = to2PI(*TWD);
*TWA = toPI(*TWD - *HDT); *TWA = toPI(*TWD - *HDT);
} }
@@ -245,12 +427,12 @@ double WindUtils::calcHDT(const double* hdmVal, const double* varVal, const doub
return hdt; return hdt;
} }
bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal, bool WindUtils::calcWinds(const double* awaVal, const double* awsVal,
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal) const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal, double* awdVal)
{ {
double stw, hdt, ctw; double stw, hdt, ctw;
double twd, tws, twa; double twd, tws, twa, awd;
double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor
if (*hdtVal != DBL_MAX) { if (*hdtVal != DBL_MAX) {
@@ -274,44 +456,59 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal,
// If STW and SOG are not available, we cannot calculate true wind // If STW and SOG are not available, we cannot calculate true wind
return false; return false;
} }
// Serial.println("\ncalcTrueWind: HDT: " + String(hdt) + ", CTW: " + String(ctw) + ", STW: " + String(stw)); // LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcWinds: HDT: %.1f, CTW %.1f, STW %.1f", hdt, ctw, stw);
if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) { if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) {
// Cannot calculate true wind without valid AWA, AWS; other checks are done earlier // Cannot calculate true wind without valid AWA, AWS; other checks are done earlier
return false; return false;
} else { } else {
calcTwdSA(awaVal, awsVal, &ctw, &stw, &hdt, &twd, &tws, &twa); calcTwdSA(awaVal, awsVal, &ctw, &stw, &hdt, &twd, &tws, &twa, &awd);
*twdVal = twd; *twdVal = twd;
*twsVal = tws; *twsVal = tws;
*twaVal = twa; *twaVal = twa;
*awdVal = awd;
return true; return true;
} }
} }
// Calculate true wind data and add to obp60task boat data list // Calculate true wind data and add to obp60task boat data list
bool WindUtils::addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog* log) { bool WindUtils::addWinds()
{
double twd, tws, twa, awd, hdt;
bool twCalculated = false;
bool awdCalculated = false;
GwLog* logger = log; double awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX;
double awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX;
double cogVal = cogBVal->valid ? cogBVal->value : DBL_MAX;
double stwVal = stwBVal->valid ? stwBVal->value : DBL_MAX;
double sogVal = sogBVal->valid ? sogBVal->value : DBL_MAX;
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);
double awaVal, awsVal, cogVal, stwVal, sogVal, hdtVal, hdmVal, varVal; // Check if TWD can be calculated from TWA and HDT/HDM
double twd, tws, twa; if (twaBVal->valid) {
bool isCalculated = false; if (!twdBVal->valid) {
if (hdtVal != DBL_MAX) {
hdt = hdtVal; // Use HDT if available
} else {
hdt = calcHDT(&hdmVal, &varVal, &cogVal, &sogVal);
}
twd = twaBVal->value + hdt;
twd = to2PI(twd);
twdBVal->value = twd;
twdBVal->valid = true;
}
awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX; } else {
awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX; // Calculate true winds and AWD; if true winds exist, use at least AWD calculation
cogVal = cogBVal->valid ? cogBVal->value : DBL_MAX; twCalculated = calcWinds(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa, &awd);
stwVal = stwBVal->valid ? stwBVal->value : DBL_MAX;
sogVal = sogBVal->valid ? sogBVal->value : DBL_MAX;
hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX;
hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX;
varVal = varBVal->valid ? varBVal->value : DBL_MAX;
LOG_DEBUG(GwLog::DEBUG,"obp60task addTrueWind: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852,
cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG);
isCalculated = calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa); if (twCalculated) { // Replace values only, if successfully calculated and not already available
if (isCalculated) { // Replace values only, if successfully calculated and not already available
if (!twdBVal->valid) { if (!twdBVal->valid) {
twdBVal->value = twd; twdBVal->value = twd;
twdBVal->valid = true; twdBVal->valid = true;
@@ -321,13 +518,19 @@ bool WindUtils::addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog* log) {
twsBVal->valid = true; twsBVal->valid = true;
} }
if (!twaBVal->valid) { 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; twaBVal->valid = true;
} }
if (!awdBVal->valid) {
awdBVal->value = awd;
awdBVal->valid = true;
} }
LOG_DEBUG(GwLog::DEBUG,"obp60task addTrueWind: isCalculated %d, TWD %.1f, TWA %.1f, TWS %.1f", isCalculated, twdBVal->value * RAD_TO_DEG, }
twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852); }
// 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 isCalculated; return twCalculated;
} }
// --- Class WindUtils -------------- // --- End Class WindUtils --------------

View File

@@ -1,67 +1,116 @@
// 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 #pragma once
#include "OBPRingBuffer.h" #include "OBPRingBuffer.h"
#include "Pagedata.h"
#include "obp60task.h" #include "obp60task.h"
#include <map>
#include <unordered_map>
typedef struct { // Calibration of boat data values, when user setting available
RingBuffer<uint16_t>* twdHstry; // supported boat data types are: AWA, AWS, COG, DBS, DBT, HDM, HDT, PRPOS, RPOS, SOG, STW, TWA, TWS, TWD, WTemp
RingBuffer<uint16_t>* twsHstry; class CalibrationData {
RingBuffer<uint16_t>* awdHstry; private:
RingBuffer<uint16_t>* awsHstry; typedef struct {
} tBoatHstryData; // Holds pointers to all history buffers for boat data 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 { class HstryBuf {
private: private:
GwLog *logger; RingBuffer<uint16_t> hstryBuf; // Circular buffer to store history values
String boatDataName;
double hstryMin;
double hstryMax;
GwApi::BoatValue* boatValue;
GwLog* logger;
RingBuffer<uint16_t> twdHstry; // Circular buffer to store true wind direction values friend class HstryBuffers;
RingBuffer<uint16_t> twsHstry; // Circular buffer to store true wind speed values (TWS)
RingBuffer<uint16_t> awdHstry; // Circular buffer to store apparent wind direction values
RingBuffer<uint16_t> awsHstry; // Circular buffer to store apparent xwind speed values (AWS)
double twdHstryMin; // Min value for wind direction (TWD) in history buffer
double twdHstryMax; // Max value for wind direction (TWD) in history buffer
double twsHstryMin;
double twsHstryMax;
double awdHstryMin;
double awdHstryMax;
double awsHstryMin;
double awsHstryMax;
// boat values for buffers and for true wind calculation
GwApi::BoatValue *twdBVal, *twsBVal, *twaBVal, *awdBVal, *awsBVal;
GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal;
public: public:
tBoatHstryData hstryBufList; HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log);
void init(const String& format, int updFreq, int mltplr, double minVal, double maxVal);
void add(double value);
void handle(bool useSimuData, CommonData& common);
};
HstryBuf(){ class HstryBuffers {
hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry}; // Generate history buffers of zero size private:
std::map<String, std::unique_ptr<HstryBuf>> hstryBuffers;
int size; // size of all history buffers
BoatValueList* boatValueList;
GwLog* logger;
GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal, *awdBVal; // boat values for true wind calculation
struct HistoryParams {
int hstryUpdFreq; // update frequency of history buffer (documentation only)
int mltplr; // specifies actual value precision being storable:
// [10000: 0 - 6.5535 | 1000: 0 - 65.535 | 100: 0 - 650.35 | 10: 0 - 6503.5
double bufferMinVal; // minimum valid data value
double bufferMaxVal; // maximum valid data value
String format; // format of data type
}; };
HstryBuf(int size) { // Define buffer parameters for supported boat data type
hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry}; std::map<String, HistoryParams> bufferParams = {
hstryBufList.twdHstry->resize(size); // store <size> xWD values for <size>/60 minutes history { "AWA", { 1000, 10000, 0.0, M_TWOPI, "formatWind" } },
hstryBufList.twsHstry->resize(size); { "AWD", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } },
hstryBufList.awdHstry->resize(size); { "AWS", { 1000, 1000, 0.0, 65.0, "formatKnots" } },
hstryBufList.awsHstry->resize(size); { "COG", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } },
{ "DBS", { 1000, 100, 0.0, 650.0, "formatDepth" } },
{ "DBT", { 1000, 100, 0.0, 650.0, "formatDepth" } },
{ "DPT", { 1000, 100, 0.0, 650.0, "formatDepth" } },
{ "HDM", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } },
{ "HDT", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } },
{ "ROT", { 1000, 10000, -M_PI / 180.0 * 99.0, M_PI / 180.0 * 99.0, "formatRot" } }, // min/max is -/+ 99 degrees for "rate of turn"
{ "SOG", { 1000, 1000, 0.0, 65.0, "formatKnots" } },
{ "STW", { 1000, 1000, 0.0, 65.0, "formatKnots" } },
{ "TWA", { 1000, 10000, 0.0, M_TWOPI, "formatWind" } },
{ "TWD", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } },
{ "TWS", { 1000, 1000, 0.0, 65.0, "formatKnots" } },
{ "WTemp", { 1000, 100, 233.0, 650.0, "kelvinToC" } } // [-50..376] °C
}; };
void init(BoatValueList* boatValues, GwLog *log);
void handleHstryBuf(bool useSimuData); public:
HstryBuffers(int size, BoatValueList* boatValues, GwLog* log);
void addBuffer(const String& name);
void handleHstryBufs(bool useSimuData, CommonData& common);
RingBuffer<uint16_t>* getBuffer(const String& name);
}; };
class WindUtils { class WindUtils {
private: private:
GwApi::BoatValue *twdBVal, *twsBVal, *twaBVal; GwApi::BoatValue *twaBVal, *twsBVal, *twdBVal;
GwApi::BoatValue *awaBVal, *awsBVal, *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal; GwApi::BoatValue *awaBVal, *awsBVal, *awdBVal, *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal;
static constexpr double DBL_MAX = std::numeric_limits<double>::max(); static constexpr double DBL_MAX = std::numeric_limits<double>::max();
GwLog* logger;
public: public:
WindUtils(BoatValueList* boatValues){ WindUtils(BoatValueList* boatValues, GwLog* log)
twdBVal = boatValues->findValueOrCreate("TWD"); : logger(log)
twsBVal = boatValues->findValueOrCreate("TWS"); {
twaBVal = boatValues->findValueOrCreate("TWA"); twaBVal = boatValues->findValueOrCreate("TWA");
twsBVal = boatValues->findValueOrCreate("TWS");
twdBVal = boatValues->findValueOrCreate("TWD");
awaBVal = boatValues->findValueOrCreate("AWA"); awaBVal = boatValues->findValueOrCreate("AWA");
awsBVal = boatValues->findValueOrCreate("AWS"); awsBVal = boatValues->findValueOrCreate("AWS");
awdBVal = boatValues->findValueOrCreate("AWD");
cogBVal = boatValues->findValueOrCreate("COG"); cogBVal = boatValues->findValueOrCreate("COG");
stwBVal = boatValues->findValueOrCreate("STW"); stwBVal = boatValues->findValueOrCreate("STW");
sogBVal = boatValues->findValueOrCreate("SOG"); sogBVal = boatValues->findValueOrCreate("SOG");
@@ -81,10 +130,10 @@ public:
double* phi, double* r); double* phi, double* r);
void calcTwdSA(const double* AWA, const double* AWS, void calcTwdSA(const double* AWA, const double* AWS,
const double* CTW, const double* STW, const double* HDT, const double* CTW, const double* STW, const double* HDT,
double* TWD, double* TWS, double* TWA); double* TWD, double* TWS, double* TWA, double* AWD);
static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal); static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal);
bool calcTrueWind(const double* awaVal, const double* awsVal, bool calcWinds(const double* awaVal, const double* awsVal,
const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal,
const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal); const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal, double* awdVal);
bool addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog *log); bool addWinds();
}; };

View File

@@ -49,8 +49,10 @@ void sensorTask(void *param){
// Init sensor stuff // Init sensor stuff
bool oneWire_ready = false; // 1Wire initialized and ready to use 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 RTC_ready = false; // DS1388 initialized and ready to use
bool GPS_ready = false; // GPS 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 BME280_ready = false; // BME280 initialized and ready to use
bool BMP280_ready = false; // BMP280 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 bool BMP180_ready = false; // BMP180 initialized and ready to use
@@ -369,7 +371,7 @@ void sensorTask(void *param){
GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP); GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP);
GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop}; GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop};
// Internal RTC with NTP init // Internal iRTC with NTP init
ESP32Time rtc(0); ESP32Time rtc(0);
if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") { if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") {
GwApi::Status status; GwApi::Status status;
@@ -382,6 +384,7 @@ void sensorTask(void *param){
if (getLocalTime(&timeinfo)) { if (getLocalTime(&timeinfo)) {
api->getLogger()->logDebug(GwLog::LOG,"NTP time: %04d-%02d-%02d %02d:%02d:%02d UTC", timeinfo.tm_year+1900, timeinfo.tm_mon+1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); 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); rtc.setTimeStruct(timeinfo);
iRTC_ready = true;
sensors.rtcValid = true; sensors.rtcValid = true;
} else { } else {
api->getLogger()->logDebug(GwLog::LOG,"NTP time fetch failed!"); api->getLogger()->logDebug(GwLog::LOG,"NTP time fetch failed!");
@@ -400,7 +403,7 @@ void sensorTask(void *param){
if (millis() > starttime0 + 100) if (millis() > starttime0 + 100)
{ {
starttime0 = millis(); 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) if (GPS_ready == true && hdop->value <= hdopAccuracy)
{ {
SNMEA0183Msg NMEA0183Msg; SNMEA0183Msg NMEA0183Msg;
@@ -412,9 +415,55 @@ void sensorTask(void *param){
} }
// If RTC DS1388 ready, then copy GPS data to RTC all 5min /*
if(millis() > starttime11 + 5*60*1000){ Time set logic for RTC and N2K
###############################
iRTC = Software RTC updatetd with NTP via internet
RTC = RTC chip on PCB
GPS = GPS Receiver on PCB
N2K = GPS time on N2K od 183 bus
0 = device not ready
1 = device ready
X = independend
() = source for set time N2K
-> = set RTC via iRTC
<- = set RTC via GPS
iRTC RTC GPS N2K
0 0 0 (1)
0 0 (1) X
0 (1) 0 X
0 1 <-(1) X
(1) 0 0 X
1 0 (1) X
1 ->(1) 0 X
1 1 <-(1) X
*/
// If RTC DS1388 ready, then copy iRTC and GPS data to RTC all 1 min
if(millis() > starttime11 + 1*60*1000){
starttime11 = millis(); 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){ if(rtcOn == "DS1388" && RTC_ready == true && GPS_ready == true){
api->getBoatDataValues(3,valueList); api->getBoatDataValues(3,valueList);
if(gpsdays->valid && gpsseconds->valid && hdop->valid){ 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" // sample input: date = "Dec 26 2009", time = "12:34:56"
// ds1388.adjust(DateTime("Dec 26 2009", "12:34:56")); // ds1388.adjust(DateTime("Dec 26 2009", "12:34:56"));
DateTime adjusttime(ts); 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 // Adjust RTC time as unix time value
ds1388.adjust(adjusttime); ds1388.adjust(adjusttime);
} }
} }
} }
// Send 1Wire data for all temperature sensors all 2s // Set RTC chip via N2K or 183 in case the internal GPS is off (only one time)
if(millis() > starttime13 + 2000 && String(oneWireOn) == "DS18B20" && oneWire_ready == true){ if(N2K_GPS_ready == false && RTC_ready == true && GPS_ready == false){
starttime13 = millis(); api->getBoatDataValues(3,valueList);
float tempC; if(gpsdays->valid && gpsseconds->valid && hdop->valid){
ds18b20.requestTemperatures(); // Collect all temperature values (max.8) 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)
for(int i=0;i<numberOfDevices; i++){ // sample input: date = "Dec 26 2009", time = "12:34:56"
// Send only one 1Wire data per loop step (time reduction) // ds1388.adjust(DateTime("Dec 26 2009", "12:34:56"));
if(i == loopCounter % numberOfDevices){ DateTime adjusttime(ts);
if(ds18b20.getAddress(tempDeviceAddress, i)){ 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());
// Read temperature value in Celsius // Adjust RTC time as unix time value
tempC = ds18b20.getTempC(tempDeviceAddress); ds1388.adjust(adjusttime);
// N2K GPS time ready
N2K_GPS_ready = true;
} }
// Send to NMEA200 bus for each sensor with instance number
if(!isnan(tempC)){
sensors.onewireTemp[i] = tempC; // Save values in SensorData
api->getLogger()->logDebug(GwLog::DEBUG,"DS18B20-%1d Temp: %.1f",i,tempC);
SetN2kPGN130316(N2kMsg, 0, i, N2kts_OutsideTemperature, CToKelvin(tempC), N2kDoubleNA);
api->sendN2kMessage(N2kMsg);
}
}
}
loopCounter++;
} }
// Get current RTC date and time all 500ms // Send RTC date and time to N2K all 500ms
if (millis() > starttime12 + 500) { if (millis() > starttime12 + 500) {
starttime12 = millis(); starttime12 = millis();
// Send date and time from RTC chip if GPS not ready
if (rtcOn == "DS1388" && RTC_ready) { if (rtcOn == "DS1388" && RTC_ready) {
DateTime dt = ds1388.now(); DateTime dt = ds1388.now();
sensors.rtcTime.tm_year = dt.year() - 1900; // Save values in SensorData sensors.rtcTime.tm_year = dt.year() - 1900; // Save values in SensorData
@@ -481,21 +523,63 @@ void sensorTask(void *param){
} }
// N2K sysTime is double in n2klib // N2K sysTime is double in n2klib
double sysTime = (dt.hour() * 3600) + (dt.minute() * 60) + dt.second(); 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+1, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
//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); //api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock); SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock);
api->sendN2kMessage(N2kMsg); api->sendN2kMessage(N2kMsg);
// }
} }
} else if (sensors.rtcValid) { }
// use internal rtc feature }
// Send date and time from software RTC (iRTC)
if (iRTC_ready == true && RTC_ready == false && GPS_ready == false) {
sensors.rtcTime = rtc.getTimeStruct(); sensors.rtcTime = rtc.getTimeStruct();
const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334};
int year = sensors.rtcTime.tm_year + 1900;
int month = sensors.rtcTime.tm_mon;
int day = sensors.rtcTime.tm_mday;
uint16_t switchYear = ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 + ((year - 1) - 1600) / 400;
long daysAt1970 = (year - 1970) * 365L + switchYear + daysOfYear[month] + day - 1;
// Leap day add if date is after Feb (i.e. month >= March)
if (month >= 2 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) {
daysAt1970 += 1;
} }
double sysTime = sensors.rtcTime.tm_hour * 3600.0 + sensors.rtcTime.tm_min * 60.0 + sensors.rtcTime.tm_sec;
//api->getLogger()->logDebug(GwLog::LOG, "iRTC time: %04d/%02d/%02d %02d:%02d:%02d", year, month + 1, day, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec);
//api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime);
SetN2kPGN126992(N2kMsg, 0, daysAt1970, sysTime, N2ktimes_LocalCrystalClock);
api->sendN2kMessage(N2kMsg);
} }
// Send supply voltage value all 1s }
// Send 1Wire data for all temperature sensors to N2K all 2s
if(millis() > starttime13 + 2000 && String(oneWireOn) == "DS18B20" && oneWire_ready == true){
starttime13 = millis();
float tempC;
ds18b20.requestTemperatures(); // Collect all temperature values (max.8)
for(int i=0;i<numberOfDevices; i++){
// Send only one 1Wire data per loop step (time reduction)
if(i == loopCounter % numberOfDevices){
if(ds18b20.getAddress(tempDeviceAddress, i)){
// Read temperature value in Celsius
tempC = ds18b20.getTempC(tempDeviceAddress);
}
// Send to NMEA200 bus for each sensor with instance number
if(!isnan(tempC)){
sensors.onewireTemp[i] = tempC; // Save values in SensorData
api->getLogger()->logDebug(GwLog::DEBUG,"DS18B20-%1d Temp: %.1f",i,tempC);
SetN2kPGN130316(N2kMsg, 0, i, N2kts_OutsideTemperature, CToKelvin(tempC), N2kDoubleNA);
api->sendN2kMessage(N2kMsg);
}
}
}
loopCounter++;
}
// Send supply voltage value to N2K all 1s
if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){ if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){
starttime5 = millis(); starttime5 = millis();
float rawVoltage = 0; // Default value float rawVoltage = 0; // Default value
@@ -565,7 +649,7 @@ void sensorTask(void *param){
#endif #endif
} }
// Send data from environment sensor all 2s // Send data from environment sensor to N2K all 2s
if(millis() > starttime6 + 2000){ if(millis() > starttime6 + 2000){
starttime6 = millis(); starttime6 = millis();
unsigned char TempSource = 2; // Inside temperature unsigned char TempSource = 2; // Inside temperature
@@ -630,7 +714,7 @@ void sensorTask(void *param){
} }
} }
// Send rotation angle all 500ms // Send rotation angle to N2K all 500ms
if(millis() > starttime7 + 500){ if(millis() > starttime7 + 500){
starttime7 = millis(); starttime7 = millis();
double rotationAngle=0; double rotationAngle=0;
@@ -678,7 +762,7 @@ void sensorTask(void *param){
} }
} }
// Send battery power value all 1s // Send battery power value to N2K all 1s
if(millis() > starttime8 + 1000 && (String(powsensor1) == "INA219" || String(powsensor1) == "INA226")){ if(millis() > starttime8 + 1000 && (String(powsensor1) == "INA219" || String(powsensor1) == "INA226")){
starttime8 = millis(); starttime8 = millis();
if(String(powsensor1) == "INA226" && INA226_1_ready == true){ if(String(powsensor1) == "INA226" && INA226_1_ready == true){
@@ -720,7 +804,7 @@ void sensorTask(void *param){
} }
} }
// Send solar power value all 1s // Send solar power value to N2K all 1s
if(millis() > starttime9 + 1000 && (String(powsensor2) == "INA219" || String(powsensor2) == "INA226")){ if(millis() > starttime9 + 1000 && (String(powsensor2) == "INA219" || String(powsensor2) == "INA226")){
starttime9 = millis(); starttime9 = millis();
if(String(powsensor2) == "INA226" && INA226_2_ready == true){ if(String(powsensor2) == "INA226" && INA226_2_ready == true){
@@ -750,7 +834,7 @@ void sensorTask(void *param){
} }
} }
// Send generator power value all 1s // Send generator power value to N2K all 1s
if(millis() > starttime10 + 1000 && (String(powsensor3) == "INA219" || String(powsensor3) == "INA226")){ if(millis() > starttime10 + 1000 && (String(powsensor3) == "INA219" || String(powsensor3) == "INA226")){
starttime10 = millis(); starttime10 = millis();
if(String(powsensor3) == "INA226" && INA226_3_ready == true){ if(String(powsensor3) == "INA226" && INA226_3_ready == true){

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +1,75 @@
// Function lib for display of boat data in various graphical chart formats // Function lib for display of boat data in various graphical chart formats
#pragma once #pragma once
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h"
struct Pos { struct Pos {
int x; int x;
int y; int y;
}; };
template <typename T> class RingBuffer;
class GwLog; struct ChartProps {
double range;
double step;
};
template <typename T> template <typename T>
class RingBuffer;
class GwLog;
class Chart { class Chart {
protected: protected:
CommonData *commonData; CommonData* commonData;
GwLog *logger; GwLog* logger;
RingBuffer<T> &dataBuf; // Buffer to display enum ChrtDataFormat {
int8_t chrtDir; // Chart timeline direction: [0] = horizontal, [1] = vertical WIND,
int8_t chrtSz; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom ROTATION,
SPEED,
DEPTH,
TEMPERATURE,
OTHER
};
static constexpr char HORIZONTAL = 'H';
static constexpr char VERTICAL = 'V';
static constexpr int8_t FULL_SIZE = 0;
static constexpr int8_t HALF_SIZE_LEFT = 1;
static constexpr int8_t HALF_SIZE_RIGHT = 2;
static constexpr int8_t MIN_FREE_VALUES = 60; // free 60 values when chart line reaches chart end
static constexpr int8_t THRESHOLD_NO_DATA = 3; // max. seconds of invalid values in a row
static constexpr int8_t VALAXIS_STEP = 60; // pixels between two chart value axis labels
static constexpr bool NO_SIMUDATA = true; // switch off simulation feature of <formatValue> function
RingBuffer<uint16_t>& dataBuf; // Buffer to display
//char chrtDir; // Chart timeline direction: 'H' = horizontal, 'V' = vertical
//int8_t chrtSz; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom
double dfltRng; // Default range of chart, e.g. 30 = [0..30] double dfltRng; // Default range of chart, e.g. 30 = [0..30]
uint16_t fgColor; // color code for any screen writing uint16_t fgColor; // color code for any screen writing
uint16_t bgColor; // color code for screen background uint16_t bgColor; // color code for screen background
bool useSimuData; // flag to indicate if simulation data is active bool useSimuData; // flag to indicate if simulation data is active
String tempFormat; // user defined format for temperature
double zeroValue; // "0" SI value for temperature
int top = 48; // display top header lines
int bottom = 22; // display bottom lines
int hGap = 11; // gap between 2 horizontal charts; actual gap is 2x <gap>
int vGap = 20; // gap between 2 vertical charts; actual gap is 2x <gap>
int xOffset = 33; // offset for horizontal axis (time/value), because of space for left vertical axis labeling
int yOffset = 10; // offset for vertical axis (time/value), because of space for top horizontal axis labeling
int dWidth; // Display width int dWidth; // Display width
int dHeight; // Display height int dHeight; // Display height
int top = 44; // chart gap at top of display (25 lines for standard gap + 19 lines for axis labels)
int bottom = 25; // chart gap at bottom of display to keep space for status line
int hGap = 11; // gap between 2 horizontal charts; actual gap is 2x <gap>
int vGap = 17; // gap between 2 vertical charts; actual gap is 2x <gap>
int timAxis, valAxis; // size of time and value chart axis int timAxis, valAxis; // size of time and value chart axis
Pos cStart; // start point of chart area Pos cRoot; // start point of chart area
double chrtRng; // Range of buffer values from min to max value double chrtRng; // Range of buffer values from min to max value
double chrtMin; // Range low end value double chrtMin; // Range low end value
double chrtMax; // Range high end value double chrtMax; // Range high end value
double chrtMid; // Range mid value double chrtMid; // Range mid value
double rngStep; // Defines the step of adjustment (e.g. 10 m/s) for value axis range double rngStep; // Defines the step of adjustment (e.g. 10 m/s) for value axis range
bool recalcRngCntr = false; // Flag for re-calculation of mid value of chart for wind data types bool recalcRngMid = false; // Flag for re-calculation of mid value of chart for wind data types
String dbName, dbFormat; // Name and format of data buffer String dbName, dbFormat; // Name and format of data buffer
int chrtDataFmt; // Data format of chart: [0] size values; [1] degree of course or wind; [2] rotational degrees ChrtDataFormat chrtDataFmt; // Data format of chart boat data type
double dbMIN_VAL; // Lowest possible value of buffer of type <T> double dbMIN_VAL; // Lowest possible value of buffer of type <T>
double dbMAX_VAL; // Highest possible value of buffer of type <T>; indicates invalid value in buffer double dbMAX_VAL; // Highest possible value of buffer of type <T>; indicates invalid value in buffer
size_t bufSize; // History buffer size: 1.920 values for 32 min. history chart size_t bufSize; // History buffer size: 1.920 values for 32 min. history chart
@@ -52,19 +80,37 @@ protected:
size_t currIdx; // Current index in TWD history buffer size_t currIdx; // Current index in TWD history buffer
size_t lastIdx; // Last index of TWD history buffer size_t lastIdx; // Last index of TWD history buffer
size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added
int numNoData; // Counter for multiple invalid data values in a row
bool bufDataValid = false; // Flag to indicate if buffer data is valid bool bufDataValid = false; // Flag to indicate if buffer data is valid
int oldChrtIntv = 0; // remember recent user selection of data interval int oldChrtIntv = 0; // remember recent user selection of data interval
void drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue); // Draw chart line double chrtPrevVal; // Last data value in chart area
double getRng(double center, size_t amount); // Calculate range between chart center and edges int x, y; // x and y coordinates for drawing
void calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, double& rng); // Calculate chart points for value axis and return range between <min> and <max> int prevX, prevY; // Last x and y coordinates for drawing
void drawChrtTimeAxis(int8_t chrtIntv); // Draw time axis of chart, value and lines
void drawChrtValAxis(); // Draw value axis of chart, value and lines bool setChartDimensions(const char direction, const int8_t size); //define dimensions and start points for chart
void prntCurrValue(GwApi::BoatValue& currValue); // Add current boat data value to chart void drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue& currValue); // Draw chart line
void getBufferStartNSize(const int8_t chrtIntv); // Identify buffer size and buffer start position for chart
void calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, double& rng); // Calculate chart points for value axis and return range between <min> and <max>
void drawChartLines(const char direction, const int8_t chrtIntv, const double chrtScale); // Draw chart graph
Pos setCurrentChartPoint(const int i, const char direction, const double chrtVal, const double chrtScale); // Set current chart point to draw
void drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8_t chrtIntv); // Draw time axis of chart, value and lines
void drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntLabel); // Draw value axis of chart, value and lines
void prntCurrValue(const char chrtDir, GwApi::BoatValue& currValue); // Add current boat data value to chart
void prntNoValidData(const char chrtDir); // print message for no valid data available
double getAngleRng(const double center, size_t amount); // Calculate range between chart center and edges
void prntVerticChartThreeValueAxisLabel(const GFXfont* font); // print value axis label with only three values: top, mid, and bottom for vertical chart
void prntHorizChartThreeValueAxisLabel(const GFXfont* font); // print value axis label with only three values: top, mid, and bottom for horizontal chart
void prntHorizChartMultiValueAxisLabel(const GFXfont* font); // print value axis label with multiple axis lines for horizontal chart
void drawBoldLine(const int16_t x1, const int16_t y1, const int16_t x2, const int16_t y2); // Draw chart line with thickness of 2px
String convNformatLabel(const double& label); // Convert and format current axis label to user defined format; helper function for easier handling of OBP60Formatter
String formatLabel(const double& label); // Format current axis label for printing w/o data format conversion (has been done earlier)
public: public:
Chart(RingBuffer<T>& dataBuf, int8_t chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData); // Chart object of data chart // Define default chart range and range step for each boat data type
~Chart(); static std::map<String, ChartProps> dfltChrtDta;
void showChrt(int8_t chrtIntv, GwApi::BoatValue currValue); // Perform all actions to draw chart
Chart(RingBuffer<uint16_t>& dataBuf, double dfltRng, CommonData& common, bool useSimuData); // Chart object of data chart
~Chart();
void showChrt(char chrtDir, int8_t chrtSz, const int8_t chrtIntv, bool prntName, bool showCurrValue, GwApi::BoatValue currValue); // Perform all actions to draw chart
}; };

View File

@@ -0,0 +1,270 @@
#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", "RPOS", "ROT"}, // Bus values we need in the page
const int HowManyValues = 11;
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 ShowRPOS = 9;
const int ShowROT = 10;
const int Compass_X0 = 200; // X center point of compass band
const int Compass_Y0 = 90; // Y position of compass lines
//const int Compass_LineLength = 22; // Length of compass lines
const int Compass_LineLength = 15; // Length of compass lines
const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
class PageAutopilot : public Page
{
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 = "-10";
commonData->keydata[1].label = "-1";
commonData->keydata[2].label = "Auto";
commonData->keydata[3].label = "+1";
commonData->keydata[4].label = "+10";
}
virtual int handleKey(int key){
// 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);
}
//***********************************************************
// 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-40);
getdisplay().print(DataName[WhichDataCompass]); // Page name
// Draw compass base line and pointer
getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor);
//getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor);
getdisplay().fillTriangle(Compass_X0,Compass_Y0-30,Compass_X0-10,Compass_Y0-60,Compass_X0+10,Compass_Y0-60,commonData->fgcolor);
// Draw trendlines
for ( int i = 1; i < abs(TheTrend) / 2; i++){
int x1;
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;
displayRudderPosition(DataValue[ShowSOG], 20, 200, 160, commonData->fgcolor, commonData->bgcolor);
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", "RPOS", "ROT"}, // Bus values we need in the page
true // Show display header on/off
);
#endif

View File

@@ -4,91 +4,298 @@
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
/* /*
* TODO mode: race timer: keys * PageClock: Clock page with
* - prepare: set countdown to 5min * - Analog mode (mode == 'A')
* reset: abort current countdown and start over with 5min preparation * - Digital mode (mode == 'D')
* - 5min: key press * - Countdown timer mode (mode == 'T')
* - 4min: key press to sync * - Keys in mode analog and digital clock:
* - 1min: buzzer signal * K1: MODE (A/D/T)
* - start: buzzer signal for start * K2: POS (select field: HH / MM / SS)
* K3:
* K4:
* K5: TZ (Local/UTC)
* *
* Regatta timer mode:
* - Format HH:MM:SS (24h, leading zeros)
* - Keys in timer mode:
* K1: MODE (A/D/T)
* K2: POS (select field: HH / MM / SS)
* K3: + (increment selected field)
* K4: - (decrement selected field)
* K5: RUN (start/stop countdown)
* - Selection marker: line under active field (width 2px, not wider than digits)
* - Editing only possible when timer is not running
* - When page is left, running timer continues in background using RTC time
* (on re-entry, remaining time is recalculated from RTC)
*/ */
class PageClock : public Page class PageClock : public Page
{ {
bool simulation = false; bool simulation = false;
int simtime; int simtime;
bool keylock = false; bool keylock = false;
char source = 'R'; // time source (R)TC | (G)PS | (N)TP #ifdef BOARD_OBP60S3
char mode = 'A'; // display mode (A)nalog | (D)igital | race (T)imer char source = 'G'; // Time source (R)TC | (G)PS | (N)TP
char tz = 'L'; // time zone (L)ocal | (U)TC #endif
double timezone = 0; // there are timezones with non int offsets, e.g. 5.5 or 5.75 #ifdef BOARD_OBP40S3
double homelat; char source = 'R'; // time source (R)TC | (G)PS | (N)TP
double homelon; #endif
bool homevalid = false; // homelat and homelon are valid char mode = 'A'; // Display mode (A)nalog | (D)igital | race (T)imer
char tz = 'L'; // Time zone (L)ocal | (U)TC
double timezone = 0; // There are timezones with non int offsets, e.g. 5.5 or 5.75
double homelat;
double homelon;
bool homevalid = false; // Homelat and homelon are valid
public: // Timer state (static so it survives page switches)
PageClock(CommonData &common){ static bool timerInitialized;
static bool timerRunning;
static int timerHours;
static int timerMinutes;
static int timerSeconds;
// Preset seconds for sync button (default 4 minutes)
static const int timerPresetSeconds = 4 * 60;
// Initial timer setting at start (so we can restore it)
static int timerStartHours;
static int timerStartMinutes;
static int timerStartSeconds;
static int selectedField; // 0 = hours, 1 = minutes, 2 = seconds
static bool showSelectionMarker;
static time_t timerEndEpoch; // Absolute end time based on RTC
void setupTimerDefaults()
{
if (!timerInitialized) {
timerInitialized = true;
timerRunning = false;
timerHours = 0;
timerMinutes = 0;
timerSeconds = 0;
timerStartHours = 0;
timerStartMinutes = 0;
timerStartSeconds = 0;
selectedField = 0;
showSelectionMarker = true;
timerEndEpoch = 0;
}
}
// Limiter for overrun settings values
static int clamp(int value, int minVal, int maxVal)
{
if (value < minVal) return maxVal;
if (value > maxVal) return minVal;
return value;
}
void incrementSelected()
{
if (selectedField == 0) {
timerHours = clamp(timerHours + 1, 0, 23);
} else if (selectedField == 1) {
timerMinutes = clamp(timerMinutes + 1, 0, 59);
} else {
timerSeconds = clamp(timerSeconds + 1, 0, 59);
}
}
void decrementSelected()
{
if (selectedField == 0) {
timerHours = clamp(timerHours - 1, 0, 23);
} else if (selectedField == 1) {
timerMinutes = clamp(timerMinutes - 1, 0, 59);
} else {
timerSeconds = clamp(timerSeconds - 1, 0, 59);
}
}
int totalTimerSeconds() const
{
return timerHours * 3600 + timerMinutes * 60 + timerSeconds;
}
public:
PageClock(CommonData& common)
{
commonData = &common; commonData = &common;
common.logger->logDebug(GwLog::LOG,"Instantiate PageClock"); common.logger->logDebug(GwLog::LOG, "Instantiate PageClock");
simulation = common.config->getBool(common.config->useSimuData); simulation = common.config->getBool(common.config->useSimuData);
timezone = common.config->getString(common.config->timeZone).toDouble(); timezone = common.config->getString(common.config->timeZone).toDouble();
homelat = common.config->getString(common.config->homeLAT).toDouble(); homelat = common.config->getString(common.config->homeLAT).toDouble();
homelon = common.config->getString(common.config->homeLON).toDouble(); homelon = common.config->getString(common.config->homeLON).toDouble();
homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0; homevalid = homelat >= -180.0 and homelat <= 180 and homelon >= -90.0 and homelon <= 90.0;
simtime = 38160; // time value 11:36 simtime = 38160; // time value 11:36
setupTimerDefaults();
} }
virtual void setupKeys(){ virtual void setupKeys()
{
Page::setupKeys(); Page::setupKeys();
commonData->keydata[0].label = "SRC";
commonData->keydata[1].label = "MODE"; if (mode == 'T') {
// Timer mode: MODE, POS, +, -, RUN
commonData->keydata[0].label = "MODE";
commonData->keydata[1].label = "POS";
// K3: '+' while editing, 'SYNC' while running to set a preset countdown
commonData->keydata[2].label = timerRunning ? "SYNC" : "+";
commonData->keydata[3].label = "-";
commonData->keydata[4].label = timerRunning ? "RESET" : "START";
} else {
// Clock modes: like original
commonData->keydata[0].label = "MODE";
commonData->keydata[1].label = "SRC";
commonData->keydata[4].label = "TZ"; commonData->keydata[4].label = "TZ";
} }
}
// Key functions // Key functions
virtual int handleKey(int key){ virtual int handleKey(int key)
// Time source {
if (key == 1) { setupTimerDefaults();
if (source == 'G') {
source = 'R';
} else {
source = 'G';
}
return 0;
}
if (key == 2) {
if (mode == 'A') {
mode = 'D';
} else if (mode == 'D') {
mode = 'T';
} else {
mode = 'A';
}
return 0;
}
// Time zone: Local / UTC
if (key == 5) {
if (tz == 'L') {
tz = 'U';
} else {
tz = 'L';
}
return 0;
}
// Keylock function // Keylock function
if(key == 11){ // Code for keylock if (key == 11) { // Code for keylock
keylock = !keylock; // Toggle keylock keylock = !keylock; // Toggle keylock
return 0; // Commit the key return 0; // Commit the key
} }
if (mode == 'T') {
// Timer mode key handling
// MODE (K1): cycle display mode A/D/T
if (key == 1) {
switch (mode) {
case 'A': mode = 'D'; break;
case 'D': mode = 'T'; break;
case 'T': mode = 'A'; break;
default: mode = 'A'; break;
}
setupKeys();
return 0;
}
// POS (K2): select field HH / MM / SS (only if timer not running)
if (key == 2 && !timerRunning) {
selectedField = (selectedField + 1) % 3;
showSelectionMarker = true;
return 0;
}
// + (K3): increment selected field (only if timer not running)
if (key == 3 && !timerRunning) {
incrementSelected();
return 0;
}
if (key == 3 && timerRunning) {
// When timer is running, K3 acts as a synchronization button:
// set remaining countdown to the preset value (e.g. 4 minutes).
if (commonData->data.rtcValid) {
int preset = timerPresetSeconds;
// update start-setting so STOP will restore this preset
timerStartHours = preset / 3600;
timerStartMinutes = (preset % 3600) / 60;
timerStartSeconds = preset % 60;
struct tm rtcCopy = commonData->data.rtcTime;
time_t nowEpoch = mktime(&rtcCopy);
timerEndEpoch = nowEpoch + preset;
// Update visible timer fields immediately
timerHours = timerStartHours;
timerMinutes = timerStartMinutes;
timerSeconds = timerStartSeconds;
// commonData->keydata[4].label = "RESET";
}
return 0;
}
// - (K4): decrement selected field (only if timer not running)
if (key == 4 && !timerRunning) {
decrementSelected();
return 0;
}
if (key == 4 && timerRunning) { // No action if timer running
return 0;
}
// RUN (K5): start/stop timer
if (key == 5) {
if (!timerRunning) {
// Start timer if a non-zero duration is set
int total = totalTimerSeconds();
if (total > 0 && commonData->data.rtcValid) {
// Remember initial timer setting at start
timerStartHours = timerHours;
timerStartMinutes = timerMinutes;
timerStartSeconds = timerSeconds;
struct tm rtcCopy = commonData->data.rtcTime;
time_t nowEpoch = mktime(&rtcCopy);
timerEndEpoch = nowEpoch + total;
timerRunning = true;
showSelectionMarker = false;
}
} else {
// Stop timer: restore initial start setting
timerHours = timerStartHours;
timerMinutes = timerStartMinutes;
timerSeconds = timerStartSeconds;
timerRunning = false;
showSelectionMarker = true;
// marker will become visible again only after POS press
}
return 0;
}
// In timer mode, other keys are passed through
return key; return key;
} }
int displayPage(PageData &pageData) // Clock (A/D) modes key handling like original PageClock
// MODE (K1)
if (key == 1) {
switch (mode) {
case 'A': mode = 'D'; break;
case 'D': mode = 'T'; break;
case 'T': mode = 'A'; break;
default: mode = 'A'; break;
}
setupKeys();
return 0;
}
// Time source (K2)
if (key == 2) {
switch (source) {
case 'G': source = 'R'; break;
case 'R': source = 'G'; break;
default: source = 'G'; break;
}
return 0;
}
// Time zone: Local / UTC (K5)
if (key == 5) {
switch (tz) {
case 'L': tz = 'U'; break;
case 'U': tz = 'L'; break;
default: tz = 'L'; break;
}
return 0;
}
return key;
}
int displayPage(PageData& pageData)
{ {
GwConfigHandler *config = commonData->config; GwConfigHandler* config = commonData->config;
GwLog *logger = commonData->logger; GwLog* logger = commonData->logger;
setupTimerDefaults();
setupKeys(); // Ensure correct key labels for current mode
static String svalue1old = ""; static String svalue1old = "";
static String unit1old = ""; static String unit1old = "";
@@ -112,58 +319,57 @@ bool homevalid = false; // homelat and homelon are valid
String backlightMode = config->getString(config->backlight); String backlightMode = config->getString(config->backlight);
// Get boat values for GPS time // Get boat values for GPS time
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) GwApi::BoatValue* bvalue1 = pageData.values[0]; // First element in list
String name1 = bvalue1->getName().c_str(); // Value name String name1 = bvalue1->getName().c_str(); // Value name
name1 = name1.substring(0, 6); // String length limit for value name name1 = name1.substring(0, 6); // String length limit for value name
if(simulation == false){ if (simulation == false) {
value1 = bvalue1->value; // Value as double in SI unit value1 = bvalue1->value; // Value as double in SI unit
} } else {
else{
value1 = simtime++; // Simulation data for time value 11:36 in seconds value1 = simtime++; // Simulation data for time value 11:36 in seconds
} // Other simulation data see OBP60Formatter.cpp } // Other simulation data see OBP60Formatter.cpp
bool valid1 = bvalue1->valid; // Valid information bool valid1 = bvalue1->valid; // Valid information
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
if(valid1 == true){ if (valid1 == true) {
svalue1old = svalue1; // Save old value svalue1old = svalue1; // Save old value
unit1old = unit1; // Save old unit unit1old = unit1; // Save old unit
} }
// Get boat values for GPS date // Get boat values for GPS date
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue) GwApi::BoatValue* bvalue2 = pageData.values[1]; // Second element in list
String name2 = bvalue2->getName().c_str(); // Value name String name2 = bvalue2->getName().c_str(); // Value name
name2 = name2.substring(0, 6); // String length limit for value name name2 = name2.substring(0, 6); // String length limit for value name
value2 = bvalue2->value; // Value as double in SI unit value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information bool valid2 = bvalue2->valid; // Valid informationgetdisplay().print("RTC");
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value
if(valid2 == true){ if (valid2 == true) {
svalue2old = svalue2; // Save old value svalue2old = svalue2; // Save old value
unit2old = unit2; // Save old unit unit2old = unit2; // Save old unit
} }
// Get boat values for HDOP date // Get boat values for HDOP
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list (only one value by PageOneValue) GwApi::BoatValue* bvalue3 = pageData.values[2]; // Third element in list
String name3 = bvalue3->getName().c_str(); // Value name String name3 = bvalue3->getName().c_str(); // Value name
name3 = name3.substring(0, 6); // String length limit for value name name3 = name3.substring(0, 6); // String length limit for value name
value3 = bvalue3->value; // Value as double in SI unit value3 = bvalue3->value; // Value as double in SI unit
bool valid3 = bvalue3->valid; // Valid information bool valid3 = bvalue3->valid; // Valid information
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value
String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value
if(valid3 == true){ if (valid3 == true) {
svalue3old = svalue3; // Save old value svalue3old = svalue3; // Save old value
unit3old = unit3; // Save old unit unit3old = unit3; // Save old unit
} }
// Optical warning by limit violation (unused) // Optical warning by limit violation (unused)
if(String(flashLED) == "Limit Violation"){ if (String(flashLED) == "Limit Violation") {
setBlinkingLED(false); setBlinkingLED(false);
setFlashLED(false); setFlashLED(false);
} }
// Logging boat values // Logging boat values
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement? if (bvalue1 == NULL) return PAGE_OK;
LOG_DEBUG(GwLog::LOG,"Drawing at PageClock, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2); LOG_DEBUG(GwLog::LOG, "Drawing at PageClock, %s:%f, %s:%f", name1.c_str(), value1, name2.c_str(), value2);
// Draw page // Draw page
//*********************************************************** //***********************************************************
@@ -174,7 +380,180 @@ bool homevalid = false; // homelat and homelon are valid
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600; time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600;
struct tm *local_tm = localtime(&tv); struct tm* local_tm = localtime(&tv);
if (mode == 'T') {
// REGATTA TIMER MODE: countdown timer HH:MM:SS in the center with 7-segment font
//*******************************************************************************
int dispH = timerHours;
int dispM = timerMinutes;
int dispS = timerSeconds;
// Update remaining time if timer is running (based on RTC)
if (timerRunning && commonData->data.rtcValid) {
struct tm rtcCopy = commonData->data.rtcTime;
time_t nowEpoch = mktime(&rtcCopy);
time_t remaining = timerEndEpoch - nowEpoch;
if(remaining <= 5 && remaining != 0){
// Short pre buzzer alarm (100% power)
setBuzzerPower(100);
buzzer(TONE2, 75);
setBuzzerPower(config->getInt(config->buzzerPower));
}
if (remaining <= 0) {
remaining = 0;
timerRunning = false;
commonData->keydata[3].label = "-";
commonData->keydata[4].label = "START";
showSelectionMarker = true;
// Buzzer alarm (100% power)
setBuzzerPower(100);
buzzer(TONE2, 800);
setBuzzerPower(config->getInt(config->buzzerPower));
// When countdown is finished, restore the initial start time
timerHours = timerStartHours;
timerMinutes = timerStartMinutes;
timerSeconds = timerStartSeconds;
}
else{
commonData->keydata[3].label = "";
commonData->keydata[4].label = "RESET";
}
int rem = static_cast<int>(remaining);
dispH = rem / 3600;
rem -= dispH * 3600;
dispM = rem / 60;
dispS = rem % 60;
}
char buf[9]; // "HH:MM:SS"
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", dispH, dispM, dispS);
String timeStr = String(buf);
// Clear central area and draw large digital time
getdisplay().fillRect(0, 110, getdisplay().width(), 80, commonData->bgcolor);
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
// Determine widths for digits and colon to position selection underline exactly
int16_t x0, y0;
uint16_t wDigit, hDigit;
uint16_t wColon, hColon;
getdisplay().getTextBounds("00", 0, 0, &x0, &y0, &wDigit, &hDigit);
getdisplay().getTextBounds(":", 0, 0, &x0, &y0, &wColon, &hColon);
uint16_t totalWidth = 3 * wDigit + 2 * wColon;
int16_t baseX = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(totalWidth)) / 2;
int16_t centerY = 150;
// Draw time string centered
int16_t x1b, y1b;
uint16_t wb, hb;
getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb);
int16_t textX = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2;
int16_t textY = centerY + hb / 2;
//getdisplay().setCursor(textX, textY); // horzontal jitter
getdisplay().setCursor(47, textY); // static X position
getdisplay().print(timeStr);
// Selection marker (only visible when not running and POS pressed)
if (!timerRunning && showSelectionMarker) {
int16_t selX = baseX - 8; // Hours start
if (selectedField == 1) {
selX = baseX + wDigit + wColon; // Minutes start
} else if (selectedField == 2) {
selX = baseX + 2 * wDigit + 2 * wColon + 12; // Seconds start
}
int16_t underlineY = centerY + hb / 2 + 5;
//getdisplay().fillRect(selX, underlineY, wDigit, 6, commonData->fgcolor);
getdisplay().fillRoundRect(selX, underlineY, wDigit, 6, 2, commonData->fgcolor);
}
// Page label
getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().setCursor(100, 70);
getdisplay().print("Regatta Timer");
} else if (mode == 'D') {
// DIGITAL CLOCK MODE: large 7-segment time based on GPS/RTC
//**********************************************************
int hour24 = 0;
int minute24 = 0;
int second24 = 0;
if (source == 'R' && commonData->data.rtcValid) {
time_t tv2 = mktime(&commonData->data.rtcTime);
if (tz == 'L') {
tv2 += static_cast<time_t>(timezone * 3600);
}
struct tm* tm2 = localtime(&tv2);
hour24 = tm2->tm_hour;
minute24 = tm2->tm_min;
second24 = tm2->tm_sec;
} else {
double t = value1;
if (tz == 'L') {
t += timezone * 3600;
}
if (t >= 86400) t -= 86400;
if (t < 0) t += 86400;
hour24 = static_cast<int>(t / 3600.0);
int rest = static_cast<int>(t) - hour24 * 3600;
minute24 = rest / 60;
second24 = rest % 60;
}
char buf[9]; // "HH:MM:SS"
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour24, minute24, second24);
String timeStr = String(buf);
getdisplay().fillRect(0, 110, getdisplay().width(), 80, commonData->bgcolor);
getdisplay().setFont(&DSEG7Classic_BoldItalic30pt7b);
int16_t x1b, y1b;
uint16_t wb, hb;
getdisplay().getTextBounds(timeStr, 0, 0, &x1b, &y1b, &wb, &hb);
int16_t x = (static_cast<int16_t>(getdisplay().width()) - static_cast<int16_t>(wb)) / 2;
int16_t y = 150 + hb / 2;
//getdisplay().setCursor(x, y); // horizontal jitter
getdisplay().setCursor(47, y); // static X position
getdisplay().print(timeStr); // Display actual time
// Small indicators: timezone and source
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(47, 110);
if (source == 'G') {
getdisplay().print("GPS");
} else {
getdisplay().print("RTC");
}
getdisplay().setCursor(47 + 40, 110);
if (holdvalues == false) {
getdisplay().print(tz == 'L' ? "LOT" : "UTC");
} else {
getdisplay().print(unit2old); // date unit
}
// Page label
getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().setCursor(100, 70);
getdisplay().print("Digital Clock");
} else {
// ANALOG CLOCK MODE (mode == 'A')
//********************************
// Show values GPS date // Show values GPS date
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
@@ -187,8 +566,7 @@ bool homevalid = false; // homelat and homelon are valid
// RTC value // RTC value
if (tz == 'L') { if (tz == 'L') {
getdisplay().print(formatDate(dateformat, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday)); getdisplay().print(formatDate(dateformat, local_tm->tm_year + 1900, local_tm->tm_mon + 1, local_tm->tm_mday));
} } else {
else {
getdisplay().print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday)); getdisplay().print(formatDate(dateformat, commonData->data.rtcTime.tm_year + 1900, commonData->data.rtcTime.tm_mon + 1, commonData->data.rtcTime.tm_mday));
} }
} else { } else {
@@ -201,28 +579,25 @@ bool homevalid = false; // homelat and homelon are valid
getdisplay().setCursor(10, 95); getdisplay().setCursor(10, 95);
getdisplay().print("Date"); // Name getdisplay().print("Date"); // Name
// Horizintal separator left // Horizontal separator left
getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor); getdisplay().fillRect(0, 149, 60, 3, commonData->fgcolor);
// Show values GPS time // Show values GPS time (small text bottom left)
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(10, 250); getdisplay().setCursor(10, 250);
if (holdvalues == false) { if (holdvalues == false) {
if (source == 'G') { if (source == 'G') {
getdisplay().print(svalue1); // Value getdisplay().print(svalue1); // Value
} } else if (commonData->data.rtcValid) {
else if (commonData->data.rtcValid) {
if (tz == 'L') { if (tz == 'L') {
getdisplay().print(formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec)); getdisplay().print(formatTime('s', local_tm->tm_hour, local_tm->tm_min, local_tm->tm_sec));
} } else {
else {
getdisplay().print(formatTime('s', commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec)); getdisplay().print(formatTime('s', commonData->data.rtcTime.tm_hour, commonData->data.rtcTime.tm_min, commonData->data.rtcTime.tm_sec));
} }
} else { } else {
getdisplay().print("---"); getdisplay().print("---");
} }
} } else {
else {
getdisplay().print(svalue1old); getdisplay().print(svalue1old);
} }
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
@@ -240,13 +615,13 @@ bool homevalid = false; // homelat and homelon are valid
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(335, 65); getdisplay().setCursor(335, 65);
if(holdvalues == false) getdisplay().print(sunrise); // Value if (holdvalues == false) getdisplay().print(sunrise); // Value
else getdisplay().print(svalue5old); else getdisplay().print(svalue5old);
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(335, 95); getdisplay().setCursor(335, 95);
getdisplay().print("SunR"); // Name getdisplay().print("SunR"); // Name
// Horizintal separator right // Horizontal separator right
getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor); getdisplay().fillRect(340, 149, 80, 3, commonData->fgcolor);
// Show values sunset // Show values sunset
@@ -260,50 +635,39 @@ bool homevalid = false; // homelat and homelon are valid
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(335, 250); getdisplay().setCursor(335, 250);
if(holdvalues == false) getdisplay().print(sunset); // Value if (holdvalues == false) getdisplay().print(sunset); // Value
else getdisplay().print(svalue6old); else getdisplay().print(svalue6old);
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(335, 220); getdisplay().setCursor(335, 220);
getdisplay().print("SunS"); // Name getdisplay().print("SunS"); // Name
//*******************************************************************************************
// Draw clock
int rInstrument = 110; // Radius of clock int rInstrument = 110; // Radius of clock
float pi = 3.141592; float pi = 3.141592;
getdisplay().fillCircle(200, 150, rInstrument + 10, commonData->fgcolor); // Outer circle getdisplay().fillCircle(200, 150, rInstrument + 10, commonData->fgcolor); // Outer circle
getdisplay().fillCircle(200, 150, rInstrument + 7, commonData->bgcolor); // Outer circle getdisplay().fillCircle(200, 150, rInstrument + 7, commonData->bgcolor); // Outer circle
for(int i=0; i<360; i=i+1) for (int i = 0; i < 360; i = i + 1)
{ {
// Scaling values // Scaling values
float x = 200 + (rInstrument-30)*sin(i/180.0*pi); // x-coordinate dots float x = 200 + (rInstrument - 30) * sin(i / 180.0 * pi); // x-coordinate dots
float y = 150 - (rInstrument-30)*cos(i/180.0*pi); // y-coordinate cots float y = 150 - (rInstrument - 30) * cos(i / 180.0 * pi); // y-coordinate dots
const char *ii = ""; const char* ii = "";
switch (i) switch (i)
{ {
case 0: ii="12"; break; case 0: ii = "12"; break;
case 30 : ii=""; break; case 90: ii = "3"; break;
case 60 : ii=""; break; case 180: ii = "6"; break;
case 90 : ii="3"; break; case 270: ii = "9"; break;
case 120 : ii=""; break;
case 150 : ii=""; break;
case 180 : ii="6"; break;
case 210 : ii=""; break;
case 240 : ii=""; break;
case 270 : ii="9"; break;
case 300 : ii=""; break;
case 330 : ii=""; break;
default: break; default: break;
} }
// Print text centered on position x, y // Print text centered on position x, y
int16_t x1, y1; // Return values of getTextBounds int16_t x1c, y1c; // Return values of getTextBounds
uint16_t w, h; // Return values of getTextBounds uint16_t wc, hc; // Return values of getTextBounds
getdisplay().getTextBounds(ii, int(x), int(y), &x1, &y1, &w, &h); // Calc width of new string getdisplay().getTextBounds(ii, int(x), int(y), &x1c, &y1c, &wc, &hc); // Calc width of new string
getdisplay().setCursor(x-w/2, y+h/2); getdisplay().setCursor(x - wc / 2, y + hc / 2);
if(i % 30 == 0){ if (i % 90 == 0) {
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().print(ii); getdisplay().print(ii);
} }
@@ -311,37 +675,36 @@ bool homevalid = false; // homelat and homelon are valid
// Draw sub scale with dots // Draw sub scale with dots
float sinx = 0; float sinx = 0;
float cosx = 0; float cosx = 0;
if(i % 6 == 0){ if (i % 6 == 0) {
float x1c = 200 + rInstrument*sin(i/180.0*pi); float x1d = 200 + rInstrument * sin(i / 180.0 * pi);
float y1c = 150 - rInstrument*cos(i/180.0*pi); float y1d = 150 - rInstrument * cos(i / 180.0 * pi);
getdisplay().fillCircle((int)x1c, (int)y1c, 2, commonData->fgcolor); getdisplay().fillCircle((int)x1d, (int)y1d, 2, commonData->fgcolor);
sinx=sin(i/180.0*pi); sinx = sin(i / 180.0 * pi);
cosx=cos(i/180.0*pi); cosx = cos(i / 180.0 * pi);
} }
// Draw sub scale with lines (two triangles) // Draw sub scale with lines (two triangles)
if(i % 30 == 0){ if (i % 30 == 0) {
float dx=2; // Line thickness = 2*dx+1 float dx = 2; // Line thickness = 2*dx+1
float xx1 = -dx; float xx1 = -dx;
float xx2 = +dx; float xx2 = +dx;
float yy1 = -(rInstrument-10); float yy1 = -(rInstrument - 10);
float yy2 = -(rInstrument+10); float yy2 = -(rInstrument + 10);
getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1), getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1),
200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1), 200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
200+(int)(cosx*xx1-sinx*yy2),150+(int)(sinx*xx1+cosx*yy2),commonData->fgcolor); 200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2), commonData->fgcolor);
getdisplay().fillTriangle(200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1), getdisplay().fillTriangle(200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
200+(int)(cosx*xx1-sinx*yy2),150+(int)(sinx*xx1+cosx*yy2), 200 + (int)(cosx * xx1 - sinx * yy2), 150 + (int)(sinx * xx1 + cosx * yy2),
200+(int)(cosx*xx2-sinx*yy2),150+(int)(sinx*xx2+cosx*yy2),commonData->fgcolor); 200 + (int)(cosx * xx2 - sinx * yy2), 150 + (int)(sinx * xx2 + cosx * yy2), commonData->fgcolor);
} }
} }
// Print Unit in clock // Print Unit in clock
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(175, 110); getdisplay().setCursor(175, 110);
if(holdvalues == false){ if (holdvalues == false) {
getdisplay().print(tz == 'L' ? "LOT" : "UTC"); getdisplay().print(tz == 'L' ? "LOT" : "UTC");
} } else {
else{
getdisplay().print(unit2old); // date unit getdisplay().print(unit2old); // date unit
} }
@@ -358,10 +721,10 @@ bool homevalid = false; // homelat and homelon are valid
double minute = 0; double minute = 0;
if (source == 'R') { if (source == 'R') {
if (tz == 'L') { if (tz == 'L') {
time_t tv = mktime(&commonData->data.rtcTime) + timezone * 3600; time_t tv2 = mktime(&commonData->data.rtcTime) + timezone * 3600;
struct tm *local_tm = localtime(&tv); struct tm* local_tm2 = localtime(&tv2);
minute = local_tm->tm_min; minute = local_tm2->tm_min;
hour = local_tm->tm_hour; hour = local_tm2->tm_hour;
} else { } else {
minute = commonData->data.rtcTime.tm_min; minute = commonData->data.rtcTime.tm_min;
hour = commonData->data.rtcTime.tm_hour; hour = commonData->data.rtcTime.tm_hour;
@@ -371,8 +734,8 @@ bool homevalid = false; // homelat and homelon are valid
if (tz == 'L') { if (tz == 'L') {
value1 += timezone * 3600; value1 += timezone * 3600;
} }
if (value1 > 86400) {value1 -= 86400;} if (value1 > 86400) { value1 -= 86400; }
if (value1 < 0) {value1 += 86400;} if (value1 < 0) { value1 += 86400; }
hour = (value1 / 3600.0); hour = (value1 / 3600.0);
// minute = (hour - int(hour)) * 3600.0 / 60.0; // Analog minute pointer smooth moving // minute = (hour - int(hour)) * 3600.0 / 60.0; // Analog minute pointer smooth moving
minute = int((hour - int(hour)) * 3600.0 / 60.0); // Jumping minute pointer from minute to minute minute = int((hour - int(hour)) * 3600.0 / 60.0); // Jumping minute pointer from minute to minute
@@ -380,22 +743,22 @@ bool homevalid = false; // homelat and homelon are valid
if (hour > 12) { if (hour > 12) {
hour -= 12.0; hour -= 12.0;
} }
LOG_DEBUG(GwLog::DEBUG,"... PageClock, value1: %f hour: %f minute:%f", value1, hour, minute); LOG_DEBUG(GwLog::DEBUG, "... PageClock, value1: %f hour: %f minute:%f", value1, hour, minute);
// Draw hour pointer // Draw hour pointer
float startwidth = 8; // Start width of pointer float startwidth = 8; // Start width of pointer
if(valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true){ if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) {
float sinx=sin(hour * 30.0 * pi / 180); // Hour float sinx = sin(hour * 30.0 * pi / 180); // Hour
float cosx=cos(hour * 30.0 * pi / 180); float cosx = cos(hour * 30.0 * pi / 180);
// Normal pointer // Normal pointer
// Pointer as triangle with center base 2*width // Pointer as triangle with center base 2*width
float xx1 = -startwidth; float xx1 = -startwidth;
float xx2 = startwidth; float xx2 = startwidth;
float yy1 = -startwidth; float yy1 = -startwidth;
float yy2 = -(rInstrument * 0.5); float yy2 = -(rInstrument * 0.5);
getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1), getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1),
200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1), 200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
200+(int)(cosx*0-sinx*yy2),150+(int)(sinx*0+cosx*yy2),commonData->fgcolor); 200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor);
// Inverted pointer // Inverted pointer
// Pointer as triangle with center base 2*width // Pointer as triangle with center base 2*width
float endwidth = 2; // End width of pointer float endwidth = 2; // End width of pointer
@@ -403,25 +766,25 @@ bool homevalid = false; // homelat and homelon are valid
float ix2 = -endwidth; float ix2 = -endwidth;
float iy1 = -(rInstrument * 0.5); float iy1 = -(rInstrument * 0.5);
float iy2 = -endwidth; float iy2 = -endwidth;
getdisplay().fillTriangle(200+(int)(cosx*ix1-sinx*iy1),150+(int)(sinx*ix1+cosx*iy1), getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1),
200+(int)(cosx*ix2-sinx*iy1),150+(int)(sinx*ix2+cosx*iy1), 200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1),
200+(int)(cosx*0-sinx*iy2),150+(int)(sinx*0+cosx*iy2),commonData->fgcolor); 200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor);
} }
// Draw minute pointer // Draw minute pointer
startwidth = 8; // Start width of pointer startwidth = 8; // Start width of pointer
if(valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true){ if (valid1 == true || (source == 'R' && commonData->data.rtcValid) || holdvalues == true || simulation == true) {
float sinx=sin(minute * 6.0 * pi / 180); // Minute float sinx = sin(minute * 6.0 * pi / 180); // Minute
float cosx=cos(minute * 6.0 * pi / 180); float cosx = cos(minute * 6.0 * pi / 180);
// Normal pointer // Normal pointer
// Pointer as triangle with center base 2*width // Pointer as triangle with center base 2*width
float xx1 = -startwidth; float xx1 = -startwidth;
float xx2 = startwidth; float xx2 = startwidth;
float yy1 = -startwidth; float yy1 = -startwidth;
float yy2 = -(rInstrument - 15); float yy2 = -(rInstrument - 15);
getdisplay().fillTriangle(200+(int)(cosx*xx1-sinx*yy1),150+(int)(sinx*xx1+cosx*yy1), getdisplay().fillTriangle(200 + (int)(cosx * xx1 - sinx * yy1), 150 + (int)(sinx * xx1 + cosx * yy1),
200+(int)(cosx*xx2-sinx*yy1),150+(int)(sinx*xx2+cosx*yy1), 200 + (int)(cosx * xx2 - sinx * yy1), 150 + (int)(sinx * xx2 + cosx * yy1),
200+(int)(cosx*0-sinx*yy2),150+(int)(sinx*0+cosx*yy2),commonData->fgcolor); 200 + (int)(cosx * 0 - sinx * yy2), 150 + (int)(sinx * 0 + cosx * yy2), commonData->fgcolor);
// Inverted pointer // Inverted pointer
// Pointer as triangle with center base 2*width // Pointer as triangle with center base 2*width
float endwidth = 2; // End width of pointer float endwidth = 2; // End width of pointer
@@ -429,28 +792,43 @@ bool homevalid = false; // homelat and homelon are valid
float ix2 = -endwidth; float ix2 = -endwidth;
float iy1 = -(rInstrument - 15); float iy1 = -(rInstrument - 15);
float iy2 = -endwidth; float iy2 = -endwidth;
getdisplay().fillTriangle(200+(int)(cosx*ix1-sinx*iy1),150+(int)(sinx*ix1+cosx*iy1), getdisplay().fillTriangle(200 + (int)(cosx * ix1 - sinx * iy1), 150 + (int)(sinx * ix1 + cosx * iy1),
200+(int)(cosx*ix2-sinx*iy1),150+(int)(sinx*ix2+cosx*iy1), 200 + (int)(cosx * ix2 - sinx * iy1), 150 + (int)(sinx * ix2 + cosx * iy1),
200+(int)(cosx*0-sinx*iy2),150+(int)(sinx*0+cosx*iy2),commonData->fgcolor); 200 + (int)(cosx * 0 - sinx * iy2), 150 + (int)(sinx * 0 + cosx * iy2), commonData->fgcolor);
} }
// Center circle // Center circle
getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor); getdisplay().fillCircle(200, 150, startwidth + 6, commonData->bgcolor);
getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor); getdisplay().fillCircle(200, 150, startwidth + 4, commonData->fgcolor);
}
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
}; };
static Page *createPage(CommonData &common){ // Static member definitions
bool PageClock::timerInitialized = false;
bool PageClock::timerRunning = false;
int PageClock::timerHours = 0;
int PageClock::timerMinutes = 0;
int PageClock::timerSeconds = 0;
int PageClock::timerStartHours = 0;
int PageClock::timerStartMinutes = 0;
int PageClock::timerStartSeconds = 0;
int PageClock::selectedField = 0;
bool PageClock::showSelectionMarker = true;
time_t PageClock::timerEndEpoch = 0;
static Page* createPage(CommonData& common)
{
return new PageClock(common); return new PageClock(common);
} }
/** /**
* with the code below we make this page known to the PageTask * 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 give it a type (name) that can be selected in the config
* we define which function is to be called * we define which function is to be called
* and we provide the number of user parameters we expect (0 here) * we provide the number of user parameters we expect (0 here)
* and will will provide the names of the fixed values we need * and we provide the names of the fixed values we need
*/ */
PageDescription registerPageClock( PageDescription registerPageClock(
"Clock", // Page name "Clock", // Page name
@@ -461,3 +839,4 @@ PageDescription registerPageClock(
); );
#endif #endif

View File

@@ -17,10 +17,10 @@ const int ShowSTW = 3;
const int ShowSOG = 4; const int ShowSOG = 4;
const int ShowDBS = 5; const int ShowDBS = 5;
const int Compass_X0 = 200; // center point of compass band const int Compass_X0 = 200; // X center point of compass band
const int Compass_Y0 = 220; // position of compass lines const int Compass_Y0 = 220; // Y position of compass lines
const int Compass_LineLength = 22; // length 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 float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
class PageCompass : public Page class PageCompass : public Page
{ {

View File

@@ -22,12 +22,22 @@ bool button3 = false;
bool button4 = false; bool button4 = false;
bool button5 = false; bool button5 = false;
public: public:
PageDigitalOut(CommonData &common){ PageDigitalOut(CommonData &common){
commonData = &common; commonData = &common;
common.logger->logDebug(GwLog::LOG,"Instantiate PageDigitalOut"); 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){ virtual int handleKey(int key){
// Code for keylock // Code for keylock
if(key == 11){ if(key == 11){
@@ -37,31 +47,31 @@ public:
// Code for button 1 // Code for button 1
if(key == 1){ if(key == 1){
button1 = !button1; 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 return 0; // Commit the key
} }
// Code for button 2 // Code for button 2
if(key == 2){ if(key == 2){
button2 = !button2; 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 return 0; // Commit the key
} }
// Code for button 3 // Code for button 3
if(key == 3){ if(key == 3){
button3 = !button3; 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 return 0; // Commit the key
} }
// Code for button 4 // Code for button 4
if(key == 4){ if(key == 4){
button4 = !button4; 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 return 0; // Commit the key
} }
// Code for button 5 // Code for button 5
if(key == 5){ if(key == 5){
button5 = !button5; 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 0; // Commit the key
} }
return key; return key;
@@ -77,6 +87,11 @@ public:
bool holdvalues = config->getBool(config->holdvalues); bool holdvalues = config->getBool(config->holdvalues);
String flashLED = config->getString(config->flashLED); String flashLED = config->getString(config->flashLED);
String backlightMode = config->getString(config->backlight); 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) // Optical warning by limit violation (unused)
if(String(flashLED) == "Limit Violation"){ if(String(flashLED) == "Limit Violation"){
@@ -94,17 +109,23 @@ public:
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().fillRoundRect(200, 250 , 200, 25, 5, commonData->fgcolor); // Black rect // Write text
getdisplay().fillRoundRect(202, 252 , 196, 21, 5, commonData->bgcolor); // White rect getdisplay().setCursor(100, 50 + 8);
getdisplay().setCursor(210, 270); getdisplay().print(name1);
getdisplay().print("Map server lost"); getdisplay().setCursor(100, 100 + 8);
getdisplay().print(name2);
// Set botton labels getdisplay().setCursor(100, 150 + 8);
commonData->keydata[0].label = "BTN 1"; getdisplay().print(name3);
commonData->keydata[1].label = "BTN 2"; getdisplay().setCursor(100,200 + 8);
commonData->keydata[2].label = "BTN 3"; getdisplay().print(name4);
commonData->keydata[3].label = "BTN 4"; getdisplay().setCursor(100, 250 + 8);
commonData->keydata[4].label = "BTN 5"; 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; return PAGE_UPDATE;
}; };

View File

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

View File

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

View File

@@ -19,20 +19,28 @@ bool firstRun = true; // Detect the first page run
int zoom = 15; // Default zoom level int zoom = 15; // Default zoom level
bool showValues = false; // Show values HDT, SOG, DBT in navigation map bool showValues = false; // Show values HDT, SOG, DBT in navigation map
private: private:
uint8_t* imageBackupData = nullptr; uint8_t* imageBackupData = nullptr;
int imageBackupWidth = 0; int imageBackupWidth = 0;
int imageBackupHeight = 0; int imageBackupHeight = 0;
size_t imageBackupSize = 0; size_t imageBackupSize = 0;
bool hasImageBackup = false; bool hasImageBackup = false;
public: public:
PageNavigation(CommonData &common){ PageNavigation(CommonData &common){
commonData = &common; commonData = &common;
common.logger->logDebug(GwLog::LOG,"Instantiate PageNavigation"); common.logger->logDebug(GwLog::LOG,"Instantiate PageNavigation");
imageBackupData = (uint8_t*)heap_caps_malloc((GxEPD_WIDTH * GxEPD_HEIGHT), MALLOC_CAP_SPIRAM); 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){ virtual int handleKey(int key){
// Code for keylock // Code for keylock
if(key == 11){ if(key == 11){
@@ -476,11 +484,6 @@ public:
getdisplay().print(svalue6); getdisplay().print(svalue6);
} }
// Set botton labels
commonData->keydata[0].label = "ZOOM -";
commonData->keydata[1].label = "ZOOM +";
commonData->keydata[4].label = "VALUES";
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
}; };

View File

@@ -2,113 +2,304 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "BoatDataCalibration.h" #include "OBPDataOperations.h"
#include "OBPcharts.h"
class PageOneValue : public Page class PageOneValue : public Page {
{ private:
public: GwLog* logger;
PageOneValue(CommonData &common){
commonData = &common; enum PageMode {
common.logger->logDebug(GwLog::LOG,"Instantiate PageOneValue"); VALUE,
BOTH,
CHART
};
enum DisplayMode {
FULL,
HALF
};
static constexpr char HORIZONTAL = 'H';
static constexpr char VERTICAL = 'V';
static constexpr int8_t FULL_SIZE = 0;
static constexpr int8_t HALF_SIZE_TOP = 1;
static constexpr int8_t HALF_SIZE_BOTTOM = 2;
static constexpr bool PRNT_NAME = true;
static constexpr bool NO_PRNT_NAME = false;
static constexpr bool PRNT_VALUE = true;
static constexpr bool NO_PRNT_VALUE = false;
int width; // Screen width
int height; // Screen height
bool keylock = false; // Keylock
PageMode pageMode = VALUE; // Page display mode
int8_t dataIntv = 1; // Update interval for wind history chart:
// (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart
// String lengthformat;
bool useSimuData;
bool holdValues;
String flashLED;
String backlightMode;
String tempFormat;
// Old values for hold function
String sValue1Old = "";
String unit1Old = "";
// Data buffer pointer (owned by HstryBuffers)
RingBuffer<uint16_t>* dataHstryBuf = nullptr;
std::unique_ptr<Chart> dataChart; // Chart object
// display data value in display <mode> [FULL|HALF]
void showData(GwApi::BoatValue* bValue1, DisplayMode mode)
{
int nameXoff, nameYoff, unitXoff, unitYoff, value1Xoff, value1Yoff;
const GFXfont *nameFnt, *unitFnt, *valueFnt1, *valueFnt2, *valueFnt3;
if (mode == FULL) { // full size data display
nameXoff = 0;
nameYoff = 0;
nameFnt = &Ubuntu_Bold32pt8b;
unitXoff = 0;
unitYoff = 0;
unitFnt = &Ubuntu_Bold20pt8b;
value1Xoff = 0;
value1Yoff = 0;
valueFnt1 = &Ubuntu_Bold20pt8b;
valueFnt2 = &Ubuntu_Bold32pt8b;
valueFnt3 = &DSEG7Classic_BoldItalic60pt7b;
} else { // half size data and chart display
nameXoff = -10;
nameYoff = -34;
nameFnt = &Ubuntu_Bold20pt8b;
unitXoff = -295;
unitYoff = -119;
unitFnt = &Ubuntu_Bold12pt8b;
valueFnt1 = &Ubuntu_Bold12pt8b;
value1Xoff = 153;
value1Yoff = -119;
valueFnt2 = &Ubuntu_Bold20pt8b;
valueFnt3 = &DSEG7Classic_BoldItalic42pt7b;
} }
virtual int handleKey(int key){ String name1 = xdrDelete(bValue1->getName()); // Value name
// Code for keylock name1 = name1.substring(0, 6); // String length limit for value name
if(key == 11){ 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
String unit1 = formatValue(bValue1, *commonData).unit; // Unit of value
// Show name
getdisplay().setTextColor(commonData->fgcolor);
getdisplay().setFont(nameFnt);
getdisplay().setCursor(20 + nameXoff, 100 + nameYoff);
getdisplay().print(name1); // name
// Show unit
getdisplay().setFont(unitFnt);
getdisplay().setCursor(305 + unitXoff, 240 + unitYoff);
if (holdValues) {
getdisplay().print(unit1Old); // name
} else {
getdisplay().print(unit1); // name
}
// Switch font depending on value format and adjust position
if (bValue1->getFormat() == "formatLatitude" || bValue1->getFormat() == "formatLongitude") {
getdisplay().setFont(valueFnt1);
getdisplay().setCursor(20 + value1Xoff, 180 + value1Yoff);
} else if (bValue1->getFormat() == "formatTime" || bValue1->getFormat() == "formatDate") {
getdisplay().setFont(valueFnt2);
getdisplay().setCursor(20 + value1Xoff, 200 + value1Yoff);
} else {
getdisplay().setFont(valueFnt3);
getdisplay().setCursor(20 + value1Xoff, 240 + value1Yoff);
}
// Show bus data
if (!holdValues || useSimuData) {
getdisplay().print(sValue1); // Real value as formated string
} else {
getdisplay().print(sValue1Old); // Old value as formated string
}
if (valid1 == true) {
sValue1Old = sValue1; // Save the old value
unit1Old = unit1; // Save the old unit
}
}
public:
PageOneValue(CommonData& common)
{
commonData = &common;
logger = commonData->logger;
LOG_DEBUG(GwLog::LOG, "Instantiate PageOneValue");
width = getdisplay().width(); // Screen width
height = getdisplay().height(); // Screen height
// Get config data
// lengthformat = commonData->config->getString(commonData->config->lengthFormat);
useSimuData = commonData->config->getBool(commonData->config->useSimuData);
holdValues = commonData->config->getBool(commonData->config->holdvalues);
flashLED = commonData->config->getString(commonData->config->flashLED);
backlightMode = commonData->config->getString(commonData->config->backlight);
tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F]
}
virtual void setupKeys()
{
Page::setupKeys();
#if defined BOARD_OBP60S3
constexpr int ZOOM_KEY = 4;
#elif defined BOARD_OBP40S3
constexpr int ZOOM_KEY = 1;
#endif
if (dataHstryBuf) { // show "Mode" key only if chart-supported boat data type is available
commonData->keydata[0].label = "MODE";
if (pageMode != VALUE) { // show "ZOOM" key only if chart is visible
commonData->keydata[ZOOM_KEY].label = "ZOOM";
} else {
commonData->keydata[ZOOM_KEY].label = "";
}
} else {
commonData->keydata[0].label = "";
commonData->keydata[ZOOM_KEY].label = "";
}
}
// Key functions
virtual int handleKey(int key)
{
if (dataHstryBuf) { // if boat data type supports charts
// Set page mode: value | value/half chart | full chart
if (key == 1) {
switch (pageMode) {
case VALUE:
pageMode = BOTH;
break;
case BOTH:
pageMode = CHART;
break;
case CHART:
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 chart
#if defined BOARD_OBP60S3
if (key == 5 && pageMode != VALUE) {
#elif defined BOARD_OBP40S3
if (key == 2 && pageMode != VALUE) {
#endif
if (dataIntv == 1) {
dataIntv = 2;
} else if (dataIntv == 2) {
dataIntv = 3;
} else if (dataIntv == 3) {
dataIntv = 4;
} else if (dataIntv == 4) {
dataIntv = 8;
} else {
dataIntv = 1;
}
return 0; // Commit the key
}
}
// Keylock function
if (key == 11) { // Code for keylock
commonData->keylock = !commonData->keylock; commonData->keylock = !commonData->keylock;
return 0; // Commit the key return 0; // Commit the key
} }
return key; return key;
} }
int displayPage(PageData &pageData){ virtual void displayNew(PageData& pageData)
GwConfigHandler *config = commonData->config; {
GwLog *logger = commonData->logger; #ifdef BOARD_OBP60S3
// Clear optical warning
if (flashLED == "Limit Violation") {
setBlinkingLED(false);
setFlashLED(false);
}
#endif
// buffer initialization will fail, if page is default page, because <displayNew> is not executed at system start for default page
if (!dataChart) { // Create chart objects if they don't exist
// Old values for hold function GwApi::BoatValue* bValue1 = pageData.values[0]; // Page boat data element
static String svalue1old = ""; String bValName1 = bValue1->getName(); // Value name
static String unit1old = ""; String bValFormat = bValue1->getFormat(); // Value format
// Get config data dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1);
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);
// Get boat values if (dataHstryBuf) {
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) dataChart.reset(new Chart(*dataHstryBuf, Chart::dfltChrtDta[bValFormat].range, *commonData, useSimuData));
String name1 = xdrDelete(bvalue1->getName()); // Value name LOG_DEBUG(GwLog::DEBUG, "PageOneValue: Created chart objects for %s", bValName1);
name1 = name1.substring(0, 6); // String length limit for value name } else {
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated LOG_DEBUG(GwLog::DEBUG, "PageOneValue: No chart objects available for %s", bValName1);
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
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
}
int displayPage(PageData& pageData)
{
LOG_DEBUG(GwLog::LOG, "Display PageOneValue");
// Get boat value for page
GwApi::BoatValue* bValue1 = pageData.values[0]; // Page boat data element
// Optical warning by limit violation (unused) // Optical warning by limit violation (unused)
if(String(flashLED) == "Limit Violation"){ if (String(flashLED) == "Limit Violation") {
setBlinkingLED(false); setBlinkingLED(false);
setFlashLED(false); setFlashLED(false);
} }
// Logging boat values if (bValue1 == NULL)
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement? return PAGE_OK; // no data, no page to display
LOG_DEBUG(GwLog::LOG,"Drawing at PageOneValue, %s: %f", name1.c_str(), value1);
LOG_DEBUG(GwLog::DEBUG, "PageOneValue: printing %s, %.3f", bValue1->getName().c_str(), bValue1->value);
// Draw page // Draw page
//*********************************************************** //***********************************************************
/// Set display in partial refresh mode getdisplay().setPartialWindow(0, 0, width, height); // Set partial update
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// Show name if (pageMode == VALUE || dataHstryBuf == nullptr) {
getdisplay().setTextColor(commonData->fgcolor); // show only data value; ignore other pageMode options if no chart supported boat data history buffer is available
getdisplay().setFont(&Ubuntu_Bold32pt8b); showData(bValue1, FULL);
getdisplay().setCursor(20, 100);
getdisplay().print(name1); // Page name
// Show unit } else if (pageMode == CHART) { // show only data chart
getdisplay().setFont(&Ubuntu_Bold20pt8b); if (dataChart) {
getdisplay().setCursor(270, 100); dataChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue1);
if(holdvalues == false){
getdisplay().print(unit1); // Unit
}
else{
getdisplay().print(unit1old);
} }
// Switch font if format for any values } else if (pageMode == BOTH) { // show data value and chart
if(bvalue1->getFormat() == "formatLatitude" || bvalue1->getFormat() == "formatLongitude"){ showData(bValue1, HALF);
getdisplay().setFont(&Ubuntu_Bold20pt8b); if (dataChart) {
getdisplay().setCursor(20, 180); dataChart->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue1);
} }
else if(bvalue1->getFormat() == "formatTime" || bvalue1->getFormat() == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold32pt8b);
getdisplay().setCursor(20, 200);
}
else{
getdisplay().setFont(&DSEG7Classic_BoldItalic60pt7b);
getdisplay().setCursor(20, 240);
}
// Show bus data
if(holdvalues == false){
getdisplay().print(svalue1); // Real value as formated string
}
else{
getdisplay().print(svalue1old); // Old value as formated string
}
if(valid1 == true){
svalue1old = svalue1; // Save the old value
unit1old = unit1; // Save the old unit
} }
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
}; };
static Page* createPage(CommonData &common){ static Page* createPage(CommonData& common)
{
return new PageOneValue(common); return new PageOneValue(common);
} }

View File

@@ -2,7 +2,6 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "BoatDataCalibration.h"
class PageRudderPosition : public Page class PageRudderPosition : public Page
{ {
@@ -41,7 +40,6 @@ public:
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list
String name1 = bvalue1->getName().c_str(); // Value name String name1 = bvalue1->getName().c_str(); // Value name
name1 = name1.substring(0, 6); // String length limit for value name name1 = name1.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
value1 = bvalue1->value; // Raw value without unit convertion value1 = bvalue1->value; // Raw value without unit convertion
bool valid1 = bvalue1->valid; // Valid information bool valid1 = bvalue1->valid; // Valid information
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -2,7 +2,6 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "BoatDataCalibration.h"
const int SixValues_x1 = 5; const int SixValues_x1 = 5;
const int SixValues_DeltaX = 200; const int SixValues_DeltaX = 200;
@@ -57,7 +56,6 @@ class PageSixValues : public Page
bvalue = pageData.values[i]; bvalue = pageData.values[i];
DataName[i] = xdrDelete(bvalue->getName()); DataName[i] = xdrDelete(bvalue->getName());
DataName[i] = DataName[i].substring(0, 6); // String length limit for value name DataName[i] = DataName[i].substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue, logger); // Check if boat data value is to be calibrated
DataValue[i] = bvalue->value; // Value as double in SI unit DataValue[i] = bvalue->value; // Value as double in SI unit
DataValid[i] = bvalue->valid; DataValid[i] = bvalue->valid;
DataText[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places DataText[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -15,6 +15,7 @@
#include "images/logo64.xbm" #include "images/logo64.xbm"
#include <esp32/clk.h> #include <esp32/clk.h>
#include "qrcode.h" #include "qrcode.h"
#include <vector>
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
#include "dirent.h" #include "dirent.h"
@@ -37,9 +38,11 @@ private:
String buzzer_mode; String buzzer_mode;
uint8_t buzzer_power; uint8_t buzzer_power;
String cpuspeed; String cpuspeed;
String powermode;
String rtc_module; String rtc_module;
String gps_module; String gps_module;
String env_module; String env_module;
String flashLED;
String batt_sensor; String batt_sensor;
String solar_sensor; String solar_sensor;
@@ -48,152 +51,59 @@ private:
double homelat; double homelat;
double homelon; double homelon;
char mode = 'N'; // (N)ormal, (S)ettings, (D)evice list, (C)ard char mode = 'N'; // (N)ormal, (S)ettings, (C)onfiguration, (D)evice list, c(A)rd
public: #ifdef PATCH_N2K
PageSystem(CommonData &common){ struct device {
commonData = &common; uint64_t NAME;
common.logger->logDebug(GwLog::LOG,"Instantiate PageSystem"); uint8_t id;
if (hasFRAM) { char hex_name[17];
mode = fram.read(FRAM_SYSTEM_MODE); uint16_t manuf_code;
common.logger->logDebug(GwLog::DEBUG, "Loaded mode '%c' from FRAM", mode); const char *model;
} };
chipid = ESP.getEfuseMac(); std::vector<device> devicelist;
simulation = common.config->getBool(common.config->useSimuData);
#ifdef BOARD_OBP40S3
use_sdcard = common.config->getBool(common.config->useSDCard);
#endif #endif
buzzer_mode = common.config->getString(common.config->buzzerMode);
buzzer_mode.toLowerCase();
buzzer_power = common.config->getInt(common.config->buzzerPower);
cpuspeed = common.config->getString(common.config->cpuSpeed);
env_module = common.config->getString(common.config->useEnvSensor);
rtc_module = common.config->getString(common.config->useRTC);
gps_module = common.config->getString(common.config->useGPS);
batt_sensor = common.config->getString(common.config->usePowSensor1);
solar_sensor = common.config->getString(common.config->usePowSensor2);
gen_sensor = common.config->getString(common.config->usePowSensor3);
rot_sensor = common.config->getString(common.config->useRotSensor);
homelat = common.config->getString(common.config->homeLAT).toDouble();
homelon = common.config->getString(common.config->homeLON).toDouble();
}
void setupKeys() { void incMode() {
commonData->keydata[0].label = "EXIT"; if (mode == 'N') { // Normal
commonData->keydata[1].label = "MODE";
commonData->keydata[2].label = "";
commonData->keydata[3].label = "RST";
commonData->keydata[4].label = "STBY";
commonData->keydata[5].label = "ILUM";
}
int handleKey(int key) {
// do *NOT* handle key #1 this handled by obp60task as exit
// Switch display mode
commonData->logger->logDebug(GwLog::LOG, "System keyboard handler");
if (key == 2) {
if (mode == 'N') {
mode = 'S'; mode = 'S';
} else if (mode == 'S') { } else if (mode == 'S') { // Settings
mode = 'C';
} else if (mode == 'C') { // Config
mode = 'D'; mode = 'D';
} else if (mode == 'D') { } else if (mode == 'D') { // Device list
if (hasSDCard) { if (use_sdcard) {
mode = 'A'; // SD-Card
} else {
mode = 'N';
}
} else {
mode = 'N';
}
}
void decMode() {
if (mode == 'N') {
if (use_sdcard) {
mode = 'A'; // SD-Card
} else {
mode = 'D'; // Device list
}
} else if (mode == 'S') { // Settings
mode = 'N';
} else if (mode == 'C') { // Config
mode = 'S';
} else if (mode == 'D') { // Device list
mode = 'C'; mode = 'C';
} else { } else {
mode = 'N'; mode = 'D';
}
} else {
mode = 'N';
}
if (hasFRAM) fram.write(FRAM_SYSTEM_MODE, mode);
return 0;
}
#ifdef BOARD_OBP60S3
// grab cursor key to disable page navigation
if (key == 3) {
return 0;
}
// soft reset
if (key == 4) {
ESP.restart();
}
// standby / deep sleep
if (key == 5) {
commonData->logger->logDebug(GwLog::LOG, "System going into deep sleep mode...");
deepSleep(*commonData);
}
// Code for keylock
if (key == 11) {
commonData->keylock = !commonData->keylock;
return 0;
}
#endif
#ifdef BOARD_OBP40S3
// grab cursor keys to disable page navigation
if (key == 9 or key == 10) {
return 0;
}
// standby / deep sleep
if (key == 12) {
commonData->logger->logDebug(GwLog::LOG, "System going into deep sleep mode...");
deepSleep(*commonData);
}
#endif
return key;
}
void displayBarcode(String serialno, uint16_t x, uint16_t y, uint16_t s) {
// Barcode with serial number
// x, y is top left corner
// s is pixel size of a single box
QRCode qrcode;
uint8_t qrcodeData[qrcode_getBufferSize(4)];
#ifdef BOARD_OBP40S3
String prefix = "OBP40:SN:";
#endif
#ifdef BOARD_OBP60S3
String prefix = "OBP60:SN:";
#endif
qrcode_initText(&qrcode, qrcodeData, 4, 0, (prefix + serialno).c_str());
int16_t x0 = x;
for (uint8_t j = 0; j < qrcode.size; j++) {
for (uint8_t i = 0; i < qrcode.size; i++) {
if (qrcode_getModule(&qrcode, i, j)) {
getdisplay().fillRect(x, y, s, s, commonData->fgcolor);
}
x += s;
}
y += s;
x = x0;
} }
} }
int displayPage(PageData &pageData){ void displayModeNormal() {
GwConfigHandler *config = commonData->config; // Default system page view
GwLog *logger = commonData->logger;
// Get config data uint16_t y0 = 155;
String flashLED = config->getString(config->flashLED);
// Optical warning by limit violation (unused)
if(String(flashLED) == "Limit Violation"){
setBlinkingLED(false);
setFlashLED(false);
}
// Logging boat values
logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
// Draw page
//***********************************************************
uint16_t x0 = 8; // left column
uint16_t y0 = 48; // data table starts here
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
if (mode == 'N') {
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(8, 48); getdisplay().setCursor(8, 48);
@@ -202,7 +112,6 @@ public:
getdisplay().drawXBitmap(320, 25, logo64_bits, logo64_width, logo64_height, commonData->fgcolor); getdisplay().drawXBitmap(320, 25, logo64_bits, logo64_width, logo64_height, commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
y0 = 155;
char ssid[13]; char ssid[13];
snprintf(ssid, 13, "%04X%08X", (uint16_t)(chipid >> 32), (uint32_t)chipid); snprintf(ssid, 13, "%04X%08X", (uint16_t)(chipid >> 32), (uint32_t)chipid);
@@ -315,16 +224,45 @@ public:
getdisplay().setCursor(300, y0 + 32); getdisplay().setCursor(300, y0 + 32);
getdisplay().print(String(RAM_free)); getdisplay().print(String(RAM_free));
} else if (mode == 'S') { }
// Settings
void displayModeConfig() {
// Configuration interface
uint16_t x0 = 16;
uint16_t y0 = 80;
uint16_t dy = 20;
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(8, 48);
getdisplay().print("System configuration");
getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(x0, y0);
getdisplay().print("CPU speed: 80 | 160 | 240");
getdisplay().setCursor(x0, y0 + 1 * dy);
getdisplay().print("Power mode: Max | 5V | Min");
getdisplay().setCursor(x0, y0 + 2 * dy);
getdisplay().print("Accesspoint: On | Off");
// TODO Change NVRAM-preferences settings here
getdisplay().setCursor(x0, y0 + 4 * dy);
getdisplay().print("Simulation: On | Off");
}
void displayModeSettings() {
// View some of the current settings
const uint16_t x0 = 8;
const uint16_t y0 = 72;
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(x0, 48); getdisplay().setCursor(x0, 48);
getdisplay().print("System settings"); getdisplay().print("System settings");
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
x0 = 8;
y0 = 72;
// left column // left column
getdisplay().setCursor(x0, y0); getdisplay().setCursor(x0, y0);
@@ -367,6 +305,12 @@ public:
getdisplay().setCursor(120, y0 + 144); getdisplay().setCursor(120, y0 + 144);
getdisplay().print(formatLongitude(homelon)); getdisplay().print(formatLongitude(homelon));
// Power
getdisplay().setCursor(x0, y0 + 176);
getdisplay().print("Power mode:");
getdisplay().setCursor(120, y0 + 176);
getdisplay().print(powermode);
// right column // right column
getdisplay().setCursor(202, y0); getdisplay().setCursor(202, y0);
getdisplay().print("Batt. sensor:"); getdisplay().print("Batt. sensor:");
@@ -385,18 +329,46 @@ public:
getdisplay().setCursor(320, y0 + 32); getdisplay().setCursor(320, y0 + 32);
getdisplay().print(gen_sensor); getdisplay().print(gen_sensor);
// Gyro sensor // TODO
// Gyro sensor (rotation)
getdisplay().setCursor(202, y0 + 48);
getdisplay().print("Rot. sensor:");
getdisplay().setCursor(320, y0 + 48);
getdisplay().print(rot_sensor);
// Temp.-sensor
// Power Mode
#ifdef BOARD_OBP60S3
// Backlight infos
getdisplay().setCursor(202, y0 + 64);
getdisplay().print("Backlight:");
getdisplay().setCursor(320, y0 + 64);
getdisplay().printf("%d%%", commonData->backlight.brightness);
// TODO test function with OBP60 device
getdisplay().setCursor(202, y0 + 80);
getdisplay().print("Bl color:");
getdisplay().setCursor(320, y0 + 80);
getdisplay().print(commonData->backlight.color.toName());
getdisplay().setCursor(202, y0 + 96);
getdisplay().print("Bl mode:");
getdisplay().setCursor(320, y0 + 96);
getdisplay().print(commonData->backlight.mode);
// TODO Buzzer mode and power
#endif
}
void displayModeSDCard() {
// SD Card info
uint16_t x0 = 20;
uint16_t y0 = 72;
} else if (mode == 'C') {
// Card info
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(8, 48); getdisplay().setCursor(8, 48);
getdisplay().print("SD Card info"); getdisplay().print("SD Card info");
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
x0 = 20;
y0 = 72;
getdisplay().setCursor(x0, y0); getdisplay().setCursor(x0, y0);
#ifdef BOARD_OBP60S3 #ifdef BOARD_OBP60S3
// This mode should not be callable by devices without card hardware // This mode should not be callable by devices without card hardware
@@ -423,7 +395,7 @@ public:
// Simple test for magic file in root // Simple test for magic file in root
getdisplay().setCursor(x0, y0 + 32); getdisplay().setCursor(x0, y0 + 32);
String file_magic = MOUNT_POINT "/magic.dat"; String file_magic = MOUNT_POINT "/magic.dat";
logger->logDebug(GwLog::LOG, "Test magicfile: %s", file_magic.c_str()); commonData->logger->logDebug(GwLog::LOG, "Test magicfile: %s", file_magic.c_str());
struct stat st; struct stat st;
if (stat(file_magic.c_str(), &st) == 0) { if (stat(file_magic.c_str(), &st) == 0) {
getdisplay().printf("File %s exists", file_magic.c_str()); getdisplay().printf("File %s exists", file_magic.c_str());
@@ -435,7 +407,7 @@ public:
DIR* dir = opendir(MOUNT_POINT); DIR* dir = opendir(MOUNT_POINT);
int dy = 0; int dy = 0;
if (dir != NULL) { if (dir != NULL) {
logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT); commonData->logger->logDebug(GwLog::LOG, "Root directory: %s", MOUNT_POINT);
struct dirent* entry; struct dirent* entry;
while (((entry = readdir(dir)) != NULL) and (dy < 140)) { while (((entry = readdir(dir)) != NULL) and (dy < 140)) {
getdisplay().setCursor(x0, y0 + 64 + dy); getdisplay().setCursor(x0, y0 + 64 + dy);
@@ -445,28 +417,243 @@ public:
getdisplay().print("/"); getdisplay().print("/");
} }
dy += 20; dy += 20;
logger->logDebug(GwLog::DEBUG, " %s type %d", entry->d_name, entry->d_type); commonData->logger->logDebug(GwLog::DEBUG, " %s type %d", entry->d_name, entry->d_type);
} }
closedir(dir); closedir(dir);
} else { } else {
logger->logDebug(GwLog::LOG, "Failed to open root directory"); commonData->logger->logDebug(GwLog::LOG, "Failed to open root directory");
} }
#endif #endif
}
} else { void displayModeDevicelist() {
// NMEA2000 device list // NMEA2000 device list
getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(8, 48); getdisplay().setCursor(8, 48);
getdisplay().print("NMEA2000 device list"); getdisplay().print("NMEA2000 device list");
getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setFont(&Ubuntu_Bold8pt8b);
getdisplay().setCursor(20, 80); getdisplay().setCursor(20, 70);
getdisplay().print("RxD: "); getdisplay().print("RxD: ");
getdisplay().print(String(commonData->status.n2kRx)); getdisplay().print(String(commonData->status.n2kRx));
getdisplay().setCursor(20, 100); getdisplay().setCursor(120, 70);
getdisplay().print("TxD: "); getdisplay().print("TxD: ");
getdisplay().print(String(commonData->status.n2kTx)); getdisplay().print(String(commonData->status.n2kTx));
#ifdef PATCH_N2K
uint16_t x0 = 20;
uint16_t y0 = 100;
getdisplay().setFont(&Ubuntu_Bold10pt8b);
getdisplay().setCursor(x0, y0);
getdisplay().print("ID");
getdisplay().setCursor(x0 + 50, y0);
getdisplay().print("Model");
getdisplay().setCursor(x0 + 250, y0);
getdisplay().print("Manuf.");
getdisplay().drawLine(18, y0 + 4, 360 , y0 + 4 , commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold8pt8b);
y0 = 120;
uint8_t n_dev = 0;
for (const device& item : devicelist) {
if (n_dev > 8) {
break;
}
getdisplay().setCursor(x0, y0 + n_dev * 20);
getdisplay().print(item.id);
getdisplay().setCursor(x0 + 50, y0 + n_dev * 20);
getdisplay().print(item.model);
getdisplay().setCursor(x0 + 250, y0 + n_dev * 20);
getdisplay().print(item.manuf_code);
n_dev++;
}
getdisplay().setCursor(x0, y0 + (n_dev + 1) * 20);
if (n_dev == 0) {
getdisplay().printf("no devices found on bus");
} else {
getdisplay().drawLine(18, y0 + n_dev * 20, 360 , y0 + n_dev * 20, commonData->fgcolor);
getdisplay().printf("%d devices of %d in total", n_dev, devicelist.size());
}
#else
getdisplay().setCursor(20, 100);
getdisplay().print("NMEA2000 not exposed to obp60 task");
#endif
}
public:
PageSystem(CommonData &common){
commonData = &common;
commonData->logger->logDebug(GwLog::LOG,"Instantiate PageSystem");
if (hasFRAM) {
mode = fram.read(FRAM_SYSTEM_MODE);
commonData->logger->logDebug(GwLog::DEBUG, "Loaded mode '%c' from FRAM", mode);
}
chipid = ESP.getEfuseMac();
simulation = common.config->getBool(common.config->useSimuData);
#ifdef BOARD_OBP40S3
use_sdcard = common.config->getBool(common.config->useSDCard);
#endif
buzzer_mode = common.config->getString(common.config->buzzerMode);
buzzer_mode.toLowerCase();
buzzer_power = common.config->getInt(common.config->buzzerPower);
cpuspeed = common.config->getString(common.config->cpuSpeed);
powermode = common.config->getString(common.config->powerMode);
env_module = common.config->getString(common.config->useEnvSensor);
rtc_module = common.config->getString(common.config->useRTC);
gps_module = common.config->getString(common.config->useGPS);
batt_sensor = common.config->getString(common.config->usePowSensor1);
solar_sensor = common.config->getString(common.config->usePowSensor2);
gen_sensor = common.config->getString(common.config->usePowSensor3);
rot_sensor = common.config->getString(common.config->useRotSensor);
homelat = common.config->getString(common.config->homeLAT).toDouble();
homelon = common.config->getString(common.config->homeLON).toDouble();
flashLED = common.config->getString(common.config->flashLED);
}
void setupKeys() {
commonData->keydata[0].label = "EXIT";
commonData->keydata[1].label = "MODE";
commonData->keydata[2].label = "";
commonData->keydata[3].label = "RST";
commonData->keydata[4].label = "STBY";
commonData->keydata[5].label = "ILUM";
}
int handleKey(int key) {
// do *NOT* handle key #1 this handled by obp60task as exit
// Switch display mode
commonData->logger->logDebug(GwLog::LOG, "System keyboard handler");
if (key == 2) {
incMode();
if (hasFRAM) fram.write(FRAM_SYSTEM_MODE, mode);
return 0;
}
#ifdef BOARD_OBP60S3
// grab cursor key to disable page navigation
if (key == 3) {
return 0;
}
// soft reset
if (key == 4) {
ESP.restart();
}
// standby / deep sleep
if (key == 5) {
commonData->logger->logDebug(GwLog::LOG, "System going into deep sleep mode...");
deepSleep(*commonData);
}
// Code for keylock
if (key == 11) {
commonData->keylock = !commonData->keylock;
return 0;
}
#endif
#ifdef BOARD_OBP40S3
// use cursor keys for local mode navigation
if (key == 9) {
incMode();
return 0;
}
if (key == 10) {
decMode();
return 0;
}
// standby / deep sleep
if (key == 12) {
commonData->logger->logDebug(GwLog::LOG, "System going into deep sleep mode...");
deepSleep(*commonData);
}
#endif
return key;
}
void displayBarcode(String serialno, uint16_t x, uint16_t y, uint16_t s) {
// Barcode with serial number
// x, y is top left corner
// s is pixel size of a single box
QRCode qrcode;
uint8_t qrcodeData[qrcode_getBufferSize(4)];
#ifdef BOARD_OBP40S3
String prefix = "OBP40:SN:";
#endif
#ifdef BOARD_OBP60S3
String prefix = "OBP60:SN:";
#endif
qrcode_initText(&qrcode, qrcodeData, 4, 0, (prefix + serialno).c_str());
int16_t x0 = x;
for (uint8_t j = 0; j < qrcode.size; j++) {
for (uint8_t i = 0; i < qrcode.size; i++) {
if (qrcode_getModule(&qrcode, i, j)) {
getdisplay().fillRect(x, y, s, s, commonData->fgcolor);
}
x += s;
}
y += s;
x = x0;
}
}
void displayNew(PageData &pageData) {
#ifdef BOARD_OBP60S3
// Clear optical warning
if (flashLED == "Limit Violation") {
setBlinkingLED(false);
setFlashLED(false);
}
#endif
#ifdef PATCH_N2K
// load current device list
tN2kDeviceList *pDevList = pageData.api->getN2kDeviceList();
// TODO check if changed
if (pDevList->ReadResetIsListUpdated()) {
// only reload if changed
devicelist.clear();
for (uint8_t i = 0; i <= 252; i++) {
const tNMEA2000::tDevice *d = pDevList->FindDeviceBySource(i);
if (d == nullptr) {
continue;
}
device dev;
dev.id = i;
dev.NAME = d->GetName();
snprintf(dev.hex_name, sizeof(dev.hex_name), "%08X%08X", (uint32_t)(dev.NAME >> 32), (uint32_t)(dev.NAME & 0xFFFFFFFF));
dev.manuf_code = d->GetManufacturerCode();
dev.model = d->GetModelID();
devicelist.push_back(dev);
}
}
#endif
};
int displayPage(PageData &pageData){
// Logging page information
commonData->logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode);
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// call current system page
switch (mode) {
case 'N':
displayModeNormal();
break;
case 'S':
displayModeSettings();
break;
case 'C':
displayModeConfig();
break;
case 'A':
displayModeSDCard();
break;
case 'D':
displayModeDevicelist();
break;
} }
// Update display // Update display

View File

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

View File

@@ -2,177 +2,331 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "BoatDataCalibration.h" #include "OBPDataOperations.h"
#include "OBPcharts.h"
class PageTwoValues : public Page class PageTwoValues : public Page {
{ private:
public: GwLog* logger;
PageTwoValues(CommonData &common){
commonData = &common; enum PageMode {
common.logger->logDebug(GwLog::LOG,"Instantiate PageTwoValue"); VALUES,
VAL1_CHART,
VAL2_CHART,
CHARTS
};
enum DisplayMode {
FULL,
HALF
};
static constexpr char HORIZONTAL = 'H';
static constexpr char VERTICAL = 'V';
static constexpr int8_t FULL_SIZE = 0;
static constexpr int8_t HALF_SIZE_TOP = 1;
static constexpr int8_t HALF_SIZE_BOTTOM = 2;
static constexpr bool PRNT_NAME = true;
static constexpr bool NO_PRNT_NAME = false;
static constexpr bool PRNT_VALUE = true;
static constexpr bool NO_PRNT_VALUE = false;
static constexpr int YOFFSET = 130; // y offset for display of 2nd boat value
int width; // Screen width
int height; // Screen height
bool keylock = false; // Keylock
PageMode pageMode = VALUES; // Page display mode
int8_t dataIntv = 1; // Update interval for wind history chart:
// (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart
// String lengthformat;
bool useSimuData;
bool holdValues;
String flashLED;
String backlightMode;
String tempFormat;
// Data buffer pointer (owned by HstryBuffers)
static constexpr int NUMVALUES = 2; // two data values in this page
RingBuffer<uint16_t>* dataHstryBuf[NUMVALUES] = { nullptr };
std::unique_ptr<Chart> dataChart[NUMVALUES]; // Chart object
// Old values for hold function
String sValueOld[NUMVALUES] = { "", "" };
String unitOld[NUMVALUES] = { "", "" };
// display data values in display <mode> [FULL|HALF]
void showData(const std::vector<GwApi::BoatValue*>& bValue, DisplayMode mode)
{
getdisplay().setTextColor(commonData->fgcolor);
int numValues = bValue.size(); // do we have to handle 1 or 2 values?
for (int i = 0; i < numValues; i++) {
int yOffset = YOFFSET * i;
String name = xdrDelete(bValue[i]->getName()); // Value name
name = name.substring(0, 6); // String length limit for value name
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
String unit = formatValue(bValue[i], *commonData).unit; // Unit of value
// Show name
getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(20, 75 + yOffset);
getdisplay().print(name); // name
// Show unit
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(20, 125 + yOffset);
if (holdValues) {
getdisplay().print(unitOld[i]); // name
} else {
getdisplay().print(unit); // name
} }
virtual int handleKey(int key){ // Switch font depending on value format and adjust position
// Code for keylock if (bValue[i]->getFormat() == "formatLatitude" || bValue[i]->getFormat() == "formatLongitude") {
if(key == 11){ getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(50, 125 + yOffset);
} else if (bValue[i]->getFormat() == "formatTime" || bValue[i]->getFormat() == "formatDate") {
getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(170, 105 + yOffset);
} else { // Default font for other formats
getdisplay().setFont(&DSEG7Classic_BoldItalic42pt7b);
getdisplay().setCursor(180, 125 + yOffset);
}
// Show bus data
if (!holdValues || useSimuData) {
getdisplay().print(sValue); // Real value as formated string
} else {
getdisplay().print(sValueOld[i]); // Old value as formated string
}
if (valid == true) {
sValueOld[i] = sValue; // Save the old value
unitOld[i] = unit; // Save the old unit
}
}
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
}
}
public:
PageTwoValues(CommonData& common)
{
commonData = &common;
logger = commonData->logger;
LOG_DEBUG(GwLog::LOG, "Instantiate PageTwoValues");
width = getdisplay().width(); // Screen width
height = getdisplay().height(); // Screen height
// Get config data
// lengthformat = commonData->config->getString(commonData->config->lengthFormat);
useSimuData = commonData->config->getBool(commonData->config->useSimuData);
holdValues = commonData->config->getBool(commonData->config->holdvalues);
flashLED = commonData->config->getString(commonData->config->flashLED);
backlightMode = commonData->config->getString(commonData->config->backlight);
tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F]
}
virtual void setupKeys()
{
Page::setupKeys();
#if defined BOARD_OBP60S3
constexpr int ZOOM_KEY = 4;
#elif defined BOARD_OBP40S3
constexpr int ZOOM_KEY = 1;
#endif
if (dataHstryBuf[0] || dataHstryBuf[1]) { // show "Mode" key only if at least 1 chart supported boat data type is available
commonData->keydata[0].label = "MODE";
if (pageMode != VALUES) { // show "ZOOM" key only if chart is visible
commonData->keydata[ZOOM_KEY].label = "ZOOM";
} else {
commonData->keydata[ZOOM_KEY].label = "";
}
} else {
commonData->keydata[0].label = "";
commonData->keydata[ZOOM_KEY].label = "";
}
}
// Key functions
virtual int handleKey(int key)
{
if (dataHstryBuf[0] || dataHstryBuf[1]) { // if at least 1 boat data type supports charts
// Set page mode: value | value/half chart | full charts
if (key == 1) {
switch (pageMode) {
case VALUES:
if (dataHstryBuf[0]) {
pageMode = VAL1_CHART;
} else if (dataHstryBuf[1]) {
pageMode = VAL2_CHART;
}
break;
case VAL1_CHART:
if (dataHstryBuf[1]) {
pageMode = VAL2_CHART;
} else {
pageMode = CHARTS;
}
break;
case VAL2_CHART:
pageMode = CHARTS;
break;
case CHARTS:
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 chart
#if defined BOARD_OBP60S3
if (key == 5 && pageMode != VALUES) {
#elif defined BOARD_OBP40S3
if (key == 2 && pageMode != VALUES) {
#endif
if (dataIntv == 1) {
dataIntv = 2;
} else if (dataIntv == 2) {
dataIntv = 3;
} else if (dataIntv == 3) {
dataIntv = 4;
} else if (dataIntv == 4) {
dataIntv = 8;
} else {
dataIntv = 1;
}
return 0; // Commit the key
}
}
// Keylock function
if (key == 11) { // Code for keylock
commonData->keylock = !commonData->keylock; commonData->keylock = !commonData->keylock;
return 0; // Commit the key return 0; // Commit the key
} }
return key; return key;
} }
int displayPage(PageData &pageData){ virtual void displayNew(PageData& pageData)
GwConfigHandler *config = commonData->config; {
GwLog *logger = commonData->logger; #ifdef BOARD_OBP60S3
// Clear optical warning
if (flashLED == "Limit Violation") {
setBlinkingLED(false);
setFlashLED(false);
}
#endif
// buffer initialization will fail, if page is default page, because <displayNew> is not executed at system start for default page
for (int i = 0; i < NUMVALUES; i++) {
if (!dataChart[i]) { // Create chart objects if they don't exist
// Old values for hold function GwApi::BoatValue* bValue = pageData.values[i]; // Page boat data element
static String svalue1old = ""; String bValName = bValue->getName(); // Value name
static String unit1old = ""; String bValFormat = bValue->getFormat(); // Value format
static String svalue2old = "";
static String unit2old = "";
// Get config data dataHstryBuf[i] = pageData.hstryBuffers->getBuffer(bValName);
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);
// Get boat values #1 if (dataHstryBuf[i]) {
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) dataChart[i].reset(new Chart(*dataHstryBuf[i], Chart::dfltChrtDta[bValFormat].range, *commonData, useSimuData));
String name1 = xdrDelete(bvalue1->getName()); // Value name LOG_DEBUG(GwLog::DEBUG, "PageTwoValues: Created chart object%d for %s", i, bValName.c_str());
name1 = name1.substring(0, 6); // String length limit for value name } else {
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated LOG_DEBUG(GwLog::DEBUG, "PageTwoValues: No chart object available for %s", bValName.c_str());
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 }
String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value
// Get boat values #2 setupKeys(); // Adjust key definition depending on <pageMode> and chart-supported boat data type
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 int displayPage(PageData& pageData)
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated {
double value2 = bvalue2->value; // Value as double in SI unit LOG_DEBUG(GwLog::LOG, "Display PageTwoValues");
bool valid2 = bvalue2->valid; // Valid information
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places // Get boat values for page
String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value std::vector<GwApi::BoatValue*> bValue;
bValue.push_back(pageData.values[0]); // Page boat data element 1
bValue.push_back(pageData.values[1]); // Page boat data element 2
// Optical warning by limit violation (unused) // Optical warning by limit violation (unused)
if(String(flashLED) == "Limit Violation"){ if (String(flashLED) == "Limit Violation") {
setBlinkingLED(false); setBlinkingLED(false);
setFlashLED(false); setFlashLED(false);
} }
// Logging boat values if (bValue[0] == NULL && bValue[1] == NULL)
if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement? return PAGE_OK; // no data, no page to display
LOG_DEBUG(GwLog::LOG,"Drawing at PageTwoValues, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2);
LOG_DEBUG(GwLog::DEBUG, "PageTwoValues: printing #1: %s, %.3f, #2: %s, %.3f",
bValue[0]->getName().c_str(), bValue[0]->value, bValue[1]->getName().c_str(), bValue[1]->value);
// Draw page // Draw page
//*********************************************************** //***********************************************************
// Set display in partial refresh mode getdisplay().setPartialWindow(0, 0, width, height); // Set partial update
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
// ############### Value 1 ################ if (pageMode == VALUES || (dataHstryBuf[0] == nullptr && dataHstryBuf[1] == nullptr)) {
// show only data value; ignore other pageMode options if no chart supported boat data history buffer is available
showData(bValue, FULL);
// Show name } else if (pageMode == VAL1_CHART) { // show data value 1 and chart
getdisplay().setTextColor(commonData->fgcolor); showData({ bValue[0] }, HALF);
getdisplay().setFont(&Ubuntu_Bold20pt8b); if (dataChart[0]) {
getdisplay().setCursor(20, 80); dataChart[0]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[0]);
getdisplay().print(name1); // Page name
// Show unit
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(20, 130);
if(holdvalues == false){
getdisplay().print(unit1); // Unit
}
else{
getdisplay().print(unit1old);
} }
// Switch font if format for any values } else if (pageMode == VAL2_CHART) { // show data value 2 and chart
if(bvalue1->getFormat() == "formatLatitude" || bvalue1->getFormat() == "formatLongitude"){ showData({ bValue[1] }, HALF);
getdisplay().setFont(&Ubuntu_Bold20pt8b); if (dataChart[1]) {
getdisplay().setCursor(50, 130); dataChart[1]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[1]);
}
else if(bvalue1->getFormat() == "formatTime" || bvalue1->getFormat() == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(170, 105);
}
else{
getdisplay().setFont(&DSEG7Classic_BoldItalic42pt7b);
getdisplay().setCursor(180, 130);
} }
// Show bus data } else if (pageMode == CHARTS) { // show both data charts
if(holdvalues == false){ if (dataChart[0]) {
getdisplay().print(svalue1); // Real value as formated string if (dataChart[1]) {
dataChart[0]->showChrt(HORIZONTAL, HALF_SIZE_TOP, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[0]);
} else {
dataChart[0]->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[0]);
} }
else{
getdisplay().print(svalue1old); // Old value as formated string
} }
if(valid1 == true){ if (dataChart[1]) {
svalue1old = svalue1; // Save the old value if (dataChart[0]) {
unit1old = unit1; // Save the old unit dataChart[1]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[1]);
} else {
dataChart[1]->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[1]);
} }
// ############### Horizontal Line ################
// Horizontal line 3 pix
getdisplay().fillRect(0, 145, 400, 3, commonData->fgcolor);
// ############### Value 2 ################
// Show name
getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(20, 190);
getdisplay().print(name2); // Page name
// Show unit
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(20, 240);
if(holdvalues == false){
getdisplay().print(unit2); // Unit
} }
else{
getdisplay().print(unit2old);
}
// Switch font if format for any values
if(bvalue2->getFormat() == "formatLatitude" || bvalue2->getFormat() == "formatLongitude"){
getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(50, 240);
}
else if(bvalue2->getFormat() == "formatTime" || bvalue2->getFormat() == "formatDate"){
getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(170, 215);
}
else{
getdisplay().setFont(&DSEG7Classic_BoldItalic42pt7b);
getdisplay().setCursor(180, 240);
}
// Show bus data
if(holdvalues == false){
getdisplay().print(svalue2); // Real value as formated string
}
else{
getdisplay().print(svalue2old); // Old value as formated string
}
if(valid2 == true){
svalue2old = svalue2; // Save the old value
unit2old = unit2; // Save the old unit
} }
return PAGE_UPDATE; return PAGE_UPDATE;
}; };
}; };
static Page *createPage(CommonData &common){ static Page* createPage(CommonData& common)
{
return new PageTwoValues(common); return new PageTwoValues(common);
} }
/** /**
* with the code below we make this page known to the PageTask * 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 give it a type (name) that can be selected in the config

View File

@@ -3,7 +3,6 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "N2kMessages.h" #include "N2kMessages.h"
#include "BoatDataCalibration.h"
#define front_width 120 #define front_width 120
#define front_height 162 #define front_height 162
@@ -324,7 +323,6 @@ public:
} }
String name1 = bvalue1->getName().c_str(); // Value name String name1 = bvalue1->getName().c_str(); // Value name
name1 = name1.substring(0, 6); // String length limit for value name name1 = name1.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
double value1 = bvalue1->value; // Value as double in SI unit double value1 = bvalue1->value; // Value as double in SI unit
// bool valid1 = bvalue1->valid; // Valid information // bool valid1 = bvalue1->valid; // Valid information
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places 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 String name2 = bvalue2->getName().c_str(); // Value name
name2 = name2.substring(0, 6); // String length limit for value name name2 = name2.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
double value2 = bvalue2->value; // Value as double in SI unit double value2 = bvalue2->value; // Value as double in SI unit
// bool valid2 = bvalue2->valid; // Valid information // bool valid2 = bvalue2->valid; // Valid information
if (simulation) { if (simulation) {

View File

@@ -2,6 +2,7 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "OBPDataOperations.h"
#include "OBPcharts.h" #include "OBPcharts.h"
// **************************************************************** // ****************************************************************
@@ -10,20 +11,58 @@ class PageWindPlot : public Page {
private: private:
GwLog* logger; GwLog* logger;
enum ChartMode {
DIRECTION,
SPEED,
BOTH
};
static constexpr char HORIZONTAL = 'H';
static constexpr char VERTICAL = 'V';
static constexpr int8_t FULL_SIZE = 0;
static constexpr int8_t HALF_SIZE_LEFT = 1;
static constexpr int8_t HALF_SIZE_RIGHT = 2;
static constexpr bool PRNT_NAME = true;
static constexpr bool NO_PRNT_NAME = false;
static constexpr bool PRNT_VALUE = true;
static constexpr bool NO_PRNT_VALUE = false;
int width; // Screen width int width; // Screen width
int height; // Screen height int height; // Screen height
bool keylock = false; // Keylock bool keylock = false; // Keylock
char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both ChartMode chrtMode = DIRECTION;
bool showTruW = true; // Show true wind or apparent wind in chart area bool showTruW = true; // Show true wind or apparent wind in chart area
bool oldShowTruW = false; // remember recent user selection of wind data type bool oldShowTruW = false; // remember recent user selection of wind data type
int dataIntv = 1; // Update interval for wind history chart: int8_t dataIntv = 1; // Update interval for wind history chart:
// (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart // (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart
bool useSimuData; bool useSimuData;
// bool holdValues;
String flashLED; String flashLED;
String backlightMode; String backlightMode;
#ifdef BOARD_OBP40S3
String wndSrc; // Wind source true/apparent wind - preselection for OBP40
#endif
// Data buffers pointers (owned by HstryBuffers)
RingBuffer<uint16_t>* twdHstry = nullptr;
RingBuffer<uint16_t>* twsHstry = nullptr;
RingBuffer<uint16_t>* awdHstry = nullptr;
RingBuffer<uint16_t>* awsHstry = nullptr;
// Chart objects
std::unique_ptr<Chart> twdChart, awdChart; // Chart object for wind direction
std::unique_ptr<Chart> twsChart, awsChart; // Chart object for wind speed
// Active charts and values
Chart* wdChart = nullptr;
Chart* wsChart = nullptr;
GwApi::BoatValue* wdBVal = nullptr;
GwApi::BoatValue* wsBVal = nullptr;
public: public:
PageWindPlot(CommonData& common) PageWindPlot(CommonData& common)
{ {
@@ -31,11 +70,16 @@ public:
logger = commonData->logger; logger = commonData->logger;
LOG_DEBUG(GwLog::LOG, "Instantiate PageWindPlot"); LOG_DEBUG(GwLog::LOG, "Instantiate PageWindPlot");
width = getdisplay().width(); // Screen width
height = getdisplay().height(); // Screen height
// Get config data // Get config data
useSimuData = common.config->getBool(common.config->useSimuData); useSimuData = common.config->getBool(common.config->useSimuData);
// holdValues = common.config->getBool(common.config->holdvalues); // holdValues = common.config->getBool(common.config->holdvalues);
flashLED = common.config->getString(common.config->flashLED); flashLED = common.config->getString(common.config->flashLED);
backlightMode = common.config->getString(common.config->backlight); backlightMode = common.config->getString(common.config->backlight);
oldShowTruW = !showTruW; // makes wind source being initialized at initial page call
} }
virtual void setupKeys() virtual void setupKeys()
@@ -44,23 +88,23 @@ public:
commonData->keydata[0].label = "MODE"; commonData->keydata[0].label = "MODE";
#if defined BOARD_OBP60S3 #if defined BOARD_OBP60S3
commonData->keydata[1].label = "SRC"; commonData->keydata[1].label = "SRC";
commonData->keydata[4].label = "INTV"; commonData->keydata[4].label = "ZOOM";
#elif defined BOARD_OBP40S3 #elif defined BOARD_OBP40S3
commonData->keydata[1].label = "INTV"; commonData->keydata[1].label = "ZOOM";
#endif #endif
} }
// Key functions // Key functions
virtual int handleKey(int key) virtual int handleKey(int key)
{ {
// Set chart mode TWD | TWS // Set chart mode
if (key == 1) { if (key == 1) {
if (chrtMode == 'D') { if (chrtMode == DIRECTION) {
chrtMode = 'S'; chrtMode = SPEED;
} else if (chrtMode == 'S') { } else if (chrtMode == SPEED) {
chrtMode = 'B'; chrtMode = BOTH;
} else { } else {
chrtMode = 'D'; chrtMode = DIRECTION;
} }
return 0; // Commit the key return 0; // Commit the key
} }
@@ -101,116 +145,77 @@ public:
virtual void displayNew(PageData& pageData) virtual void displayNew(PageData& pageData)
{ {
#ifdef BOARD_OBP60S3
// Clear optical warning
if (flashLED == "Limit Violation") {
setBlinkingLED(false);
setFlashLED(false);
}
#endif
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
String wndSrc; // Wind source true/apparent wind - preselection for OBP40 // we can only initialize user defined wind source here, because "pageData" is not available at object instantiation
wndSrc = commonData->config->getString("page" + String(pageData.pageNumber) + "wndsrc"); wndSrc = commonData->config->getString("page" + String(pageData.pageNumber) + "wndsrc");
if (wndSrc == "True wind") { if (wndSrc == "True wind") {
showTruW = true; showTruW = true;
} else { } else {
showTruW = false; // Wind source is apparent wind showTruW = false; // Wind source is apparent wind
} }
LOG_DEBUG(GwLog::LOG, "New PageWindPlot; wind source=%s", wndSrc); oldShowTruW = !showTruW; // Force chart update in displayPage
#endif #endif
oldShowTruW = !showTruW; // makes wind source being initialized at initial page call
width = getdisplay().width(); // Screen width if (!twdChart) { // Create true wind charts if they don't exist
height = getdisplay().height(); // Screen height twdHstry = pageData.hstryBuffers->getBuffer("TWD");
twsHstry = pageData.hstryBuffers->getBuffer("TWS");
if (twdHstry) {
twdChart.reset(new Chart(*twdHstry, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData));
}
if (twsHstry) {
twsChart.reset(new Chart(*twsHstry, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData));
}
}
if (!awdChart) { // Create apparent wind charts if they don't exist
awdHstry = pageData.hstryBuffers->getBuffer("AWD");
awsHstry = pageData.hstryBuffers->getBuffer("AWS");
if (awdHstry) {
awdChart.reset(new Chart(*awdHstry, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData));
}
if (awsHstry) {
awsChart.reset(new Chart(*awsHstry, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData));
}
if (twdHstry && twsHstry && awdHstry && awsHstry) {
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Created wind charts");
} else {
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Some/all chart objects for wind data missing");
}
}
} }
int displayPage(PageData& pageData) int displayPage(PageData& pageData)
{ {
GwConfigHandler* config = commonData->config;
static RingBuffer<uint16_t>* wdHstry; // Wind direction data buffer
static RingBuffer<uint16_t>* wsHstry; // Wind speed data buffer
static String wdName, wdFormat; // Wind direction name and format
static String wsName, wsFormat; // Wind speed name and format
// Separate chart objects for true wind and apparent wind
static std::unique_ptr<Chart<uint16_t>> twdFlChart, awdFlChart; // chart object for wind direction chart, full size
static std::unique_ptr<Chart<uint16_t>> twsFlChart, awsFlChart; // chart object for wind speed chart, full size
static std::unique_ptr<Chart<uint16_t>> twdHfChart, awdHfChart; // chart object for wind direction chart, half size
static std::unique_ptr<Chart<uint16_t>> twsHfChart, awsHfChart; // chart object for wind speed chart, half size
// Pointers to the currently active charts
static Chart<uint16_t>* wdFlChart;
static Chart<uint16_t>* wsFlChart;
static Chart<uint16_t>* wdHfChart;
static Chart<uint16_t>* wsHfChart;
static GwApi::BoatValue* wdBVal = new GwApi::BoatValue("TWD"); // temp BoatValue for wind direction unit identification; required by OBP60Formater
static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater */
double dfltRngWd = 60.0 * DEG_TO_RAD; // default range for course chart from min to max value in RAD
double dfltRngWs = 7.5; // default range for wind speed chart from min to max value in m/s
const int numBoatData = 4;
GwApi::BoatValue* bvalue[numBoatData]; // current boat data values
LOG_DEBUG(GwLog::LOG, "Display PageWindPlot"); LOG_DEBUG(GwLog::LOG, "Display PageWindPlot");
ulong pageTime = millis(); // ulong pageTime = millis();
// read boat data values
for (int i = 0; i < numBoatData; i++) {
bvalue[i] = pageData.values[i];
}
// Optical warning by limit violation (unused)
if (String(flashLED) == "Limit Violation") {
setBlinkingLED(false);
setFlashLED(false);
}
if (showTruW != oldShowTruW) { if (showTruW != oldShowTruW) {
if (!twdFlChart) { // Create true wind charts if they don't exist
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating true wind charts");
auto* twdHstry = pageData.boatHstry->hstryBufList.twdHstry;
auto* twsHstry = pageData.boatHstry->hstryBufList.twsHstry;
// LOG_DEBUG(GwLog::DEBUG,"History Buffer addresses PageWindPlot: twdBuf: %p, twsBuf: %p", (void*)pageData.boatHstry->hstryBufList.twdHstry,
// (void*)pageData.boatHstry->hstryBufList.twsHstry);
twdFlChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*twdHstry, 1, 0, dfltRngWd, *commonData, useSimuData));
twsFlChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*twsHstry, 0, 0, dfltRngWs, *commonData, useSimuData));
twdHfChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*twdHstry, 1, 1, dfltRngWd, *commonData, useSimuData));
twsHfChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*twsHstry, 1, 2, dfltRngWs, *commonData, useSimuData));
// twdHfChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*twdHstry, 0, 1, dfltRngWd, *commonData, useSimuData));
// twsHfChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*twsHstry, 0, 2, dfltRngWs, *commonData, useSimuData));
// LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: twdHstry: %p, twsHstry: %p", (void*)twdHstry, (void*)twsHstry);
}
if (!awdFlChart) { // Create apparent wind charts if they don't exist
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating apparent wind charts");
auto* awdHstry = pageData.boatHstry->hstryBufList.awdHstry;
auto* awsHstry = pageData.boatHstry->hstryBufList.awsHstry;
awdFlChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*awdHstry, 1, 0, dfltRngWd, *commonData, useSimuData));
awsFlChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*awsHstry, 0, 0, dfltRngWs, *commonData, useSimuData));
awdHfChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*awdHstry, 1, 1, dfltRngWd, *commonData, useSimuData));
awsHfChart = std::unique_ptr<Chart<uint16_t>>(new Chart<uint16_t>(*awsHstry, 1, 2, dfltRngWs, *commonData, useSimuData));
}
// Switch active charts based on showTruW // Switch active charts based on showTruW
if (showTruW) { if (showTruW) {
wdHstry = pageData.boatHstry->hstryBufList.twdHstry; wdChart = twdChart.get();
wsHstry = pageData.boatHstry->hstryBufList.twsHstry; wsChart = twsChart.get();
wdFlChart = twdFlChart.get(); wdBVal = pageData.values[0];
wsFlChart = twsFlChart.get(); wsBVal = pageData.values[1];
wdHfChart = twdHfChart.get();
wsHfChart = twsHfChart.get();
} else { } else {
wdHstry = pageData.boatHstry->hstryBufList.awdHstry; wdChart = awdChart.get();
wsHstry = pageData.boatHstry->hstryBufList.awsHstry; wsChart = awsChart.get();
wdFlChart = awdFlChart.get(); wdBVal = pageData.values[2];
wsFlChart = awsFlChart.get(); wsBVal = pageData.values[3];
wdHfChart = awdHfChart.get();
wsHfChart = awsHfChart.get();
} }
wdHstry->getMetaData(wdName, wdFormat);
wsHstry->getMetaData(wsName, wsFormat);
oldShowTruW = showTruW; oldShowTruW = showTruW;
} }
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: draw with data %s: %.2f, %s: %.2f", wdBVal->getName().c_str(), wdBVal->value, wsBVal->getName().c_str(), wsBVal->value);
// Draw page // Draw page
//*********************************************************** //***********************************************************
@@ -219,28 +224,26 @@ public:
getdisplay().setPartialWindow(0, 0, width, height); // Set partial update getdisplay().setPartialWindow(0, 0, width, height); // Set partial update
getdisplay().setTextColor(commonData->fgcolor); getdisplay().setTextColor(commonData->fgcolor);
if (chrtMode == 'D') { if (chrtMode == DIRECTION) {
wdBVal->value = wdHstry->getLast(); if (wdChart) {
wdBVal->valid = wdBVal->value != wdHstry->getMaxVal(); wdChart->showChrt(VERTICAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *wdBVal);
wdFlChart->showChrt(dataIntv, *bvalue[0]);
} else if (chrtMode == 'S') {
wsBVal->value = wsHstry->getLast();
wsBVal->valid = wsBVal->value != wsHstry->getMaxVal();
wsFlChart->showChrt(dataIntv, *bvalue[1]);
} else if (chrtMode == 'B') {
wdBVal->value = wdHstry->getLast();
wdBVal->valid = wdBVal->value != wdHstry->getMaxVal();
wsBVal->value = wsHstry->getLast();
wsBVal->valid = wsBVal->value != wsHstry->getMaxVal();
LOG_DEBUG(GwLog::DEBUG, "PageWindPlot showChrt: wsBVal.name: %s, format: %s, wsBVal.value: %.1f, valid: %d, address: %p", wsBVal->getName(), wsBVal->getFormat(), wsBVal->value,
wsBVal->valid, wsBVal);
wdHfChart->showChrt(dataIntv, *bvalue[0]);
wsHfChart->showChrt(dataIntv, *bvalue[1]);
} }
LOG_DEBUG(GwLog::LOG, "PageWindPlot: page time %ldms", millis() - pageTime); } else if (chrtMode == SPEED) {
if (wsChart) {
wsChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *wsBVal);
}
} else if (chrtMode == BOTH) {
if (wdChart) {
wdChart->showChrt(VERTICAL, HALF_SIZE_LEFT, dataIntv, PRNT_NAME, PRNT_VALUE, *wdBVal);
}
if (wsChart) {
wsChart->showChrt(VERTICAL, HALF_SIZE_RIGHT, dataIntv, PRNT_NAME, PRNT_VALUE, *wsBVal);
}
}
// LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: page time %ldms", millis() - pageTime);
return PAGE_UPDATE; return PAGE_UPDATE;
} }
}; };

View File

@@ -2,7 +2,6 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "BoatDataCalibration.h"
class PageWindRose : public Page class PageWindRose : public Page
{ {
@@ -52,7 +51,6 @@ public:
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
String name1 = xdrDelete(bvalue1->getName()); // Value name String name1 = xdrDelete(bvalue1->getName()); // Value name
name1 = name1.substring(0, 6); // String length limit for value name name1 = name1.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
double value1 = bvalue1->value; // Value as double in SI unit double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information bool valid1 = bvalue1->valid; // Valid information
value1 = formatValue(bvalue1, *commonData).value;// Format only nesaccery for simulation data for pointer 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 GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
String name2 = xdrDelete(bvalue2->getName()); // Value name String name2 = xdrDelete(bvalue2->getName()); // Value name
name2 = name2.substring(0, 6); // String length limit for value name name2 = name2.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
double value2 = bvalue2->value; // Value as double in SI unit double value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information bool valid2 = bvalue2->valid; // Valid information
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places 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 GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
String name3 = xdrDelete(bvalue3->getName()); // Value name String name3 = xdrDelete(bvalue3->getName()); // Value name
name3 = name3.substring(0, 6); // String length limit for value name name3 = name3.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
double value3 = bvalue3->value; // Value as double in SI unit double value3 = bvalue3->value; // Value as double in SI unit
bool valid3 = bvalue3->valid; // Valid information bool valid3 = bvalue3->valid; // Valid information
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places 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 GwApi::BoatValue *bvalue4 = pageData.values[3]; // Fourth element in list
String name4 = xdrDelete(bvalue4->getName()); // Value name String name4 = xdrDelete(bvalue4->getName()); // Value name
name4 = name4.substring(0, 6); // String length limit for value name name4 = name4.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
double value4 = bvalue4->value; // Value as double in SI unit double value4 = bvalue4->value; // Value as double in SI unit
bool valid4 = bvalue4->valid; // Valid information bool valid4 = bvalue4->valid; // Valid information
String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places 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 GwApi::BoatValue *bvalue5 = pageData.values[4]; // Fifth element in list
String name5 = xdrDelete(bvalue5->getName()); // Value name String name5 = xdrDelete(bvalue5->getName()); // Value name
name5 = name5.substring(0, 6); // String length limit for value name name5 = name5.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
double value5 = bvalue5->value; // Value as double in SI unit double value5 = bvalue5->value; // Value as double in SI unit
bool valid5 = bvalue5->valid; // Valid information bool valid5 = bvalue5->valid; // Valid information
String svalue5 = formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places 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 GwApi::BoatValue *bvalue6 = pageData.values[5]; // Sixth element in list
String name6 = xdrDelete(bvalue6->getName()); // Value name String name6 = xdrDelete(bvalue6->getName()); // Value name
name6 = name6.substring(0, 6); // String length limit for value name name6 = name6.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
double value6 = bvalue6->value; // Value as double in SI unit double value6 = bvalue6->value; // Value as double in SI unit
bool valid6 = bvalue6->valid; // Valid information bool valid6 = bvalue6->valid; // Valid information
String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -2,7 +2,6 @@
#include "Pagedata.h" #include "Pagedata.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "BoatDataCalibration.h"
class PageWindRoseFlex : public Page class PageWindRoseFlex : public Page
{ {
@@ -79,7 +78,6 @@ public:
} }
String name1 = bvalue1->getName().c_str(); // Value name String name1 = bvalue1->getName().c_str(); // Value name
name1 = name1.substring(0, 6); // String length limit for value name name1 = name1.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
double value1 = bvalue1->value; // Value as double in SI unit double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information bool valid1 = bvalue1->valid; // Valid information
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places 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 String name2 = bvalue2->getName().c_str(); // Value name
name2 = name2.substring(0, 6); // String length limit for value name name2 = name2.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
double value2 = bvalue2->value; // Value as double in SI unit double value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information bool valid2 = bvalue2->valid; // Valid information
if (simulation) { if (simulation) {
@@ -122,7 +119,6 @@ public:
else{ else{
name3font=Ubuntu_Bold12pt8b; 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 double value3 = bvalue3->value; // Value as double in SI unit
bool valid3 = bvalue3->valid; // Valid information bool valid3 = bvalue3->valid; // Valid information
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -142,7 +138,6 @@ public:
else{ else{
name4font=Ubuntu_Bold12pt8b; 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 double value4 = bvalue4->value; // Value as double in SI unit
bool valid4 = bvalue4->valid; // Valid information bool valid4 = bvalue4->valid; // Valid information
String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -162,7 +157,6 @@ public:
else{ else{
name5font=Ubuntu_Bold12pt8b; 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 double value5 = bvalue5->value; // Value as double in SI unit
bool valid5 = bvalue5->valid; // Valid information bool valid5 = bvalue5->valid; // Valid information
String svalue5 = formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue5 = formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -182,7 +176,6 @@ public:
else{ else{
name6font=Ubuntu_Bold8pt8b; 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 double value6 = bvalue6->value; // Value as double in SI unit
bool valid6 = bvalue6->valid; // Valid information bool valid6 = bvalue6->valid; // Valid information
String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -4,19 +4,20 @@
#include <functional> #include <functional>
#include <vector> #include <vector>
#include "LedSpiTask.h" #include "LedSpiTask.h"
#include "OBPDataOperations.h"
#define MAX_PAGE_NUMBER 10 // Max number of pages for show data #define MAX_PAGE_NUMBER 10 // Max number of pages for show data
typedef std::vector<GwApi::BoatValue *> ValueList; typedef std::vector<GwApi::BoatValue *> ValueList;
class HstryBuffers;
typedef struct{ typedef struct{
GwApi *api; GwApi *api;
String pageName; String pageName;
uint8_t pageNumber; // page number in sequence of visible pages uint8_t pageNumber; // page number in sequence of visible pages
//the values will always contain the user defined values first //the values will always contain the user defined values first
ValueList values; ValueList values;
HstryBuf* boatHstry; HstryBuffers* hstryBuffers; // list of all boat history buffers
} PageData; } PageData;
// Sensor data structure (only for extended sensors, not for NMEA bus sensors) // Sensor data structure (only for extended sensors, not for NMEA bus sensors)
@@ -50,7 +51,7 @@ typedef struct{
double rotationAngle = 0; // Rotation angle in radiant double rotationAngle = 0; // Rotation angle in radiant
bool validRotAngle = false; // Valid flag magnet present for rotation sensor bool validRotAngle = false; // Valid flag magnet present for rotation sensor
struct tm rtcTime; // UTC time from internal RTC struct tm rtcTime; // UTC time from internal RTC
bool rtcValid = false; bool rtcValid = false; // Internal RTC chip
int sunsetHour = 0; int sunsetHour = 0;
int sunsetMinute = 0; int sunsetMinute = 0;
int sunriseHour = 0; int sunriseHour = 0;
@@ -203,3 +204,8 @@ typedef struct{
// Formatter for boat values // Formatter for boat values
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata); FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata);
FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool ignoreSimuDataSetting);
// Helper method for conversion of any data value from SI to user defined format (defined in OBP60Formatter)
double convertValue(const double &value, const String &format, CommonData &commondata);
double convertValue(const double &value, const String &name, const String &format, CommonData &commondata);

View File

@@ -672,6 +672,61 @@
"obp40": "true" "obp40": "true"
} }
}, },
{
"name": "mod1Out1",
"label": "Name1",
"type": "string",
"default": "text1",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp40":"true"
}
},
{
"name": "mod1Out2",
"label": "Name2",
"type": "string",
"default": "text2",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp40":"true"
}
},
{
"name": "mod1Out3",
"label": "Name3",
"type": "string",
"default": "text3",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp40":"true"
}
},
{
"name": "mod1Out4",
"label": "Name4",
"type": "string",
"default": "text4",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp40":"true"
}
},
{
"name": "mod1Out5",
"label": "Name5",
"type": "string",
"default": "text5",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp40":"true"
}
},
{ {
"name": "tSensitivity", "name": "tSensitivity",
"label": "Touch Sensitivity [%]", "label": "Touch Sensitivity [%]",
@@ -719,8 +774,10 @@
"AWA", "AWA",
"AWS", "AWS",
"COG", "COG",
"DBS",
"DBT", "DBT",
"HDM", "HDM",
"HDT",
"PRPOS", "PRPOS",
"RPOS", "RPOS",
"SOG", "SOG",
@@ -746,7 +803,7 @@
"obp40":"true" "obp40":"true"
}, },
"condition": [ "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 +811,13 @@
"label": "Data Instance 1 Calibration Slope", "label": "Data Instance 1 Calibration Slope",
"type": "number", "type": "number",
"default": "1.00", "default": "1.00",
"description": "Slope for data instance 1", "description": "Slope for data instance 1, Default: 1(!)",
"category": "OBP40 Calibrations", "category": "OBP40 Calibrations",
"capabilities": { "capabilities": {
"obp40":"true" "obp40":"true"
}, },
"condition": [ "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 +834,7 @@
"obp40":"true" "obp40":"true"
}, },
"condition": [ "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 +848,10 @@
"AWA", "AWA",
"AWS", "AWS",
"COG", "COG",
"DBS",
"DBT", "DBT",
"HDM", "HDM",
"HDT",
"PRPOS", "PRPOS",
"RPOS", "RPOS",
"SOG", "SOG",
@@ -818,7 +877,7 @@
"obp40":"true" "obp40":"true"
}, },
"condition": [ "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 +885,13 @@
"label": "Data Instance 2 Calibration Slope", "label": "Data Instance 2 Calibration Slope",
"type": "number", "type": "number",
"default": "1.00", "default": "1.00",
"description": "Slope for data instance 2", "description": "Slope for data instance 2; Default: 1(!)",
"category": "OBP40 Calibrations", "category": "OBP40 Calibrations",
"capabilities": { "capabilities": {
"obp40":"true" "obp40":"true"
}, },
"condition": [ "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 +908,7 @@
"obp40":"true" "obp40":"true"
}, },
"condition": [ "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 +922,10 @@
"AWA", "AWA",
"AWS", "AWS",
"COG", "COG",
"DBS",
"DBT", "DBT",
"HDM", "HDM",
"HDT",
"PRPOS", "PRPOS",
"RPOS", "RPOS",
"SOG", "SOG",
@@ -890,7 +951,7 @@
"obp40":"true" "obp40":"true"
}, },
"condition": [ "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 +959,13 @@
"label": "Data Instance 3 Calibration Slope", "label": "Data Instance 3 Calibration Slope",
"type": "number", "type": "number",
"default": "1.00", "default": "1.00",
"description": "Slope for data instance 3", "description": "Slope for data instance 3, Default: 1(!)",
"category": "OBP40 Calibrations", "category": "OBP40 Calibrations",
"capabilities": { "capabilities": {
"obp40":"true" "obp40":"true"
}, },
"condition": [ "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 +982,81 @@
"obp40":"true" "obp40":"true"
}, },
"condition": [ "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" ] }
] ]
}, },
{ {
@@ -1110,8 +1245,8 @@
"name": "timeSource", "name": "timeSource",
"label": "Status Time Source", "label": "Status Time Source",
"type": "list", "type": "list",
"default": "GPS", "default": "iRTC",
"description": "Data source for date and time display in status line [RTC|iRTC|GPS]", "description": "Data source for date and time display in status line [iRTC|RTC|GPS]",
"list": [ "list": [
{"l":"Internal real time clock (iRTC)","v":"iRTC"}, {"l":"Internal real time clock (iRTC)","v":"iRTC"},
{"l":"External real time clock (RTC)","v":"RTC"}, {"l":"External real time clock (RTC)","v":"RTC"},
@@ -1230,9 +1365,9 @@
"type": "number", "type": "number",
"default": "50", "default": "50",
"check": "checkMinMax", "check": "checkMinMax",
"min": 20, "min": 5,
"max": 100, "max": 100,
"description": "Backlight brightness [20...100%]", "description": "Backlight brightness [5...100%]",
"category": "OBP40 Display", "category": "OBP40 Display",
"capabilities": { "capabilities": {
"obp40": "false" "obp40": "false"
@@ -1382,6 +1517,7 @@
"default": "Voltage", "default": "Voltage",
"description": "Type of page for page 1", "description": "Type of page for page 1",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -1683,7 +1819,7 @@
"description": "Wind source for page 1: [true|apparent]", "description": "Wind source for page 1: [true|apparent]",
"list": [ "list": [
"True wind", "True wind",
"apparent wind" "Apparent wind"
], ],
"category": "OBP40 Page 1", "category": "OBP40 Page 1",
"capabilities": { "capabilities": {
@@ -1712,6 +1848,7 @@
"default": "WindRose", "default": "WindRose",
"description": "Type of page for page 2", "description": "Type of page for page 2",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -2005,7 +2142,7 @@
"description": "Wind source for page 2: [true|apparent]", "description": "Wind source for page 2: [true|apparent]",
"list": [ "list": [
"True wind", "True wind",
"apparent wind" "Apparent wind"
], ],
"category": "OBP40 Page 2", "category": "OBP40 Page 2",
"capabilities": { "capabilities": {
@@ -2033,6 +2170,7 @@
"default": "OneValue", "default": "OneValue",
"description": "Type of page for page 3", "description": "Type of page for page 3",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -2318,7 +2456,7 @@
"description": "Wind source for page 3: [true|apparent]", "description": "Wind source for page 3: [true|apparent]",
"list": [ "list": [
"True wind", "True wind",
"apparent wind" "Apparent wind"
], ],
"category": "OBP40 Page 3", "category": "OBP40 Page 3",
"capabilities": { "capabilities": {
@@ -2345,6 +2483,7 @@
"default": "TwoValues", "default": "TwoValues",
"description": "Type of page for page 4", "description": "Type of page for page 4",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -2622,7 +2761,7 @@
"description": "Wind source for page 4: [true|apparent]", "description": "Wind source for page 4: [true|apparent]",
"list": [ "list": [
"True wind", "True wind",
"apparent wind" "Apparent wind"
], ],
"category": "OBP40 Page 4", "category": "OBP40 Page 4",
"capabilities": { "capabilities": {
@@ -2648,6 +2787,7 @@
"default": "ThreeValues", "default": "ThreeValues",
"description": "Type of page for page 5", "description": "Type of page for page 5",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -2917,7 +3057,7 @@
"description": "Wind source for page 5: [true|apparent]", "description": "Wind source for page 5: [true|apparent]",
"list": [ "list": [
"True wind", "True wind",
"apparent wind" "Apparent wind"
], ],
"category": "OBP40 Page 5", "category": "OBP40 Page 5",
"capabilities": { "capabilities": {
@@ -2942,6 +3082,7 @@
"default": "FourValues", "default": "FourValues",
"description": "Type of page for page 6", "description": "Type of page for page 6",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -3203,7 +3344,7 @@
"description": "Wind source for page 6: [true|apparent]", "description": "Wind source for page 6: [true|apparent]",
"list": [ "list": [
"True wind", "True wind",
"apparent wind" "Apparent wind"
], ],
"category": "OBP40 Page 6", "category": "OBP40 Page 6",
"capabilities": { "capabilities": {
@@ -3227,6 +3368,7 @@
"default": "FourValues2", "default": "FourValues2",
"description": "Type of page for page 7", "description": "Type of page for page 7",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -3480,7 +3622,7 @@
"description": "Wind source for page 7: [true|apparent]", "description": "Wind source for page 7: [true|apparent]",
"list": [ "list": [
"True wind", "True wind",
"apparent wind" "Apparent wind"
], ],
"category": "OBP40 Page 7", "category": "OBP40 Page 7",
"capabilities": { "capabilities": {
@@ -3503,6 +3645,7 @@
"default": "Clock", "default": "Clock",
"description": "Type of page for page 8", "description": "Type of page for page 8",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -3748,7 +3891,7 @@
"description": "Wind source for page 8: [true|apparent]", "description": "Wind source for page 8: [true|apparent]",
"list": [ "list": [
"True wind", "True wind",
"apparent wind" "Apparent wind"
], ],
"category": "OBP40 Page 8", "category": "OBP40 Page 8",
"capabilities": { "capabilities": {
@@ -3770,6 +3913,7 @@
"default": "RollPitch", "default": "RollPitch",
"description": "Type of page for page 9", "description": "Type of page for page 9",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -4007,7 +4151,7 @@
"description": "Wind source for page 9: [true|apparent]", "description": "Wind source for page 9: [true|apparent]",
"list": [ "list": [
"True wind", "True wind",
"apparent wind" "Apparent wind"
], ],
"category": "OBP40 Page 9", "category": "OBP40 Page 9",
"capabilities": { "capabilities": {
@@ -4028,6 +4172,7 @@
"default": "Battery2", "default": "Battery2",
"description": "Type of page for page 10", "description": "Type of page for page 10",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -4257,7 +4402,7 @@
"description": "Wind source for page 10: [true|apparent]", "description": "Wind source for page 10: [true|apparent]",
"list": [ "list": [
"True wind", "True wind",
"apparent wind" "Apparent wind"
], ],
"category": "OBP40 Page 10", "category": "OBP40 Page 10",
"capabilities": { "capabilities": {

View File

@@ -37,7 +37,7 @@
"name": "homeLAT", "name": "homeLAT",
"label": "Home latitude", "label": "Home latitude",
"type": "number", "type": "number",
"default": "", "default": "0.00000",
"check": "checkMinMax", "check": "checkMinMax",
"min": -90.0, "min": -90.0,
"max": 90.0, "max": 90.0,
@@ -51,7 +51,7 @@
"name": "homeLON", "name": "homeLON",
"label": "Home longitude", "label": "Home longitude",
"type": "number", "type": "number",
"default": "", "default": "0.00000",
"check": "checkMinMax", "check": "checkMinMax",
"min": -180.0, "min": -180.0,
"max": 180.0, "max": 180.0,
@@ -661,6 +661,61 @@
"obp60":"true" "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", "name": "tSensitivity",
"label": "Touch Sensitivity [%]", "label": "Touch Sensitivity [%]",
@@ -708,8 +763,10 @@
"AWA", "AWA",
"AWS", "AWS",
"COG", "COG",
"DBS",
"DBT", "DBT",
"HDM", "HDM",
"HDT",
"PRPOS", "PRPOS",
"RPOS", "RPOS",
"SOG", "SOG",
@@ -735,7 +792,7 @@
"obp60":"true" "obp60":"true"
}, },
"condition": [ "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 +800,13 @@
"label": "Data Instance 1 Calibration Slope", "label": "Data Instance 1 Calibration Slope",
"type": "number", "type": "number",
"default": "1.00", "default": "1.00",
"description": "Slope for data instance 1", "description": "Slope for data instance 1; Default: 1(!)",
"category": "OBP60 Calibrations", "category": "OBP60 Calibrations",
"capabilities": { "capabilities": {
"obp60":"true" "obp60":"true"
}, },
"condition": [ "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 +823,7 @@
"obp60":"true" "obp60":"true"
}, },
"condition": [ "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 +837,10 @@
"AWA", "AWA",
"AWS", "AWS",
"COG", "COG",
"DBS",
"DBT", "DBT",
"HDM", "HDM",
"HDT",
"PRPOS", "PRPOS",
"RPOS", "RPOS",
"SOG", "SOG",
@@ -807,7 +866,7 @@
"obp60":"true" "obp60":"true"
}, },
"condition": [ "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 +874,13 @@
"label": "Data Instance 2 Calibration Slope", "label": "Data Instance 2 Calibration Slope",
"type": "number", "type": "number",
"default": "1.00", "default": "1.00",
"description": "Slope for data instance 2", "description": "Slope for data instance 2; Default: 1(!)",
"category": "OBP60 Calibrations", "category": "OBP60 Calibrations",
"capabilities": { "capabilities": {
"obp60":"true" "obp60":"true"
}, },
"condition": [ "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 +897,7 @@
"obp60":"true" "obp60":"true"
}, },
"condition": [ "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 +911,10 @@
"AWA", "AWA",
"AWS", "AWS",
"COG", "COG",
"DBS",
"DBT", "DBT",
"HDM", "HDM",
"HDT",
"PRPOS", "PRPOS",
"RPOS", "RPOS",
"SOG", "SOG",
@@ -879,7 +940,7 @@
"obp60":"true" "obp60":"true"
}, },
"condition": [ "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 +948,13 @@
"label": "Data Instance 3 Calibration Slope", "label": "Data Instance 3 Calibration Slope",
"type": "number", "type": "number",
"default": "1.00", "default": "1.00",
"description": "Slope for data instance 3", "description": "Slope for data instance 3; Default: 1(!)",
"category": "OBP60 Calibrations", "category": "OBP60 Calibrations",
"capabilities": { "capabilities": {
"obp60":"true" "obp60":"true"
}, },
"condition": [ "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 +971,81 @@
"obp60":"true" "obp60":"true"
}, },
"condition": [ "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" ] }
] ]
}, },
{ {
@@ -1100,8 +1235,9 @@
"label": "Status Time Source", "label": "Status Time Source",
"type": "list", "type": "list",
"default": "GPS", "default": "GPS",
"description": "Data source for date and time display in status line [RTC|GPS]", "description": "Data source for date and time display in status line [iRTC|RTC|GPS]",
"list": [ "list": [
{"l":"Internal real time clock (iRTC)","v":"iRTC"},
{"l":"Real time clock (RTC)","v":"RTC"}, {"l":"Real time clock (RTC)","v":"RTC"},
{"l":"Time via bus (GPS)","v":"GPS"} {"l":"Time via bus (GPS)","v":"GPS"}
], ],
@@ -1218,9 +1354,9 @@
"type": "number", "type": "number",
"default": "50", "default": "50",
"check": "checkMinMax", "check": "checkMinMax",
"min": 20, "min": 5,
"max": 100, "max": 100,
"description": "Backlight brightness [20...100%]", "description": "Backlight brightness [5...100%]",
"category": "OBP60 Display", "category": "OBP60 Display",
"capabilities": { "capabilities": {
"obp60":"true" "obp60":"true"
@@ -1359,6 +1495,7 @@
"default": "Voltage", "default": "Voltage",
"description": "Type of page for page 1", "description": "Type of page for page 1",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -1659,6 +1796,7 @@
"default": "WindRose", "default": "WindRose",
"description": "Type of page for page 2", "description": "Type of page for page 2",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -1951,6 +2089,7 @@
"default": "OneValue", "default": "OneValue",
"description": "Type of page for page 3", "description": "Type of page for page 3",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -2235,6 +2374,7 @@
"default": "TwoValues", "default": "TwoValues",
"description": "Type of page for page 4", "description": "Type of page for page 4",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -2511,6 +2651,7 @@
"default": "ThreeValues", "default": "ThreeValues",
"description": "Type of page for page 5", "description": "Type of page for page 5",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -2779,6 +2920,7 @@
"default": "FourValues", "default": "FourValues",
"description": "Type of page for page 6", "description": "Type of page for page 6",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -3039,6 +3181,7 @@
"default": "FourValues2", "default": "FourValues2",
"description": "Type of page for page 7", "description": "Type of page for page 7",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -3291,6 +3434,7 @@
"default": "Clock", "default": "Clock",
"description": "Type of page for page 8", "description": "Type of page for page 8",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -3535,6 +3679,7 @@
"default": "RollPitch", "default": "RollPitch",
"description": "Type of page for page 9", "description": "Type of page for page 9",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",
@@ -3771,6 +3916,7 @@
"default": "Battery2", "default": "Battery2",
"description": "Type of page for page 10", "description": "Type of page for page 10",
"list": [ "list": [
"Autopilot",
"BME280", "BME280",
"Battery", "Battery",
"Battery2", "Battery2",

View File

@@ -1,12 +1,29 @@
# PlatformIO extra script for obp60task # PlatformIO extra script for obp60task
import subprocess
def cleanup_patches(source, target, env):
for p in patchfiles:
patch = os.path.join(patchdir, p)
print(f"removing {patch}")
res = subprocess.run(["git", "apply", "-R", patch], capture_output=True, text=True)
if res.returncode != 0:
print(res.stderr)
patching = False
epdtype = "unknown" epdtype = "unknown"
pcbvers = "unknown" pcbvers = "unknown"
for x in env["BUILD_FLAGS"]: 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] pcbvers = x.split('_')[1]
if x.startswith("-D DISPLAY_"): elif opt.startswith("DISPLAY_"):
epdtype = x.split('_')[1] epdtype = x.split('_')[1]
elif opt == 'ENABLE_PATCHES':
patching = True
propfilename = os.path.join(env["PROJECT_LIBDEPS_DIR"], env["PIOENV"], "GxEPD2/library.properties") propfilename = os.path.join(env["PROJECT_LIBDEPS_DIR"], env["PIOENV"], "GxEPD2/library.properties")
properties = {} properties = {}
@@ -28,3 +45,22 @@ except:
env["CPPDEFINES"].extend([("BOARD", env["BOARD"]), ("EPDTYPE", epdtype), ("PCBVERS", pcbvers), ("GXEPD2VERS", gxepd2vers)]) env["CPPDEFINES"].extend([("BOARD", env["BOARD"]), ("EPDTYPE", epdtype), ("PCBVERS", pcbvers), ("GXEPD2VERS", gxepd2vers)])
print("added hardware info to CPPDEFINES") print("added hardware info to CPPDEFINES")
if patching:
# apply patches to gateway code
print("applying gateway patches")
patchdir = os.path.join(os.path.dirname(script), "patches")
if not os.path.isdir(patchdir):
print("patchdir not found, no patches applied")
else:
patchfiles = [f for f in os.listdir(patchdir)]
if len(patchfiles) > 0:
for p in patchfiles:
patch = os.path.join(patchdir, p)
print(f"applying {patch}")
res = subprocess.run(["git", "apply", patch], capture_output=True, text=True)
if res.returncode != 0:
print(res.stderr)
env.AddPostAction("$PROGPATH", cleanup_patches)
else:
print("no patches found")

View File

@@ -12,7 +12,6 @@
#include <GxEPD2_BW.h> // GxEPD2 lib for b/w E-Ink displays #include <GxEPD2_BW.h> // GxEPD2 lib for b/w E-Ink displays
#include "OBP60Extensions.h" // Functions lib for extension board #include "OBP60Extensions.h" // Functions lib for extension board
#include "OBP60Keypad.h" // Functions for keypad #include "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 #include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
@@ -147,7 +146,6 @@ void keyboardTask(void *param){
vTaskDelete(NULL); vTaskDelete(NULL);
} }
// Scorgan: moved class declaration to header file <obp60task.h> to make class available to other functions
// --- Class BoatValueList -------------- // --- Class BoatValueList --------------
bool BoatValueList::addValueToList(GwApi::BoatValue *v){ bool BoatValueList::addValueToList(GwApi::BoatValue *v){
for (int i=0;i<numValues;i++){ for (int i=0;i<numValues;i++){
@@ -172,7 +170,7 @@ GwApi::BoatValue *BoatValueList::findValueOrCreate(String name){
addValueToList(rt); addValueToList(rt);
return rt; return rt;
} }
// --- Class BoatValueList -------------- // --- End Class BoatValueList --------------
//we want to have a list that has all our page definitions //we want to have a list that has all our page definitions
//this way each page can easily be added here //this way each page can easily be added here
@@ -264,6 +262,8 @@ void registerAllPages(PageList &list){
list.add(&registerPageNavigation); list.add(&registerPageNavigation);
extern PageDescription registerPageDigitalOut; extern PageDescription registerPageDigitalOut;
list.add(&registerPageDigitalOut); list.add(&registerPageDigitalOut);
extern PageDescription registerPageAutopilot;
list.add(&registerPageAutopilot);
} }
// Undervoltage detection for shutdown display // Undervoltage detection for shutdown display
@@ -332,7 +332,7 @@ void OBP60Task(GwApi *api){
// return; // return;
GwLog *logger=api->getLogger(); GwLog *logger=api->getLogger();
GwConfigHandler *config=api->getConfig(); GwConfigHandler *config=api->getConfig();
#ifdef HARDWARE_V21 #if defined HARDWARE_V20 || HARDWARE_V21
startLedTask(api); startLedTask(api);
#endif #endif
PageList allPages; PageList allPages;
@@ -341,7 +341,7 @@ void OBP60Task(GwApi *api){
commonData.logger=logger; commonData.logger=logger;
commonData.config=config; commonData.config=config;
#ifdef HARDWARE_V21 #if defined HARDWARE_V20 || HARDWARE_V21
// Keyboard coordinates for page footer // Keyboard coordinates for page footer
initKeys(commonData); initKeys(commonData);
#endif #endif
@@ -432,13 +432,12 @@ void OBP60Task(GwApi *api){
#endif #endif
LOG_DEBUG(GwLog::LOG,"...done"); LOG_DEBUG(GwLog::LOG,"...done");
int lastPage=pageNumber; int lastPage=-1; // initialize with an impossible value, so we can detect wether we are during startup and no page has been displayed yet
BoatValueList boatValues; //all the boat values for the api query BoatValueList boatValues; //all the boat values for the api query
HstryBuf hstryBufList(1920); // Create ring buffers for history storage of some boat data (1920 seconds = 32 minutes) HstryBuffers hstryBufferList(1920, &boatValues, logger); // Create empty list of boat data history buffers (1.920 values = seconds = 32 min.)
WindUtils trueWind(&boatValues); // Create helper object for true wind calculation WindUtils trueWind(&boatValues, logger); // Create helper object for true wind calculation
//commonData.distanceformat=config->getString(xxx); CalibrationData calibrationDataList(logger); // all boat data types which are supposed to be calibrated
//add all necessary data to common data
//fill the page data from config //fill the page data from config
numPages=config->getInt(config->visiblePages,1); numPages=config->getInt(config->visiblePages,1);
@@ -479,21 +478,26 @@ void OBP60Task(GwApi *api){
LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i); LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i);
pages[i].parameters.values.push_back(value); pages[i].parameters.values.push_back(value);
} }
// Add boat history data to page parameters
pages[i].parameters.boatHstry = &hstryBufList; // 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) {
hstryBufferList.addBuffer(pVal->getName());
} }
}
// Add list of history buffers to page parameters
pages[i].parameters.hstryBuffers = &hstryBufferList;
}
// add out of band system page (always available) // add out of band system page (always available)
Page *syspage = allPages.pages[0]->creator(commonData); Page *syspage = allPages.pages[0]->creator(commonData);
// Read all calibration data settings from config // Read user settings from config file
calibrationData.readConfig(config, logger);
// Check user settings for true wind calculation
bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false); bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false);
bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false); bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false);
// Read user calibration data settings from config file
// Initialize history buffer for certain boat data calibrationDataList.readConfig(config);
hstryBufList.init(&boatValues, logger);
// Display screenshot handler for HTTP request // Display screenshot handler for HTTP request
// http://192.168.15.1/api/user/OBP60Task/screenshot // http://192.168.15.1/api/user/OBP60Task/screenshot
@@ -525,7 +529,7 @@ void OBP60Task(GwApi *api){
commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight,true)->asString()); commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight,true)->asString());
commonData.backlight.color = colorMapping(config->getConfigItem(config->blColor,true)->asString()); commonData.backlight.color = colorMapping(config->getConfigItem(config->blColor,true)->asString());
commonData.backlight.brightness = 2.55 * uint(config->getConfigItem(config->blBrightness,true)->asInt()); commonData.backlight.brightness = uint(config->getConfigItem(config->blBrightness,true)->asInt());
commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString(); commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString();
bool uvoltage = config->getConfigItem(config->underVoltage, true)->asBoolean(); bool uvoltage = config->getConfigItem(config->underVoltage, true)->asBoolean();
@@ -652,7 +656,7 @@ void OBP60Task(GwApi *api){
// if(String(backlight) == "Control by Key"){ // if(String(backlight) == "Control by Key"){
if(keyboardMessage == 6){ if(keyboardMessage == 6){
LOG_DEBUG(GwLog::LOG,"Toggle Backlight LED"); LOG_DEBUG(GwLog::LOG,"Toggle Backlight LED");
toggleBacklightLED(commonData.backlight.brightness, commonData.backlight.color); stepsBacklightLED(commonData.backlight.brightness, commonData.backlight.color);
} }
} }
#ifdef BOARD_OBP40S3 #ifdef BOARD_OBP40S3
@@ -725,7 +729,7 @@ void OBP60Task(GwApi *api){
else{ else{
getdisplay().fillScreen(commonData.fgcolor); // Clear display getdisplay().fillScreen(commonData.fgcolor); // Clear display
#ifdef DISPLAY_GDEY042T81 #ifdef DISPLAY_GDEY042T81
getdisplay().hibernate(); // Set display in hybenate mode getdisplay().hibernate(); // Set display in hibenate mode
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
#else #else
getdisplay().init(115200); // Init for normal displays getdisplay().init(115200); // Init for normal displays
@@ -753,7 +757,7 @@ void OBP60Task(GwApi *api){
else{ else{
getdisplay().fillScreen(commonData.fgcolor); // Clear display getdisplay().fillScreen(commonData.fgcolor); // Clear display
#ifdef DISPLAY_GDEY042T81 #ifdef DISPLAY_GDEY042T81
getdisplay().hibernate(); // Set display in hybenate mode getdisplay().hibernate(); // Set display in hibernate mode
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
#else #else
getdisplay().init(115200); // Init for normal displays getdisplay().init(115200); // Init for normal displays
@@ -778,7 +782,7 @@ void OBP60Task(GwApi *api){
else{ else{
getdisplay().fillScreen(commonData.fgcolor); // Clear display getdisplay().fillScreen(commonData.fgcolor); // Clear display
#ifdef DISPLAY_GDEY042T81 #ifdef DISPLAY_GDEY042T81
getdisplay().hibernate(); // Set display in hybenate mode getdisplay().hibernate(); // Set display in hibernate mode
getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse
#else #else
getdisplay().init(115200); // Init for normal displays getdisplay().init(115200); // Init for normal displays
@@ -808,10 +812,10 @@ void OBP60Task(GwApi *api){
api->getStatus(commonData.status); api->getStatus(commonData.status);
if (calcTrueWnds) { if (calcTrueWnds) {
trueWind.addTrueWind(api, &boatValues, logger); trueWind.addWinds(); // calculate true wind data from apparent wind values
} }
// Handle history buffers for certain boat data for windplot page and other usage calibrationDataList.handleCalibration(&boatValues); // Process calibration for all boat data in <calibrationDataList>
hstryBufList.handleHstryBuf(useSimuData); hstryBufferList.handleHstryBufs(useSimuData, commonData); // Handle history buffers for certain boat data for windplot page and other usage
// Clear display // Clear display
// getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor); // getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);
@@ -848,8 +852,10 @@ void OBP60Task(GwApi *api){
} }
else{ else{
if (lastPage != pageNumber){ if (lastPage != pageNumber){
if (lastPage != -1){ // skip cleanup if we are during startup, and no page has been displayed yet.
pages[lastPage].page->leavePage(pages[lastPage].parameters); // call page cleanup code pages[lastPage].page->leavePage(pages[lastPage].parameters); // call page cleanup code
if (hasFRAM) fram.write(FRAM_PAGE_NO, pageNumber); // remember new page for device restart if (hasFRAM) fram.write(FRAM_PAGE_NO, pageNumber); // remember new page for device restart
}
currentPage->setupKeys(); currentPage->setupKeys();
currentPage->displayNew(pages[pageNumber].parameters); currentPage->displayNew(pages[pageNumber].parameters);
lastPage = pageNumber; lastPage = pageNumber;

View File

@@ -0,0 +1,103 @@
diff --git a/lib/api/GwApi.h b/lib/api/GwApi.h
index 88f9690..9663a65 100644
--- a/lib/api/GwApi.h
+++ b/lib/api/GwApi.h
@@ -2,6 +2,8 @@
#define _GWAPI_H
#include "GwMessage.h"
#include "N2kMsg.h"
+#include "Nmea2kTwai.h"
+#include "N2kDeviceList.h"
#include "NMEA0183Msg.h"
#include "GWConfig.h"
#include "GwBoatData.h"
@@ -222,6 +224,8 @@ class GwApi{
* accessing boat data must only be executed from within the main thread
* you need to use the request pattern as shown in GwExampleTask.cpp
*/
+ virtual Nmea2kTwai *getNMEA2000()=0;
+ virtual tN2kDeviceList *getN2kDeviceList()=0;
virtual GwBoatData *getBoatData()=0;
virtual ~GwApi(){}
};
diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h
index 604c356..2fe4496 100644
--- a/lib/obp60task/OBP60Extensions.h
+++ b/lib/obp60task/OBP60Extensions.h
@@ -15,6 +15,9 @@
#define MOUNT_POINT "/sdcard"
#endif
+// Patches to apply to gateway code
+#define PATCH_N2K
+
// FRAM address reservations 32kB: 0x0000 - 0x7FFF
// 0x0000 - 0x03ff: single variables
#define FRAM_PAGE_NO 0x0002
diff --git a/lib/usercode/GwUserCode.cpp b/lib/usercode/GwUserCode.cpp
index 1b007f8..90087d4 100644
--- a/lib/usercode/GwUserCode.cpp
+++ b/lib/usercode/GwUserCode.cpp
@@ -216,6 +216,14 @@ public:
{
return api->getLogger();
}
+ virtual Nmea2kTwai *getNMEA2000()
+ {
+ return api->getNMEA2000();
+ }
+ virtual tN2kDeviceList *getN2kDeviceList()
+ {
+ return api->getN2kDeviceList();
+ }
virtual GwBoatData *getBoatData()
{
return api->getBoatData();
@@ -428,4 +436,4 @@ void GwUserCode::handleWebRequest(const String &url,AsyncWebServerRequest *req){
}
LOG_DEBUG(GwLog::DEBUG,"no task found for web request %s[%s]",url.c_str(),tname.c_str());
req->send(404, "text/plain", "not found");
-}
\ No newline at end of file
+}
diff --git a/src/main.cpp b/src/main.cpp
index 44c715f..fdb0366 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -100,6 +100,7 @@ GwLog logger(LOGLEVEL,NULL);
GwConfigHandler config(&logger);
#include "Nmea2kTwai.h"
+#include <N2kDeviceList.h>
static const unsigned long CAN_RECOVERY_PERIOD=3000; //ms
static const unsigned long NMEA2000_HEARTBEAT_INTERVAL=5000;
class Nmea2kTwaiLog : public Nmea2kTwai{
@@ -126,6 +127,7 @@ class Nmea2kTwaiLog : public Nmea2kTwai{
#endif
Nmea2kTwai &NMEA2000=*(new Nmea2kTwaiLog((gpio_num_t)ESP32_CAN_TX_PIN,(gpio_num_t)ESP32_CAN_RX_PIN,CAN_RECOVERY_PERIOD,&logger));
+tN2kDeviceList *pN2kDeviceList;
#ifdef GWBUTTON_PIN
bool fixedApPass=false;
@@ -333,6 +335,12 @@ public:
status.n2kTx=countNMEA2KOut.getGlobal();
channels.fillStatus(status);
}
+ virtual Nmea2kTwai *getNMEA2000(){
+ return &NMEA2000;
+ }
+ virtual tN2kDeviceList *getN2kDeviceList(){
+ return pN2kDeviceList;
+ }
virtual GwBoatData *getBoatData(){
return &boatData;
}
@@ -935,6 +943,7 @@ void setup() {
NMEA2000.SetMsgHandler([](const tN2kMsg &n2kMsg){
handleN2kMessage(n2kMsg,N2K_CHANNEL_ID);
});
+ pN2kDeviceList = new tN2kDeviceList(&NMEA2000);
NMEA2000.Open();
logger.logDebug(GwLog::LOG,"starting addon tasks");
logger.flush();

View File

@@ -16,6 +16,8 @@ board_build.variants_dir = variants
board = obp60_s3_n16r8 #ESP32-S3 N16R8, 16MB flash, 8MB PSRAM, production series 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_8MB.csv #ESP32-S3 N8, 8MB flash
board_build.partitions = default_16MB.csv #ESP32-S3 N16, 16MB 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 framework = arduino
lib_deps = lib_deps =
${basedeps.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_GYE042A87 #alternativ E-Ink display from Genyo Optical, R10 2.2 ohm - medium
# -D DISPLAY_SE0420NQ04 #alternativ E-Ink display from SID Technology, R10 2.2 ohm - bad (burn in effects) # -D DISPLAY_SE0420NQ04 #alternativ E-Ink display from SID Technology, R10 2.2 ohm - bad (burn in effects)
# -D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good # -D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
# -D ENABLE_PATCHES #enable patching of gateway code
${env.build_flags} ${env.build_flags}
#CONFIG_ESP_TASK_WDT_TIMEOUT_S = 10 #Task Watchdog timeout period (seconds) [1...60] 5 default #CONFIG_ESP_TASK_WDT_TIMEOUT_S = 10 #Task Watchdog timeout period (seconds) [1...60] 5 default
upload_port = /dev/ttyACM0 #OBP60 download via USB-C direct upload_port = /dev/ttyACM0 #OBP60 download via USB-C direct
@@ -68,7 +71,8 @@ platform = espressif32@6.8.1
board_build.variants_dir = variants board_build.variants_dir = variants
board = obp40_s3_n8r8 #ESP32-S3 N8R8, 8MB flash, 8MB PSRAM, OBP60 clone (CrowPanel 4.2) 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 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 framework = arduino
lib_deps = lib_deps =
${basedeps.lib_deps} ${basedeps.lib_deps}
@@ -105,6 +109,7 @@ build_flags=
#-D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good #-D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good
-D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh -D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh
-D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors -D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors
#-D ENABLE_PATCHES #enable patching of gateway code
${env.build_flags} ${env.build_flags}
upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter
upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27 upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27

View File

@@ -8,6 +8,6 @@
# Install tools # Install tools
echo "Installing tools" echo "Installing tools"
cd /workspace/esp32-nmea2000 cd /workspaces/esp32-nmea2000
pip3 install -U esptool pip3 install -U esptool
pip3 install platformio pip3 install platformio

View File

@@ -3,7 +3,7 @@
This code is free software; you can redistribute it and/or This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 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, This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU

View File

@@ -3,7 +3,7 @@
This code is free software; you can redistribute it and/or This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 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, This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
@@ -93,6 +93,7 @@ class GwSensorConfig{
} }
bool readConfig(T* s,GwConfigHandler *cfg){ bool readConfig(T* s,GwConfigHandler *cfg){
if (s == nullptr) return false; if (s == nullptr) return false;
if (prefix != s->prefix) return false;
configReader(s,cfg); configReader(s,cfg);
return s->ok; return s->ok;
} }

View File

@@ -66,18 +66,8 @@ GwSerial::~GwSerial()
if (lock != nullptr) vSemaphoreDelete(lock); if (lock != nullptr) vSemaphoreDelete(lock);
} }
String GwSerial::getMode(){ int GwSerial::getType() {
switch (type){ return 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";
} }
bool GwSerial::isInitialized() { return initialized; } bool GwSerial::isInitialized() { return initialized; }

View File

@@ -42,7 +42,7 @@ class GwSerial : public GwChannelInterface{
virtual Stream *getStream(bool partialWrites); virtual Stream *getStream(bool partialWrites);
bool getAvailableWrite(){return availableWrite;} 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 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; friend GwSerialStream;
}; };
@@ -122,6 +122,7 @@ template<typename T>
setError(serial,logger); setError(serial,logger);
}; };
}; };

View File

@@ -6,7 +6,6 @@
#include "GwSocketHelper.h" #include "GwSocketHelper.h"
#include "GWWifi.h" #include "GWWifi.h"
GwUdpReader::GwUdpReader(const GwConfigHandler *config, GwLog *logger, int minId) GwUdpReader::GwUdpReader(const GwConfigHandler *config, GwLog *logger, int minId)
{ {
this->config = config; this->config = config;

View File

@@ -3,7 +3,7 @@
This code is free software; you can redistribute it and/or This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 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, This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU

View File

@@ -3,7 +3,7 @@
This code is free software; you can redistribute it and/or This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 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, This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU

View File

@@ -3,7 +3,7 @@
This code is free software; you can redistribute it and/or This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 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, This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU

View File

@@ -3,7 +3,7 @@
This code is free software; you can redistribute it and/or This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 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, This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU

View File

@@ -3,7 +3,7 @@
This code is free software; you can redistribute it and/or This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either 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, This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU

View File

@@ -431,7 +431,8 @@ GwXDRFoundMapping GwXDRMappings::getMapping(String xName,String xType,String xUn
} }
return selectMapping(&(it->second),instance,n183Key.c_str()); 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); unsigned long n2kKey=GwXDRMappingDef::n2kKey(category,selector,field);
auto it=n2kMap.find(n2kKey); auto it=n2kMap.find(n2kKey);
if (it == n2kMap.end()){ if (it == n2kMap.end()){

View File

@@ -244,7 +244,7 @@ class GwXDRMappings{
//get the mappings //get the mappings
//the returned mapping will exactly contain one mapping def //the returned mapping will exactly contain one mapping def
GwXDRFoundMapping getMapping(String xName,String xType,String xUnit); 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); String getXdrEntry(String mapping, double value,int instance=0);
const char * getUnMapped(); const char * getUnMapped();
const GwXDRType * findType(const String &typeString, const String &unitString) const; const GwXDRType * findType(const String &typeString, const String &unitString) const;

View File

@@ -18,7 +18,7 @@ extra_configs=
[basedeps] [basedeps]
lib_deps = lib_deps =
ttlappalainen/NMEA2000-library @ 4.22.0 ttlappalainen_NMEA2000=https://github.com/wellenvogel/NMEA2000.git#20251126
ttlappalainen/NMEA0183 @ 1.10.1 ttlappalainen/NMEA0183 @ 1.10.1
ArduinoJson @ 6.18.5 ArduinoJson @ 6.18.5
AsyncTCP-esphome @ 2.0.1 AsyncTCP-esphome @ 2.0.1
@@ -29,6 +29,18 @@ lib_deps =
WiFi WiFi
Update 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] [env]
platform = espressif32 @ 6.8.1 platform = espressif32 @ 6.8.1
framework = arduino framework = arduino
@@ -67,6 +79,17 @@ lib_deps =
adafruit/Adafruit BusIO @ 1.14.5 adafruit/Adafruit BusIO @ 1.14.5
adafruit/Adafruit Unified Sensor @ 1.1.13 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] [env:m5stack-atom]
board = m5stack-atom board = m5stack-atom
lib_deps = ${env.lib_deps} lib_deps = ${env.lib_deps}
@@ -185,3 +208,14 @@ build_flags =
${env.build_flags} ${env.build_flags}
upload_port = /dev/esp32 upload_port = /dev/esp32
upload_protocol = esptool 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

Some files were not shown because too many files have changed in this diff Show More